OIDC SSH Login for Linux, Without the Gateway

rust dev.to

TL;DR: prmana is a PAM module and agent that replaces static SSH keys with short-lived OIDC tokens, bound to proof-of-possession via DPoP (RFC 9449). Rust, Apache-2.0. No proxy, no SSH CA — just your IdP and your Linux hosts.


Static SSH keys are the problem

Every org with more than a handful of Linux servers has the same issue: SSH keys everywhere, rotated never.

That key a developer generated in 2021? Still works on production. The contractor who left six months ago? Their authorized_keys entry is probably still on a dozen servers. Your security team mandates MFA for email, but root access to your database server? Static key on a laptop.

The usual solutions:

  • Access platforms — proxy or gateway in front of everything
  • SSH certificate authorities — new CA infrastructure to operate
  • PAM/OIDC modules — SSO but still bearer tokens (interceptable)

Each solves part of the problem. Each adds operational complexity.

prmana: direct-to-host OIDC with DPoP

prmana brings OIDC authentication directly to the Linux host via PAM, and binds every token to a cryptographic proof that it hasn't been stolen.

Your Machine                          Linux Server
┌─────────────────┐                  ┌──────────────────┐
│ prmana-agent    │     SSH          │ pam_prmana.so    │
│ (OIDC + DPoP)   │ ──────────────▶  │ (validate + bind)│
└─────────────────┘                  └──────────────────┘
        │                                     │
        ▼                                     ▼
  Your IdP                             JWKS verification
  (Keycloak/Okta/                      + DPoP proof check
   Azure AD/Auth0)                     + replay protection
Enter fullscreen mode Exit fullscreen mode
  1. The agent gets a short-lived token from your identity provider
  2. It generates a DPoP proof — a signed JWT proving you hold the private key
  3. On SSH, the server's PAM module validates the token, checks expiry, verifies DPoP binding
  4. If it all checks out and the username maps to a local account, you're in

No gateway. No SSH CA. No static keys.

Why DPoP matters

Most OIDC-for-SSH approaches use bearer tokens. If someone intercepts the token — from a log, a compromised proxy, a memory dump — they can replay it from anywhere.

DPoP (RFC 9449) changes that. Every authentication includes a proof signed by an ephemeral key pair. The token carries a thumbprint of the public key. The server verifies the proof matches. Token leaks? Useless without the key.

{"sub":"alice","iss":"https://idp.company.com","cnf":{"jkt":"NKnABZgU1F7M5JW5uFrETiYx..."}}
Enter fullscreen mode Exit fullscreen mode

That cnf.jkt field binds the token to a specific key pair. No key, no access.

The name "prmana" comes from Sanskrit (प्रमाण) — "proof" and "means of knowledge." Felt appropriate for a tool whose whole point is cryptographic proof of possession.

Hardware key support

DPoP proofs are signed by a key pair. By default that's a software key. prmana also supports:

  • YubiKey — PKCS#11 via PIV slot
  • TPM 2.0 — platform TPM on Linux

When the DPoP key lives on hardware, the private key can't be exported. Compromised laptop? They have the token but can't sign a valid proof.

prmana-agent login --signer yubikey:9a
# touch the key when it blinks, then SSH normally
ssh server.example.com
Enter fullscreen mode Exit fullscreen mode

What's in the repo

Three Rust crates:

Crate Purpose
prmana-core OIDC discovery, JWKS caching
pam-prmana PAM module — validation, DPoP verification, replay protection, break-glass
prmana-agent Client — token acquisition (device flow, auth code + PKCE), DPoP signing, credential storage

Plus cross-language DPoP libraries in Go, Python, Java, and Rust.

The PAM module plugs into OpenSSH's existing stack. No patches to sshd. Standard ssh on the client, standard sshd on the server.

What it's not

prmana does SSH login. It's not a session recorder, not a proxy, not a privileged access management suite. One job, done well.

Getting started

git clone https://github.com/prodnull/prmana.git
cd prmana && cargo build --workspace

# Install PAM module
sudo cp target/release/libpam_prmana.so /lib/security/pam_prmana.so

# Point to your IdP
sudo mkdir -p /etc/prmana
cat << 'EOF' | sudo tee /etc/prmana/policy.yaml
issuers:
  - url: https://your-idp.com/realms/your-realm
    client_id: prmana
    audiences: ["prmana"]
break_glass:
  enabled: true
  users: ["emergency-admin"]
EOF

# Login and SSH
prmana-agent login --issuer https://your-idp.com/realms/your-realm
ssh user@server
Enter fullscreen mode Exit fullscreen mode

Docs cover setup for Keycloak, Entra ID, and Auth0.

Feedback welcome

github.com/prodnull/prmana — Apache-2.0.

What we'd find most useful:

  • Security review — it's a PAM module. Poke holes.
  • IdP testing — tested against Keycloak, Auth0, Google, Entra ID. If yours does something unexpected with DPoP or device flow, we want to know.
  • Platform testing — Ubuntu 22.04/24.04 primary. RHEL, Rocky, Debian reports welcome.

GitHub Discussions for questions and ideas.


Source: dev.to

arrow_back Back to Tutorials