From 1a64deeb894dc95e2645a75771732c6cc53a79ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isma=C3=ABl=20Bouya?= Date: Wed, 4 Oct 2023 01:35:06 +0200 Subject: Squash changes containing private information There were a lot of changes since the previous commit, but a lot of them contained personnal information about users. All thos changes got stashed into a single commit (history is kept in a different place) and private information was moved in a separate private repository --- systems/eldiron/databases/default.nix | 56 +++++ systems/eldiron/databases/mariadb.nix | 188 +++++++++++++++ systems/eldiron/databases/openldap/default.nix | 304 +++++++++++++++++++++++++ systems/eldiron/databases/postgresql.nix | 236 +++++++++++++++++++ systems/eldiron/databases/redis.nix | 138 +++++++++++ 5 files changed, 922 insertions(+) create mode 100644 systems/eldiron/databases/default.nix create mode 100644 systems/eldiron/databases/mariadb.nix create mode 100644 systems/eldiron/databases/openldap/default.nix create mode 100644 systems/eldiron/databases/postgresql.nix create mode 100644 systems/eldiron/databases/redis.nix (limited to 'systems/eldiron/databases') diff --git a/systems/eldiron/databases/default.nix b/systems/eldiron/databases/default.nix new file mode 100644 index 0000000..d8d3048 --- /dev/null +++ b/systems/eldiron/databases/default.nix @@ -0,0 +1,56 @@ +{ lib, config, secrets, ... }: +let + cfg = config.myServices.databases; +in +{ + options.myServices = { + databases.enable = lib.mkEnableOption "my databases service"; + }; + + config.myServices.dns.zones."immae.eu".subdomains.db-1 = lib.mkIf cfg.enable (with config.myServices.dns.helpers; ips servers.eldiron.ips.main); + config.myServices.databases = lib.mkIf cfg.enable { + + mariadb = { + enable = true; + ldapConfig = { + inherit (config.myEnv.ldap) host base; + inherit (config.myEnv.databases.mysql.pam) dn filter password; + }; + replicationLdapConfig = { + inherit (config.myEnv.ldap) host base; + inherit (config.myEnv.servers.eldiron.ldap) dn password; + }; + credentials.root = config.myEnv.databases.mysql.systemUsers.root; + }; + + openldap = { + accessFile = secrets.ldap-conf; + baseDn = config.myEnv.ldap.base; + rootDn = config.myEnv.ldap.root_dn; + rootPw = config.myEnv.ldap.root_pw; + enable = true; + }; + + postgresql = { + ldapConfig = { + inherit (config.myEnv.ldap) host base; + inherit (config.myEnv.databases.postgresql.pam) dn filter password; + }; + replicationLdapConfig = { + inherit (config.myEnv.ldap) host base; + inherit (config.myEnv.servers.eldiron.ldap) dn password; + }; + authorizedHosts = { + }; + replicationHosts = { + backup-2 = { + ip4 = config.myEnv.servers.backup-2.ips.main.ip4; + ip6 = config.myEnv.servers.backup-2.ips.main.ip6; + }; + }; + enable = true; + }; + + redis.enable = true; + }; +} diff --git a/systems/eldiron/databases/mariadb.nix b/systems/eldiron/databases/mariadb.nix new file mode 100644 index 0000000..b4a6917 --- /dev/null +++ b/systems/eldiron/databases/mariadb.nix @@ -0,0 +1,188 @@ +{ lib, pkgs, config, ... }: +let + cfg = config.myServices.databases.mariadb; +in { + options.myServices.databases = { + mariadb = { + enable = lib.mkOption { + default = false; + example = true; + description = "Whether to enable mariadb database"; + type = lib.types.bool; + }; + package = lib.mkOption { + type = lib.types.package; + default = pkgs.mariadb; + description = '' + Mariadb package to use. + ''; + }; + credentials = lib.mkOption { + default = {}; + description = "Credentials"; + type = lib.types.attrsOf lib.types.str; + }; + 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; }; + }; + }; + }; + dataDir = lib.mkOption { + type = lib.types.path; + default = "/var/lib/mysql"; + description = '' + The directory where Mariadb stores its data. + ''; + }; + # Output variables + socketsDir = lib.mkOption { + type = lib.types.path; + default = "/run/mysqld"; + description = '' + The directory where Mariadb puts sockets. + ''; + }; + sockets = lib.mkOption { + type = lib.types.attrsOf lib.types.path; + default = { + mysqld = "${cfg.socketsDir}/mysqld.sock"; + }; + readOnly = true; + description = '' + Mariadb sockets + ''; + }; + }; + }; + + config = lib.mkIf cfg.enable { + networking.firewall.allowedTCPPorts = [ config.myEnv.databases.mysql.port ]; + + # for adminer, ssl is implemented with mysqli only, which is + # currently disabled because it’s not compatible with pam. + # Thus we need to generate two users for each 'remote': one remote + # with SSL, and one localhost without SSL. + # User identified by LDAP: + # CREATE USER foo@% IDENTIFIED VIA pam USING 'mysql' REQUIRE SSL; + # CREATE USER foo@localhost IDENTIFIED VIA pam USING 'mysql'; + + # To create a user (host) for replication: + # CREATE USER 'host'@'%' IDENTIFIED VIA pam USING 'mysql_replication' REQUIRE SSL; + # GRANT REPLICATION SLAVE, REPLICATION CLIENT, RELOAD, LOCK TABLES, SELECT, SHOW VIEW ON *.* TO 'host'@'%'; + # (the lock/select grant permits to let the replication host handle + # the initial fetch of the database) + # % should be valid for both localhost (for cron dumps) and the origin host. + services.mysql = { + enable = true; + package = cfg.package; + dataDir = cfg.dataDir; + settings = { + mysqld = { + port = config.myEnv.databases.mysql.port; + ssl_ca = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"; + ssl_key = "${config.security.acme.certs.mysql.directory}/key.pem"; + ssl_cert = "${config.security.acme.certs.mysql.directory}/fullchain.pem"; + + # for replication + log-bin = "mariadb-bin"; + server-id = "1"; + + # this introduces a small delay before storing on disk, but + # makes it order of magnitudes quicker + innodb_flush_log_at_trx_commit = "0"; + + # This is necessary since the default ("dialog") is not + # supported by php's mysqlnd plugin (in mysqli). But with that + # change only regular login+password schemes can work (no + # "fancy" authentication methods like fprintd or keys) + pam_use_cleartext_plugin = true; + }; + }; + }; + + users.users.mysql.extraGroups = [ "keys" ]; + security.acme.certs."mysql" = { + group = "mysql"; + domain = "db-1.immae.eu"; + postRun = '' + systemctl restart mysql.service + ''; + }; + + secrets.keys = { + "mysql/mysqldump" = { + permissions = "0400"; + user = "root"; + group = "root"; + text = '' + [mysqldump] + user = root + password = ${cfg.credentials.root} + ''; + }; + "mysql/pam" = { + permissions = "0400"; + user = "mysql"; + group = "mysql"; + text = with cfg.ldapConfig; '' + host ${host} + base ${base} + binddn ${dn} + bindpw ${password} + pam_filter ${filter} + ssl start_tls + ''; + }; + "mysql/pam_replication" = { + permissions = "0400"; + user = "mysql"; + group = "mysql"; + 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 { + mysql = { + text = '' + # https://mariadb.com/kb/en/mariadb/pam-authentication-plugin/ + auth required ${pam_ldap} config=${config.secrets.fullPaths."mysql/pam"} + account required ${pam_ldap} config=${config.secrets.fullPaths."mysql/pam"} + ''; + }; + mysql_replication = { + text = '' + auth required ${pam_ldap} config=${config.secrets.fullPaths."mysql/pam_replication"} + account required ${pam_ldap} config=${config.secrets.fullPaths."mysql/pam_replication"} + ''; + }; + }; + + }; +} diff --git a/systems/eldiron/databases/openldap/default.nix b/systems/eldiron/databases/openldap/default.nix new file mode 100644 index 0000000..7cd15da --- /dev/null +++ b/systems/eldiron/databases/openldap/default.nix @@ -0,0 +1,304 @@ +{ lib, pkgs, config, openldap, ... }: +let + cfg = config.myServices.databases.openldap; +in +{ + options.myServices.databases = { + openldap = { + enable = lib.mkOption { + default = false; + example = true; + description = "Whether to enable ldap"; + type = lib.types.bool; + }; + baseDn = lib.mkOption { + type = lib.types.str; + description = '' + Base DN for LDAP + ''; + }; + rootDn = lib.mkOption { + type = lib.types.str; + description = '' + Root DN + ''; + }; + rootPw = lib.mkOption { + type = lib.types.str; + description = '' + Root (Hashed) password + ''; + }; + accessFile = lib.mkOption { + type = lib.types.path; + description = '' + The file path that defines the access + ''; + }; + dataDir = lib.mkOption { + type = lib.types.path; + default = "/var/lib/openldap/mdb"; + description = '' + The directory where Openldap stores its data. + ''; + }; + socketsDir = lib.mkOption { + type = lib.types.path; + default = "/run/openldap"; + description = '' + The directory where Openldap puts sockets and pid files. + ''; + }; + # Output variables + pids = lib.mkOption { + type = lib.types.attrsOf lib.types.path; + default = { + pid = "${cfg.socketsDir}/slapd.pid"; + args = "${cfg.socketsDir}/slapd.args"; + }; + readOnly = true; + description = '' + Slapd pid files + ''; + }; + }; + }; + + config = lib.mkIf cfg.enable { + myServices.dns.zones."immae.eu".subdomains.ldap = + with config.myServices.dns.helpers; ips servers.eldiron.ips.main; + + nixpkgs.overlays = [ + (self: super: { + openldap_libressl_cyrus = (self.openldap.override { + openssl = self.libressl; + cyrus_sasl = self.cyrus_sasl.overrideAttrs (old: { + configureFlags = old.configureFlags ++ [ "--with-configdir=/etc/sasl2" ]; + }); + }).overrideAttrs (old: { + configureFlags = old.configureFlags ++ [ "--with-cyrus-sasl" "--enable-spasswd" ]; + }); + }) + ]; + + secrets.keys = { + "ldap/password" = { + permissions = "0400"; + user = "openldap"; + group = "openldap"; + text = "${cfg.rootPw}"; + }; + "ldap/access" = { + permissions = "0400"; + user = "openldap"; + group = "openldap"; + text = builtins.readFile cfg.accessFile; + }; + "ldap" = { + permissions = "0500"; + user = "openldap"; + group = "openldap"; + isDir = true; + }; + }; + users.users.openldap.extraGroups = [ "keys" ]; + networking.firewall.allowedTCPPorts = [ 636 389 ]; + + security.acme.certs."ldap" = { + group = "openldap"; + domain = "ldap.immae.eu"; + postRun = '' + systemctl restart openldap.service + ''; + }; + + services.filesWatcher.openldap = { + restart = true; + paths = [ config.secrets.fullPaths."ldap" ]; + }; + + services.openldap = { + enable = true; + urlList = [ "ldap://" "ldaps://" ]; + package = pkgs.openldap_libressl_cyrus; + settings = { + attrs = { + olcPidFile = cfg.pids.pid; + olcArgsFile = cfg.pids.args; + olcLogLevel = "none"; + olcTLSCertificateFile = "${config.security.acme.certs.ldap.directory}/cert.pem"; + olcTLSCertificateKeyFile = "${config.security.acme.certs.ldap.directory}/key.pem"; + olcTLSCACertificateFile = "${config.security.acme.certs.ldap.directory}/fullchain.pem"; + olcTLSCACertificatePath = "${pkgs.cacert.unbundled}/etc/ssl/certs/"; + # This makes openldap crash + # olcTLSCipherSuite = "DEFAULT"; + #olcSaslHost = "kerberos.immae.eu"; + # Map sasl "dn" to ldap dn + #olcAuthzRegexp = ''{0}"uid=([^,]*)(,cn=IMMAE.EU)?,cn=(gssapi|gss-spnego),cn=auth" "uid=$1,ou=users,dc=immae,dc=eu"''; + }; + children = { + "cn=module{0}" = { + attrs = { + cn = "module{0}"; + objectClass = [ "olcModuleList" ]; + olcModuleLoad = [ "{0}back_mdb" "{1}memberof" "{2}syncprov" ]; + }; + }; + "cn=schema".includes = map (schema: + "${config.services.openldap.package}/etc/schema/${schema}.ldif" + ) [ "core" "cosine" "inetorgperson" "nis" ] ++ [ + "${openldap.immae-ldif}" + ]; + "olcDatabase={0}config" = { + attrs = { + objectClass = "olcDatabaseConfig"; + olcDatabase = "{0}config"; + olcAccess = ["{0}to * by * none"]; + }; + }; + "olcDatabase={1}mdb" = { + attrs = { + objectClass = [ "olcDatabaseConfig" "olcMdbConfig" ]; + olcDatabase = "{1}mdb"; + olcDbIndex = [ + "objectClass eq" + "uid pres,eq" + "mail pres,eq,sub" + "cn pres,eq,sub" + "sn pres,eq,sub" + "dc eq" + "member eq" + "memberOf eq" + ]; + olcAccess = let + join = builtins.replaceStrings ["\n"] [" "]; + in [ + # First matching "to" + "by" wins + #### Replication needs full access + (join ''{0}to * + by dn.base="uid=ldap_replication,cn=ldap,ou=services,dc=immae,dc=eu" read + by * break + '') + #### Prevent modification of SASL passwords + (join ''{1}to attrs=userPassword val.regex="^.SASL..+" + by self read + by anonymous auth + by * none + '') + #### Oneself needs access to users password + (join ''{2}to attrs=userPassword,shadowLastChange + by self write + by anonymous auth + by * none + '') + #### Should be write, but disabled during migration to psql + (join ''{3}to attrs=immaeSshKey + by self read + by * break + '') + + #### Anyone can auth, and I can see myself + (join ''{4}to * + by self read + by anonymous auth + by * break + '') + + #### Specific access for phpldapadmin + (join ''{5}to filter="(uid=*)" attrs=entry,uid + by dn.base="cn=phpldapadmin,ou=services,dc=immae,dc=eu" read + by * break + '') + + #### Hosts + # The attributes are available to every host + (join ''{6}to dn.one="ou=hosts,dc=immae,dc=eu" + by dn.subtree="ou=hosts,dc=immae,dc=eu" read + by dn.base="dc=immae,dc=eu" search + by * break + '') + #### /Hosts + + #### Local services + # this/-* & user : all your ancestors have access to you + # this/memberOf/-* & user : all those whom you belong to (in a group), + # and their ancestors, have access to you + # user/immaeAccessWriteDn*/member & this : you have write access to the + # members of your immaeAccessDn + # attributes + # user/immaeAccessDn*/member & this : you have access to the members + # of your immaeAccessDn attributes + # user/immaeAccessReadSubtree* & this/-* : you have access to the + # childrens of your immaeAccessReadSubtree + # attributes + # this/memberOf/-* & user/immaeAccessReadSubtree*: you have access to + # the members of the childrens of your + # immaeAccessReadSubtree attributes + # http://www.openldap.org/faq/data/cache/1133.html + (join ''{7}to dn.subtree="dc=immae,dc=eu" + by dn.subtree="ou=external_services,dc=immae,dc=eu" break + by set.exact="this/-* & user" read + by set.exact="this/memberOf/-* & user" read + by set.exact="user/immaeAccessWriteDn*/member & this" write + by set.exact="user/immaeAccessDn*/member & this" read + by set.exact="user/immaeAccessReadSubtree* & this/-*" read + by set.exact="this/memberOf/-* & user/immaeAccessReadSubtree*" read + by users search + by * break + '') + #### /Local services + + #### External services + # http://www.openldap.org/faq/data/cache/429.html + # FIXME: Find a way to whitelist? + (join ''{8}to attrs=immaeSshKey + by dn.subtree="ou=external_services,dc=immae,dc=eu" none + '') + (join ''{9}to dn.subtree="dc=immae,dc=eu" + by set.exact="this/-* & user" read + by set.exact="this/memberOf/-* & user" read + by set.exact="user/immaeAccessDn*/member & this/-*" read + by users search + by * none + '') + #### /External services + ]; + olcDbDirectory = cfg.dataDir; + olcRootDN = cfg.rootDn; + olcRootPW.path = config.secrets.fullPaths."ldap/password"; + olcSuffix = cfg.baseDn; + }; + children = { + "olcOverlay={0}memberof" = { + attrs = { + objectClass = [ "olcOverlayConfig" "olcMemberOf" ]; + olcOverlay = "{0}memberof"; + }; + }; + "olcOverlay={1}syncprov" = { + attrs = { + objectClass = [ "olcOverlayConfig" "olcSyncProvConfig" ]; + olcOverlay = "{1}syncprov"; + olcSpCheckpoint = "100 10"; + }; + }; + }; + }; + }; + }; + }; + myServices.monitoring.fromMasterActivatedPlugins = [ "tcp" ]; + myServices.monitoring.fromMasterObjects.service = [ + { + service_description = "ldap SSL is up to date"; + host_name = config.hostEnv.fqdn; + use = "external-service"; + check_command = ["check_tcp_ssl" "636"]; + + servicegroups = "webstatus-ssl"; + _webstatus_name = "LDAP"; + _webstatus_url = "ldap.immae.eu"; + } + ]; + }; +} diff --git a/systems/eldiron/databases/postgresql.nix b/systems/eldiron/databases/postgresql.nix new file mode 100644 index 0000000..721059a --- /dev/null +++ b/systems/eldiron/databases/postgresql.nix @@ -0,0 +1,236 @@ +{ 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" = { + group = "postgres"; + domain = "db-1.immae.eu"; + postRun = '' + systemctl reload postgresql.service + ''; + }; + + systemd.services.postgresql.serviceConfig = { + SupplementaryGroups = "keys"; + ExecStartPre = [ ("+" + (pkgs.writeShellScript "postgresql-fix-cert" '' + # postgresql complains: + # private key file "${config.security.acme.certs.postgresql.directory}/key.pem" must be owned by the database user or root + cp -f "${config.security.acme.certs.postgresql.directory}/key.pem" "${config.services.postgresql.dataDir}/key.pem" + chown postgres:postgres "${config.services.postgresql.dataDir}/key.pem" + chmod go-r "${config.services.postgresql.dataDir}/key.pem" + '')) ]; + }; + systemd.services.postgresql.postStart = lib.mkAfter '' + # This line is already defined in 19.09 + PSQL="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; + checkConfig = false; + logLinePrefix = "%h %q%u@%d "; # Default: '%m [%p] ', already stored independently by journald. %h needed for fail2ban + settings = { + max_connections = 300; + 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.services.postgresql.dataDir}/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} ${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"} + ''; + }; + }; + }; +} + diff --git a/systems/eldiron/databases/redis.nix b/systems/eldiron/databases/redis.nix new file mode 100644 index 0000000..1f57aa9 --- /dev/null +++ b/systems/eldiron/databases/redis.nix @@ -0,0 +1,138 @@ +{ lib, config, pkgs, ... }: +let + cfg = config.myServices.databases.redis; +in { + options.myServices.databases.redis = { + enable = lib.mkOption { + default = false; + example = true; + description = "Whether to enable redis database"; + type = lib.types.bool; + }; + socketsDir = lib.mkOption { + type = lib.types.path; + default = "/run/redis"; + description = '' + The directory where Redis puts sockets. + ''; + }; + # Output variables + sockets = lib.mkOption { + type = lib.types.attrsOf lib.types.path; + default = { + redis = "${cfg.socketsDir}/redis.sock"; + }; + readOnly = true; + description = '' + Redis sockets + ''; + }; + }; + + config = lib.mkIf cfg.enable { + users.users.redis.uid = config.ids.uids.redis; + users.groups.redis.gid = config.ids.gids.redis; + services.redis.servers."" = { + enable = true; + bind = "127.0.0.1"; + unixSocket = cfg.sockets.redis; + unixSocketPerm = 777; + maxclients = 1024; + }; + systemd.services.redis.serviceConfig.Slice = "redis.slice"; + systemd.services.redis.serviceConfig.RuntimeDirectoryMode = lib.mkForce "0755"; + services.redis.servers."php-sessions" = { + enable = true; + maxclients = 1024; + unixSocketPerm = 777; + user = "wwwrun"; + }; + + services.spiped = { + enable = true; + config.redis = { + decrypt = true; + source = "0.0.0.0:16379"; + target = "/run/redis/redis.sock"; + keyfile = config.secrets.fullPaths."redis/spiped_keyfile"; + }; + }; + systemd.services.spiped_redis = { + description = "Secure pipe 'redis'"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + + serviceConfig = { + Slice = "redis.slice"; + Restart = "always"; + User = "spiped"; + PermissionsStartOnly = true; + SupplementaryGroups = "keys"; + }; + + script = "exec ${pkgs.spiped}/bin/spiped -F `cat /etc/spiped/redis.spec`"; + }; + + #services.filesWatcher.predixy = { + # restart = true; + # paths = [ config.secrets.fullPaths."redis/predixy.conf" ]; + #}; + + networking.firewall.allowedTCPPorts = [ 16379 ]; + secrets.keys = { + #"redis/predixy.conf" = { + # user = "redis"; + # group = "redis"; + # permissions = "0400"; + # text = '' + # Name Predixy + # Bind 127.0.0.1:7617 + # ClientTimeout 300 + # WorkerThreads 1 + + # Authority { + # Auth "${config.myEnv.databases.redis.predixy.read}" { + # Mode read + # } + # } + + # StandaloneServerPool { + # Databases 16 + # RefreshMethod fixed + # Group shard001 { + # + ${config.myEnv.databases.redis.socket} + # } + # } + # ''; + #}; + "redis/spiped_keyfile" = { + user = "spiped"; + group = "spiped"; + permissions = "0400"; + text = config.myEnv.databases.redis.spiped_key; + }; + }; + + systemd.slices.redis = { + description = "Redis slice"; + }; + + #systemd.services.predixy = { + # description = "Redis proxy"; + # wantedBy = [ "multi-user.target" ]; + # after = [ "redis.service" ]; + + # serviceConfig = { + # Slice = "redis.slice"; + # User = "redis"; + # Group = "redis"; + # SupplementaryGroups = "keys"; + # Type = "simple"; + + # ExecStart = "${pkgs.predixy}/bin/predixy ${config.secrets.fullPaths."redis/predixy.conf"}"; + # }; + + #}; + }; +} + -- cgit v1.2.3