diff --git a/.gitignore b/.gitignore index b8803d5..92d0144 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -/ipng-router-backup +ipng-router-backup /*.yaml # Debian packaging artifacts diff --git a/src/go.mod b/src/go.mod index 37bde20..10cebcd 100644 --- a/src/go.mod +++ b/src/go.mod @@ -10,6 +10,7 @@ require ( require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/spf13/pflag v1.0.5 // indirect golang.org/x/sys v0.16.0 // indirect ) diff --git a/src/go.sum b/src/go.sum index 9dac6a3..4d12bbe 100644 --- a/src/go.sum +++ b/src/go.sum @@ -1,6 +1,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= diff --git a/src/main.go b/src/main.go index 771b498..3321477 100644 --- a/src/main.go +++ b/src/main.go @@ -10,9 +10,11 @@ import ( "os" "path/filepath" "regexp" + "strconv" "strings" "time" + "github.com/kevinburke/ssh_config" "github.com/spf13/cobra" "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/agent" @@ -60,12 +62,70 @@ func NewRouterBackup(hostname, username, password, keyFile string, port int) *Ro // Connect establishes SSH connection to the router func (rb *RouterBackup) Connect() error { + // Get SSH config values for this host + hostname := ssh_config.Get(rb.hostname, "Hostname") + if hostname == "" { + hostname = rb.hostname + } + + portStr := ssh_config.Get(rb.hostname, "Port") + port := rb.port + if portStr != "" { + if p, err := strconv.Atoi(portStr); err == nil { + port = p + } + } + + username := ssh_config.Get(rb.hostname, "User") + if rb.username != "" { + username = rb.username + } + + keyFile := ssh_config.Get(rb.hostname, "IdentityFile") + if rb.keyFile != "" { + keyFile = rb.keyFile + } + config := &ssh.ClientConfig{ - User: rb.username, + User: username, HostKeyCallback: ssh.InsecureIgnoreHostKey(), Timeout: 30 * time.Second, } + // Apply SSH config crypto settings with compatibility filtering + if kexAlgorithms := ssh_config.Get(rb.hostname, "KexAlgorithms"); kexAlgorithms != "" && !strings.HasPrefix(kexAlgorithms, "+") { + // Only apply if it's an explicit list, not a +append + algorithms := strings.Split(kexAlgorithms, ",") + var finalAlgorithms []string + for _, alg := range algorithms { + finalAlgorithms = append(finalAlgorithms, strings.TrimSpace(alg)) + } + config.KeyExchanges = finalAlgorithms + } + + // Note: Cipher overrides disabled - Go SSH library defaults work + // if ciphers := ssh_config.Get(rb.hostname, "Ciphers"); ciphers != "" { + // config.Ciphers = ... + // } + + if macs := ssh_config.Get(rb.hostname, "MACs"); macs != "" { + macList := strings.Split(macs, ",") + for i, mac := range macList { + macList[i] = strings.TrimSpace(mac) + } + config.MACs = macList + } + + if hostKeyAlgorithms := ssh_config.Get(rb.hostname, "HostKeyAlgorithms"); hostKeyAlgorithms != "" && !strings.HasPrefix(hostKeyAlgorithms, "+") { + // Only apply if it's an explicit list, not a +append + algorithms := strings.Split(hostKeyAlgorithms, ",") + var finalAlgorithms []string + for _, alg := range algorithms { + finalAlgorithms = append(finalAlgorithms, strings.TrimSpace(alg)) + } + config.HostKeyAlgorithms = finalAlgorithms + } + // Try SSH agent first if available if sshAuthSock := os.Getenv("SSH_AUTH_SOCK"); sshAuthSock != "" { if conn, err := net.Dial("unix", sshAuthSock); err == nil { @@ -75,8 +135,16 @@ func (rb *RouterBackup) Connect() error { } // If SSH agent didn't work, try key file - if len(config.Auth) == 0 && rb.keyFile != "" { - key, err := ioutil.ReadFile(rb.keyFile) + if len(config.Auth) == 0 && keyFile != "" { + // Expand ~ in keyFile path + if strings.HasPrefix(keyFile, "~/") { + homeDir, err := os.UserHomeDir() + if err == nil { + keyFile = filepath.Join(homeDir, keyFile[2:]) + } + } + + key, err := ioutil.ReadFile(keyFile) if err != nil { return fmt.Errorf("unable to read private key: %v", err) } @@ -98,14 +166,14 @@ func (rb *RouterBackup) Connect() error { return fmt.Errorf("no authentication method available") } - address := fmt.Sprintf("%s:%d", rb.hostname, rb.port) - client, err := ssh.Dial("tcp", address, config) + address := fmt.Sprintf("%s:%d", hostname, port) + client, err := ssh.Dial("tcp4", address, config) if err != nil { - return fmt.Errorf("failed to connect to %s: %v", rb.hostname, err) + return fmt.Errorf("failed to connect to %s: %v", hostname, err) } rb.client = client - fmt.Printf("Successfully connected to %s\n", rb.hostname) + fmt.Printf("Successfully connected to %s\n", hostname) return nil }