{ config, lib, pkgs, environment, ... }: with lib; let cfg = config.fudo.postgresql; userOpts = { username, ... }: { options = { password = mkOption { type = with types; nullOr str; description = "The user's (plaintext) password."; default = null; }; databases = mkOption { type = with types; loaOf str; description = "Map of databases to which this user has access, to the required perms."; default = {}; example = { my_database = "ALL PRIVILEGES"; }; }; }; }; databaseOpts = { dbname, ... }: { options = { users = mkOption { type = with types; listOf str; description = "A list of users who should have full access to this database."; default = []; }; }; }; userDatabaseAccess = user: databases: mapAttrs' (database: perms: nameValuePair "DATABASE ${database}" perms) databases; stringJoin = joiner: els: if (length els) == 0 then "" else foldr(lel: rel: "${lel}${joiner}${rel}") (last els) (init els); makeEntry = nw: "host all all ${nw} gss include_realm=0 krb_realm=FUDO.ORG"; makeNetworksEntry = networks: stringJoin "\n" (map makeEntry networks); setPasswordSql = username: attrs: "ALTER USER ${username} ENCRYPTED PASSWORD '${attrs.password}';"; setPasswordsSql = users: (stringJoin "\n" (mapAttrsToList (username: attrs: setPasswordSql username attrs) (filterAttrs (user: attrs: attrs.password != null) users))) + "\n"; makeLocalUserPasswordEntries = users: stringJoin "\n" (mapAttrsToList (username: attrs: stringJoin "\n" (map (db: '' host ${username} ${db} 127.0.0.1/16 md5 host ${username} ${db} ::1/128 md5 '') (attrNames attrs.databases))) users); in { options.fudo.postgresql = { enable = mkEnableOption "Fudo PostgreSQL Server"; ssl-private-key = mkOption { type = types.str; description = "Location of the server SSL private key."; }; ssl-certificate = mkOption { type = types.str; description = "Location of the server SSL certificate."; }; keytab = mkOption { type = types.str; description = "Location of the server Kerberos keytab."; }; local-networks = mkOption { type = with types; listOf str; description = "A list of networks from which to accept connections."; example = [ "10.0.0.1/16" ]; default = []; }; users = mkOption { type = with types; loaOf (submodule userOpts); description = "A map of users to user attributes."; example = { sampleUser = { password = "some-password"; databases = [ "sample_user_db" ]; }; }; default = {}; }; databases = mkOption { type = with types; loaOf (submodule databaseOpts); description = "A map of databases to database options."; default = {}; }; }; config = mkIf cfg.enable { environment = { systemPackages = with pkgs; [ postgresql_11_gssapi ]; etc = { "postgresql/private/privkey.pem" = { mode = "0400"; user = "postgres"; group = "postgres"; source = cfg.ssl-private-key; }; "postgresql/cert.pem" = { mode = "0444"; user = "postgres"; group = "postgres"; source = cfg.ssl-certificate; }; "postgresql/private/postgres.keytab" = { mode = "0400"; user = "postgres"; group = "postgres"; source = cfg.keytab; }; "postgresql/private/user-script.sql" = { mode = "0400"; user = "postgres"; group = "postgres"; text = setPasswordsSql cfg.users; }; }; }; services.postgresql = { enable = true; package = pkgs.postgresql_11_gssapi; enableTCPIP = true; ensureDatabases = mapAttrsToList (name: value: name) cfg.databases; ensureUsers = mapAttrsToList (username: attrs: { name = username; ensurePermissions = #{ "DATABASE ${username}" = "ALL PRIVILEGES"; }; (userDatabaseAccess username attrs.databases); }) cfg.users; extraConfig = '' krb_server_keyfile = '/etc/postgresql/private/postgres.keytab' ssl = true ssl_cert_file = '/etc/postgresql/cert.pem' ssl_key_file = '/etc/postgresql/private/privkey.pem' unix_socket_directories = '/var/run/postgresql' ''; authentication = '' local all all ident ${makeLocalUserPasswordEntries cfg.users} # host-local host all all 127.0.0.1/32 gss include_realm=0 krb_realm=FUDO.ORG host all all ::1/128 gss include_realm=0 krb_realm=FUDO.ORG # local networks ${makeNetworksEntry cfg.local-networks} ''; # initialScript = pkgs.writeText "database-init.sql" '' # ${setPasswordsSql cfg.users} # ''; }; systemd.services.postgresql.postStart = '' ${pkgs.sudo}/bin/sudo -u ${config.services.postgresql.superUser} ${pkgs.postgresql}/bin/psql --port ${toString config.services.postgresql.port} -f /etc/postgresql/private/user-script.sql -d postgres ''; }; }