feat(handler): recognise SSH user certificates in PrivateKey secrets - #2007
Open
yellmean wants to merge 1 commit into
Open
feat(handler): recognise SSH user certificates in PrivateKey secrets#2007yellmean wants to merge 1 commit into
yellmean wants to merge 1 commit into
Conversation
yellmean
force-pushed
the
feat/ssh-cert-auth
branch
from
July 16, 2026 02:09
26cee69 to
7a4e280
Compare
pkg/handler.buildSSHClientOptions currently calls gossh.ParsePrivateKey on account.Secret and silently discards any OpenSSH certificate line that follows the PEM block. This means a certificate signed by an external CA (such as step-ca, Vault SSH, or ssh-keygen -s) bundled in the same secret is never presented during user-auth, and the SSH user-auth packet reaches sshd as a plain publickey request instead of an *-cert-v01@openssh.com request. Introduce pkg/sshcert, a small helper package that detects the trailing certificate line, validates that its public key matches the embedded private key, and wraps both with ssh.NewCertSigner when present. Swap the ParsePrivateKey call in buildSSHClientOptions for sshcert.NewSigner: blobs without a matching certificate line fall back to the existing plain-key behaviour, so the change is fully backwards-compatible for any existing deployment whose account.Secret is a plain OpenSSH private key block. The PR is deliberately agnostic about the certificate source - the secret blob can be produced by step-ca, ssh-keygen, Vault SSH secrets engine, or any other CA. The companion documentation in docs/ssh-certificate.md describes the supported wire format and references the external tools, without binding koko to any particular one. Tests in pkg/sshcert/cert_test.go cover plain-key fallback, certificate wrapping, key/cert mismatch (ErrCertMismatch), and the Parse() metadata path.
yellmean
force-pushed
the
feat/ssh-cert-auth
branch
from
July 16, 2026 02:42
7a4e280 to
b5da3e1
Compare
|
Member
|
This format is not a common OpenSSH private key format. In theory, saving the account private key as “private key + certificate line” may fail, because the current API/backend validation usually only accepts standard private key content that can be parsed by ssh.ParsePrivateKey, and there is no separate logic to store or recognize the certificate. In other words, even if Koko supports parsing a trailing certificate line, the combined format may not be persisted if Core/API already validates the private key format when saving the account secret. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



feat(handler): recognise SSH user certificates in PrivateKey secrets
Summary
pkg/handler.buildSSHClientOptionscurrently callsgossh.ParsePrivateKeyonaccount.Secretand silently discards anyOpenSSH certificate line that follows the PEM block. This means a
certificate signed by an external CA (such as step-ca, Vault SSH, or
ssh-keygen -s) bundled in the same secret is never presented duringuser-auth, and the SSH user-auth packet reaches sshd as a plain
publickeyrequest instead of an*-cert-v01@openssh.comrequest.This PR introduces a small internal package
pkg/sshcertthat detectsthe trailing certificate line, validates that its public key matches
the embedded private key, and wraps both with
ssh.NewCertSignerwhenpresent. The
ParsePrivateKeycall inbuildSSHClientOptionsisswapped for
sshcert.NewSigner: blobs without a matching certificateline fall back to the existing plain-key behaviour, so the change is
fully backwards-compatible for any existing deployment whose
account.Secretis a plain OpenSSH private key block.The PR is deliberately agnostic about the certificate source — the
secret blob can be produced by step-ca, ssh-keygen, Vault SSH secrets
engine, or any other CA. The companion documentation in
docs/ssh-certificate.mddescribes the supported wire format andreferences the external tools, without binding koko to any particular
one.
Diff stat
The runtime change in
pkg/handler/server_ssh.gois one function call(
gossh.ParsePrivateKey→sshcert.NewSigner) plus one import line.Compatibility
account.Secretsshcert.NewSignerreturns the plain signeraccount.SecretTrustedUserCAKeyssshcert.ErrCertMismatch, fail closed, no auth attemptedTests
pkg/sshcert/cert_test.gocovers plain-key fallback, valid certwrapping, key/cert mismatch, and
Parse()metadata (serial,principals). The runtime change in
pkg/handler/server_ssh.goiscovered by the package's existing tests; no change to those is
required.
Documentation
docs/ssh-certificate.mddescribes the package, its entry points,the supported wire format, and links to the relevant OpenSSH and
step-ca references. The PR does not add a step-ca integration
client, since that is a separate concern from the parsing capability.
Why this PR
Several operators deploy JumpServer alongside an external SSH CA
(smallstep step-ca being the most common) to issue short-lived SSH
user certificates against their asset fleet. With this PR, koko
recognises the resulting
account.Secretpayload and authenticateswith the certificate, with zero new dependencies and no change
to the Luna UI, the asset-account schema, or the JMS API.
This PR does not endorse or integrate any specific CA. It only adds
the parsing capability that lets koko USE a certificate whenever
one is present in the secret blob. Operators can fill the secret