summary refs log blame commit diff
path: root/nixos/modules/config/ldap.nix
blob: e008497a2a6e04c6eb7bcceaba0b7dfd231fad5d (plain) (tree)
1
2
3
4
5
6
7
8
9
                           
 
          
         
 
   

                          
 











                                                                     







                                                                          






















                                                                                           
 

  
 
 
                  
 




                         
                          



                                                                                 





                                                                                    





                                                                      














































                                                                          
                             




                                                                      










                                                                    
                                    



                                                  

                                                                               

             





                                                 
                           





                                                                           
                                 
                                              
                           


















                                                                             
                                                               

















                                                                           
                           







                                                                    
 
      
 

    



                            
                                                               
 
                                                          
                                                      
                                                    

                          
                                                                  

                                             
          
         






                                                           
                      


                                    
                     




                                    
 
                                               
               
                                           

                     


                          
                              



                                                                                          
                    
                                              
           
                                                      
 
                         
                                                  
                           
                             

                          
                                         
                                           
          
        
 
      
 
    



                                                                                                         
 
{ config, lib, pkgs, ... }:

with pkgs;
with lib;

let

  cfg = config.users.ldap;

  # Careful: OpenLDAP seems to be very picky about the indentation of
  # this file.  Directives HAVE to start in the first column!
  ldapConfig = {
    target = "ldap.conf";
    source = writeText "ldap.conf" ''
      uri ${config.users.ldap.server}
      base ${config.users.ldap.base}
      timelimit ${toString config.users.ldap.timeLimit}
      bind_timelimit ${toString config.users.ldap.bind.timeLimit}
      bind_policy ${config.users.ldap.bind.policy}
      ${optionalString config.users.ldap.useTLS ''
        ssl start_tls
      ''}
      ${optionalString (config.users.ldap.bind.distinguishedName != "") ''
        binddn ${config.users.ldap.bind.distinguishedName}
      ''}
      ${optionalString (cfg.extraConfig != "") cfg.extraConfig }
    '';
  };

  nslcdConfig = writeText "nslcd.conf" ''
    uid nslcd
    gid nslcd
    uri ${cfg.server}
    base ${cfg.base}
    timelimit ${toString cfg.timeLimit}
    bind_timelimit ${toString cfg.bind.timeLimit}
    ${optionalString (cfg.bind.distinguishedName != "")
      "binddn ${cfg.bind.distinguishedName}" }
    ${optionalString (cfg.daemon.rootpwmoddn != "")
      "rootpwmoddn ${cfg.daemon.rootpwmoddn}" }
    ${optionalString (cfg.daemon.extraConfig != "") cfg.daemon.extraConfig }
  '';

  # nslcd normally reads configuration from /etc/nslcd.conf.
  # this file might contain secrets. We append those at runtime,
  # so redirect its location to something more temporary.
  nslcdWrapped = runCommandNoCC "nslcd-wrapped" { nativeBuildInputs = [ makeWrapper ]; } ''
    mkdir -p $out/bin
    makeWrapper ${nss_pam_ldapd}/sbin/nslcd $out/bin/nslcd \
      --set LD_PRELOAD    "${pkgs.libredirect}/lib/libredirect.so" \
      --set NIX_REDIRECTS "/etc/nslcd.conf=/run/nslcd/nslcd.conf"
  '';

in

{

  ###### interface

  options = {

    users.ldap = {

      enable = mkOption {
        type = types.bool;
        default = false;
        description = "Whether to enable authentication against an LDAP server.";
      };

      loginPam = mkOption {
        type = types.bool;
        default = true;
        description = "Whether to include authentication against LDAP in login PAM";
      };

      nsswitch = mkOption {
        type = types.bool;
        default = true;
        description = "Whether to include lookup against LDAP in NSS";
      };

      server = mkOption {
        example = "ldap://ldap.example.org/";
        description = "The URL of the LDAP server.";
      };

      base = mkOption {
        example = "dc=example,dc=org";
        description = "The distinguished name of the search base.";
      };

      useTLS = mkOption {
        default = false;
        description = ''
          If enabled, use TLS (encryption) over an LDAP (port 389)
          connection.  The alternative is to specify an LDAPS server (port
          636) in <option>users.ldap.server</option> or to forego
          security.
        '';
      };

      timeLimit = mkOption {
        default = 0;
        type = types.int;
        description = ''
          Specifies the time limit (in seconds) to use when performing
          searches. A value of zero (0), which is the default, is to
          wait indefinitely for searches to be completed.
        '';
      };

      daemon = {
        enable = mkOption {
          default = false;
          description = ''
            Whether to let the nslcd daemon (nss-pam-ldapd) handle the
            LDAP lookups for NSS and PAM. This can improve performance,
            and if you need to bind to the LDAP server with a password,
            it increases security, since only the nslcd user needs to
            have access to the bindpw file, not everyone that uses NSS
            and/or PAM. If this option is enabled, a local nscd user is
            created automatically, and the nslcd service is started
            automatically when the network get up.
          '';
        };

        extraConfig = mkOption {
          default =  "";
          type = types.lines;
          description = ''
            Extra configuration options that will be added verbatim at
            the end of the nslcd configuration file (nslcd.conf).
          '' ;
        } ;

        rootpwmoddn = mkOption {
          default = "";
          example = "cn=admin,dc=example,dc=com";
          type = types.str;
          description = ''
            The distinguished name to use to bind to the LDAP server
            when the root user tries to modify a user's password.
          '';
        };

        rootpwmodpwFile = mkOption {
          default = "";
          example = "/run/keys/nslcd.rootpwmodpw";
          type = types.str;
          description = ''
            The path to a file containing the credentials with which to bind to
            the LDAP server if the root user tries to change a user's password.
          '';
        };
      };

      bind = {
        distinguishedName = mkOption {
          default = "";
          example = "cn=admin,dc=example,dc=com";
          type = types.str;
          description = ''
            The distinguished name to bind to the LDAP server with. If this
            is not specified, an anonymous bind will be done.
          '';
        };

        passwordFile = mkOption {
          default = "/etc/ldap/bind.password";
          type = types.str;
          description = ''
            The path to a file containing the credentials to use when binding
            to the LDAP server (if not binding anonymously).
          '';
        };

        timeLimit = mkOption {
          default = 30;
          type = types.int;
          description = ''
            Specifies the time limit (in seconds) to use when connecting
            to the directory server. This is distinct from the time limit
            specified in <literal>users.ldap.timeLimit</literal> and affects
            the initial server connection only.
          '';
        };

        policy = mkOption {
          default = "hard_open";
          type = types.enum [ "hard_open" "hard_init" "soft" ];
          description = ''
            Specifies the policy to use for reconnecting to an unavailable
            LDAP server. The default is <literal>hard_open</literal>, which
            reconnects if opening the connection to the directory server
            failed. By contrast, <literal>hard_init</literal> reconnects if
            initializing the connection failed. Initializing may not
            actually contact the directory server, and it is possible that
            a malformed configuration file will trigger reconnection. If
            <literal>soft</literal> is specified, then
            <literal>nss_ldap</literal> will return immediately on server
            failure. All hard reconnect policies block with exponential
            backoff before retrying.
          '';
        };
      };

      extraConfig = mkOption {
        default = "";
        type = types.lines;
        description = ''
          Extra configuration options that will be added verbatim at
          the end of the ldap configuration file (ldap.conf).
          If <literal>users.ldap.daemon</literal> is enabled, this
          configuration will not be used. In that case, use
          <literal>users.ldap.daemon.extraConfig</literal> instead.
        '' ;
      };

    };

  };

  ###### implementation

  config = mkIf cfg.enable {

    environment.etc = optional (!cfg.daemon.enable) ldapConfig;

    system.activationScripts = mkIf (!cfg.daemon.enable) {
      ldap = stringAfter [ "etc" "groups" "users" ] ''
        if test -f "${cfg.bind.passwordFile}" ; then
          umask 0077
          conf="$(mktemp)"
          printf 'bindpw %s\n' "$(cat ${cfg.bind.passwordFile})" |
          cat ${ldapConfig.source} - >"$conf"
          mv -fT "$conf" /etc/ldap.conf
        fi
      '';
    };

    system.nssModules = singleton (
      if cfg.daemon.enable then nss_pam_ldapd else nss_ldap
    );

    users = mkIf cfg.daemon.enable {
      groups.nslcd = {
        gid = config.ids.gids.nslcd;
      };

      users.nslcd = {
        uid = config.ids.uids.nslcd;
        description = "nslcd user.";
        group = "nslcd";
      };
    };

    systemd.services = mkIf cfg.daemon.enable {
      nslcd = {
        wantedBy = [ "multi-user.target" ];

        preStart = ''
          umask 0077
          conf="$(mktemp)"
          {
            cat ${nslcdConfig}
            test -z '${cfg.bind.distinguishedName}' -o ! -f '${cfg.bind.passwordFile}' ||
            printf 'bindpw %s\n' "$(cat '${cfg.bind.passwordFile}')"
            test -z '${cfg.daemon.rootpwmoddn}' -o ! -f '${cfg.daemon.rootpwmodpwFile}' ||
            printf 'rootpwmodpw %s\n' "$(cat '${cfg.daemon.rootpwmodpwFile}')"
          } >"$conf"
          mv -fT "$conf" /run/nslcd/nslcd.conf
        '';
        restartTriggers = [ "/run/nslcd/nslcd.conf" ];

        serviceConfig = {
          ExecStart = "${nslcdWrapped}/bin/nslcd";
          Type = "forking";
          Restart = "always";
          User = "nslcd";
          Group = "nslcd";
          RuntimeDirectory = [ "nslcd" ];
          PIDFile = "/run/nslcd/nslcd.pid";
        };
      };

    };

  };

  imports =
    [ (mkRenamedOptionModule [ "users" "ldap" "bind" "password"] [ "users" "ldap" "bind" "passwordFile"])
    ];
}