diff --git a/client/tailscale/tailscale.go b/client/tailscale/tailscale.go index 47d19b05d..fb4f7153b 100644 --- a/client/tailscale/tailscale.go +++ b/client/tailscale/tailscale.go @@ -331,7 +331,14 @@ func GetCertificate(hi *tls.ClientHelloInfo) (*tls.Certificate, error) { } ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() - certPEM, keyPEM, err := CertPair(ctx, hi.ServerName) + + name := hi.ServerName + if !strings.Contains(name, ".") { + if v, ok := ExpandSNIName(ctx, name); ok { + name = v + } + } + certPEM, keyPEM, err := CertPair(ctx, name) if err != nil { return nil, err } @@ -341,3 +348,17 @@ func GetCertificate(hi *tls.ClientHelloInfo) (*tls.Certificate, error) { } return &cert, nil } + +// ExpandSNIName expands bare label name into the the most likely actual TLS cert name. +func ExpandSNIName(ctx context.Context, name string) (fqdn string, ok bool) { + st, err := StatusWithoutPeers(ctx) + if err != nil { + return "", false + } + for _, d := range st.CertDomains { + if len(d) > len(name)+1 && strings.HasPrefix(d, name) && d[len(name)] == '.' { + return d, true + } + } + return "", false +} diff --git a/cmd/tailscale/cli/cert.go b/cmd/tailscale/cli/cert.go index 32817f45e..f6acb7137 100644 --- a/cmd/tailscale/cli/cert.go +++ b/cmd/tailscale/cli/cert.go @@ -13,6 +13,7 @@ import ( "log" "net/http" "os" + "strings" "github.com/peterbourgon/ff/v2/ffcli" "tailscale.com/atomicfile" @@ -46,6 +47,12 @@ func runCert(ctx context.Context, args []string) error { GetCertificate: tailscale.GetCertificate, }, Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.TLS != nil && !strings.Contains(r.Host, ".") && r.Method == "GET" { + if v, ok := tailscale.ExpandSNIName(r.Context(), r.Host); ok { + http.Redirect(w, r, "https://"+v+r.URL.Path, http.StatusTemporaryRedirect) + return + } + } fmt.Fprintf(w, "

Hello from Tailscale

It works.") }), }