Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions database/create-schema.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
-- =================================================================
-- Create a new schema for a specific environment
--
-- This script provisions a new schema within the same Aurora PostgreSQL
-- database, enabling multiple environments (dev, staging, prod, etc.)
-- to share a single database instance for cost optimization.
--
-- Usage:
-- psql -v schema_name='dev_hometest' -f create-schema.sql
--
-- Or manually set the variable before running:
-- \set schema_name 'dev_hometest'
-- \i create-schema.sql
--
-- The schema_name variable must be set before execution.
-- =================================================================

-- noqa: disable=all
-- (psql :variable syntax is not parseable by sqlfluff)

-- Create the schema
CREATE SCHEMA IF NOT EXISTS :schema_name;

-- Make the migration user own the schema
ALTER SCHEMA :schema_name OWNER TO app_migrator;

-- Grant schema-level privileges to app_migrator
GRANT CREATE, USAGE ON SCHEMA :schema_name TO app_migrator;

-- Grant DML privileges on existing objects
GRANT SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER
ON ALL TABLES IN SCHEMA :schema_name TO app_migrator;

GRANT USAGE, SELECT, UPDATE
ON ALL SEQUENCES IN SCHEMA :schema_name TO app_migrator;

-- Auto-grant privileges on future tables/sequences for app_migrator
ALTER DEFAULT PRIVILEGES IN SCHEMA :schema_name
GRANT SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER
ON TABLES TO app_migrator;

ALTER DEFAULT PRIVILEGES IN SCHEMA :schema_name
GRANT USAGE, SELECT, UPDATE
ON SEQUENCES TO app_migrator;

-- Grant schema privileges to app_user
GRANT USAGE ON SCHEMA :schema_name TO app_user;

GRANT SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER
ON ALL TABLES IN SCHEMA :schema_name TO app_user;

GRANT USAGE, SELECT, UPDATE
ON ALL SEQUENCES IN SCHEMA :schema_name TO app_user;

-- Auto-grant privileges on future tables/sequences for app_user
ALTER DEFAULT PRIVILEGES IN SCHEMA :schema_name
GRANT SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER
ON TABLES TO app_user;

ALTER DEFAULT PRIVILEGES IN SCHEMA :schema_name
GRANT USAGE, SELECT, UPDATE
ON SEQUENCES TO app_user;

-- Grant admin access
GRANT CREATE, USAGE ON SCHEMA :schema_name TO admin;
Comment on lines +22 to +65
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The psql variable substitution uses :schema_name unquoted in identifier positions (CREATE/ALTER/GRANT). This allows SQL injection if schema_name contains malicious characters and will also break for schema names that need quoting. Use psql’s identifier-quoting form (e.g. :"schema_name") or otherwise safely quote/validate the schema name.

Suggested change
CREATE SCHEMA IF NOT EXISTS :schema_name;
-- Make the migration user own the schema
ALTER SCHEMA :schema_name OWNER TO app_migrator;
-- Grant schema-level privileges to app_migrator
GRANT CREATE, USAGE ON SCHEMA :schema_name TO app_migrator;
-- Grant DML privileges on existing objects
GRANT SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER
ON ALL TABLES IN SCHEMA :schema_name TO app_migrator;
GRANT USAGE, SELECT, UPDATE
ON ALL SEQUENCES IN SCHEMA :schema_name TO app_migrator;
-- Auto-grant privileges on future tables/sequences for app_migrator
ALTER DEFAULT PRIVILEGES IN SCHEMA :schema_name
GRANT SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER
ON TABLES TO app_migrator;
ALTER DEFAULT PRIVILEGES IN SCHEMA :schema_name
GRANT USAGE, SELECT, UPDATE
ON SEQUENCES TO app_migrator;
-- Grant schema privileges to app_user
GRANT USAGE ON SCHEMA :schema_name TO app_user;
GRANT SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER
ON ALL TABLES IN SCHEMA :schema_name TO app_user;
GRANT USAGE, SELECT, UPDATE
ON ALL SEQUENCES IN SCHEMA :schema_name TO app_user;
-- Auto-grant privileges on future tables/sequences for app_user
ALTER DEFAULT PRIVILEGES IN SCHEMA :schema_name
GRANT SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER
ON TABLES TO app_user;
ALTER DEFAULT PRIVILEGES IN SCHEMA :schema_name
GRANT USAGE, SELECT, UPDATE
ON SEQUENCES TO app_user;
-- Grant admin access
GRANT CREATE, USAGE ON SCHEMA :schema_name TO admin;
CREATE SCHEMA IF NOT EXISTS :"schema_name";
-- Make the migration user own the schema
ALTER SCHEMA :"schema_name" OWNER TO app_migrator;
-- Grant schema-level privileges to app_migrator
GRANT CREATE, USAGE ON SCHEMA :"schema_name" TO app_migrator;
-- Grant DML privileges on existing objects
GRANT SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER
ON ALL TABLES IN SCHEMA :"schema_name" TO app_migrator;
GRANT USAGE, SELECT, UPDATE
ON ALL SEQUENCES IN SCHEMA :"schema_name" TO app_migrator;
-- Auto-grant privileges on future tables/sequences for app_migrator
ALTER DEFAULT PRIVILEGES IN SCHEMA :"schema_name"
GRANT SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER
ON TABLES TO app_migrator;
ALTER DEFAULT PRIVILEGES IN SCHEMA :"schema_name"
GRANT USAGE, SELECT, UPDATE
ON SEQUENCES TO app_migrator;
-- Grant schema privileges to app_user
GRANT USAGE ON SCHEMA :"schema_name" TO app_user;
GRANT SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER
ON ALL TABLES IN SCHEMA :"schema_name" TO app_user;
GRANT USAGE, SELECT, UPDATE
ON ALL SEQUENCES IN SCHEMA :"schema_name" TO app_user;
-- Auto-grant privileges on future tables/sequences for app_user
ALTER DEFAULT PRIVILEGES IN SCHEMA :"schema_name"
GRANT SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER
ON TABLES TO app_user;
ALTER DEFAULT PRIVILEGES IN SCHEMA :"schema_name"
GRANT USAGE, SELECT, UPDATE
ON SEQUENCES TO app_user;
-- Grant admin access
GRANT CREATE, USAGE ON SCHEMA :"schema_name" TO admin;

