OpenSSH Hardened Where sshd Reads It First

An sshd drop-in numbered 01, so it is read before the 50-redhat.conf that asks for X11 forwarding: root login off, passwords off, MaxAuthTries 4, idle timeouts. The live test proves the policy with the daemon rather than the file: a password login refused, a key login accepted, root refused, and the banner delivered before authentication. 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-26 · 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-ssh-hardening/badge)](https://www.iac-bazaar.com/catalog/ansible-ssh-hardening?utm_source=syndication&utm_medium=readme&utm_campaign=artifact)

Ansible role 1.0.0, live-tested on IaC Bazaar: [OpenSSH Hardened Where sshd Reads It First](https://www.iac-bazaar.com/catalog/ansible-ssh-hardening?utm_source=syndication&utm_medium=readme&utm_campaign=artifact)

```yaml
# OpenSSH Hardened Where sshd Reads It First: https://www.iac-bazaar.com/catalog/ansible-ssh-hardening (download from your IaC Bazaar account)
```

Preview:IaC Bazaar: live-tested

Documentation

ssh-hardening

An SSH policy that takes effect, proven by a refused login. The role writes one drop-in numbered to be read before the distribution's own, checks the configuration with sshd -t before anything is reloaded, and enables the unit EL ships. The live test reads sshd -T for the effective policy and then attempts three real logins: a password, a key, and root.

No download, and no version to pin. EL 10 packages OpenSSH, so the role installs it by name and manages the unit the distribution ships. What the role owns is the policy: who may log in, with what, for how long, and what is written down about it.

The number in the filename is the whole trick. sshd takes the FIRST occurrence of a directive it reads, and EL ships /etc/ssh/sshd_config.d/50-redhat.conf with X11Forwarding yes. A hardening drop-in numbered above 50 is read second and silently loses; this one is 01-iacbazaar-ssh.conf. The live test asserts both halves together: that forwarding is off in sshd -T, AND that the 50 file still says yes, so a future rename could not pass quietly.

The algorithms are deliberately not here. EL's 40-redhat-crypto-policies.conf includes the system crypto policy's own opensshserver.config, and sshd -T shows its ciphers, MACs and key exchange list (post-quantum mlkem768x25519-sha256 among them on EL 10). Because this role's file is read FIRST, a Ciphers line in it would override the entire system policy from one drop-in. It sets none, and the live test asserts the policy include is still in place. Change the algorithms with the crypto-policy role, where the change applies to every service at once.

PasswordAuthentication no is not enough on its own. With PAM in the stack, a password can still arrive through keyboard-interactive, so the role sets KbdInteractiveAuthentication to match and the live test proves it by trying a password login and reading the refusal.

A banner file is not a banner. Banner is what makes sshd read /etc/issue.net; nothing else on EL reads that file. The live test asserts the text arrives at a client that has not authenticated.

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