diff --git a/cmd/k8s-operator/manifests/authproxy-rbac.yaml b/cmd/k8s-operator/manifests/authproxy-rbac.yaml index ae711a858..ddbdda32e 100644 --- a/cmd/k8s-operator/manifests/authproxy-rbac.yaml +++ b/cmd/k8s-operator/manifests/authproxy-rbac.yaml @@ -7,7 +7,7 @@ metadata: name: tailscale-auth-proxy rules: - apiGroups: [""] - resources: ["users"] + resources: ["users", "groups"] verbs: ["impersonate"] --- apiVersion: rbac.authorization.k8s.io/v1 diff --git a/cmd/k8s-operator/proxy.go b/cmd/k8s-operator/proxy.go index 83a4cfb81..94dde4809 100644 --- a/cmd/k8s-operator/proxy.go +++ b/cmd/k8s-operator/proxy.go @@ -51,13 +51,16 @@ func runAuthProxy(lc *tailscale.LocalClient, ls net.Listener, rt http.RoundTripp 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) + // We want to proxy to the Kubernetes API, but we want to use + // the caller's identity to do so. We do this by impersonating + // the caller using the Kubernetes User Impersonation feature: + // https://kubernetes.io/docs/reference/access-authn-authz/authentication/#user-impersonation - // Remove all authentication headers. + // Out of paranoia, remove all authentication headers that might + // have been set by the client. r.Header.Del("Authorization") r.Header.Del("Impersonate-Group") + r.Header.Del("Impersonate-User") r.Header.Del("Impersonate-Uid") for k := range r.Header { if strings.HasPrefix(k, "Impersonate-Extra-") { @@ -65,6 +68,19 @@ func runAuthProxy(lc *tailscale.LocalClient, ls net.Listener, rt http.RoundTripp } } + // Now add the impersonation headers that we want. + who := r.Context().Value(whoIsKey{}).(*apitype.WhoIsResponse) + if who.Node.IsTagged() { + // Use the nodes FQDN as the username, and the nodes tags as the groups. + // "Impersonate-Group" requires "Impersonate-User" to be set. + r.Header.Set("Impersonate-User", who.Node.Name) + for _, tag := range who.Node.Tags { + r.Header.Add("Impersonate-Group", tag) + } + } else { + r.Header.Set("Impersonate-User", who.UserProfile.LoginName) + } + // Replace the URL with the Kubernetes APIServer. r.URL.Scheme = u.Scheme r.URL.Host = u.Host