From 8512e5cee82119a02fe025d9101a9a352a65a16c Mon Sep 17 00:00:00 2001 From: Stephen Gelman Date: Mon, 12 Nov 2018 06:22:07 +0000 Subject: [PATCH 1/3] Add ldap_bind_as_user support This allows using the authenticating user's username and password to bind to the LDAP server. This is desirable because it allows for looking up the yubikey attributes without needing to create a service account. --- README | 6 ++++++ pam_yubico.8.txt | 3 +++ pam_yubico.c | 50 ++++++++++++++++++++++++++++++++---------------- 3 files changed, 43 insertions(+), 16 deletions(-) diff --git a/README b/README index d6c3ec5..7c540ac 100644 --- a/README +++ b/README @@ -177,6 +177,12 @@ in the authorization mapping files or in LDAP. This can be used to make YubiKey authentication optional unless the user has associated tokens. +ldap_bind_as_user:: +If set, use the user logging in to bind to LDAP. This will use the +password provided by the user via PAM. If this is set, ldapdn +and uid_attr must also be set. Enabling this will cause +'ldap_bind_user' and 'ldap_bind_password' to be ignored + urllist:: List of URL templates to be used. This is set by calling ykclient_set_url_bases. The list should be in the format : diff --git a/pam_yubico.8.txt b/pam_yubico.8.txt index 013bcef..6d40703 100644 --- a/pam_yubico.8.txt +++ b/pam_yubico.8.txt @@ -44,6 +44,9 @@ Forces the module to use a previous stacked modules password and will never prom *nullok*:: Don’t fail when there are no tokens declared for the user in the authorization mapping files or in LDAP. This can be used to make YubiKey authentication optional unless the user has associated tokens. +*ldap_bind_as_user*:: +Use the user logging in to bind to ldap. This will use the password provided by the user via PAM. If this is set, ldapdn and uid_attr must also be set. Enabling this will cause ldap_bind_user and ldap_bind_password to be ignored. + *urllist*=_list_:: List of URL templates to be used. This is set by calling ykclient_set_url_bases. The list should be in the format: diff --git a/pam_yubico.c b/pam_yubico.c index e128bd2..27f9d96 100644 --- a/pam_yubico.c +++ b/pam_yubico.c @@ -110,6 +110,7 @@ struct cfg int try_first_pass; int use_first_pass; int nullok; + int ldap_bind_as_user; const char *auth_file; const char *capath; const char *cainfo; @@ -229,7 +230,8 @@ free_out: static int authorize_user_token_ldap (struct cfg *cfg, const char *user, - const char *token_id) + const char *token_id, + pam_handle_t *pamh) { int retval = AUTH_ERROR; #ifdef HAVE_LIBLDAP @@ -288,19 +290,6 @@ authorize_user_token_ldap (struct cfg *cfg, /* Set CA CERTFILE. This makes ldaps work when using ldap_uri */ ldap_set_option (0, LDAP_OPT_X_TLS_CACERTFILE, cfg->ldap_cacertfile); } - /* Bind anonymously to the LDAP server. */ - if (cfg->ldap_bind_user && cfg->ldap_bind_password) { - DBG ("try bind with: %s:[%s]", cfg->ldap_bind_user, cfg->ldap_bind_password); - rc = ldap_simple_bind_s (ld, cfg->ldap_bind_user, cfg->ldap_bind_password); - } else { - DBG ("try anonymous bind"); - rc = ldap_simple_bind_s (ld, NULL, NULL); - } - if (rc != LDAP_SUCCESS) - { - DBG ("ldap_simple_bind_s: %s", ldap_err2string (rc)); - goto done; - } /* Allocation of memory for search strings depending on input size */ if (cfg->user_attr && cfg->yubi_attr && cfg->ldapdn) { @@ -318,6 +307,32 @@ authorize_user_token_ldap (struct cfg *cfg, } else if (cfg->ldapdn) { find = strdup(cfg->ldapdn); /* allow free later */ } + + /* Bind to the LDAP server. */ + if (cfg->ldap_bind_as_user && cfg->user_attr && cfg->yubi_attr && cfg->ldapdn) { + /* Bind as the user logging in with their password they provided to PAM */ + const char *bind_password = NULL; + rc = pam_get_item (pamh, PAM_AUTHTOK, (const void **) &bind_password); + if (rc != PAM_SUCCESS) { + DBG ("pam_get_item failed to retrieve password: %s", pam_strerror (pamh, rc)); + goto done; + } + DBG ("try bind as user with: %s", find); + rc = ldap_simple_bind_s (ld, find, bind_password); + } else if (cfg->ldap_bind_user && cfg->ldap_bind_password) { + /* Bind with a provided username and password */ + DBG ("try bind with: %s:[%s]", cfg->ldap_bind_user, cfg->ldap_bind_password); + rc = ldap_simple_bind_s (ld, cfg->ldap_bind_user, cfg->ldap_bind_password); + } else { + DBG ("try anonymous bind"); + rc = ldap_simple_bind_s (ld, NULL, NULL); + } + if (rc != LDAP_SUCCESS) + { + DBG ("ldap_simple_bind_s: %s", ldap_err2string (rc)); + goto done; + } + if (cfg->ldap_filter) { filter = filter_printf(cfg->ldap_filter, user); scope = LDAP_SCOPE_SUBTREE; @@ -766,6 +781,8 @@ parse_cfg (int flags, int argc, const char **argv, struct cfg *cfg) cfg->use_first_pass = 1; if (strcmp (argv[i], "nullok") == 0) cfg->nullok = 1; + if (strcmp (argv[i], "ldap_bind_as_user") == 0) + cfg->ldap_bind_as_user = 1; if (strncmp (argv[i], "authfile=", 9) == 0) cfg->auth_file = argv[i] + 9; if (strncmp (argv[i], "capath=", 7) == 0) @@ -856,6 +873,7 @@ parse_cfg (int flags, int argc, const char **argv, struct cfg *cfg) DBG ("try_first_pass=%d", cfg->try_first_pass); DBG ("use_first_pass=%d", cfg->use_first_pass); DBG ("nullok=%d", cfg->nullok); + DBG ("ldap_bind_as_user=%d", cfg->ldap_bind_as_user); DBG ("authfile=%s", cfg->auth_file ? cfg->auth_file : "(null)"); DBG ("ldapserver=%s", cfg->ldapserver ? cfg->ldapserver : "(null)"); DBG ("ldap_uri=%s", cfg->ldap_uri ? cfg->ldap_uri : "(null)"); @@ -1028,7 +1046,7 @@ pam_sm_authenticate (pam_handle_t * pamh, /* we set otp_id to NULL so that no matches will ever be found * but AUTH_NO_TOKENS will be returned if there are no tokens for the user */ if (cfg->ldapserver != NULL || cfg->ldap_uri != NULL) - valid_token = authorize_user_token_ldap (cfg, user, NULL); + valid_token = authorize_user_token_ldap (cfg, user, NULL, pamh); else valid_token = authorize_user_token (cfg, user, NULL, pamh); @@ -1153,7 +1171,7 @@ pam_sm_authenticate (pam_handle_t * pamh, /* authorize the user with supplied token id */ if (cfg->ldapserver != NULL || cfg->ldap_uri != NULL) - valid_token = authorize_user_token_ldap (cfg, user, otp_id); + valid_token = authorize_user_token_ldap (cfg, user, otp_id, pamh); else valid_token = authorize_user_token (cfg, user, otp_id, pamh); From fc2dc1a025bbbee6960700ddbdf5110b29b3ebcb Mon Sep 17 00:00:00 2001 From: Stephen Gelman Date: Mon, 12 Nov 2018 06:40:47 +0000 Subject: [PATCH 2/3] Add STARTTLS support for LDAP This allows connecting to LDAP servers that only listen on port 389 but use STARTTLS to get a TLS connection --- README | 5 +++++ pam_yubico.8.txt | 3 +++ pam_yubico.c | 12 ++++++++++++ 3 files changed, 20 insertions(+) diff --git a/README b/README index 7c540ac..d32a3e1 100644 --- a/README +++ b/README @@ -177,6 +177,11 @@ in the authorization mapping files or in LDAP. This can be used to make YubiKey authentication optional unless the user has associated tokens. +ldap_starttls:: +If set, issue a STARTTLS command to the LDAP connection before +attempting to bind to it. This is a common setup for servers +that only listen on port 389 but still require TLS. + ldap_bind_as_user:: If set, use the user logging in to bind to LDAP. This will use the password provided by the user via PAM. If this is set, ldapdn diff --git a/pam_yubico.8.txt b/pam_yubico.8.txt index 6d40703..415207b 100644 --- a/pam_yubico.8.txt +++ b/pam_yubico.8.txt @@ -44,6 +44,9 @@ Forces the module to use a previous stacked modules password and will never prom *nullok*:: Don’t fail when there are no tokens declared for the user in the authorization mapping files or in LDAP. This can be used to make YubiKey authentication optional unless the user has associated tokens. +*ldap_starttls*:: +If set, issue a STARTTLS command to the LDAP connection before attempting to bind to it. This is a common setup for servers that only listen on port 389 but still require TLS. + *ldap_bind_as_user*:: Use the user logging in to bind to ldap. This will use the password provided by the user via PAM. If this is set, ldapdn and uid_attr must also be set. Enabling this will cause ldap_bind_user and ldap_bind_password to be ignored. diff --git a/pam_yubico.c b/pam_yubico.c index 27f9d96..e911060 100644 --- a/pam_yubico.c +++ b/pam_yubico.c @@ -110,6 +110,7 @@ struct cfg int try_first_pass; int use_first_pass; int nullok; + int ldap_starttls; int ldap_bind_as_user; const char *auth_file; const char *capath; @@ -291,6 +292,14 @@ authorize_user_token_ldap (struct cfg *cfg, ldap_set_option (0, LDAP_OPT_X_TLS_CACERTFILE, cfg->ldap_cacertfile); } + if (cfg->ldap_starttls) { + rc = ldap_start_tls_s (ld, NULL, NULL); + if (rc != LDAP_SUCCESS) { + DBG ("ldap_start_tls: %s", ldap_err2string (rc)); + goto done; + } + } + /* Allocation of memory for search strings depending on input size */ if (cfg->user_attr && cfg->yubi_attr && cfg->ldapdn) { i = (strlen(cfg->user_attr) + strlen(cfg->ldapdn) + strlen(user) + 3) * sizeof(char); @@ -781,6 +790,8 @@ parse_cfg (int flags, int argc, const char **argv, struct cfg *cfg) cfg->use_first_pass = 1; if (strcmp (argv[i], "nullok") == 0) cfg->nullok = 1; + if (strcmp (argv[i], "ldap_starttls") == 0) + cfg->ldap_starttls = 1; if (strcmp (argv[i], "ldap_bind_as_user") == 0) cfg->ldap_bind_as_user = 1; if (strncmp (argv[i], "authfile=", 9) == 0) @@ -873,6 +884,7 @@ parse_cfg (int flags, int argc, const char **argv, struct cfg *cfg) DBG ("try_first_pass=%d", cfg->try_first_pass); DBG ("use_first_pass=%d", cfg->use_first_pass); DBG ("nullok=%d", cfg->nullok); + DBG ("ldap_starttls=%d", cfg->ldap_starttls); DBG ("ldap_bind_as_user=%d", cfg->ldap_bind_as_user); DBG ("authfile=%s", cfg->auth_file ? cfg->auth_file : "(null)"); DBG ("ldapserver=%s", cfg->ldapserver ? cfg->ldapserver : "(null)"); From c8c76fbf4c6b25b3ae60b0df4553bd506cdc1f7b Mon Sep 17 00:00:00 2001 From: Stephen Gelman Date: Mon, 12 Nov 2018 06:49:28 +0000 Subject: [PATCH 3/3] Add support for LDAP client certificate authentication This adds support for using a client cert/key to authenticate to an LDAP server. It is separate from binding with a username and password and can either be used alongside it or with an anonymous bind to the server. --- README | 9 +++++++++ pam_yubico.8.txt | 6 ++++++ pam_yubico.c | 21 +++++++++++++++++++++ 3 files changed, 36 insertions(+) diff --git a/README b/README index d32a3e1..35b57da 100644 --- a/README +++ b/README @@ -236,6 +236,15 @@ ldapdn:: specify the dn where the users are stored (eg: ou=users,dc=domain,dc=com). +ldap_clientcertfile:: +The path to a client cert file to use when talking to the LDAP +server. Note this requires 'ldap_clientkeyfile' to be set as well. + +ldap_clientkeyfile:: +The path to a key to be used with the client cert when talking to +the LDAP server. Note this requires 'ldap_clientcertfile' to be +set as well. + ldap_bind_user:: The user to attempt a LDAP bind as. diff --git a/pam_yubico.8.txt b/pam_yubico.8.txt index 415207b..de98d2e 100644 --- a/pam_yubico.8.txt +++ b/pam_yubico.8.txt @@ -80,6 +80,12 @@ The LDAP server host (default LDAP port is used). *Deprecated. Use 'ldap_uri' in *ldapdn*=_dn_:: The distinguished name (DN) where the users are stored (eg: ou=users,dc=domain,dc=com). If 'ldap_filter' is used this is the base from which the subtree search will be performed. +*ldap_clientcertfile*=_clientcertfile_:: +The path to a client cert file to use when talking to the LDAP server. Note this requires 'ldap_clientkeyfile' to be set as well. + +*ldap_clientkeyfile*=_clientkeyfile_:: +The path to a key to be used with the client cert when talking to the LDAP server. Note this requires 'ldap_clientcertfile' to be set as well. + *user_attr*=_attr_:: The LDAP attribute used to store user names (eg:cn). diff --git a/pam_yubico.c b/pam_yubico.c index e911060..2c6c5d9 100644 --- a/pam_yubico.c +++ b/pam_yubico.c @@ -125,6 +125,8 @@ struct cfg const char *ldap_filter; const char *ldap_cacertfile; const char *ldapdn; + const char *ldap_clientcertfile; + const char *ldap_clientkeyfile; const char *user_attr; const char *yubi_attr; const char *yubi_attr_prefix; @@ -292,6 +294,19 @@ authorize_user_token_ldap (struct cfg *cfg, ldap_set_option (0, LDAP_OPT_X_TLS_CACERTFILE, cfg->ldap_cacertfile); } + if (cfg->ldap_clientcertfile && cfg->ldap_clientkeyfile) { + rc = ldap_set_option (NULL, LDAP_OPT_X_TLS_CERTFILE, cfg->ldap_clientcertfile); + if (rc != LDAP_SUCCESS) { + DBG ("tls_certfile: %s", ldap_err2string (rc)); + goto done; + } + rc = ldap_set_option (NULL, LDAP_OPT_X_TLS_KEYFILE, cfg->ldap_clientkeyfile); + if (rc != LDAP_SUCCESS) { + DBG ("tls_keyfile: %s", ldap_err2string (rc)); + goto done; + } + } + if (cfg->ldap_starttls) { rc = ldap_start_tls_s (ld, NULL, NULL); if (rc != LDAP_SUCCESS) { @@ -818,6 +833,10 @@ parse_cfg (int flags, int argc, const char **argv, struct cfg *cfg) cfg->ldap_filter = argv[i] + 12; if (strncmp (argv[i], "ldap_cacertfile=", 16) == 0) cfg->ldap_cacertfile = argv[i] + 16; + if (strncmp (argv[i], "ldap_clientcertfile=", 20) == 0) + cfg->ldap_clientcertfile = argv[i] + 20; + if (strncmp (argv[i], "ldap_clientkeyfile=", 19) == 0) + cfg->ldap_clientkeyfile = argv[i] + 19; if (strncmp (argv[i], "ldapdn=", 7) == 0) cfg->ldapdn = argv[i] + 7; if (strncmp (argv[i], "user_attr=", 10) == 0) @@ -894,6 +913,8 @@ parse_cfg (int flags, int argc, const char **argv, struct cfg *cfg) DBG ("ldap_filter=%s", cfg->ldap_filter ? cfg->ldap_filter : "(null)"); DBG ("ldap_cacertfile=%s", cfg->ldap_cacertfile ? cfg->ldap_cacertfile : "(null)"); DBG ("ldapdn=%s", cfg->ldapdn ? cfg->ldapdn : "(null)"); + DBG ("ldap_clientcertfile=%s", cfg->ldap_clientcertfile ? cfg->ldap_clientcertfile : "(null)"); + DBG ("ldap_clientkeyfile=%s", cfg->ldap_clientkeyfile ? cfg->ldap_clientkeyfile : "(null)"); DBG ("user_attr=%s", cfg->user_attr ? cfg->user_attr : "(null)"); DBG ("yubi_attr=%s", cfg->yubi_attr ? cfg->yubi_attr : "(null)"); DBG ("yubi_attr_prefix=%s", cfg->yubi_attr_prefix ? cfg->yubi_attr_prefix : "(null)");