Merge pull request #44127 from johanot/nixos-cfssl

nixos/cfssl: Add new module for cfssl

Changed files
+280
nixos
modules
misc
services
security
tests
+2
nixos/modules/misc/ids.nix
···
mapred = 296;
hadoop = 297;
hydron = 298;
+
cfssl = 299;
# When adding a uid, make sure it doesn't match an existing gid. And don't use uids above 399!
···
mapred = 296;
hadoop = 297;
hydron = 298;
+
cfssl = 299;
# When adding a gid, make sure it doesn't match an existing
# uid. Users and groups with the same name should have equal
+1
nixos/modules/module-list.nix
···
./services/search/hound.nix
./services/search/kibana.nix
./services/search/solr.nix
+
./services/security/cfssl.nix
./services/security/clamav.nix
./services/security/fail2ban.nix
./services/security/fprintd.nix
+209
nixos/modules/services/security/cfssl.nix
···
+
{ config, lib, pkgs, ... }:
+
+
with lib;
+
+
let
+
cfg = config.services.cfssl;
+
in {
+
options.services.cfssl = {
+
enable = mkEnableOption "the CFSSL CA api-server";
+
+
dataDir = mkOption {
+
default = "/var/lib/cfssl";
+
type = types.path;
+
description = "Cfssl work directory.";
+
};
+
+
address = mkOption {
+
default = "127.0.0.1";
+
type = types.str;
+
description = "Address to bind.";
+
};
+
+
port = mkOption {
+
default = 8888;
+
type = types.ints.u16;
+
description = "Port to bind.";
+
};
+
+
ca = mkOption {
+
defaultText = "\${cfg.dataDir}/ca.pem";
+
type = types.str;
+
description = "CA used to sign the new certificate -- accepts '[file:]fname' or 'env:varname'.";
+
};
+
+
caKey = mkOption {
+
defaultText = "file:\${cfg.dataDir}/ca-key.pem";
+
type = types.str;
+
description = "CA private key -- accepts '[file:]fname' or 'env:varname'.";
+
};
+
+
caBundle = mkOption {
+
default = null;
+
type = types.nullOr types.path;
+
description = "Path to root certificate store.";
+
};
+
+
intBundle = mkOption {
+
default = null;
+
type = types.nullOr types.path;
+
description = "Path to intermediate certificate store.";
+
};
+
+
intDir = mkOption {
+
default = null;
+
type = types.nullOr types.path;
+
description = "Intermediates directory.";
+
};
+
+
metadata = mkOption {
+
default = null;
+
type = types.nullOr types.path;
+
description = ''
+
Metadata file for root certificate presence.
+
The content of the file is a json dictionary (k,v): each key k is
+
a SHA-1 digest of a root certificate while value v is a list of key
+
store filenames.
+
'';
+
};
+
+
remote = mkOption {
+
default = null;
+
type = types.nullOr types.str;
+
description = "Remote CFSSL server.";
+
};
+
+
configFile = mkOption {
+
default = null;
+
type = types.nullOr types.str;
+
description = "Path to configuration file. Do not put this in nix-store as it might contain secrets.";
+
};
+
+
responder = mkOption {
+
default = null;
+
type = types.nullOr types.path;
+
description = "Certificate for OCSP responder.";
+
};
+
+
responderKey = mkOption {
+
default = null;
+
type = types.nullOr types.str;
+
description = "Private key for OCSP responder certificate. Do not put this in nix-store.";
+
};
+
+
tlsKey = mkOption {
+
default = null;
+
type = types.nullOr types.str;
+
description = "Other endpoint's CA private key. Do not put this in nix-store.";
+
};
+
+
tlsCert = mkOption {
+
default = null;
+
type = types.nullOr types.path;
+
description = "Other endpoint's CA to set up TLS protocol.";
+
};
+
+
mutualTlsCa = mkOption {
+
default = null;
+
type = types.nullOr types.path;
+
description = "Mutual TLS - require clients be signed by this CA.";
+
};
+
+
mutualTlsCn = mkOption {
+
default = null;
+
type = types.nullOr types.str;
+
description = "Mutual TLS - regex for whitelist of allowed client CNs.";
+
};
+
+
tlsRemoteCa = mkOption {
+
default = null;
+
type = types.nullOr types.path;
+
description = "CAs to trust for remote TLS requests.";
+
};
+
+
mutualTlsClientCert = mkOption {
+
default = null;
+
type = types.nullOr types.path;
+
description = "Mutual TLS - client certificate to call remote instance requiring client certs.";
+
};
+
+
mutualTlsClientKey = mkOption {
+
default = null;
+
type = types.nullOr types.path;
+
description = "Mutual TLS - client key to call remote instance requiring client certs. Do not put this in nix-store.";
+
};
+
+
dbConfig = mkOption {
+
default = null;
+
type = types.nullOr types.path;
+
description = "Certificate db configuration file. Path must be writeable.";
+
};
+
+
logLevel = mkOption {
+
default = 1;
+
type = types.enum [ 0 1 2 3 4 5 ];
+
description = "Log level (0 = DEBUG, 5 = FATAL).";
+
};
+
};
+
+
config = {
+
users.extraGroups.cfssl = {
+
gid = config.ids.gids.cfssl;
+
};
+
+
users.extraUsers.cfssl = {
+
description = "cfssl user";
+
createHome = true;
+
home = cfg.dataDir;
+
group = "cfssl";
+
uid = config.ids.uids.cfssl;
+
};
+
+
systemd.services.cfssl = mkIf cfg.enable {
+
description = "CFSSL CA API server";
+
wantedBy = [ "multi-user.target" ];
+
after = [ "network.target" ];
+
+
serviceConfig = {
+
WorkingDirectory = cfg.dataDir;
+
StateDirectory = cfg.dataDir;
+
StateDirectoryMode = 700;
+
Restart = "always";
+
User = "cfssl";
+
+
ExecStart = with cfg; let
+
opt = n: v: optionalString (v != null) ''-${n}="${v}"'';
+
in
+
lib.concatStringsSep " \\\n" [
+
"${pkgs.cfssl}/bin/cfssl serve"
+
(opt "address" address)
+
(opt "port" (toString port))
+
(opt "ca" ca)
+
(opt "ca-key" caKey)
+
(opt "ca-bundle" caBundle)
+
(opt "int-bundle" intBundle)
+
(opt "int-dir" intDir)
+
(opt "metadata" metadata)
+
(opt "remote" remote)
+
(opt "config" configFile)
+
(opt "responder" responder)
+
(opt "responder-key" responderKey)
+
(opt "tls-key" tlsKey)
+
(opt "tls-cert" tlsCert)
+
(opt "mutual-tls-ca" mutualTlsCa)
+
(opt "mutual-tls-cn" mutualTlsCn)
+
(opt "mutual-tls-client-key" mutualTlsClientKey)
+
(opt "mutual-tls-client-cert" mutualTlsClientCert)
+
(opt "tls-remote-ca" tlsRemoteCa)
+
(opt "db-config" dbConfig)
+
(opt "loglevel" (toString logLevel))
+
];
+
};
+
};
+
+
services.cfssl = {
+
ca = mkDefault "${cfg.dataDir}/ca.pem";
+
caKey = mkDefault "${cfg.dataDir}/ca-key.pem";
+
};
+
};
+
}
+1
nixos/release.nix
···
tests.buildbot = callTest tests/buildbot.nix {};
tests.cadvisor = callTestOnMatchingSystems ["x86_64-linux"] tests/cadvisor.nix {};
tests.ceph = callTestOnMatchingSystems ["x86_64-linux"] tests/ceph.nix {};
+
tests.cfssl = callTestOnMatchingSystems ["x86_64-linux"] tests/cfssl.nix {};
tests.chromium = (callSubTestsOnMatchingSystems ["x86_64-linux"] tests/chromium.nix {}).stable or {};
tests.cjdns = callTest tests/cjdns.nix {};
tests.cloud-init = callTest tests/cloud-init.nix {};
+67
nixos/tests/cfssl.nix
···
+
import ./make-test.nix ({ pkgs, ...} : {
+
name = "cfssl";
+
+
machine = { config, lib, pkgs, ... }:
+
{
+
networking.firewall.allowedTCPPorts = [ config.services.cfssl.port ];
+
+
services.cfssl.enable = true;
+
systemd.services.cfssl.after = [ "cfssl-init.service" ];
+
+
systemd.services.cfssl-init = {
+
description = "Initialize the cfssl CA";
+
wantedBy = [ "multi-user.target" ];
+
serviceConfig = {
+
User = "cfssl";
+
Type = "oneshot";
+
WorkingDirectory = config.services.cfssl.dataDir;
+
};
+
script = with pkgs; ''
+
${cfssl}/bin/cfssl genkey -initca ${pkgs.writeText "ca.json" (builtins.toJSON {
+
hosts = [ "ca.example.com" ];
+
key = {
+
algo = "rsa"; size = 4096; };
+
names = [
+
{
+
C = "US";
+
L = "San Francisco";
+
O = "Internet Widgets, LLC";
+
OU = "Certificate Authority";
+
ST = "California";
+
}
+
];
+
})} | ${cfssl}/bin/cfssljson -bare ca
+
'';
+
};
+
};
+
+
testScript =
+
let
+
cfsslrequest = with pkgs; writeScript "cfsslrequest" ''
+
curl -X POST -H "Content-Type: application/json" -d @${csr} \
+
http://localhost:8888/api/v1/cfssl/newkey | ${cfssl}/bin/cfssljson /tmp/certificate
+
'';
+
csr = pkgs.writeText "csr.json" (builtins.toJSON {
+
CN = "www.example.com";
+
hosts = [ "example.com" "www.example.com" ];
+
key = {
+
algo = "rsa";
+
size = 2048;
+
};
+
names = [
+
{
+
C = "US";
+
L = "San Francisco";
+
O = "Example Company, LLC";
+
OU = "Operations";
+
ST = "California";
+
}
+
];
+
});
+
in
+
''
+
$machine->waitForUnit('cfssl.service');
+
$machine->waitUntilSucceeds('${cfsslrequest}');
+
$machine->succeed('ls /tmp/certificate-key.pem');
+
'';
+
})