+++ /dev/null
-{ lib, pkgs, config, ... }:
-let
- cfg = config.myServices.databases.postgresql;
-in {
- options.myServices.databases = {
- postgresql = {
- enable = lib.mkOption {
- default = false;
- example = true;
- description = "Whether to enable postgresql database";
- type = lib.types.bool;
- };
- package = lib.mkOption {
- type = lib.types.package;
- default = pkgs.postgresql;
- description = ''
- Postgresql package to use.
- '';
- };
- ldapConfig = lib.mkOption {
- description = "LDAP configuration to allow PAM identification via LDAP";
- type = lib.types.submodule {
- options = {
- host = lib.mkOption { type = lib.types.str; };
- base = lib.mkOption { type = lib.types.str; };
- dn = lib.mkOption { type = lib.types.str; };
- password = lib.mkOption { type = lib.types.str; };
- filter = lib.mkOption { type = lib.types.str; };
- };
- };
- };
- replicationLdapConfig = lib.mkOption {
- description = "LDAP configuration to allow replication";
- type = lib.types.submodule {
- options = {
- host = lib.mkOption { type = lib.types.str; };
- base = lib.mkOption { type = lib.types.str; };
- dn = lib.mkOption { type = lib.types.str; };
- password = lib.mkOption { type = lib.types.str; };
- };
- };
- };
- authorizedHosts = lib.mkOption {
- default = {};
- description = "Hosts to allow connections from";
- type = lib.types.attrsOf (lib.types.listOf (lib.types.submodule {
- options = {
- method = lib.mkOption {
- default = "md5";
- type = lib.types.str;
- };
- username = lib.mkOption {
- default = "all";
- type = lib.types.str;
- };
- database = lib.mkOption {
- default = "all";
- type = lib.types.str;
- };
- ip4 = lib.mkOption {
- default = [];
- type = lib.types.listOf lib.types.str;
- };
- ip6 = lib.mkOption {
- default = [];
- type = lib.types.listOf lib.types.str;
- };
- };
- }));
- };
- replicationHosts = lib.mkOption {
- default = {};
- description = "Hosts to allow replication from";
- type = lib.types.attrsOf (lib.types.submodule {
- options = {
- ip4 = lib.mkOption {
- type = lib.types.listOf lib.types.str;
- };
- ip6 = lib.mkOption {
- type = lib.types.listOf lib.types.str;
- };
- };
- });
- };
- # Output variables
- socketsDir = lib.mkOption {
- type = lib.types.path;
- default = "/run/postgresql";
- description = ''
- The directory where Postgresql puts sockets.
- '';
- readOnly = true;
- };
- };
- };
-
- config = lib.mkIf cfg.enable {
- networking.firewall.allowedTCPPorts = [ 5432 ];
-
- security.acme.certs."postgresql" = config.myServices.databasesCerts // {
- user = "postgres";
- group = "postgres";
- domain = "db-1.immae.eu";
- postRun = ''
- systemctl reload postgresql.service
- '';
- };
-
- systemd.services.postgresql.serviceConfig = {
- SupplementaryGroups = "keys";
- };
- systemd.services.postgresql.postStart = lib.mkAfter ''
- # This line is already defined in 19.09
- PSQL="${pkgs.sudo}/bin/sudo -u postgres psql --port=5432"
-
- ${builtins.concatStringsSep "\n" (lib.mapAttrsToList (role: _: ''
- $PSQL -tAc "SELECT 1 FROM pg_roles WHERE rolname='${role}'" \
- | grep -q 1 \
- || $PSQL -tAc 'CREATE USER "${role}" WITH REPLICATION'
- '') cfg.replicationHosts)}
-
- ${builtins.concatStringsSep "\n" (lib.mapAttrsToList (role: _:
- let
- sname = builtins.replaceStrings ["-"] ["_"] role;
- in
- ''
- $PSQL -tAc "SELECT 1 FROM pg_replication_slots WHERE slot_name='${sname}'" \
- | grep -q 1 \
- || $PSQL -tAc "SELECT * FROM pg_create_physical_replication_slot('${sname}')"
- '') cfg.replicationHosts)}
- '';
-
- services.postgresql = {
- enable = true;
- package = cfg.package;
- enableTCPIP = true;
- extraConfig = ''
- max_connections = 100
- wal_level = logical
- shared_buffers = 512MB
- work_mem = 10MB
- max_wal_size = 1GB
- min_wal_size = 80MB
- log_timezone = 'Europe/Paris'
- datestyle = 'iso, mdy'
- timezone = 'Europe/Paris'
- lc_messages = 'en_US.UTF-8'
- lc_monetary = 'en_US.UTF-8'
- lc_numeric = 'en_US.UTF-8'
- lc_time = 'en_US.UTF-8'
- default_text_search_config = 'pg_catalog.english'
- # this introduces a small delay before storing on disk, but
- # makes it order of magnitudes quicker
- synchronous_commit = off
- ssl = on
- ssl_cert_file = '${config.security.acme.certs.postgresql.directory}/fullchain.pem'
- ssl_key_file = '${config.security.acme.certs.postgresql.directory}/key.pem'
- '';
- authentication = let
- hosts = builtins.concatStringsSep "\n" (
- lib.lists.flatten (lib.mapAttrsToList (k: vs: map (v:
- map (ip6: "hostssl ${v.database} ${v.username} ${ip6}/128 ${v.method}") v.ip6
- ++ map (ip4: "hostssl ${v.database} ${v.username} ${ip4}/32 ${v.method}") v.ip4
- ) vs) cfg.authorizedHosts
- ));
- replication = builtins.concatStringsSep "\n" (
- lib.lists.flatten (lib.mapAttrsToList (k: v:
- map (ip6: "hostssl replication ${k} ${ip6}/128 pam pamservice=postgresql_replication") v.ip6
- ++ map (ip4: "hostssl replication ${k} ${ip4}/32 pam pamservice=postgresql_replication") v.ip4
- ) cfg.replicationHosts
- ));
- in ''
- local all postgres ident
- local all all md5
- ${hosts}
- hostssl all all all pam
- ${replication}
- '';
- };
-
- secrets.keys = {
- "postgresql/pam" = {
- permissions = "0400";
- group = "postgres";
- user = "postgres";
- text = with cfg.ldapConfig; ''
- host ${host}
- base ${base}
- binddn ${dn}
- bindpw ${password}
- pam_filter ${filter}
- ssl start_tls
- '';
- };
- "postgresql/pam_replication" = {
- permissions = "0400";
- group = "postgres";
- user = "postgres";
- text = with cfg.replicationLdapConfig; ''
- host ${host}
- base ${base}
- binddn ${dn}
- bindpw ${password}
- pam_login_attribute cn
- ssl start_tls
- '';
- };
- };
-
- security.pam.services = let
- pam_ldap = "${pkgs.pam_ldap}/lib/security/pam_ldap.so";
- in {
- postgresql = {
- text = ''
- auth required ${pam_ldap} config=${config.secrets.fullPaths."postgresql/pam"}
- account required ${pam_ldap} config=${config.secrets.fullPaths."postgresql/pam"}
- '';
- };
- postgresql_replication = {
- text = ''
- auth required ${pam_ldap} config=${config.secrets.fullPaths."postgresql/pam_replication"}
- account required ${pam_ldap} config=${config.secrets.fullPaths."postgresql/pam_replication"}
- '';
- };
- };
- };
-}
-