1{ config, lib, pkgs, ... }:
2let
3 cfg = config.services.step-ca;
4 settingsFormat = (pkgs.formats.json { });
5in
6{
7 meta.maintainers = with lib.maintainers; [ mohe2015 ];
8
9 options = {
10 services.step-ca = {
11 enable = lib.mkEnableOption "the smallstep certificate authority server";
12 openFirewall = lib.mkEnableOption "opening the certificate authority server port";
13 package = lib.mkOption {
14 type = lib.types.package;
15 default = pkgs.step-ca;
16 description = "Which step-ca package to use.";
17 };
18 address = lib.mkOption {
19 type = lib.types.str;
20 example = "127.0.0.1";
21 description = ''
22 The address (without port) the certificate authority should listen at.
23 This combined with <option>services.step-ca.port</option> overrides <option>services.step-ca.settings.address</option>.
24 '';
25 };
26 port = lib.mkOption {
27 type = lib.types.port;
28 example = 8443;
29 description = ''
30 The port the certificate authority should listen on.
31 This combined with <option>services.step-ca.address</option> overrides <option>services.step-ca.settings.address</option>.
32 '';
33 };
34 settings = lib.mkOption {
35 type = with lib.types; attrsOf anything;
36 description = ''
37 Settings that go into <filename>ca.json</filename>. See
38 <link xlink:href="https://smallstep.com/docs/step-ca/configuration">
39 the step-ca manual</link> for more information. The easiest way to
40 configure this module would be to run <literal>step ca init</literal>
41 to generate <filename>ca.json</filename> and then import it using
42 <literal>builtins.fromJSON</literal>.
43 <link xlink:href="https://smallstep.com/docs/step-cli/basic-crypto-operations#run-an-offline-x509-certificate-authority">This article</link>
44 may also be useful if you want to customize certain aspects of
45 certificate generation for your CA.
46 You need to change the database storage path to <filename>/var/lib/step-ca/db</filename>.
47
48 <warning>
49 <para>
50 The <option>services.step-ca.settings.address</option> option
51 will be ignored and overwritten by
52 <option>services.step-ca.address</option> and
53 <option>services.step-ca.port</option>.
54 </para>
55 </warning>
56 '';
57 };
58 intermediatePasswordFile = lib.mkOption {
59 type = lib.types.path;
60 example = "/run/keys/smallstep-password";
61 description = ''
62 Path to the file containing the password for the intermediate
63 certificate private key.
64
65 <warning>
66 <para>
67 Make sure to use a quoted absolute path instead of a path literal
68 to prevent it from being copied to the globally readable Nix
69 store.
70 </para>
71 </warning>
72 '';
73 };
74 };
75 };
76
77 config = lib.mkIf config.services.step-ca.enable (
78 let
79 configFile = settingsFormat.generate "ca.json" (cfg.settings // {
80 address = cfg.address + ":" + toString cfg.port;
81 });
82 in
83 {
84 assertions =
85 [
86 {
87 assertion = !lib.isStorePath cfg.intermediatePasswordFile;
88 message = ''
89 <option>services.step-ca.intermediatePasswordFile</option> points to
90 a file in the Nix store. You should use a quoted absolute path to
91 prevent this.
92 '';
93 }
94 ];
95
96 systemd.packages = [ cfg.package ];
97
98 # configuration file indirection is needed to support reloading
99 environment.etc."smallstep/ca.json".source = configFile;
100
101 systemd.services."step-ca" = {
102 wantedBy = [ "multi-user.target" ];
103 restartTriggers = [ configFile ];
104 unitConfig = {
105 ConditionFileNotEmpty = ""; # override upstream
106 };
107 serviceConfig = {
108 Environment = "HOME=%S/step-ca";
109 WorkingDirectory = ""; # override upstream
110 ReadWriteDirectories = ""; # override upstream
111
112 # LocalCredential handles file permission problems arising from the use of DynamicUser.
113 LoadCredential = "intermediate_password:${cfg.intermediatePasswordFile}";
114
115 ExecStart = [
116 "" # override upstream
117 "${cfg.package}/bin/step-ca /etc/smallstep/ca.json --password-file \${CREDENTIALS_DIRECTORY}/intermediate_password"
118 ];
119
120 # ProtectProc = "invisible"; # not supported by upstream yet
121 # ProcSubset = "pid"; # not supported by upstream upstream yet
122 # PrivateUsers = true; # doesn't work with privileged ports therefore not supported by upstream
123
124 DynamicUser = true;
125 StateDirectory = "step-ca";
126 };
127 };
128
129 networking.firewall = lib.mkIf cfg.openFirewall {
130 allowedTCPPorts = [ cfg.port ];
131 };
132 }
133 );
134}