impersonator: prep work for future SA token support

Signed-off-by: Monis Khan <mok@vmware.com>
This commit is contained in:
Monis Khan 2021-03-12 10:33:30 -05:00
parent 12b13b1ea5
commit 8c0bafd5be
No known key found for this signature in database
GPG Key ID: 52C90ADA01B269B8
2 changed files with 34 additions and 12 deletions

View File

@ -17,6 +17,7 @@ import (
"k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apimachinery/pkg/util/errors" "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/authorization/authorizer" "k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/endpoints/request"
genericapiserver "k8s.io/apiserver/pkg/server" genericapiserver "k8s.io/apiserver/pkg/server"
@ -79,6 +80,8 @@ func newInternal( //nolint:funlen // yeah, it's kind of long.
// Wire up the impersonation proxy signer CA as another valid authenticator for client cert auth, // Wire up the impersonation proxy signer CA as another valid authenticator for client cert auth,
// along with the Kube API server's CA. // along with the Kube API server's CA.
// Note: any changes to the the Authentication stack need to be kept in sync with any assumptions made
// by getTransportForUser, especially if we ever update the TCR API to start returning bearer tokens.
kubeClient, err := kubeclient.New(clientOpts...) kubeClient, err := kubeclient.New(clientOpts...)
if err != nil { if err != nil {
return nil, err return nil, err
@ -241,12 +244,13 @@ func newImpersonationReverseProxyFunc(restConfig *rest.Config) (func(*genericapi
return return
} }
if len(userInfo.GetUID()) > 0 { rt, err := getTransportForUser(userInfo, kubeRoundTripper)
plog.Warning("rejecting request with UID since we cannot impersonate UIDs", if err != nil {
plog.WarningErr("rejecting request as we cannot act as the current user", err,
"url", r.URL.String(), "url", r.URL.String(),
"method", r.Method, "method", r.Method,
) )
http.Error(w, "unexpected uid", http.StatusUnprocessableEntity) http.Error(w, "unable to act as user", http.StatusUnprocessableEntity)
return return
} }
@ -257,15 +261,8 @@ func newImpersonationReverseProxyFunc(restConfig *rest.Config) (func(*genericapi
) )
reverseProxy := httputil.NewSingleHostReverseProxy(serverURL) reverseProxy := httputil.NewSingleHostReverseProxy(serverURL)
impersonateConfig := transport.ImpersonationConfig{ reverseProxy.Transport = rt
UserName: userInfo.GetName(),
Groups: userInfo.GetGroups(),
Extra: userInfo.GetExtra(),
}
reverseProxy.Transport = transport.NewImpersonatingRoundTripper(impersonateConfig, kubeRoundTripper)
reverseProxy.FlushInterval = 200 * time.Millisecond // the "watch" verb will not work without this line reverseProxy.FlushInterval = 200 * time.Millisecond // the "watch" verb will not work without this line
// transport.NewImpersonatingRoundTripper clones the request before setting headers
// so this call will not accidentally mutate the input request (see http.Handler docs)
reverseProxy.ServeHTTP(w, r) reverseProxy.ServeHTTP(w, r)
}) })
}, nil }, nil
@ -280,3 +277,28 @@ func ensureNoImpersonationHeaders(r *http.Request) error {
return nil return nil
} }
func getTransportForUser(userInfo user.Info, delegate http.RoundTripper) (http.RoundTripper, error) {
if len(userInfo.GetUID()) == 0 {
impersonateConfig := transport.ImpersonationConfig{
UserName: userInfo.GetName(),
Groups: userInfo.GetGroups(),
Extra: userInfo.GetExtra(),
}
// transport.NewImpersonatingRoundTripper clones the request before setting headers
// thus it will not accidentally mutate the input request (see http.Handler docs)
return transport.NewImpersonatingRoundTripper(impersonateConfig, delegate), nil
}
// 0. in the case of a request that is not attempting to do nested impersonation
// 1. if we make the assumption that the TCR API does not issue tokens (or pass the TCR API bearer token
// authenticator into this func - we need to know the authentication cred is something KAS would honor)
// 2. then if preserve the incoming authorization header into the request's context
// 3. we could reauthenticate it here (it would be a free cache hit)
// 4. confirm that it matches the passed in user info (i.e. it was actually the cred used to authenticate and not a client cert)
// 5. then we could issue a reverse proxy request using an anonymous rest config and the bearer token
// 6. thus instead of impersonating the user, we would just be passing their request through
// 7. this would preserve the UID info and thus allow us to safely support all token based auth
// 8. the above would be safe even if in the future Kube started supporting UIDs asserted by client certs
return nil, constable.Error("unexpected uid")
}

View File

@ -361,7 +361,7 @@ func TestImpersonatorHTTPHandler(t *testing.T) {
{ {
name: "unexpected UID", name: "unexpected UID",
request: newRequest(map[string][]string{}, &user.DefaultInfo{UID: "007"}), request: newRequest(map[string][]string{}, &user.DefaultInfo{UID: "007"}),
wantHTTPBody: "unexpected uid\n", wantHTTPBody: "unable to act as user\n",
wantHTTPStatus: http.StatusUnprocessableEntity, wantHTTPStatus: http.StatusUnprocessableEntity,
}, },
// happy path // happy path