Merge pull request #121750 from m1cr0man/master

nixos/acme: Ensure certs are always protected

authored by

Martin Weinelt and committed by
GitHub
dc940ecd f51caec3

+27 -13
+10 -6
nixos/modules/security/acme.nix
··· 46 46 serviceConfig = commonServiceConfig // { 47 47 StateDirectory = "acme/.minica"; 48 48 BindPaths = "/var/lib/acme/.minica:/tmp/ca"; 49 + UMask = 0077; 49 50 }; 50 51 51 52 # Working directory will be /tmp ··· 54 55 --ca-key ca/key.pem \ 55 56 --ca-cert ca/cert.pem \ 56 57 --domains selfsigned.local 57 - 58 - chmod 600 ca/* 59 58 ''; 60 59 }; 61 60 ··· 196 195 197 196 serviceConfig = commonServiceConfig // { 198 197 Group = data.group; 198 + UMask = 0027; 199 199 200 200 StateDirectory = "acme/${cert}"; 201 201 ··· 220 220 cat cert.pem chain.pem > fullchain.pem 221 221 cat key.pem fullchain.pem > full.pem 222 222 223 - chmod 640 * 224 - 225 223 # Group might change between runs, re-apply it 226 224 chown 'acme:${data.group}' * 225 + 226 + # Default permissions make the files unreadable by group + anon 227 + # Need to be readable by group 228 + chmod 640 * 227 229 ''; 228 230 }; 229 231 ··· 340 342 fi 341 343 342 344 mv domainhash.txt certificates/ 343 - chmod 640 certificates/* 344 - chmod -R u=rwX,g=,o= accounts/* 345 345 346 346 # Group might change between runs, re-apply it 347 347 chown 'acme:${data.group}' certificates/* ··· 357 357 ln -sf fullchain.pem out/cert.pem 358 358 cat out/key.pem out/fullchain.pem > out/full.pem 359 359 fi 360 + 361 + # By default group will have no access to the cert files. 362 + # This chmod will fix that. 363 + chmod 640 out/* 360 364 ''; 361 365 }; 362 366 };
+17 -7
nixos/tests/acme.nix
··· 330 330 331 331 with subtest("Can request certificate with HTTPS-01 challenge"): 332 332 webserver.wait_for_unit("acme-finished-a.example.test.target") 333 - check_fullchain(webserver, "a.example.test") 334 - check_issuer(webserver, "a.example.test", "pebble") 335 - check_connection(client, "a.example.test") 336 333 337 334 with subtest("Certificates and accounts have safe + valid permissions"): 338 335 group = "${nodes.webserver.config.security.acme.certs."a.example.test".group}" 339 336 webserver.succeed( 340 - f"test $(stat -L -c \"%a %U %G\" /var/lib/acme/a.example.test/* | tee /dev/stderr | grep '640 acme {group}' | wc -l) -eq 5" 337 + f"test $(stat -L -c '%a %U %G' /var/lib/acme/a.example.test/*.pem | tee /dev/stderr | grep '640 acme {group}' | wc -l) -eq 5" 341 338 ) 342 339 webserver.succeed( 343 - f"test $(stat -L -c \"%a %U %G\" /var/lib/acme/.lego/a.example.test/**/* | tee /dev/stderr | grep '640 acme {group}' | wc -l) -eq 5" 340 + f"test $(stat -L -c '%a %U %G' /var/lib/acme/.lego/a.example.test/**/a.example.test* | tee /dev/stderr | grep '600 acme {group}' | wc -l) -eq 4" 344 341 ) 345 342 webserver.succeed( 346 - f"test $(stat -L -c \"%a %U %G\" /var/lib/acme/a.example.test | tee /dev/stderr | grep '750 acme {group}' | wc -l) -eq 1" 343 + f"test $(stat -L -c '%a %U %G' /var/lib/acme/a.example.test | tee /dev/stderr | grep '750 acme {group}' | wc -l) -eq 1" 347 344 ) 348 345 webserver.succeed( 349 - f"test $(find /var/lib/acme/accounts -type f -exec stat -L -c \"%a %U %G\" {{}} \\; | tee /dev/stderr | grep -v '600 acme {group}' | wc -l) -eq 0" 346 + f"test $(find /var/lib/acme/accounts -type f -exec stat -L -c '%a %U %G' {{}} \\; | tee /dev/stderr | grep -v '600 acme {group}' | wc -l) -eq 0" 350 347 ) 351 348 349 + with subtest("Certs are accepted by web server"): 350 + webserver.succeed("systemctl start nginx.service") 351 + check_fullchain(webserver, "a.example.test") 352 + check_issuer(webserver, "a.example.test", "pebble") 353 + check_connection(client, "a.example.test") 354 + 355 + # Selfsigned certs tests happen late so we aren't fighting the system init triggering cert renewal 352 356 with subtest("Can generate valid selfsigned certs"): 353 357 webserver.succeed("systemctl clean acme-a.example.test.service --what=state") 354 358 webserver.succeed("systemctl start acme-selfsigned-a.example.test.service") 355 359 check_fullchain(webserver, "a.example.test") 356 360 check_issuer(webserver, "a.example.test", "minica") 361 + # Check selfsigned permissions 362 + webserver.succeed( 363 + f"test $(stat -L -c '%a %U %G' /var/lib/acme/a.example.test/*.pem | tee /dev/stderr | grep '640 acme {group}' | wc -l) -eq 5" 364 + ) 357 365 # Will succeed if nginx can load the certs 358 366 webserver.succeed("systemctl start nginx-config-reload.service") 359 367 ··· 376 384 webserver.wait_for_unit("acme-finished-a.example.test.target") 377 385 check_connection_key_bits(client, "a.example.test", "384") 378 386 webserver.succeed("grep testing /var/lib/acme/a.example.test/test") 387 + # Clean to remove the testing file (and anything else messy we did) 388 + webserver.succeed("systemctl clean acme-a.example.test.service --what=state") 379 389 380 390 with subtest("Correctly implements OCSP stapling"): 381 391 switch_to(webserver, "ocsp-stapling")