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