aboutsummaryrefslogtreecommitdiff
path: root/systems/eldiron/databases
diff options
context:
space:
mode:
authorIsmaël Bouya <ismael.bouya@normalesup.org>2023-10-04 01:35:06 +0200
committerIsmaël Bouya <ismael.bouya@normalesup.org>2023-10-04 02:11:48 +0200
commit1a64deeb894dc95e2645a75771732c6cc53a79ad (patch)
tree1b9df4838f894577a09b9b260151756272efeb53 /systems/eldiron/databases
parentfa25ffd4583cc362075cd5e1b4130f33306103f0 (diff)
downloadNix-1a64deeb894dc95e2645a75771732c6cc53a79ad.tar.gz
Nix-1a64deeb894dc95e2645a75771732c6cc53a79ad.tar.zst
Nix-1a64deeb894dc95e2645a75771732c6cc53a79ad.zip
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
Diffstat (limited to 'systems/eldiron/databases')
-rw-r--r--systems/eldiron/databases/default.nix56
-rw-r--r--systems/eldiron/databases/mariadb.nix188
-rw-r--r--systems/eldiron/databases/openldap/default.nix304
-rw-r--r--systems/eldiron/databases/postgresql.nix236
-rw-r--r--systems/eldiron/databases/redis.nix138
5 files changed, 922 insertions, 0 deletions
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 @@
1{ lib, config, secrets, ... }:
2let
3 cfg = config.myServices.databases;
4in
5{
6 options.myServices = {
7 databases.enable = lib.mkEnableOption "my databases service";
8 };
9
10 config.myServices.dns.zones."immae.eu".subdomains.db-1 = lib.mkIf cfg.enable (with config.myServices.dns.helpers; ips servers.eldiron.ips.main);
11 config.myServices.databases = lib.mkIf cfg.enable {
12
13 mariadb = {
14 enable = true;
15 ldapConfig = {
16 inherit (config.myEnv.ldap) host base;
17 inherit (config.myEnv.databases.mysql.pam) dn filter password;
18 };
19 replicationLdapConfig = {
20 inherit (config.myEnv.ldap) host base;
21 inherit (config.myEnv.servers.eldiron.ldap) dn password;
22 };
23 credentials.root = config.myEnv.databases.mysql.systemUsers.root;
24 };
25
26 openldap = {
27 accessFile = secrets.ldap-conf;
28 baseDn = config.myEnv.ldap.base;
29 rootDn = config.myEnv.ldap.root_dn;
30 rootPw = config.myEnv.ldap.root_pw;
31 enable = true;
32 };
33
34 postgresql = {
35 ldapConfig = {
36 inherit (config.myEnv.ldap) host base;
37 inherit (config.myEnv.databases.postgresql.pam) dn filter password;
38 };
39 replicationLdapConfig = {
40 inherit (config.myEnv.ldap) host base;
41 inherit (config.myEnv.servers.eldiron.ldap) dn password;
42 };
43 authorizedHosts = {
44 };
45 replicationHosts = {
46 backup-2 = {
47 ip4 = config.myEnv.servers.backup-2.ips.main.ip4;
48 ip6 = config.myEnv.servers.backup-2.ips.main.ip6;
49 };
50 };
51 enable = true;
52 };
53
54 redis.enable = true;
55 };
56}
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 @@
1{ lib, pkgs, config, ... }:
2let
3 cfg = config.myServices.databases.mariadb;
4in {
5 options.myServices.databases = {
6 mariadb = {
7 enable = lib.mkOption {
8 default = false;
9 example = true;
10 description = "Whether to enable mariadb database";
11 type = lib.types.bool;
12 };
13 package = lib.mkOption {
14 type = lib.types.package;
15 default = pkgs.mariadb;
16 description = ''
17 Mariadb package to use.
18 '';
19 };
20 credentials = lib.mkOption {
21 default = {};
22 description = "Credentials";
23 type = lib.types.attrsOf lib.types.str;
24 };
25 ldapConfig = lib.mkOption {
26 description = "LDAP configuration to allow PAM identification via LDAP";
27 type = lib.types.submodule {
28 options = {
29 host = lib.mkOption { type = lib.types.str; };
30 base = lib.mkOption { type = lib.types.str; };
31 dn = lib.mkOption { type = lib.types.str; };
32 password = lib.mkOption { type = lib.types.str; };
33 filter = lib.mkOption { type = lib.types.str; };
34 };
35 };
36 };
37 replicationLdapConfig = lib.mkOption {
38 description = "LDAP configuration to allow replication";
39 type = lib.types.submodule {
40 options = {
41 host = lib.mkOption { type = lib.types.str; };
42 base = lib.mkOption { type = lib.types.str; };
43 dn = lib.mkOption { type = lib.types.str; };
44 password = lib.mkOption { type = lib.types.str; };
45 };
46 };
47 };
48 dataDir = lib.mkOption {
49 type = lib.types.path;
50 default = "/var/lib/mysql";
51 description = ''
52 The directory where Mariadb stores its data.
53 '';
54 };
55 # Output variables
56 socketsDir = lib.mkOption {
57 type = lib.types.path;
58 default = "/run/mysqld";
59 description = ''
60 The directory where Mariadb puts sockets.
61 '';
62 };
63 sockets = lib.mkOption {
64 type = lib.types.attrsOf lib.types.path;
65 default = {
66 mysqld = "${cfg.socketsDir}/mysqld.sock";
67 };
68 readOnly = true;
69 description = ''
70 Mariadb sockets
71 '';
72 };
73 };
74 };
75
76 config = lib.mkIf cfg.enable {
77 networking.firewall.allowedTCPPorts = [ config.myEnv.databases.mysql.port ];
78
79 # for adminer, ssl is implemented with mysqli only, which is
80 # currently disabled because it’s not compatible with pam.
81 # Thus we need to generate two users for each 'remote': one remote
82 # with SSL, and one localhost without SSL.
83 # User identified by LDAP:
84 # CREATE USER foo@% IDENTIFIED VIA pam USING 'mysql' REQUIRE SSL;
85 # CREATE USER foo@localhost IDENTIFIED VIA pam USING 'mysql';
86
87 # To create a user (host) for replication:
88 # CREATE USER 'host'@'%' IDENTIFIED VIA pam USING 'mysql_replication' REQUIRE SSL;
89 # GRANT REPLICATION SLAVE, REPLICATION CLIENT, RELOAD, LOCK TABLES, SELECT, SHOW VIEW ON *.* TO 'host'@'%';
90 # (the lock/select grant permits to let the replication host handle
91 # the initial fetch of the database)
92 # % should be valid for both localhost (for cron dumps) and the origin host.
93 services.mysql = {
94 enable = true;
95 package = cfg.package;
96 dataDir = cfg.dataDir;
97 settings = {
98 mysqld = {
99 port = config.myEnv.databases.mysql.port;
100 ssl_ca = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
101 ssl_key = "${config.security.acme.certs.mysql.directory}/key.pem";
102 ssl_cert = "${config.security.acme.certs.mysql.directory}/fullchain.pem";
103
104 # for replication
105 log-bin = "mariadb-bin";
106 server-id = "1";
107
108 # this introduces a small delay before storing on disk, but
109 # makes it order of magnitudes quicker
110 innodb_flush_log_at_trx_commit = "0";
111
112 # This is necessary since the default ("dialog") is not
113 # supported by php's mysqlnd plugin (in mysqli). But with that
114 # change only regular login+password schemes can work (no
115 # "fancy" authentication methods like fprintd or keys)
116 pam_use_cleartext_plugin = true;
117 };
118 };
119 };
120
121 users.users.mysql.extraGroups = [ "keys" ];
122 security.acme.certs."mysql" = {
123 group = "mysql";
124 domain = "db-1.immae.eu";
125 postRun = ''
126 systemctl restart mysql.service
127 '';
128 };
129
130 secrets.keys = {
131 "mysql/mysqldump" = {
132 permissions = "0400";
133 user = "root";
134 group = "root";
135 text = ''
136 [mysqldump]
137 user = root
138 password = ${cfg.credentials.root}
139 '';
140 };
141 "mysql/pam" = {
142 permissions = "0400";
143 user = "mysql";
144 group = "mysql";
145 text = with cfg.ldapConfig; ''
146 host ${host}
147 base ${base}
148 binddn ${dn}
149 bindpw ${password}
150 pam_filter ${filter}
151 ssl start_tls
152 '';
153 };
154 "mysql/pam_replication" = {
155 permissions = "0400";
156 user = "mysql";
157 group = "mysql";
158 text = with cfg.replicationLdapConfig; ''
159 host ${host}
160 base ${base}
161 binddn ${dn}
162 bindpw ${password}
163 pam_login_attribute cn
164 ssl start_tls
165 '';
166 };
167 };
168
169 security.pam.services = let
170 pam_ldap = "${pkgs.pam_ldap}/lib/security/pam_ldap.so";
171 in {
172 mysql = {
173 text = ''
174 # https://mariadb.com/kb/en/mariadb/pam-authentication-plugin/
175 auth required ${pam_ldap} config=${config.secrets.fullPaths."mysql/pam"}
176 account required ${pam_ldap} config=${config.secrets.fullPaths."mysql/pam"}
177 '';
178 };
179 mysql_replication = {
180 text = ''
181 auth required ${pam_ldap} config=${config.secrets.fullPaths."mysql/pam_replication"}
182 account required ${pam_ldap} config=${config.secrets.fullPaths."mysql/pam_replication"}
183 '';
184 };
185 };
186
187 };
188}
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 @@
1{ lib, pkgs, config, openldap, ... }:
2let
3 cfg = config.myServices.databases.openldap;
4in
5{
6 options.myServices.databases = {
7 openldap = {
8 enable = lib.mkOption {
9 default = false;
10 example = true;
11 description = "Whether to enable ldap";
12 type = lib.types.bool;
13 };
14 baseDn = lib.mkOption {
15 type = lib.types.str;
16 description = ''
17 Base DN for LDAP
18 '';
19 };
20 rootDn = lib.mkOption {
21 type = lib.types.str;
22 description = ''
23 Root DN
24 '';
25 };
26 rootPw = lib.mkOption {
27 type = lib.types.str;
28 description = ''
29 Root (Hashed) password
30 '';
31 };
32 accessFile = lib.mkOption {
33 type = lib.types.path;
34 description = ''
35 The file path that defines the access
36 '';
37 };
38 dataDir = lib.mkOption {
39 type = lib.types.path;
40 default = "/var/lib/openldap/mdb";
41 description = ''
42 The directory where Openldap stores its data.
43 '';
44 };
45 socketsDir = lib.mkOption {
46 type = lib.types.path;
47 default = "/run/openldap";
48 description = ''
49 The directory where Openldap puts sockets and pid files.
50 '';
51 };
52 # Output variables
53 pids = lib.mkOption {
54 type = lib.types.attrsOf lib.types.path;
55 default = {
56 pid = "${cfg.socketsDir}/slapd.pid";
57 args = "${cfg.socketsDir}/slapd.args";
58 };
59 readOnly = true;
60 description = ''
61 Slapd pid files
62 '';
63 };
64 };
65 };
66
67 config = lib.mkIf cfg.enable {
68 myServices.dns.zones."immae.eu".subdomains.ldap =
69 with config.myServices.dns.helpers; ips servers.eldiron.ips.main;
70
71 nixpkgs.overlays = [
72 (self: super: {
73 openldap_libressl_cyrus = (self.openldap.override {
74 openssl = self.libressl;
75 cyrus_sasl = self.cyrus_sasl.overrideAttrs (old: {
76 configureFlags = old.configureFlags ++ [ "--with-configdir=/etc/sasl2" ];
77 });
78 }).overrideAttrs (old: {
79 configureFlags = old.configureFlags ++ [ "--with-cyrus-sasl" "--enable-spasswd" ];
80 });
81 })
82 ];
83
84 secrets.keys = {
85 "ldap/password" = {
86 permissions = "0400";
87 user = "openldap";
88 group = "openldap";
89 text = "${cfg.rootPw}";
90 };
91 "ldap/access" = {
92 permissions = "0400";
93 user = "openldap";
94 group = "openldap";
95 text = builtins.readFile cfg.accessFile;
96 };
97 "ldap" = {
98 permissions = "0500";
99 user = "openldap";
100 group = "openldap";
101 isDir = true;
102 };
103 };
104 users.users.openldap.extraGroups = [ "keys" ];
105 networking.firewall.allowedTCPPorts = [ 636 389 ];
106
107 security.acme.certs."ldap" = {
108 group = "openldap";
109 domain = "ldap.immae.eu";
110 postRun = ''
111 systemctl restart openldap.service
112 '';
113 };
114
115 services.filesWatcher.openldap = {
116 restart = true;
117 paths = [ config.secrets.fullPaths."ldap" ];
118 };
119
120 services.openldap = {
121 enable = true;
122 urlList = [ "ldap://" "ldaps://" ];
123 package = pkgs.openldap_libressl_cyrus;
124 settings = {
125 attrs = {
126 olcPidFile = cfg.pids.pid;
127 olcArgsFile = cfg.pids.args;
128 olcLogLevel = "none";
129 olcTLSCertificateFile = "${config.security.acme.certs.ldap.directory}/cert.pem";
130 olcTLSCertificateKeyFile = "${config.security.acme.certs.ldap.directory}/key.pem";
131 olcTLSCACertificateFile = "${config.security.acme.certs.ldap.directory}/fullchain.pem";
132 olcTLSCACertificatePath = "${pkgs.cacert.unbundled}/etc/ssl/certs/";
133 # This makes openldap crash
134 # olcTLSCipherSuite = "DEFAULT";
135 #olcSaslHost = "kerberos.immae.eu";
136 # Map sasl "dn" to ldap dn
137 #olcAuthzRegexp = ''{0}"uid=([^,]*)(,cn=IMMAE.EU)?,cn=(gssapi|gss-spnego),cn=auth" "uid=$1,ou=users,dc=immae,dc=eu"'';
138 };
139 children = {
140 "cn=module{0}" = {
141 attrs = {
142 cn = "module{0}";
143 objectClass = [ "olcModuleList" ];
144 olcModuleLoad = [ "{0}back_mdb" "{1}memberof" "{2}syncprov" ];
145 };
146 };
147 "cn=schema".includes = map (schema:
148 "${config.services.openldap.package}/etc/schema/${schema}.ldif"
149 ) [ "core" "cosine" "inetorgperson" "nis" ] ++ [
150 "${openldap.immae-ldif}"
151 ];
152 "olcDatabase={0}config" = {
153 attrs = {
154 objectClass = "olcDatabaseConfig";
155 olcDatabase = "{0}config";
156 olcAccess = ["{0}to * by * none"];
157 };
158 };
159 "olcDatabase={1}mdb" = {
160 attrs = {
161 objectClass = [ "olcDatabaseConfig" "olcMdbConfig" ];
162 olcDatabase = "{1}mdb";
163 olcDbIndex = [
164 "objectClass eq"
165 "uid pres,eq"
166 "mail pres,eq,sub"
167 "cn pres,eq,sub"
168 "sn pres,eq,sub"
169 "dc eq"
170 "member eq"
171 "memberOf eq"
172 ];
173 olcAccess = let
174 join = builtins.replaceStrings ["\n"] [" "];
175 in [
176 # First matching "to" + "by" wins
177 #### Replication needs full access
178 (join ''{0}to *
179 by dn.base="uid=ldap_replication,cn=ldap,ou=services,dc=immae,dc=eu" read
180 by * break
181 '')
182 #### Prevent modification of SASL passwords
183 (join ''{1}to attrs=userPassword val.regex="^.SASL..+"
184 by self read
185 by anonymous auth
186 by * none
187 '')
188 #### Oneself needs access to users password
189 (join ''{2}to attrs=userPassword,shadowLastChange
190 by self write
191 by anonymous auth
192 by * none
193 '')
194 #### Should be write, but disabled during migration to psql
195 (join ''{3}to attrs=immaeSshKey
196 by self read
197 by * break
198 '')
199
200 #### Anyone can auth, and I can see myself
201 (join ''{4}to *
202 by self read
203 by anonymous auth
204 by * break
205 '')
206
207 #### Specific access for phpldapadmin
208 (join ''{5}to filter="(uid=*)" attrs=entry,uid
209 by dn.base="cn=phpldapadmin,ou=services,dc=immae,dc=eu" read
210 by * break
211 '')
212
213 #### Hosts
214 # The attributes are available to every host
215 (join ''{6}to dn.one="ou=hosts,dc=immae,dc=eu"
216 by dn.subtree="ou=hosts,dc=immae,dc=eu" read
217 by dn.base="dc=immae,dc=eu" search
218 by * break
219 '')
220 #### /Hosts
221
222 #### Local services
223 # this/-* & user : all your ancestors have access to you
224 # this/memberOf/-* & user : all those whom you belong to (in a group),
225 # and their ancestors, have access to you
226 # user/immaeAccessWriteDn*/member & this : you have write access to the
227 # members of your immaeAccessDn
228 # attributes
229 # user/immaeAccessDn*/member & this : you have access to the members
230 # of your immaeAccessDn attributes
231 # user/immaeAccessReadSubtree* & this/-* : you have access to the
232 # childrens of your immaeAccessReadSubtree
233 # attributes
234 # this/memberOf/-* & user/immaeAccessReadSubtree*: you have access to
235 # the members of the childrens of your
236 # immaeAccessReadSubtree attributes
237 # http://www.openldap.org/faq/data/cache/1133.html
238 (join ''{7}to dn.subtree="dc=immae,dc=eu"
239 by dn.subtree="ou=external_services,dc=immae,dc=eu" break
240 by set.exact="this/-* & user" read
241 by set.exact="this/memberOf/-* & user" read
242 by set.exact="user/immaeAccessWriteDn*/member & this" write
243 by set.exact="user/immaeAccessDn*/member & this" read
244 by set.exact="user/immaeAccessReadSubtree* & this/-*" read
245 by set.exact="this/memberOf/-* & user/immaeAccessReadSubtree*" read
246 by users search
247 by * break
248 '')
249 #### /Local services
250
251 #### External services
252 # http://www.openldap.org/faq/data/cache/429.html
253 # FIXME: Find a way to whitelist?
254 (join ''{8}to attrs=immaeSshKey
255 by dn.subtree="ou=external_services,dc=immae,dc=eu" none
256 '')
257 (join ''{9}to dn.subtree="dc=immae,dc=eu"
258 by set.exact="this/-* & user" read
259 by set.exact="this/memberOf/-* & user" read
260 by set.exact="user/immaeAccessDn*/member & this/-*" read
261 by users search
262 by * none
263 '')
264 #### /External services
265 ];
266 olcDbDirectory = cfg.dataDir;
267 olcRootDN = cfg.rootDn;
268 olcRootPW.path = config.secrets.fullPaths."ldap/password";
269 olcSuffix = cfg.baseDn;
270 };
271 children = {
272 "olcOverlay={0}memberof" = {
273 attrs = {
274 objectClass = [ "olcOverlayConfig" "olcMemberOf" ];
275 olcOverlay = "{0}memberof";
276 };
277 };
278 "olcOverlay={1}syncprov" = {
279 attrs = {
280 objectClass = [ "olcOverlayConfig" "olcSyncProvConfig" ];
281 olcOverlay = "{1}syncprov";
282 olcSpCheckpoint = "100 10";
283 };
284 };
285 };
286 };
287 };
288 };
289 };
290 myServices.monitoring.fromMasterActivatedPlugins = [ "tcp" ];
291 myServices.monitoring.fromMasterObjects.service = [
292 {
293 service_description = "ldap SSL is up to date";
294 host_name = config.hostEnv.fqdn;
295 use = "external-service";
296 check_command = ["check_tcp_ssl" "636"];
297
298 servicegroups = "webstatus-ssl";
299 _webstatus_name = "LDAP";
300 _webstatus_url = "ldap.immae.eu";
301 }
302 ];
303 };
304}
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 @@
1{ lib, pkgs, config, ... }:
2let
3 cfg = config.myServices.databases.postgresql;
4in {
5 options.myServices.databases = {
6 postgresql = {
7 enable = lib.mkOption {
8 default = false;
9 example = true;
10 description = "Whether to enable postgresql database";
11 type = lib.types.bool;
12 };
13 package = lib.mkOption {
14 type = lib.types.package;
15 default = pkgs.postgresql;
16 description = ''
17 Postgresql package to use.
18 '';
19 };
20 ldapConfig = lib.mkOption {
21 description = "LDAP configuration to allow PAM identification via LDAP";
22 type = lib.types.submodule {
23 options = {
24 host = lib.mkOption { type = lib.types.str; };
25 base = lib.mkOption { type = lib.types.str; };
26 dn = lib.mkOption { type = lib.types.str; };
27 password = lib.mkOption { type = lib.types.str; };
28 filter = lib.mkOption { type = lib.types.str; };
29 };
30 };
31 };
32 replicationLdapConfig = lib.mkOption {
33 description = "LDAP configuration to allow replication";
34 type = lib.types.submodule {
35 options = {
36 host = lib.mkOption { type = lib.types.str; };
37 base = lib.mkOption { type = lib.types.str; };
38 dn = lib.mkOption { type = lib.types.str; };
39 password = lib.mkOption { type = lib.types.str; };
40 };
41 };
42 };
43 authorizedHosts = lib.mkOption {
44 default = {};
45 description = "Hosts to allow connections from";
46 type = lib.types.attrsOf (lib.types.listOf (lib.types.submodule {
47 options = {
48 method = lib.mkOption {
49 default = "md5";
50 type = lib.types.str;
51 };
52 username = lib.mkOption {
53 default = "all";
54 type = lib.types.str;
55 };
56 database = lib.mkOption {
57 default = "all";
58 type = lib.types.str;
59 };
60 ip4 = lib.mkOption {
61 default = [];
62 type = lib.types.listOf lib.types.str;
63 };
64 ip6 = lib.mkOption {
65 default = [];
66 type = lib.types.listOf lib.types.str;
67 };
68 };
69 }));
70 };
71 replicationHosts = lib.mkOption {
72 default = {};
73 description = "Hosts to allow replication from";
74 type = lib.types.attrsOf (lib.types.submodule {
75 options = {
76 ip4 = lib.mkOption {
77 type = lib.types.listOf lib.types.str;
78 };
79 ip6 = lib.mkOption {
80 type = lib.types.listOf lib.types.str;
81 };
82 };
83 });
84 };
85 # Output variables
86 socketsDir = lib.mkOption {
87 type = lib.types.path;
88 default = "/run/postgresql";
89 description = ''
90 The directory where Postgresql puts sockets.
91 '';
92 readOnly = true;
93 };
94 };
95 };
96
97 config = lib.mkIf cfg.enable {
98 networking.firewall.allowedTCPPorts = [ 5432 ];
99
100 security.acme.certs."postgresql" = {
101 group = "postgres";
102 domain = "db-1.immae.eu";
103 postRun = ''
104 systemctl reload postgresql.service
105 '';
106 };
107
108 systemd.services.postgresql.serviceConfig = {
109 SupplementaryGroups = "keys";
110 ExecStartPre = [ ("+" + (pkgs.writeShellScript "postgresql-fix-cert" ''
111 # postgresql complains:
112 # private key file "${config.security.acme.certs.postgresql.directory}/key.pem" must be owned by the database user or root
113 cp -f "${config.security.acme.certs.postgresql.directory}/key.pem" "${config.services.postgresql.dataDir}/key.pem"
114 chown postgres:postgres "${config.services.postgresql.dataDir}/key.pem"
115 chmod go-r "${config.services.postgresql.dataDir}/key.pem"
116 '')) ];
117 };
118 systemd.services.postgresql.postStart = lib.mkAfter ''
119 # This line is already defined in 19.09
120 PSQL="psql --port=5432"
121
122 ${builtins.concatStringsSep "\n" (lib.mapAttrsToList (role: _: ''
123 $PSQL -tAc "SELECT 1 FROM pg_roles WHERE rolname='${role}'" \
124 | grep -q 1 \
125 || $PSQL -tAc 'CREATE USER "${role}" WITH REPLICATION'
126 '') cfg.replicationHosts)}
127
128 ${builtins.concatStringsSep "\n" (lib.mapAttrsToList (role: _:
129 let
130 sname = builtins.replaceStrings ["-"] ["_"] role;
131 in
132 ''
133 $PSQL -tAc "SELECT 1 FROM pg_replication_slots WHERE slot_name='${sname}'" \
134 | grep -q 1 \
135 || $PSQL -tAc "SELECT * FROM pg_create_physical_replication_slot('${sname}')"
136 '') cfg.replicationHosts)}
137 '';
138
139 services.postgresql = {
140 enable = true;
141 package = cfg.package;
142 enableTCPIP = true;
143 checkConfig = false;
144 logLinePrefix = "%h %q%u@%d "; # Default: '%m [%p] ', already stored independently by journald. %h needed for fail2ban
145 settings = {
146 max_connections = 300;
147 wal_level = "logical";
148 shared_buffers = "512MB";
149 work_mem = "10MB";
150 max_wal_size = "1GB";
151 min_wal_size = "80MB";
152 log_timezone = "Europe/Paris";
153 datestyle = "iso, mdy";
154 timezone = "Europe/Paris";
155 lc_messages = "en_US.UTF-8";
156 lc_monetary = "en_US.UTF-8";
157 lc_numeric = "en_US.UTF-8";
158 lc_time = "en_US.UTF-8";
159 default_text_search_config = "pg_catalog.english";
160 # this introduces a small delay before storing on disk, but
161 # makes it order of magnitudes quicker
162 synchronous_commit = "off";
163 ssl = "on";
164 ssl_cert_file = "${config.security.acme.certs.postgresql.directory}/fullchain.pem";
165 ssl_key_file = "${config.services.postgresql.dataDir}/key.pem";
166 };
167 authentication = let
168 hosts = builtins.concatStringsSep "\n" (
169 lib.lists.flatten (lib.mapAttrsToList (k: vs: map (v:
170 map (ip6: "hostssl ${v.database} ${v.username} ${ip6} ${v.method}") v.ip6
171 ++ map (ip4: "hostssl ${v.database} ${v.username} ${ip4}/32 ${v.method}") v.ip4
172 ) vs) cfg.authorizedHosts
173 ));
174 replication = builtins.concatStringsSep "\n" (
175 lib.lists.flatten (lib.mapAttrsToList (k: v:
176 map (ip6: "hostssl replication ${k} ${ip6}/128 pam pamservice=postgresql_replication") v.ip6
177 ++ map (ip4: "hostssl replication ${k} ${ip4}/32 pam pamservice=postgresql_replication") v.ip4
178 ) cfg.replicationHosts
179 ));
180 in ''
181 local all postgres ident
182 local all all md5
183 ${hosts}
184 hostssl all all all pam
185 ${replication}
186 '';
187 };
188
189 secrets.keys = {
190 "postgresql/pam" = {
191 permissions = "0400";
192 group = "postgres";
193 user = "postgres";
194 text = with cfg.ldapConfig; ''
195 host ${host}
196 base ${base}
197 binddn ${dn}
198 bindpw ${password}
199 pam_filter ${filter}
200 ssl start_tls
201 '';
202 };
203 "postgresql/pam_replication" = {
204 permissions = "0400";
205 group = "postgres";
206 user = "postgres";
207 text = with cfg.replicationLdapConfig; ''
208 host ${host}
209 base ${base}
210 binddn ${dn}
211 bindpw ${password}
212 pam_login_attribute cn
213 ssl start_tls
214 '';
215 };
216 };
217
218 security.pam.services = let
219 pam_ldap = "${pkgs.pam_ldap}/lib/security/pam_ldap.so";
220 in {
221 postgresql = {
222 text = ''
223 auth required ${pam_ldap} config=${config.secrets.fullPaths."postgresql/pam"}
224 account required ${pam_ldap} config=${config.secrets.fullPaths."postgresql/pam"}
225 '';
226 };
227 postgresql_replication = {
228 text = ''
229 auth required ${pam_ldap} config=${config.secrets.fullPaths."postgresql/pam_replication"}
230 account required ${pam_ldap} config=${config.secrets.fullPaths."postgresql/pam_replication"}
231 '';
232 };
233 };
234 };
235}
236
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 @@
1{ lib, config, pkgs, ... }:
2let
3 cfg = config.myServices.databases.redis;
4in {
5 options.myServices.databases.redis = {
6 enable = lib.mkOption {
7 default = false;
8 example = true;
9 description = "Whether to enable redis database";
10 type = lib.types.bool;
11 };
12 socketsDir = lib.mkOption {
13 type = lib.types.path;
14 default = "/run/redis";
15 description = ''
16 The directory where Redis puts sockets.
17 '';
18 };
19 # Output variables
20 sockets = lib.mkOption {
21 type = lib.types.attrsOf lib.types.path;
22 default = {
23 redis = "${cfg.socketsDir}/redis.sock";
24 };
25 readOnly = true;
26 description = ''
27 Redis sockets
28 '';
29 };
30 };
31
32 config = lib.mkIf cfg.enable {
33 users.users.redis.uid = config.ids.uids.redis;
34 users.groups.redis.gid = config.ids.gids.redis;
35 services.redis.servers."" = {
36 enable = true;
37 bind = "127.0.0.1";
38 unixSocket = cfg.sockets.redis;
39 unixSocketPerm = 777;
40 maxclients = 1024;
41 };
42 systemd.services.redis.serviceConfig.Slice = "redis.slice";
43 systemd.services.redis.serviceConfig.RuntimeDirectoryMode = lib.mkForce "0755";
44 services.redis.servers."php-sessions" = {
45 enable = true;
46 maxclients = 1024;
47 unixSocketPerm = 777;
48 user = "wwwrun";
49 };
50
51 services.spiped = {
52 enable = true;
53 config.redis = {
54 decrypt = true;
55 source = "0.0.0.0:16379";
56 target = "/run/redis/redis.sock";
57 keyfile = config.secrets.fullPaths."redis/spiped_keyfile";
58 };
59 };
60 systemd.services.spiped_redis = {
61 description = "Secure pipe 'redis'";
62 after = [ "network.target" ];
63 wantedBy = [ "multi-user.target" ];
64
65 serviceConfig = {
66 Slice = "redis.slice";
67 Restart = "always";
68 User = "spiped";
69 PermissionsStartOnly = true;
70 SupplementaryGroups = "keys";
71 };
72
73 script = "exec ${pkgs.spiped}/bin/spiped -F `cat /etc/spiped/redis.spec`";
74 };
75
76 #services.filesWatcher.predixy = {
77 # restart = true;
78 # paths = [ config.secrets.fullPaths."redis/predixy.conf" ];
79 #};
80
81 networking.firewall.allowedTCPPorts = [ 16379 ];
82 secrets.keys = {
83 #"redis/predixy.conf" = {
84 # user = "redis";
85 # group = "redis";
86 # permissions = "0400";
87 # text = ''
88 # Name Predixy
89 # Bind 127.0.0.1:7617
90 # ClientTimeout 300
91 # WorkerThreads 1
92
93 # Authority {
94 # Auth "${config.myEnv.databases.redis.predixy.read}" {
95 # Mode read
96 # }
97 # }
98
99 # StandaloneServerPool {
100 # Databases 16
101 # RefreshMethod fixed
102 # Group shard001 {
103 # + ${config.myEnv.databases.redis.socket}
104 # }
105 # }
106 # '';
107 #};
108 "redis/spiped_keyfile" = {
109 user = "spiped";
110 group = "spiped";
111 permissions = "0400";
112 text = config.myEnv.databases.redis.spiped_key;
113 };
114 };
115
116 systemd.slices.redis = {
117 description = "Redis slice";
118 };
119
120 #systemd.services.predixy = {
121 # description = "Redis proxy";
122 # wantedBy = [ "multi-user.target" ];
123 # after = [ "redis.service" ];
124
125 # serviceConfig = {
126 # Slice = "redis.slice";
127 # User = "redis";
128 # Group = "redis";
129 # SupplementaryGroups = "keys";
130 # Type = "simple";
131
132 # ExecStart = "${pkgs.predixy}/bin/predixy ${config.secrets.fullPaths."redis/predixy.conf"}";
133 # };
134
135 #};
136 };
137}
138