diff options
-rw-r--r-- | hosts/surtr/email/ccert-policy-server/ccert_policy_server/__main__.py | 35 | ||||
-rw-r--r-- | hosts/surtr/email/default.nix | 57 | ||||
-rw-r--r-- | hosts/surtr/postgresql/default.nix | 14 | ||||
-rw-r--r-- | hosts/surtr/ruleset.nft | 6 |
4 files changed, 85 insertions, 27 deletions
diff --git a/hosts/surtr/email/ccert-policy-server/ccert_policy_server/__main__.py b/hosts/surtr/email/ccert-policy-server/ccert_policy_server/__main__.py index f481090c..00182523 100644 --- a/hosts/surtr/email/ccert-policy-server/ccert_policy_server/__main__.py +++ b/hosts/surtr/email/ccert-policy-server/ccert_policy_server/__main__.py | |||
@@ -27,20 +27,27 @@ class PolicyHandler(StreamRequestHandler): | |||
27 | logger.info('Connection parameters: %s', self.args) | 27 | logger.info('Connection parameters: %s', self.args) |
28 | 28 | ||
29 | allowed = False | 29 | allowed = False |
30 | with self.server.db_pool.connection() as conn: | 30 | user = None |
31 | local, domain = self.args['sender'].split(sep='@', maxsplit=1) | 31 | if self.args['sasl_username']: |
32 | extension = None | 32 | user = self.args['sasl_username'] |
33 | if '+' in local: | 33 | if self.args['ccert_subject']: |
34 | local, extension = local.split(sep='+', maxsplit=1) | 34 | user = self.args['ccert_subject'] |
35 | 35 | ||
36 | logger.debug('Parsed address: %s', {'local': local, 'extension': extension, 'domain': domain}) | 36 | if user: |
37 | 37 | with self.server.db_pool.connection() as conn: | |
38 | with conn.cursor() as cur: | 38 | local, domain = self.args['sender'].split(sep='@', maxsplit=1) |
39 | cur.row_factory = namedtuple_row | 39 | extension = None |
40 | cur.execute('SELECT "mailbox"."mailbox" as "user", "local", "extension", "domain" FROM "mailbox" INNER JOIN "mailbox_mapping" ON "mailbox".id = "mailbox_mapping"."mailbox" WHERE "mailbox"."mailbox" = %(user)s AND ("local" = %(local)s OR "local" IS NULL) AND ("extension" = %(extension)s OR "extension" IS NULL) AND "domain" = %(domain)s', params = {'user': self.args['ccert_subject'], 'local': local, 'extension': extension if extension is not None else '', 'domain': domain}, prepare=True) | 40 | if '+' in local: |
41 | for record in cur: | 41 | local, extension = local.split(sep='+', maxsplit=1) |
42 | logger.debug('Received result: %s', record) | 42 | |
43 | allowed = True | 43 | logger.debug('Parsed address: %s', {'local': local, 'extension': extension, 'domain': domain}) |
44 | |||
45 | with conn.cursor() as cur: | ||
46 | cur.row_factory = namedtuple_row | ||
47 | cur.execute('SELECT "mailbox"."mailbox" as "user", "local", "extension", "domain" FROM "mailbox" INNER JOIN "mailbox_mapping" ON "mailbox".id = "mailbox_mapping"."mailbox" WHERE "mailbox"."mailbox" = %(user)s AND ("local" = %(local)s OR "local" IS NULL) AND ("extension" = %(extension)s OR "extension" IS NULL) AND "domain" = %(domain)s', params = {'user': user, 'local': local, 'extension': extension if extension is not None else '', 'domain': domain}, prepare=True) | ||
48 | for record in cur: | ||
49 | logger.debug('Received result: %s', record) | ||
50 | allowed = True | ||
44 | 51 | ||
45 | action = '550 5.7.0 Sender address not authorized for current user' | 52 | action = '550 5.7.0 Sender address not authorized for current user' |
46 | if allowed: | 53 | if allowed: |
diff --git a/hosts/surtr/email/default.nix b/hosts/surtr/email/default.nix index 9c3e8849..66c39e8f 100644 --- a/hosts/surtr/email/default.nix +++ b/hosts/surtr/email/default.nix | |||
@@ -204,17 +204,15 @@ in { | |||
204 | postscreen_greet_action = "enforce"; | 204 | postscreen_greet_action = "enforce"; |
205 | }; | 205 | }; |
206 | masterConfig = { | 206 | masterConfig = { |
207 | smtps = { | 207 | "465" = { |
208 | type = "inet"; | 208 | type = "inet"; |
209 | private = false; | 209 | private = false; |
210 | command = "smtpd"; | 210 | command = "smtpd -v"; |
211 | args = [ | 211 | args = [ |
212 | "-o" "smtpd_tls_security_level=encrypt" | 212 | "-o" "smtpd_tls_security_level=encrypt" |
213 | "-o" "{smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1, !TLSv1.2}" | 213 | "-o" "{smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1, !TLSv1.2}" |
214 | "-o" "{smtpd_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1, !TLSv1.2}" | 214 | "-o" "{smtpd_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1, !TLSv1.2}" |
215 | "-o" "smtpd_tls_mandatory_ciphers=high" | 215 | "-o" "smtpd_tls_mandatory_ciphers=high" |
216 | "-o" "smtpd_tls_dh1024_param_file=${toString config.security.dhparams.params."postfix-smtps-1024".path}" | ||
217 | "-o" "smtpd_tls_dh512_param_file=${toString config.security.dhparams.params."postfix-smtps-512".path}" | ||
218 | "-o" "{tls_eecdh_auto_curves = X25519 X448}" | 216 | "-o" "{tls_eecdh_auto_curves = X25519 X448}" |
219 | 217 | ||
220 | "-o" "smtpd_tls_wrappermode=yes" | 218 | "-o" "smtpd_tls_wrappermode=yes" |
@@ -223,16 +221,46 @@ in { | |||
223 | "-o" "smtpd_tls_received_header=no" | 221 | "-o" "smtpd_tls_received_header=no" |
224 | "-o" "cleanup_service_name=subcleanup" | 222 | "-o" "cleanup_service_name=subcleanup" |
225 | "-o" "smtpd_client_restrictions=permit_tls_all_clientcerts,reject" | 223 | "-o" "smtpd_client_restrictions=permit_tls_all_clientcerts,reject" |
226 | "-o" "{smtpd_data_restrictions = check_policy_service unix:/run/postfwd3/postfwd3.sock}" | ||
227 | "-o" "smtpd_relay_restrictions=permit_tls_all_clientcerts,reject" | ||
228 | "-o" "{smtpd_sender_restrictions = reject_unknown_sender_domain,reject_unverified_sender,check_policy_service unix:/run/postfix-ccert-sender-policy.sock}" | 224 | "-o" "{smtpd_sender_restrictions = reject_unknown_sender_domain,reject_unverified_sender,check_policy_service unix:/run/postfix-ccert-sender-policy.sock}" |
225 | "-o" ''{smtpd_recipient_restrictions=reject_unauth_pipelining,reject_non_fqdn_recipient,reject_unknown_recipient_domain,check_recipient_access pgsql:${pkgs.writeText "check_recipient_access.cf" '' | ||
226 | hosts = postgresql:///email | ||
227 | dbname = email | ||
228 | query = SELECT action FROM virtual_mailbox_access WHERE lookup = '%s' OR (lookup = regexp_replace('%s', '\+[^@]*@', '@') AND NOT EXISTS (SELECT 1 FROM virtual_mailbox_access WHERE lookup = '%s')) | ||
229 | ''},permit_tls_all_clientcerts,reject}'' | ||
230 | "-o" "smtpd_relay_restrictions=permit_tls_all_clientcerts,reject" | ||
231 | "-o" "{smtpd_data_restrictions = check_policy_service unix:/run/postfwd3/postfwd3.sock}" | ||
229 | "-o" "unverified_sender_reject_code=550" | 232 | "-o" "unverified_sender_reject_code=550" |
230 | "-o" "unverified_sender_reject_reason={Sender address rejected: undeliverable address}" | 233 | "-o" "unverified_sender_reject_reason={Sender address rejected: undeliverable address}" |
234 | "-o" "milter_macro_daemon_name=surtr.yggdrasil.li" | ||
235 | "-o" ''smtpd_milters=${config.services.opendkim.socket}'' | ||
236 | ]; | ||
237 | }; | ||
238 | "466" = { | ||
239 | type = "inet"; | ||
240 | private = false; | ||
241 | command = "smtpd -v"; | ||
242 | args = [ | ||
243 | "-o" "smtpd_tls_security_level=encrypt" | ||
244 | |||
245 | "-o" "smtpd_tls_wrappermode=yes" | ||
246 | "-o" "smtpd_tls_ask_ccert=no" | ||
247 | "-o" "smtpd_tls_req_ccert=no" | ||
248 | "-o" "smtpd_sasl_type=dovecot" | ||
249 | "-o" "smtpd_sasl_path=/run/dovecot-sasl" | ||
250 | "-o" "smtpd_sasl_auth_enable=yes" | ||
251 | "-o" "smtpd_tls_received_header=no" | ||
252 | "-o" "cleanup_service_name=subcleanup" | ||
253 | "-o" "smtpd_client_restrictions=permit_sasl_authenticated,reject" | ||
254 | "-o" "{smtpd_sender_restrictions = reject_unknown_sender_domain,reject_unverified_sender,check_policy_service unix:/run/postfix-ccert-sender-policy.sock}" | ||
231 | "-o" ''{smtpd_recipient_restrictions=reject_unauth_pipelining,reject_non_fqdn_recipient,reject_unknown_recipient_domain,check_recipient_access pgsql:${pkgs.writeText "check_recipient_access.cf" '' | 255 | "-o" ''{smtpd_recipient_restrictions=reject_unauth_pipelining,reject_non_fqdn_recipient,reject_unknown_recipient_domain,check_recipient_access pgsql:${pkgs.writeText "check_recipient_access.cf" '' |
232 | hosts = postgresql:///email | 256 | hosts = postgresql:///email |
233 | dbname = email | 257 | dbname = email |
234 | query = SELECT action FROM virtual_mailbox_access WHERE lookup = '%s' OR (lookup = regexp_replace('%s', '\+[^@]*@', '@') AND NOT EXISTS (SELECT 1 FROM virtual_mailbox_access WHERE lookup = '%s')) | 258 | query = SELECT action FROM virtual_mailbox_access WHERE lookup = '%s' OR (lookup = regexp_replace('%s', '\+[^@]*@', '@') AND NOT EXISTS (SELECT 1 FROM virtual_mailbox_access WHERE lookup = '%s')) |
235 | ''},permit_tls_all_clientcerts,reject}'' | 259 | ''},permit_sasl_authenticated,reject}'' |
260 | "-o" "smtpd_relay_restrictions=permit_sasl_authenticated,reject" | ||
261 | "-o" "{smtpd_data_restrictions = check_policy_service unix:/run/postfwd3/postfwd3.sock}" | ||
262 | "-o" "unverified_sender_reject_code=550" | ||
263 | "-o" "unverified_sender_reject_reason={Sender address rejected: undeliverable address}" | ||
236 | "-o" "milter_macro_daemon_name=surtr.yggdrasil.li" | 264 | "-o" "milter_macro_daemon_name=surtr.yggdrasil.li" |
237 | "-o" ''smtpd_milters=${config.services.opendkim.socket}'' | 265 | "-o" ''smtpd_milters=${config.services.opendkim.socket}'' |
238 | ]; | 266 | ]; |
@@ -256,7 +284,7 @@ in { | |||
256 | smtp_pass = { | 284 | smtp_pass = { |
257 | name = "smtpd"; | 285 | name = "smtpd"; |
258 | type = "pass"; | 286 | type = "pass"; |
259 | command = "smtpd"; | 287 | command = "smtpd -v"; |
260 | }; | 288 | }; |
261 | postscreen = { | 289 | postscreen = { |
262 | name = "smtp"; | 290 | name = "smtp"; |
@@ -413,7 +441,7 @@ in { | |||
413 | dovecotSqlConf = pkgs.writeText "dovecot-sql.conf" '' | 441 | dovecotSqlConf = pkgs.writeText "dovecot-sql.conf" '' |
414 | driver = pgsql | 442 | driver = pgsql |
415 | connect = dbname=email | 443 | connect = dbname=email |
416 | password_query = SELECT NULL as password, 'Y' as nopassword, "user", quota_rule, 'dovecot2' as uid, 'dovecot2' as gid FROM imap_user WHERE "user" = '%n' | 444 | password_query = SELECT (CASE WHEN '%k' = 'valid' AND '%m' = 'EXTERNAL' THEN NULL ELSE "password" END) as password, (CASE WHEN '%k' = 'valid' AND '%m' = 'EXTERNAL' THEN true WHEN password IS NULL THEN true ELSE NULL END) as nopassword, "user", quota_rule, 'dovecot2' as uid, 'dovecot2' as gid FROM imap_user WHERE "user" = '%n' |
417 | user_query = SELECT "user", quota_rule, 'dovecot2' as uid, 'dovecot2' as gid FROM imap_user WHERE "user" = '%n' | 445 | user_query = SELECT "user", quota_rule, 'dovecot2' as uid, 'dovecot2' as gid FROM imap_user WHERE "user" = '%n' |
418 | iterate_query = SELECT "user" FROM imap_user | 446 | iterate_query = SELECT "user" FROM imap_user |
419 | ''; | 447 | ''; |
@@ -445,7 +473,7 @@ in { | |||
445 | 473 | ||
446 | auth_ssl_username_from_cert = yes | 474 | auth_ssl_username_from_cert = yes |
447 | ssl_cert_username_field = commonName | 475 | ssl_cert_username_field = commonName |
448 | auth_mechanisms = external | 476 | auth_mechanisms = plain login external |
449 | 477 | ||
450 | auth_verbose = yes | 478 | auth_verbose = yes |
451 | verbose_ssl = yes | 479 | verbose_ssl = yes |
@@ -501,6 +529,15 @@ in { | |||
501 | group = postfix | 529 | group = postfix |
502 | } | 530 | } |
503 | } | 531 | } |
532 | service auth { | ||
533 | vsz_limit = 2G | ||
534 | |||
535 | unix_listener /run/dovecot-sasl { | ||
536 | mode = 0600 | ||
537 | user = postfix | ||
538 | group = postfix | ||
539 | } | ||
540 | } | ||
504 | 541 | ||
505 | namespace inbox { | 542 | namespace inbox { |
506 | separator = / | 543 | separator = / |
diff --git a/hosts/surtr/postgresql/default.nix b/hosts/surtr/postgresql/default.nix index f0e42ee8..583e4443 100644 --- a/hosts/surtr/postgresql/default.nix +++ b/hosts/surtr/postgresql/default.nix | |||
@@ -262,6 +262,20 @@ in { | |||
262 | 262 | ||
263 | GRANT DELETE ON "mailbox_mapping" TO "spm"; | 263 | GRANT DELETE ON "mailbox_mapping" TO "spm"; |
264 | COMMIT; | 264 | COMMIT; |
265 | |||
266 | BEGIN; | ||
267 | SELECT _v.register_patch('011-password', ARRAY['000-base'], null); | ||
268 | |||
269 | ALTER TABLE mailbox ADD COLUMN password text CONSTRAINT password_non_empty CHECK (password IS DISTINCT FROM '''); | ||
270 | COMMIT; | ||
271 | |||
272 | BEGIN; | ||
273 | SELECT _v.register_patch('012-imap-password', ARRAY['000-base', '002-citext'], null); | ||
274 | |||
275 | DROP VIEW imap_user; | ||
276 | CREATE VIEW imap_user ("user", "password", quota_rule) AS SELECT mailbox.mailbox AS "user", "password", quota_rule FROM mailbox_quota_rule INNER JOIN mailbox ON mailbox_quota_rule.mailbox = mailbox.mailbox; | ||
277 | |||
278 | COMMIT; | ||
265 | ''} | 279 | ''} |
266 | 280 | ||
267 | psql etebase postgres -eXf ${pkgs.writeText "etebase.sql" '' | 281 | psql etebase postgres -eXf ${pkgs.writeText "etebase.sql" '' |
diff --git a/hosts/surtr/ruleset.nft b/hosts/surtr/ruleset.nft index ee72614f..14fc9b79 100644 --- a/hosts/surtr/ruleset.nft +++ b/hosts/surtr/ruleset.nft | |||
@@ -178,7 +178,7 @@ table inet filter { | |||
178 | udp dport 49000-50000 counter name turn-rx accept | 178 | udp dport 49000-50000 counter name turn-rx accept |
179 | 179 | ||
180 | tcp dport 25 counter name smtp-rx accept | 180 | tcp dport 25 counter name smtp-rx accept |
181 | tcp dport 465 counter name submissions-rx accept | 181 | tcp dport {465, 466} counter name submissions-rx accept |
182 | tcp dport 993 counter name imaps-rx accept | 182 | tcp dport 993 counter name imaps-rx accept |
183 | tcp dport 4190 counter name managesieve-rx accept | 183 | tcp dport 4190 counter name managesieve-rx accept |
184 | iifname yggdrasil tcp dport 8432 counter name pgbackrest-rx accept | 184 | iifname yggdrasil tcp dport 8432 counter name pgbackrest-rx accept |
@@ -224,7 +224,7 @@ table inet filter { | |||
224 | udp sport 49000-50000 counter name turn-tx accept | 224 | udp sport 49000-50000 counter name turn-tx accept |
225 | 225 | ||
226 | tcp sport 25 counter name smtp-tx accept | 226 | tcp sport 25 counter name smtp-tx accept |
227 | tcp sport 465 counter name submissions-tx accept | 227 | tcp sport {465, 466} counter name submissions-tx accept |
228 | tcp sport 993 counter name imaps-tx accept | 228 | tcp sport 993 counter name imaps-tx accept |
229 | tcp sport 4190 counter name managesieve-tx accept | 229 | tcp sport 4190 counter name managesieve-tx accept |
230 | tcp sport 8432 counter name pgbackrest-tx accept | 230 | tcp sport 8432 counter name pgbackrest-tx accept |
@@ -232,4 +232,4 @@ table inet filter { | |||
232 | 232 | ||
233 | counter name tx | 233 | counter name tx |
234 | } | 234 | } |
235 | } \ No newline at end of file | 235 | } |