@cryptotaxi247 / infra / commits / 0b768b82

Allow `umbriel` to send emails as `nixos.org`

Once this change is deployed, we should be able to start sending test emails from `@nixos.org` email addresses using `umbriel`. I updated our SPF record in a way such that it should allow `umbriel` without breaking our existing email sending capabilities with ImprovMX. This does *not* change our MX (yet): ImprovMX will still be receiving emails send to `nixos.org`. To verify that we can receive emails sent to `nixos.org` addresses, I plan to edit `/etc/hosts` on my personal mailserver and send some test emails. Do folks have better ideas for testing this out?

Jeremy Fleischman committed Feb 22, 2025 at 17:32 UTC 0b768b820025f2cbd7744a539f313279bdef7e4d
5 files changed +80 -46
non-critical-infra/hosts/umbriel/README.md
-11
@@ -1,12 +1 @@
1 # `umbriel`
2 -
3 -## Provisioning
4 -
5 -If you recreate `umbriel`, it will generate a new `DKIM` signature. That's ok to
6 -do, but you'll need to update the corresponding `mail._domainkey.*` `TXT` DNS
7 -record in `terraform/dns.tf` with the generated key in
8 -`/var/dkim/mail-test.nixos.org.mail.txt`.
9 -
10 -TODO: declaratively manage the `DKIM` key once
11 -<https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/merge_requests/344>
12 -lands.
non-critical-infra/modules/mailserver/default.nix
+29 -4
@@ -9,8 +9,33 @@
9
10 fqdn = config.networking.fqdn;
11
12 - # TODO: change to `nixos.org` when ready
13 - domains = [ "mail-test.nixos.org" ];
12 + domains = [ "nixos.org" ];
13 + };
14 +
15 + sops.secrets."nixos.org.mail.key" = {
16 + format = "binary";
17 + owner = "opendkim";
18 + group = "opendkim";
19 + mode = "0600";
20 +
21 + # How to generate:
22 + #
23 + # ```console
24 + # cd non-critical-infra
25 + # DOMAIN=nixos.org
26 + # SELECTOR=mail
27 + # PRIVATE_KEY_PATH=secrets/$DOMAIN.$SELECTOR.key.umbriel
28 + # nix shell nixpkgs#opendkim --command opendkim-genkey --selector="$SELECTOR" --domain="$DOMAIN" --bits=1024
29 + # mv mail.private "$PRIVATE_KEY_PATH"
30 + # sops encrypt --in-place "$PRIVATE_KEY_PATH"
31 + # ```
32 + #
33 + # Next, look at `mail.txt` and update DNS accordingly.
34 + sopsFile = ../../secrets/nixos.org.mail.key.umbriel;
35 +
36 + # Ensure the file gets symlinked to where Simple NixOS Mailserver expects
37 + # to find it.
38 + path = "${config.mailserver.dkimKeyDirectory}/nixos.org.mail.key";
39 };
40
41 ### Mailing lists go here ###
@@ -22,14 +47,14 @@
47 # follow the instructions.
48 mailing-lists = {
49 # TODO: replace with the real `nixos.org` mailing lists.
25 - "test-list@mail-test.nixos.org" = {
50 + "test-list@nixos.org" = {
51 forwardTo = [
52 "jfly@playground.jflei.com"
53 ../../secrets/jfly-email-address.umbriel
54 "jeremyfleischman+subscriber@gmail.com"
55 ];
56 };
32 - "test-sender@mail-test.nixos.org" = {
57 + "test-sender@nixos.org" = {
58 forwardTo = [ "jeremy@playground.jflei.com" ];
59 loginAccount.encryptedHashedPassword = ../../secrets/test-sender-email-login.umbriel;
60 };
non-critical-infra/packages/encrypt-email/encrypt-email.py
+1 -1
@@ -149,7 +149,7 @@ def login(address_id: str, force: bool) -> None:
149
150 nix_code = dedent(
151 f"""\
152 - "{address_id}@mail-test.nixos.org" = {{
152 + "{address_id}@nixos.org" = {{
153 forwardTo = [
154 # Add emails here
155 ];
non-critical-infra/secrets/nixos.org.mail.key.umbriel new
+28
@@ -0,0 +1,28 @@
1 +{
2 + "data": "ENC[AES256_GCM,data:xzDS4Dzi5vDKnLRZCmaH/j5oI5rknLLSzm/BAcqVL3pReOW7rE6C7QGY2EoS99TfLQ3BxB1ckrB4f3/pefEH018KIgJ7UeaeWebmkBFsXF8r+4m/yOEiDWgnGKnsq1EIzbs67AvVYV4rcEPUmTrkeFYd6TOEqIhOSM/6DO6Lg6x+ioAGSEHK6eGgRVIFVjByOhvufhOib1exX2GEM6ayluutig6ToxdnE5jRGIY1gp1bOuwDSazD07RIZanAlS0sjZvMOASacLRvwwM0HC2NEsfHHjy1YMNr1WIbW3HWV/r/j0FTGEDI0W7IP9b0livuAX+K+EixZ6Cw+9ZMhUQMZzfLRcR/c8Zo+hgmc8SJtaPCwck1RlRqOXbqceH1oMIWpYxWmdLIhSQTNgoiMp6L4JzVz7flpnzzEB2U4tkIMwnEBpa8Jz0TAtgyw9clgdbgR+11T56CGzdykbDxPjkDFhnDjhpqmUDgq8rSRRh0fhGdi8FX2rUQXnOMVrYALV0zkAN4cg2Px5dIjlMBZmHkawR7U8phYMBfQRC2FRz9USIrYVTof+J+UImWbWJwa2zMDb5ogRwOF4XhI6NNolCdorlmCvbdG7nE0LbnIbdC1b5VEeVmo0YObnm5HcI11ctugirpXb1kIcfhpg7Zaq3s2iRCbaiy5UVqQNbcKmLktItdxfAzWPQQyyQqA5xA7vlr2jQvKYP7i86H2woKEUwum8htYJewYB3w91NsJaoPikuVLmrBe00hoMwXqb/MtstEhJbI6FN0SC9r+YE8OMvlyoTzmxrysq4/X5qxeXhpbH7fqUxY77UPtOjHfG/vA8cpYHNBnvjXiRGFQwB/P4/fTdtJA5UZiO+EwvNyepadazqmN6szXxHLOcEu+qnoEGsAcbQjjN+y2IUgTXTpje2qfH75141oqhG/DHT4DFDRkI6AMuwQAVtM+6Nqk4hTzJVua8AK+HS+UwBqap0GbtVg3inSuhmcq2wQQzezz6XNpajvEvFosSpIHBwxkBsY6gn/RiCEm7iHvdePhGzOBylX+kLRLp1/PatRLCBVnZ5uwpVGhzdIMvoz7i7+Grmify1XlLuY8hxcmjJzPVn3El5euSeSamDkgdGkaaWeDRaU33OIBo0aRLFnmQeNfm2wwKYba1moqCtuZxSBfze9wiOacAmicUZZ9i2fjcYa/ejtgxTl/k10S4Z6YVhxHvK1y4D5r2zzFA==,iv:lYv/cuI7dQnBq/UAOh0tP1e+GOPgMKzHc66+cmyjZXY=,tag:mEYrazYwd93xOV5Fw5vqHg==,type:str]",
3 + "sops": {
4 + "kms": null,
5 + "gcp_kms": null,
6 + "azure_kv": null,
7 + "hc_vault": null,
8 + "age": [
9 + {
10 + "recipient": "age15vcp7875xwtf64j4yshyld0a3hpgzv6n2kxky493s3q0swr9hdaqxugpv6",
11 + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBBalpYcW9xYUZsTi9ZUDJV\nWEV0ZEZKWnMyNXdXUHBmRkkyS1gzaU81bGt3CkZoZVp4OUhmKzU0dkNmOHRFb1I5\nQjBacWVYYWdjSHh4NHFGQlhyNENmb2sKLS0tIGNmZFZjQ2dMYTQ1OFJnNFBLejNo\ndDJ0SUgwVHcvNjRTVVZ5Wis0NDZ1dWcKi2RTUzwVVg2x+9L+96QNwpA32IupkzV7\nmTfRtizJXHbzfUBSCUiuVis92bsk05PBRB9Zw5hQMY7K7ZAkctLprQ==\n-----END AGE ENCRYPTED FILE-----\n"
12 + },
13 + {
14 + "recipient": "age1j3mkgedmeru63vwww6m44zfw09tg8yw6xdzstaq7ejfkvgcau40qwakm8x",
15 + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBoWFdCSWd4Z1VwOEU5L3or\nelpPcm9JcDE0eXB1L1hrcVFuaGxQRDN5b1V3CnlyZzhnbkNlVUswWUFLclBqOHdv\nSEwvbTN6R3oyMTc4aS8xSDhkcVFpNVUKLS0tIGh0ZWRuUkFMSEhvRjZuV05zbTNR\ncytkaXlIOG1vQ2RWVnZLaGtJOFQ2dDQK04Fq2wcKRINC9iTCWuDMbJY8QPQAknQk\nTOEvgZ4DRQa/MnG5WGZkoA0PygirZNQTJFge2RRa0YMY+wypQvQNgg==\n-----END AGE ENCRYPTED FILE-----\n"
16 + },
17 + {
18 + "recipient": "age1jrh8yyq3swjru09s75s4mspu0mphh7h6z54z946raa9wx3pcdegq0x8t4h",
19 + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBMWUtZd2RzOEhKbnE4THZL\nOXRsMHhJVk9XancrTi9XbGF2SWl4SjU3bm1ZCjlDWlZ3WlU3bDZma0gvbUIwdk5P\nMGt6YVZjOVBGRkFnQ3ZVakl0Z2xjTTgKLS0tIFFzbWlDNi9rNloxUzJHbFd6Qlgr\neVVudjJFSThLeWMvVFozTUg1Yy9JSVkKH4gAq3XTuWVlylHxIOU5l4pbrsU0cFAA\nSAZUQk3TsBw427B02uocjjpTQByuxFxAf3hoV5WgFfEZf04gMlEUUQ==\n-----END AGE ENCRYPTED FILE-----\n"
20 + }
21 + ],
22 + "lastmodified": "2025-03-11T08:24:47Z",
23 + "mac": "ENC[AES256_GCM,data:NpYf1LwINXQyuiN4jCkp95Q1UUnp+7M++k28GfGM1oWquOYtUH0wbRKEk1ooIABGSj8uz6qx5KBBGRh8eU6ldh7qwYqqTLDey2qeJ/5kt1ZTC5aOm9qtDWYWYzzb+lznOegSL25ny16d6ipOiKJeCdfcrgJBSr+Y6MCE+GxqGEg=,iv:pzx0e4ySgKmer/zF3wsrN4vVAzPMWE8RPPbkIkx0W5o=,tag:Smm0vqPToCcy2XHyHyVkUw==,type:str]",
24 + "pgp": null,
25 + "unencrypted_suffix": "_unencrypted",
26 + "version": "3.9.4"
27 + }
28 +}
\ No newline at end of file
terraform/dns.tf
+22 -30
@@ -206,11 +206,6 @@ locals {
206 type = "TXT"
207 value = "9e10a04a4b"
208 },
209 - {
210 - hostname = "nixos.org"
211 - type = "TXT"
212 - value = "v=spf1 include:spf.improvmx.com ~all"
213 - },
209 {
210 # hetzner ax162-r 2548595
211 hostname = "elated-minsky.builder.nixos.org"
@@ -410,29 +405,34 @@ locals {
405 value = "2a01:4f9:c012:8178::"
406 },
407
413 - # Mailserver configuration for `mail-test.nixos.org`
408 + # Mailserver configuration for `nixos.org`
409 + # TODO: remove the 2 MX records for improvmx below in favor of this once
410 + # we're ready to switch to the new mailserver:
411 + # https://github.com/NixOS/infra/issues/485
412 + # {
413 + # hostname = "nixos.org"
414 + # type = "MX"
415 + # value = "umbriel.nixos.org"
416 + # },
417 {
415 - hostname = "mail-test.nixos.org"
416 - type = "MX"
417 - value = "umbriel.nixos.org"
418 - },
419 - {
420 - hostname = "mail-test.nixos.org"
418 + hostname = "nixos.org"
419 type = "TXT"
422 - value = "v=spf1 mx ~all"
420 + # TODO: simplify to just a `mx` rule once umbriel is our one and only
421 + # mailserver:
422 + # https://github.com/NixOS/infra/issues/485
423 + # value = "v=spf1 mx ~all"
424 + value = "v=spf1 include:spf.improvmx.com a:umbriel.nixos.org ~all"
425 },
426 {
425 - hostname = "mail._domainkey.mail-test.nixos.org"
427 + hostname = "mail._domainkey.nixos.org"
428 type = "TXT"
427 - # From `/var/dkim/mail-test.nixos.org.mail.txt` on `umbriel`.
428 - value = "v=DKIM1; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDG4Tx788TCAW/sv1h6JefVJChqbaot1yhycwEq0Uo5x9ZIyq43Dkxxl7LdsHIW75HMI7aTKQRru+5xQ26vQmwiIRFJlJlRSYzlZZ2xnFZPXQ27dXnFh7MxLGC7YEyQFksiA2xxgqtQSyIvwu1whm2WK0fXkoJf87SgTtVjjKjnkQIDAQAB"
429 + # See `nixos.org.mail.key` in `non-critical-infra/modules/mailserver/default.nix`.
430 + value = "v=DKIM1; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDcgNq4+Y23GxN8Mdza437tL5DuJJZU1y6VzTCwSi6cBNLyBDci2cmqXx/gm1sA3yv7+h+8/OyJpEgcbCIW/Ygs1XLuECqvXVX8MU6Djn4KY+d2sU1tlUdqvNM86puoneQtjEv9rDsjf3HGqaeOcjetFnQW7H+qcNcaEShxyKztzQIDAQAB"
431 },
432 {
431 - hostname = "_dmarc.mail-test.nixos.org"
433 + hostname = "_dmarc.nixos.org"
434 type = "TXT"
433 - # TODO: consider making this strict (`v=DMARC1; p=reject; adkim=s; aspf=s;`),
434 - # but make sure this doesn't break mailing lists: https://dmarcian.com/mailing-lists-dmarc/
435 - value = "v=DMARC1; p=none"
435 + value = "v=DMARC1; p=none"
436 },
437 ]
438 }
@@ -450,9 +450,9 @@ resource "netlify_dns_record" "nixos" {
450 value = each.value.value
451 }
452
453 +### TODO: remove, see https://github.com/NixOS/infra/issues/485 ###
454 # MX records both have the same hostname and type and would clash on the above
455 # mapping.
455 -
456 resource "netlify_dns_record" "nixos_MX1" {
457 zone_id = local.zone_id
458 hostname = "nixos.org"
@@ -467,8 +467,6 @@ resource "netlify_dns_record" "nixos_MX2" {
467 value = "mx2.improvmx.com"
468 }
469
470 -# additional records for improvmx for dkim & dmarc
471 -
470 resource "netlify_dns_record" "nixos_DKIM1" {
471 zone_id = local.zone_id
472 hostname = "dkimprovmx1._domainkey.nixos.org"
@@ -482,13 +480,7 @@ resource "netlify_dns_record" "nixos_DKIM2" {
480 type = "CNAME"
481 value = "dkimprovmx2.improvmx.com"
482 }
485 -
486 -resource "netlify_dns_record" "nixos_DMARC" {
487 - zone_id = local.zone_id
488 - hostname = "_dmarc.nixos.org"
489 - type = "TXT"
490 - value = "v=DMARC1; p=none;"
491 -}
483 +### END TODO: remove ###
484
485 resource "netlify_dns_record" "nixos_google_verification" {
486 zone_id = local.zone_id