From e2a04311a285713697a3a9c18dba5b2f9dac5f71 Mon Sep 17 00:00:00 2001 From: Atemu Date: Thu, 11 Jul 2024 07:48:10 +0200 Subject: [PATCH 1/3] nixos/postgresql: add ensureUsers.*.passwordFile option Previously you had to roll your own systemd service that runs some SQL and risk inadvertently exposing your password to the Nix store by doing it wrong. This provides a standard method to set up user passwords via password files which can safely be deployed via secrets management solutions for those who care about security or alternatively also simply generated for those who don't. Adapted from https://discourse.nixos.org/t/assign-password-to-postgres-user-declaratively/9726/3 Co-authored-by: Giovanni d'Amelio --- .../modules/services/databases/postgresql.nix | 27 +++++++++++++++++++ nixos/tests/postgresql.nix | 17 ++++++++++++ 2 files changed, 44 insertions(+) diff --git a/nixos/modules/services/databases/postgresql.nix b/nixos/modules/services/databases/postgresql.nix index 8a9d8c210b34d..a383a757bcf65 100644 --- a/nixos/modules/services/databases/postgresql.nix +++ b/nixos/modules/services/databases/postgresql.nix @@ -189,6 +189,19 @@ in ''; }; + passwordFile = mkOption { + type = with lib.types; nullOr path; + example = "/run/keys/db_user_pw"; + default = null; + description = '' + The path to a file containing a password that is to be set as the user's PASSWORD field. + + A path must be given because Nix strings would be exposed in Nix store through the .drv files. + + Note that the contents of this file are stripped of newlines and that surrounding whitespace is trimmed. + ''; + }; + ensureClauses = mkOption { description = '' An attrset of clauses to grant to the user. Under the hood this uses the @@ -586,6 +599,19 @@ in user.ensureDBOwnership ''$PSQL -tAc 'ALTER DATABASE "${user.name}" OWNER TO "${user.name}";' ''; + setPW = + pkgs.writeText + "set-pw.sql" + # You can't use prepared statements for ALTER ROLE, we must wrap it in a procedure + '' + DO $$ + DECLARE password TEXT; + BEGIN + password := trim(both from replace(pg_read_file('${user.passwordFile}'), E'\n', ''')); + EXECUTE 'ALTER ROLE "${user.name}" WITH PASSWORD' || quote_literal(password); + END $$; + ''; + filteredClauses = filterAttrs (name: value: value != null) user.ensureClauses; clauseSqlStatements = attrValues (mapAttrs (n: v: if v then n else "no${n}") filteredClauses); @@ -593,6 +619,7 @@ in userClauses = ''$PSQL -tAc 'ALTER ROLE "${user.name}" ${concatStringsSep " " clauseSqlStatements}' ''; in '' $PSQL -tAc "SELECT 1 FROM pg_roles WHERE rolname='${user.name}'" | grep -q 1 || $PSQL -tAc 'CREATE USER "${user.name}"' + ${lib.optionalString (user.passwordFile != null) "$PSQL -tAf ${setPW}"} ${userClauses} ${dbOwnershipStmt} diff --git a/nixos/tests/postgresql.nix b/nixos/tests/postgresql.nix index c0dd24cf6ad2e..1aff4b76d2a42 100644 --- a/nixos/tests/postgresql.nix +++ b/nixos/tests/postgresql.nix @@ -32,6 +32,15 @@ let services.postgresql = { enable = true; package = postgresql-package; + enableTCPIP = true; + ensureDatabases = [ "pw-user" ]; + ensureUsers = [ + { + name = "pw-user"; + passwordFile = pkgs.writeText "password" "password"; + ensureDBOwnership = true; + } + ]; }; services.postgresqlBackup = { @@ -71,6 +80,14 @@ let machine.fail(check_count("SELECT * FROM sth;", 4)) machine.succeed(check_count("SELECT xpath('/test/text()', doc) FROM xmltest;", 1)) + with subtest("Connection with user password works correctly"): + machine.succeed( + "sudo -u postgres PGPASSWORD=password psql -h localhost -U pw-user -tc '\c'" + ) + machine.fail( + "sudo -u postgres PGPASSWORD=wrong-password psql -h localhost -U pw-user -tc '\c'" + ) + with subtest("Backup service works"): machine.succeed( "systemctl start ${backupService}.service", From 3757773806e1535766dfd719b6df754775192c7c Mon Sep 17 00:00:00 2001 From: Atemu Date: Sat, 20 Jul 2024 00:31:37 +0200 Subject: [PATCH 2/3] nixos/postgresql: mitigate adding the passwordFile to the Nix store I tested it and this prevents trivial cases of adding the referenced file to the Nix store. --- nixos/modules/services/databases/postgresql.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/nixos/modules/services/databases/postgresql.nix b/nixos/modules/services/databases/postgresql.nix index a383a757bcf65..5efc21e3bf1e3 100644 --- a/nixos/modules/services/databases/postgresql.nix +++ b/nixos/modules/services/databases/postgresql.nix @@ -191,6 +191,7 @@ in passwordFile = mkOption { type = with lib.types; nullOr path; + apply = toString; example = "/run/keys/db_user_pw"; default = null; description = '' From c56832f8a7abda575ac6335202db6b5c36740390 Mon Sep 17 00:00:00 2001 From: Atemu Date: Sat, 20 Jul 2024 22:02:22 +0200 Subject: [PATCH 3/3] nixos/postgresql: enforce passing the path as a string The path type was used for its check but we don't actually want to allow path literals here to begin with, so let's use str instead but keep the check. Suggested-by: Maximilian Bosch --- nixos/modules/services/databases/postgresql.nix | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/nixos/modules/services/databases/postgresql.nix b/nixos/modules/services/databases/postgresql.nix index 5efc21e3bf1e3..30542995a09e9 100644 --- a/nixos/modules/services/databases/postgresql.nix +++ b/nixos/modules/services/databases/postgresql.nix @@ -190,14 +190,18 @@ in }; passwordFile = mkOption { - type = with lib.types; nullOr path; - apply = toString; + type = with lib.types; nullOr (str // { + # We don't want users to be able to pass a path literal here but + # it should look like a path. + check = it: lib.isString it && lib.types.path.check it; + }); example = "/run/keys/db_user_pw"; default = null; description = '' The path to a file containing a password that is to be set as the user's PASSWORD field. - A path must be given because Nix strings would be exposed in Nix store through the .drv files. + A path must be given because Nix strings would be exposed in Nix store through the .drv files. You must + not pass a path literal either for the same reason. The path must therefore be represented as a string. Note that the contents of this file are stripped of newlines and that surrounding whitespace is trimmed. '';