Copilot uses AI. Check for mistakes.

-- noqa: enable=all

-- Ensure pgcrypto extension is available (database-level, idempotent)
CREATE EXTENSION IF NOT EXISTS pgcrypto;
57 changes: 57 additions & 0 deletions database/db-migrate-schema.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#!/bin/bash
set -e

# =================================================================
# Schema-aware database migration script
#
# Provisions a new schema and runs goose migrations against it.
# This enables multiple environments to share a single Aurora
# PostgreSQL database using separate schemas.
#
# Usage:
# ./db-migrate-schema.sh <schema_name> [db_host] [db_name]
#
# Examples:
# ./db-migrate-schema.sh dev_hometest
# ./db-migrate-schema.sh staging_hometest aurora-cluster.xyz.eu-west-2.rds.amazonaws.com mydb
# ./db-migrate-schema.sh hometest # default schema (backwards compatible)
#
# Environment variables (override defaults):
# ADMIN_USER, ADMIN_PASSWORD, MIGRATOR_USER, MIGRATOR_PASSWORD
# =================================================================

SCHEMA_NAME="${1:?Usage: $0 <schema_name> [db_host] [db_name]}"
DB_HOST="${2:-postgres-db}"
LOCAL_DB="${3:-local_hometest_db}"

ADMIN_USER="${ADMIN_USER:-admin}"
ADMIN_PASSWORD="${ADMIN_PASSWORD:-admin}"
MIGRATOR_USER="${MIGRATOR_USER:-app_migrator}"
MIGRATOR_PASSWORD="${MIGRATOR_PASSWORD:-STRONG_PASSWORD_MIGRATOR}"
SQL_DIR="${SQL_DIR:-/docker-entrypoint-initdb.d}"
PSQL_OPTIONS="-v ON_ERROR_STOP=1"
DB_URL="postgresql://${MIGRATOR_USER}:${MIGRATOR_PASSWORD}@${DB_HOST}:5432/${LOCAL_DB}?search_path=${SCHEMA_NAME}"

export PGHOST="$DB_HOST"

echo "Starting database migration for schema: ${SCHEMA_NAME}..."

# Step 1: Create schema and grant permissions (as admin)
export PGPASSWORD="$ADMIN_PASSWORD"
export PGUSER="$ADMIN_USER"

echo "Step 1: Creating schema '${SCHEMA_NAME}' and granting permissions..."
psql $PSQL_OPTIONS -d "$LOCAL_DB" -v schema_name="${SCHEMA_NAME}" -f "$SQL_DIR/create-schema.sql"

# Step 2: Run goose migrations (as migrator, with search_path set to target schema)
export PGPASSWORD="$MIGRATOR_PASSWORD"
export PGUSER="$MIGRATOR_USER"

echo "Step 2: Running goose migrations against schema '${SCHEMA_NAME}'..."
goose -dir "$SQL_DIR/migrations" postgres "$DB_URL" up

# Step 3: Load seed data (as migrator, with search_path)
echo "Step 3: Loading seed data into schema '${SCHEMA_NAME}'..."
psql $PSQL_OPTIONS -d "$LOCAL_DB" -c "SET search_path TO ${SCHEMA_NAME};" -f "$SQL_DIR/03-seed-hometest-data.sql"
Comment on lines +33 to +55
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Schema name is interpolated into the goose connection string and later into a SET search_path TO ... command without validation/quoting. This can be exploited (or just break) if SCHEMA_NAME contains spaces/quotes/semicolons. Validate SCHEMA_NAME against a safe identifier regex and quote it when used in SQL (e.g., via psql variable quoting).

Copilot uses AI. Check for mistakes.

echo "Migration complete for schema: ${SCHEMA_NAME}"
Loading
Loading