nixos/h2o: disable OCSP stapling w/ Let’s Encrypt (support sunset) (#393765)

Changed files
+45 -34
nixos
modules
services
web-servers
+45 -34
nixos/modules/services/web-servers/h2o/default.nix
···
# Attrset with the ACME certificate names split by whether or not they depend
# on H2O serving challenges.
-
certNames =
let
partition =
acc: vhostSettings:
···
else
acc;
-
certNames' = lib.lists.foldl partition {
dependent = [ ];
independent = [ ];
} acmeEnabledHostsConfigs;
in
-
certNames'
// {
-
all = certNames'.dependent ++ certNames'.independent;
};
mozTLSRecs =
···
names = getNames name value;
-
acmeSettings = lib.optionalAttrs (builtins.elem names.cert certNames.dependent) (
let
acmePort = 80;
acmeChallengePath = "/.well-known/acme-challenge";
···
hasTLSRecommendations = tlsRecommendations != null && mozTLSRecs != null;
-
# NOTE: Let’s Encrypt has sunset OCSP stapling. Mozilla’s
-
# ssl-config-generator is at present still recommending this setting, but
-
# this module will skip setting a stapling value as Let’s Encrypt +
-
# ACME is the most likely use case.
-
#
-
# See: https://github.com/mozilla/ssl-config-generator/issues/323
-
tlsRecAttrs = lib.optionalAttrs hasTLSRecommendations (
-
let
-
recs = mozTLSRecs.${tlsRecommendations};
-
in
-
{
-
min-version = builtins.head recs.tls_versions;
-
cipher-preference = "server";
-
"cipher-suite-tls1.3" = recs.ciphersuites;
}
-
// lib.optionalAttrs (recs.ciphers.openssl != [ ]) {
-
cipher-suite = lib.concatStringsSep ":" recs.ciphers.openssl;
-
}
-
);
headerRecAttrs =
lib.optionalAttrs
···
let
identity =
value.tls.identity
-
++ lib.optional (builtins.elem names.cert certNames.all) {
key-file = "${certs.${names.cert}.directory}/key.pem";
certificate-file = "${certs.${names.cert}.directory}/fullchain.pem";
};
···
groups = config.users.groups;
services = [
config.systemd.services.h2o
-
] ++ lib.optional (certNames.all != [ ]) config.systemd.services.h2o-config-reload;
}
-
) certNames.all;
users = {
users.${cfg.user} =
···
systemd.services.h2o = {
description = "H2O HTTP server";
wantedBy = [ "multi-user.target" ];
-
wants = lib.concatLists (map (certName: [ "acme-finished-${certName}.target" ]) certNames.all);
# Since H2O will be hosting the challenges, H2O must be started
-
before = builtins.map (certName: "acme-${certName}.service") certNames.dependent;
after =
[ "network.target" ]
-
++ builtins.map (certName: "acme-selfsigned-${certName}.service") certNames.all
-
++ builtins.map (certName: "acme-${certName}.service") certNames.independent; # avoid loading self-signed key w/ real cert, or vice-versa
serviceConfig = {
ExecStart = "${h2oExe} --mode 'master'";
···
# of certs end-to-end.
systemd.services.h2o-config-reload =
let
-
tlsTargets = map (certName: "acme-${certName}.target") certNames.all;
-
tlsServices = map (certName: "acme-${certName}.service") certNames.all;
in
-
mkIf (certNames.all != [ ]) {
wantedBy = tlsServices ++ [ "multi-user.target" ];
before = tlsTargets;
after = tlsServices;
unitConfig = {
-
ConditionPathExists = map (certName: "${certs.${certName}.directory}/fullchain.pem") certNames.all;
# Disable rate limiting for this since it may be triggered quickly
# a bunch of times if a lot of certificates are renewed in quick
# succession. The reload itself is cheap, so even doing a lot of them
···
# Attrset with the ACME certificate names split by whether or not they depend
# on H2O serving challenges.
+
acmeCertNames =
let
partition =
acc: vhostSettings:
···
else
acc;
+
certNames = lib.lists.foldl partition {
dependent = [ ];
independent = [ ];
} acmeEnabledHostsConfigs;
in
+
certNames
// {
+
all = certNames.dependent ++ certNames.independent;
};
mozTLSRecs =
···
names = getNames name value;
+
acmeSettings = lib.optionalAttrs (builtins.elem names.cert acmeCertNames.dependent) (
let
acmePort = 80;
acmeChallengePath = "/.well-known/acme-challenge";
···
hasTLSRecommendations = tlsRecommendations != null && mozTLSRecs != null;
+
# ATTENTION: Let’s Encrypt has sunset OCSP stapling.
+
tlsRecAttrs =
+
# If using ACME, this module will disable H2O’s default OCSP
+
# stapling.
+
#
+
# See: https://letsencrypt.org/2024/12/05/ending-ocsp/
+
lib.optionalAttrs (builtins.elem names.cert acmeCertNames.all) {
+
ocsp-update-interval = 0;
}
+
# Mozilla’s ssl-config-generator is at present still
+
# recommending this setting as well, but this module will
+
# skip setting a stapling value as Let’s Encrypt + ACME is
+
# the most likely use case.
+
#
+
# See: https://github.com/mozilla/ssl-config-generator/issues/323
+
// lib.optionalAttrs hasTLSRecommendations (
+
let
+
recs = mozTLSRecs.${tlsRecommendations};
+
in
+
{
+
min-version = builtins.head recs.tls_versions;
+
cipher-preference = "server";
+
"cipher-suite-tls1.3" = recs.ciphersuites;
+
}
+
// lib.optionalAttrs (recs.ciphers.openssl != [ ]) {
+
cipher-suite = lib.concatStringsSep ":" recs.ciphers.openssl;
+
}
+
);
headerRecAttrs =
lib.optionalAttrs
···
let
identity =
value.tls.identity
+
++ lib.optional (builtins.elem names.cert acmeCertNames.all) {
key-file = "${certs.${names.cert}.directory}/key.pem";
certificate-file = "${certs.${names.cert}.directory}/fullchain.pem";
};
···
groups = config.users.groups;
services = [
config.systemd.services.h2o
+
] ++ lib.optional (acmeCertNames.all != [ ]) config.systemd.services.h2o-config-reload;
}
+
) acmeCertNames.all;
users = {
users.${cfg.user} =
···
systemd.services.h2o = {
description = "H2O HTTP server";
wantedBy = [ "multi-user.target" ];
+
wants = lib.concatLists (map (certName: [ "acme-finished-${certName}.target" ]) acmeCertNames.all);
# Since H2O will be hosting the challenges, H2O must be started
+
before = builtins.map (certName: "acme-${certName}.service") acmeCertNames.dependent;
after =
[ "network.target" ]
+
++ builtins.map (certName: "acme-selfsigned-${certName}.service") acmeCertNames.all
+
++ builtins.map (certName: "acme-${certName}.service") acmeCertNames.independent; # avoid loading self-signed key w/ real cert, or vice-versa
serviceConfig = {
ExecStart = "${h2oExe} --mode 'master'";
···
# of certs end-to-end.
systemd.services.h2o-config-reload =
let
+
tlsTargets = map (certName: "acme-${certName}.target") acmeCertNames.all;
+
tlsServices = map (certName: "acme-${certName}.service") acmeCertNames.all;
in
+
mkIf (acmeCertNames.all != [ ]) {
wantedBy = tlsServices ++ [ "multi-user.target" ];
before = tlsTargets;
after = tlsServices;
unitConfig = {
+
ConditionPathExists = map (
+
certName: "${certs.${certName}.directory}/fullchain.pem"
+
) acmeCertNames.all;
# Disable rate limiting for this since it may be triggered quickly
# a bunch of times if a lot of certificates are renewed in quick
# succession. The reload itself is cheap, so even doing a lot of them