#2013

[Bug]: SSH proxy host key is hard-restricted to Ed25519, blocking FIPS-mode clients

Opencwen11opened 10d ago

Bug Description

The gateway's SSH proxy can only use an Ed25519 host key, so it advertises ssh-ed25519 as its sole host key algorithm. Clients on FIPS-restricted systems (ex: RHEL with FIPS:STIG crypto policy) cannot connect to 12222 SSH proxy, because their SSH build has no Ed25519 support at all and the handshake fails at host key negotiation.

gateway/proxyproto/sshproxy/keys.go:20 discards any non-Ed25519 key:

func decodeOpenSSHPrivateKey(pemBytes []byte) (ed25519.PrivateKey, error) {
	privateKey, err := ssh.ParseRawPrivateKey(pemBytes)
	if err != nil { return nil, err }
	ed25519Key, ok := privateKey.(*ed25519.PrivateKey)
	if !ok {
		return nil, fmt.Errorf("key is not Ed25519, found=%T", privateKey)
	}
	return *ed25519Key, nil
}

hosts_key is documented as "Base64-encoded PEM private key used as the SSH host key", which reads as algorithm-agnostic, but supplying an ECDSA or RSA key fails the config apply, and on a cold boot gateway/main.go hits log.Fatalf and the pod crashloops. When hosts_key is empty the gateway auto-generates Ed25519 (gateway/api/serverconfig/misc.go:501), so there is no way to obtain a FIPS-compatible host key. Both the password and certificate proxies share this key, so cert auth doesn't avoid it.

Running parseHostsKey against real ssh-keygen output on the pinned golang.org/x/crypto v0.56.0:

ed25519                ACCEPTED -> advertises "ssh-ed25519"
ecdsa-sha2-nistp256    REJECTED: key is not Ed25519, found=*ecdsa.PrivateKey
ecdsa-sha2-nistp384    REJECTED: key is not Ed25519, found=*ecdsa.PrivateKey
rsa-3072               REJECTED: key is not Ed25519, found=*rsa.PrivateKey

Proposed fix

ssh.NewSignerFromKey in parseHostsKey already handles ECDSA and RSA; only the assertion blocks them:

func decodeOpenSSHPrivateKey(pemBytes []byte) (any, error) {
	return ssh.ParseRawPrivateKey(pemBytes)
}

parseHostsKey needs no change. keys_test.go:45 asserts the concrete Ed25519 type and would need a type assert. Auto-generation can stay Ed25519 — this only affects operator-supplied keys.

I verified the patched function on the pinned x/crypto, including an Ed25519 regression check:

ed25519 (regression)   OK -> advertises "ssh-ed25519"
ecdsa-sha2-nistp256    OK -> advertises "ecdsa-sha2-nistp256"
ecdsa-sha2-nistp384    OK -> advertises "ecdsa-sha2-nistp384"
rsa-3072               OK -> advertises "ssh-rsa"

And end-to-end with a real OpenSSH client restricted to FIPS algorithms, against a server built the same way the gateway builds its own:

# ECDSA host key, client restricted to ecdsa-sha2-nistp256
debug1: kex: host key algorithm: ecdsa-sha2-nistp256

# RSA-3072 host key, client restricted to rsa-sha2-512,rsa-sha2-256
debug1: kex: host key algorithm: rsa-sha2-512

The RSA signer's base type string is ssh-rsa, but x/crypto advertises the RFC 8332 rsa-sha2-256/512 variants and the handshake negotiates SHA-2, so no SHA-1 signature is involved.

Steps to Reproduce

  1. (After deploying hoop gateway) run ssh hoop@<placeholder> -p 12222

Expected Behavior

Should be able to SSH via SSH proxy

Actual Behavior

When trying to connect via SSH Proxy (ssh hoop@<placeholder> -p 12222) connect fails (doesn't even prompt for password).

HostKeyAlgorithms=ecdsa-sha2-nistp256,rsa-sha2-256,rsa-sha2-512
  -> Unable to negotiate: no matching host key type found. Their offer: ssh-ed25519

Adding ed25519 as a fallback connects, but only by abandoning the FIPS requirement. (FIPS 186-5 does approve EdDSA, but deployed FIPS SSH builds — RHEL's included — generally still exclude it, so this isn't reachable as a policy change.)

Version

v1.195.1

Browser(s)

Other

Operating System

RHEL9

Screenshots

No response

Additional Context

No response

Relevant Log Output

undefined

Code of Conduct

  • I agree to follow this project's Code of Conduct

Activity

1
github-actions[bot]10d ago

🔗 Synced to Linear: SUP-282