Skip to content

Commit

Permalink
pam: adding support to manage pam_access and pam_faillock
Browse files Browse the repository at this point in the history
  • Loading branch information
Dan Lavu committed Aug 15, 2023
1 parent 97a3994 commit 05b60e1
Show file tree
Hide file tree
Showing 3 changed files with 278 additions and 0 deletions.
52 changes: 52 additions & 0 deletions docs/guides/pam.rst
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")
6 changes: 6 additions & 0 deletions sssd_test_framework/roles/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from ..utils.automount import AutomountUtils
from ..utils.ldb import LDBUtils
from ..utils.local_users import LocalUsersUtils
from ..utils.pam import PAMUtils
from ..utils.sss_override import SSSOverrideUtils
from ..utils.sssctl import SSSCTLUtils
from ..utils.sssd import SSSDUtils
Expand Down Expand Up @@ -70,6 +71,11 @@ def __init__(self, *args, **kwargs) -> None:
Managing local overrides users and groups.
"""

self.pam: PAMUtils = PAMUtils(self.host, self.fs)
"""
Managing PAM modules; pam_access and pam_faillock.
"""

def setup(self) -> None:
"""
Called before execution of each test.
Expand Down
220 changes: 220 additions & 0 deletions sssd_test_framework/utils/pam.py
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

0 comments on commit 05b60e1

Please sign in to comment.