Skip to content

Commit 48cca92

Browse files
committed
chore: parse single ssh config
1 parent 912d924 commit 48cca92

File tree

2 files changed

+162
-0
lines changed

2 files changed

+162
-0
lines changed

internal/parser/ssh.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,74 @@ func GetSSHConfigContent(host string, input SSHHostConfigGroup) (config SSHHostC
6666
config.Config = "Host " + host + "\n" + config.Config
6767
return config
6868
}
69+
70+
type HostConfig struct {
71+
HostName string `yaml:"HostName,omitempty"`
72+
User string `yaml:"User,omitempty"`
73+
IdentityFile string `yaml:"IdentityFile,omitempty"`
74+
Port string `yaml:"Port,omitempty"`
75+
ControlPath string `yaml:"ControlPath,omitempty"`
76+
ControlPersist string `yaml:"ControlPersist,omitempty"`
77+
TCPKeepAlive string `yaml:"TCPKeepAlive,omitempty"`
78+
Compression string `yaml:"Compression,omitempty"`
79+
ForwardAgent string `yaml:"ForwardAgent,omitempty"`
80+
Ciphers string `yaml:"Ciphers,omitempty"`
81+
HostKeyAlgorithms string `yaml:"HostKeyAlgorithms,omitempty"`
82+
KexAlgorithms string `yaml:"KexAlgorithms,omitempty"`
83+
PubkeyAuthentication string `yaml:"PubkeyAuthentication,omitempty"`
84+
ProxyCommand string `yaml:"ProxyCommand,omitempty"`
85+
Note string `yaml:"Note,omitempty"`
86+
}
87+
88+
func ParseSSHConfig(input string) (config HostConfig) {
89+
lines := strings.Split(input, "\n")
90+
for _, line := range lines {
91+
line = strings.TrimSpace(line)
92+
if line == "" || strings.HasPrefix(line, "#") {
93+
continue
94+
}
95+
96+
parts := strings.SplitN(line, " ", 2)
97+
if len(parts) == 2 {
98+
key := strings.ToLower(parts[0])
99+
value := strings.TrimSpace(parts[1])
100+
101+
switch key {
102+
case "host":
103+
// do nothing
104+
case "hostname":
105+
config.HostName = value
106+
case "user":
107+
config.User = value
108+
case "identityfile":
109+
config.IdentityFile = value
110+
case "port":
111+
config.Port = value
112+
case "controlpath":
113+
config.ControlPath = value
114+
case "controlpersist":
115+
config.ControlPersist = value
116+
case "tcpkeepalive":
117+
config.TCPKeepAlive = value
118+
case "compression":
119+
config.Compression = value
120+
case "forwardagent":
121+
config.ForwardAgent = value
122+
case "ciphers":
123+
config.Ciphers = value
124+
case "hostkeyalgorithms":
125+
config.HostKeyAlgorithms = value
126+
case "kexalgorithms":
127+
config.KexAlgorithms = value
128+
case "pubkeyauthentication":
129+
config.PubkeyAuthentication = value
130+
case "proxycommand":
131+
config.ProxyCommand = value
132+
default:
133+
fmt.Println("Unknown key", key)
134+
}
135+
}
136+
}
137+
138+
return config
139+
}

internal/parser/ssh_test.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,3 +114,94 @@ func TestGetSSHConfigContent(t *testing.T) {
114114
})
115115
}
116116
}
117+
118+
func TestParseSSHConfig(t *testing.T) {
119+
tests := []struct {
120+
name string
121+
input string
122+
expected Parser.HostConfig
123+
}{
124+
{
125+
name: "Basic Config",
126+
input: `
127+
Host example
128+
HostName example.com
129+
User testuser
130+
IdentityFile ~/.ssh/id_rsa
131+
Port 2222
132+
`,
133+
expected: Parser.HostConfig{
134+
HostName: "example.com",
135+
User: "testuser",
136+
IdentityFile: "~/.ssh/id_rsa",
137+
Port: "2222",
138+
},
139+
},
140+
{
141+
name: "Full Config",
142+
input: `
143+
Host fullexample
144+
HostName fullexample.com
145+
User fulluser
146+
IdentityFile ~/.ssh/full_id_rsa
147+
Port 3333
148+
ControlPath ~/.ssh/cm_%r@%h:%p
149+
ControlPersist 30m
150+
TCPKeepAlive yes
151+
Compression yes
152+
ForwardAgent yes
153+
Ciphers aes128-ctr,aes192-ctr,aes256-ctr
154+
HostKeyAlgorithms ssh-ed25519,rsa-sha2-512
155+
KexAlgorithms curve25519-sha256,diffie-hellman-group14-sha256
156+
PubkeyAuthentication yes
157+
ProxyCommand ssh jumphost nc %h %p
158+
`,
159+
expected: Parser.HostConfig{
160+
HostName: "fullexample.com",
161+
User: "fulluser",
162+
IdentityFile: "~/.ssh/full_id_rsa",
163+
Port: "3333",
164+
ControlPath: "~/.ssh/cm_%r@%h:%p",
165+
ControlPersist: "30m",
166+
TCPKeepAlive: "yes",
167+
Compression: "yes",
168+
ForwardAgent: "yes",
169+
Ciphers: "aes128-ctr,aes192-ctr,aes256-ctr",
170+
HostKeyAlgorithms: "ssh-ed25519,rsa-sha2-512",
171+
KexAlgorithms: "curve25519-sha256,diffie-hellman-group14-sha256",
172+
PubkeyAuthentication: "yes",
173+
ProxyCommand: "ssh jumphost nc %h %p",
174+
},
175+
},
176+
{
177+
name: "Empty Config",
178+
input: `
179+
# This is a comment
180+
Host empty
181+
# This is another comment
182+
`,
183+
expected: Parser.HostConfig{},
184+
},
185+
{
186+
name: "Unknown Keys",
187+
input: `
188+
Host unknown
189+
HostName unknown.com
190+
UnknownKey1 value1
191+
UnknownKey2 value2
192+
`,
193+
expected: Parser.HostConfig{
194+
HostName: "unknown.com",
195+
},
196+
},
197+
}
198+
199+
for _, tt := range tests {
200+
t.Run(tt.name, func(t *testing.T) {
201+
got := Parser.ParseSSHConfig(tt.input)
202+
if !reflect.DeepEqual(got, tt.expected) {
203+
t.Errorf("ParseSSHConfig() = %v, want %v", got, tt.expected)
204+
}
205+
})
206+
}
207+
}

0 commit comments

Comments
 (0)