-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
pam: adding support to manage pam_access and pam_faillock
- Loading branch information
Dan Lavu
committed
Aug 15, 2023
1 parent
97a3994
commit 05b60e1
Showing
3 changed files
with
278 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
Pluggable Authentication Module (PAM) | ||
##################################### | ||
|
||
Class :class:`sssd_test_framework.utils.pam.PAMUtils` provides | ||
an API to manage PAM modules; pam_access and pam_faillock. | ||
|
||
pam_access: A module that performs host based access control on a system. | ||
|
||
.. code-block:: python | ||
:caption: Example PAM Access usage | ||
@pytest.mark.topology(KnownTopologyGroup.AnyProvider) | ||
def test_example(client: Client, provider: GenericProvider): | ||
# Add users | ||
provider.user("user1").add(password="Secret123") | ||
provider.user("user1").add(password="Secret123") | ||
# Add rule to permit "user1" and deny "user2" | ||
client.pam.access.add(["+:user1:ALL","-:user2:NONE] | ||
client.authselect.select("sssd", "[with-pamaccess]") | ||
# Start SSSD | ||
client.sssd.start() | ||
# Check the results | ||
assert client.auth.ssh.password("user1", "Secret123") | ||
assert client.auth.ssh.password("user2", "Secret123") is False | ||
pam_faillock: A module that sets login attempts and lock out time. | ||
.. code-block:: python | ||
:caption: Example PAM Faillock usage | ||
@pytest.mark.topology(KnownTopologyGroup.AnyProvider) | ||
def test_example(client: Client, provider: GenericProvider): | ||
# Add user | ||
provider.user("user1").add(password="Secret123") | ||
# Setup faillock | ||
client.pam.faillock.config() | ||
client.authselect.select("sssd", "with-faillock") | ||
# Start SSSD | ||
client.sssd.start() | ||
# Check the results | ||
assert client.auth.ssh.password("user1", "Secret123") | ||
assert client.auth.ssh.password("user1", "bad_password") is False | ||
assert client.auth.ssh.password("user1", "bad_password") is False | ||
assert client.auth.ssh.password("user1", "bad_password") is False | ||
assert client.auth.ssh.password("user1", "Secret123") is False | ||
client.pam.faillock("user1").reset | ||
assert client.auth.ssh.password("user1", "Secret123") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,220 @@ | ||
""""PAM Tools.""" | ||
|
||
from __future__ import annotations | ||
|
||
from pytest_mh import MultihostHost, MultihostUtility | ||
from pytest_mh.utils.fs import LinuxFileSystem | ||
|
||
__all__ = [ | ||
"PAMUtils", | ||
"PAMAccess", | ||
"PAMFaillock", | ||
] | ||
|
||
|
||
class PAMUtils(MultihostUtility[MultihostHost]): | ||
""" | ||
Management of PAM modules | ||
""" | ||
|
||
def __init__(self, host: MultihostHost, fs: LinuxFileSystem) -> None: | ||
""" | ||
:param host: Remote host instance | ||
:type host: MultihostHost | ||
""" | ||
super().__init__(host) | ||
|
||
self.fs: LinuxFileSystem = fs | ||
|
||
def access(self) -> PAMAccess: | ||
""" | ||
:return: PAM Access object | ||
:rtype: PAMAccess | ||
""" | ||
return PAMAccess(self) | ||
|
||
def faillock(self) -> PAMFaillock: | ||
""" | ||
:return: PAM Faillock object | ||
:rtype: PAMFaillock | ||
""" | ||
return PAMFaillock(self) | ||
|
||
|
||
class PAMAccess: | ||
""" | ||
Management of PAM Access on the client host. | ||
.. code-block:: python | ||
:caption: Example usage | ||
@pytest.mark.topology(KnownTopologyGroup.AnyProvider) | ||
def test_example(client: Client, provider: GenericProvider): | ||
# Add users | ||
provider.user("user1").add(password="Secret123") | ||
provider.user("user1").add(password="Secret123") | ||
# Add rule to permit "user1" and deny "user2" | ||
client.pam.access.add(["+:user1:ALL","-:user2:NONE] | ||
client.authselect.select("sssd", "[with-pamaccess]") | ||
# Start SSSD | ||
client.sssd.start() | ||
# Check the results | ||
assert client.auth.ssh.password("user1", "Secret123") | ||
assert client.auth.ssh.password("user2", "Secret123") is False | ||
""" | ||
|
||
def __init__(self, util: PAMUtils, file: str | None = "/etc/security/access.conf") -> None: | ||
""" | ||
:param util: PAMUtils utility object | ||
:type util: PAMUtils | ||
:param file: File name of access file | ||
:type file: str, optional | ||
""" | ||
self.util: PAMUtils = util | ||
self.file: str = file | ||
|
||
def get(self) -> list[str]: | ||
""" | ||
Get PAM rules from access file. | ||
:return: List of PAM access rules | ||
:rtype: list | ||
""" | ||
result = self.util.fs.read(self.file).split("\n") | ||
self.util.logger.info(f"{result} are in {self.file} on {self.util.host.hostname}") | ||
|
||
return result | ||
|
||
def add(self, rules: list[str]) -> PAMAccess: | ||
""" | ||
Add one or more rules to access file. | ||
:param rules: Access rule | ||
:type rules: list[str], required | ||
:return: self | ||
:rtype: PAMAccess | ||
""" | ||
content = "" | ||
for i in rules: | ||
content += f"{i}\n" | ||
|
||
self.util.logger.info(f"{content} written to {self.file} on {self.util.host.hostname}") | ||
self.util.fs.write(self.file, content) | ||
|
||
return self | ||
|
||
def delete(self, rules: list[str]) -> PAMAccess: | ||
""" | ||
Delete one or more rules from access file. | ||
:param rules: PAM Access rule | ||
:type rules: list[str], required | ||
""" | ||
result = self.get() | ||
for i in result: | ||
if i in rules: | ||
result.pop() | ||
|
||
content = "" | ||
for i in result: | ||
content += f"{i}\n" | ||
|
||
self.util.logger.info(f"{content} written to {self.file} on {self.util.host.hostname}") | ||
self.util.fs.write(self.file, content) | ||
|
||
return self | ||
|
||
|
||
class PAMFaillock: | ||
""" | ||
Management of PAM Faillock on the client host. | ||
.. code-block:: python | ||
:caption: Example usage | ||
@pytest.mark.topology(KnownTopologyGroup.AnyProvider) | ||
def test_example(client: Client, provider: GenericProvider): | ||
# Add user | ||
provider.user("user1").add(password="Secret123") | ||
# Setup faillock | ||
client.pam.faillock.config() | ||
client.authselect.select("sssd", "with-faillock") | ||
# Start SSSD | ||
client.sssd.start() | ||
# Check the results | ||
assert client.auth.ssh.password("user1", "Secret123") | ||
assert client.auth.ssh.password("user1", "bad_password") is False | ||
assert client.auth.ssh.password("user1", "bad_password") is False | ||
assert client.auth.ssh.password("user1", "bad_password") is False | ||
assert client.auth.ssh.password("user1", "Secret123") is False | ||
client.pam.faillock("user1").reset | ||
assert client.auth.ssh.password("user1", "Secret123") | ||
""" | ||
|
||
def __init__( | ||
self, util: PAMUtils, user: str | None = None, file: str | None = "/etc/security/faillock.conf" | ||
) -> None: | ||
""" | ||
:param util: PAMUtils object | ||
:type util: PAMUtils | ||
:param user: User | ||
:type user: str, optional | ||
:param file: Faillock configuration file | ||
:type file: str, optional | ||
""" | ||
self.util: PAMUtils = util | ||
self.user: str = user | ||
self.file: str = file | ||
|
||
def config(self, deny: int | None = 3, unlock_time: int | None = 300) -> None: | ||
""" | ||
Configure the settings for PAM faillock. | ||
:param deny: Deny attempts | ||
:type deny: int, defaults to 3 | ||
:param unlock_time: Unlock timeout in seconds | ||
:type unlock_time: int, defaults to 300 | ||
:return: Self | ||
:rtype: PAMFaillock | ||
""" | ||
content = f"deny={deny}\nunlock_time={unlock_time}\nsilent" | ||
|
||
self.util.logger.info(f"{content} written to {self.file} on {self.util.host.hostname}") | ||
self.util.fs.write(self.file, content) | ||
|
||
def get_config(self) -> str: | ||
""" | ||
Get the configuration for PAM Faillock. | ||
:return: Contents of faillock.conf | ||
:rtype: str | ||
""" | ||
result = self.util.fs.read(self.file) | ||
|
||
return result | ||
|
||
def info(self) -> str: | ||
""" | ||
Get user faillock information. | ||
:return: Output from faillock | ||
:rtype: str | ||
""" | ||
self.util.logger.info(f"Getting faillock information for {self.user} on {self.util.host.hostname}") | ||
result = self.util.host.ssh.exec(["faillock", "--username", self.user]) | ||
|
||
return result.stdout | ||
|
||
def reset(self) -> PAMFaillock: | ||
""" | ||
Reset user tally information. | ||
:return: Self | ||
:rtype: PAMFaillock | ||
""" | ||
self.util.logger.info(f"Resetting faillock tally for {self.user} on {self.util.host.hostname}") | ||
self.util.host.ssh.exec(["faillock", "--username", self.user, "--reset"]) | ||
|
||
return self |