1
0
Fork 0

add postgresql vm test that runs in CI (#19)

Fixes #14 

The tests actually showed a flaw in the implementation, we needed
"password" and not "trust" in the auth file.

Also, having the port defined at the same time as enabling listening for
TCP/IP connection made no sense.
This commit is contained in:
Pierre Penninckx 2023-11-23 01:03:33 -08:00 committed by GitHub
parent 9e46d93e08
commit 52b9233a6c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 220 additions and 36 deletions

View file

@ -10,4 +10,9 @@ jobs:
- uses: cachix/install-nix-action@v22 - uses: cachix/install-nix-action@v22
with: with:
github_access_token: ${{ secrets.GITHUB_TOKEN }} github_access_token: ${{ secrets.GITHUB_TOKEN }}
- run: nix flake check extra_nix_config: "system-features = nixos-test benchmark big-parallel kvm"
- uses: cachix/cachix-action@v12
with:
name: selfhostblocks
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
- run: nix flake check -L

View file

@ -692,12 +692,15 @@ Run all tests:
```bash ```bash
$ nix build .#checks.${system}.all $ nix build .#checks.${system}.all
# or
$ nix flake check
``` ```
Run one group of tests: Run one group of tests:
```bash ```bash
$ nix build .#checks.${system}.module $ nix build .#checks.${system}.modules
$ nix build .#checks.${system}.vm_postgresql_peerAuth
``` ```
### Deploy using colmena ### Deploy using colmena
@ -798,6 +801,11 @@ $ nix run nixpkgs#openssl -- rand -hex 64
## Links that helped ## Links that helped
While creating NixOS tests:
- https://www.haskellforall.com/2020/11/how-to-use-nixos-for-lightweight.html
- https://nixos.org/manual/nixos/stable/index.html#sec-nixos-tests
While creating an XML config generator for Radarr: While creating an XML config generator for Radarr:
- https://stackoverflow.com/questions/4906977/how-can-i-access-environment-variables-in-python - https://stackoverflow.com/questions/4906977/how-can-i-access-environment-variables-in-python

View file

@ -8,7 +8,7 @@
flake-utils.url = "github:numtide/flake-utils"; flake-utils.url = "github:numtide/flake-utils";
}; };
outputs = inputs@{ self, nixpkgs, sops-nix, nix-flake-tests, flake-utils, ... }: flake-utils.lib.eachDefaultSystem (system: outputs = { nixpkgs, nix-flake-tests, flake-utils, ... }: flake-utils.lib.eachDefaultSystem (system:
let let
pkgs = nixpkgs.legacyPackages.${system}; pkgs = nixpkgs.legacyPackages.${system};
in in
@ -45,7 +45,11 @@
}) files; }) files;
mergeTests = pkgs.lib.lists.foldl pkgs.lib.trivial.mergeAttrs {}; mergeTests = pkgs.lib.lists.foldl pkgs.lib.trivial.mergeAttrs {};
in rec {
flattenAttrs = root: attrset: pkgs.lib.attrsets.foldlAttrs (acc: name: value: acc // {
"${root}_${name}" = value;
}) {} attrset;
in (rec {
all = mergeTests [ all = mergeTests [
modules modules
]; ];
@ -59,7 +63,9 @@
./test/modules/postgresql.nix ./test/modules/postgresql.nix
]); ]);
}; };
}; }
// (flattenAttrs "vm_postgresql" (import ./test/vm/postgresql.nix {inherit pkgs; inherit (pkgs) lib;}))
);
} }
); );
} }

View file

