mirror of
https://github.com/tailscale/tailscale.git
synced 2025-04-16 03:31:39 +00:00
cmd/k8s-operator: log user/group impersonated by apiserver proxy (#10334)
Updates tailscale/tailscale#10127 Signed-off-by: Irbe Krumina <irbe@tailscale.com>
This commit is contained in:
parent
4f80f403be
commit
dd8bc9ba03
@ -23,7 +23,6 @@ import (
|
|||||||
"tailscale.com/client/tailscale/apitype"
|
"tailscale.com/client/tailscale/apitype"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
"tailscale.com/tsnet"
|
"tailscale.com/tsnet"
|
||||||
"tailscale.com/types/logger"
|
|
||||||
"tailscale.com/util/clientmetric"
|
"tailscale.com/util/clientmetric"
|
||||||
"tailscale.com/util/set"
|
"tailscale.com/util/set"
|
||||||
)
|
)
|
||||||
@ -109,21 +108,21 @@ func maybeLaunchAPIServerProxy(zlog *zap.SugaredLogger, restConfig *rest.Config,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
startlog.Fatalf("could not get rest.TransportConfig(): %v", err)
|
startlog.Fatalf("could not get rest.TransportConfig(): %v", err)
|
||||||
}
|
}
|
||||||
go runAPIServerProxy(s, rt, zlog.Named("apiserver-proxy").Infof, mode)
|
go runAPIServerProxy(s, rt, zlog.Named("apiserver-proxy"), mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// apiserverProxy is an http.Handler that authenticates requests using the Tailscale
|
// apiserverProxy is an http.Handler that authenticates requests using the Tailscale
|
||||||
// LocalAPI and then proxies them to the Kubernetes API.
|
// LocalAPI and then proxies them to the Kubernetes API.
|
||||||
type apiserverProxy struct {
|
type apiserverProxy struct {
|
||||||
logf logger.Logf
|
log *zap.SugaredLogger
|
||||||
lc *tailscale.LocalClient
|
lc *tailscale.LocalClient
|
||||||
rp *httputil.ReverseProxy
|
rp *httputil.ReverseProxy
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *apiserverProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (h *apiserverProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
who, err := h.lc.WhoIs(r.Context(), r.RemoteAddr)
|
who, err := h.lc.WhoIs(r.Context(), r.RemoteAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logf("failed to authenticate caller: %v", err)
|
h.log.Errorf("failed to authenticate caller: %v", err)
|
||||||
http.Error(w, "failed to authenticate caller", http.StatusInternalServerError)
|
http.Error(w, "failed to authenticate caller", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -145,7 +144,7 @@ func (h *apiserverProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
// are passed through to the Kubernetes API.
|
// are passed through to the Kubernetes API.
|
||||||
//
|
//
|
||||||
// It never returns.
|
// It never returns.
|
||||||
func runAPIServerProxy(s *tsnet.Server, rt http.RoundTripper, logf logger.Logf, mode apiServerProxyMode) {
|
func runAPIServerProxy(s *tsnet.Server, rt http.RoundTripper, log *zap.SugaredLogger, mode apiServerProxyMode) {
|
||||||
if mode == apiserverProxyModeDisabled {
|
if mode == apiserverProxyModeDisabled {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -163,8 +162,8 @@ func runAPIServerProxy(s *tsnet.Server, rt http.RoundTripper, logf logger.Logf,
|
|||||||
log.Fatalf("could not get local client: %v", err)
|
log.Fatalf("could not get local client: %v", err)
|
||||||
}
|
}
|
||||||
ap := &apiserverProxy{
|
ap := &apiserverProxy{
|
||||||
logf: logf,
|
log: log,
|
||||||
lc: lc,
|
lc: lc,
|
||||||
rp: &httputil.ReverseProxy{
|
rp: &httputil.ReverseProxy{
|
||||||
Rewrite: func(r *httputil.ProxyRequest) {
|
Rewrite: func(r *httputil.ProxyRequest) {
|
||||||
// Replace the URL with the Kubernetes APIServer.
|
// Replace the URL with the Kubernetes APIServer.
|
||||||
@ -196,7 +195,7 @@ func runAPIServerProxy(s *tsnet.Server, rt http.RoundTripper, logf logger.Logf,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Now add the impersonation headers that we want.
|
// Now add the impersonation headers that we want.
|
||||||
if err := addImpersonationHeaders(r.Out); err != nil {
|
if err := addImpersonationHeaders(r.Out, log); err != nil {
|
||||||
panic("failed to add impersonation headers: " + err.Error())
|
panic("failed to add impersonation headers: " + err.Error())
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -213,6 +212,7 @@ func runAPIServerProxy(s *tsnet.Server, rt http.RoundTripper, logf logger.Logf,
|
|||||||
TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)),
|
TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)),
|
||||||
Handler: ap,
|
Handler: ap,
|
||||||
}
|
}
|
||||||
|
log.Infof("listening on %s", ln.Addr())
|
||||||
if err := hs.ServeTLS(ln, "", ""); err != nil {
|
if err := hs.ServeTLS(ln, "", ""); err != nil {
|
||||||
log.Fatalf("runAPIServerProxy: failed to serve %v", err)
|
log.Fatalf("runAPIServerProxy: failed to serve %v", err)
|
||||||
}
|
}
|
||||||
@ -235,7 +235,8 @@ type impersonateRule struct {
|
|||||||
// addImpersonationHeaders adds the appropriate headers to r to impersonate the
|
// addImpersonationHeaders adds the appropriate headers to r to impersonate the
|
||||||
// caller when proxying to the Kubernetes API. It uses the WhoIsResponse stashed
|
// caller when proxying to the Kubernetes API. It uses the WhoIsResponse stashed
|
||||||
// in the context by the apiserverProxy.
|
// in the context by the apiserverProxy.
|
||||||
func addImpersonationHeaders(r *http.Request) error {
|
func addImpersonationHeaders(r *http.Request, log *zap.SugaredLogger) error {
|
||||||
|
log = log.With("remote", r.RemoteAddr)
|
||||||
who := whoIsFromRequest(r)
|
who := whoIsFromRequest(r)
|
||||||
rules, err := tailcfg.UnmarshalCapJSON[capRule](who.CapMap, capabilityName)
|
rules, err := tailcfg.UnmarshalCapJSON[capRule](who.CapMap, capabilityName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -253,21 +254,26 @@ func addImpersonationHeaders(r *http.Request) error {
|
|||||||
}
|
}
|
||||||
r.Header.Add("Impersonate-Group", group)
|
r.Header.Add("Impersonate-Group", group)
|
||||||
groupsAdded.Add(group)
|
groupsAdded.Add(group)
|
||||||
|
log.Debugf("adding group impersonation header for user group %s", group)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !who.Node.IsTagged() {
|
if !who.Node.IsTagged() {
|
||||||
r.Header.Set("Impersonate-User", who.UserProfile.LoginName)
|
r.Header.Set("Impersonate-User", who.UserProfile.LoginName)
|
||||||
|
log.Debugf("adding user impersonation header for user %s", who.UserProfile.LoginName)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// "Impersonate-Group" requires "Impersonate-User" to be set, so we set it
|
// "Impersonate-Group" requires "Impersonate-User" to be set, so we set it
|
||||||
// to the node FQDN for tagged nodes.
|
// to the node FQDN for tagged nodes.
|
||||||
r.Header.Set("Impersonate-User", strings.TrimSuffix(who.Node.Name, "."))
|
nodeName := strings.TrimSuffix(who.Node.Name, ".")
|
||||||
|
r.Header.Set("Impersonate-User", nodeName)
|
||||||
|
log.Debugf("adding user impersonation header for node name %s", nodeName)
|
||||||
|
|
||||||
// For legacy behavior (before caps), set the groups to the nodes tags.
|
// For legacy behavior (before caps), set the groups to the nodes tags.
|
||||||
if groupsAdded.Slice().Len() == 0 {
|
if groupsAdded.Slice().Len() == 0 {
|
||||||
for _, tag := range who.Node.Tags {
|
for _, tag := range who.Node.Tags {
|
||||||
r.Header.Add("Impersonate-Group", tag)
|
r.Header.Add("Impersonate-Group", tag)
|
||||||
|
log.Debugf("adding group impersonation header for node tag %s", tag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -10,12 +10,17 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"go.uber.org/zap"
|
||||||
"tailscale.com/client/tailscale/apitype"
|
"tailscale.com/client/tailscale/apitype"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
"tailscale.com/util/must"
|
"tailscale.com/util/must"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestImpersonationHeaders(t *testing.T) {
|
func TestImpersonationHeaders(t *testing.T) {
|
||||||
|
zl, err := zap.NewDevelopment()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
emailish string
|
emailish string
|
||||||
@ -100,7 +105,7 @@ func TestImpersonationHeaders(t *testing.T) {
|
|||||||
},
|
},
|
||||||
CapMap: tc.capMap,
|
CapMap: tc.capMap,
|
||||||
})
|
})
|
||||||
addImpersonationHeaders(r)
|
addImpersonationHeaders(r, zl.Sugar())
|
||||||
|
|
||||||
if d := cmp.Diff(tc.wantHeaders, r.Header); d != "" {
|
if d := cmp.Diff(tc.wantHeaders, r.Header); d != "" {
|
||||||
t.Errorf("unexpected header (-want +got):\n%s", d)
|
t.Errorf("unexpected header (-want +got):\n%s", d)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user