You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tailscale/cmd/k8s-operator/proxy.go

81 lines
2.1 KiB
Go

// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package main
import (
"context"
"crypto/tls"
"fmt"
"log"
"net"
"net/http"
"net/http/httputil"
"net/url"
"os"
"strings"
"tailscale.com/client/tailscale"
"tailscale.com/client/tailscale/apitype"
"tailscale.com/types/logger"
)
type whoIsKey struct{}
// authProxy is an http.Handler that authenticates requests using the Tailscale
// LocalAPI and then proxies them to the Kubernetes API.
type authProxy struct {
logf logger.Logf
lc *tailscale.LocalClient
rp *httputil.ReverseProxy
}
func (h *authProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
who, err := h.lc.WhoIs(r.Context(), r.RemoteAddr)
if err != nil {
h.logf("failed to authenticate caller: %v", err)
http.Error(w, "failed to authenticate caller", http.StatusInternalServerError)
return
}
r = r.WithContext(context.WithValue(r.Context(), whoIsKey{}, who))
h.rp.ServeHTTP(w, r)
}
func runAuthProxy(lc *tailscale.LocalClient, ls net.Listener, rt http.RoundTripper, logf logger.Logf) {
u, err := url.Parse(fmt.Sprintf("https://%s:%s", os.Getenv("KUBERNETES_SERVICE_HOST"), os.Getenv("KUBERNETES_SERVICE_PORT_HTTPS")))
if err != nil {
log.Fatalf("runAuthProxy: failed to parse URL %v", err)
}
ap := &authProxy{
logf: logf,
lc: lc,
rp: &httputil.ReverseProxy{
Director: func(r *http.Request) {
// Replace the request with the user's identity.
who := r.Context().Value(whoIsKey{}).(*apitype.WhoIsResponse)
r.Header.Set("Impersonate-User", who.UserProfile.LoginName)
// Remove all authentication headers.
r.Header.Del("Authorization")
r.Header.Del("Impersonate-Group")
r.Header.Del("Impersonate-Uid")
for k := range r.Header {
if strings.HasPrefix(k, "Impersonate-Extra-") {
r.Header.Del(k)
}
}
// Replace the URL with the Kubernetes APIServer.
r.URL.Scheme = u.Scheme
r.URL.Host = u.Host
},
Transport: rt,
},
}
if err := http.Serve(tls.NewListener(ls, &tls.Config{
GetCertificate: lc.GetCertificate,
}), ap); err != nil {
log.Fatalf("runAuthProxy: failed to serve %v", err)
}
}