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:
parent
9e46d93e08
commit
52b9233a6c
9 changed files with 220 additions and 36 deletions
7
.github/workflows/test.yml
vendored
7
.github/workflows/test.yml
vendored
|
@ -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
|
||||||
|
|
10
README.md
10
README.md
|
@ -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
|
||||||
|
|
12
flake.nix
12
flake.nix
|
@ -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;}))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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)
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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
167
test/vm/postgresql.nix
Normal 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)
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
}
|
Loading…
Reference in a new issue