From 1980e2c415666650cdb57b73055603bfd4549474 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanis=C5=82aw=20Pitucha?= Date: Sat, 26 Aug 2023 14:43:07 +1000 Subject: [PATCH] LDAP: Allow ignoring the ppolicy extension MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce `ldap_use_ppolicy` and allow disabling it to interact with providers that send broken ppolicy responses. This fixes interaction with the Okta LDAP gateway. Resolves: https://github.com/SSSD/sssd/issues/6666 :config: Add a ldap_use_ppolicy option for backends with broken ppolicy extension handling. Reviewed-by: Pavel Březina Reviewed-by: Tomáš Halman --- src/config/SSSDConfig/sssdoptions.py | 1 + src/config/cfg_rules.ini | 1 + src/config/etc/sssd.api.d/sssd-ldap.conf | 1 + src/man/sssd-ldap.5.xml | 15 +++++++ src/providers/ad/ad_opts.c | 1 + src/providers/ipa/ipa_auth.c | 5 ++- src/providers/ipa/ipa_opts.c | 1 + src/providers/ldap/ldap_auth.c | 10 +++-- src/providers/ldap/ldap_opts.c | 1 + src/providers/ldap/sdap.h | 1 + src/providers/ldap/sdap_async.c | 21 +++++----- src/providers/ldap/sdap_async.h | 6 ++- src/providers/ldap/sdap_async_connection.c | 47 +++++++++++++--------- src/tests/system/tests/test_ldap.py | 16 ++++++-- 14 files changed, 90 insertions(+), 37 deletions(-) diff --git a/src/config/SSSDConfig/sssdoptions.py b/src/config/SSSDConfig/sssdoptions.py index 68f1d2f6101..ec0e276cbda 100644 --- a/src/config/SSSDConfig/sssdoptions.py +++ b/src/config/SSSDConfig/sssdoptions.py @@ -395,6 +395,7 @@ def __init__(self): 'ldap_disable_paging': _('Disable the LDAP paging control'), 'ldap_disable_range_retrieval': _('Disable Active Directory range retrieval'), + 'ldap_use_ppolicy': _('Use the ppolicy extension'), # [provider/ldap/id] 'ldap_search_timeout': _('Length of time to wait for a search request'), diff --git a/src/config/cfg_rules.ini b/src/config/cfg_rules.ini index 41e0ab8601b..0fa06d586ab 100644 --- a/src/config/cfg_rules.ini +++ b/src/config/cfg_rules.ini @@ -744,6 +744,7 @@ option = ldap_tls_cipher_suite option = ldap_tls_key option = ldap_tls_reqcert option = ldap_uri +option = ldap_use_ppolicy option = ldap_user_ad_account_expires option = ldap_user_ad_user_account_control option = ldap_user_authorized_host diff --git a/src/config/etc/sssd.api.d/sssd-ldap.conf b/src/config/etc/sssd.api.d/sssd-ldap.conf index 237cf40ccd5..3ec313ceccb 100644 --- a/src/config/etc/sssd.api.d/sssd-ldap.conf +++ b/src/config/etc/sssd.api.d/sssd-ldap.conf @@ -43,6 +43,7 @@ ldap_connection_idle_timeout = int, None, false ldap_disable_paging = bool, None, false ldap_disable_range_retrieval = bool, None, false wildcard_limit = int, None, false +ldap_use_ppolicy = bool, None, false [provider/ldap/id] ldap_search_timeout = int, None, false diff --git a/src/man/sssd-ldap.5.xml b/src/man/sssd-ldap.5.xml index c2602ba0d9e..08460b0278f 100644 --- a/src/man/sssd-ldap.5.xml +++ b/src/man/sssd-ldap.5.xml @@ -1639,6 +1639,21 @@ ldap_access_filter = (employeeType=admin) + + ldap_use_ppolicy (boolean) + + + Turns on requesting and relying on the server-side + password policy controls. Disabling this allows + interacting with services which send back invalid + ppolicy extension. + + + Default: true + + + + diff --git a/src/providers/ad/ad_opts.c b/src/providers/ad/ad_opts.c index 9dd2f03871a..085cb5ad1ba 100644 --- a/src/providers/ad/ad_opts.c +++ b/src/providers/ad/ad_opts.c @@ -167,6 +167,7 @@ struct dp_option ad_def_ldap_opts[] = { { "ldap_pwdlockout_dn", DP_OPT_STRING, NULL_STRING, NULL_STRING }, { "wildcard_limit", DP_OPT_NUMBER, { .number = 1000 }, NULL_NUMBER}, { "ldap_library_debug_level", DP_OPT_NUMBER, NULL_NUMBER, NULL_NUMBER}, + { "ldap_use_ppolicy", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, DP_OPTION_TERMINATOR }; diff --git a/src/providers/ipa/ipa_auth.c b/src/providers/ipa/ipa_auth.c index 1d61a1052aa..6ebbff69ae0 100644 --- a/src/providers/ipa/ipa_auth.c +++ b/src/providers/ipa/ipa_auth.c @@ -345,6 +345,7 @@ static void ipa_pam_auth_handler_connect_done(struct tevent_req *subreq) struct ldb_message *msg; const char *dn; int timeout; + bool use_ppolicy; errno_t ret; req = tevent_req_callback_data(subreq, struct tevent_req); @@ -379,9 +380,11 @@ static void ipa_pam_auth_handler_connect_done(struct tevent_req *subreq) timeout = dp_opt_get_int(state->auth_ctx->sdap_auth_ctx->opts->basic, SDAP_OPT_TIMEOUT); + use_ppolicy = dp_opt_get_bool(state->auth_ctx->sdap_auth_ctx->opts->basic, + SDAP_USE_PPOLICY); subreq = sdap_auth_send(state, state->ev, sh, NULL, NULL, dn, - state->pd->authtok, timeout); + state->pd->authtok, timeout, use_ppolicy); if (subreq == NULL) { goto done; } diff --git a/src/providers/ipa/ipa_opts.c b/src/providers/ipa/ipa_opts.c index 97cddb1d70d..621bb1d27c9 100644 --- a/src/providers/ipa/ipa_opts.c +++ b/src/providers/ipa/ipa_opts.c @@ -177,6 +177,7 @@ struct dp_option ipa_def_ldap_opts[] = { { "ldap_pwdlockout_dn", DP_OPT_STRING, NULL_STRING, NULL_STRING }, { "wildcard_limit", DP_OPT_NUMBER, { .number = 1000 }, NULL_NUMBER}, { "ldap_library_debug_level", DP_OPT_NUMBER, NULL_NUMBER, NULL_NUMBER}, + { "ldap_use_ppolicy", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, DP_OPTION_TERMINATOR }; diff --git a/src/providers/ldap/ldap_auth.c b/src/providers/ldap/ldap_auth.c index 8ec4d3af5a6..b9a244ee1ff 100644 --- a/src/providers/ldap/ldap_auth.c +++ b/src/providers/ldap/ldap_auth.c @@ -891,12 +891,14 @@ static void auth_do_bind(struct tevent_req *req) { struct auth_state *state = tevent_req_data(req, struct auth_state); struct tevent_req *subreq; + bool use_ppolicy = dp_opt_get_bool(state->ctx->opts->basic, + SDAP_USE_PPOLICY); + int timeout = dp_opt_get_int(state->ctx->opts->basic, SDAP_OPT_TIMEOUT); subreq = sdap_auth_send(state, state->ev, state->sh, NULL, NULL, state->dn, state->authtok, - dp_opt_get_int(state->ctx->opts->basic, - SDAP_OPT_TIMEOUT)); + timeout, use_ppolicy); if (!subreq) { tevent_req_error(req, ENOMEM); return; @@ -1160,6 +1162,7 @@ sdap_pam_change_password_send(TALLOC_CTX *mem_ctx, char *pwd_attr; int timeout; errno_t ret; + bool use_ppolicy; pwd_attr = opts->user_map[SDAP_AT_USER_PWD].name; @@ -1186,9 +1189,10 @@ sdap_pam_change_password_send(TALLOC_CTX *mem_ctx, switch (opts->pwmodify_mode) { case SDAP_PWMODIFY_EXOP: + use_ppolicy = dp_opt_get_int(opts->basic, SDAP_USE_PPOLICY); subreq = sdap_exop_modify_passwd_send(state, ev, sh, user_dn, password, new_password, - timeout); + timeout, use_ppolicy); break; case SDAP_PWMODIFY_LDAP: subreq = sdap_modify_passwd_send(state, ev, sh, timeout, pwd_attr, diff --git a/src/providers/ldap/ldap_opts.c b/src/providers/ldap/ldap_opts.c index 37616caadb7..04aaf914afa 100644 --- a/src/providers/ldap/ldap_opts.c +++ b/src/providers/ldap/ldap_opts.c @@ -134,6 +134,7 @@ struct dp_option default_basic_opts[] = { { "ldap_pwdlockout_dn", DP_OPT_STRING, NULL_STRING, NULL_STRING }, { "wildcard_limit", DP_OPT_NUMBER, { .number = 1000 }, NULL_NUMBER}, { "ldap_library_debug_level", DP_OPT_NUMBER, NULL_NUMBER, NULL_NUMBER}, + { "ldap_use_ppolicy", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, DP_OPTION_TERMINATOR }; diff --git a/src/providers/ldap/sdap.h b/src/providers/ldap/sdap.h index 678bf76701e..2b647067221 100644 --- a/src/providers/ldap/sdap.h +++ b/src/providers/ldap/sdap.h @@ -237,6 +237,7 @@ enum sdap_basic_opt { SDAP_PWDLOCKOUT_DN, SDAP_WILDCARD_LIMIT, SDAP_LIBRARY_DEBUG_LEVEL, + SDAP_USE_PPOLICY, SDAP_OPTS_BASIC /* opts counter */ }; diff --git a/src/providers/ldap/sdap_async.c b/src/providers/ldap/sdap_async.c index ab3572d1d4f..9d4c6cdc584 100644 --- a/src/providers/ldap/sdap_async.c +++ b/src/providers/ldap/sdap_async.c @@ -607,7 +607,8 @@ struct tevent_req *sdap_exop_modify_passwd_send(TALLOC_CTX *memctx, char *user_dn, const char *password, const char *new_password, - int timeout) + int timeout, + bool use_ppolicy) { struct tevent_req *req = NULL; struct sdap_exop_modify_passwd_state *state; @@ -652,15 +653,17 @@ struct tevent_req *sdap_exop_modify_passwd_send(TALLOC_CTX *memctx, return NULL; } - ret = sdap_control_create(state->sh, LDAP_CONTROL_PASSWORDPOLICYREQUEST, - 0, NULL, 0, &ctrls[0]); - if (ret != LDAP_SUCCESS && ret != LDAP_NOT_SUPPORTED) { - DEBUG(SSSDBG_CRIT_FAILURE, "sdap_control_create failed to create " - "Password Policy control.\n"); - ret = ERR_INTERNAL; - goto fail; + if (use_ppolicy) { + ret = sdap_control_create(state->sh, LDAP_CONTROL_PASSWORDPOLICYREQUEST, + 0, NULL, 0, &ctrls[0]); + if (ret != LDAP_SUCCESS && ret != LDAP_NOT_SUPPORTED) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_control_create failed to create " + "Password Policy control.\n"); + ret = ERR_INTERNAL; + goto fail; + } + request_controls = ctrls; } - request_controls = ctrls; DEBUG(SSSDBG_CONF_SETTINGS, "Executing extended operation\n"); diff --git a/src/providers/ldap/sdap_async.h b/src/providers/ldap/sdap_async.h index a7b0f691283..02d84672e16 100644 --- a/src/providers/ldap/sdap_async.h +++ b/src/providers/ldap/sdap_async.h @@ -146,7 +146,8 @@ struct tevent_req *sdap_auth_send(TALLOC_CTX *memctx, const char *sasl_user, const char *user_dn, struct sss_auth_token *authtok, - int simple_bind_timeout); + int simple_bind_timeout, + bool use_ppolicy); errno_t sdap_auth_recv(struct tevent_req *req, TALLOC_CTX *memctx, @@ -170,7 +171,8 @@ struct tevent_req *sdap_exop_modify_passwd_send(TALLOC_CTX *memctx, char *user_dn, const char *password, const char *new_password, - int timeout); + int timeout, + bool use_ppolicy); errno_t sdap_exop_modify_passwd_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, char **user_error_msg); diff --git a/src/providers/ldap/sdap_async_connection.c b/src/providers/ldap/sdap_async_connection.c index e8638725c78..a6d4ee4438b 100644 --- a/src/providers/ldap/sdap_async_connection.c +++ b/src/providers/ldap/sdap_async_connection.c @@ -38,6 +38,7 @@ struct sdap_rebind_proc_params { struct sdap_options *opts; struct sdap_handle *sh; bool use_start_tls; + bool use_ppolicy; }; static int sdap_rebind_proc(LDAP *ldap, LDAP_CONST char *url, ber_tag_t request, @@ -240,6 +241,8 @@ static void sdap_sys_connect_done(struct tevent_req *subreq) rebind_proc_params->opts = state->opts; rebind_proc_params->sh = state->sh; rebind_proc_params->use_start_tls = state->use_start_tls; + rebind_proc_params->use_ppolicy = dp_opt_get_bool(state->opts->basic, + SDAP_USE_PPOLICY); lret = ldap_set_rebind_proc(state->sh->ldap, sdap_rebind_proc, rebind_proc_params); @@ -659,7 +662,8 @@ static struct tevent_req *simple_bind_send(TALLOC_CTX *memctx, struct sdap_handle *sh, int timeout, const char *user_dn, - struct berval *pw) + struct berval *pw, + bool use_ppolicy) { struct tevent_req *req; struct simple_bind_state *state; @@ -683,14 +687,16 @@ static struct tevent_req *simple_bind_send(TALLOC_CTX *memctx, state->sh = sh; state->user_dn = user_dn; - ret = sss_ldap_control_create(LDAP_CONTROL_PASSWORDPOLICYREQUEST, - 0, NULL, 0, &ctrls[0]); - if (ret != LDAP_SUCCESS && ret != LDAP_NOT_SUPPORTED) { - DEBUG(SSSDBG_CRIT_FAILURE, "sss_ldap_control_create failed to create " - "Password Policy control.\n"); - goto fail; + if (use_ppolicy) { + ret = sss_ldap_control_create(LDAP_CONTROL_PASSWORDPOLICYREQUEST, + 0, NULL, 0, &ctrls[0]); + if (ret != LDAP_SUCCESS && ret != LDAP_NOT_SUPPORTED) { + DEBUG(SSSDBG_CRIT_FAILURE, "sss_ldap_control_create failed to create " + "Password Policy control.\n"); + goto fail; + } + request_controls = ctrls; } - request_controls = ctrls; DEBUG(SSSDBG_CONF_SETTINGS, "Executing simple bind as: %s\n", state->user_dn); @@ -1358,7 +1364,8 @@ struct tevent_req *sdap_auth_send(TALLOC_CTX *memctx, const char *sasl_user, const char *user_dn, struct sss_auth_token *authtok, - int simple_bind_timeout) + int simple_bind_timeout, + bool use_ppolicy) { struct tevent_req *req, *subreq; struct sdap_auth_state *state; @@ -1397,7 +1404,7 @@ struct tevent_req *sdap_auth_send(TALLOC_CTX *memctx, pw.bv_len = pwlen; state->is_sasl = false; - subreq = simple_bind_send(state, ev, sh, simple_bind_timeout, user_dn, &pw); + subreq = simple_bind_send(state, ev, sh, simple_bind_timeout, user_dn, &pw, use_ppolicy); if (!subreq) { tevent_req_error(req, ENOMEM); return tevent_req_post(req, ev); @@ -1972,7 +1979,9 @@ static void sdap_cli_auth_step(struct tevent_req *req) SDAP_SASL_AUTHID), user_dn, authtok, dp_opt_get_int(state->opts->basic, - SDAP_OPT_TIMEOUT)); + SDAP_OPT_TIMEOUT), + dp_opt_get_bool(state->opts->basic, + SDAP_USE_PPOLICY)); talloc_free(authtok); if (!subreq) { tevent_req_error(req, ENOMEM); @@ -2320,15 +2329,17 @@ static int sdap_rebind_proc(LDAP *ldap, LDAP_CONST char *url, ber_tag_t request, sasl_mech = dp_opt_get_string(p->opts->basic, SDAP_SASL_MECH); if (sasl_mech == NULL) { - ret = sss_ldap_control_create(LDAP_CONTROL_PASSWORDPOLICYREQUEST, - 0, NULL, 0, &ctrls[0]); - if (ret != LDAP_SUCCESS && ret != LDAP_NOT_SUPPORTED) { - DEBUG(SSSDBG_CRIT_FAILURE, - "sss_ldap_control_create failed to create " + if (p->use_ppolicy) { + ret = sss_ldap_control_create(LDAP_CONTROL_PASSWORDPOLICYREQUEST, + 0, NULL, 0, &ctrls[0]); + if (ret != LDAP_SUCCESS && ret != LDAP_NOT_SUPPORTED) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sss_ldap_control_create failed to create " "Password Policy control.\n"); - goto done; + goto done; + } + request_controls = ctrls; } - request_controls = ctrls; user_dn = dp_opt_get_string(p->opts->basic, SDAP_DEFAULT_BIND_DN); if (user_dn != NULL) { diff --git a/src/tests/system/tests/test_ldap.py b/src/tests/system/tests/test_ldap.py index 5ae7f1876a7..fde23d9cf9b 100644 --- a/src/tests/system/tests/test_ldap.py +++ b/src/tests/system/tests/test_ldap.py @@ -16,8 +16,9 @@ @pytest.mark.importance("critical") @pytest.mark.authentication @pytest.mark.parametrize("modify_mode", ["exop", "ldap_modify"]) +@pytest.mark.parametrize("use_ppolicy", ["true", "false"]) @pytest.mark.topology(KnownTopology.LDAP) -def test_ldap__change_password(client: Client, ldap: LDAP, modify_mode: str): +def test_ldap__change_password(client: Client, ldap: LDAP, modify_mode: str, use_ppolicy: str): """ :title: Change password with "ldap_pwmodify_mode" set to @modify_mode :setup: @@ -45,6 +46,7 @@ def test_ldap__change_password(client: Client, ldap: LDAP, modify_mode: str): ldap.aci.add('(targetattr="userpassword")(version 3.0; acl "pwp test"; allow (all) userdn="ldap:///self";)') client.sssd.domain["ldap_pwmodify_mode"] = modify_mode + client.sssd.domain["ldap_use_ppolicy"] = use_ppolicy client.sssd.start() assert client.auth.ssh.password(user, old_pass), "Authentication with old correct password failed" @@ -57,8 +59,9 @@ def test_ldap__change_password(client: Client, ldap: LDAP, modify_mode: str): @pytest.mark.ticket(bz=[795044, 1695574]) @pytest.mark.parametrize("modify_mode", ["exop", "ldap_modify"]) +@pytest.mark.parametrize("use_ppolicy", ["true", "false"]) @pytest.mark.topology(KnownTopology.LDAP) -def test_ldap__change_password_new_pass_not_match(client: Client, ldap: LDAP, modify_mode: str): +def test_ldap__change_password_new_pass_not_match(client: Client, ldap: LDAP, modify_mode: str, use_ppolicy: str): """ :title: Change password with "ldap_pwmodify_mode" set to @modify_mode, but retyped password do not match :setup: @@ -76,6 +79,7 @@ def test_ldap__change_password_new_pass_not_match(client: Client, ldap: LDAP, mo ldap.aci.add('(targetattr="userpassword")(version 3.0; acl "pwp test"; allow (all) userdn="ldap:///self";)') client.sssd.domain["ldap_pwmodify_mode"] = modify_mode + client.sssd.domain["ldap_use_ppolicy"] = use_ppolicy client.sssd.start() assert not client.auth.passwd.password( @@ -85,8 +89,9 @@ def test_ldap__change_password_new_pass_not_match(client: Client, ldap: LDAP, mo @pytest.mark.ticket(bz=[795044, 1695574, 1795220]) @pytest.mark.parametrize("modify_mode", ["exop", "ldap_modify"]) +@pytest.mark.parametrize("use_ppolicy", ["true", "false"]) @pytest.mark.topology(KnownTopology.LDAP) -def test_ldap__change_password_lowercase(client: Client, ldap: LDAP, modify_mode: str): +def test_ldap__change_password_lowercase(client: Client, ldap: LDAP, modify_mode: str, use_ppolicy: str): """ :title: Change password to lower-case letters, password check fail :setup: @@ -108,6 +113,7 @@ def test_ldap__change_password_lowercase(client: Client, ldap: LDAP, modify_mode ldap.ldap.modify("cn=config", replace={"passwordCheckSyntax": "on"}) client.sssd.domain["ldap_pwmodify_mode"] = modify_mode + client.sssd.domain["ldap_use_ppolicy"] = use_ppolicy client.sssd.start() assert not client.auth.passwd.password( @@ -122,8 +128,9 @@ def test_ldap__change_password_lowercase(client: Client, ldap: LDAP, modify_mode @pytest.mark.ticket(bz=[1695574, 1795220]) @pytest.mark.parametrize("modify_mode", ["exop", "ldap_modify"]) +@pytest.mark.parametrize("use_ppolicy", ["true", "false"]) @pytest.mark.topology(KnownTopology.LDAP) -def test_ldap__change_password_wrong_current(client: Client, ldap: LDAP, modify_mode: str): +def test_ldap__change_password_wrong_current(client: Client, ldap: LDAP, modify_mode: str, use_ppolicy: str): """ :title: Password change failed because an incorrect password was used :setup: @@ -141,6 +148,7 @@ def test_ldap__change_password_wrong_current(client: Client, ldap: LDAP, modify_ ldap.aci.add('(targetattr="userpassword")(version 3.0; acl "pwp test"; allow (all) userdn="ldap:///self";)') client.sssd.domain["ldap_pwmodify_mode"] = modify_mode + client.sssd.domain["ldap_use_ppolicy"] = use_ppolicy client.sssd.start() assert not client.auth.passwd.password("user1", "wrong123", "Newpass123"), "Password change did not fail"