219 lines
5.1 KiB
Go
219 lines
5.1 KiB
Go
package sshconfig
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
const testConfig = `# Test SSH config
|
|
|
|
Host *
|
|
ServerAliveInterval 60
|
|
|
|
Host webserver
|
|
HostName web.example.com
|
|
Port 2222
|
|
User deploy
|
|
IdentityFile ~/.ssh/deploy_key
|
|
ForwardAgent yes
|
|
Compression yes
|
|
|
|
Host dbserver
|
|
HostName db.internal.example.com
|
|
User postgres
|
|
ProxyJump bastion@jump.example.com:2200
|
|
|
|
Host simple
|
|
HostName 10.0.0.5
|
|
|
|
Host wildcard-*
|
|
User admin
|
|
`
|
|
|
|
func TestParseFile(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "config")
|
|
if err := os.WriteFile(path, []byte(testConfig), 0600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
blocks, err := ParseFile(path)
|
|
if err != nil {
|
|
t.Fatalf("ParseFile failed: %v", err)
|
|
}
|
|
|
|
// Should have 3 blocks: webserver, dbserver, simple.
|
|
// "Host *" and "Host wildcard-*" should be skipped.
|
|
if len(blocks) != 3 {
|
|
t.Fatalf("expected 3 blocks, got %d", len(blocks))
|
|
}
|
|
|
|
// Verify webserver block.
|
|
wb := blocks[0]
|
|
if wb.Aliases[0] != "webserver" {
|
|
t.Errorf("expected alias 'webserver', got %q", wb.Aliases[0])
|
|
}
|
|
if wb.HostName != "web.example.com" {
|
|
t.Errorf("expected HostName 'web.example.com', got %q", wb.HostName)
|
|
}
|
|
if wb.Port != 2222 {
|
|
t.Errorf("expected port 2222, got %d", wb.Port)
|
|
}
|
|
if wb.User != "deploy" {
|
|
t.Errorf("expected user 'deploy', got %q", wb.User)
|
|
}
|
|
if !wb.ForwardAgent {
|
|
t.Error("expected ForwardAgent to be true")
|
|
}
|
|
if !wb.Compression {
|
|
t.Error("expected Compression to be true")
|
|
}
|
|
|
|
// Verify dbserver block with ProxyJump.
|
|
db := blocks[1]
|
|
if db.Aliases[0] != "dbserver" {
|
|
t.Errorf("expected alias 'dbserver', got %q", db.Aliases[0])
|
|
}
|
|
if db.ProxyJump != "bastion@jump.example.com:2200" {
|
|
t.Errorf("unexpected ProxyJump: %q", db.ProxyJump)
|
|
}
|
|
|
|
// Verify simple block.
|
|
sb := blocks[2]
|
|
if sb.HostName != "10.0.0.5" {
|
|
t.Errorf("expected HostName '10.0.0.5', got %q", sb.HostName)
|
|
}
|
|
if sb.Port != 22 {
|
|
t.Errorf("expected default port 22, got %d", sb.Port)
|
|
}
|
|
}
|
|
|
|
func TestParseFileRejectsOversizedFile(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "config")
|
|
f, err := os.Create(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := f.Truncate(maxConfigFileBytes + 1); err != nil {
|
|
f.Close()
|
|
t.Fatal(err)
|
|
}
|
|
if err := f.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, err = ParseFile(path)
|
|
if err == nil {
|
|
t.Fatal("expected oversized config file to be rejected")
|
|
}
|
|
if !strings.Contains(err.Error(), "too large") {
|
|
t.Fatalf("error = %q, want too large", err.Error())
|
|
}
|
|
}
|
|
|
|
func TestParseFileRejectsTooManyHostBlocks(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "config")
|
|
var b strings.Builder
|
|
for i := 0; i < maxHostBlocks+1; i++ {
|
|
b.WriteString("Host host")
|
|
b.WriteString(strconv.Itoa(i))
|
|
b.WriteString("\n HostName example.com\n")
|
|
}
|
|
if err := os.WriteFile(path, []byte(b.String()), 0600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, err := ParseFile(path)
|
|
if err == nil {
|
|
t.Fatal("expected excessive Host blocks to be rejected")
|
|
}
|
|
if !strings.Contains(err.Error(), "too many Host blocks") {
|
|
t.Fatalf("error = %q, want too many Host blocks", err.Error())
|
|
}
|
|
}
|
|
|
|
func TestToProfiles(t *testing.T) {
|
|
blocks := []HostBlock{
|
|
{
|
|
Aliases: []string{"myserver"},
|
|
HostName: "srv.example.com",
|
|
Port: 22,
|
|
User: "admin",
|
|
ForwardAgent: true,
|
|
},
|
|
{
|
|
Aliases: []string{"jumpbox"},
|
|
HostName: "jump.example.com",
|
|
Port: 2200,
|
|
User: "jumper",
|
|
ProxyJump: "gateway@gw.example.com",
|
|
},
|
|
}
|
|
|
|
profiles := ToProfiles(blocks, "Imported")
|
|
if len(profiles) != 2 {
|
|
t.Fatalf("expected 2 profiles, got %d", len(profiles))
|
|
}
|
|
|
|
p := profiles[0]
|
|
if p.DisplayName != "myserver" {
|
|
t.Errorf("expected DisplayName 'myserver', got %q", p.DisplayName)
|
|
}
|
|
if p.Host != "srv.example.com" {
|
|
t.Errorf("expected host 'srv.example.com', got %q", p.Host)
|
|
}
|
|
if p.SSH == nil {
|
|
t.Fatal("expected SSH config to be set")
|
|
}
|
|
if !p.SSH.AgentAuth {
|
|
t.Error("expected AgentAuth to be true for ForwardAgent host")
|
|
}
|
|
|
|
jp := profiles[1]
|
|
if jp.SSH == nil || len(jp.SSH.JumpHosts) == 0 {
|
|
t.Fatal("expected jump host from ProxyJump")
|
|
}
|
|
jh := jp.SSH.JumpHosts[0]
|
|
if jh.Host != "gw.example.com" {
|
|
t.Errorf("expected jump host 'gw.example.com', got %q", jh.Host)
|
|
}
|
|
if jh.Username != "gateway" {
|
|
t.Errorf("expected jump user 'gateway', got %q", jh.Username)
|
|
}
|
|
}
|
|
|
|
func TestParseProxyJump(t *testing.T) {
|
|
tests := []struct {
|
|
input string
|
|
wantHost string
|
|
wantUser string
|
|
wantPort int
|
|
}{
|
|
{"bastion.example.com", "bastion.example.com", "", 22},
|
|
{"user@bastion.example.com", "bastion.example.com", "user", 22},
|
|
{"user@bastion.example.com:2200", "bastion.example.com", "user", 2200},
|
|
{"host:443", "host", "", 443},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.input, func(t *testing.T) {
|
|
jh := parseProxyJump(tt.input)
|
|
if jh == nil {
|
|
t.Fatal("expected non-nil result")
|
|
}
|
|
if jh.Host != tt.wantHost {
|
|
t.Errorf("host: got %q, want %q", jh.Host, tt.wantHost)
|
|
}
|
|
if jh.Username != tt.wantUser {
|
|
t.Errorf("user: got %q, want %q", jh.Username, tt.wantUser)
|
|
}
|
|
if jh.Port != tt.wantPort {
|
|
t.Errorf("port: got %d, want %d", jh.Port, tt.wantPort)
|
|
}
|
|
})
|
|
}
|
|
}
|