diff --git a/internal/client/ssh.go b/internal/client/ssh.go index a7beae7..d9e8492 100644 --- a/internal/client/ssh.go +++ b/internal/client/ssh.go @@ -8,11 +8,7 @@ import ( "golang.org/x/crypto/ssh" ) -// LoadPrivateKey loads an SSH private key from either: -// - 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. +// LoadPrivateKey loads an SSH private key from a file path or raw PEM content. func LoadPrivateKey(keyOrPath string) (ssh.Signer, error) { var keyBytes []byte @@ -23,7 +19,6 @@ func LoadPrivateKey(keyOrPath string) (ssh.Signer, error) { } keyBytes = data } else { - // Treat as raw PEM content. keyBytes = []byte(keyOrPath) } @@ -35,17 +30,11 @@ func LoadPrivateKey(keyOrPath string) (ssh.Signer, error) { 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 { if strings.HasPrefix(v, "/") || strings.HasPrefix(v, "./") || strings.HasPrefix(v, "~") { return true } - // If it doesn't look like PEM, assume it's a path. - if !strings.Contains(v, "-----BEGIN") { - return true - } - return false + return !strings.Contains(v, "-----BEGIN") } // Connect establishes an SSH connection to the tunnel server. diff --git a/internal/server/keyutil.go b/internal/server/keyutil.go index d49f5a2..91dc27c 100644 --- a/internal/server/keyutil.go +++ b/internal/server/keyutil.go @@ -2,15 +2,15 @@ package server import ( "fmt" + "log" "os" "strings" "golang.org/x/crypto/ssh" ) -// LoadSigner loads an SSH private key from either a file path or raw PEM content. -// If the value starts with "/" or "./" or "~", it's treated as a file path. -// If it contains "-----BEGIN", it's treated as raw PEM content. +// LoadSigner loads an SSH private key from a file path or raw PEM content. +// If -cert.pub exists alongside the key, openssh-style cert auth is used. func LoadSigner(keyOrPath string) (ssh.Signer, error) { var keyBytes []byte @@ -29,16 +29,28 @@ func LoadSigner(keyOrPath string) (ssh.Signer, error) { 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 } -// isFilePath heuristic: paths start with / ./ ~ or don't contain PEM markers. func isFilePath(v string) bool { if strings.HasPrefix(v, "/") || strings.HasPrefix(v, "./") || strings.HasPrefix(v, "~") { return true } - if !strings.Contains(v, "-----BEGIN") { - return true - } - return false + return !strings.Contains(v, "-----BEGIN") } diff --git a/stack.production.yml b/stack.production.yml index e6fce2e..08dca3d 100644 --- a/stack.production.yml +++ b/stack.production.yml @@ -31,7 +31,7 @@ services: PORT_RANGE_END: "10100" SSH_HOST_KEY: "/run/secrets/host_key" 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_KEY: "/run/secrets/traefik_deploy_key" SWARM_SERVICE_NAME: "better-argo-tunnels_tunnel-server"