Skip to content

Commit 8b0f86e

Browse files
kachickalexandear
andcommitted
sshutil: suppress warnings when SSH lacks GSSAPI
Co-authored-by: Oleksandr Redko <[email protected]> Signed-off-by: Kenichi Kamiya <[email protected]> Signed-off-by: Oleksandr Redko <[email protected]>
1 parent db2c41a commit 8b0f86e

File tree

2 files changed

+57
-23
lines changed

2 files changed

+57
-23
lines changed

pkg/sshutil/sshutil.go

Lines changed: 47 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -154,13 +154,22 @@ func DefaultPubKeys(loadDotSSH bool) ([]PubKey, error) {
154154
return res, nil
155155
}
156156

157+
type openSSHInfo struct {
158+
// Version is set to the version of OpenSSH, or semver.New("0.0.0") if the version cannot be determined.
159+
Version semver.Version
160+
161+
// Some distributions omit this feature by default, for example, Alpine, NixOS.
162+
GSSAPISupported bool
163+
}
164+
157165
var sshInfo struct {
158166
sync.Once
159167
// aesAccelerated is set to true when AES acceleration is available.
160168
// Available on almost all modern Intel/AMD processors.
161169
aesAccelerated bool
162-
// openSSHVersion is set to the version of OpenSSH, or semver.New("0.0.0") if the version cannot be determined.
163-
openSSHVersion semver.Version
170+
171+
// OpenSSH executable information for the version and supported options.
172+
openSSH openSSHInfo
164173
}
165174

