package main import ( "context" "crypto/tls" "flag" "fmt" "log" "net/http" "net/http/httputil" "net/url" "tailscale.com/client/tailscale" "tailscale.com/tsnet" ) var ( target = flag.String("target", "http://127.0.0.1:3000", "target HTTP server for Grafana") hostname = flag.String("hostname", "grafana", "the hostname to use on the tailnet") ) func main() { flag.Parse() ctx := context.Background() u, err := url.Parse(*target) if err != nil { log.Fatal(err) } hdlr := tsAuthMiddleware{ next: httputil.NewSingleHostReverseProxy(u), } srv := &tsnet.Server{ Hostname: *hostname, Logf: log.Printf, } selfFQDN, ok := tailscale.ExpandSNIName(ctx, *hostname) if !ok { log.Fatal("could not get sni name") } l, err := srv.Listen("tcp", ":443") if err != nil { log.Fatal(err) } l = tls.NewListener(l, &tls.Config{ GetCertificate: func(chi *tls.ClientHelloInfo) (*tls.Certificate, error) { if chi.ServerName != selfFQDN { return nil, fmt.Errorf("wanted hostname %s, got: %s", selfFQDN, chi.ServerName) } c, err := tailscale.GetCertificate(chi) if err != nil { log.Printf("%s: %v", chi.Conn.RemoteAddr(), err) } return c, err }, }) log.Printf("listening on https://%s", selfFQDN) log.Fatal(http.Serve(l, hdlr)) } type tsAuthMiddleware struct { next http.Handler } func (t tsAuthMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) { userInfo, err := tailscale.WhoIs(r.Context(), r.RemoteAddr) if err != nil { log.Printf("can't get whois response: %v", err) http.Error(w, "can't get whois response: "+err.Error(), http.StatusInternalServerError) return } r.Header.Set("X-WebAuth-User", userInfo.UserProfile.LoginName) r.Header.Set("X-WebAuth-Name", userInfo.UserProfile.DisplayName) t.next.ServeHTTP(w, r) }