diff --git a/README.md b/README.md index dac8e59a..aaf1f685 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ This is a [BOSH](https://www.bosh.io) release for [PostgreSQL](https://www.postg * [Contributing](#contributing) * [Known Limitation](#known-limitation) * [Upgrading](#upgrading) + * [Migrating password authentication from MD5 to SCRAM-SHA-256](#migrating-password-authentication-from-md5-to-scram-sha-256) * [CI](#ci) ## Deploying @@ -194,6 +195,48 @@ Even if you deploy more instances, no replication is configured. Refer to [versions.yml](versions.yml) in order to assess if a postgres-release version upgrades the PostgreSQL version. +### Migrating password authentication from MD5 to SCRAM-SHA-256 + +> **⚠ Upcoming breaking change** +> +> The current default for `databases.password_authentication_algorithm` is `md5` for backwards +> compatibility. A future release will change the default to `scram-sha-256`, which is the modern, +> cryptographically stronger algorithm recommended by the PostgreSQL project. + +**Why this matters** + +`databases.password_authentication_algorithm` controls both the `password_encryption` setting in +`postgresql.conf` and the auth-method column in `pg_hba.conf`. If the value is changed to +`scram-sha-256` while existing role passwords are still stored as MD5 hashes, those roles will +**immediately fail to authenticate** — clients will receive an error until every affected role's +password has been reset so PostgreSQL can re-hash it with SCRAM. + +**Migration procedure** (perform these steps in order) + +1. Set the property in your deployment manifest: + ```yaml + databases.password_authentication_algorithm: scram-sha-256 + ``` +2. Re-deploy so the new `postgresql.conf` and `pg_hba.conf` are applied: + ```bash + bosh -d DEPLOYMENT_NAME deploy MANIFEST_PATH + ``` +3. Reset **every** role password so PostgreSQL re-hashes it with SCRAM. + Connect as a superuser and run for each role: + ```sql + ALTER ROLE WITH PASSWORD ''; + ``` + > Roles whose passwords are still stored as MD5 hashes cannot authenticate via SCRAM + > and will be locked out until this step is completed. +4. Verify that no MD5 hashes remain: + ```sql + SELECT rolname, rolpassword + FROM pg_authid + WHERE rolpassword NOT LIKE 'SCRAM-SHA-256$%' + AND rolpassword IS NOT NULL; + ``` + The query must return **zero rows** before the migration is complete. + ### Upgrade Test Policy The maintainers of the postgres-release test the following upgrade paths: diff --git a/jobs/postgres/spec b/jobs/postgres/spec index 8440d47b..bbab6cf9 100644 --- a/jobs/postgres/spec +++ b/jobs/postgres/spec @@ -68,6 +68,30 @@ properties: databases.max_connections: description: "Maximum number of database connections" default: 500 + databases.password_authentication_algorithm: + description: | + Defines the password hashing algorithm used by PostgreSQL for both `password_encryption` + (postgresql.conf) and the auth-method column in pg_hba.conf. + Accepted values: "md5" or "scram-sha-256". + + Default is "md5" for backwards compatibility with existing deployments. + + *** IMPORTANT – BREAKING CHANGE NOTICE *** + A future release will change the default to "scram-sha-256", which is the modern, + cryptographically stronger algorithm recommended by the PostgreSQL project. + + If you want to migrate now, or when the default changes, you MUST: + 1. Set this property to "scram-sha-256" in your deployment manifest. + 2. Restart / re-deploy PostgreSQL so the new postgresql.conf and pg_hba.conf are applied. + 3. Reset every role password so PostgreSQL re-hashes it with SCRAM: + ALTER ROLE WITH PASSWORD ''; + Roles whose passwords are still stored as MD5 hashes cannot authenticate + via SCRAM and will be locked out until their password is reset. + 4. Verify with: + SELECT rolname, rolpassword FROM pg_authid + WHERE rolpassword NOT LIKE 'SCRAM-SHA-256$%' AND rolpassword IS NOT NULL; + The query should return zero rows when the migration is complete. + default: "md5" databases.log_line_prefix: description: "The postgres `printf` style string that is output at the beginning of each log line" default: "%m: " diff --git a/jobs/postgres/templates/pg_hba.conf.erb b/jobs/postgres/templates/pg_hba.conf.erb index 5210295a..309af396 100644 --- a/jobs/postgres/templates/pg_hba.conf.erb +++ b/jobs/postgres/templates/pg_hba.conf.erb @@ -1,8 +1,16 @@ +<% + _algo = p("databases.password_authentication_algorithm") + unless ["md5", "scram-sha-256"].include?(_algo) + raise "databases.password_authentication_algorithm must be 'md5' or 'scram-sha-256', got: '#{_algo}'. " \ + "Note: pg_hba.conf auth-methods such as 'password', 'peer', or 'trust' must be " \ + "configured through other properties." + end +%> local all vcap trust host all vcap 127.0.0.1/32 trust host all vcap ::1/128 trust <% if !p("databases.trust_local_connections").nil? && !p("databases.trust_local_connections") %> -local all all md5 +local all all <%= p("databases.password_authentication_algorithm") %> <% else %> local all all trust host all all 127.0.0.1/32 trust @@ -18,4 +26,4 @@ host all all ::1/128 trust line %> <% end %> -host all all 0.0.0.0/0 md5 +host all all 0.0.0.0/0 <%= p("databases.password_authentication_algorithm") %> diff --git a/jobs/postgres/templates/postgresql.conf.erb b/jobs/postgres/templates/postgresql.conf.erb index eea87418..5f247605 100644 --- a/jobs/postgres/templates/postgresql.conf.erb +++ b/jobs/postgres/templates/postgresql.conf.erb @@ -16,6 +16,12 @@ p("databases.roles", []).each do |role| end end end + +_algo = p("databases.password_authentication_algorithm") +unless ["md5", "scram-sha-256"].include?(_algo) + raise "databases.password_authentication_algorithm must be 'md5' or 'scram-sha-256', got: '#{_algo}'. " \ + "These are the only values accepted by PostgreSQL's password_encryption parameter." +end %> # Control the available listen_addresses via # deployment manifest - add networks to the job @@ -35,6 +41,7 @@ end listen_addresses = '0.0.0.0' port = <%= p("databases.port") %> max_connections = <%= p("databases.max_connections") %> +password_encryption = <%= p("databases.password_authentication_algorithm") %> external_pid_file = '/var/vcap/sys/run/postgres/postgres.pid' authentication_timeout = 1min