166175
// CommonOpts returns ssh option key-value pairs like {"IdentityFile=/path/to/id_foo"}.
@@ -226,7 +235,6 @@ func CommonOpts(sshPath string, useDotSSH bool) ([]string, error) {
226235
"StrictHostKeyChecking=no",
227236
"UserKnownHostsFile=/dev/null",
228237
"NoHostAuthenticationForLocalhost=yes",
229-
"GSSAPIAuthentication=no",
230238
"PreferredAuthentications=publickey",
231239
"Compression=no",
232240
"BatchMode=yes",
@@ -235,11 +243,15 @@ func CommonOpts(sshPath string, useDotSSH bool) ([]string, error) {
235243

236244
sshInfo.Do(func() {
237245
sshInfo.aesAccelerated = detectAESAcceleration()
238-
sshInfo.openSSHVersion = DetectOpenSSHVersion(sshPath)
246+
sshInfo.openSSH = detectOpenSSHInfo(sshPath)
239247
})
240248

249+
if sshInfo.openSSH.GSSAPISupported {
250+
opts = append(opts, "GSSAPIAuthentication=no")
251+
}
252+
241253
// Only OpenSSH version 8.1 and later support adding ciphers to the front of the default set
242-
if !sshInfo.openSSHVersion.LessThan(*semver.New("8.1.0")) {
254+
if !sshInfo.openSSH.Version.LessThan(*semver.New("8.1.0")) {
243255
// By default, `ssh` choose [email protected], even when AES accelerator is available.
244256
// (OpenSSH_8.1p1, macOS 11.6, MacBookPro 2020, Core i7-1068NG7)
245257
//
@@ -321,7 +333,7 @@ func SSHArgsFromOpts(opts []string) []string {
321333
}
322334

323335
func ParseOpenSSHVersion(version []byte) *semver.Version {
324-
regex := regexp.MustCompile(`^OpenSSH_(\d+\.\d+)(?:p(\d+))?\b`)
336+
regex := regexp.MustCompile(`(?m)^OpenSSH_(\d+\.\d+)(?:p(\d+))?\b`)
325337
matches := regex.FindSubmatch(version)
326338
if len(matches) == 3 {
327339
if len(matches[2]) == 0 {
@@ -332,6 +344,10 @@ func ParseOpenSSHVersion(version []byte) *semver.Version {
332344
return &semver.Version{}
333345
}
334346

347+
func parseOpenSSHGSSAPISupported(version string) bool {
348+
return !strings.Contains(version, `Unsupported option "gssapiauthentication"`)
349+
}
350+
335351
// sshExecutable beyond path also records size and mtime, in the case of ssh upgrades.
336352
type sshExecutable struct {
337353
Path string
@@ -340,14 +356,14 @@ type sshExecutable struct {
340356
}
341357

342358
var (
343-
// sshVersions caches the parsed version of each ssh executable, if it is needed again.
344-
sshVersions = map[sshExecutable]*semver.Version{}
345-
sshVersionsRW sync.RWMutex
359+
// openSSHInfos caches the parsed version and supported options of each ssh executable, if it is needed again.
360+
openSSHInfos = map[sshExecutable]*openSSHInfo{}
361+
openSSHInfosRW sync.RWMutex
346362
)
347363

348-
func DetectOpenSSHVersion(ssh string) semver.Version {
364+
func detectOpenSSHInfo(ssh string) openSSHInfo {
349365
var (
350-
v semver.Version
366+
info openSSHInfo
351367
exe sshExecutable
352368
stderr bytes.Buffer
353369
)
@@ -357,25 +373,33 @@ func DetectOpenSSHVersion(ssh string) semver.Version {
357373
} else {
358374
st, _ := os.Stat(path)
359375
exe = sshExecutable{Path: path, Size: st.Size(), ModTime: st.ModTime()}
360-
sshVersionsRW.RLock()
361-
ver := sshVersions[exe]
362-
sshVersionsRW.RUnlock()
363-
if ver != nil {
364-
return *ver
376+
openSSHInfosRW.RLock()
377+
info := openSSHInfos[exe]
378+
openSSHInfosRW.RUnlock()
379+
if info != nil {
380+
return *info
365381
}
366382
}
367-
cmd := exec.Command(path, "-V")
383+
// -V should be last
384+
cmd := exec.Command(path, "-o", "GSSAPIAuthentication=no", "-V")
368385
cmd.Stderr = &stderr
369386
if err := cmd.Run(); err != nil {
370387
logrus.Warnf("failed to run %v: stderr=%q", cmd.Args, stderr.String())
371388
} else {
372-
v = *ParseOpenSSHVersion(stderr.Bytes())
373-
logrus.Debugf("OpenSSH version %s detected", v)
374-
sshVersionsRW.Lock()
375-
sshVersions[exe] = &v
376-
sshVersionsRW.Unlock()
389+
info = openSSHInfo{
390+
Version: *ParseOpenSSHVersion(stderr.Bytes()),
391+
GSSAPISupported: parseOpenSSHGSSAPISupported(stderr.String()),
392+
}
393+
logrus.Debugf("OpenSSH version %s detected, is GSSAPI supported: %t", info.Version, info.GSSAPISupported)
394+
openSSHInfosRW.Lock()
395+
openSSHInfos[exe] = &info
396+
openSSHInfosRW.Unlock()
377397
}
378-
return v
398+
return info
399+
}
400+
401+
func DetectOpenSSHVersion(ssh string) semver.Version {
402+
return detectOpenSSHInfo(ssh).Version
379403
}
380404

381405
// detectValidPublicKey returns whether content represent a public key.

pkg/sshutil/sshutil_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,16 @@ func TestParseOpenSSHVersion(t *testing.T) {
2929

3030
// OpenBSD 5.8
3131
assert.Check(t, ParseOpenSSHVersion([]byte("OpenSSH_7.0, LibreSSL")).Equal(*semver.New("7.0.0")))
32+
33+
// NixOS 25.05
34+
assert.Check(t, ParseOpenSSHVersion([]byte(`command-line line 0: Unsupported option "gssapiauthentication"
35+
OpenSSH_10.0p2, OpenSSL 3.4.1 11 Feb 2025`)).Equal(*semver.New("10.0.2")))
36+
}
37+
38+
func TestParseOpenSSHGSSAPISupported(t *testing.T) {
39+
assert.Check(t, parseOpenSSHGSSAPISupported("OpenSSH_8.4p1 Ubuntu"))
40+
assert.Check(t, !parseOpenSSHGSSAPISupported(`command-line line 0: Unsupported option "gssapiauthentication"
41+
OpenSSH_10.0p2, OpenSSL 3.4.1 11 Feb 2025`))
3242
}
3343

3444
func Test_detectValidPublicKey(t *testing.T) {

0 commit comments

Comments
 (0)