access.conf, In The Stack And Proven With PAM Itself

EL ships /etc/security/access.conf with no active rule and pam_access is not in the stack to read it, so every account is admitted from anywhere. This role writes the policy, adds the module through authselect, and refuses to write a rule set that would lock out the account running it. The live test asks PAM, with the origin set. Original role, live-tested on Rocky Linux 10.

ansibleSecurity & Secrets

Verification

Live-tested

Really deployed to a container sandbox, proven idempotent (a second run changes nothing), verified against the role’s assertions, then torn down.

Conformance

  • Static validation (yamllint · ansible-lint)

Provenance

  • SHA-256 checksum
  • Signature (pending)

Functional

  • Live-tested - applied, verified, destroyed

Last verified 2026-09-27 · podman 4.9.3 · ansible 2.21.4 · how we verify

Cite it in your README

badge · attribution

Paste this beside the module in the repository that uses it. The badge is rendered from this artifact's verification record, so it reads live-tested because the record says so, and the link lands on this page.

README.md, GitLab, Gitea
[![IaC Bazaar: live-tested](https://www.iac-bazaar.com/api/artifacts/ansible-pam-access/badge)](https://www.iac-bazaar.com/catalog/ansible-pam-access?utm_source=syndication&utm_medium=readme&utm_campaign=artifact)

Ansible role 1.0.0, live-tested on IaC Bazaar: [access.conf, In The Stack And Proven With PAM Itself](https://www.iac-bazaar.com/catalog/ansible-pam-access?utm_source=syndication&utm_medium=readme&utm_campaign=artifact)

```yaml
# access.conf, In The Stack And Proven With PAM Itself: https://www.iac-bazaar.com/catalog/ansible-pam-access (download from your IaC Bazaar account)
```

Preview:IaC Bazaar: live-tested

Documentation

pam-access

EL ships /etc/security/access.conf with no rules in it, and pam_access is not in the stack to read them. 4,671 bytes of documentation, a control that appears configured, and every account admitted from anywhere. This role writes the policy, puts the module into the authentication stack through authselect - /etc/pam.d/system-auth is a symlink into generated files, so editing it by hand does not survive - and proves each rule by asking PAM.

No download, and no version to pin. pam and authselect are EL 10 packages. What the role owns is access.conf in full and one authselect feature, and what it proves is that a named account gets in from a named place and nobody else does.

The order of the file is the policy. pam_access stops at the first rule that matches, so a blanket allow above a deny makes the deny dead. That is why the template renders the rules in a fixed order and why pam_access_prepend_rules is documented as the dangerous escape hatch rather than the convenient one.

This role can lock you out of a host, so it checks first. With pam_access_deny_all and a group list that does not include you, the next login is refused - and after - : ALL : ALL, root's ssh logins are refused too, because pam_access_allow_root_from is LOCAL by default. Before writing anything the role reads the account this play is connected as, the groups the host gives it, and whether the connection arrived over ssh, and refuses to write a policy that would shut that account out. The check has one limit, stated because it matters: it requires that a non-LOCAL origin exists for root, not that the origin is yours.

A line pam_access cannot parse is ignored, and the account is admitted. Measured: with this is not a rule in the file, the account under test was still allowed. Nothing validates access.conf - there is no checker to give a template validate: - so the live test drives PAM with pamtester rather than reading the file back, and it reads the refusal message as well as the exit code.

First match wins, in both directions. With + : ALL : ALL above - : user : ALL the user was admitted; with the deny first the same user was refused. This is the trap that makes a long access.conf unreadable: a rule added at the top for one host silently retires everything below it.

LOCAL is a real origin, not the absence of one. A rule allowing a user from 10.99.0.0/24 refused that user from 203.0.113.7 and from a console login with no remote host at all. + : root : LOCAL is what admits root on the console, and it is what makes root's network logins refused.

License

Commercial - IaC Bazaar EULA. (c) IaC Bazaar.

Usage code & full reference need an account

The complete copy-paste usage, the full input/output reference, and operational notes are free with an account - shown here and bundled in the download. Sign in and this section fills in.

  • Variables
  • Test

Related modules

Live-tested

ansible-aide

AIDE from AppStream on EL 10: a watched tree, a baseline database built once, and the oneshot unit and timer the package does not ship. The live test passes on an unchanged host, FAILS when a file appears inside a watched path and names it, ignores one inside an excluded path, and passes again once the change is gone. Original role, live-tested on Rocky Linux 10.

View module
Live-tested

ansible-authelia

Authelia from the upstream release (sha256-verified) as a hardened system service on loopback with file users and sqlite; its secrets are generated once on the host and reach the service as AUTHELIA_*_FILE variables. The live test hashes a password with Authelia's hasher, logs in, sees a wrong password refused and an anonymous visitor sent to the portal. Original role, live-tested on Rocky 10.

View module
Live-tested

ansible-bandit

bandit on EL 10 from PyPI into a venv of its own with a link in the PATH; the live test runs pip check, then scans a module that passes a string to subprocess.call with shell=True (B602, High, exit 1) and a clean module (exit 0). Pinned. Original role, live-tested on Rocky Linux 10.

View module
Live-tested

ansible-boundary

boundary on EL 10 from releases.hashicorp.com; SHA256SUMS is signature-checked against HashiCorp's key before get_url trusts it, the live test re-checks both, then runs authenticate against a dead address ('connection refused') and database init with a throwaway config (parsed, root key loaded, then the database refused). Pinned. Original role, live-tested on Rocky Linux 10.

View module
Live-tested

ansible-dockle

dockle on EL 10 from the GitHub release; get_url refuses it unless its SHA-256 is in the vendor's checksums file, and the live test re-checks it, then scans a one-layer image whose config names no user, expecting CIS-DI-0001 in the output, exit code 1 with --exit-level warn, and the warning counted in the JSON form. Pinned. Original role, live-tested on Rocky Linux 10.

View module
Live-tested

ansible-gitleaks

gitleaks on EL 10 from the GitHub release, refused by Ansible's get_url unless its SHA-256 is the one in the project's checksums file, and re-checked with sha256sum -c by the live test, which then plants a file holding an AWS access key that is not one and runs gitleaks detect over it, expecting 'leaks found: 1'. Original role, live-tested on Rocky Linux 10.

View module