about summary refs log tree commit diff
path: root/nixos/modules/services/security/step-ca.nix
blob: c708cb2b8910dc8ee71f6ea0b9be370302cafcfe (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
{ config, lib, pkgs, ... }:
let
  cfg = config.services.step-ca;
  settingsFormat = (pkgs.formats.json { });
in
{
  meta.maintainers = with lib.maintainers; [ mohe2015 ];

  options = {
    services.step-ca = {
      enable = lib.mkEnableOption "the smallstep certificate authority server";
      openFirewall = lib.mkEnableOption "opening the certificate authority server port";
      package = lib.mkOption {
        type = lib.types.package;
        default = pkgs.step-ca;
        defaultText = lib.literalExpression "pkgs.step-ca";
        description = "Which step-ca package to use.";
      };
      address = lib.mkOption {
        type = lib.types.str;
        example = "127.0.0.1";
        description = ''
          The address (without port) the certificate authority should listen at.
          This combined with {option}`services.step-ca.port` overrides {option}`services.step-ca.settings.address`.
        '';
      };
      port = lib.mkOption {
        type = lib.types.port;
        example = 8443;
        description = ''
          The port the certificate authority should listen on.
          This combined with {option}`services.step-ca.address` overrides {option}`services.step-ca.settings.address`.
        '';
      };
      settings = lib.mkOption {
        type = with lib.types; attrsOf anything;
        description = ''
          Settings that go into {file}`ca.json`. See
          [the step-ca manual](https://smallstep.com/docs/step-ca/configuration)
          for more information. The easiest way to
          configure this module would be to run `step ca init`
          to generate {file}`ca.json` and then import it using
          `builtins.fromJSON`.
          [This article](https://smallstep.com/docs/step-cli/basic-crypto-operations#run-an-offline-x509-certificate-authority)
          may also be useful if you want to customize certain aspects of
          certificate generation for your CA.
          You need to change the database storage path to {file}`/var/lib/step-ca/db`.

          ::: {.warning}
          The {option}`services.step-ca.settings.address` option
          will be ignored and overwritten by
          {option}`services.step-ca.address` and
          {option}`services.step-ca.port`.
          :::
        '';
      };
      intermediatePasswordFile = lib.mkOption {
        type = lib.types.path;
        example = "/run/keys/smallstep-password";
        description = ''
          Path to the file containing the password for the intermediate
          certificate private key.

          ::: {.warning}
          Make sure to use a quoted absolute path instead of a path literal
          to prevent it from being copied to the globally readable Nix
          store.
          :::
        '';
      };
    };
  };

  config = lib.mkIf config.services.step-ca.enable (
    let
      configFile = settingsFormat.generate "ca.json" (cfg.settings // {
        address = cfg.address + ":" + toString cfg.port;
      });
    in
    {
      assertions =
        [
          {
            assertion = !lib.isStorePath cfg.intermediatePasswordFile;
            message = ''
              <option>services.step-ca.intermediatePasswordFile</option> points to
              a file in the Nix store. You should use a quoted absolute path to
              prevent this.
            '';
          }
        ];

      systemd.packages = [ cfg.package ];

      # configuration file indirection is needed to support reloading
      environment.etc."smallstep/ca.json".source = configFile;

      systemd.services."step-ca" = {
        wantedBy = [ "multi-user.target" ];
        restartTriggers = [ configFile ];
        unitConfig = {
          ConditionFileNotEmpty = ""; # override upstream
        };
        serviceConfig = {
          User = "step-ca";
          Group = "step-ca";
          UMask = "0077";
          Environment = "HOME=%S/step-ca";
          WorkingDirectory = ""; # override upstream
          ReadWriteDirectories = ""; # override upstream

          # LocalCredential handles file permission problems arising from the use of DynamicUser.
          LoadCredential = "intermediate_password:${cfg.intermediatePasswordFile}";

          ExecStart = [
            "" # override upstream
            "${cfg.package}/bin/step-ca /etc/smallstep/ca.json --password-file \${CREDENTIALS_DIRECTORY}/intermediate_password"
          ];

          # ProtectProc = "invisible"; # not supported by upstream yet
          # ProcSubset = "pid"; # not supported by upstream yet
          # PrivateUsers = true; # doesn't work with privileged ports therefore not supported by upstream

          DynamicUser = true;
          StateDirectory = "step-ca";
        };
      };

      users.users.step-ca = {
        home = "/var/lib/step-ca";
        group = "step-ca";
        isSystemUser = true;
      };

      users.groups.step-ca = {};

      networking.firewall = lib.mkIf cfg.openFirewall {
        allowedTCPPorts = [ cfg.port ];
      };
    }
  );
}