nixos/modules: Add security.pki.caBundle option and make all services use it for CA bundles (#352244)

Previously some modules used `config.environment.etc."ssl/certs/ca-certificates.crt".source`, some used `"/etc/ssl/certs/ca-certificates.crt"`, and some used `"${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"`. These were all bad in one way or another:

- `config.environment.etc."ssl/certs/ca-certificates.crt".source` relies on `source` being set; if `text` is set instead this breaks, introducing a weird undocumented requirement
- `"/etc/ssl/certs/ca-certificates.crt"` is probably okay but very un-nix. It's a magic string, and the path doesn't change when the file changes (and so you can't trigger service reloads, for example, when the contents change in a new system activation)
- `"${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"` silently doesn't include the options from `security.pki`

Co-authored-by: Shelvacu <git@shelvacu.com>

shelvacu 1a4575f9 f5dadc8f

+2
nixos/doc/manual/release-notes/rl-2505.section.md
···
- `services.avahi.ipv6` now defaults to true.
+
- All services that require a root certificate bundle now use the value of a new read-only option, `security.pki.caBundle`.
+
- hddfancontrol has been updated to major release 2. See the [migration guide](https://github.com/desbma/hddfancontrol/tree/master?tab=readme-ov-file#migrating-from-v1x), as there are breaking changes.
- The Home Assistant module has new options {option}`services.home-assistant.blueprints.automation`, `services.home-assistant.blueprints.script`, and {option}`services.home-assistant.blueprints.template` that allow for the declarative installation of [blueprints](https://www.home-assistant.io/docs/blueprint/) into the appropriate configuration directories.
+20 -12
nixos/modules/security/ca.nix
···
...
}:
let
-
cfg = config.security.pki;
cacertPackage = pkgs.cacert.override {
···
'';
};
+
security.pki.caBundle = lib.mkOption {
+
type = lib.types.path;
+
readOnly = true;
+
description = ''
+
(Read-only) the path to the final bundle of certificate authorities as a single file.
+
'';
+
};
};
-
config = lib.mkIf cfg.installCACerts {
+
config = lib.mkMerge [
+
(lib.mkIf cfg.installCACerts {
-
# NixOS canonical location + Debian/Ubuntu/Arch/Gentoo compatibility.
-
environment.etc."ssl/certs/ca-certificates.crt".source = caBundle;
+
# NixOS canonical location + Debian/Ubuntu/Arch/Gentoo compatibility.
+
environment.etc."ssl/certs/ca-certificates.crt".source = caBundle;
-
# Old NixOS compatibility.
-
environment.etc."ssl/certs/ca-bundle.crt".source = caBundle;
+
# Old NixOS compatibility.
+
environment.etc."ssl/certs/ca-bundle.crt".source = caBundle;
-
# CentOS/Fedora compatibility.
-
environment.etc."pki/tls/certs/ca-bundle.crt".source = caBundle;
+
# CentOS/Fedora compatibility.
+
environment.etc."pki/tls/certs/ca-bundle.crt".source = caBundle;
-
# P11-Kit trust source.
-
environment.etc."ssl/trust-source".source = "${cacertPackage.p11kit}/etc/ssl/trust-source";
-
-
};
+
# P11-Kit trust source.
+
environment.etc."ssl/trust-source".source = "${cacertPackage.p11kit}/etc/ssl/trust-source";
+
})
+
{ security.pki.caBundle = caBundle; }
+
];
}
+1 -1
nixos/modules/services/audio/gonic.nix
···
BindReadOnlyPaths = [
# gonic can access scrobbling services
"-/etc/resolv.conf"
-
"-/etc/ssl/certs/ca-certificates.crt"
+
"${config.security.pki.caBundle}:/etc/ssl/certs/ca-certificates.crt"
builtins.storeDir
] ++ cfg.settings.music-path
++ lib.optional (cfg.settings.tls-cert != null) cfg.settings.tls-cert
+1 -3
nixos/modules/services/audio/navidrome.nix
···
BindReadOnlyPaths =
[
# navidrome uses online services to download additional album metadata / covers
-
"${
-
config.environment.etc."ssl/certs/ca-certificates.crt".source
-
}:/etc/ssl/certs/ca-certificates.crt"
+
"${config.security.pki.caBundle}:/etc/ssl/certs/ca-certificates.crt"
builtins.storeDir
"/etc"
]
+1 -1
nixos/modules/services/continuous-integration/gocd-agent/default.nix
···
rm -f config/autoregister.properties
ln -s "${pkgs.writeText "autoregister.properties" cfg.agentConfig}" config/autoregister.properties
-
${pkgs.git}/bin/git config --global --add http.sslCAinfo /etc/ssl/certs/ca-certificates.crt
+
${pkgs.git}/bin/git config --global --add http.sslCAinfo ${config.security.pki.caBundle}
${pkgs.jre}/bin/java ${lib.concatStringsSep " " cfg.startupOptions} \
${lib.concatStringsSep " " cfg.extraOptions} \
-jar ${pkgs.gocd-agent}/go-agent/agent-bootstrapper.jar \
+1 -1
nixos/modules/services/continuous-integration/gocd-server/default.nix
···
path = cfg.packages;
script = ''
-
${pkgs.git}/bin/git config --global --add http.sslCAinfo /etc/ssl/certs/ca-certificates.crt
+
${pkgs.git}/bin/git config --global --add http.sslCAinfo ${config.security.pki.caBundle}
${pkgs.jre}/bin/java -server ${concatStringsSep " " cfg.startupOptions} \
${concatStringsSep " " cfg.extraOptions} \
-jar ${pkgs.gocd-server}/go-server/lib/go.jar
+4 -3
nixos/modules/services/mail/postfix.nix
···
tlsTrustedAuthorities = lib.mkOption {
type = lib.types.str;
-
default = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
-
defaultText = lib.literalExpression ''"''${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"'';
+
default = config.security.pki.caBundle;
+
defaultText = lib.literalExpression "config.security.pki.caBundle";
+
example = lib.literalExpression ''"''${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"'';
description = ''
-
File containing trusted certification authorities (CA) to verify certificates of mailservers contacted for mail delivery. This basically sets smtp_tls_CAfile and enables opportunistic tls. Defaults to NixOS trusted certification authorities.
+
File containing trusted certification authorities (CA) to verify certificates of mailservers contacted for mail delivery. This sets [smtp_tls_CAfile](https://www.postfix.org/postconf.5.html#smtp_tls_CAfile). Defaults to system trusted certificates (see `security.pki.*` options).
'';
};
+1 -1
nixos/modules/services/misc/db-rest.nix
···
};
environment = {
NODE_ENV = "production";
-
NODE_EXTRA_CA_CERTS = "/etc/ssl/certs/ca-certificates.crt";
+
NODE_EXTRA_CA_CERTS = config.security.pki.caBundle;
HOSTNAME = cfg.host;
PORT = toString cfg.port;
};
+1 -1
nixos/modules/services/misc/gitlab.nix
···
${optionalString (cfg.smtp.authentication != null) "authentication: :${cfg.smtp.authentication},"}
enable_starttls_auto: ${boolToString cfg.smtp.enableStartTLSAuto},
tls: ${boolToString cfg.smtp.tls},
-
ca_file: "/etc/ssl/certs/ca-certificates.crt",
+
ca_file: "${config.security.pki.caBundle}",
openssl_verify_mode: '${cfg.smtp.opensslVerifyMode}'
}
end
+1 -1
nixos/modules/services/misc/portunus.nix
···
in
{
PORTUNUS_SERVER_HTTP_SECURE = "true";
-
PORTUNUS_SLAPD_TLS_CA_CERTIFICATE = "/etc/ssl/certs/ca-certificates.crt";
+
PORTUNUS_SLAPD_TLS_CA_CERTIFICATE = config.security.pki.caBundle;
PORTUNUS_SLAPD_TLS_CERTIFICATE = "${acmeDirectory}/cert.pem";
PORTUNUS_SLAPD_TLS_DOMAIN_NAME = cfg.domain;
PORTUNUS_SLAPD_TLS_PRIVATE_KEY = "${acmeDirectory}/key.pem";
+1 -1
nixos/modules/services/misc/radicle.nix
···
BindReadOnlyPaths = [
"${cfg.configFile}:${env.RAD_HOME}/config.json"
"${if lib.types.path.check cfg.publicKey then cfg.publicKey else pkgs.writeText "radicle.pub" cfg.publicKey}:${env.RAD_HOME}/keys/radicle.pub"
+
"${config.security.pki.caBundle}:/etc/ssl/certs/ca-certificates.crt"
];
KillMode = "process";
StateDirectory = [ "radicle" ];
···
{
BindReadOnlyPaths = [
"-/etc/resolv.conf"
-
"/etc/ssl/certs/ca-certificates.crt"
"/run/systemd"
];
AmbientCapabilities = "";
+1 -3
nixos/modules/services/misc/tandoor-recipes.nix
···
RuntimeDirectory = "tandoor-recipes";
BindReadOnlyPaths = [
-
"${
-
config.environment.etc."ssl/certs/ca-certificates.crt".source
-
}:/etc/ssl/certs/ca-certificates.crt"
+
"${config.security.pki.caBundle}:/etc/ssl/certs/ca-certificates.crt"
builtins.storeDir
"-/etc/resolv.conf"
"-/etc/nsswitch.conf"
+2 -2
nixos/modules/services/monitoring/ocsinventory-agent.nix
···
ca = lib.mkOption {
type = lib.types.path;
-
default = "/etc/ssl/certs/ca-certificates.crt";
+
default = config.security.pki.caBundle;
+
defaultText = lib.literalExpression "config.security.pki.caBundle";
description = ''
Path to CA certificates file in PEM format, for server
SSL certificate validation.
···
};
default = { };
example = {
-
ca = "/etc/ssl/certs/ca-certificates.crt";
debug = true;
server = "https://ocsinventory.localhost:8080/ocsinventory";
tag = "01234567890123";
+2 -1
nixos/modules/services/monitoring/parsedmarc.nix
···
cert_path = lib.mkOption {
type = lib.types.path;
-
default = "/etc/ssl/certs/ca-certificates.crt";
+
default = config.security.pki.caBundle;
+
defaultText = lib.literalExpression "config.security.pki.caBundle";
description = ''
The path to a TLS certificate bundle used to verify
the server's certificate.
+1 -1
nixos/modules/services/monitoring/uptime-kuma.nix
···
default = { };
example = {
PORT = "4000";
-
NODE_EXTRA_CA_CERTS = "/etc/ssl/certs/ca-certificates.crt";
+
NODE_EXTRA_CA_CERTS = lib.literalExpression "config.security.pki.caBundle";
};
description = ''
Additional configuration for Uptime Kuma, see
+2 -1
nixos/modules/services/networking/biboumi.nix
···
};
options.ca_file = lib.mkOption {
type = lib.types.path;
-
default = "/etc/ssl/certs/ca-certificates.crt";
+
default = config.security.pki.caBundle;
+
defaultText = lib.literalExpression "config.security.pki.caBundle";
description = ''
Specifies which file should be used as the list of trusted CA
when negotiating a TLS session.
+1 -2
nixos/modules/services/networking/privoxy.nix
···
# This allows setting absolute key/crt paths
ca-directory = "/var/empty";
certificate-directory = "/run/privoxy/certs";
-
trusted-cas-file = "/etc/ssl/certs/ca-certificates.crt";
+
trusted-cas-file = config.security.pki.caBundle;
});
-
};
imports =
+2 -2
nixos/modules/services/networking/stunnel.nix
···
description = ''
Define the client configurations.
-
By default, verifyChain and OCSPaia are enabled and a CAFile is provided from pkgs.cacert.
+
By default, verifyChain and OCSPaia are enabled and CAFile is set to `security.pki.caBundle`.
See "SERVICE-LEVEL OPTIONS" in {manpage}`stunnel(8)`.
'';
···
applyDefaults =
c:
{
-
CAFile = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
+
CAFile = config.security.pki.caBundle;
OCSPaia = true;
verifyChain = true;
}
+1 -1
nixos/modules/services/networking/unbound.nix
···
interface = mkDefault ([ "127.0.0.1" ] ++ (optional config.networking.enableIPv6 "::1"));
access-control = mkDefault ([ "127.0.0.0/8 allow" ] ++ (optional config.networking.enableIPv6 "::1/128 allow"));
auto-trust-anchor-file = mkIf cfg.enableRootTrustAnchor rootTrustAnchorFile;
-
tls-cert-bundle = mkDefault "/etc/ssl/certs/ca-certificates.crt";
+
tls-cert-bundle = mkDefault config.security.pki.caBundle;
# prevent race conditions on system startup when interfaces are not yet
# configured
ip-freebind = mkDefault true;
+1 -1
nixos/modules/services/search/hound.nix
···
User = cfg.user;
Group = cfg.group;
WorkingDirectory = cfg.home;
-
ExecStartPre = "${pkgs.git}/bin/git config --global --replace-all http.sslCAinfo /etc/ssl/certs/ca-certificates.crt";
+
ExecStartPre = "${pkgs.git}/bin/git config --global --replace-all http.sslCAinfo ${config.security.pki.caBundle}";
ExecStart = "${cfg.package}/bin/houndd -addr ${cfg.listen} -conf /etc/hound/config.json";
};
};
+1 -1
nixos/modules/services/system/nix-daemon.nix
···
environment =
cfg.envVars
// {
-
CURL_CA_BUNDLE = "/etc/ssl/certs/ca-certificates.crt";
+
CURL_CA_BUNDLE = config.security.pki.caBundle;
}
// config.networking.proxy.envVars;
+1 -1
nixos/modules/services/torrent/transmission.nix
···
wantedBy = [ "multi-user.target" ];
environment = {
-
CURL_CA_BUNDLE = etc."ssl/certs/ca-certificates.crt".source;
+
CURL_CA_BUNDLE = config.security.pki.caBundle;
TRANSMISSION_WEB_HOME = lib.mkIf (cfg.webHome != null) cfg.webHome;
};
+1 -1
nixos/modules/services/web-apps/cryptpad.nix
···
"-/etc/resolv.conf"
"-/run/systemd"
"/etc/hosts"
-
"/etc/ssl/certs/ca-certificates.crt"
+
"${config.security.pki.caBundle}:/etc/ssl/certs/ca-certificates.crt"
];
};
};
+1 -1
nixos/modules/services/web-apps/dex.nix
···
"-/etc/localtime"
"-/etc/nsswitch.conf"
"-/etc/resolv.conf"
-
"-/etc/ssl/certs/ca-certificates.crt"
+
"${config.security.pki.caBundle}:/etc/ssl/certs/ca-certificates.crt"
];
BindPaths = optional (cfg.settings.storage.type == "postgres") "/var/run/postgresql";
# ProtectClock= adds DeviceAllow=char-rtc r
+1 -1
nixos/modules/services/web-apps/grav.nix
···
"opcache.memory_consumption" = "128";
"opcache.revalidate_freq" = "1";
"opcache.fast_shutdown" = "1";
-
"openssl.cafile" = "/etc/ssl/certs/ca-certificates.crt";
+
"openssl.cafile" = config.security.pki.caBundle;
catch_workers_output = "yes";
upload_max_filesize = cfg.maxUploadSize;
+2 -2
nixos/modules/services/web-apps/nextcloud.nix
···
"opcache.memory_consumption" = "128";
"opcache.revalidate_freq" = "1";
"opcache.fast_shutdown" = "1";
-
"openssl.cafile" = "/etc/ssl/certs/ca-certificates.crt";
+
"openssl.cafile" = config.security.pki.caBundle;
catch_workers_output = "yes";
};
···
phpOptions = mkOption {
type = with types; attrsOf (oneOf [ str int ]);
-
defaultText = literalExpression (generators.toPretty { } defaultPHPSettings);
+
defaultText = literalExpression (generators.toPretty { } (defaultPHPSettings // { "openssl.cafile" = literalExpression "config.security.pki.caBundle"; }));
description = ''
Options for PHP's php.ini file for nextcloud.
+1 -1
nixos/modules/services/web-apps/peertube.nix
···
env = {
NODE_CONFIG_DIR = "/var/lib/peertube/config";
NODE_ENV = "production";
-
NODE_EXTRA_CA_CERTS = "/etc/ssl/certs/ca-certificates.crt";
+
NODE_EXTRA_CA_CERTS = config.security.pki.caBundle;
NPM_CONFIG_CACHE = "/var/cache/peertube/.npm";
NPM_CONFIG_PREFIX = cfg.package;
HOME = cfg.package;
+1 -1
nixos/modules/services/web-apps/sogo.nix
···
wantedBy = [ "multi-user.target" ];
restartTriggers = [ config.environment.etc."sogo/sogo.conf.raw".source ];
-
environment.LDAPTLS_CACERT = "/etc/ssl/certs/ca-certificates.crt";
+
environment.LDAPTLS_CACERT = config.security.pki.caBundle;
serviceConfig = {
Type = "forking";