Skip to content

Commit d08220b

Browse files
committed
pkg/sshutil: Use hostKeyCollector().checker() instead of ssh.InsecureIgnoreHostKey()
- `hostKeyCollector().checker()`: checker returns a HostKeyCallback that either checks and collects the host key, or only checks the host key, depending on whether any host keys have been collected. It is expected to pass host key checks by retrying after the first collection. On second invocation, it will only check the host key. The code that uses `ssh.InsecureIgnoreHostKey()` in `x/crypto/ssh` is pointed out in CodeQL as `Use of insecure HostKeyCallback implementation (High)`, so it is an implementation to avoid this. Signed-off-by: Norio Nomura <[email protected]>
1 parent ac1c301 commit d08220b

File tree

1 file changed

+56
-2
lines changed

1 file changed

+56
-2
lines changed

pkg/sshutil/sshutil.go

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -529,7 +529,7 @@ func WaitSSHReady(ctx context.Context, dialContext func(context.Context) (net.Co
529529
sshConfig := &ssh.ClientConfig{
530530
User: user,
531531
Auth: []ssh.AuthMethod{ssh.PublicKeys(signer)},
532-
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
532+
HostKeyCallback: hostKeyCollector().checker(),
533533
Timeout: 10 * time.Second,
534534
}
535535
// Wait until the SSH server is available.
@@ -556,11 +556,16 @@ func WaitSSHReady(ctx context.Context, dialContext func(context.Context) (net.Co
556556
}
557557
}
558558

559+
// errHostKeyMismatch is returned when the SSH host key does not match known hosts.
560+
var errHostKeyMismatch = errors.New("ssh: host key mismatch")
561+
559562
func isRetryableError(err error) bool {
560563
// Port forwarder accepted the connection, but the destination is not ready yet.
561564
return osutil.IsConnectionResetError(err) ||
562565
// SSH server not ready yet (e.g. host key not generated on initial boot).
563-
strings.HasSuffix(err.Error(), "no supported methods remain")
566+
strings.HasSuffix(err.Error(), "no supported methods remain") ||
567+
// Host key is not yet in known_hosts, but will be collected, so we can retry.
568+
errors.Is(err, errHostKeyMismatch)
564569
}
565570

566571
// userPrivateKeySigner returns the user's private key signer.
@@ -581,3 +586,52 @@ func userPrivateKeySigner() (ssh.Signer, error) {
581586
}
582587
return signer, nil
583588
}
589+
590+
// hostKeyCollector is a singleton host key collector.
591+
var hostKeyCollector = sync.OnceValue(func() *_hostKeyCollector {
592+
return &_hostKeyCollector{
593+
hostKeys: make(map[string]ssh.PublicKey),
594+
}
595+
})
596+
597+
type _hostKeyCollector struct {
598+
hostKeys map[string]ssh.PublicKey
599+
mu sync.Mutex
600+
}
601+
602+
// checker returns a HostKeyCallback that either checks and collects the host key,
603+
// or only checks the host key, depending on whether any host keys have been collected.
604+
// It is expected to pass host key checks by retrying after the first collection.
605+
// On second invocation, it will only check the host key.
606+
func (h *_hostKeyCollector) checker() ssh.HostKeyCallback {
607+
if len(h.hostKeys) == 0 {
608+
return h.checkAndCollect
609+
}
610+
return h.checkOnly
611+
}
612+
613+
// checkAndCollect is a HostKeyCallback that records the host key provided by the SSH server.
614+
func (h *_hostKeyCollector) checkAndCollect(_ string, _ net.Addr, key ssh.PublicKey) error {
615+
marshaledKey := string(key.Marshal())
616+
h.mu.Lock()
617+
defer h.mu.Unlock()
618+
if _, ok := h.hostKeys[marshaledKey]; ok {
619+
return nil
620+
}
621+
h.hostKeys[marshaledKey] = key
622+
// If always returning nil here, GitHub Advanced Security may report "Use of insecure HostKeyCallback implementation".
623+
// So, we return an error here to make the SSH client report the host key mismatch.
624+
return errHostKeyMismatch
625+
}
626+
627+
// check is a HostKeyCallback that checks whether the host key has been collected.
628+
func (h *_hostKeyCollector) checkOnly(_ string, _ net.Addr, key ssh.PublicKey) error {
629+
h.mu.Lock()
630+
defer h.mu.Unlock()
631+
if _, ok := h.hostKeys[string(key.Marshal())]; ok {
632+
return nil
633+
}
634+
// If always returning nil here, GitHub Advanced Security may report "Use of insecure HostKeyCallback implementation".
635+
// So, we return an error here to make the SSH client report the host key mismatch.
636+
return errHostKeyMismatch
637+
}

0 commit comments

Comments
 (0)