Fix SSH to ingress: port 65522, auto-load companion cert

- keyutil.go / client ssh.go: if <key>-cert.pub exists next to
  the private key, load it automatically (mirrors openssh behavior)
- stack.production.yml: TRAEFIK_SSH_HOST uses port 65522

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Leopere 2026-02-08 18:38:31 -05:00
parent 39fe9bc40c
commit f6b40d2432
Signed by: colin
SSH Key Fingerprint: SHA256:nRPCQTeMFLdGytxRQmPVK9VXY3/ePKQ5lGRyJhT5DY8
3 changed files with 23 additions and 22 deletions

View File

@ -8,11 +8,7 @@ import (
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
) )
// LoadPrivateKey loads an SSH private key from either: // LoadPrivateKey loads an SSH private key from a file path or raw PEM content.
// - A file path (if the value starts with "/" or "./")
// - Raw PEM content (if the value looks like a PEM key)
//
// This allows TUNNEL_KEY to be set as a file path or pasted PEM content.
func LoadPrivateKey(keyOrPath string) (ssh.Signer, error) { func LoadPrivateKey(keyOrPath string) (ssh.Signer, error) {
var keyBytes []byte var keyBytes []byte
@ -23,7 +19,6 @@ func LoadPrivateKey(keyOrPath string) (ssh.Signer, error) {
} }
keyBytes = data keyBytes = data
} else { } else {
// Treat as raw PEM content.
keyBytes = []byte(keyOrPath) keyBytes = []byte(keyOrPath)
} }
@ -35,17 +30,11 @@ func LoadPrivateKey(keyOrPath string) (ssh.Signer, error) {
return signer, nil return signer, nil
} }
// isFilePath returns true if the value looks like a filesystem path
// rather than raw PEM key content.
func isFilePath(v string) bool { func isFilePath(v string) bool {
if strings.HasPrefix(v, "/") || strings.HasPrefix(v, "./") || strings.HasPrefix(v, "~") { if strings.HasPrefix(v, "/") || strings.HasPrefix(v, "./") || strings.HasPrefix(v, "~") {
return true return true
} }
// If it doesn't look like PEM, assume it's a path. return !strings.Contains(v, "-----BEGIN")
if !strings.Contains(v, "-----BEGIN") {
return true
}
return false
} }
// Connect establishes an SSH connection to the tunnel server. // Connect establishes an SSH connection to the tunnel server.

View File

@ -2,15 +2,15 @@ package server
import ( import (
"fmt" "fmt"
"log"
"os" "os"
"strings" "strings"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
) )
// LoadSigner loads an SSH private key from either a file path or raw PEM content. // LoadSigner loads an SSH private key from a file path or raw PEM content.
// If the value starts with "/" or "./" or "~", it's treated as a file path. // If <path>-cert.pub exists alongside the key, openssh-style cert auth is used.
// If it contains "-----BEGIN", it's treated as raw PEM content.
func LoadSigner(keyOrPath string) (ssh.Signer, error) { func LoadSigner(keyOrPath string) (ssh.Signer, error) {
var keyBytes []byte var keyBytes []byte
@ -29,16 +29,28 @@ func LoadSigner(keyOrPath string) (ssh.Signer, error) {
return nil, fmt.Errorf("parse private key: %w", err) return nil, fmt.Errorf("parse private key: %w", err)
} }
// If a companion -cert.pub exists, use it (same as openssh auto-loading).
if isFilePath(keyOrPath) {
certPath := keyOrPath + "-cert.pub"
if certData, err := os.ReadFile(certPath); err == nil {
pub, _, _, _, err := ssh.ParseAuthorizedKey(certData)
if err == nil {
if cert, ok := pub.(*ssh.Certificate); ok {
if cs, err := ssh.NewCertSigner(cert, signer); err == nil {
log.Printf("Auto-loaded %s", certPath)
return cs, nil
}
}
}
}
}
return signer, nil return signer, nil
} }
// isFilePath heuristic: paths start with / ./ ~ or don't contain PEM markers.
func isFilePath(v string) bool { func isFilePath(v string) bool {
if strings.HasPrefix(v, "/") || strings.HasPrefix(v, "./") || strings.HasPrefix(v, "~") { if strings.HasPrefix(v, "/") || strings.HasPrefix(v, "./") || strings.HasPrefix(v, "~") {
return true return true
} }
if !strings.Contains(v, "-----BEGIN") { return !strings.Contains(v, "-----BEGIN")
return true
}
return false
} }

View File

@ -31,7 +31,7 @@ services:
PORT_RANGE_END: "10100" PORT_RANGE_END: "10100"
SSH_HOST_KEY: "/run/secrets/host_key" SSH_HOST_KEY: "/run/secrets/host_key"
AUTHORIZED_KEYS: "/run/secrets/authorized_keys" AUTHORIZED_KEYS: "/run/secrets/authorized_keys"
TRAEFIK_SSH_HOST: "ingress.nixc.us" TRAEFIK_SSH_HOST: "ingress.nixc.us:65522"
TRAEFIK_SSH_USER: "root" TRAEFIK_SSH_USER: "root"
TRAEFIK_SSH_KEY: "/run/secrets/traefik_deploy_key" TRAEFIK_SSH_KEY: "/run/secrets/traefik_deploy_key"
SWARM_SERVICE_NAME: "better-argo-tunnels_tunnel-server" SWARM_SERVICE_NAME: "better-argo-tunnels_tunnel-server"