summary refs log blame commit diff
path: root/nixos/modules/security/dhparams.nix
blob: 9fed7e012b1ed2485950e1b5b7da1d7ad6f2059c (plain) (tree)
1
2
3
4
5
6
7
                                    
 
   
                                                 
                                 
                                  
 




                                                      

                                            

                                   
                                                                     
                                







                                                                       
                                


                                                                     
                                         


         




                                                                     


    

                         


                          
                                  



                                                                       
                         

                                           
                                                                      
                     
                                                                 
                                  



                                                                           
                                                          
 

                                                                 
                                                                           

                                  
 


                                                                 


                                                                             


                                                 
 

                                                       

                                                                       

                                                     
           

        


                           
                                  


                                                                               
                                                                   

                                       

                                                
                                                                           

                                    


           


                                 
                                  

                                                                     
                                             


           
                       

                                      
                                  

                                                                          

                                                 
           
        


      
                                                  

                        





                                                               
                                       





                                       
                               







                                                                                  

                        








                                                                              
        












                                                                                             
    

                                                      
 
{ 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 = lib.mdDoc ''
        The bit size for the prime that is used during a Diffie-Hellman
        key exchange.
      '';
    };

    options.path = mkOption {
      type = types.path;
      readOnly = true;
      description = lib.mdDoc ''
        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`.
      '';
    };

    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 = lib.mdDoc ''
          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 = lib.mdDoc ''
          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
          `config.security.dhparams.params.«name».path`.

          ::: {.note}
          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.
          :::

          ::: {.warning}
          If you are removing all dhparams from this list, you
          have to leave {option}`security.dhparams.enable` 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` is
          `true`.
          :::

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

      stateful = mkOption {
        type = types.bool;
        default = true;
        description = lib.mdDoc ''
          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`. Otherwise the files are
          created within the Nix store.

          ::: {.note}
          If this is `false` the resulting store
          path will be non-deterministic and will be rebuilt every time the
          `openssl` package changes.
          :::
        '';
      };

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

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

  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 ];
}