Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ldap: add 'exop_force' value for ldap_pwmodify_mode #7614

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/man/sssd-ldap.5.xml
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,17 @@
userPassword (not recommended).
</para>
</listitem>
<listitem>
<para>
exop_force - Try Password Modify
Extended Operation (RFC 3062) even if
there are no grace logins left.
pbrezina marked this conversation as resolved.
Show resolved Hide resolved
Depending on the type and configuration
of the LDAP server the password change
might fail because an authenticated bind
is not possible.
</para>
</listitem>
</itemizedlist>
</para>
<para>
Expand Down
3 changes: 2 additions & 1 deletion src/providers/ipa/ipa_auth.c
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,8 @@ static void ipa_pam_auth_handler_connect_done(struct tevent_req *subreq)
SDAP_USE_PPOLICY);

subreq = sdap_auth_send(state, state->ev, sh, NULL, NULL, dn,
state->pd->authtok, timeout, use_ppolicy);
state->pd->authtok, timeout, use_ppolicy,
state->auth_ctx->sdap_auth_ctx->opts->pwmodify_mode);
if (subreq == NULL) {
goto done;
}
Expand Down
5 changes: 4 additions & 1 deletion src/providers/ldap/ldap_auth.c
Original file line number Diff line number Diff line change
Expand Up @@ -914,7 +914,8 @@ static void auth_do_bind(struct tevent_req *req)
subreq = sdap_auth_send(state, state->ev, state->sh,
NULL, NULL, state->dn,
state->authtok,
timeout, use_ppolicy);
timeout, use_ppolicy,
state->ctx->opts->pwmodify_mode);
if (!subreq) {
tevent_req_error(req, ENOMEM);
return;
Expand Down Expand Up @@ -1208,6 +1209,7 @@ sdap_pam_change_password_send(TALLOC_CTX *mem_ctx,

switch (opts->pwmodify_mode) {
case SDAP_PWMODIFY_EXOP:
case SDAP_PWMODIFY_EXOP_FORCE:
use_ppolicy = dp_opt_get_bool(opts->basic, SDAP_USE_PPOLICY);
subreq = sdap_exop_modify_passwd_send(state, ev, sh, user_dn,
password, new_password,
Expand Down Expand Up @@ -1252,6 +1254,7 @@ static void sdap_pam_change_password_done(struct tevent_req *subreq)

switch (state->mode) {
case SDAP_PWMODIFY_EXOP:
case SDAP_PWMODIFY_EXOP_FORCE:
ret = sdap_exop_modify_passwd_recv(subreq, state,
&state->user_error_message);
break;
Expand Down
2 changes: 2 additions & 0 deletions src/providers/ldap/ldap_options.c
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,8 @@ int ldap_get_options(TALLOC_CTX *memctx,
opts->pwmodify_mode = SDAP_PWMODIFY_EXOP;
} else if (strcasecmp(pwmodify, "ldap_modify") == 0) {
opts->pwmodify_mode = SDAP_PWMODIFY_LDAP;
} else if (strcasecmp(pwmodify, "exop_force") == 0) {
opts->pwmodify_mode = SDAP_PWMODIFY_EXOP_FORCE;
} else {
DEBUG(SSSDBG_FATAL_FAILURE, "Unrecognized pwmodify mode: %s\n", pwmodify);
ret = EINVAL;
Expand Down
5 changes: 3 additions & 2 deletions src/providers/ldap/sdap.h
Original file line number Diff line number Diff line change
Expand Up @@ -550,8 +550,9 @@ struct sdap_options {

/* password modify mode */
enum pwmodify_mode {
SDAP_PWMODIFY_EXOP = 1, /* pwmodify extended operation */
SDAP_PWMODIFY_LDAP = 2 /* ldap_modify of userPassword */
SDAP_PWMODIFY_EXOP = 1, /* pwmodify extended operation */
SDAP_PWMODIFY_LDAP = 2, /* ldap_modify of userPassword */
SDAP_PWMODIFY_EXOP_FORCE = 3 /* forced pwmodify extended operation */
} pwmodify_mode;

/* The search bases for the domain or its subdomain */
Expand Down
3 changes: 2 additions & 1 deletion src/providers/ldap/sdap_async.h
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,8 @@ struct tevent_req *sdap_auth_send(TALLOC_CTX *memctx,
const char *user_dn,
struct sss_auth_token *authtok,
int simple_bind_timeout,
bool use_ppolicy);
bool use_ppolicy,
enum pwmodify_mode pwmodify_mode);

errno_t sdap_auth_recv(struct tevent_req *req,
TALLOC_CTX *memctx,
Expand Down
27 changes: 21 additions & 6 deletions src/providers/ldap/sdap_async_connection.c
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,7 @@ struct simple_bind_state {
struct tevent_context *ev;
struct sdap_handle *sh;
const char *user_dn;
enum pwmodify_mode pwmodify_mode;

struct sdap_op *op;

Expand All @@ -663,7 +664,8 @@ static struct tevent_req *simple_bind_send(TALLOC_CTX *memctx,
int timeout,
const char *user_dn,
struct berval *pw,
bool use_ppolicy)
bool use_ppolicy,
enum pwmodify_mode pwmodify_mode)
{
struct tevent_req *req;
struct simple_bind_state *state;
Expand All @@ -686,6 +688,7 @@ static struct tevent_req *simple_bind_send(TALLOC_CTX *memctx,
state->ev = ev;
state->sh = sh;
state->user_dn = user_dn;
state->pwmodify_mode = pwmodify_mode;

if (use_ppolicy) {
ret = sss_ldap_control_create(LDAP_CONTROL_PASSWORDPOLICYREQUEST,
Expand Down Expand Up @@ -872,7 +875,12 @@ static void simple_bind_done(struct sdap_op *op,
* Grace Authentications". */
DEBUG(SSSDBG_TRACE_LIBS,
"Password expired, grace logins exhausted.\n");
ret = ERR_AUTH_FAILED;
if (state->pwmodify_mode == SDAP_PWMODIFY_EXOP_FORCE) {
DEBUG(SSSDBG_TRACE_LIBS, "Password change forced.\n");
ret = ERR_PASSWORD_EXPIRED;
} else {
ret = ERR_AUTH_FAILED;
}
}
} else if (strcmp(response_controls[c]->ldctl_oid,
LDAP_CONTROL_PWEXPIRED) == 0) {
Expand All @@ -885,7 +893,12 @@ static void simple_bind_done(struct sdap_op *op,
if (result == LDAP_INVALID_CREDENTIALS) {
DEBUG(SSSDBG_TRACE_LIBS,
"Password expired, grace logins exhausted.\n");
ret = ERR_AUTH_FAILED;
if (state->pwmodify_mode == SDAP_PWMODIFY_EXOP_FORCE) {
DEBUG(SSSDBG_TRACE_LIBS, "Password change forced.\n");
ret = ERR_PASSWORD_EXPIRED;
} else {
ret = ERR_AUTH_FAILED;
}
} else {
DEBUG(SSSDBG_TRACE_LIBS,
"Password expired, user must set a new password.\n");
Expand Down Expand Up @@ -1365,7 +1378,8 @@ struct tevent_req *sdap_auth_send(TALLOC_CTX *memctx,
const char *user_dn,
struct sss_auth_token *authtok,
int simple_bind_timeout,
bool use_ppolicy)
bool use_ppolicy,
enum pwmodify_mode pwmodify_mode)
{
struct tevent_req *req, *subreq;
struct sdap_auth_state *state;
Expand Down Expand Up @@ -1404,7 +1418,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, use_ppolicy);
subreq = simple_bind_send(state, ev, sh, simple_bind_timeout, user_dn, &pw, use_ppolicy, pwmodify_mode);
if (!subreq) {
tevent_req_error(req, ENOMEM);
return tevent_req_post(req, ev);
Expand Down Expand Up @@ -1981,7 +1995,8 @@ static void sdap_cli_auth_step(struct tevent_req *req)
dp_opt_get_int(state->opts->basic,
SDAP_OPT_TIMEOUT),
dp_opt_get_bool(state->opts->basic,
SDAP_USE_PPOLICY));
SDAP_USE_PPOLICY),
state->opts->pwmodify_mode);
pbrezina marked this conversation as resolved.
Show resolved Hide resolved
talloc_free(authtok);
if (!subreq) {
tevent_req_error(req, ENOMEM);
Expand Down
58 changes: 54 additions & 4 deletions src/tests/system/tests/test_ldap.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

@pytest.mark.ticket(bz=[795044, 1695574])
@pytest.mark.importance("critical")
@pytest.mark.parametrize("modify_mode", ["exop", "ldap_modify"])
@pytest.mark.parametrize("modify_mode", ["exop", "ldap_modify", "exop_force"])
@pytest.mark.parametrize("use_ppolicy", ["true", "false"])
@pytest.mark.parametrize("sssd_service_user", ("root", "sssd"))
@pytest.mark.topology(KnownTopology.LDAP)
Expand Down Expand Up @@ -75,7 +75,7 @@ def test_ldap__password_change_using_ppolicy(

@pytest.mark.ticket(bz=[795044, 1695574])
@pytest.mark.importance("critical")
@pytest.mark.parametrize("modify_mode", ["exop", "ldap_modify"])
@pytest.mark.parametrize("modify_mode", ["exop", "ldap_modify", "exop_force"])
@pytest.mark.parametrize("use_ppolicy", ["true", "false"])
@pytest.mark.topology(KnownTopology.LDAP)
@pytest.mark.builtwith("ldap_use_ppolicy")
Expand Down Expand Up @@ -109,7 +109,7 @@ def test_ldap__password_change_new_passwords_do_not_match_using_ppolicy(

@pytest.mark.ticket(bz=[795044, 1695574, 1795220])
@pytest.mark.importance("critical")
@pytest.mark.parametrize("modify_mode", ["exop", "ldap_modify"])
@pytest.mark.parametrize("modify_mode", ["exop", "ldap_modify", "exop_force"])
@pytest.mark.parametrize("use_ppolicy", ["true", "false"])
@pytest.mark.topology(KnownTopology.LDAP)
@pytest.mark.builtwith("ldap_use_ppolicy")
Expand Down Expand Up @@ -150,7 +150,7 @@ def test_ldap__password_change_new_password_does_not_meet_complexity_requirement

@pytest.mark.ticket(bz=[1695574, 1795220])
@pytest.mark.importance("critical")
@pytest.mark.parametrize("modify_mode", ["exop", "ldap_modify"])
@pytest.mark.parametrize("modify_mode", ["exop", "ldap_modify", "exop_force"])
@pytest.mark.parametrize("use_ppolicy", ["true", "false"])
@pytest.mark.topology(KnownTopology.LDAP)
@pytest.mark.builtwith("ldap_use_ppolicy")
Expand Down Expand Up @@ -452,3 +452,53 @@ def test_ldap__lookup_and_authenticate_as_user_with_different_object_search_base
assert result is not None, "User is not found!"
assert result.name == user.name, "Username is not correct!"
assert client.auth.ssh.password(user.name, "Secret123"), "User login failed!"


@pytest.mark.ticket(jira="RHEL-55993")
@pytest.mark.importance("critical")
@pytest.mark.parametrize(
"modify_mode, expected, err_msg",
[("exop", 1, "Expected login failure"), ("exop_force", 3, "Expected password change request")],
)
@pytest.mark.parametrize("method", ["su", "ssh"])
@pytest.mark.topology(KnownTopology.LDAP)
def test_ldap__password_change_no_grace_logins_left(
client: Client, ldap: LDAP, modify_mode: str, expected: int, err_msg: str, method: str
):
"""
:title: Password change when no grace logins left
:description: Typically the LDAP extended operation to change a password
requires an authenticated bind, even if the data send with the extended
operation contains the old password. If the old password is expired and
there are no grace logins left an authenticated bind is not possible anymore
and as a result it is not possible for the user to change their password.
With 'exop' SSSD will not try to ask the user for new credentials while with
'exop_force' SSSD will ask for new credentials and will try to run the password
change extended operation.
:setup:
1. Set "passwordExp" to "on"
2. Set "passwordMaxAge" to "1"
3. Set "passwordGraceLimit" to "0"
4. Add a user to LDAP
5. Wait until the password is expired
6. Set "ldap_pwmodify_mode"
7. Start SSSD
:steps:
1. Authenticate as the user with 'exop_force' set
2. Authenticate as the user with 'exop' set
:expectedresults:
1. With 'exop_force' expect a request to change the password
2. With 'exop' expect just a failed login
:customerscenario: False
"""
ldap.ldap.modify("cn=config", replace={"passwordExp": "on", "passwordMaxAge": "1", "passwordGraceLimit": "0"})
ldap.user("user1").add(password="Secret123")

# make sure the password is expired
time.sleep(3)

client.sssd.domain["ldap_pwmodify_mode"] = modify_mode
client.sssd.start()

rc, _, _, _ = client.auth.parametrize(method).password_with_output("user1", "Secret123")
assert rc == expected, err_msg
Loading