Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit77364ba

Browse files
committed
feat: modifies config-ssh to check for Coder Connect
1 parent3b54254 commit77364ba

File tree

2 files changed

+166
-132
lines changed

2 files changed

+166
-132
lines changed

‎cli/configssh.go

Lines changed: 155 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,17 @@ const (
4848
typesshConfigOptionsstruct {
4949
waitEnumstring
5050
// Deprecated: moving away from prefix to hostnameSuffix
51-
userHostPrefixstring
52-
hostnameSuffixstring
53-
sshOptions []string
54-
disableAutostartbool
55-
header []string
56-
headerCommandstring
57-
removedKeysmap[string]bool
51+
userHostPrefixstring
52+
hostnameSuffixstring
53+
sshOptions []string
54+
disableAutostartbool
55+
header []string
56+
headerCommandstring
57+
removedKeysmap[string]bool
58+
globalConfigPathstring
59+
coderBinaryPathstring
60+
skipProxyCommandbool
61+
forceUnixSeparatorsbool
5862
}
5963

6064
// addOptions expects options in the form of "option=value" or "option value".
@@ -107,6 +111,80 @@ func (o sshConfigOptions) equal(other sshConfigOptions) bool {
107111
o.hostnameSuffix==other.hostnameSuffix
108112
}
109113

114+
func (osshConfigOptions)writeToBuffer(buf*bytes.Buffer)error {
115+
escapedCoderBinary,err:=sshConfigExecEscape(o.coderBinaryPath,o.forceUnixSeparators)
116+
iferr!=nil {
117+
returnxerrors.Errorf("escape coder binary for ssh failed: %w",err)
118+
}
119+
120+
escapedGlobalConfig,err:=sshConfigExecEscape(o.globalConfigPath,o.forceUnixSeparators)
121+
iferr!=nil {
122+
returnxerrors.Errorf("escape global config for ssh failed: %w",err)
123+
}
124+
125+
rootFlags:=fmt.Sprintf("--global-config %s",escapedGlobalConfig)
126+
for_,h:=rangeo.header {
127+
rootFlags+=fmt.Sprintf(" --header %q",h)
128+
}
129+
ifo.headerCommand!="" {
130+
rootFlags+=fmt.Sprintf(" --header-command %q",o.headerCommand)
131+
}
132+
133+
flags:=""
134+
ifo.waitEnum!="auto" {
135+
flags+=" --wait="+o.waitEnum
136+
}
137+
ifo.disableAutostart {
138+
flags+=" --disable-autostart=true"
139+
}
140+
141+
// Prefix block:
142+
ifo.userHostPrefix!="" {
143+
_,_=buf.WriteString("Host")
144+
145+
_,_=buf.WriteString(" ")
146+
_,_=buf.WriteString(o.userHostPrefix)
147+
_,_=buf.WriteString("*\n")
148+
149+
for_,v:=rangeo.sshOptions {
150+
_,_=buf.WriteString("\t")
151+
_,_=buf.WriteString(v)
152+
_,_=buf.WriteString("\n")
153+
}
154+
if!o.skipProxyCommand&&o.userHostPrefix!="" {
155+
_,_=buf.WriteString("\t")
156+
_,_=fmt.Fprintf(buf,
157+
"ProxyCommand %s %s ssh --stdio%s --ssh-host-prefix %s %%h",
158+
escapedCoderBinary,rootFlags,flags,o.userHostPrefix,
159+
)
160+
_,_=buf.WriteString("\n")
161+
}
162+
}
163+
164+
// Suffix block
165+
ifo.hostnameSuffix=="" {
166+
returnnil
167+
}
168+
_,_=fmt.Fprintf(buf,"\nHost *.%s\n",o.hostnameSuffix)
169+
for_,v:=rangeo.sshOptions {
170+
_,_=buf.WriteString("\t")
171+
_,_=buf.WriteString(v)
172+
_,_=buf.WriteString("\n")
173+
}
174+
// the ^^ options should always apply, but we only want to use the proxy command if Coder Connect is not running.
175+
if!o.skipProxyCommand {
176+
_,_=fmt.Fprintf(buf,"\nMatch host *.%s !exec\"%s connect exists %%h\"\n",
177+
o.hostnameSuffix,escapedCoderBinary)
178+
_,_=buf.WriteString("\t")
179+
_,_=fmt.Fprintf(buf,
180+
"ProxyCommand %s %s ssh --stdio%s --hostname-suffix %s %%h",
181+
escapedCoderBinary,rootFlags,flags,o.hostnameSuffix,
182+
)
183+
_,_=buf.WriteString("\n")
184+
}
185+
returnnil
186+
}
187+
110188
// slicesSortedEqual compares two slices without side-effects or regard to order.
111189
funcslicesSortedEqual[S~[]E,E constraints.Ordered](a,bS)bool {
112190
iflen(a)!=len(b) {
@@ -147,13 +225,11 @@ func (o sshConfigOptions) asList() (list []string) {
147225

148226
func (r*RootCmd)configSSH()*serpent.Command {
149227
var (
150-
sshConfigFilestring
151-
sshConfigOptssshConfigOptions
152-
usePreviousOptsbool
153-
dryRunbool
154-
skipProxyCommandbool
155-
forceUnixSeparatorsbool
156-
coderCliPathstring
228+
sshConfigFilestring
229+
sshConfigOptssshConfigOptions
230+
usePreviousOptsbool
231+
dryRunbool
232+
coderCliPathstring
157233
)
158234
client:=new(codersdk.Client)
159235
cmd:=&serpent.Command{
@@ -177,7 +253,7 @@ func (r *RootCmd) configSSH() *serpent.Command {
177253
Handler:func(inv*serpent.Invocation)error {
178254
ctx:=inv.Context()
179255

180-
ifsshConfigOpts.waitEnum!="auto"&&skipProxyCommand {
256+
ifsshConfigOpts.waitEnum!="auto"&&sshConfigOpts.skipProxyCommand {
181257
// The wait option is applied to the ProxyCommand. If the user
182258
// specifies skip-proxy-command, then wait cannot be applied.
183259
returnxerrors.Errorf("cannot specify both --skip-proxy-command and --wait")
@@ -207,18 +283,7 @@ func (r *RootCmd) configSSH() *serpent.Command {
207283
returnerr
208284
}
209285
}
210-
211-
escapedCoderBinary,err:=sshConfigExecEscape(coderBinary,forceUnixSeparators)
212-
iferr!=nil {
213-
returnxerrors.Errorf("escape coder binary for ssh failed: %w",err)
214-
}
215-
216286
root:=r.createConfig()
217-
escapedGlobalConfig,err:=sshConfigExecEscape(string(root),forceUnixSeparators)
218-
iferr!=nil {
219-
returnxerrors.Errorf("escape global config for ssh failed: %w",err)
220-
}
221-
222287
homedir,err:=os.UserHomeDir()
223288
iferr!=nil {
224289
returnxerrors.Errorf("user home dir failed: %w",err)
@@ -320,94 +385,15 @@ func (r *RootCmd) configSSH() *serpent.Command {
320385
coderdConfig.HostnamePrefix="coder."
321386
}
322387

323-
ifsshConfigOpts.userHostPrefix!="" {
324-
// Override with user flag.
325-
coderdConfig.HostnamePrefix=sshConfigOpts.userHostPrefix
326-
}
327-
ifsshConfigOpts.hostnameSuffix!="" {
328-
// Override with user flag.
329-
coderdConfig.HostnameSuffix=sshConfigOpts.hostnameSuffix
330-
}
331-
332-
// Write agent configuration.
333-
defaultOptions:= []string{
334-
"ConnectTimeout=0",
335-
"StrictHostKeyChecking=no",
336-
// Without this, the "REMOTE HOST IDENTITY CHANGED"
337-
// message will appear.
338-
"UserKnownHostsFile=/dev/null",
339-
// This disables the "Warning: Permanently added 'hostname' (RSA) to the list of known hosts."
340-
// message from appearing on every SSH. This happens because we ignore the known hosts.
341-
"LogLevel ERROR",
342-
}
343-
344-
if!skipProxyCommand {
345-
rootFlags:=fmt.Sprintf("--global-config %s",escapedGlobalConfig)
346-
for_,h:=rangesshConfigOpts.header {
347-
rootFlags+=fmt.Sprintf(" --header %q",h)
348-
}
349-
ifsshConfigOpts.headerCommand!="" {
350-
rootFlags+=fmt.Sprintf(" --header-command %q",sshConfigOpts.headerCommand)
351-
}
352-
353-
flags:=""
354-
ifsshConfigOpts.waitEnum!="auto" {
355-
flags+=" --wait="+sshConfigOpts.waitEnum
356-
}
357-
ifsshConfigOpts.disableAutostart {
358-
flags+=" --disable-autostart=true"
359-
}
360-
ifcoderdConfig.HostnamePrefix!="" {
361-
flags+=" --ssh-host-prefix "+coderdConfig.HostnamePrefix
362-
}
363-
ifcoderdConfig.HostnameSuffix!="" {
364-
flags+=" --hostname-suffix "+coderdConfig.HostnameSuffix
365-
}
366-
defaultOptions=append(defaultOptions,fmt.Sprintf(
367-
"ProxyCommand %s %s ssh --stdio%s %%h",
368-
escapedCoderBinary,rootFlags,flags,
369-
))
370-
}
371-
372-
// Create a copy of the options so we can modify them.
373-
configOptions:=sshConfigOpts
374-
configOptions.sshOptions=nil
375-
376-
// User options first (SSH only uses the first
377-
// option unless it can be given multiple times)
378-
for_,opt:=rangesshConfigOpts.sshOptions {
379-
err:=configOptions.addOptions(opt)
380-
iferr!=nil {
381-
returnxerrors.Errorf("add flag config option %q: %w",opt,err)
382-
}
383-
}
384-
385-
// Deployment options second, allow them to
386-
// override standard options.
387-
fork,v:=rangecoderdConfig.SSHConfigOptions {
388-
opt:=fmt.Sprintf("%s %s",k,v)
389-
err:=configOptions.addOptions(opt)
390-
iferr!=nil {
391-
returnxerrors.Errorf("add coderd config option %q: %w",opt,err)
392-
}
393-
}
394-
395-
// Finally, add the standard options.
396-
iferr:=configOptions.addOptions(defaultOptions...);err!=nil {
388+
configOptions,err:=mergeSSHOptions(sshConfigOpts,coderdConfig,string(root),coderBinary)
389+
iferr!=nil {
397390
returnerr
398391
}
399-
400-
hostBlock:= []string{
401-
sshConfigHostLinePatterns(coderdConfig),
402-
}
403-
// Prefix with '\t'
404-
for_,v:=rangeconfigOptions.sshOptions {
405-
hostBlock=append(hostBlock,"\t"+v)
392+
err=configOptions.writeToBuffer(buf)
393+
iferr!=nil {
394+
returnerr
406395
}
407396

408-
_,_=buf.WriteString(strings.Join(hostBlock,"\n"))
409-
_=buf.WriteByte('\n')
410-
411397
sshConfigWriteSectionEnd(buf)
412398

413399
// Write the remainder of the users config file to buf.
@@ -523,7 +509,7 @@ func (r *RootCmd) configSSH() *serpent.Command {
523509
Flag:"skip-proxy-command",
524510
Env:"CODER_SSH_SKIP_PROXY_COMMAND",
525511
Description:"Specifies whether the ProxyCommand option should be skipped. Useful for testing.",
526-
Value:serpent.BoolOf(&skipProxyCommand),
512+
Value:serpent.BoolOf(&sshConfigOpts.skipProxyCommand),
527513
Hidden:true,
528514
},
529515
{
@@ -564,7 +550,7 @@ func (r *RootCmd) configSSH() *serpent.Command {
564550
Description:"By default, 'config-ssh' uses the os path separator when writing the ssh config. "+
565551
"This might be an issue in Windows machine that use a unix-like shell. "+
566552
"This flag forces the use of unix file paths (the forward slash '/').",
567-
Value:serpent.BoolOf(&forceUnixSeparators),
553+
Value:serpent.BoolOf(&sshConfigOpts.forceUnixSeparators),
568554
// On non-windows showing this command is useless because it is a noop.
569555
// Hide vs disable it though so if a command is copied from a Windows
570556
// machine to a unix machine it will still work and not throw an
@@ -577,6 +563,63 @@ func (r *RootCmd) configSSH() *serpent.Command {
577563
returncmd
578564
}
579565

566+
funcmergeSSHOptions(
567+
usersshConfigOptions,coderd codersdk.SSHConfigResponse,globalConfigPath,coderBinaryPathstring,
568+
) (
569+
sshConfigOptions,error,
570+
) {
571+
// Write agent configuration.
572+
defaultOptions:= []string{
573+
"ConnectTimeout=0",
574+
"StrictHostKeyChecking=no",
575+
// Without this, the "REMOTE HOST IDENTITY CHANGED"
576+
// message will appear.
577+
"UserKnownHostsFile=/dev/null",
578+
// This disables the "Warning: Permanently added 'hostname' (RSA) to the list of known hosts."
579+
// message from appearing on every SSH. This happens because we ignore the known hosts.
580+
"LogLevel ERROR",
581+
}
582+
583+
// Create a copy of the options so we can modify them.
584+
configOptions:=user
585+
configOptions.sshOptions=nil
586+
587+
configOptions.globalConfigPath=globalConfigPath
588+
configOptions.coderBinaryPath=coderBinaryPath
589+
// user config takes precedence
590+
ifuser.userHostPrefix=="" {
591+
configOptions.userHostPrefix=coderd.HostnamePrefix
592+
}
593+
ifuser.hostnameSuffix=="" {
594+
configOptions.hostnameSuffix=coderd.HostnameSuffix
595+
}
596+
597+
// User options first (SSH only uses the first
598+
// option unless it can be given multiple times)
599+
for_,opt:=rangeuser.sshOptions {
600+
err:=configOptions.addOptions(opt)
601+
iferr!=nil {
602+
returnsshConfigOptions{},xerrors.Errorf("add flag config option %q: %w",opt,err)
603+
}
604+
}
605+
606+
// Deployment options second, allow them to
607+
// override standard options.
608+
fork,v:=rangecoderd.SSHConfigOptions {
609+
opt:=fmt.Sprintf("%s %s",k,v)
610+
err:=configOptions.addOptions(opt)
611+
iferr!=nil {
612+
returnsshConfigOptions{},xerrors.Errorf("add coderd config option %q: %w",opt,err)
613+
}
614+
}
615+
616+
// Finally, add the standard options.
617+
iferr:=configOptions.addOptions(defaultOptions...);err!=nil {
618+
returnsshConfigOptions{},err
619+
}
620+
returnconfigOptions,nil
621+
}
622+
580623
//nolint:revive
581624
funcsshConfigWriteSectionHeader(w io.Writer,addNewlinebool,osshConfigOptions) {
582625
nl:="\n"
@@ -844,19 +887,3 @@ func diffBytes(name string, b1, b2 []byte, color bool) ([]byte, error) {
844887
}
845888
returnb,nil
846889
}
847-
848-
funcsshConfigHostLinePatterns(config codersdk.SSHConfigResponse)string {
849-
builder:= strings.Builder{}
850-
// by inspection, WriteString always returns nil error
851-
_,_=builder.WriteString("Host")
852-
ifconfig.HostnamePrefix!="" {
853-
_,_=builder.WriteString(" ")
854-
_,_=builder.WriteString(config.HostnamePrefix)
855-
_,_=builder.WriteString("*")
856-
}
857-
ifconfig.HostnameSuffix!="" {
858-
_,_=builder.WriteString(" *.")
859-
_,_=builder.WriteString(config.HostnameSuffix)
860-
}
861-
returnbuilder.String()
862-
}

‎cli/configssh_test.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -615,13 +615,21 @@ func TestConfigSSH_FileWriteAndOptionsFlow(t *testing.T) {
615615
name:"Hostname Suffix",
616616
args: []string{
617617
"--yes",
618+
"--ssh-option","Foo=bar",
618619
"--hostname-suffix","testy",
619620
},
620621
wantErr:false,
621622
hasAgent:true,
622623
wantConfig:wantConfig{
623-
ssh: []string{"Host coder.* *.testy"},
624-
regexMatch:`ProxyCommand .* ssh .* --hostname-suffix testy %h`,
624+
ssh: []string{
625+
"Host *.testy",
626+
"Foo=bar",
627+
"ConnectTimeout=0",
628+
"StrictHostKeyChecking=no",
629+
"UserKnownHostsFile=/dev/null",
630+
"LogLevel ERROR",
631+
},
632+
regexMatch:`Match host \*\.testy !exec ".* connect exists %h"\n\tProxyCommand .* ssh .* --hostname-suffix testy %h`,
625633
},
626634
},
627635
{
@@ -634,8 +642,7 @@ func TestConfigSSH_FileWriteAndOptionsFlow(t *testing.T) {
634642
wantErr:false,
635643
hasAgent:true,
636644
wantConfig:wantConfig{
637-
ssh: []string{"Host presto.* *.testy"},
638-
regexMatch:`ProxyCommand .* ssh .* --ssh-host-prefix presto\. --hostname-suffix testy %h`,
645+
ssh: []string{"Host presto.*","Match host *.testy !exec"},
639646
},
640647
},
641648
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp