Certificate-based OpenSSH for Federated Identities
oinit is a Go-based certificate management system for OpenSSH that enables federated identity login. It uses OpenID Connect tokens (via oidc-agent) to authenticate users and issues SSH certificates signed by a Certificate Authority.
This repository contains a collection of programs to enable OpenSSH login for federated identities based on certificates.

OpenID Connect access token for selected provider is loaded from oidc-agent.
- Client (
oinit match) is invoked transparently by OpenSSH via aMatch execblock in~/.ssh/config. - The client resolves the CA URL for the target host (from
~/.ssh/oinit_hostsor/etc/ssh/ssh_oinit_hosts). - An OIDC access token is obtained — checked in order: environment variables (
ACCESS_TOKEN,BEARER_TOKEN, etc.),BEARER_TOKEN_FILE,$XDG_RUNTIME_DIR/bt_u$UID,/tmp/bt_u$UID, oidc-agent, manual TTY prompt. - The client generates an ephemeral Ed25519 key pair, sends the public key + token to the CA.
- CA (
oinit-ca) validates the token against motley_cue (/user/deploy), then signs and returns an SSH certificate withforce-command: oinit-switch <username>and principaloinit. - The client loads the certificate into ssh-agent (preferred) or saves it to
~/.ssh/oinit_<host>_<port>-cert.pub. If gpg-agent is detected, file-based storage is used instead. - OpenSSH connects as the
oinituser. The server'sForceCommandinvokes oinit-shell, which only allows running oinit-switch, whichsus to the target user. If the user connected directly as themselves (via the username principal),oinit-switchdetectstargetUid == curUidand exits immediately.
No setuid bit is needed on any oinit binary. Privilege escalation is handled entirely by PAM: a rule in /etc/pam.d/su allows the oinit user to su to non-system users without a password. The oinit-switch code enforces that only the oinit user can invoke it (others would be prompted for a password by su and fail). The system user guard (targetUid < SYS_UID_MAX) is an additional safety check, even though oinit-ca would never issue a certificate targeting a system user.
oinit-switch supports forwarding the client's oidc-agent socket to the remote server via SSH remote port forwarding (ssh -R /tmp/oidc-forward-$RANDOM:<local-oidc-socket> host). On the server side, oinit-switch:
- Detects the forwarded socket by scanning
/proc/net/unixfor entries matchingoidc-forward. Multiple concurrent sessions are disambiguated by matching socket inodes against the session's sshd process; if that fails, the newest socket owned by the current user is selected. - Chowns the socket to the target user (requires
CAP_CHOWNon the oinit-switch binary, set viasetcap cap_chown=ep). - Sets
OIDC_SOCKin the environment and passes it throughsu -w OIDC_SOCKso the target user's session can use it. - Cleans up the socket on session exit (via
rm -fappended to thesu -ccommand) and on fatal errors.
When running oinit add <host>, the CA is auto-discovered via:
- DNS TXT records:
_oinit-ca.<hostname>, then_oinit-ca.<parent-domain> - HTTPS probes (fallback):
https://<hostname>/oinit/, thenhttps://<parent-domain>/oinit/
Results are cached (5 min success, 1 min failure) using the generic TimedCache.
A few properties of the design are worth being explicit about for operators:
-
CA discovery is trust-on-first-use.
oinit add <host>accepts whatever CA a DNS TXT record (or, as a fallback, an HTTPS probe) points to. The DNS lookup itself is not authenticated, so without DNSSEC an attacker who can spoof DNS responses during the firstoinit addcan substitute their own CA. To bound this,oinit addprints the CA host-key SHA256 fingerprint and requires confirmation before pinning it intoknown_hosts; verify that fingerprint out of band. Operators are encouraged to publish it in a DNSSEC-signed record (see Verifying the CA key); this is currently an operational check, not auto-verified. Prefer specifying the CA explicitly (oinit add <host> <ca>);-y/--yesaccepts the fingerprint non-interactively. -
Issued certificates permit port and agent forwarding. The user certificate carries the
permit-port-forwardingandpermit-agent-forwardingextensions. This is required for the oidc-agent socket forwarding feature (which uses SSH remote port forwarding), so it cannot be disabled without removing that feature. Operators who do not need agent forwarding and want to restrict tunnelling should enforce it server-side (e.g.AllowTcpForwarding,PermitOpeninsshd_config) rather than relying on the certificate. -
The CA delegates access-token validity to motley_cue. For a JWT the CA additionally verifies the signature against the issuer's JWKS (and the audience, if
require-token-audis set), but the decision that a token is valid and authorises the user is made by motley_cue. Opaque (non-JWT) tokens have no CA-inspectable audience, sorequire-token-auddoes not apply to them. The discovery/JWKS/userinfo requests the CA makes are restricted to motley_cue-advertised issuers, require HTTPS, and do not follow redirects. -
Issued certificates are not bound to a specific host. An SSH user certificate carries no host restriction, and a host group may cover many hosts (including wildcards) that all trust the same user CA. A certificate obtained for one host in a group is therefore technically usable on any host trusting that CA. This is inherent to the SSH user-certificate model; it is mitigated by the short certificate lifetime (
cert-validity). Use separate host groups / user CAs to isolate sets of hosts that must not share trust. -
Revocation relies on short certificate lifetimes. Certificates are issued with serial
0and oinit does not maintain a KRL, so there is no serial- or key-based revocation — keepcert-validityshort. Serial-based KRL revocation is a possible future addition. -
gpg-agent detection is heuristic. The client decides whether the running agent is gpg-agent (which cannot hold SSH certificates) from
GPG_AGENT_INFOand theSSH_AUTH_SOCKpath. A misdetection only changes whether the certificate is held in the agent or written to~/.ssh(the private key file is mode0600), so the impact is limited to storage location, not exposure of the key to others. -
The client public-key strength floor is defence-in-depth. The CA refuses to certify weak client keys (RSA below 3072 bits, and non-Ed25519/ECDSA/RSA types such as DSA). Because the client certifies its own freshly generated key, this primarily guards against a non-standard or malicious client rather than a third-party risk.
See configs/config.sample.ini for the CA config format. Key settings per host group: host-ca-privkey, host-ca-pubkey, user-ca-privkey, user-ca-pubkey, cert-validity (seconds or "token"), cert-validity-fallback, cache-duration. The listen-address can also be set in the default section or overridden via -l flag.
make oinit-ca-dockerbuilds usingbuild/Dockerfile(multi-stage, Go 1.20 + Alpine)
# Client application
$ make oinit
# oinit-shell and oinit-switch
$ make oinit-shell oinit-switch
# Server application (CA)
$ make oinit-caWhen changing the REST API annotations, run make swagger to generate the Swagger files.
Development happens on feature branches checked out from and merged back into prerel.
When ready, commits are merged into main and tagged as release.
This project is licensed under the MIT License.