at 23.11-pre 6.2 kB view raw
1{ config, lib, options, pkgs, ... }: 2 3let 4 inherit (lib) literalExpression mkOption types; 5 cfg = config.security.dhparams; 6 opt = options.security.dhparams; 7 8 bitType = types.addCheck types.int (b: b >= 16) // { 9 name = "bits"; 10 description = "integer of at least 16 bits"; 11 }; 12 13 paramsSubmodule = { name, config, ... }: { 14 options.bits = mkOption { 15 type = bitType; 16 default = cfg.defaultBitSize; 17 defaultText = literalExpression "config.${opt.defaultBitSize}"; 18 description = lib.mdDoc '' 19 The bit size for the prime that is used during a Diffie-Hellman 20 key exchange. 21 ''; 22 }; 23 24 options.path = mkOption { 25 type = types.path; 26 readOnly = true; 27 description = lib.mdDoc '' 28 The resulting path of the generated Diffie-Hellman parameters 29 file for other services to reference. This could be either a 30 store path or a file inside the directory specified by 31 {option}`security.dhparams.path`. 32 ''; 33 }; 34 35 config.path = let 36 generated = pkgs.runCommand "dhparams-${name}.pem" { 37 nativeBuildInputs = [ pkgs.openssl ]; 38 } "openssl dhparam -out \"$out\" ${toString config.bits}"; 39 in if cfg.stateful then "${cfg.path}/${name}.pem" else generated; 40 }; 41 42in { 43 options = { 44 security.dhparams = { 45 enable = mkOption { 46 type = types.bool; 47 default = false; 48 description = lib.mdDoc '' 49 Whether to generate new DH params and clean up old DH params. 50 ''; 51 }; 52 53 params = mkOption { 54 type = with types; let 55 coerce = bits: { inherit bits; }; 56 in attrsOf (coercedTo int coerce (submodule paramsSubmodule)); 57 default = {}; 58 example = lib.literalExpression "{ nginx.bits = 3072; }"; 59 description = lib.mdDoc '' 60 Diffie-Hellman parameters to generate. 61 62 The value is the size (in bits) of the DH params to generate. The 63 generated DH params path can be found in 64 `config.security.dhparams.params.«name».path`. 65 66 ::: {.note} 67 The name of the DH params is taken as being the name of 68 the service it serves and the params will be generated before the 69 said service is started. 70 ::: 71 72 ::: {.warning} 73 If you are removing all dhparams from this list, you 74 have to leave {option}`security.dhparams.enable` for at 75 least one activation in order to have them be cleaned up. This also 76 means if you rollback to a version without any dhparams the 77 existing ones won't be cleaned up. Of course this only applies if 78 {option}`security.dhparams.stateful` is 79 `true`. 80 ::: 81 82 ::: {.note} 83 **For module implementers:** It's recommended 84 to not set a specific bit size here, so that users can easily 85 override this by setting 86 {option}`security.dhparams.defaultBitSize`. 87 ::: 88 ''; 89 }; 90 91 stateful = mkOption { 92 type = types.bool; 93 default = true; 94 description = lib.mdDoc '' 95 Whether generation of Diffie-Hellman parameters should be stateful or 96 not. If this is enabled, PEM-encoded files for Diffie-Hellman 97 parameters are placed in the directory specified by 98 {option}`security.dhparams.path`. Otherwise the files are 99 created within the Nix store. 100 101 ::: {.note} 102 If this is `false` the resulting store 103 path will be non-deterministic and will be rebuilt every time the 104 `openssl` package changes. 105 ::: 106 ''; 107 }; 108 109 defaultBitSize = mkOption { 110 type = bitType; 111 default = 2048; 112 description = lib.mdDoc '' 113 This allows to override the default bit size for all of the 114 Diffie-Hellman parameters set in 115 {option}`security.dhparams.params`. 116 ''; 117 }; 118 119 path = mkOption { 120 type = types.str; 121 default = "/var/lib/dhparams"; 122 description = lib.mdDoc '' 123 Path to the directory in which Diffie-Hellman parameters will be 124 stored. This only is relevant if 125 {option}`security.dhparams.stateful` is 126 `true`. 127 ''; 128 }; 129 }; 130 }; 131 132 config = lib.mkIf (cfg.enable && cfg.stateful) { 133 systemd.services = { 134 dhparams-init = { 135 description = "Clean Up Old Diffie-Hellman Parameters"; 136 137 # Clean up even when no DH params is set 138 wantedBy = [ "multi-user.target" ]; 139 140 serviceConfig.RemainAfterExit = true; 141 serviceConfig.Type = "oneshot"; 142 143 script = '' 144 if [ ! -d ${cfg.path} ]; then 145 mkdir -p ${cfg.path} 146 fi 147 148 # Remove old dhparams 149 for file in ${cfg.path}/*; do 150 if [ ! -f "$file" ]; then 151 continue 152 fi 153 ${lib.concatStrings (lib.mapAttrsToList (name: { bits, path, ... }: '' 154 if [ "$file" = ${lib.escapeShellArg path} ] && \ 155 ${pkgs.openssl}/bin/openssl dhparam -in "$file" -text \ 156 | head -n 1 | grep "(${toString bits} bit)" > /dev/null; then 157 continue 158 fi 159 '') cfg.params)} 160 rm $file 161 done 162 163 # TODO: Ideally this would be removing the *former* cfg.path, though 164 # this does not seem really important as changes to it are quite 165 # unlikely 166 rmdir --ignore-fail-on-non-empty ${cfg.path} 167 ''; 168 }; 169 } // lib.mapAttrs' (name: { bits, path, ... }: lib.nameValuePair "dhparams-gen-${name}" { 170 description = "Generate Diffie-Hellman Parameters for ${name}"; 171 after = [ "dhparams-init.service" ]; 172 before = [ "${name}.service" ]; 173 wantedBy = [ "multi-user.target" ]; 174 unitConfig.ConditionPathExists = "!${path}"; 175 serviceConfig.Type = "oneshot"; 176 script = '' 177 mkdir -p ${lib.escapeShellArg cfg.path} 178 ${pkgs.openssl}/bin/openssl dhparam -out ${lib.escapeShellArg path} \ 179 ${toString bits} 180 ''; 181 }) cfg.params; 182 }; 183 184 meta.maintainers = with lib.maintainers; [ ekleog ]; 185}