Interactive user certificates
The everyday path: a person types ssh host and gets a shell. It runs in four
stages. The client requests a certificate (1a), the user approves in a browser
(1b), the server signs and delivers (1c), and ssh connects (1d). For the same
story told for a newcomer, with pictures, see the
illustrated walkthrough.
1a. ssh invokes the client, which requests a certificate
Section titled “1a. ssh invokes the client, which requests a certificate”sequenceDiagram
accTitle: Step 1a — ssh invokes the client, which requests a certificate
accDescr {
The user runs ssh to a host. Through a ProxyCommand or a Match exec
rule, ssh invokes the ssoossh client. A valid certificate already in the
agent or on disk is reused and the flow ends there. Otherwise the client
generates an SSH keypair, posts the public key and the host IP list,
receives an authorization URL and prints it (opening a browser only
when try_open_browser is set), then opens a server-sent events stream
and waits.
}
autonumber
actor User
participant SSH as ssh
participant Client as ssoossh client
participant Server as ssoossh server
User->>SSH: ssh host
SSH->>Client: invoke (ProxyCommand / Match exec)
Note over Client: a valid certificate already in the<br/>agent or on disk is reused: done
Client->>Client: generate SSH keypair
Client->>Server: POST public key + host IP list
Server-->>Client: authorization URL
Client->>User: print URL (and open a browser if configured)
Client->>Server: open SSE stream and wait
- The user runs
ssh host. sshinvokes the ssoossh client, from aProxyCommandor aMatch execline inssh_config. A valid certificate already in the agent or on disk is reused and the flow ends here, with no browser at all.- The client generates a fresh SSH keypair, locally.
- It posts the public key and the host IP list to the server.
- The server answers with an authorization URL.
- The client prints that URL, and opens it in a browser only when
try_open_browseris set; it is off by default. - The client opens an SSE stream and waits.
The wait window is configurable:
cert_options.client_timeout
is the whole budget and defaults to 5m, and the client sets no shorter limit
of its own. The server owns that deadline and
measures it from the request’s creation, so a client that reconnects to its
event stream re-attaches to the original deadline instead of extending it.
The client never opens a listening port. The browser lands on the server, and the client learns the outcome only over this SSE stream.
1b. The user authenticates and approves in a browser
Section titled “1b. The user authenticates and approves in a browser”This runs in the browser while 1a waits. The client is not a participant.
sequenceDiagram
accTitle: Step 1b — the user authenticates and approves in a browser
accDescr {
The user opens the authorization URL. The server sends an authorization
request to the OIDC provider, which returns identity claims; an LDAP
lookup fills in claims that arrived incomplete. The server trims the
requested options to what its configuration permits and applies the
lifetime policy, then renders the approval page showing exactly what
will be issued, with trimmed options struck through. The user approves.
}
autonumber
actor User
participant Browser
participant Server as ssoossh server
participant IdP as OIDC provider
User->>Browser: open authorization URL
Browser->>Server: authorization request
Server->>IdP: OIDC authentication
IdP-->>Server: identity claims
Note over Server: optional LDAP lookup when<br/>claims are incomplete
Server->>Server: trim options to server config,<br/>apply lifetime policy
Server->>Browser: approval page: what will be issued,<br/>trimmed options struck through
User->>Browser: approve
- The user opens the authorization URL.
- The browser sends the authorization request to the server.
- The server sends the user to the OIDC provider.
- The provider returns identity claims.
- Optionally, the server enriches an incomplete identity from LDAP.
- The server trims the requested options down to what the config permits and applies the lifetime policy.
- The approval page shows what will be issued, with the trimmed options struck through.
- The user approves.
The identity provider stays authoritative for everything here, including who counts as an administrator. Option trimming and lifetime policy have their own page: Options and lifetime resolution.
1c. The server signs and delivers
Section titled “1c. The server signs and delivers”Signing happens asynchronously off a queue after approval. The signer holds the CA key and has no database access, so it can run as a separate, minimally privileged process.
sequenceDiagram
accTitle: Step 1c — the server signs and delivers
accDescr {
The web tier publishes a signing job. The signer, which has no database,
signs with the CA key and publishes the signed certificate. The listener
records the audit row and resolves the request, then delivers the
certificate to the client over the server-sent events stream it has been
holding open. The client loads it into ssh-agent, or writes key files
when there is no agent.
}
autonumber
participant Server as ssoossh server
participant Signer as signer (no database)
participant Listener as listener/resolver
participant Client as ssoossh client
Server-)Signer: signing job [certrequest.sign]
Signer->>Signer: sign with CA key
Signer-)Listener: signed certificate [certrequest.signed]
Listener->>Listener: record audit row, resolve request
Listener-->>Client: certificate over the waiting SSE stream
Note over Client: load into ssh-agent,<br/>or write key files when no agent
- The server publishes a signing job on
certrequest.sign. - The signer signs the public key with the CA key.
- The signer publishes the signed certificate on
certrequest.signed. - The listener records the audit row and resolves the request.
- The certificate goes to the client over the waiting SSE stream.
- The client loads it into the ssh-agent, or writes key files when there is no agent.
The certificate is never stored server-side. Delivery is the only copy; a client that misses the window re-requests.
1d. ssh connects to the target host
Section titled “1d. ssh connects to the target host”Nothing here involves ssoossh. The client has exited, or in ProxyCommand mode
has replaced itself with your relay command, and this is ordinary SSH
certificate authentication.
sequenceDiagram
accTitle: Step 1d — ssh connects to the target host
accDescr {
ssh presents the certificate to the target host’s sshd, which asks two
questions: was it signed by a CA listed in TrustedUserCAKeys, and is the
requested login name one of the certificate’s principals. Both yes gives
the user a session; either no gives an authentication failure.
}
autonumber
actor User
participant SSH as ssh
participant Host as target host sshd
SSH->>Host: connect, present certificate
Host->>Host: signed by a CA in TrustedUserCAKeys?
Host->>Host: requested login name an allowed principal?
alt permitted
Host-->>User: session
else not permitted
Host-->>User: authentication failure
end
sshconnects and presents the certificate.sshdchecks that it was signed by a CA inTrustedUserCAKeys.sshdchecks that the requested login name is an allowed principal.- If both hold, the session starts; otherwise the login fails.
sshd also checks the validity window, enforces critical options such as
source-address and force-command, applies the extension set, and requires
proof of private-key possession. Principals map onto local accounts through
AuthorizedPrincipalsFile or AuthorizedPrincipalsCommand.
The certificate does not bound the session
Section titled “The certificate does not bound the session”The certificate is used once, at session initiation. A ten-hour certificate is not a ten-hour session limit, and an expiring certificate does not drop a session already running.
A valid certificate is reused until it expires, so you see the browser when a
new certificate is needed, not once per connection. How often that is follows
the lifetime the deployment sets: the shipped default for a user certificate
(cert_options.user.valid_duration)
is 30 seconds; a deployment that sets eight hours sees about one approval a
working day.
What the record says
Section titled “What the record says”Each stage leaves an audit event, which is what makes “who issued this, and when did nobody answer” answerable afterwards:
stateDiagram-v2
accTitle: The states a certificate request moves through
accDescr {
A request starts as requested. From there it either expires because
nobody answered in time, or becomes claimed when the first browser opens
its approval page. A claimed request is approved or denied. An approved
request becomes issued, or sign_failed if signing did not complete.
Issued is the end of the line.
}
[*] --> requested
requested --> claimed: first browser opens the approval page
requested --> expired: nobody answered in time
claimed --> approved
claimed --> denied
approved --> issued
approved --> sign_failed
issued --> [*]
cert.claimed has no actor: the user agent is what tells a person from a link
scanner. cert.expired is a system event, raised when nobody answered within
the type’s budget. cert.sign_failed means an approval produced no
certificate, either because the signer refused or because the stranded sweep
found the row stuck.
Where this is configured
Section titled “Where this is configured”| What | Key or file |
|---|---|
| How long a client may wait | cert_options.client_timeout |
| User certificate lifetime ceiling | cert_options.user.valid_duration |
| Extensions a user certificate may carry | cert_options.user.extensions |
| Who may get a user certificate at all | cert_options.user.require |
What sshd logs on every login |
cert_options.user.key_id_template |
| The identity provider | authentication |
| Trusting the CA on a target host | TrustedUserCAKeys in sshd_config |
Related
Section titled “Related”- The ssoossh client and ssh_config integration – the two invocation modes and how to wire them up.
- Approving in the browser – what the approval page shows you.
- Options and lifetime resolution – stage 1b’s trimming step in full.
- Trusting the CA in sshd – the host side of stage 1d.
- Identity provider and LDAP enrichment.
- Signing pipeline – the queue topics in stage 1c.
- Audit log – the events above.