@ -279,7 +279,7 @@ in
user = autheliaCfg.user; user = autheliaCfg.user;
}; };
shb.postgresql.passwords = [ shb.postgresql.ensures = [
{ {
username = autheliaCfg.user; username = autheliaCfg.user;
database = autheliaCfg.user; database = autheliaCfg.user;

View file

@ -36,7 +36,7 @@ in
}; };
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
shb.postgresql.passwords = [ shb.postgresql.ensures = [
{ {
username = "grafana"; username = "grafana";
database = "grafana"; database = "grafana";

View file

@ -14,13 +14,13 @@ in
See https://www.postgresql.org/docs/current/pgstatstatements.html''; See https://www.postgresql.org/docs/current/pgstatstatements.html'';
default = false; default = false;
}; };
tcpIPPort = lib.mkOption { enableTCPIP = lib.mkOption {
type = lib.types.nullOr lib.types.port; type = lib.types.bool;
description = "Enable TCP/IP connection on given port."; description = "Enable TCP/IP connection on given port.";
default = null; default = false;
}; };
passwords = lib.mkOption { ensures = lib.mkOption {
type = lib.types.listOf (lib.types.submodule { type = lib.types.listOf (lib.types.submodule {
options = { options = {
username = lib.mkOption { username = lib.mkOption {
@ -35,7 +35,7 @@ in
passwordFile = lib.mkOption { passwordFile = lib.mkOption {
type = lib.types.nullOr lib.types.str; type = lib.types.nullOr lib.types.str;
description = "Optional password file for the postgres user."; description = "Optional password file for the postgres user. If not given, only peer auth is accepted for this user, otherwise password auth is allowed.";
default = null; default = null;
example = "/run/secrets/postgresql/password"; example = "/run/secrets/postgresql/password";
}; };
@ -47,22 +47,21 @@ in
config = config =
let let
tcpConfig = port: { tcpConfig = {
services.postgresql.enableTCPIP = true; services.postgresql.enableTCPIP = true;
services.postgresql.port = port;
services.postgresql.authentication = lib.mkOverride 10 '' services.postgresql.authentication = lib.mkOverride 10 ''
#type database DBuser origin-address auth-method #type database DBuser origin-address auth-method
local all all peer local all all peer
# ipv4 # ipv4
host all all 127.0.0.1/32 trust host all all 127.0.0.1/32 password
# ipv6 # ipv6
host all all ::1/128 trust host all all ::1/128 password
''; '';
}; };
dbConfig = passwordCfgs: { dbConfig = ensureCfgs: {
services.postgresql.enable = lib.mkDefault ((builtins.length passwordCfgs) > 0); services.postgresql.enable = lib.mkDefault ((builtins.length ensureCfgs) > 0);
services.postgresql.ensureDatabases = map ({ database, ... }: database) passwordCfgs; services.postgresql.ensureDatabases = map ({ database, ... }: database) ensureCfgs;
services.postgresql.ensureUsers = map ({ username, database, ... }: { services.postgresql.ensureUsers = map ({ username, database, ... }: {
name = username; name = username;
ensurePermissions = { ensurePermissions = {
@ -71,10 +70,10 @@ in
ensureClauses = { ensureClauses = {
"login" = true; "login" = true;
}; };
}) passwordCfgs; }) ensureCfgs;
}; };
pwdConfig = passwordCfgs: { pwdConfig = ensureCfgs: {
systemd.services.postgresql.postStart = systemd.services.postgresql.postStart =
let let
prefix = '' prefix = ''
@ -91,7 +90,7 @@ in
password := trim(both from replace(pg_read_file('${passwordFile}'), E'\n', ''')); password := trim(both from replace(pg_read_file('${passwordFile}'), E'\n', '''));
EXECUTE format('ALTER ROLE ${username} WITH PASSWORD '''%s''';', password); EXECUTE format('ALTER ROLE ${username} WITH PASSWORD '''%s''';', password);
''; '';
cfgsWithPasswords = builtins.filter (cfg: cfg.passwordFile != null) passwordCfgs; cfgsWithPasswords = builtins.filter (cfg: cfg.passwordFile != null) ensureCfgs;
in in
if (builtins.length cfgsWithPasswords) == 0 then "" else if (builtins.length cfgsWithPasswords) == 0 then "" else
prefix + (lib.concatStrings (map exec cfgsWithPasswords)) + suffix; prefix + (lib.concatStrings (map exec cfgsWithPasswords)) + suffix;
@ -103,9 +102,9 @@ in
in in
lib.mkMerge ( lib.mkMerge (
[ [
(dbConfig cfg.passwords) (dbConfig cfg.ensures)
(pwdConfig cfg.passwords) (pwdConfig cfg.ensures)
(lib.mkIf (!(isNull cfg.tcpIPPort)) (tcpConfig cfg.tcpIPPort)) (lib.mkIf cfg.enableTCPIP tcpConfig)
(debugConfig cfg.debug) (debugConfig cfg.debug)
] ]
); );

View file

@ -183,8 +183,8 @@ in
} }
]; ];
shb.postgresql.tcpIPPort= 5432; shb.postgresql.enableTCPIP = true;
shb.postgresql.passwords = [ shb.postgresql.ensures = [
{ {
username = "vaultwarden"; username = "vaultwarden";
database = "vaultwarden"; database = "vaultwarden";

View file

@ -68,7 +68,7 @@ in
systemd.services.postgresql.postStart = ""; systemd.services.postgresql.postStart = "";
}; };
expr = testConfig { expr = testConfig {
shb.postgresql.passwords = [ shb.postgresql.ensures = [
{ {
username = "myuser"; username = "myuser";
database = "mydatabase"; database = "mydatabase";
@ -104,7 +104,7 @@ in
''; '';
}; };
expr = testConfig { expr = testConfig {
shb.postgresql.passwords = [ shb.postgresql.ensures = [
{ {
username = "myuser"; username = "myuser";
database = "mydatabase"; database = "mydatabase";
@ -143,7 +143,7 @@ in
systemd.services.postgresql.postStart = ""; systemd.services.postgresql.postStart = "";
}; };
expr = testConfig { expr = testConfig {
shb.postgresql.passwords = [ shb.postgresql.ensures = [
{ {
username = "user1"; username = "user1";
database = "db1"; database = "db1";
@ -196,7 +196,7 @@ in
''; '';
}; };
expr = testConfig { expr = testConfig {
shb.postgresql.passwords = [ shb.postgresql.ensures = [
{ {
username = "user1"; username = "user1";
database = "db1"; database = "db1";
@ -249,7 +249,7 @@ in
''; '';
}; };
expr = testConfig { expr = testConfig {
shb.postgresql.passwords = [ shb.postgresql.ensures = [
{ {
username = "user1"; username = "user1";
database = "db1"; database = "db1";
@ -271,20 +271,19 @@ in
ensureDatabases = []; ensureDatabases = [];
enableTCPIP = true; enableTCPIP = true;
port = 1234;
authentication = '' authentication = ''
#type database DBuser origin-address auth-method #type database DBuser origin-address auth-method
local all all peer local all all peer
# ipv4 # ipv4
host all all 127.0.0.1/32 trust host all all 127.0.0.1/32 password
# ipv6 # ipv6
host all all ::1/128 trust host all all ::1/128 password
''; '';
}; };
systemd.services.postgresql.postStart = ""; systemd.services.postgresql.postStart = "";
}; };
expr = testConfig { expr = testConfig {
shb.postgresql.tcpIPPort = 1234; shb.postgresql.enableTCPIP = true;
}; };
}; };
} }

167
test/vm/postgresql.nix Normal file
View file

@ -0,0 +1,167 @@
{ pkgs, lib, ... }:
{
peerWithoutUser = pkgs.nixosTest {
name = "postgresql-peerWithoutUser";
nodes.machine = { config, pkgs, ... }: {
imports = [
../../modules/blocks/postgresql.nix
];
shb.postgresql.ensures = [
{
username = "me";
database = "mine";
}
];
};
testScript = { nodes, ... }: ''
start_all()
machine.wait_for_unit("postgresql.service")
def peer_cmd(user, database):
return "sudo -u me psql -U {user} {db} --command \"\"".format(user=user, db=database)
with subtest("cannot login because of missing user"):
machine.fail(peer_cmd("me", "mine"), timeout=10)
with subtest("cannot login with unknown user"):
machine.fail(peer_cmd("notme", "mine"), timeout=10)
with subtest("cannot login to unknown database"):
machine.fail(peer_cmd("me", "notmine"), timeout=10)
'';
};
peerAuth = pkgs.nixosTest {
name = "postgresql-peerAuth";
nodes.machine = { config, pkgs, ... }: {
imports = [
../../modules/blocks/postgresql.nix
];
users.users.me = {
isSystemUser = true;
group = "me";
extraGroups = [ "sudoers" ];
};
users.groups.me = {};
shb.postgresql.ensures = [
{
username = "me";
database = "mine";
}
];
};
testScript = { nodes, ... }: ''
start_all()
machine.wait_for_unit("postgresql.service")
def peer_cmd(user, database):
return "sudo -u me psql -U {user} {db} --command \"\"".format(user=user, db=database)
def tcpip_cmd(user, database, port):
return "psql -h 127.0.0.1 -p {port} -U {user} {db} --command \"\"".format(user=user, db=database, port=port)
with subtest("can login with provisioned user and database"):
machine.succeed(peer_cmd("me", "mine"), timeout=10)
with subtest("cannot login with unknown user"):
machine.fail(peer_cmd("notme", "mine"), timeout=10)
with subtest("cannot login to unknown database"):
machine.fail(peer_cmd("me", "notmine"), timeout=10)
with subtest("cannot login with tcpip"):
machine.fail(tcpip_cmd("me", "mine", "5432"), timeout=10)
'';
};
tcpIPWithoutPasswordAuth = pkgs.nixosTest {
name = "postgresql-tcpIpWithoutPasswordAuth";
nodes.machine = { config, pkgs, ... }: {
imports = [
../../modules/blocks/postgresql.nix
];
shb.postgresql.enableTCPIP = true;
shb.postgresql.ensures = [
{
username = "me";
database = "mine";
}
];
};
testScript = { nodes, ... }: ''
start_all()
machine.wait_for_unit("postgresql.service")
def peer_cmd(user, database):
return "sudo -u me psql -U {user} {db} --command \"\"".format(user=user, db=database)
def tcpip_cmd(user, database, port):
return "psql -h 127.0.0.1 -p {port} -U {user} {db} --command \"\"".format(user=user, db=database, port=port)
with subtest("cannot login without existing user"):
machine.fail(peer_cmd("me", "mine"), timeout=10)
with subtest("cannot login with user without password"):
machine.fail(tcpip_cmd("me", "mine", "5432"), timeout=10)
'';
};
tcpIPPasswordAuth = pkgs.nixosTest {
name = "postgresql-tcpIPPasswordAuth";
nodes.machine = { config, pkgs, ... }: {
imports = [
../../modules/blocks/postgresql.nix
];
users.users.me = {
isSystemUser = true;
group = "me";
extraGroups = [ "sudoers" ];
};
users.groups.me = {};
system.activationScripts.secret = ''
echo secretpw > /run/dbsecret
'';
shb.postgresql.enableTCPIP = true;
shb.postgresql.ensures = [
{
username = "me";
database = "mine";
passwordFile = "/run/dbsecret";
}
];
};
testScript = { nodes, ... }: ''
start_all()
machine.wait_for_unit("postgresql.service")
def peer_cmd(user, database):
return "sudo -u me psql -U {user} {db} --command \"\"".format(user=user, db=database)
def tcpip_cmd(user, database, port, password):
return "PGPASSWORD={password} psql -h 127.0.0.1 -p {port} -U {user} {db} --command \"\"".format(user=user, db=database, port=port, password=password)
with subtest("can peer login with provisioned user and database"):
machine.succeed(peer_cmd("me", "mine"), timeout=10)
with subtest("can tcpip login with provisioned user and database"):
machine.succeed(tcpip_cmd("me", "mine", "5432", "secretpw"), timeout=10)
with subtest("cannot tcpip login with wrong password"):
machine.fail(tcpip_cmd("me", "mine", "5432", "oops"), timeout=10)
'';
};
}