@@ -6,10 +6,13 @@ package client
66import (
77 "fmt"
88 "net/url"
9+ "os"
910 "runtime"
11+ "strings"
1012
1113 "github.com/google/uuid"
1214 "github.com/pkg/errors"
15+ "golang.org/x/term"
1316)
1417
1518// Parses and formats ssh:// URLs
@@ -20,6 +23,7 @@ type SshParams struct {
2023 User string
2124 SocketPath string
2225 CliPath string
26+ Options map [string ]string
2327}
2428
2529func ParseSshUrl (u * url.URL ) (* SshParams , error ) {
@@ -48,8 +52,16 @@ func ParseSshUrl(u *url.URL) (*SshParams, error) {
4852 queryParams .Del ("cliPath" )
4953 }
5054
51- if len (queryParams ) > 0 {
52- return nil , errors .Errorf ("unexpected query parameters: %v. Only 'cliPath' is supported" , queryParams )
55+ for k , v := range queryParams {
56+ if strings .HasPrefix (k , "option[" ) && strings .HasSuffix (k , "]" ) {
57+ name := k [7 : len (k )- 1 ]
58+ if sp .Options == nil {
59+ sp .Options = make (map [string ]string )
60+ }
61+ sp .Options [name ] = v [0 ]
62+ } else {
63+ return nil , errors .Errorf ("unexpected query parameter: %q. Only 'cliPath' and 'option[<SSH_OPTION>]' are suported" , k )
64+ }
5365 }
5466 }
5567
@@ -77,30 +89,58 @@ func (sp *SshParams) URL() *url.URL {
7789 if sp .Port != "" {
7890 u .Host += ":" + sp .Port
7991 }
92+ q := u .Query ()
8093 if sp .CliPath != "" {
81- q := u .Query ()
8294 q .Set ("cliPath" , sp .CliPath )
95+ }
96+ for k , v := range sp .Options {
97+ q .Set (fmt .Sprintf ("option[%s]" , k ), v )
98+ }
99+
100+ if len (q ) > 0 {
83101 u .RawQuery = q .Encode ()
84102 }
85103
86104 return & u
87105}
88106
89107func (sp * SshParams ) FormatCmdLine (add ... string ) []string {
90- return sp .formatCmdLine (nil , add ... )
108+ sshOptions := map [string ]string {
109+ "StrictHostKeyChecking" : "yes" ,
110+ }
111+ return sp .formatCmdLine (sshOptions , nil , add ... )
91112}
92113
93- func (sp * SshParams ) formatCmdLine (sshArgs []string , cmdArgs ... string ) []string {
114+ func (sp * SshParams ) formatCmdLine (sshOptions map [ string ] string , otherSshArgs []string , cmdArgs ... string ) []string {
94115 args := []string {sp .Host }
95116
117+ var combinedSshOptions map [string ]string
118+ if sp .Options == nil {
119+ combinedSshOptions = sshOptions
120+ } else if sshOptions == nil {
121+ combinedSshOptions = sp .Options
122+ } else {
123+ combinedSshOptions = make (map [string ]string )
124+ for k , v := range sshOptions {
125+ combinedSshOptions [k ] = v
126+ }
127+ for k , v := range sp .Options {
128+ combinedSshOptions [k ] = v
129+ }
130+ }
131+
132+ for k , v := range combinedSshOptions {
133+ args = append (args , "-o" , fmt .Sprintf ("%s=%s" , k , v ))
134+ }
135+
96136 if sp .User != "" {
97137 args = append (args , "-l" , sp .User )
98138 }
99139 if sp .Port != "" {
100140 args = append (args , "-p" , sp .Port )
101141 }
102142
103- args = append (args , sshArgs ... )
143+ args = append (args , otherSshArgs ... )
104144
105145 args = append (args , "--" )
106146
@@ -117,31 +157,40 @@ func (sp *SshParams) formatCmdLine(sshArgs []string, cmdArgs ...string) []string
117157}
118158
119159func (sp * SshParams ) FormatLoginArgs (add ... string ) []string {
160+ var sshOptions map [string ]string
161+ if ! term .IsTerminal (int (os .Stdin .Fd ())) {
162+ // avoid interactive prompt
163+ sshOptions = map [string ]string {
164+ "StrictHostKeyChecking" : "yes" ,
165+ }
166+ }
167+
120168 // Disable stdin and disable pseudo-terminal allocation.
121169 // On Windows, we can get a hang when the remote process exits quickly because
122170 // the SSH process is waiting for Enter to be pressed.
123171
124- sshArgs := []string {"-nT" }
172+ otherSshArgs := []string {"-nT" }
125173 args := []string {"login" }
126174
127175 if sp .SocketPath != "" {
128176 args = append (args , "--socket-path" , sp .SocketPath )
129177 }
130178
131179 args = append (args , add ... )
132- return sp .formatCmdLine (sshArgs , args ... )
180+ return sp .formatCmdLine (sshOptions , otherSshArgs , args ... )
133181}
134182
135183func (sp * SshParams ) FormatDataPlaneCmdLine (add ... string ) []string {
136- var sshArgs []string
184+ sshOptions := map [string ]string {
185+ "StrictHostKeyChecking" : "yes" ,
186+ }
187+
137188 if runtime .GOOS != "windows" {
138189 // create a dedicated control socket for this process
139- sshArgs = []string {
140- "-o" , "ControlMaster=auto" ,
141- "-o" , fmt .Sprintf ("ControlPath=/tmp/%s" , uuid .New ().String ()),
142- "-o" , "ControlPersist=2m" ,
143- }
190+ sshOptions ["ControlMaster" ] = "auto"
191+ sshOptions ["ControlPath" ] = fmt .Sprintf ("/tmp/%s" , uuid .New ().String ())
192+ sshOptions ["ControlPersist" ] = "2m"
144193 }
145194
146- return sp .formatCmdLine (sshArgs , add ... )
195+ return sp .formatCmdLine (sshOptions , nil , add ... )
147196}
0 commit comments