summary refs log tree commit diff
path: root/nixos/modules/security/dhparams.nix
blob: cfa9003f12fb6053e908b6f2bb42297e0b2cb950 (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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
{ config, lib, options, pkgs, ... }:

let
  inherit (lib) literalExpression mkOption types;
  cfg = config.security.dhparams;
  opt = options.security.dhparams;

  bitType = types.addCheck types.int (b: b >= 16) // {
    name = "bits";
    description = "integer of at least 16 bits";
  };

  paramsSubmodule = { name, config, ... }: {
    options.bits = mkOption {
      type = bitType;
      default = cfg.defaultBitSize;
      defaultText = literalExpression "config.${opt.defaultBitSize}";
      description = ''
        The bit size for the prime that is used during a Diffie-Hellman
        key exchange.
      '';
    };

    options.path = mkOption {
      type = types.path;
      readOnly = true;
      description = ''
        The resulting path of the generated Diffie-Hellman parameters
        file for other services to reference. This could be either a
        store path or a file inside the directory specified by
        <option>security.dhparams.path</option>.
      '';
    };

    config.path = let
      generated = pkgs.runCommand "dhparams-${name}.pem" {
        nativeBuildInputs = [ pkgs.openssl ];
      } "openssl dhparam -out \"$out\" ${toString config.bits}";
    in if cfg.stateful then "${cfg.path}/${name}.pem" else generated;
  };

in {
  options = {
    security.dhparams = {
      enable = mkOption {
        type = types.bool;
        default = false;
        description = ''
          Whether to generate new DH params and clean up old DH params.
        '';
      };

      params = mkOption {
        type = with types; let
          coerce = bits: { inherit bits; };
        in attrsOf (coercedTo int coerce (submodule paramsSubmodule));
        default = {};
        example = lib.literalExpression "{ nginx.bits = 3072; }";
        description = ''
          Diffie-Hellman parameters to generate.

          The value is the size (in bits) of the DH params to generate. The
          generated DH params path can be found in
          <literal>config.security.dhparams.params.<replaceable>name</replaceable>.path</literal>.

          <note><para>The name of the DH params is taken as being the name of
          the service it serves and the params will be generated before the
          said service is started.</para></note>

          <warning><para>If you are removing all dhparams from this list, you
          have to leave <option>security.dhparams.enable</option> for at
          least one activation in order to have them be cleaned up. This also
          means if you rollback to a version without any dhparams the
          existing ones won't be cleaned up. Of course this only applies if
          <option>security.dhparams.stateful</option> is
          <literal>true</literal>.</para></warning>

          <note><title>For module implementers:</title><para>It's recommended
          to not set a specific bit size here, so that users can easily
          override this by setting
          <option>security.dhparams.defaultBitSize</option>.</para></note>
        '';
      };

      stateful = mkOption {
        type = types.bool;
        default = true;
        description = ''
          Whether generation of Diffie-Hellman parameters should be stateful or
          not. If this is enabled, PEM-encoded files for Diffie-Hellman
          parameters are placed in the directory specified by
          <option>security.dhparams.path</option>. Otherwise the files are
          created within the Nix store.

          <note><para>If this is <literal>false</literal> the resulting store
          path will be non-deterministic and will be rebuilt every time the
          <package>openssl</package> package changes.</para></note>
        '';
      };

      defaultBitSize = mkOption {
        type = bitType;
        default = 2048;
        description = ''
          This allows to override the default bit size for all of the
          Diffie-Hellman parameters set in
          <option>security.dhparams.params</option>.
        '';
      };

      path = mkOption {
        type = types.str;
        default = "/var/lib/dhparams";
        description = ''
          Path to the directory in which Diffie-Hellman parameters will be
          stored. This only is relevant if
          <option>security.dhparams.stateful</option> is
          <literal>true</literal>.
        '';
      };
    };
  };

  config = lib.mkIf (cfg.enable && cfg.stateful) {
    systemd.services = {
      dhparams-init = {
        description = "Clean Up Old Diffie-Hellman Parameters";

        # Clean up even when no DH params is set
        wantedBy = [ "multi-user.target" ];

        serviceConfig.RemainAfterExit = true;
        serviceConfig.Type = "oneshot";

        script = ''
          if [ ! -d ${cfg.path} ]; then
            mkdir -p ${cfg.path}
          fi

          # Remove old dhparams
          for file in ${cfg.path}/*; do
            if [ ! -f "$file" ]; then
              continue
            fi
            ${lib.concatStrings (lib.mapAttrsToList (name: { bits, path, ... }: ''
              if [ "$file" = ${lib.escapeShellArg path} ] && \
                 ${pkgs.openssl}/bin/openssl dhparam -in "$file" -text \
                 | head -n 1 | grep "(${toString bits} bit)" > /dev/null; then
                continue
              fi
            '') cfg.params)}
            rm $file
          done

          # TODO: Ideally this would be removing the *former* cfg.path, though
          # this does not seem really important as changes to it are quite
          # unlikely
          rmdir --ignore-fail-on-non-empty ${cfg.path}
        '';
      };
    } // lib.mapAttrs' (name: { bits, path, ... }: lib.nameValuePair "dhparams-gen-${name}" {
      description = "Generate Diffie-Hellman Parameters for ${name}";
      after = [ "dhparams-init.service" ];
      before = [ "${name}.service" ];
      wantedBy = [ "multi-user.target" ];
      unitConfig.ConditionPathExists = "!${path}";
      serviceConfig.Type = "oneshot";
      script = ''
        mkdir -p ${lib.escapeShellArg cfg.path}
        ${pkgs.openssl}/bin/openssl dhparam -out ${lib.escapeShellArg path} \
          ${toString bits}
      '';
    }) cfg.params;
  };

  meta.maintainers = with lib.maintainers; [ ekleog ];
}