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:
parent
39fe9bc40c
commit
f6b40d2432
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue