cmd/tailscale: allow SSH to IPs or DNS names without MagicDNS (#16591)

fixes #16381

Signed-off-by: Danni Popova <danni@tailscale.com>
This commit is contained in:
Danni Popova 2025-07-25 10:21:41 +01:00 committed by GitHub
parent 2a5d9c7269
commit c572442548
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -70,12 +70,28 @@ func runSSH(ctx context.Context, args []string) error {
return err
}
prefs, err := localClient.GetPrefs(ctx)
if err != nil {
return err
}
// hostForSSH is the hostname we'll tell OpenSSH we're
// connecting to, so we have to maintain fewer entries in the
// known_hosts files.
hostForSSH := host
if v, ok := nodeDNSNameFromArg(st, host); ok {
hostForSSH = v
ps, ok := peerStatusFromArg(st, host)
if ok {
hostForSSH = ps.DNSName
// If MagicDNS isn't enabled on the client,
// we will use the first IPv4 we know about
// or fallback to the first IPv6 address
if !prefs.CorpDNS {
ipHost, found := ipFromPeerStatus(ps)
if found {
hostForSSH = ipHost
}
}
}
ssh, err := findSSH()
@ -169,11 +185,40 @@ func genKnownHosts(st *ipnstate.Status) []byte {
continue
}
fmt.Fprintf(&buf, "%s %s\n", ps.DNSName, hostKey)
for _, ip := range ps.TailscaleIPs {
fmt.Fprintf(&buf, "%s %s\n", ip.String(), hostKey)
}
}
}
return buf.Bytes()
}
// peerStatusFromArg returns the PeerStatus that matches
// the input arg which can be a base name, full DNS name, or an IP.
func peerStatusFromArg(st *ipnstate.Status, arg string) (*ipnstate.PeerStatus, bool) {
if arg == "" {
return nil, false
}
argIP, _ := netip.ParseAddr(arg)
for _, ps := range st.Peer {
if argIP.IsValid() {
for _, ip := range ps.TailscaleIPs {
if ip == argIP {
return ps, true
}
}
continue
}
if strings.EqualFold(strings.TrimSuffix(arg, "."), strings.TrimSuffix(ps.DNSName, ".")) {
return ps, true
}
if base, _, ok := strings.Cut(ps.DNSName, "."); ok && strings.EqualFold(base, arg) {
return ps, true
}
}
return nil, false
}
// nodeDNSNameFromArg returns the PeerStatus.DNSName value from a peer
// in st that matches the input arg which can be a base name, full
// DNS name, or an IP.
@ -202,6 +247,20 @@ func nodeDNSNameFromArg(st *ipnstate.Status, arg string) (dnsName string, ok boo
return "", false
}
func ipFromPeerStatus(ps *ipnstate.PeerStatus) (string, bool) {
if len(ps.TailscaleIPs) < 1 {
return "", false
}
// Look for a IPv4 address or default to the first IP of the list
for _, ip := range ps.TailscaleIPs {
if ip.Is4() {
return ip.String(), true
}
}
return ps.TailscaleIPs[0].String(), true
}
// getSSHClientEnvVar returns the "SSH_CLIENT" environment variable
// for the current process group, if any.
var getSSHClientEnvVar = func() string {