Installing pam_ssoossh
pam_ssoossh is a PAM module that authenticates a local account by having a
human approve a certificate request. This page covers getting it onto a host.
Wiring it into a service is a separate decision, described on
sudo and su,
console login and
sshd keyboard-interactive.
What you are installing
Section titled “What you are installing”pam_ssoossh lives in its own repository,
github.com/mnestor/ssoossh-pam. It
is around 82 KB stripped and links only libraries the operating system
already ships: libpam, libcrypto, libcurl. Nothing it needs is bundled,
which is why it reports the versions it actually linked on every
authentication.
It has both flows, the browser flow and the console code-and-QR flow, and the
mode= argument that picks between them. pam_ssoossh(8) is the contract,
and the reference page restates it in full.
Supported platforms
Section titled “Supported platforms”| Platform | Crypto | Console mode | Ships an artifact |
|---|---|---|---|
| Linux glibc (x86-64, arm64) | OpenSSL 1.1.1 or newer | yes | yes |
| Linux musl / Alpine | OpenSSL 1.1.1 or newer | yes | yes |
| FreeBSD | OpenSSL in base | yes | yes |
| macOS 15 or newer (Apple silicon) | Security.framework | no | yes, a signed .pkg |
OpenSSL 1.1.1 is a hard floor, set by RHEL 8: a build against anything older fails at compile time. OpenSSL 1.0.2 and the releases carrying it – RHEL 7, CentOS 7 – are out of scope permanently.
CA key types
Section titled “CA key types”The crypto backend decides what can be verified, so this differs by platform. Anything unsupported fails with an error naming the algorithm, never with a vague signature failure.
CA key type in trusted-ca-file |
Linux, FreeBSD | macOS |
|---|---|---|
ecdsa-sha2-nistp256 / 384 / 521 |
supported | supported |
rsa-sha2-256, rsa-sha2-512 |
supported | supported |
ssh-ed25519 |
supported | supported (macOS 14 and later) |
ssh-rsa (SHA-1) |
refused by policy | refused by policy |
ssh-rsa names RSA with SHA-1, which OpenSSH has disabled by default since
8.8. It is refused on every platform. Check what your CA key is before you
deploy: ssh-keygen -l -f /etc/ssoossh/ca.pub.
From the package repository
Section titled “From the package repository”On Linux, the module and its SELinux policy are served from ssoossh’s signed repository, which saves choosing an artifact by hand – the metadata for your release offers only the build that matches it:
sudo dnf install pam-ssoossh # RHEL 8, 9 and 10, and rebuildssudo apt install pam-ssoossh # Debian, UbuntuSELinux on EL hosts
Section titled “SELinux on EL hosts”With SELinux enforcing, the module cannot reach ssoosshd without policy for
it. There are two ways to grant that, and neither happens on its own.
The policy package. pam-ssoossh-selinux carries a policy module scoped
to the domains where the denial has actually been reproduced. It is installed
separately and nothing pulls it in for you. It is published for EL 8 and EL 9
only – EL 10 is a different generation of the base policy, and shipping a
module there would assert something nobody has observed on it.
A boolean Red Hat already ships. The base policy carries the same access
behind authlogin_yubikey, which is off by default:
sudo setsebool -P authlogin_yubikey onIt grants name_connect to http_port_t for the login_pgm attribute, which
holds sshd_t, local_login_t, remote_login_t and xdm_t on EL 8, 9 and
10 – plus cockpit_session_t on EL 8 and sshd_session_t on EL 10.
On EL 10 this is the route, since no policy package is published for it.
Which to choose is a real trade rather than a default. The policy package is narrower, versioned, and removable with the package. The boolean installs nothing, is supported policy from your distribution, and covers login domains the policy package deliberately does not name – which matters if your site forbids third-party SELinux modules, as regulated environments often do.
sudo needs neither, and it is worth knowing why rather than taking it on
trust – the reason says when it would stop being true. sudo does not
transition to a domain of its own, so the module runs as the calling user.
The question is therefore always about the user’s domain and never about
sudo, and every user domain the targeted policy ships already reaches
http_port_t: unconfined_t through corenet_unconfined_type, and the
confined ones – staff_t, user_t, sysadm_t – through an unconditional
rule from their *_usertype attribute to port_type.
It is the login domains – sshd, console login – that need one of the two
routes above.
For deb hosts this is the path worth taking. apt verifies repositories
rather than individual files and Debian disables per-package signature
checking by default, so a .deb fetched from the release page and installed
with dpkg -i is verified by nothing unless you check SHA256SUMS yourself.
See Installing from packages for the one-time setup, and
Verifying a download below for the manual route.
Alpine, FreeBSD and macOS are not served from the repository and stay direct downloads.
Which package
Section titled “Which package”Releases are built per platform and wrapped as distribution packages by nfpm, and the macOS one as an installer package built with Apple’s own tools.
| Artifact | For | Package formats |
|---|---|---|
linux-{x86_64,aarch64}-glibc-openssl3 |
RHEL 9 and 10 and rebuilds, Debian 12+, Ubuntu 22.04+, anything with libcrypto.so.3 |
.deb, .rpm |
linux-{x86_64,aarch64}-glibc-openssl1.1 |
RHEL 8 and rebuilds, anything with libcrypto.so.1.1 |
.rpm |
linux-{x86_64,aarch64}-musl |
Alpine | .apk |
freebsd14-x86_64 |
FreeBSD 14 | tarball only |
macos15-aarch64 |
macOS 15 and later on Apple silicon | .pkg, Developer ID signed and notarized |
There is no .deb for the OpenSSL 1.1 build: the Debian and Ubuntu releases
that shipped libssl1.1 are past their support dates.
The module links the host’s libraries rather than shipping them, so the
artifact name says where it loads. Package dependencies are sonames wherever
the format allows – libcrypto.so.3()(64bit) for rpm, so:libcrypto.so.3
for apk – which is what rpmbuild and abuild derive on their own. Debian
has no such provides, so the .deb names packages instead, carrying both
spellings of the ones the 64-bit time_t transition renamed
(libssl3t64 | libssl3).
Where the package puts things
Section titled “Where the package puts things”| Package | Module path |
|---|---|
.deb (Debian, Ubuntu) |
/usr/lib/<triplet>/security/pam_ssoossh.so, for example /usr/lib/x86_64-linux-gnu/security/pam_ssoossh.so |
.rpm (RHEL and rebuilds) |
/usr/lib64/security/pam_ssoossh.so |
.apk (Alpine) |
/lib/security/pam_ssoossh.so |
.pkg (macOS) |
/usr/local/lib/pam/pam_ssoossh.so |
On Linux and FreeBSD the module lands where that platform’s libpam already
looks, so a pam.d line names it by bare filename. macOS is the exception:
/usr/lib/pam, where OpenPAM looks a bare name up, is protected by System
Integrity Protection, so the package installs under /usr/local/lib/pam and
the pam.d line has to name that absolute path. See
macOS and Touch ID.
The macOS package is signed with a Developer ID Installer certificate, notarized by Apple with the ticket stapled on, so it installs without a Gatekeeper prompt. Check it before installing:
spctl --assess --type install -v pam_ssoossh-v1.2.0-macos15-aarch64.pkgEverything else is documentation:
| Path | Contents |
|---|---|
/usr/share/man/man8/pam_ssoossh.8.gz |
The module manual page |
/usr/share/man/man5/pam_ssoossh-ca.5.gz |
The trusted CA file format |
/usr/share/man/man5/pam_ssoossh-principals.5.gz |
The principals map format |
/usr/share/doc/pam-ssoossh/examples/pam.d/ |
A commented fragment for sudo, su, sshd and login |
/usr/share/doc/pam-ssoossh/examples/principals.yaml |
A principals map to start from |
/usr/share/doc/pam-ssoossh/BUILDINFO |
The compiler, the library versions built against, and the sonames needed |
The paths the examples use for configuration – /etc/ssoossh/ca.pub and
/etc/ssoossh/principals.yaml – are conventions. Nothing in the module fixes
them; both are named on the pam.d line.
Verifying a download
Section titled “Verifying a download”Each release carries a SHA256SUMS, build provenance, and, when the
repository holds the signing keys, package signatures. Both public halves are
attached to the release as pam-ssoossh-release-key.asc (OpenPGP) and
pam-ssoossh.rsa.pub (the RSA key Alpine expects).
$ gpg --import pam-ssoossh-release-key.asc$ gpg --verify SHA256SUMS.asc SHA256SUMS$ sha256sum -c SHA256SUMS --ignore-missingPer format:
$ rpm --import pam-ssoossh-release-key.asc && rpm -K pam-ssoossh-*.rpm$ sudo cp pam-ssoossh.rsa.pub /etc/apk/keys/ && sudo apk add ./pam-ssoossh-*.apkThe .apk public key filename is what apk matches the signature against,
and it must land as exactly /etc/apk/keys/pam-ssoossh.rsa.pub.
For the .deb, apt verifies repositories rather than files, so the
SHA256SUMS signature is the check that matters. The embedded _gpgorigin
signature can be checked with dpkg-sig --verify where that tool exists.
GitHub’s own build provenance:
$ gh attestation verify pam-ssoossh_1.2.0_linux-glibc-openssl3_amd64.deb -R <owner>/<repo>A release built without the signing secrets is unsigned, and the release job
log says so. SHA256SUMS is still published either way.
A tag with a hyphen (v1.2.0-rc1) is a pre-release. Package versions come
from git describe, and a pre-release sorts before the release it precedes in
every format, so an untagged or release-candidate build can never outrank a
real one on a host.
Building from source
Section titled “Building from source”Build dependencies are libpam, libcrypto and libcurl:
| Platform | Packages |
|---|---|
| Debian, Ubuntu | build-essential pkg-config libssl-dev libcurl4-openssl-dev libpam0g-dev |
| RHEL 8+, AlmaLinux, Rocky | gcc make pkgconf-pkg-config openssl-devel libcurl-devel pam-devel |
| Alpine | build-base pkgconf openssl-dev curl-dev linux-pam-dev |
| FreeBSD | gmake pkgconf curl – libpam and OpenSSL are in base, libcurl is not |
| macOS 15+ | Xcode command line tools only. No OpenSSL, no Homebrew |
$ make # pam_ssoossh.so, or pam_ssoossh.bundle on macOS$ make test # symbol gate, load gate, and the unit suite$ make dist # the release tarball$ make packages # deb, rpm and apk from that tarball, needs nfpm on PATHTwo vendored libraries are compiled in rather than linked – a JSON tokenizer
and the QR encoder console mode draws with – so neither appears in ldd
output or in the exported symbol table. Every link-time dependency is a
library the operating system already ships.
For a host whose distribution has stopped issuing OpenSSL updates, build against a self-maintained one without changing any source:
$ make OPENSSL_PREFIX=/opt/openssl-3.5After installing
Section titled “After installing”- Put the CA public key where the module will read it – the trusted CA file.
- Write a principals map, unless every identity provider username is spelled exactly like the local account it logs in as – the principals map.
- Add the module to one service, from a second root session – sudo and su.
There is no automatic stack registration on RHEL
Section titled “There is no automatic stack registration on RHEL”Step 3 is a per-service edit on every platform, and on RHEL and its rebuilds
that is the whole story: no authselect feature is provided, and that is a
decision rather than an omission.
authselect features work by modifying system-auth and password-auth.
Those are exactly the files that make a shared include dangerous for this
module: a screen locker, display manager or polkit reads them too, and an
approval prompt that nobody can see is a lock-out rather than a failed login.
So an authselect feature could not be safer than editing the stack by hand
– it is the same trade with different filenames, applied to more services at
once.
Editing one service file directly is not a workaround here. authselect owns
system-auth and password-auth; it does not own /etc/pam.d/sudo or the
other per-service files, so an edit there is stable and survives
authselect apply-changes.
Debian and Ubuntu do ship a pam-auth-update profile, because that platform
has no comparable per-service mechanism. It installs disabled and nothing
enables it for you – turning it on is an explicit, informed choice, and the
same lock-out warning applies when you make it.