security: use net.JoinHostPort for SSH addresses to support IPv6
fmt.Sprintf("%s:%d") produces invalid addresses for IPv6. Also adds
mockable sshDial variable for testability.
This commit is contained in:
parent
8b65dffc37
commit
a0516c2bd1
2 changed files with 39 additions and 2 deletions
7
ssh.go
7
ssh.go
|
|
@ -4,6 +4,8 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/towerops-app/towerops-agent/pb"
|
"github.com/towerops-app/towerops-agent/pb"
|
||||||
|
|
@ -11,6 +13,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var sshBackup = executeMikrotikBackup
|
var sshBackup = executeMikrotikBackup
|
||||||
|
var sshDial = ssh.Dial
|
||||||
var doPing = pingDevice
|
var doPing = pingDevice
|
||||||
|
|
||||||
// executeMikrotikBackup connects via SSH and runs /export compact.
|
// executeMikrotikBackup connects via SSH and runs /export compact.
|
||||||
|
|
@ -26,8 +29,8 @@ func executeMikrotikBackup(ip string, port uint16, username, password string) (s
|
||||||
Timeout: 30 * time.Second,
|
Timeout: 30 * time.Second,
|
||||||
}
|
}
|
||||||
|
|
||||||
addr := fmt.Sprintf("%s:%d", ip, port)
|
addr := net.JoinHostPort(ip, strconv.Itoa(int(port)))
|
||||||
conn, err := ssh.Dial("tcp", addr, config)
|
conn, err := sshDial("tcp", addr, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("ssh dial %s: %w", addr, err)
|
return "", fmt.Errorf("ssh dial %s: %w", addr, err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
34
ssh_test.go
34
ssh_test.go
|
|
@ -270,6 +270,40 @@ func TestExecuteMikrotikBackupViaSSH(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSSHBackupIPv6Address(t *testing.T) {
|
||||||
|
origDial := sshDial
|
||||||
|
defer func() { sshDial = origDial }()
|
||||||
|
|
||||||
|
var capturedAddr string
|
||||||
|
sshDial = func(network, addr string, config *ssh.ClientConfig) (*ssh.Client, error) {
|
||||||
|
capturedAddr = addr
|
||||||
|
return nil, fmt.Errorf("mock dial")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _ = executeMikrotikBackup("::1", 22, "admin", "pass")
|
||||||
|
|
||||||
|
if capturedAddr != "[::1]:22" {
|
||||||
|
t.Errorf("expected [::1]:22, got %q", capturedAddr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSSHBackupIPv4Address(t *testing.T) {
|
||||||
|
origDial := sshDial
|
||||||
|
defer func() { sshDial = origDial }()
|
||||||
|
|
||||||
|
var capturedAddr string
|
||||||
|
sshDial = func(network, addr string, config *ssh.ClientConfig) (*ssh.Client, error) {
|
||||||
|
capturedAddr = addr
|
||||||
|
return nil, fmt.Errorf("mock dial")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _ = executeMikrotikBackup("10.0.0.1", 22, "admin", "pass")
|
||||||
|
|
||||||
|
if capturedAddr != "10.0.0.1:22" {
|
||||||
|
t.Errorf("expected 10.0.0.1:22, got %q", capturedAddr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestExecuteMikrotikBackupDialError(t *testing.T) {
|
func TestExecuteMikrotikBackupDialError(t *testing.T) {
|
||||||
_, err := executeMikrotikBackup("127.0.0.1", 1, "admin", "pass")
|
_, err := executeMikrotikBackup("127.0.0.1", 1, "admin", "pass")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue