infra/services/postgres/default.nix
Pierre Bourdon 0dd333c573
postgres: add mTLS support
New client certs can be minted via the provided script, which is meant
to be run on the postgres server (where the CA private key is
conveniently deployed).
2024-08-16 07:59:12 +02:00

106 lines
3 KiB
Nix

{ config, lib, pkgs, ... }:
let
cfg = config.bagel.services.postgres;
dataDir = "/var/db/postgresql/16";
in {
options.bagel.services.postgres = with lib; {
enable = mkEnableOption "PostgreSQL server";
};
config = lib.mkIf cfg.enable {
age.secrets.postgresql-tls-priv.owner = "postgres";
age.secrets.postgresql-tls-priv.file = ../../secrets/postgres-tls-priv.age;
systemd.tmpfiles.rules = [
"d /var/db 0755 root root - -"
"d /var/db/postgresql 0750 postgres postgres - -"
"d ${dataDir} 0750 postgres postgres - -"
];
services.postgresql = {
enable = true;
package = pkgs.postgresql_16;
dataDir = dataDir;
enableTCPIP = true;
# TODO: Where to put this to properly couple things? It doesn't belong
# here, but using it in services/hydra would require running on
# localhost. Probably needs to be replaced with some different way of
# ensuring the DB/user exist.
ensureDatabases = [ "hydra" ];
ensureUsers = [
{
name = "hydra";
ensureDBOwnership = true;
}
];
identMap = ''
hydra-users hydra hydra
hydra-users hydra-queue-runner hydra
hydra-users hydra-www hydra
hydra-users root hydra
# The postgres user is used to create the pg_trgm extension for the hydra database
hydra-users postgres postgres
'';
authentication = ''
local hydra all peer map=hydra-users
# Allow any connection over TLS with a valid client certificate: signed
# by our CA, and with username = cert CN.
hostssl all all all cert clientcert=verify-full
'';
settings = {
max_connections = 500;
ssl = true;
ssl_ca_file = "${./ca.crt}";
ssl_cert_file = "${./server.crt}";
ssl_key_file = config.age.secrets.postgresql-tls-priv.path;
};
};
networking.firewall.allowedTCPPorts = [ config.services.postgresql.settings.port ];
# Provisioned on the server so that CA operations can be done there.
age.secrets.postgresql-ca-priv.owner = "postgres";
age.secrets.postgresql-ca-priv.file = ../../secrets/postgres-ca-priv.age;
users.users.postgres.packages = [
(pkgs.writeShellScriptBin "postgres-mint-new-client" ''
#! ${pkgs.runtimeShell}
set -eo pipefail
if [ $# -eq 0 ]; then
echo "usage: $0 <pg-username>" >&2
exit 1
fi
user=$1
step=${pkgs.lib.getExe pkgs.step-cli}
tmpdir=$(mktemp -d)
trap "rm -rf $tmpdir" EXIT
$step certificate create "$user" "$tmpdir/client.crt" "$tmpdir/client.key" \
--profile leaf \
--ca ${./ca.crt} \
--ca-key ${config.age.secrets.postgresql-ca-priv.path} \
--not-after 87660h \
--no-password --insecure
echo "Client certificate:"
cat "$tmpdir/client.crt"
echo
echo "Client private key:"
cat "$tmpdir/client.key"
'')
];
};
}