Skip to content
Merged
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
6 changes: 6 additions & 0 deletions src/providers/ldap/ldap_auth.c
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,12 @@ static errno_t check_pwexpire_shadow(struct spwd *spwd, time_t now,
if (spwd->sp_lstchg <= 0) {
DEBUG(SSSDBG_CONF_SETTINGS,
"Last change day is not set, new password needed.\n");
if (pd != NULL) {
ret = add_expired_warning(pd, 0);
if (ret != EOK) {
DEBUG(SSSDBG_CRIT_FAILURE, "add_expired_warning failed.\n");
}
}
return ERR_PASSWORD_EXPIRED;
}

Expand Down
9 changes: 6 additions & 3 deletions src/providers/ldap/sdap_access.c
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ struct sdap_access_req_ctx {
struct ldb_message *user_entry;
size_t current_rule;
enum sdap_access_control_type ac_type;
bool pw_expire_warn_seen;
};

static errno_t sdap_access_check_next_rule(struct sdap_access_req_ctx *state,
Expand Down Expand Up @@ -150,6 +151,7 @@ sdap_access_send(TALLOC_CTX *mem_ctx,
state->access_ctx = access_ctx;
state->conn = conn;
state->current_rule = 0;
state->pw_expire_warn_seen = false;

DEBUG(SSSDBG_TRACE_FUNC,
"Performing access check for user [%s]\n", pd->user);
Expand Down Expand Up @@ -212,8 +214,8 @@ static errno_t sdap_access_check_next_rule(struct sdap_access_req_ctx *state,
while (ret == EOK) {
switch (state->access_ctx->access_rule[state->current_rule]) {
case LDAP_ACCESS_EMPTY:
/* we are done with no errors */
return EOK;
/* all rules passed; preserve warn semantics if seen */
return state->pw_expire_warn_seen ? ERR_PASSWORD_EXPIRED_WARN : EOK;

/* This option is deprecated by LDAP_ACCESS_PPOLICY */
case LDAP_ACCESS_LOCKOUT:
Expand Down Expand Up @@ -295,7 +297,8 @@ static errno_t sdap_access_check_next_rule(struct sdap_access_req_ctx *state,
state->access_ctx->type,
state->access_ctx->id_ctx->opts);
if (ret == ERR_PASSWORD_EXPIRED) {
ret = ERR_PASSWORD_EXPIRED_WARN;
state->pw_expire_warn_seen = true;
ret = EOK;
}
break;

Expand Down
69 changes: 69 additions & 0 deletions src/tests/system/tests/test_ipa.py
Original file line number Diff line number Diff line change
Expand Up @@ -1256,3 +1256,72 @@ def test_ipa__prohibited_short_names(ipa: IPA):

assert result is not None, "Test failure - no result of SSSD restart!"
assert result.rc != 0, "SSSD should refuse to start with full_name_format set to '%1$s'!"


@pytest.mark.importance("low")
@pytest.mark.topology(KnownTopology.IPA)
def test_ipa__password_expiration_warning_does_not_overwrite_errors(client: Client, ipa: IPA):
"""
:title: SSSD warns that a password is about to expire
:setup:
1. Create user with expired password
2. Lock the user by disabling it
3. Set 'ipa_access_order = pwd_expire_policy_warn, expire' and restart SSSD
4. Create SSH key for the user and store it one the server
:steps:
1. SSH into the local host with the SSH key of the user
2. Change 'ldap_access_order' to 'pwd_expire_policy_warn, expire'
3. SSH into the local host with the SSH key of the user
:expectedresults:
1. SSH is successful and shows 'Your password has expired.' on standard error
2. Config change is successful
3. SSH fails because the 'expire' policy rejects the locked user
:customerscenario: False
"""

user = ipa.user("user1").add(password="Secret123", require_password_reset=True)

result = ipa.host.conn.run(f"ipa user-disable {user.name}")
assert result.rc == 0, "'ipa user-disable' failed!"
assert f'Disabled user account "{user.name}"' in result.stdout, "Unexpected output from 'ipa user-disable'!"

client.sssd.domain["ipa_access_order"] = "pwd_expire_policy_warn"
client.sssd.enable_responder("ssh")
client.sssd.restart()

pwd = client.tools.getent.passwd(user.name)
assert pwd is not None, "User not found!"
assert pwd.name is not None, "User name is missing!"
assert pwd.home is not None, "home directory is missing!"

key = client.tools.sshkey.generate(pwd.name, pwd.home)[0]
user.modify(sshpubkey=key)
# remove password from keyfile to make non-interactive login work
client.host.conn.run(f"ssh-keygen -p -P ' ' -N '' -f {pwd.home}/.ssh/id_rsa")

client.sssd.restart(clean=True)

result = client.host.conn.run(
f"sudo -u {user.name} ssh -o StrictHostKeychecking=no "
"-o PubkeyAuthentication=yes -o GSSAPIAuthentication=no "
"-o KbdInteractiveAuthentication=no -o PasswordAuthentication=no "
f"-l {user.name} localhost whoami",
raise_on_error=False,
)
assert result.rc == 0, "SSH with pubkey failed!"
assert result.stdout == f"{user.name}", "Missing user name output!"
assert "Your password has expired." in result.stderr, "Missing 'Your password has expired.' in error output!"

client.sssd.domain["ipa_access_order"] = "pwd_expire_policy_warn, expire"
client.sssd.restart()

result = client.host.conn.run(
f"sudo -u {user.name} ssh -o StrictHostKeychecking=no "
"-o PubkeyAuthentication=yes -o GSSAPIAuthentication=no "
"-o KbdInteractiveAuthentication=no -o PasswordAuthentication=no "
f"-l {user.name} localhost whoami",
raise_on_error=False,
)
assert result.rc != 0, "SSH was successful but should have failed!"
assert result.stdout == "", "Output is not empty!"
assert "Your password has expired." in result.stderr, "Missing 'Your password has expired.' in error output!"
70 changes: 70 additions & 0 deletions src/tests/system/tests/test_ldap.py
Original file line number Diff line number Diff line change
Expand Up @@ -1108,3 +1108,73 @@ def test_ldap__search_base_limits_rfc2307bis_posixgroup_scope(client: Client, ld
assert (
client.tools.getent.group("outside_group") is None
), "outside_group should not be visible outside search base!"


@pytest.mark.topology(KnownTopology.LDAP)
@pytest.mark.importance("low")
def test_ldap__password_expiration_warning_does_not_overwrite_errors(client: Client, provider: LDAP):
"""
:title: SSSD warns that a password is about to expire
:setup:
1. Create user with expired password
3. Set 'ldap_access_order = pwd_expire_policy_warn' and restart SSSD
4. Create SSH key for the user and write it to 'authorizred_keys'
:steps:
1. SSH into the local host with the SSH key of the user
2. Change 'ldap_access_order' to 'pwd_expire_policy_warn, expire'
3. SSH into the local host with the SSH key of the user
:expectedresults:
1. SSH is successful and shows 'Your password has expired.' on standard error
2. Config change is successful
3. SSH fails because the 'expire' policy rejects the locked user
:customerscenario: False
"""

user = provider.user("user1").add(
shadowMin=0, shadowMax=99999, shadowWarning=7, shadowLastChange=0, shadowExpire=1, password="Secret123"
)

client.sssd.domain["auto_private_groups"] = "True"
client.sssd.domain["access_provider"] = "ldap"
client.sssd.domain["ldap_pwd_policy"] = "shadow"
client.sssd.domain["ldap_account_expire_policy"] = "shadow"
client.sssd.domain["ldap_access_order"] = "pwd_expire_policy_warn"
client.sssd.restart()

pwd = client.tools.getent.passwd(user.name)
assert pwd is not None, "User not found!"
assert pwd.name is not None, "User name is missing!"
assert pwd.home is not None, "home directory is missing!"

client.host.conn.run(f"su - {user.name}", raise_on_error=False)
key = client.tools.sshkey.generate(pwd.name, pwd.home)[0]
client.fs.write(f"{pwd.home}/.ssh/authorized_keys", key)
# remove password from keyfile to make non-interactive login work
client.host.conn.run(f"ssh-keygen -p -P ' ' -N '' -f {pwd.home}/.ssh/id_rsa")

client.sssd.restart(clean=True)

result = client.host.conn.run(
f"sudo -u {user.name} ssh -o StrictHostKeychecking=no "
"-o PubkeyAuthentication=yes -o GSSAPIAuthentication=no "
"-o KbdInteractiveAuthentication=no -o PasswordAuthentication=no "
f"-l {user.name} localhost whoami",
raise_on_error=False,
)
assert result.rc == 0, "SSH with pubkey failed!"
assert result.stdout == f"{user.name}", "Missing user name output!"
assert "Your password has expired." in result.stderr, "Missing 'Your password has expired.' in error output!"

client.sssd.domain["ldap_access_order"] = "pwd_expire_policy_warn, expire"
client.sssd.restart(clean=True)

result = client.host.conn.run(
f"sudo -u {user.name} ssh -o StrictHostKeychecking=no "
"-o PubkeyAuthentication=yes -o GSSAPIAuthentication=no "
"-o KbdInteractiveAuthentication=no -o PasswordAuthentication=no "
f"-l {user.name} localhost whoami",
raise_on_error=False,
)
assert result.rc != 0, "SSH was successful but should have failed!"
assert result.stdout == "", "Output is not empty!"
assert "Your password has expired." in result.stderr, "Missing 'Your password has expired.' in error output!"
Loading