2022-01-14 18:49:22 +00:00
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
2021-01-20 00:37:02 +00:00
// SPDX-License-Identifier: Apache-2.0
package impersonator
import (
2021-02-09 18:25:24 +00:00
"context"
2022-04-14 16:59:19 +00:00
"crypto/tls"
"crypto/x509"
2021-05-27 14:17:19 +00:00
"fmt"
2021-03-18 19:32:33 +00:00
"math/rand"
2021-03-10 18:30:06 +00:00
"net"
2021-01-20 00:37:02 +00:00
"net/http"
"net/http/httptest"
"net/url"
2021-03-10 18:30:06 +00:00
"strconv"
2021-06-11 18:03:18 +00:00
"sync"
2021-01-20 00:37:02 +00:00
"testing"
2021-03-10 18:30:06 +00:00
"time"
2021-01-20 00:37:02 +00:00
"github.com/stretchr/testify/require"
2021-04-09 21:52:53 +00:00
authenticationv1 "k8s.io/api/authentication/v1"
2021-03-16 16:59:07 +00:00
corev1 "k8s.io/api/core/v1"
2021-05-27 14:17:19 +00:00
"k8s.io/apimachinery/pkg/api/errors"
2021-03-12 00:27:16 +00:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2021-05-27 14:17:19 +00:00
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructuredscheme"
2021-03-14 01:25:23 +00:00
"k8s.io/apimachinery/pkg/runtime"
2021-05-27 14:17:19 +00:00
"k8s.io/apimachinery/pkg/runtime/schema"
2021-03-14 01:25:23 +00:00
"k8s.io/apimachinery/pkg/runtime/serializer"
2021-03-19 17:39:55 +00:00
"k8s.io/apimachinery/pkg/util/httpstream"
2021-04-09 21:52:53 +00:00
auditinternal "k8s.io/apiserver/pkg/apis/audit"
2021-12-10 22:22:36 +00:00
"k8s.io/apiserver/pkg/audit"
2021-04-20 15:19:58 +00:00
"k8s.io/apiserver/pkg/authentication/authenticator"
"k8s.io/apiserver/pkg/authentication/request/bearertoken"
2021-01-20 00:37:02 +00:00
"k8s.io/apiserver/pkg/authentication/user"
2021-06-11 18:03:18 +00:00
"k8s.io/apiserver/pkg/authorization/authorizer"
2021-03-10 18:30:06 +00:00
"k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/features"
2021-03-14 01:25:23 +00:00
genericapiserver "k8s.io/apiserver/pkg/server"
2021-03-10 18:30:06 +00:00
genericoptions "k8s.io/apiserver/pkg/server/options"
utilfeature "k8s.io/apiserver/pkg/util/feature"
2021-10-20 11:59:24 +00:00
"k8s.io/client-go/kubernetes"
2021-01-20 00:37:02 +00:00
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd/api"
2021-03-10 18:30:06 +00:00
featuregatetesting "k8s.io/component-base/featuregate/testing"
2021-05-27 14:17:19 +00:00
"k8s.io/utils/pointer"
2021-01-20 00:37:02 +00:00
2021-05-27 14:17:19 +00:00
loginv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/login/v1alpha1"
2021-03-10 18:30:06 +00:00
"go.pinniped.dev/internal/certauthority"
2021-04-20 15:19:58 +00:00
"go.pinniped.dev/internal/constable"
2021-10-20 11:59:24 +00:00
"go.pinniped.dev/internal/crypto/ptls"
2021-03-11 21:20:25 +00:00
"go.pinniped.dev/internal/dynamiccert"
2021-05-27 14:17:19 +00:00
"go.pinniped.dev/internal/groupsuffix"
2021-03-12 00:27:16 +00:00
"go.pinniped.dev/internal/here"
2021-03-16 16:59:07 +00:00
"go.pinniped.dev/internal/httputil/roundtripper"
2021-03-10 18:30:06 +00:00
"go.pinniped.dev/internal/kubeclient"
2021-10-20 11:59:24 +00:00
"go.pinniped.dev/internal/testutil/tlsserver"
2021-01-20 00:37:02 +00:00
)
2021-03-12 00:27:16 +00:00
func TestImpersonator ( t * testing . T ) {
2021-03-13 00:09:16 +00:00
ca , err := certauthority . New ( "ca" , time . Hour )
2021-03-10 18:30:06 +00:00
require . NoError ( t , err )
2021-03-11 21:20:25 +00:00
caKey , err := ca . PrivateKeyToPEM ( )
require . NoError ( t , err )
2021-03-15 16:24:07 +00:00
caContent := dynamiccert . NewCA ( "ca" )
2021-03-11 21:20:25 +00:00
err = caContent . SetCertKeyContent ( ca . Bundle ( ) , caKey )
2021-03-10 18:30:06 +00:00
require . NoError ( t , err )
2021-03-11 21:20:25 +00:00
2021-03-13 00:09:16 +00:00
cert , key , err := ca . IssueServerCertPEM ( nil , [ ] net . IP { net . ParseIP ( "127.0.0.1" ) } , time . Hour )
2021-03-10 18:30:06 +00:00
require . NoError ( t , err )
2021-03-15 16:24:07 +00:00
certKeyContent := dynamiccert . NewServingCert ( "cert-key" )
2021-03-11 21:20:25 +00:00
err = certKeyContent . SetCertKeyContent ( cert , key )
2021-03-10 18:30:06 +00:00
require . NoError ( t , err )
2021-03-13 00:09:16 +00:00
unrelatedCA , err := certauthority . New ( "ca" , time . Hour )
2021-03-12 01:11:38 +00:00
require . NoError ( t , err )
2021-04-09 21:52:53 +00:00
// turn off this code path for all tests because it does not handle the config we remove correctly
2021-03-10 18:30:06 +00:00
defer featuregatetesting . SetFeatureGateDuringTest ( t , utilfeature . DefaultFeatureGate , features . APIPriorityAndFairness , false ) ( )
tests := [ ] struct {
2021-03-12 00:27:16 +00:00
name string
2021-03-12 01:24:52 +00:00
clientCert * clientCert
2021-03-12 00:27:16 +00:00
clientImpersonateUser rest . ImpersonationConfig
2021-03-16 16:59:07 +00:00
clientMutateHeaders func ( http . Header )
2021-03-19 17:39:55 +00:00
clientNextProtos [ ] string
2021-03-12 00:27:16 +00:00
kubeAPIServerClientBearerTokenFile string
kubeAPIServerStatusCode int
2021-05-27 14:17:19 +00:00
kubeAPIServerHealthz http . Handler
anonymousAuthDisabled bool
2021-03-12 01:11:38 +00:00
wantKubeAPIServerRequestHeaders http . Header
2021-03-12 00:27:16 +00:00
wantError string
wantConstructionError string
2021-06-11 18:03:18 +00:00
wantAuthorizerAttributes [ ] authorizer . AttributesRecord
2021-03-10 18:30:06 +00:00
} {
{
2021-03-12 00:27:16 +00:00
name : "happy path" ,
2021-03-12 01:24:52 +00:00
clientCert : newClientCert ( t , ca , "test-username" , [ ] string { "test-group1" , "test-group2" } ) ,
2021-03-12 00:27:16 +00:00
kubeAPIServerClientBearerTokenFile : "required-to-be-set" ,
2021-03-12 01:11:38 +00:00
wantKubeAPIServerRequestHeaders : http . Header {
"Impersonate-User" : { "test-username" } ,
"Impersonate-Group" : { "test-group1" , "test-group2" , "system:authenticated" } ,
"Authorization" : { "Bearer some-service-account-token" } ,
"User-Agent" : { "test-agent" } ,
"Accept" : { "application/vnd.kubernetes.protobuf,application/json" } ,
"Accept-Encoding" : { "gzip" } ,
"X-Forwarded-For" : { "127.0.0.1" } ,
} ,
2021-06-11 18:03:18 +00:00
wantAuthorizerAttributes : [ ] authorizer . AttributesRecord {
{
User : & user . DefaultInfo { Name : "test-username" , UID : "" , Groups : [ ] string { "test-group1" , "test-group2" , "system:authenticated" } , Extra : nil } ,
Verb : "list" , Namespace : "" , APIGroup : "" , APIVersion : "v1" , Resource : "namespaces" , Subresource : "" , Name : "" , ResourceRequest : true , Path : "/api/v1/namespaces" ,
} ,
} ,
2021-03-12 00:27:16 +00:00
} ,
2021-05-27 14:17:19 +00:00
{
name : "happy path with forbidden healthz" ,
clientCert : newClientCert ( t , ca , "test-username" , [ ] string { "test-group1" , "test-group2" } ) ,
kubeAPIServerClientBearerTokenFile : "required-to-be-set" ,
kubeAPIServerHealthz : http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
w . WriteHeader ( http . StatusForbidden )
_ , _ = w . Write ( [ ] byte ( "no healthz for you" ) )
} ) ,
wantKubeAPIServerRequestHeaders : http . Header {
"Impersonate-User" : { "test-username" } ,
"Impersonate-Group" : { "test-group1" , "test-group2" , "system:authenticated" } ,
"Authorization" : { "Bearer some-service-account-token" } ,
"User-Agent" : { "test-agent" } ,
"Accept" : { "application/vnd.kubernetes.protobuf,application/json" } ,
"Accept-Encoding" : { "gzip" } ,
"X-Forwarded-For" : { "127.0.0.1" } ,
} ,
2021-06-11 18:03:18 +00:00
wantAuthorizerAttributes : [ ] authorizer . AttributesRecord {
{
User : & user . DefaultInfo { Name : "test-username" , UID : "" , Groups : [ ] string { "test-group1" , "test-group2" , "system:authenticated" } , Extra : nil } ,
Verb : "list" , Namespace : "" , APIGroup : "" , APIVersion : "v1" , Resource : "namespaces" , Subresource : "" , Name : "" , ResourceRequest : true , Path : "/api/v1/namespaces" ,
} ,
} ,
2021-05-27 14:17:19 +00:00
} ,
{
name : "happy path with unauthorized healthz" ,
clientCert : newClientCert ( t , ca , "test-username" , [ ] string { "test-group1" , "test-group2" } ) ,
kubeAPIServerClientBearerTokenFile : "required-to-be-set" ,
kubeAPIServerHealthz : http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
w . WriteHeader ( http . StatusUnauthorized )
_ , _ = w . Write ( [ ] byte ( "no healthz for you" ) )
} ) ,
anonymousAuthDisabled : true ,
wantKubeAPIServerRequestHeaders : http . Header {
"Impersonate-User" : { "test-username" } ,
"Impersonate-Group" : { "test-group1" , "test-group2" , "system:authenticated" } ,
"Authorization" : { "Bearer some-service-account-token" } ,
"User-Agent" : { "test-agent" } ,
"Accept" : { "application/vnd.kubernetes.protobuf,application/json" } ,
"Accept-Encoding" : { "gzip" } ,
"X-Forwarded-For" : { "127.0.0.1" } ,
} ,
2021-06-11 18:03:18 +00:00
wantAuthorizerAttributes : [ ] authorizer . AttributesRecord {
{
User : & user . DefaultInfo { Name : "test-username" , UID : "" , Groups : [ ] string { "test-group1" , "test-group2" , "system:authenticated" } , Extra : nil } ,
Verb : "list" , Namespace : "" , APIGroup : "" , APIVersion : "v1" , Resource : "namespaces" , Subresource : "" , Name : "" , ResourceRequest : true , Path : "/api/v1/namespaces" ,
} ,
} ,
2021-05-27 14:17:19 +00:00
} ,
2021-03-19 17:39:55 +00:00
{
name : "happy path with upgrade" ,
clientCert : newClientCert ( t , ca , "test-username2" , [ ] string { "test-group3" , "test-group4" } ) ,
kubeAPIServerClientBearerTokenFile : "required-to-be-set" ,
clientMutateHeaders : func ( header http . Header ) {
header . Add ( "Connection" , "Upgrade" )
header . Add ( "Upgrade" , "spdy/3.1" )
if ok := httpstream . IsUpgradeRequest ( & http . Request { Header : header } ) ; ! ok {
panic ( "request must be upgrade in this test" )
}
} ,
clientNextProtos : [ ] string { "http/1.1" } , // we need to use http1 as http2 does not support upgrades, see http2checkConnHeaders
wantKubeAPIServerRequestHeaders : http . Header {
"Impersonate-User" : { "test-username2" } ,
"Impersonate-Group" : { "test-group3" , "test-group4" , "system:authenticated" } ,
"Authorization" : { "Bearer some-service-account-token" } ,
"User-Agent" : { "test-agent" } ,
"Accept" : { "application/vnd.kubernetes.protobuf,application/json" } ,
"Accept-Encoding" : { "gzip" } ,
"X-Forwarded-For" : { "127.0.0.1" } ,
"Connection" : { "Upgrade" } ,
"Upgrade" : { "spdy/3.1" } ,
} ,
2021-06-11 18:03:18 +00:00
wantAuthorizerAttributes : [ ] authorizer . AttributesRecord {
{
User : & user . DefaultInfo { Name : "test-username2" , UID : "" , Groups : [ ] string { "test-group3" , "test-group4" , "system:authenticated" } , Extra : nil } ,
Verb : "list" , Namespace : "" , APIGroup : "" , APIVersion : "v1" , Resource : "namespaces" , Subresource : "" , Name : "" , ResourceRequest : true , Path : "/api/v1/namespaces" ,
} ,
} ,
2021-03-19 17:39:55 +00:00
} ,
2021-03-19 19:35:06 +00:00
{
name : "happy path ignores forwarded header" ,
clientCert : newClientCert ( t , ca , "test-username2" , [ ] string { "test-group3" , "test-group4" } ) ,
kubeAPIServerClientBearerTokenFile : "required-to-be-set" ,
clientMutateHeaders : func ( header http . Header ) {
header . Add ( "X-Forwarded-For" , "example.com" )
} ,
wantKubeAPIServerRequestHeaders : http . Header {
"Impersonate-User" : { "test-username2" } ,
"Impersonate-Group" : { "test-group3" , "test-group4" , "system:authenticated" } ,
"Authorization" : { "Bearer some-service-account-token" } ,
"User-Agent" : { "test-agent" } ,
"Accept" : { "application/vnd.kubernetes.protobuf,application/json" } ,
"Accept-Encoding" : { "gzip" } ,
"X-Forwarded-For" : { "127.0.0.1" } ,
} ,
2021-06-11 18:03:18 +00:00
wantAuthorizerAttributes : [ ] authorizer . AttributesRecord {
{
User : & user . DefaultInfo { Name : "test-username2" , UID : "" , Groups : [ ] string { "test-group3" , "test-group4" , "system:authenticated" } , Extra : nil } ,
Verb : "list" , Namespace : "" , APIGroup : "" , APIVersion : "v1" , Resource : "namespaces" , Subresource : "" , Name : "" , ResourceRequest : true , Path : "/api/v1/namespaces" ,
} ,
} ,
2021-03-19 19:35:06 +00:00
} ,
{
name : "happy path ignores forwarded header canonicalization" ,
clientCert : newClientCert ( t , ca , "test-username2" , [ ] string { "test-group3" , "test-group4" } ) ,
kubeAPIServerClientBearerTokenFile : "required-to-be-set" ,
clientMutateHeaders : func ( header http . Header ) {
2021-04-09 21:52:53 +00:00
header [ "x-FORWARDED-for" ] = append ( header [ "x-FORWARDED-for" ] , "example.com" )
2021-03-19 19:35:06 +00:00
} ,
wantKubeAPIServerRequestHeaders : http . Header {
"Impersonate-User" : { "test-username2" } ,
"Impersonate-Group" : { "test-group3" , "test-group4" , "system:authenticated" } ,
"Authorization" : { "Bearer some-service-account-token" } ,
"User-Agent" : { "test-agent" } ,
"Accept" : { "application/vnd.kubernetes.protobuf,application/json" } ,
"Accept-Encoding" : { "gzip" } ,
"X-Forwarded-For" : { "127.0.0.1" } ,
} ,
2021-06-11 18:03:18 +00:00
wantAuthorizerAttributes : [ ] authorizer . AttributesRecord {
{
User : & user . DefaultInfo { Name : "test-username2" , UID : "" , Groups : [ ] string { "test-group3" , "test-group4" , "system:authenticated" } , Extra : nil } ,
Verb : "list" , Namespace : "" , APIGroup : "" , APIVersion : "v1" , Resource : "namespaces" , Subresource : "" , Name : "" , ResourceRequest : true , Path : "/api/v1/namespaces" ,
} ,
} ,
2021-03-19 19:35:06 +00:00
} ,
2021-03-12 00:27:16 +00:00
{
name : "user is authenticated but the kube API request returns an error" ,
kubeAPIServerStatusCode : http . StatusNotFound ,
2021-03-12 01:24:52 +00:00
clientCert : newClientCert ( t , ca , "test-username" , [ ] string { "test-group1" , "test-group2" } ) ,
2021-03-12 00:27:16 +00:00
kubeAPIServerClientBearerTokenFile : "required-to-be-set" ,
wantError : ` the server could not find the requested resource (get namespaces) ` ,
2021-03-12 01:11:38 +00:00
wantKubeAPIServerRequestHeaders : http . Header {
"Impersonate-User" : { "test-username" } ,
"Impersonate-Group" : { "test-group1" , "test-group2" , "system:authenticated" } ,
"Authorization" : { "Bearer some-service-account-token" } ,
"User-Agent" : { "test-agent" } ,
"Accept" : { "application/vnd.kubernetes.protobuf,application/json" } ,
"Accept-Encoding" : { "gzip" } ,
"X-Forwarded-For" : { "127.0.0.1" } ,
} ,
2021-06-11 18:03:18 +00:00
wantAuthorizerAttributes : [ ] authorizer . AttributesRecord {
{
User : & user . DefaultInfo { Name : "test-username" , UID : "" , Groups : [ ] string { "test-group1" , "test-group2" , "system:authenticated" } , Extra : nil } ,
Verb : "list" , Namespace : "" , APIGroup : "" , APIVersion : "v1" , Resource : "namespaces" , Subresource : "" , Name : "" , ResourceRequest : true , Path : "/api/v1/namespaces" ,
} ,
} ,
2021-03-12 01:11:38 +00:00
} ,
{
name : "when there is no client cert on request, it is an anonymous request" ,
2021-03-12 01:24:52 +00:00
clientCert : & clientCert { } ,
2021-03-12 01:11:38 +00:00
kubeAPIServerClientBearerTokenFile : "required-to-be-set" ,
wantKubeAPIServerRequestHeaders : http . Header {
"Impersonate-User" : { "system:anonymous" } ,
"Impersonate-Group" : { "system:unauthenticated" } ,
"Authorization" : { "Bearer some-service-account-token" } ,
"User-Agent" : { "test-agent" } ,
"Accept" : { "application/vnd.kubernetes.protobuf,application/json" } ,
"Accept-Encoding" : { "gzip" } ,
"X-Forwarded-For" : { "127.0.0.1" } ,
} ,
2021-06-11 18:03:18 +00:00
wantAuthorizerAttributes : [ ] authorizer . AttributesRecord {
{
User : & user . DefaultInfo { Name : "system:anonymous" , UID : "" , Groups : [ ] string { "system:unauthenticated" } , Extra : nil } ,
Verb : "list" , Namespace : "" , APIGroup : "" , APIVersion : "v1" , Resource : "namespaces" , Subresource : "" , Name : "" , ResourceRequest : true , Path : "/api/v1/namespaces" ,
} ,
} ,
2021-03-12 01:11:38 +00:00
} ,
2021-04-20 15:19:58 +00:00
{
name : "when there is no client cert on request but it has basic auth, it is still an anonymous request" ,
clientCert : & clientCert { } ,
clientMutateHeaders : func ( header http . Header ) {
header . Set ( "Test" , "val" )
req := & http . Request { Header : header }
req . SetBasicAuth ( "foo" , "bar" )
} ,
kubeAPIServerClientBearerTokenFile : "required-to-be-set" ,
wantKubeAPIServerRequestHeaders : http . Header {
"Impersonate-User" : { "system:anonymous" } ,
"Impersonate-Group" : { "system:unauthenticated" } ,
"Authorization" : { "Bearer some-service-account-token" } ,
"User-Agent" : { "test-agent" } ,
"Accept" : { "application/vnd.kubernetes.protobuf,application/json" } ,
"Accept-Encoding" : { "gzip" } ,
"X-Forwarded-For" : { "127.0.0.1" } ,
"Test" : { "val" } ,
} ,
2021-06-11 18:03:18 +00:00
wantAuthorizerAttributes : [ ] authorizer . AttributesRecord {
{
User : & user . DefaultInfo { Name : "system:anonymous" , UID : "" , Groups : [ ] string { "system:unauthenticated" } , Extra : nil } ,
Verb : "list" , Namespace : "" , APIGroup : "" , APIVersion : "v1" , Resource : "namespaces" , Subresource : "" , Name : "" , ResourceRequest : true , Path : "/api/v1/namespaces" ,
} ,
} ,
2021-04-20 15:19:58 +00:00
} ,
2021-03-12 01:11:38 +00:00
{
name : "failed client cert authentication" ,
2021-03-12 01:24:52 +00:00
clientCert : newClientCert ( t , unrelatedCA , "test-username" , [ ] string { "test-group1" } ) ,
2021-03-12 01:11:38 +00:00
kubeAPIServerClientBearerTokenFile : "required-to-be-set" ,
wantError : "Unauthorized" ,
2021-06-11 18:03:18 +00:00
wantAuthorizerAttributes : nil ,
2021-03-12 00:27:16 +00:00
} ,
{
2021-04-09 21:52:53 +00:00
name : "nested impersonation by regular users calls delegating authorizer" ,
2021-03-12 01:24:52 +00:00
clientCert : newClientCert ( t , ca , "test-username" , [ ] string { "test-group1" , "test-group2" } ) ,
2021-03-12 00:27:16 +00:00
clientImpersonateUser : rest . ImpersonationConfig { UserName : "some-other-username" } ,
kubeAPIServerClientBearerTokenFile : "required-to-be-set" ,
2021-04-09 21:52:53 +00:00
// this fails because the delegating authorizer in this test only allows system:masters and fails everything else
2021-03-12 00:27:16 +00:00
wantError : ` users "some-other-username" is forbidden: User "test-username" ` +
2021-06-11 18:03:18 +00:00
` cannot impersonate resource "users" in API group "" at the cluster scope: ` +
` decision made by impersonation-proxy.concierge.pinniped.dev ` ,
wantAuthorizerAttributes : [ ] authorizer . AttributesRecord {
{
User : & user . DefaultInfo { Name : "test-username" , UID : "" , Groups : [ ] string { "test-group1" , "test-group2" , "system:authenticated" } , Extra : nil } ,
Verb : "impersonate" , Namespace : "" , APIGroup : "" , APIVersion : "" , Resource : "users" , Subresource : "" , Name : "some-other-username" , ResourceRequest : true , Path : "" ,
} ,
} ,
2021-03-10 18:30:06 +00:00
} ,
{
2021-04-09 21:52:53 +00:00
name : "nested impersonation by admin users calls delegating authorizer" ,
clientCert : newClientCert ( t , ca , "test-admin" , [ ] string { "system:masters" , "test-group2" } ) ,
clientImpersonateUser : rest . ImpersonationConfig {
UserName : "fire" ,
Groups : [ ] string { "elements" } ,
Extra : map [ string ] [ ] string {
"colors" : { "red" , "orange" , "blue" } ,
// gke
"iam.gke.io/user-assertion" : { "good" , "stuff" } ,
"user-assertion.cloud.google.com" : { "smaller" , "things" } ,
// openshift
"scopes.authorization.openshift.io" : { "user:info" , "user:full" , "user:check-access" } ,
// openstack
"alpha.kubernetes.io/identity/roles" : { "a-role1" , "a-role2" } ,
"alpha.kubernetes.io/identity/project/id" : { "a-project-id" } ,
"alpha.kubernetes.io/identity/project/name" : { "a-project-name" } ,
"alpha.kubernetes.io/identity/user/domain/id" : { "a-domain-id" } ,
"alpha.kubernetes.io/identity/user/domain/name" : { "a-domain-name" } ,
} ,
} ,
kubeAPIServerClientBearerTokenFile : "required-to-be-set" ,
wantKubeAPIServerRequestHeaders : http . Header {
"Impersonate-User" : { "fire" } ,
"Impersonate-Group" : { "elements" , "system:authenticated" } ,
"Impersonate-Extra-Colors" : { "red" , "orange" , "blue" } ,
"Impersonate-Extra-Iam.gke.io%2fuser-Assertion" : { "good" , "stuff" } ,
"Impersonate-Extra-User-Assertion.cloud.google.com" : { "smaller" , "things" } ,
"Impersonate-Extra-Scopes.authorization.openshift.io" : { "user:info" , "user:full" , "user:check-access" } ,
"Impersonate-Extra-Alpha.kubernetes.io%2fidentity%2froles" : { "a-role1" , "a-role2" } ,
"Impersonate-Extra-Alpha.kubernetes.io%2fidentity%2fproject%2fid" : { "a-project-id" } ,
"Impersonate-Extra-Alpha.kubernetes.io%2fidentity%2fproject%2fname" : { "a-project-name" } ,
"Impersonate-Extra-Alpha.kubernetes.io%2fidentity%2fuser%2fdomain%2fid" : { "a-domain-id" } ,
"Impersonate-Extra-Alpha.kubernetes.io%2fidentity%2fuser%2fdomain%2fname" : { "a-domain-name" } ,
"Impersonate-Extra-Original-User-Info.impersonation-Proxy.concierge.pinniped.dev" : { ` { "username":"test-admin","groups":["test-group2","system:masters","system:authenticated"]} ` } ,
"Authorization" : { "Bearer some-service-account-token" } ,
"User-Agent" : { "test-agent" } ,
"Accept" : { "application/vnd.kubernetes.protobuf,application/json" } ,
"Accept-Encoding" : { "gzip" } ,
"X-Forwarded-For" : { "127.0.0.1" } ,
} ,
2021-06-11 18:03:18 +00:00
wantAuthorizerAttributes : [ ] authorizer . AttributesRecord {
{
User : & user . DefaultInfo { Name : "test-admin" , UID : "" , Groups : [ ] string { "test-group2" , "system:masters" , "system:authenticated" } , Extra : nil } ,
Verb : "impersonate" , Namespace : "" , APIGroup : "" , APIVersion : "" , Resource : "users" , Subresource : "" , Name : "fire" , ResourceRequest : true , Path : "" ,
} ,
{
User : & user . DefaultInfo { Name : "test-admin" , UID : "" , Groups : [ ] string { "test-group2" , "system:masters" , "system:authenticated" } , Extra : nil } ,
Verb : "impersonate" , Namespace : "" , APIGroup : "" , APIVersion : "" , Resource : "groups" , Subresource : "" , Name : "elements" , ResourceRequest : true , Path : "" ,
} ,
{
User : & user . DefaultInfo { Name : "test-admin" , UID : "" , Groups : [ ] string { "test-group2" , "system:masters" , "system:authenticated" } , Extra : nil } ,
Verb : "impersonate" , Namespace : "" , APIGroup : "authentication.k8s.io" , APIVersion : "v1" , Resource : "userextras" , Subresource : "iam.gke.io/user-assertion" , Name : "good" , ResourceRequest : true , Path : "" ,
} ,
{
User : & user . DefaultInfo { Name : "test-admin" , UID : "" , Groups : [ ] string { "test-group2" , "system:masters" , "system:authenticated" } , Extra : nil } ,
Verb : "impersonate" , Namespace : "" , APIGroup : "authentication.k8s.io" , APIVersion : "v1" , Resource : "userextras" , Subresource : "iam.gke.io/user-assertion" , Name : "stuff" , ResourceRequest : true , Path : "" ,
} ,
{
User : & user . DefaultInfo { Name : "test-admin" , UID : "" , Groups : [ ] string { "test-group2" , "system:masters" , "system:authenticated" } , Extra : nil } ,
Verb : "impersonate" , Namespace : "" , APIGroup : "authentication.k8s.io" , APIVersion : "v1" , Resource : "userextras" , Subresource : "alpha.kubernetes.io/identity/roles" , Name : "a-role1" , ResourceRequest : true , Path : "" ,
} ,
{
User : & user . DefaultInfo { Name : "test-admin" , UID : "" , Groups : [ ] string { "test-group2" , "system:masters" , "system:authenticated" } , Extra : nil } ,
Verb : "impersonate" , Namespace : "" , APIGroup : "authentication.k8s.io" , APIVersion : "v1" , Resource : "userextras" , Subresource : "alpha.kubernetes.io/identity/roles" , Name : "a-role2" , ResourceRequest : true , Path : "" ,
} ,
{
User : & user . DefaultInfo { Name : "test-admin" , UID : "" , Groups : [ ] string { "test-group2" , "system:masters" , "system:authenticated" } , Extra : nil } ,
Verb : "impersonate" , Namespace : "" , APIGroup : "authentication.k8s.io" , APIVersion : "v1" , Resource : "userextras" , Subresource : "user-assertion.cloud.google.com" , Name : "smaller" , ResourceRequest : true , Path : "" ,
} ,
{
User : & user . DefaultInfo { Name : "test-admin" , UID : "" , Groups : [ ] string { "test-group2" , "system:masters" , "system:authenticated" } , Extra : nil } ,
Verb : "impersonate" , Namespace : "" , APIGroup : "authentication.k8s.io" , APIVersion : "v1" , Resource : "userextras" , Subresource : "user-assertion.cloud.google.com" , Name : "things" , ResourceRequest : true , Path : "" ,
} ,
{
User : & user . DefaultInfo { Name : "test-admin" , UID : "" , Groups : [ ] string { "test-group2" , "system:masters" , "system:authenticated" } , Extra : nil } ,
Verb : "impersonate" , Namespace : "" , APIGroup : "authentication.k8s.io" , APIVersion : "v1" , Resource : "userextras" , Subresource : "colors" , Name : "red" , ResourceRequest : true , Path : "" ,
} ,
{
User : & user . DefaultInfo { Name : "test-admin" , UID : "" , Groups : [ ] string { "test-group2" , "system:masters" , "system:authenticated" } , Extra : nil } ,
Verb : "impersonate" , Namespace : "" , APIGroup : "authentication.k8s.io" , APIVersion : "v1" , Resource : "userextras" , Subresource : "colors" , Name : "orange" , ResourceRequest : true , Path : "" ,
} ,
{
User : & user . DefaultInfo { Name : "test-admin" , UID : "" , Groups : [ ] string { "test-group2" , "system:masters" , "system:authenticated" } , Extra : nil } ,
Verb : "impersonate" , Namespace : "" , APIGroup : "authentication.k8s.io" , APIVersion : "v1" , Resource : "userextras" , Subresource : "colors" , Name : "blue" , ResourceRequest : true , Path : "" ,
} ,
{
User : & user . DefaultInfo { Name : "test-admin" , UID : "" , Groups : [ ] string { "test-group2" , "system:masters" , "system:authenticated" } , Extra : nil } ,
Verb : "impersonate" , Namespace : "" , APIGroup : "authentication.k8s.io" , APIVersion : "v1" , Resource : "userextras" , Subresource : "scopes.authorization.openshift.io" , Name : "user:info" , ResourceRequest : true , Path : "" ,
} ,
{
User : & user . DefaultInfo { Name : "test-admin" , UID : "" , Groups : [ ] string { "test-group2" , "system:masters" , "system:authenticated" } , Extra : nil } ,
Verb : "impersonate" , Namespace : "" , APIGroup : "authentication.k8s.io" , APIVersion : "v1" , Resource : "userextras" , Subresource : "scopes.authorization.openshift.io" , Name : "user:full" , ResourceRequest : true , Path : "" ,
} ,
{
User : & user . DefaultInfo { Name : "test-admin" , UID : "" , Groups : [ ] string { "test-group2" , "system:masters" , "system:authenticated" } , Extra : nil } ,
Verb : "impersonate" , Namespace : "" , APIGroup : "authentication.k8s.io" , APIVersion : "v1" , Resource : "userextras" , Subresource : "scopes.authorization.openshift.io" , Name : "user:check-access" , ResourceRequest : true , Path : "" ,
} ,
{
User : & user . DefaultInfo { Name : "test-admin" , UID : "" , Groups : [ ] string { "test-group2" , "system:masters" , "system:authenticated" } , Extra : nil } ,
Verb : "impersonate" , Namespace : "" , APIGroup : "authentication.k8s.io" , APIVersion : "v1" , Resource : "userextras" , Subresource : "alpha.kubernetes.io/identity/project/name" , Name : "a-project-name" , ResourceRequest : true , Path : "" ,
} ,
{
User : & user . DefaultInfo { Name : "test-admin" , UID : "" , Groups : [ ] string { "test-group2" , "system:masters" , "system:authenticated" } , Extra : nil } ,
Verb : "impersonate" , Namespace : "" , APIGroup : "authentication.k8s.io" , APIVersion : "v1" , Resource : "userextras" , Subresource : "alpha.kubernetes.io/identity/user/domain/id" , Name : "a-domain-id" , ResourceRequest : true , Path : "" ,
} ,
{
User : & user . DefaultInfo { Name : "test-admin" , UID : "" , Groups : [ ] string { "test-group2" , "system:masters" , "system:authenticated" } , Extra : nil } ,
Verb : "impersonate" , Namespace : "" , APIGroup : "authentication.k8s.io" , APIVersion : "v1" , Resource : "userextras" , Subresource : "alpha.kubernetes.io/identity/user/domain/name" , Name : "a-domain-name" , ResourceRequest : true , Path : "" ,
} ,
{
User : & user . DefaultInfo { Name : "test-admin" , UID : "" , Groups : [ ] string { "test-group2" , "system:masters" , "system:authenticated" } , Extra : nil } ,
Verb : "impersonate" , Namespace : "" , APIGroup : "authentication.k8s.io" , APIVersion : "v1" , Resource : "userextras" , Subresource : "alpha.kubernetes.io/identity/project/id" , Name : "a-project-id" , ResourceRequest : true , Path : "" ,
} ,
{
User : & user . DefaultInfo { Name : "fire" , UID : "" , Groups : [ ] string { "elements" , "system:authenticated" } ,
Extra : map [ string ] [ ] string {
"alpha.kubernetes.io/identity/project/id" : { "a-project-id" } ,
"alpha.kubernetes.io/identity/project/name" : { "a-project-name" } ,
"alpha.kubernetes.io/identity/roles" : { "a-role1" , "a-role2" } ,
"alpha.kubernetes.io/identity/user/domain/id" : { "a-domain-id" } ,
"alpha.kubernetes.io/identity/user/domain/name" : { "a-domain-name" } ,
"colors" : { "red" , "orange" , "blue" } ,
"iam.gke.io/user-assertion" : { "good" , "stuff" } ,
"scopes.authorization.openshift.io" : { "user:info" , "user:full" , "user:check-access" } ,
"user-assertion.cloud.google.com" : { "smaller" , "things" } ,
} ,
} ,
Verb : "list" , Namespace : "" , APIGroup : "" , APIVersion : "v1" , Resource : "namespaces" , Subresource : "" , Name : "" , ResourceRequest : true , Path : "/api/v1/namespaces" ,
} ,
} ,
2021-04-09 21:52:53 +00:00
} ,
{
name : "nested impersonation by admin users cannot impersonate UID" ,
clientCert : newClientCert ( t , ca , "test-admin" , [ ] string { "system:masters" , "test-group2" } ) ,
clientImpersonateUser : rest . ImpersonationConfig { UserName : "some-other-username" } ,
clientMutateHeaders : func ( header http . Header ) {
header [ "Impersonate-Uid" ] = [ ] string { "root" }
} ,
2021-03-12 00:27:16 +00:00
kubeAPIServerClientBearerTokenFile : "required-to-be-set" ,
2021-08-09 23:11:32 +00:00
wantError : "Internal error occurred: unimplemented functionality - unable to act as current user" ,
2021-06-11 18:03:18 +00:00
wantAuthorizerAttributes : [ ] authorizer . AttributesRecord {
{
User : & user . DefaultInfo { Name : "test-admin" , UID : "" , Groups : [ ] string { "test-group2" , "system:masters" , "system:authenticated" } , Extra : nil } ,
Verb : "impersonate" , Namespace : "" , APIGroup : "" , APIVersion : "" , Resource : "users" , Subresource : "" , Name : "some-other-username" , ResourceRequest : true , Path : "" ,
} ,
{
2021-08-09 23:11:32 +00:00
User : & user . DefaultInfo { Name : "test-admin" , UID : "" , Groups : [ ] string { "test-group2" , "system:masters" , "system:authenticated" } , Extra : nil } ,
Verb : "impersonate" , Namespace : "" , APIGroup : "authentication.k8s.io" , APIVersion : "v1" , Resource : "uids" , Subresource : "" , Name : "root" , ResourceRequest : true , Path : "" ,
} ,
{
User : & user . DefaultInfo { Name : "some-other-username" , UID : "root" , Groups : [ ] string { "system:authenticated" } , Extra : map [ string ] [ ] string { } } ,
2021-06-11 18:03:18 +00:00
Verb : "list" , Namespace : "" , APIGroup : "" , APIVersion : "v1" , Resource : "namespaces" , Subresource : "" , Name : "" , ResourceRequest : true , Path : "/api/v1/namespaces" ,
} ,
} ,
2021-04-09 21:52:53 +00:00
} ,
{
name : "nested impersonation by admin users cannot impersonate UID header canonicalization" ,
clientCert : newClientCert ( t , ca , "test-admin" , [ ] string { "system:masters" , "test-group2" } ) ,
clientImpersonateUser : rest . ImpersonationConfig { UserName : "some-other-username" } ,
clientMutateHeaders : func ( header http . Header ) {
header [ "imPerSoNaTE-uid" ] = [ ] string { "magic" }
} ,
kubeAPIServerClientBearerTokenFile : "required-to-be-set" ,
2021-08-09 23:11:32 +00:00
wantError : "Internal error occurred: unimplemented functionality - unable to act as current user" ,
2021-06-11 18:03:18 +00:00
wantAuthorizerAttributes : [ ] authorizer . AttributesRecord {
{
User : & user . DefaultInfo { Name : "test-admin" , UID : "" , Groups : [ ] string { "test-group2" , "system:masters" , "system:authenticated" } , Extra : nil } ,
Verb : "impersonate" , Namespace : "" , APIGroup : "" , APIVersion : "" , Resource : "users" , Subresource : "" , Name : "some-other-username" , ResourceRequest : true , Path : "" ,
} ,
{
2021-08-09 23:11:32 +00:00
User : & user . DefaultInfo { Name : "test-admin" , UID : "" , Groups : [ ] string { "test-group2" , "system:masters" , "system:authenticated" } , Extra : nil } ,
Verb : "impersonate" , Namespace : "" , APIGroup : "authentication.k8s.io" , APIVersion : "v1" , Resource : "uids" , Subresource : "" , Name : "magic" , ResourceRequest : true , Path : "" ,
} ,
{
User : & user . DefaultInfo { Name : "some-other-username" , UID : "magic" , Groups : [ ] string { "system:authenticated" } , Extra : map [ string ] [ ] string { } } ,
2021-06-11 18:03:18 +00:00
Verb : "list" , Namespace : "" , APIGroup : "" , APIVersion : "v1" , Resource : "namespaces" , Subresource : "" , Name : "" , ResourceRequest : true , Path : "/api/v1/namespaces" ,
} ,
} ,
2021-04-09 21:52:53 +00:00
} ,
{
name : "nested impersonation by admin users cannot use reserved key" ,
clientCert : newClientCert ( t , ca , "test-admin" , [ ] string { "system:masters" , "test-group2" } ) ,
clientImpersonateUser : rest . ImpersonationConfig {
UserName : "other-user-to-impersonate" ,
Groups : [ ] string { "other-peeps" } ,
Extra : map [ string ] [ ] string {
"key" : { "good" } ,
"something.impersonation-proxy.concierge.pinniped.dev" : { "bad data" } ,
} ,
} ,
kubeAPIServerClientBearerTokenFile : "required-to-be-set" ,
wantError : "Internal error occurred: unimplemented functionality - unable to act as current user" ,
2021-06-11 18:03:18 +00:00
wantAuthorizerAttributes : [ ] authorizer . AttributesRecord {
{
User : & user . DefaultInfo { Name : "test-admin" , UID : "" , Groups : [ ] string { "test-group2" , "system:masters" , "system:authenticated" } , Extra : nil } ,
Verb : "impersonate" , Namespace : "" , APIGroup : "" , APIVersion : "" , Resource : "users" , Subresource : "" , Name : "other-user-to-impersonate" , ResourceRequest : true , Path : "" ,
} ,
{
User : & user . DefaultInfo { Name : "test-admin" , UID : "" , Groups : [ ] string { "test-group2" , "system:masters" , "system:authenticated" } , Extra : nil } ,
Verb : "impersonate" , Namespace : "" , APIGroup : "" , APIVersion : "" , Resource : "groups" , Subresource : "" , Name : "other-peeps" , ResourceRequest : true , Path : "" ,
} ,
{
User : & user . DefaultInfo { Name : "test-admin" , UID : "" , Groups : [ ] string { "test-group2" , "system:masters" , "system:authenticated" } , Extra : nil } ,
Verb : "impersonate" , Namespace : "" , APIGroup : "authentication.k8s.io" , APIVersion : "v1" , Resource : "userextras" , Subresource : "something.impersonation-proxy.concierge.pinniped.dev" , Name : "bad data" , ResourceRequest : true , Path : "" ,
} ,
{
User : & user . DefaultInfo { Name : "test-admin" , UID : "" , Groups : [ ] string { "test-group2" , "system:masters" , "system:authenticated" } , Extra : nil } ,
Verb : "impersonate" , Namespace : "" , APIGroup : "authentication.k8s.io" , APIVersion : "v1" , Resource : "userextras" , Subresource : "key" , Name : "good" , ResourceRequest : true , Path : "" ,
} ,
{
User : & user . DefaultInfo { Name : "other-user-to-impersonate" , UID : "" , Groups : [ ] string { "other-peeps" , "system:authenticated" } ,
Extra : map [ string ] [ ] string {
"key" : { "good" } ,
"something.impersonation-proxy.concierge.pinniped.dev" : { "bad data" } ,
} ,
} ,
Verb : "list" , Namespace : "" , APIGroup : "" , APIVersion : "v1" , Resource : "namespaces" , Subresource : "" , Name : "" , ResourceRequest : true , Path : "/api/v1/namespaces" ,
} ,
} ,
2021-04-09 21:52:53 +00:00
} ,
{
name : "nested impersonation by admin users cannot use invalid key" ,
clientCert : newClientCert ( t , ca , "test-admin" , [ ] string { "system:masters" , "test-group2" } ) ,
clientImpersonateUser : rest . ImpersonationConfig {
UserName : "panda" ,
Groups : [ ] string { "other-peeps" } ,
Extra : map [ string ] [ ] string {
"party~~time" : { "danger" } ,
} ,
} ,
kubeAPIServerClientBearerTokenFile : "required-to-be-set" ,
wantError : "Internal error occurred: unimplemented functionality - unable to act as current user" ,
2021-06-11 18:03:18 +00:00
wantAuthorizerAttributes : [ ] authorizer . AttributesRecord {
{
User : & user . DefaultInfo { Name : "test-admin" , UID : "" , Groups : [ ] string { "test-group2" , "system:masters" , "system:authenticated" } , Extra : nil } ,
Verb : "impersonate" , Namespace : "" , APIGroup : "" , APIVersion : "" , Resource : "users" , Subresource : "" , Name : "panda" , ResourceRequest : true , Path : "" ,
} ,
{
User : & user . DefaultInfo { Name : "test-admin" , UID : "" , Groups : [ ] string { "test-group2" , "system:masters" , "system:authenticated" } , Extra : nil } ,
Verb : "impersonate" , Namespace : "" , APIGroup : "" , APIVersion : "" , Resource : "groups" , Subresource : "" , Name : "other-peeps" , ResourceRequest : true , Path : "" ,
} ,
{
User : & user . DefaultInfo { Name : "test-admin" , UID : "" , Groups : [ ] string { "test-group2" , "system:masters" , "system:authenticated" } , Extra : nil } ,
Verb : "impersonate" , Namespace : "" , APIGroup : "authentication.k8s.io" , APIVersion : "v1" , Resource : "userextras" , Subresource : "party~~time" , Name : "danger" , ResourceRequest : true , Path : "" ,
} ,
{
User : & user . DefaultInfo { Name : "panda" , UID : "" , Groups : [ ] string { "other-peeps" , "system:authenticated" } , Extra : map [ string ] [ ] string { "party~~time" : { "danger" } } } ,
Verb : "list" , Namespace : "" , APIGroup : "" , APIVersion : "v1" , Resource : "namespaces" , Subresource : "" , Name : "" , ResourceRequest : true , Path : "/api/v1/namespaces" ,
} ,
} ,
2021-04-09 21:52:53 +00:00
} ,
{
name : "nested impersonation by admin users can use uppercase key because impersonation is lossy" ,
clientCert : newClientCert ( t , ca , "test-admin" , [ ] string { "system:masters" , "test-group2" } ) ,
clientImpersonateUser : rest . ImpersonationConfig {
UserName : "panda" ,
Groups : [ ] string { "other-peeps" } ,
Extra : map [ string ] [ ] string {
"ROAR" : { "tiger" } , // by the time our code sees this key, it is lowercased to "roar"
} ,
} ,
kubeAPIServerClientBearerTokenFile : "required-to-be-set" ,
wantKubeAPIServerRequestHeaders : http . Header {
"Impersonate-User" : { "panda" } ,
"Impersonate-Group" : { "other-peeps" , "system:authenticated" } ,
"Impersonate-Extra-Roar" : { "tiger" } ,
"Impersonate-Extra-Original-User-Info.impersonation-Proxy.concierge.pinniped.dev" : { ` { "username":"test-admin","groups":["test-group2","system:masters","system:authenticated"]} ` } ,
"Authorization" : { "Bearer some-service-account-token" } ,
"User-Agent" : { "test-agent" } ,
"Accept" : { "application/vnd.kubernetes.protobuf,application/json" } ,
"Accept-Encoding" : { "gzip" } ,
"X-Forwarded-For" : { "127.0.0.1" } ,
} ,
2021-06-11 18:03:18 +00:00
wantAuthorizerAttributes : [ ] authorizer . AttributesRecord {
{
User : & user . DefaultInfo { Name : "test-admin" , UID : "" , Groups : [ ] string { "test-group2" , "system:masters" , "system:authenticated" } , Extra : nil } ,
Verb : "impersonate" , Namespace : "" , APIGroup : "" , APIVersion : "" , Resource : "users" , Subresource : "" , Name : "panda" , ResourceRequest : true , Path : "" ,
} ,
{
User : & user . DefaultInfo { Name : "test-admin" , UID : "" , Groups : [ ] string { "test-group2" , "system:masters" , "system:authenticated" } , Extra : nil } ,
Verb : "impersonate" , Namespace : "" , APIGroup : "" , APIVersion : "" , Resource : "groups" , Subresource : "" , Name : "other-peeps" , ResourceRequest : true , Path : "" ,
} ,
{
User : & user . DefaultInfo { Name : "test-admin" , UID : "" , Groups : [ ] string { "test-group2" , "system:masters" , "system:authenticated" } , Extra : nil } ,
Verb : "impersonate" , Namespace : "" , APIGroup : "authentication.k8s.io" , APIVersion : "v1" , Resource : "userextras" , Subresource : "roar" , Name : "tiger" , ResourceRequest : true , Path : "" ,
} ,
{
User : & user . DefaultInfo { Name : "panda" , UID : "" , Groups : [ ] string { "other-peeps" , "system:authenticated" } , Extra : map [ string ] [ ] string { "roar" : { "tiger" } } } ,
Verb : "list" , Namespace : "" , APIGroup : "" , APIVersion : "v1" , Resource : "namespaces" , Subresource : "" , Name : "" , ResourceRequest : true , Path : "/api/v1/namespaces" ,
} ,
} ,
2021-03-10 18:30:06 +00:00
} ,
2021-03-12 01:11:38 +00:00
{
2021-06-11 18:03:18 +00:00
name : "no bearer token file in Kube API server client config" ,
wantConstructionError : "invalid impersonator loopback rest config has wrong bearer token semantics" ,
wantAuthorizerAttributes : nil ,
2021-03-12 01:11:38 +00:00
} ,
2021-05-27 14:17:19 +00:00
{
name : "unexpected healthz response" ,
kubeAPIServerHealthz : http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
w . WriteHeader ( http . StatusInternalServerError )
_ , _ = w . Write ( [ ] byte ( "broken" ) )
} ) ,
2021-06-11 18:03:18 +00:00
wantConstructionError : ` could not detect if anonymous authentication is enabled: an error on the server ("broken") has prevented the request from succeeding ` ,
wantAuthorizerAttributes : nil ,
2021-05-27 14:17:19 +00:00
} ,
2021-03-16 16:59:07 +00:00
{
name : "header canonicalization user header" ,
clientCert : newClientCert ( t , ca , "test-username" , [ ] string { "test-group1" , "test-group2" } ) ,
clientMutateHeaders : func ( header http . Header ) {
2021-04-09 21:52:53 +00:00
header [ "imPerSonaTE-USer" ] = [ ] string { "PANDA" }
2021-03-16 16:59:07 +00:00
} ,
kubeAPIServerClientBearerTokenFile : "required-to-be-set" ,
wantError : ` users "PANDA" is forbidden: User "test-username" ` +
2021-06-11 18:03:18 +00:00
` cannot impersonate resource "users" in API group "" at the cluster scope: ` +
` decision made by impersonation-proxy.concierge.pinniped.dev ` ,
wantAuthorizerAttributes : [ ] authorizer . AttributesRecord {
{
User : & user . DefaultInfo { Name : "test-username" , UID : "" , Groups : [ ] string { "test-group1" , "test-group2" , "system:authenticated" } , Extra : nil } ,
Verb : "impersonate" , Namespace : "" , APIGroup : "" , APIVersion : "" , Resource : "users" , Subresource : "" , Name : "PANDA" , ResourceRequest : true , Path : "" ,
} ,
} ,
2021-03-16 16:59:07 +00:00
} ,
{
2021-08-09 23:11:32 +00:00
name : "header canonicalization future UID header" , // no longer future as it exists in Kube v1.22
2021-03-16 16:59:07 +00:00
clientCert : newClientCert ( t , ca , "test-username" , [ ] string { "test-group1" , "test-group2" } ) ,
clientMutateHeaders : func ( header http . Header ) {
2021-04-09 21:52:53 +00:00
header [ "imPerSonaTE-uid" ] = [ ] string { "007" }
2021-03-16 16:59:07 +00:00
} ,
kubeAPIServerClientBearerTokenFile : "required-to-be-set" ,
2021-08-09 23:11:32 +00:00
wantError : ` an error on the server ("Internal Server Error: \"/api/v1/namespaces\": requested [ { UID 007 authentication.k8s.io/v1 }] without impersonating a user") has prevented the request from succeeding (get namespaces) ` ,
wantAuthorizerAttributes : [ ] authorizer . AttributesRecord { } ,
2021-03-16 16:59:07 +00:00
} ,
{
2021-08-09 23:11:32 +00:00
name : "future UID header" , // no longer future as it exists in Kube v1.22
2021-03-16 16:59:07 +00:00
clientCert : newClientCert ( t , ca , "test-username" , [ ] string { "test-group1" , "test-group2" } ) ,
clientMutateHeaders : func ( header http . Header ) {
2021-04-09 21:52:53 +00:00
header [ "Impersonate-Uid" ] = [ ] string { "008" }
2021-03-16 16:59:07 +00:00
} ,
kubeAPIServerClientBearerTokenFile : "required-to-be-set" ,
2021-08-09 23:11:32 +00:00
wantError : ` an error on the server ("Internal Server Error: \"/api/v1/namespaces\": requested [ { UID 008 authentication.k8s.io/v1 }] without impersonating a user") has prevented the request from succeeding (get namespaces) ` ,
wantAuthorizerAttributes : [ ] authorizer . AttributesRecord { } ,
2021-03-16 16:59:07 +00:00
} ,
2021-03-10 18:30:06 +00:00
}
for _ , tt := range tests {
tt := tt
t . Run ( tt . name , func ( t * testing . T ) {
2021-04-09 21:52:53 +00:00
t . Parallel ( )
2021-05-27 14:17:19 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , time . Minute )
t . Cleanup ( cancel )
2021-04-09 21:52:53 +00:00
// we need to create this listener ourselves because the API server
// code treats (port == 0 && listener == nil) to mean "do nothing"
listener , port , err := genericoptions . CreateListener ( "" , "127.0.0.1:0" , net . ListenConfig { } )
require . NoError ( t , err )
2021-03-16 16:59:07 +00:00
// After failing to start and after shutdown, the impersonator port should be available again.
defer requireCanBindToPort ( t , port )
2021-03-12 00:27:16 +00:00
if tt . kubeAPIServerStatusCode == 0 {
tt . kubeAPIServerStatusCode = http . StatusOK
}
2021-03-10 18:30:06 +00:00
2021-03-12 00:27:16 +00:00
// Set up a fake Kube API server which will stand in for the real one. The impersonator
// will proxy incoming calls to this fake server.
testKubeAPIServerWasCalled := false
2021-03-16 16:59:07 +00:00
var testKubeAPIServerSawHeaders http . Header
2021-10-20 11:59:24 +00:00
testKubeAPIServer := tlsserver . TLSTestServer ( t , http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
2022-04-14 16:59:19 +00:00
tlsConfigFunc := func ( rootCAs * x509 . CertPool ) * tls . Config {
// Requests to get configmaps, flowcontrol requests, and healthz requests
// are not done by our http round trippers that specify only one protocol
// (either http1.1 or http2, not both).
// For all other requests from the impersonator, if it is not an upgrade
// request it should only be using http2.
// If it is an upgrade request it should only be using http1.1, but that
// is covered by the AssertTLS function.
secure := ptls . Secure ( rootCAs )
switch r . URL . Path {
case "/api/v1/namespaces/kube-system/configmaps" ,
"/apis/flowcontrol.apiserver.k8s.io/v1beta2/prioritylevelconfigurations" ,
"/apis/flowcontrol.apiserver.k8s.io/v1beta2/flowschemas" ,
"/healthz" :
default :
if ! httpstream . IsUpgradeRequest ( r ) {
secure . NextProtos = [ ] string { secure . NextProtos [ 0 ] }
}
}
return secure
}
tlsserver . AssertTLS ( t , r , tlsConfigFunc )
2021-10-20 11:59:24 +00:00
2021-03-12 00:27:16 +00:00
switch r . URL . Path {
case "/api/v1/namespaces/kube-system/configmaps" :
2021-05-27 14:17:19 +00:00
require . Equal ( t , http . MethodGet , r . Method )
2021-03-12 00:27:16 +00:00
// The production code uses NewDynamicCAFromConfigMapController which fetches a ConfigMap,
// so treat that differently. It wants to read the Kube API server CA from that ConfigMap
// to use it to validate client certs. We don't need it for this test, so return NotFound.
http . NotFound ( w , r )
return
2021-05-27 14:17:19 +00:00
2021-12-10 22:22:36 +00:00
case "/apis/flowcontrol.apiserver.k8s.io/v1beta2/prioritylevelconfigurations" ,
"/apis/flowcontrol.apiserver.k8s.io/v1beta2/flowschemas" :
2021-10-20 11:59:24 +00:00
// ignore requests related to priority and fairness logic
require . Equal ( t , http . MethodGet , r . Method )
http . NotFound ( w , r )
return
2021-03-12 00:27:16 +00:00
case "/api/v1/namespaces" :
2021-05-27 14:17:19 +00:00
require . Equal ( t , http . MethodGet , r . Method )
2021-03-12 00:27:16 +00:00
testKubeAPIServerWasCalled = true
testKubeAPIServerSawHeaders = r . Header
if tt . kubeAPIServerStatusCode != http . StatusOK {
w . WriteHeader ( tt . kubeAPIServerStatusCode )
2021-05-27 14:17:19 +00:00
return
}
w . Header ( ) . Add ( "Content-Type" , "application/json; charset=UTF-8" )
_ , _ = w . Write ( [ ] byte ( here . Doc ( `
2021-03-12 00:27:16 +00:00
{
"kind" : "NamespaceList" ,
"apiVersion" : "v1" ,
"items" : [
{ "metadata" : { "name" : "namespace1" } } ,
{ "metadata" : { "name" : "namespace2" } }
]
}
` ) ) )
2021-05-27 14:17:19 +00:00
return
case "/probe" :
require . Equal ( t , http . MethodGet , r . Method )
_ , _ = fmt . Fprint ( w , "probed" )
return
case "/healthz" :
require . Equal ( t , http . MethodGet , r . Method )
require . Empty ( t , r . Header . Get ( "Authorization" ) )
require . Contains ( t , r . Header . Get ( "User-Agent" ) , "kubernetes" )
if tt . kubeAPIServerHealthz != nil {
tt . kubeAPIServerHealthz . ServeHTTP ( w , r )
return
2021-03-12 00:27:16 +00:00
}
2021-05-27 14:17:19 +00:00
// by default just match the KAS /healthz endpoint
w . Header ( ) . Set ( "Content-Type" , "text/plain; charset=utf-8" )
w . Header ( ) . Set ( "X-Content-Type-Options" , "nosniff" )
_ , _ = fmt . Fprint ( w , "ok" )
return
case "/apis/login.concierge.pinniped.dev/v1alpha1/tokencredentialrequests" :
require . Equal ( t , http . MethodPost , r . Method )
w . Header ( ) . Add ( "Content-Type" , "application/json; charset=UTF-8" )
_ , _ = w . Write ( [ ] byte ( ` { } ` ) )
return
case "/apis/login.concierge.walrus.tld/v1alpha1/tokencredentialrequests" :
require . Equal ( t , http . MethodPost , r . Method )
w . Header ( ) . Add ( "Content-Type" , "application/json; charset=UTF-8" )
_ , _ = w . Write ( [ ] byte ( ` { } ` ) )
return
case "/apis/not-concierge.walrus.tld/v1/tokencredentialrequests" :
require . Equal ( t , http . MethodGet , r . Method )
w . Header ( ) . Add ( "Content-Type" , "application/json; charset=UTF-8" )
_ , _ = w . Write ( [ ] byte ( ` { "hello": "quack"} ` ) )
return
case "/apis/not-concierge.walrus.tld/v1/ducks" :
require . Equal ( t , http . MethodGet , r . Method )
w . Header ( ) . Add ( "Content-Type" , "application/json; charset=UTF-8" )
_ , _ = w . Write ( [ ] byte ( ` { "hello": "birds"} ` ) )
return
2021-03-12 00:27:16 +00:00
default :
2021-05-27 14:17:19 +00:00
require . Fail ( t , "fake Kube API server got an unexpected request" , "path: %s" , r . URL . Path )
return
2021-03-10 18:30:06 +00:00
}
2021-10-20 11:59:24 +00:00
} ) , tlsserver . RecordTLSHello )
2021-03-12 00:44:08 +00:00
// Create the client config that the impersonation server should use to talk to the Kube API server.
2021-03-12 00:27:16 +00:00
testKubeAPIServerKubeconfig := rest . Config {
2021-10-20 11:59:24 +00:00
Host : testKubeAPIServer . URL ,
2021-03-12 00:27:16 +00:00
BearerToken : "some-service-account-token" ,
2021-10-20 11:59:24 +00:00
TLSClientConfig : rest . TLSClientConfig { CAData : tlsserver . TLSTestServerCA ( testKubeAPIServer ) } ,
2021-03-12 00:27:16 +00:00
BearerTokenFile : tt . kubeAPIServerClientBearerTokenFile ,
}
clientOpts := [ ] kubeclient . Option { kubeclient . WithConfig ( & testKubeAPIServerKubeconfig ) }
2021-03-10 18:30:06 +00:00
2021-04-09 21:52:53 +00:00
// Punch out just enough stuff to make New actually run without error.
recOpts := func ( options * genericoptions . RecommendedOptions ) {
options . Authentication . RemoteKubeConfigFileOptional = true
options . Authorization . RemoteKubeConfigFileOptional = true
options . Admission = nil
options . SecureServing . Listener = listener // use our listener with the dynamic port
}
2021-06-11 18:03:18 +00:00
recorder := & attributeRecorder { }
defer func ( ) {
require . ElementsMatch ( t , tt . wantAuthorizerAttributes , recorder . attributes )
require . Len ( t , recorder . attributes , len ( tt . wantAuthorizerAttributes ) )
} ( )
// Allow standard REST verbs to be authorized so that tests pass without invasive changes
recConfig := func ( config * genericapiserver . RecommendedConfig ) {
authz := config . Authorization . Authorizer . ( * comparableAuthorizer )
2021-10-20 11:59:24 +00:00
delegate := authz . AuthorizerFunc
authz . AuthorizerFunc = func ( ctx context . Context , a authorizer . Attributes ) ( authorizer . Decision , string , error ) {
2021-06-11 18:03:18 +00:00
recorder . record ( a )
switch a . GetVerb ( ) {
case "create" , "get" , "list" :
return authorizer . DecisionAllow , "standard verbs are allowed in tests" , nil
default :
return delegate ( ctx , a )
}
}
}
2021-10-20 11:59:24 +00:00
restConfigFunc := func ( config * rest . Config ) ( kubernetes . Interface , * rest . Config , error ) {
if config == nil {
config = & testKubeAPIServerKubeconfig
}
return kubeclient . Secure ( config )
}
2021-04-09 21:52:53 +00:00
// Create an impersonator. Use an invalid port number to make sure our listener override works.
2021-10-20 11:59:24 +00:00
runner , constructionErr := newInternal ( - 1000 , certKeyContent , caContent , restConfigFunc , clientOpts , recOpts , recConfig )
2021-03-12 00:27:16 +00:00
if len ( tt . wantConstructionError ) > 0 {
require . EqualError ( t , constructionErr , tt . wantConstructionError )
require . Nil ( t , runner )
// The rest of the test doesn't make sense when you expect a construction error, so stop here.
return
2021-03-10 18:30:06 +00:00
}
2021-03-12 00:27:16 +00:00
require . NoError ( t , constructionErr )
require . NotNil ( t , runner )
2021-03-10 18:30:06 +00:00
2021-03-12 00:27:16 +00:00
// Start the impersonator.
stopCh := make ( chan struct { } )
errCh := make ( chan error )
go func ( ) {
stopErr := runner ( stopCh )
errCh <- stopErr
2021-03-10 18:30:06 +00:00
} ( )
2021-03-12 00:27:16 +00:00
// Create a kubeconfig to talk to the impersonator as a client.
clientKubeconfig := & rest . Config {
Host : "https://127.0.0.1:" + strconv . Itoa ( port ) ,
TLSClientConfig : rest . TLSClientConfig {
2021-03-19 17:39:55 +00:00
CAData : ca . Bundle ( ) ,
CertData : tt . clientCert . certPEM ,
KeyData : tt . clientCert . keyPEM ,
NextProtos : tt . clientNextProtos ,
2021-03-12 00:27:16 +00:00
} ,
2021-03-12 00:44:08 +00:00
UserAgent : "test-agent" ,
2021-03-12 01:11:38 +00:00
// BearerToken should be ignored during auth when there are valid client certs,
2021-03-12 00:44:08 +00:00
// and it should not passed into the impersonator handler func as an authorization header.
BearerToken : "must-be-ignored" ,
2021-03-12 00:27:16 +00:00
Impersonate : tt . clientImpersonateUser ,
2021-03-16 16:59:07 +00:00
WrapTransport : func ( rt http . RoundTripper ) http . RoundTripper {
if tt . clientMutateHeaders == nil {
return rt
}
2021-10-20 11:59:24 +00:00
return roundtripper . WrapFunc ( rt , func ( req * http . Request ) ( * http . Response , error ) {
2021-03-16 16:59:07 +00:00
req = req . Clone ( req . Context ( ) )
tt . clientMutateHeaders ( req . Header )
return rt . RoundTrip ( req )
} )
} ,
2021-03-12 00:27:16 +00:00
}
// Create a real Kube client to make API requests to the impersonator.
client , err := kubeclient . New ( kubeclient . WithConfig ( clientKubeconfig ) )
require . NoError ( t , err )
// The fake Kube API server knows how to to list namespaces, so make that request using the client
// through the impersonator.
2021-05-27 14:17:19 +00:00
listResponse , err := client . Kubernetes . CoreV1 ( ) . Namespaces ( ) . List ( ctx , metav1 . ListOptions { } )
2021-03-12 00:27:16 +00:00
if len ( tt . wantError ) > 0 {
require . EqualError ( t , err , tt . wantError )
2021-03-16 16:59:07 +00:00
require . Equal ( t , & corev1 . NamespaceList { } , listResponse )
2021-03-12 00:27:16 +00:00
} else {
require . NoError ( t , err )
2021-03-16 16:59:07 +00:00
require . Equal ( t , & corev1 . NamespaceList {
Items : [ ] corev1 . Namespace {
2021-03-12 00:27:16 +00:00
{ ObjectMeta : metav1 . ObjectMeta { Name : "namespace1" } } ,
{ ObjectMeta : metav1 . ObjectMeta { Name : "namespace2" } } ,
} ,
} , listResponse )
}
2021-03-16 16:59:07 +00:00
// If we expect to see some headers, then the fake KAS should have been called.
require . Equal ( t , len ( tt . wantKubeAPIServerRequestHeaders ) != 0 , testKubeAPIServerWasCalled )
// If the impersonator proxied the request to the fake Kube API server, we should see the headers
// of the original request mutated by the impersonator. Otherwise the headers should be nil.
require . Equal ( t , tt . wantKubeAPIServerRequestHeaders , testKubeAPIServerSawHeaders )
2021-06-11 18:03:18 +00:00
// these authorization checks are caused by the anonymous auth checks below
tt . wantAuthorizerAttributes = append ( tt . wantAuthorizerAttributes ,
authorizer . AttributesRecord {
User : & user . DefaultInfo { Name : "system:anonymous" , UID : "" , Groups : [ ] string { "system:unauthenticated" } , Extra : nil } ,
Verb : "create" , Namespace : "" , APIGroup : "login.concierge.pinniped.dev" , APIVersion : "v1alpha1" , Resource : "tokencredentialrequests" , Subresource : "" , Name : "" , ResourceRequest : true , Path : "/apis/login.concierge.pinniped.dev/v1alpha1/tokencredentialrequests" ,
} ,
authorizer . AttributesRecord {
User : & user . DefaultInfo { Name : "system:anonymous" , UID : "" , Groups : [ ] string { "system:unauthenticated" } , Extra : nil } ,
Verb : "create" , Namespace : "" , APIGroup : "login.concierge.walrus.tld" , APIVersion : "v1alpha1" , Resource : "tokencredentialrequests" , Subresource : "" , Name : "" , ResourceRequest : true , Path : "/apis/login.concierge.walrus.tld/v1alpha1/tokencredentialrequests" ,
} ,
)
if ! tt . anonymousAuthDisabled {
tt . wantAuthorizerAttributes = append ( tt . wantAuthorizerAttributes ,
authorizer . AttributesRecord {
User : & user . DefaultInfo { Name : "system:anonymous" , UID : "" , Groups : [ ] string { "system:unauthenticated" } , Extra : nil } ,
Verb : "get" , Namespace : "" , APIGroup : "" , APIVersion : "" , Resource : "" , Subresource : "" , Name : "" , ResourceRequest : false , Path : "/probe" ,
} ,
authorizer . AttributesRecord {
User : & user . DefaultInfo { Name : "system:anonymous" , UID : "" , Groups : [ ] string { "system:unauthenticated" } , Extra : nil } ,
Verb : "list" , Namespace : "" , APIGroup : "not-concierge.walrus.tld" , APIVersion : "v1" , Resource : "tokencredentialrequests" , Subresource : "" , Name : "" , ResourceRequest : true , Path : "/apis/not-concierge.walrus.tld/v1/tokencredentialrequests" ,
} ,
authorizer . AttributesRecord {
User : & user . DefaultInfo { Name : "system:anonymous" , UID : "" , Groups : [ ] string { "system:unauthenticated" } , Extra : nil } ,
Verb : "list" , Namespace : "" , APIGroup : "not-concierge.walrus.tld" , APIVersion : "v1" , Resource : "ducks" , Subresource : "" , Name : "" , ResourceRequest : true , Path : "/apis/not-concierge.walrus.tld/v1/ducks" ,
} ,
)
}
2021-05-27 14:17:19 +00:00
// anonymous TCR should always work
2021-10-20 11:59:24 +00:00
tcrRegGroup , err := kubeclient . New ( kubeclient . WithConfig ( kubeclient . SecureAnonymousClientConfig ( clientKubeconfig ) ) )
2021-05-27 14:17:19 +00:00
require . NoError ( t , err )
2021-10-20 11:59:24 +00:00
tcrOtherGroup , err := kubeclient . New ( kubeclient . WithConfig ( kubeclient . SecureAnonymousClientConfig ( clientKubeconfig ) ) ,
2021-05-27 14:17:19 +00:00
kubeclient . WithMiddleware ( groupsuffix . New ( "walrus.tld" ) ) )
require . NoError ( t , err )
_ , errTCR := tcrRegGroup . PinnipedConcierge . LoginV1alpha1 ( ) . TokenCredentialRequests ( ) . Create ( ctx , & loginv1alpha1 . TokenCredentialRequest { } , metav1 . CreateOptions { } )
require . NoError ( t , errTCR )
_ , errTCROtherGroup := tcrOtherGroup . PinnipedConcierge . LoginV1alpha1 ( ) . TokenCredentialRequests ( ) . Create ( ctx ,
& loginv1alpha1 . TokenCredentialRequest {
Spec : loginv1alpha1 . TokenCredentialRequestSpec {
Authenticator : corev1 . TypedLocalObjectReference {
APIGroup : pointer . String ( "anything.pinniped.dev" ) ,
} ,
} ,
} , metav1 . CreateOptions { } )
require . NoError ( t , errTCROtherGroup )
// these calls should only work when anonymous auth is enabled
2021-10-20 11:59:24 +00:00
anonymousConfig := kubeclient . SecureAnonymousClientConfig ( clientKubeconfig )
2021-05-27 14:17:19 +00:00
anonymousConfig . GroupVersion = & schema . GroupVersion {
Group : "not-concierge.walrus.tld" ,
Version : "v1" ,
}
anonymousConfig . APIPath = "/apis"
anonymousConfig . NegotiatedSerializer = unstructuredscheme . NewUnstructuredNegotiatedSerializer ( )
rc , err := rest . RESTClientFor ( anonymousConfig )
require . NoError ( t , err )
probeBody , errProbe := rc . Get ( ) . AbsPath ( "/probe" ) . DoRaw ( ctx )
if tt . anonymousAuthDisabled {
require . True ( t , errors . IsUnauthorized ( errProbe ) , errProbe )
require . Equal ( t , ` { "kind":"Status","apiVersion":"v1","metadata": { },"status":"Failure","message":"Unauthorized","reason":"Unauthorized","code":401} ` + "\n" , string ( probeBody ) )
} else {
require . NoError ( t , errProbe )
require . Equal ( t , "probed" , string ( probeBody ) )
}
notTCRBody , errNotTCR := rc . Get ( ) . Resource ( "tokencredentialrequests" ) . DoRaw ( ctx )
if tt . anonymousAuthDisabled {
require . True ( t , errors . IsUnauthorized ( errNotTCR ) , errNotTCR )
require . Equal ( t , ` { "kind":"Status","apiVersion":"v1","metadata": { },"status":"Failure","message":"Unauthorized","reason":"Unauthorized","code":401} ` + "\n" , string ( notTCRBody ) )
} else {
require . NoError ( t , errNotTCR )
require . Equal ( t , ` { "hello": "quack"} ` , string ( notTCRBody ) )
}
ducksBody , errDucks := rc . Get ( ) . Resource ( "ducks" ) . DoRaw ( ctx )
if tt . anonymousAuthDisabled {
require . True ( t , errors . IsUnauthorized ( errDucks ) , errDucks )
require . Equal ( t , ` { "kind":"Status","apiVersion":"v1","metadata": { },"status":"Failure","message":"Unauthorized","reason":"Unauthorized","code":401} ` + "\n" , string ( ducksBody ) )
} else {
require . NoError ( t , errDucks )
require . Equal ( t , ` { "hello": "birds"} ` , string ( ducksBody ) )
}
// this should always fail as unauthorized (even for TCR) because the cert is not valid
2021-10-20 11:59:24 +00:00
badCertConfig := kubeclient . SecureAnonymousClientConfig ( clientKubeconfig )
2021-05-27 14:17:19 +00:00
badCert := newClientCert ( t , unrelatedCA , "bad-user" , [ ] string { "bad-group" } )
badCertConfig . TLSClientConfig . CertData = badCert . certPEM
badCertConfig . TLSClientConfig . KeyData = badCert . keyPEM
tcrBadCert , err := kubeclient . New ( kubeclient . WithConfig ( badCertConfig ) )
require . NoError ( t , err )
_ , errBadCert := tcrBadCert . PinnipedConcierge . LoginV1alpha1 ( ) . TokenCredentialRequests ( ) . Create ( ctx , & loginv1alpha1 . TokenCredentialRequest { } , metav1 . CreateOptions { } )
require . True ( t , errors . IsUnauthorized ( errBadCert ) , errBadCert )
require . EqualError ( t , errBadCert , "Unauthorized" )
2021-03-12 00:27:16 +00:00
// Stop the impersonator server.
close ( stopCh )
exitErr := <- errCh
require . NoError ( t , exitErr )
2021-03-10 18:30:06 +00:00
} )
}
}
2021-02-16 14:09:54 +00:00
2021-03-12 00:27:16 +00:00
func TestImpersonatorHTTPHandler ( t * testing . T ) {
2021-03-10 18:30:06 +00:00
const testUser = "test-user"
2021-02-15 23:00:10 +00:00
2021-02-16 14:09:54 +00:00
testGroups := [ ] string { "test-group-1" , "test-group-2" }
testExtra := map [ string ] [ ] string {
"extra-1" : { "some" , "extra" , "stuff" } ,
"extra-2" : { "some" , "more" , "extra" , "stuff" } ,
}
2021-01-20 00:37:02 +00:00
tests := [ ] struct {
2021-02-23 01:23:11 +00:00
name string
2021-03-10 18:30:06 +00:00
restConfig * rest . Config
2021-02-23 01:23:11 +00:00
wantCreationErr string
request * http . Request
2021-04-20 15:19:58 +00:00
authenticator authenticator . Request
2021-02-23 01:23:11 +00:00
wantHTTPBody string
wantHTTPStatus int
wantKubeAPIServerRequestHeaders http . Header
2021-03-10 18:30:06 +00:00
kubeAPIServerStatusCode int
2021-01-20 00:37:02 +00:00
} {
{
2021-03-10 18:30:06 +00:00
name : "invalid kubeconfig host" ,
restConfig : & rest . Config { Host : ":" } ,
2021-01-20 00:37:02 +00:00
wantCreationErr : "could not parse host URL from in-cluster config: parse \":\": missing protocol scheme" ,
} ,
{
name : "invalid transport config" ,
2021-03-10 18:30:06 +00:00
restConfig : & rest . Config {
Host : "pinniped.dev/blah" ,
ExecProvider : & api . ExecConfig { } ,
AuthProvider : & api . AuthProviderConfig { } ,
2021-01-20 00:37:02 +00:00
} ,
2021-10-20 11:59:24 +00:00
wantCreationErr : "could not create secure client config: execProvider and authProvider cannot be used in combination" ,
2021-01-20 00:37:02 +00:00
} ,
{
name : "fail to get transport from config" ,
2021-03-10 18:30:06 +00:00
restConfig : & rest . Config {
Host : "pinniped.dev/blah" ,
BearerToken : "test-bearer-token" ,
Transport : http . DefaultTransport ,
TLSClientConfig : rest . TLSClientConfig { Insecure : true } ,
2021-01-20 00:37:02 +00:00
} ,
2021-10-20 11:59:24 +00:00
wantCreationErr : "could not create secure client config: failed to build transport: using a custom transport with TLS certificate options or the insecure flag is not allowed" ,
2021-01-20 00:37:02 +00:00
} ,
2021-02-16 13:15:50 +00:00
{
name : "Impersonate-User header already in request" ,
2021-04-20 15:19:58 +00:00
request : newRequest ( t , map [ string ] [ ] string { "Impersonate-User" : { "some-user" } } , nil , nil , "" ) ,
2021-03-14 01:25:23 +00:00
wantHTTPBody : ` { "kind":"Status","apiVersion":"v1","metadata": { },"status":"Failure","message":"Internal error occurred: invalid impersonation","reason":"InternalError","details": { "causes":[ { "message":"invalid impersonation"}]},"code":500} ` + "\n" ,
2021-03-10 18:30:06 +00:00
wantHTTPStatus : http . StatusInternalServerError ,
2021-02-16 13:15:50 +00:00
} ,
{
name : "Impersonate-Group header already in request" ,
2021-04-20 15:19:58 +00:00
request : newRequest ( t , map [ string ] [ ] string { "Impersonate-Group" : { "some-group" } } , nil , nil , "" ) ,
2021-03-14 01:25:23 +00:00
wantHTTPBody : ` { "kind":"Status","apiVersion":"v1","metadata": { },"status":"Failure","message":"Internal error occurred: invalid impersonation","reason":"InternalError","details": { "causes":[ { "message":"invalid impersonation"}]},"code":500} ` + "\n" ,
2021-03-10 18:30:06 +00:00
wantHTTPStatus : http . StatusInternalServerError ,
2021-02-16 13:15:50 +00:00
} ,
{
name : "Impersonate-Extra header already in request" ,
2021-04-20 15:19:58 +00:00
request : newRequest ( t , map [ string ] [ ] string { "Impersonate-Extra-something" : { "something" } } , nil , nil , "" ) ,
2021-03-14 01:25:23 +00:00
wantHTTPBody : ` { "kind":"Status","apiVersion":"v1","metadata": { },"status":"Failure","message":"Internal error occurred: invalid impersonation","reason":"InternalError","details": { "causes":[ { "message":"invalid impersonation"}]},"code":500} ` + "\n" ,
2021-03-10 18:30:06 +00:00
wantHTTPStatus : http . StatusInternalServerError ,
2021-01-20 00:37:02 +00:00
} ,
{
2021-03-10 18:30:06 +00:00
name : "Impersonate-* header already in request" ,
2021-04-20 15:19:58 +00:00
request : newRequest ( t , map [ string ] [ ] string { "Impersonate-Something" : { "some-newfangled-impersonate-header" } } , nil , nil , "" ) ,
2021-03-14 01:25:23 +00:00
wantHTTPBody : ` { "kind":"Status","apiVersion":"v1","metadata": { },"status":"Failure","message":"Internal error occurred: invalid impersonation","reason":"InternalError","details": { "causes":[ { "message":"invalid impersonation"}]},"code":500} ` + "\n" ,
2021-03-10 18:30:06 +00:00
wantHTTPStatus : http . StatusInternalServerError ,
2021-01-20 00:37:02 +00:00
} ,
{
2021-03-10 18:30:06 +00:00
name : "unexpected authorization header" ,
2021-04-20 15:19:58 +00:00
request : newRequest ( t , map [ string ] [ ] string { "Authorization" : { "panda" } } , nil , nil , "" ) ,
2021-03-14 01:25:23 +00:00
wantHTTPBody : ` { "kind":"Status","apiVersion":"v1","metadata": { },"status":"Failure","message":"Internal error occurred: invalid authorization header","reason":"InternalError","details": { "causes":[ { "message":"invalid authorization header"}]},"code":500} ` + "\n" ,
2021-03-10 18:30:06 +00:00
wantHTTPStatus : http . StatusInternalServerError ,
2021-01-20 00:37:02 +00:00
} ,
{
2021-03-10 18:30:06 +00:00
name : "missing user" ,
2021-04-20 15:19:58 +00:00
request : newRequest ( t , map [ string ] [ ] string { } , nil , nil , "" ) ,
2021-03-14 01:25:23 +00:00
wantHTTPBody : ` { "kind":"Status","apiVersion":"v1","metadata": { },"status":"Failure","message":"Internal error occurred: invalid user","reason":"InternalError","details": { "causes":[ { "message":"invalid user"}]},"code":500} ` + "\n" ,
2021-03-10 18:30:06 +00:00
wantHTTPStatus : http . StatusInternalServerError ,
2021-02-15 23:00:10 +00:00
} ,
{
2021-03-10 18:30:06 +00:00
name : "unexpected UID" ,
2021-04-20 15:19:58 +00:00
request : newRequest ( t , map [ string ] [ ] string { } , & user . DefaultInfo { UID : "007" } , nil , "" ) ,
2021-04-09 21:52:53 +00:00
wantHTTPBody : ` { "kind":"Status","apiVersion":"v1","metadata": { },"status":"Failure","message":"Internal error occurred: unimplemented functionality - unable to act as current user","reason":"InternalError","details": { "causes":[ { "message":"unimplemented functionality - unable to act as current user"}]},"code":500} ` + "\n" ,
wantHTTPStatus : http . StatusInternalServerError ,
} ,
{
name : "authenticated user but missing audit event" ,
request : func ( ) * http . Request {
2021-04-20 15:19:58 +00:00
req := newRequest ( t , map [ string ] [ ] string {
2021-04-09 21:52:53 +00:00
"User-Agent" : { "test-user-agent" } ,
"Connection" : { "Upgrade" } ,
"Upgrade" : { "some-upgrade" } ,
"Other-Header" : { "test-header-value-1" } ,
} , & user . DefaultInfo {
Name : testUser ,
Groups : testGroups ,
Extra : testExtra ,
2021-04-20 15:19:58 +00:00
} , nil , "" )
2022-12-15 18:02:04 +00:00
ctx := audit . WithAuditContext ( req . Context ( ) )
2021-04-09 21:52:53 +00:00
req = req . WithContext ( ctx )
return req
} ( ) ,
wantHTTPBody : ` { "kind":"Status","apiVersion":"v1","metadata": { },"status":"Failure","message":"Internal error occurred: invalid audit event","reason":"InternalError","details": { "causes":[ { "message":"invalid audit event"}]},"code":500} ` + "\n" ,
wantHTTPStatus : http . StatusInternalServerError ,
} ,
{
name : "authenticated user with upper case extra" ,
2021-04-20 15:19:58 +00:00
request : newRequest ( t , map [ string ] [ ] string {
2021-04-09 21:52:53 +00:00
"User-Agent" : { "test-user-agent" } ,
"Connection" : { "Upgrade" } ,
"Upgrade" : { "some-upgrade" } ,
"Content-Type" : { "some-type" } ,
"Content-Length" : { "some-length" } ,
"Other-Header" : { "test-header-value-1" } ,
} , & user . DefaultInfo {
Name : testUser ,
Groups : testGroups ,
Extra : map [ string ] [ ] string {
"valid-key" : { "valid-value" } ,
"Invalid-key" : { "still-valid-value" } ,
} ,
2021-04-20 15:19:58 +00:00
} , nil , "" ) ,
2021-04-09 21:52:53 +00:00
wantHTTPBody : ` { "kind":"Status","apiVersion":"v1","metadata": { },"status":"Failure","message":"Internal error occurred: unimplemented functionality - unable to act as current user","reason":"InternalError","details": { "causes":[ { "message":"unimplemented functionality - unable to act as current user"}]},"code":500} ` + "\n" ,
wantHTTPStatus : http . StatusInternalServerError ,
} ,
{
name : "authenticated user with upper case extra across multiple lines" ,
2021-04-20 15:19:58 +00:00
request : newRequest ( t , map [ string ] [ ] string {
2021-04-09 21:52:53 +00:00
"User-Agent" : { "test-user-agent" } ,
"Connection" : { "Upgrade" } ,
"Upgrade" : { "some-upgrade" } ,
"Content-Type" : { "some-type" } ,
"Content-Length" : { "some-length" } ,
"Other-Header" : { "test-header-value-1" } ,
} , & user . DefaultInfo {
Name : testUser ,
Groups : testGroups ,
Extra : map [ string ] [ ] string {
"valid-key" : { "valid-value" } ,
"valid-data\nInvalid-key" : { "still-valid-value" } ,
} ,
2021-04-20 15:19:58 +00:00
} , nil , "" ) ,
2021-04-09 21:52:53 +00:00
wantHTTPBody : ` { "kind":"Status","apiVersion":"v1","metadata": { },"status":"Failure","message":"Internal error occurred: unimplemented functionality - unable to act as current user","reason":"InternalError","details": { "causes":[ { "message":"unimplemented functionality - unable to act as current user"}]},"code":500} ` + "\n" ,
wantHTTPStatus : http . StatusInternalServerError ,
} ,
{
name : "authenticated user with reserved extra key" ,
2021-04-20 15:19:58 +00:00
request : newRequest ( t , map [ string ] [ ] string {
2021-04-09 21:52:53 +00:00
"User-Agent" : { "test-user-agent" } ,
"Connection" : { "Upgrade" } ,
"Upgrade" : { "some-upgrade" } ,
"Content-Type" : { "some-type" } ,
"Content-Length" : { "some-length" } ,
"Other-Header" : { "test-header-value-1" } ,
} , & user . DefaultInfo {
Name : testUser ,
Groups : testGroups ,
Extra : map [ string ] [ ] string {
"valid-key" : { "valid-value" } ,
"foo.impersonation-proxy.concierge.pinniped.dev" : { "still-valid-value" } ,
} ,
2021-04-20 15:19:58 +00:00
} , nil , "" ) ,
wantHTTPBody : ` { "kind":"Status","apiVersion":"v1","metadata": { },"status":"Failure","message":"Internal error occurred: unimplemented functionality - unable to act as current user","reason":"InternalError","details": { "causes":[ { "message":"unimplemented functionality - unable to act as current user"}]},"code":500} ` + "\n" ,
wantHTTPStatus : http . StatusInternalServerError ,
} ,
{
name : "authenticated user with UID but no bearer token" ,
request : newRequest ( t , map [ string ] [ ] string {
"User-Agent" : { "test-user-agent" } ,
"Connection" : { "Upgrade" } ,
"Upgrade" : { "some-upgrade" } ,
"Content-Type" : { "some-type" } ,
"Content-Length" : { "some-length" } ,
"Other-Header" : { "test-header-value-1" } ,
} , & user . DefaultInfo {
UID : "-" , // anything non-empty, rest of the fields get ignored in this code path
} ,
& auditinternal . Event {
User : authenticationv1 . UserInfo {
Username : testUser ,
UID : "fancy-uid" ,
Groups : testGroups ,
Extra : map [ string ] authenticationv1 . ExtraValue {
"extra-1" : { "some" , "extra" , "stuff" } ,
"extra-2" : { "some" , "more" , "extra" , "stuff" } ,
} ,
} ,
ImpersonatedUser : nil ,
} ,
"" ,
) ,
authenticator : nil ,
wantHTTPBody : ` { "kind":"Status","apiVersion":"v1","metadata": { },"status":"Failure","message":"Internal error occurred: unimplemented functionality - unable to act as current user","reason":"InternalError","details": { "causes":[ { "message":"unimplemented functionality - unable to act as current user"}]},"code":500} ` + "\n" ,
wantHTTPStatus : http . StatusInternalServerError ,
} ,
{
name : "authenticated user with UID and bearer token and nested impersonation" ,
request : newRequest ( t , map [ string ] [ ] string {
"User-Agent" : { "test-user-agent" } ,
"Connection" : { "Upgrade" } ,
"Upgrade" : { "some-upgrade" } ,
"Content-Type" : { "some-type" } ,
"Content-Length" : { "some-length" } ,
"Other-Header" : { "test-header-value-1" } ,
} , & user . DefaultInfo {
UID : "-" , // anything non-empty, rest of the fields get ignored in this code path
} ,
& auditinternal . Event {
User : authenticationv1 . UserInfo {
Username : "dude" ,
UID : "--1--" ,
Groups : [ ] string { "--a--" , "--b--" } ,
Extra : map [ string ] authenticationv1 . ExtraValue {
"--c--" : { "--d--" } ,
"--e--" : { "--f--" } ,
} ,
} ,
ImpersonatedUser : & authenticationv1 . UserInfo { } ,
} ,
"token-from-user-nested" ,
) ,
authenticator : nil ,
wantHTTPBody : ` { "kind":"Status","apiVersion":"v1","metadata": { },"status":"Failure","message":"Internal error occurred: unimplemented functionality - unable to act as current user","reason":"InternalError","details": { "causes":[ { "message":"unimplemented functionality - unable to act as current user"}]},"code":500} ` + "\n" ,
wantHTTPStatus : http . StatusInternalServerError ,
} ,
{
name : "authenticated user with UID and bearer token results in error" ,
request : newRequest ( t , map [ string ] [ ] string {
"User-Agent" : { "test-user-agent" } ,
"Connection" : { "Upgrade" } ,
"Upgrade" : { "some-upgrade" } ,
"Content-Type" : { "some-type" } ,
"Content-Length" : { "some-length" } ,
"Other-Header" : { "test-header-value-1" } ,
} , & user . DefaultInfo {
UID : "-" , // anything non-empty, rest of the fields get ignored in this code path
} ,
& auditinternal . Event {
User : authenticationv1 . UserInfo {
Username : "dude" ,
UID : "--1--" ,
Groups : [ ] string { "--a--" , "--b--" } ,
Extra : map [ string ] authenticationv1 . ExtraValue {
"--c--" : { "--d--" } ,
"--e--" : { "--f--" } ,
} ,
} ,
ImpersonatedUser : nil ,
} ,
"some-non-empty-token" ,
) ,
authenticator : testTokenAuthenticator ( t , "" , nil , constable . Error ( "some err" ) ) ,
wantHTTPBody : ` { "kind":"Status","apiVersion":"v1","metadata": { },"status":"Failure","message":"Internal error occurred: unimplemented functionality - unable to act as current user","reason":"InternalError","details": { "causes":[ { "message":"unimplemented functionality - unable to act as current user"}]},"code":500} ` + "\n" ,
wantHTTPStatus : http . StatusInternalServerError ,
} ,
{
name : "authenticated user with UID and bearer token does not authenticate" ,
request : newRequest ( t , map [ string ] [ ] string {
"User-Agent" : { "test-user-agent" } ,
"Connection" : { "Upgrade" } ,
"Upgrade" : { "some-upgrade" } ,
"Content-Type" : { "some-type" } ,
"Content-Length" : { "some-length" } ,
"Other-Header" : { "test-header-value-1" } ,
} , & user . DefaultInfo {
UID : "-" , // anything non-empty, rest of the fields get ignored in this code path
} ,
& auditinternal . Event {
User : authenticationv1 . UserInfo {
Username : "dude" ,
UID : "--1--" ,
Groups : [ ] string { "--a--" , "--b--" } ,
Extra : map [ string ] authenticationv1 . ExtraValue {
"--c--" : { "--d--" } ,
"--e--" : { "--f--" } ,
} ,
} ,
ImpersonatedUser : nil ,
} ,
"this-token-does-not-work" ,
) ,
authenticator : testTokenAuthenticator ( t , "some-other-token-works" , nil , nil ) ,
wantHTTPBody : ` { "kind":"Status","apiVersion":"v1","metadata": { },"status":"Failure","message":"Internal error occurred: unimplemented functionality - unable to act as current user","reason":"InternalError","details": { "causes":[ { "message":"unimplemented functionality - unable to act as current user"}]},"code":500} ` + "\n" ,
wantHTTPStatus : http . StatusInternalServerError ,
} ,
{
name : "authenticated user with UID and bearer token authenticates as different user" ,
request : newRequest ( t , map [ string ] [ ] string {
"User-Agent" : { "test-user-agent" } ,
"Connection" : { "Upgrade" } ,
"Upgrade" : { "some-upgrade" } ,
"Content-Type" : { "some-type" } ,
"Content-Length" : { "some-length" } ,
"Other-Header" : { "test-header-value-1" } ,
} , & user . DefaultInfo {
UID : "-" , // anything non-empty, rest of the fields get ignored in this code path
} ,
& auditinternal . Event {
User : authenticationv1 . UserInfo {
Username : "dude" ,
UID : "--1--" ,
Groups : [ ] string { "--a--" , "--b--" } ,
Extra : map [ string ] authenticationv1 . ExtraValue {
"--c--" : { "--d--" } ,
"--e--" : { "--f--" } ,
} ,
} ,
ImpersonatedUser : nil ,
} ,
"this-token-does-work" ,
) ,
authenticator : testTokenAuthenticator ( t , "this-token-does-work" , & user . DefaultInfo { Name : "someone-else" } , nil ) ,
2021-03-14 01:25:23 +00:00
wantHTTPBody : ` { "kind":"Status","apiVersion":"v1","metadata": { },"status":"Failure","message":"Internal error occurred: unimplemented functionality - unable to act as current user","reason":"InternalError","details": { "causes":[ { "message":"unimplemented functionality - unable to act as current user"}]},"code":500} ` + "\n" ,
wantHTTPStatus : http . StatusInternalServerError ,
2021-01-20 00:37:02 +00:00
} ,
// happy path
{
2021-03-10 18:30:06 +00:00
name : "authenticated user" ,
2021-04-20 15:19:58 +00:00
request : newRequest ( t , map [ string ] [ ] string {
2021-03-02 22:56:54 +00:00
"User-Agent" : { "test-user-agent" } ,
"Accept" : { "some-accepted-format" } ,
"Accept-Encoding" : { "some-accepted-encoding" } ,
"Connection" : { "Upgrade" } , // the value "Upgrade" is handled in a special way by `httputil.NewSingleHostReverseProxy`
"Upgrade" : { "some-upgrade" } ,
"Content-Type" : { "some-type" } ,
"Content-Length" : { "some-length" } ,
"Other-Header" : { "test-header-value-1" } , // this header will be passed through
2021-03-10 18:30:06 +00:00
} , & user . DefaultInfo {
Name : testUser ,
Groups : testGroups ,
Extra : testExtra ,
2021-04-20 15:19:58 +00:00
} , nil , "" ) ,
2021-04-09 21:52:53 +00:00
wantKubeAPIServerRequestHeaders : map [ string ] [ ] string {
"Authorization" : { "Bearer some-service-account-token" } ,
"Impersonate-Extra-Extra-1" : { "some" , "extra" , "stuff" } ,
"Impersonate-Extra-Extra-2" : { "some" , "more" , "extra" , "stuff" } ,
"Impersonate-Group" : { "test-group-1" , "test-group-2" } ,
"Impersonate-User" : { "test-user" } ,
"User-Agent" : { "test-user-agent" } ,
"Accept" : { "some-accepted-format" } ,
"Accept-Encoding" : { "some-accepted-encoding" } ,
"Connection" : { "Upgrade" } ,
"Upgrade" : { "some-upgrade" } ,
"Content-Type" : { "some-type" } ,
"Other-Header" : { "test-header-value-1" } ,
} ,
wantHTTPBody : "successful proxied response" ,
wantHTTPStatus : http . StatusOK ,
} ,
2021-04-20 15:19:58 +00:00
{
name : "authenticated user with UID and bearer token" ,
request : newRequest ( t , map [ string ] [ ] string {
"User-Agent" : { "test-user-agent" } ,
"Accept" : { "some-accepted-format" } ,
"Accept-Encoding" : { "some-accepted-encoding" } ,
"Connection" : { "Upgrade" } ,
"Upgrade" : { "some-upgrade" } ,
"Content-Type" : { "some-type" } ,
"Content-Length" : { "some-length" } ,
"Other-Header" : { "test-header-value-1" } ,
} , & user . DefaultInfo {
UID : "-" , // anything non-empty, rest of the fields get ignored in this code path
} ,
& auditinternal . Event {
User : authenticationv1 . UserInfo {
Username : testUser ,
UID : "fancy-uid" ,
Groups : testGroups ,
Extra : map [ string ] authenticationv1 . ExtraValue {
"extra-1" : { "some" , "extra" , "stuff" } ,
"extra-2" : { "some" , "more" , "extra" , "stuff" } ,
} ,
} ,
ImpersonatedUser : nil ,
} ,
"token-from-user" ,
) ,
authenticator : testTokenAuthenticator (
t ,
"token-from-user" ,
& user . DefaultInfo {
Name : testUser ,
UID : "fancy-uid" ,
Groups : testGroups ,
Extra : testExtra ,
} ,
nil ,
) ,
wantKubeAPIServerRequestHeaders : map [ string ] [ ] string {
"Authorization" : { "Bearer token-from-user" } ,
"User-Agent" : { "test-user-agent" } ,
"Accept" : { "some-accepted-format" } ,
"Accept-Encoding" : { "some-accepted-encoding" } ,
"Connection" : { "Upgrade" } ,
"Upgrade" : { "some-upgrade" } ,
"Content-Type" : { "some-type" } ,
"Other-Header" : { "test-header-value-1" } ,
} ,
wantHTTPBody : "successful proxied response" ,
wantHTTPStatus : http . StatusOK ,
} ,
2021-04-09 21:52:53 +00:00
{
name : "authenticated gke user" ,
2021-04-20 15:19:58 +00:00
request : newRequest ( t , map [ string ] [ ] string {
2021-04-09 21:52:53 +00:00
"User-Agent" : { "test-user-agent" } ,
"Accept" : { "some-accepted-format" } ,
"Accept-Encoding" : { "some-accepted-encoding" } ,
"Connection" : { "Upgrade" } , // the value "Upgrade" is handled in a special way by `httputil.NewSingleHostReverseProxy`
"Upgrade" : { "some-upgrade" } ,
"Content-Type" : { "some-type" } ,
"Content-Length" : { "some-length" } ,
"Other-Header" : { "test-header-value-1" } , // this header will be passed through
} , & user . DefaultInfo {
Name : "username@company.com" ,
Groups : [ ] string { "system:authenticated" } ,
Extra : map [ string ] [ ] string {
// make sure we can handle these keys
"iam.gke.io/user-assertion" : { "ABC" } ,
"user-assertion.cloud.google.com" : { "XYZ" } ,
} ,
2021-04-20 15:19:58 +00:00
} , nil , "" ) ,
2021-04-09 21:52:53 +00:00
wantKubeAPIServerRequestHeaders : map [ string ] [ ] string {
"Authorization" : { "Bearer some-service-account-token" } ,
"Impersonate-Extra-Iam.gke.io%2fuser-Assertion" : { "ABC" } ,
"Impersonate-Extra-User-Assertion.cloud.google.com" : { "XYZ" } ,
"Impersonate-Group" : { "system:authenticated" } ,
"Impersonate-User" : { "username@company.com" } ,
"User-Agent" : { "test-user-agent" } ,
"Accept" : { "some-accepted-format" } ,
"Accept-Encoding" : { "some-accepted-encoding" } ,
"Connection" : { "Upgrade" } ,
"Upgrade" : { "some-upgrade" } ,
"Content-Type" : { "some-type" } ,
"Other-Header" : { "test-header-value-1" } ,
} ,
wantHTTPBody : "successful proxied response" ,
wantHTTPStatus : http . StatusOK ,
} ,
{
name : "authenticated openshift/openstack user" ,
2021-04-20 15:19:58 +00:00
request : newRequest ( t , map [ string ] [ ] string {
2021-04-09 21:52:53 +00:00
"User-Agent" : { "test-user-agent" } ,
"Accept" : { "some-accepted-format" } ,
"Accept-Encoding" : { "some-accepted-encoding" } ,
"Connection" : { "Upgrade" } , // the value "Upgrade" is handled in a special way by `httputil.NewSingleHostReverseProxy`
"Upgrade" : { "some-upgrade" } ,
"Content-Type" : { "some-type" } ,
"Content-Length" : { "some-length" } ,
"Other-Header" : { "test-header-value-1" } , // this header will be passed through
} , & user . DefaultInfo {
Name : "kube:admin" ,
// both of these auth stacks set UID but we cannot handle it today
// UID: "user-id",
Groups : [ ] string { "system:cluster-admins" , "system:authenticated" } ,
Extra : map [ string ] [ ] string {
// openshift
"scopes.authorization.openshift.io" : { "user:info" , "user:full" } ,
// openstack
"alpha.kubernetes.io/identity/roles" : { "role1" , "role2" } ,
"alpha.kubernetes.io/identity/project/id" : { "project-id" } ,
"alpha.kubernetes.io/identity/project/name" : { "project-name" } ,
"alpha.kubernetes.io/identity/user/domain/id" : { "domain-id" } ,
"alpha.kubernetes.io/identity/user/domain/name" : { "domain-name" } ,
} ,
2021-04-20 15:19:58 +00:00
} , nil , "" ) ,
2021-04-09 21:52:53 +00:00
wantKubeAPIServerRequestHeaders : map [ string ] [ ] string {
"Authorization" : { "Bearer some-service-account-token" } ,
"Impersonate-Extra-Scopes.authorization.openshift.io" : { "user:info" , "user:full" } ,
"Impersonate-Extra-Alpha.kubernetes.io%2fidentity%2froles" : { "role1" , "role2" } ,
"Impersonate-Extra-Alpha.kubernetes.io%2fidentity%2fproject%2fid" : { "project-id" } ,
"Impersonate-Extra-Alpha.kubernetes.io%2fidentity%2fproject%2fname" : { "project-name" } ,
"Impersonate-Extra-Alpha.kubernetes.io%2fidentity%2fuser%2fdomain%2fid" : { "domain-id" } ,
"Impersonate-Extra-Alpha.kubernetes.io%2fidentity%2fuser%2fdomain%2fname" : { "domain-name" } ,
"Impersonate-Group" : { "system:cluster-admins" , "system:authenticated" } ,
"Impersonate-User" : { "kube:admin" } ,
"User-Agent" : { "test-user-agent" } ,
"Accept" : { "some-accepted-format" } ,
"Accept-Encoding" : { "some-accepted-encoding" } ,
"Connection" : { "Upgrade" } ,
"Upgrade" : { "some-upgrade" } ,
"Content-Type" : { "some-type" } ,
"Other-Header" : { "test-header-value-1" } ,
} ,
wantHTTPBody : "successful proxied response" ,
wantHTTPStatus : http . StatusOK ,
} ,
{
name : "authenticated user with almost reserved key" ,
2021-04-20 15:19:58 +00:00
request : newRequest ( t , map [ string ] [ ] string {
2021-04-09 21:52:53 +00:00
"User-Agent" : { "test-user-agent" } ,
"Accept" : { "some-accepted-format" } ,
"Accept-Encoding" : { "some-accepted-encoding" } ,
"Connection" : { "Upgrade" } , // the value "Upgrade" is handled in a special way by `httputil.NewSingleHostReverseProxy`
"Upgrade" : { "some-upgrade" } ,
"Content-Type" : { "some-type" } ,
"Content-Length" : { "some-length" } ,
"Other-Header" : { "test-header-value-1" } , // this header will be passed through
} , & user . DefaultInfo {
Name : "username@company.com" ,
Groups : [ ] string { "system:authenticated" } ,
Extra : map [ string ] [ ] string {
"foo.iimpersonation-proxy.concierge.pinniped.dev" : { "still-valid-value" } ,
} ,
2021-04-20 15:19:58 +00:00
} , nil , "" ) ,
2021-04-09 21:52:53 +00:00
wantKubeAPIServerRequestHeaders : map [ string ] [ ] string {
"Authorization" : { "Bearer some-service-account-token" } ,
"Impersonate-Extra-Foo.iimpersonation-Proxy.concierge.pinniped.dev" : { "still-valid-value" } ,
"Impersonate-Group" : { "system:authenticated" } ,
"Impersonate-User" : { "username@company.com" } ,
"User-Agent" : { "test-user-agent" } ,
"Accept" : { "some-accepted-format" } ,
"Accept-Encoding" : { "some-accepted-encoding" } ,
"Connection" : { "Upgrade" } ,
"Upgrade" : { "some-upgrade" } ,
"Content-Type" : { "some-type" } ,
"Other-Header" : { "test-header-value-1" } ,
} ,
wantHTTPBody : "successful proxied response" ,
wantHTTPStatus : http . StatusOK ,
} ,
{
name : "authenticated user with almost reserved key and nested impersonation" ,
2021-04-20 15:19:58 +00:00
request : newRequest ( t , map [ string ] [ ] string {
2021-04-09 21:52:53 +00:00
"User-Agent" : { "test-user-agent" } ,
"Accept" : { "some-accepted-format" } ,
"Accept-Encoding" : { "some-accepted-encoding" } ,
"Connection" : { "Upgrade" } , // the value "Upgrade" is handled in a special way by `httputil.NewSingleHostReverseProxy`
"Upgrade" : { "some-upgrade" } ,
"Content-Type" : { "some-type" } ,
"Content-Length" : { "some-length" } ,
"Other-Header" : { "test-header-value-1" } , // this header will be passed through
} , & user . DefaultInfo {
Name : "username@company.com" ,
Groups : [ ] string { "system:authenticated" } ,
Extra : map [ string ] [ ] string {
"original-user-info.impersonation-proxyy.concierge.pinniped.dev" : { "log confusion stuff here" } ,
} ,
} ,
& auditinternal . Event {
User : authenticationv1 . UserInfo {
Username : "panda" ,
UID : "0x001" ,
Groups : [ ] string { "bears" , "friends" } ,
Extra : map [ string ] authenticationv1 . ExtraValue {
"original-user-info.impersonation-proxy.concierge.pinniped.dev" : { "this is allowed" } ,
} ,
} ,
ImpersonatedUser : & authenticationv1 . UserInfo { } ,
} ,
2021-04-20 15:19:58 +00:00
"" ,
2021-04-09 21:52:53 +00:00
) ,
wantKubeAPIServerRequestHeaders : map [ string ] [ ] string {
"Authorization" : { "Bearer some-service-account-token" } ,
"Impersonate-Extra-Original-User-Info.impersonation-Proxyy.concierge.pinniped.dev" : { "log confusion stuff here" } ,
"Impersonate-Extra-Original-User-Info.impersonation-Proxy.concierge.pinniped.dev" : { ` { "username":"panda","uid":"0x001","groups":["bears","friends"],"extra": { "original-user-info.impersonation-proxy.concierge.pinniped.dev":["this is allowed"]}} ` } ,
"Impersonate-Group" : { "system:authenticated" } ,
"Impersonate-User" : { "username@company.com" } ,
"User-Agent" : { "test-user-agent" } ,
"Accept" : { "some-accepted-format" } ,
"Accept-Encoding" : { "some-accepted-encoding" } ,
"Connection" : { "Upgrade" } ,
"Upgrade" : { "some-upgrade" } ,
"Content-Type" : { "some-type" } ,
"Other-Header" : { "test-header-value-1" } ,
} ,
wantHTTPBody : "successful proxied response" ,
wantHTTPStatus : http . StatusOK ,
} ,
{
name : "authenticated user with nested impersonation" ,
2021-04-20 15:19:58 +00:00
request : newRequest ( t , map [ string ] [ ] string {
2021-04-09 21:52:53 +00:00
"User-Agent" : { "test-user-agent" } ,
"Accept" : { "some-accepted-format" } ,
"Accept-Encoding" : { "some-accepted-encoding" } ,
"Connection" : { "Upgrade" } , // the value "Upgrade" is handled in a special way by `httputil.NewSingleHostReverseProxy`
"Upgrade" : { "some-upgrade" } ,
"Content-Type" : { "some-type" } ,
"Content-Length" : { "some-length" } ,
"Other-Header" : { "test-header-value-1" } , // this header will be passed through
} , & user . DefaultInfo {
Name : testUser ,
Groups : testGroups ,
Extra : testExtra ,
} ,
& auditinternal . Event {
User : authenticationv1 . UserInfo {
Username : "panda" ,
UID : "0x001" ,
Groups : [ ] string { "bears" , "friends" } ,
Extra : map [ string ] authenticationv1 . ExtraValue {
"assertion" : { "sha" , "md5" } ,
"req-id" : { "0123" } ,
} ,
} ,
ImpersonatedUser : & authenticationv1 . UserInfo { } ,
} ,
2021-04-20 15:19:58 +00:00
"" ,
2021-04-09 21:52:53 +00:00
) ,
2021-02-23 01:23:11 +00:00
wantKubeAPIServerRequestHeaders : map [ string ] [ ] string {
"Authorization" : { "Bearer some-service-account-token" } ,
"Impersonate-Extra-Extra-1" : { "some" , "extra" , "stuff" } ,
"Impersonate-Extra-Extra-2" : { "some" , "more" , "extra" , "stuff" } ,
"Impersonate-Group" : { "test-group-1" , "test-group-2" } ,
"Impersonate-User" : { "test-user" } ,
"User-Agent" : { "test-user-agent" } ,
"Accept" : { "some-accepted-format" } ,
"Accept-Encoding" : { "some-accepted-encoding" } ,
"Connection" : { "Upgrade" } ,
"Upgrade" : { "some-upgrade" } ,
"Content-Type" : { "some-type" } ,
2021-03-02 22:56:54 +00:00
"Other-Header" : { "test-header-value-1" } ,
2021-04-09 21:52:53 +00:00
"Impersonate-Extra-Original-User-Info.impersonation-Proxy.concierge.pinniped.dev" : { ` { "username":"panda","uid":"0x001","groups":["bears","friends"],"extra": { "assertion":["sha","md5"],"req-id":["0123"]}} ` } ,
} ,
wantHTTPBody : "successful proxied response" ,
wantHTTPStatus : http . StatusOK ,
} ,
{
name : "authenticated gke user with nested impersonation" ,
2021-04-20 15:19:58 +00:00
request : newRequest ( t , map [ string ] [ ] string {
2021-04-09 21:52:53 +00:00
"User-Agent" : { "test-user-agent" } ,
"Accept" : { "some-accepted-format" } ,
"Accept-Encoding" : { "some-accepted-encoding" } ,
"Connection" : { "Upgrade" } , // the value "Upgrade" is handled in a special way by `httputil.NewSingleHostReverseProxy`
"Upgrade" : { "some-upgrade" } ,
"Content-Type" : { "some-type" } ,
"Content-Length" : { "some-length" } ,
"Other-Header" : { "test-header-value-1" } , // this header will be passed through
} , & user . DefaultInfo {
Name : testUser ,
Groups : testGroups ,
Extra : testExtra ,
} ,
& auditinternal . Event {
User : authenticationv1 . UserInfo {
Username : "username@company.com" ,
Groups : [ ] string { "system:authenticated" } ,
Extra : map [ string ] authenticationv1 . ExtraValue {
// make sure we can handle these keys
"iam.gke.io/user-assertion" : { "ABC" } ,
"user-assertion.cloud.google.com" : { "999" } ,
} ,
} ,
ImpersonatedUser : & authenticationv1 . UserInfo { } ,
} ,
2021-04-20 15:19:58 +00:00
"" ,
2021-04-09 21:52:53 +00:00
) ,
wantKubeAPIServerRequestHeaders : map [ string ] [ ] string {
"Authorization" : { "Bearer some-service-account-token" } ,
"Impersonate-Extra-Extra-1" : { "some" , "extra" , "stuff" } ,
"Impersonate-Extra-Extra-2" : { "some" , "more" , "extra" , "stuff" } ,
"Impersonate-Group" : { "test-group-1" , "test-group-2" } ,
"Impersonate-User" : { "test-user" } ,
"User-Agent" : { "test-user-agent" } ,
"Accept" : { "some-accepted-format" } ,
"Accept-Encoding" : { "some-accepted-encoding" } ,
"Connection" : { "Upgrade" } ,
"Upgrade" : { "some-upgrade" } ,
"Content-Type" : { "some-type" } ,
"Other-Header" : { "test-header-value-1" } ,
"Impersonate-Extra-Original-User-Info.impersonation-Proxy.concierge.pinniped.dev" : { ` { "username":"username@company.com","groups":["system:authenticated"],"extra": { "iam.gke.io/user-assertion":["ABC"],"user-assertion.cloud.google.com":["999"]}} ` } ,
} ,
wantHTTPBody : "successful proxied response" ,
wantHTTPStatus : http . StatusOK ,
} ,
{
name : "authenticated user with nested impersonation of gke user" ,
2021-04-20 15:19:58 +00:00
request : newRequest ( t , map [ string ] [ ] string {
2021-04-09 21:52:53 +00:00
"User-Agent" : { "test-user-agent" } ,
"Accept" : { "some-accepted-format" } ,
"Accept-Encoding" : { "some-accepted-encoding" } ,
"Connection" : { "Upgrade" } , // the value "Upgrade" is handled in a special way by `httputil.NewSingleHostReverseProxy`
"Upgrade" : { "some-upgrade" } ,
"Content-Type" : { "some-type" } ,
"Content-Length" : { "some-length" } ,
"Other-Header" : { "test-header-value-1" } , // this header will be passed through
} , & user . DefaultInfo {
Name : "username@company.com" ,
Groups : [ ] string { "system:authenticated" } ,
Extra : map [ string ] [ ] string {
// make sure we can handle these keys
"iam.gke.io/user-assertion" : { "DEF" } ,
"user-assertion.cloud.google.com" : { "XYZ" } ,
} ,
} ,
& auditinternal . Event {
User : authenticationv1 . UserInfo {
Username : "panda" ,
UID : "0x001" ,
Groups : [ ] string { "bears" , "friends" } ,
Extra : map [ string ] authenticationv1 . ExtraValue {
"assertion" : { "sha" , "md5" } ,
"req-id" : { "0123" } ,
} ,
} ,
ImpersonatedUser : & authenticationv1 . UserInfo { } ,
} ,
2021-04-20 15:19:58 +00:00
"" ,
2021-04-09 21:52:53 +00:00
) ,
wantKubeAPIServerRequestHeaders : map [ string ] [ ] string {
"Authorization" : { "Bearer some-service-account-token" } ,
"Impersonate-Extra-Iam.gke.io%2fuser-Assertion" : { "DEF" } ,
"Impersonate-Extra-User-Assertion.cloud.google.com" : { "XYZ" } ,
"Impersonate-Group" : { "system:authenticated" } ,
"Impersonate-User" : { "username@company.com" } ,
"User-Agent" : { "test-user-agent" } ,
"Accept" : { "some-accepted-format" } ,
"Accept-Encoding" : { "some-accepted-encoding" } ,
"Connection" : { "Upgrade" } ,
"Upgrade" : { "some-upgrade" } ,
"Content-Type" : { "some-type" } ,
"Other-Header" : { "test-header-value-1" } ,
"Impersonate-Extra-Original-User-Info.impersonation-Proxy.concierge.pinniped.dev" : { ` { "username":"panda","uid":"0x001","groups":["bears","friends"],"extra": { "assertion":["sha","md5"],"req-id":["0123"]}} ` } ,
2021-02-23 01:23:11 +00:00
} ,
2021-02-15 23:00:10 +00:00
wantHTTPBody : "successful proxied response" ,
wantHTTPStatus : http . StatusOK ,
} ,
2021-02-23 01:23:11 +00:00
{
2021-03-10 18:30:06 +00:00
name : "user is authenticated but the kube API request returns an error" ,
2021-04-20 15:19:58 +00:00
request : newRequest ( t , map [ string ] [ ] string {
2021-03-10 18:30:06 +00:00
"User-Agent" : { "test-user-agent" } ,
} , & user . DefaultInfo {
Name : testUser ,
Groups : testGroups ,
Extra : testExtra ,
2021-04-20 15:19:58 +00:00
} , nil , "" ) ,
2021-03-10 18:30:06 +00:00
kubeAPIServerStatusCode : http . StatusNotFound ,
2021-02-23 01:23:11 +00:00
wantKubeAPIServerRequestHeaders : map [ string ] [ ] string {
"Accept-Encoding" : { "gzip" } , // because the rest client used in this test does not disable compression
"Authorization" : { "Bearer some-service-account-token" } ,
"Impersonate-Extra-Extra-1" : { "some" , "extra" , "stuff" } ,
"Impersonate-Extra-Extra-2" : { "some" , "more" , "extra" , "stuff" } ,
"Impersonate-Group" : { "test-group-1" , "test-group-2" } ,
"Impersonate-User" : { "test-user" } ,
"User-Agent" : { "test-user-agent" } ,
} ,
wantHTTPStatus : http . StatusNotFound ,
2021-01-20 00:37:02 +00:00
} ,
}
for _ , tt := range tests {
tt := tt
t . Run ( tt . name , func ( t * testing . T ) {
2021-03-12 00:44:08 +00:00
t . Parallel ( )
2021-03-10 18:30:06 +00:00
if tt . kubeAPIServerStatusCode == 0 {
tt . kubeAPIServerStatusCode = http . StatusOK
2021-02-23 01:23:11 +00:00
}
2021-03-12 00:44:08 +00:00
testKubeAPIServerWasCalled := false
testKubeAPIServerSawHeaders := http . Header { }
2021-10-20 11:59:24 +00:00
testKubeAPIServer := tlsserver . TLSTestServer ( t , http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
2022-04-14 16:59:19 +00:00
tlsConfigFunc := func ( rootCAs * x509 . CertPool ) * tls . Config {
// Requests to get configmaps, flowcontrol requests, and healthz requests
// are not done by our http round trippers that specify only one protocol
// (either http1.1 or http2, not both).
// For all other requests from the impersonator, if it is not an upgrade
// request it should only be using http2.
// If it is an upgrade request it should only be using http1.1, but that
// is covered by the AssertTLS function.
secure := ptls . Secure ( rootCAs )
switch r . URL . Path {
case "/api/v1/namespaces/kube-system/configmaps" ,
"/apis/flowcontrol.apiserver.k8s.io/v1beta2/prioritylevelconfigurations" ,
"/apis/flowcontrol.apiserver.k8s.io/v1beta2/flowschemas" ,
"/healthz" :
default :
if ! httpstream . IsUpgradeRequest ( r ) {
secure . NextProtos = [ ] string { secure . NextProtos [ 0 ] }
}
}
return secure
}
tlsserver . AssertTLS ( t , r , tlsConfigFunc )
2021-10-20 11:59:24 +00:00
2021-03-12 00:44:08 +00:00
testKubeAPIServerWasCalled = true
testKubeAPIServerSawHeaders = r . Header
2021-03-10 18:30:06 +00:00
if tt . kubeAPIServerStatusCode != http . StatusOK {
w . WriteHeader ( tt . kubeAPIServerStatusCode )
2021-02-23 01:23:11 +00:00
} else {
_ , _ = w . Write ( [ ] byte ( "successful proxied response" ) )
}
2021-10-20 11:59:24 +00:00
} ) , tlsserver . RecordTLSHello )
2021-03-12 00:44:08 +00:00
testKubeAPIServerKubeconfig := rest . Config {
2021-10-20 11:59:24 +00:00
Host : testKubeAPIServer . URL ,
2021-02-23 01:23:11 +00:00
BearerToken : "some-service-account-token" ,
2021-10-20 11:59:24 +00:00
TLSClientConfig : rest . TLSClientConfig { CAData : tlsserver . TLSTestServerCA ( testKubeAPIServer ) } ,
2021-02-23 01:23:11 +00:00
}
2021-03-10 18:30:06 +00:00
if tt . restConfig == nil {
2021-03-12 00:44:08 +00:00
tt . restConfig = & testKubeAPIServerKubeconfig
2021-02-15 23:00:10 +00:00
}
2021-10-20 11:59:24 +00:00
// mimic how newInternal would call newImpersonationReverseProxyFunc
impersonatorHTTPHandlerFunc , err := func ( ) ( func ( * genericapiserver . Config ) http . Handler , error ) {
kubeClientForProxy , err := kubeclient . New ( kubeclient . WithConfig ( tt . restConfig ) )
if err != nil {
return nil , err
}
return newImpersonationReverseProxyFunc ( rest . CopyConfig ( kubeClientForProxy . ProtoConfig ) )
} ( )
2021-01-20 00:37:02 +00:00
if tt . wantCreationErr != "" {
require . EqualError ( t , err , tt . wantCreationErr )
2021-03-12 14:56:34 +00:00
require . Nil ( t , impersonatorHTTPHandlerFunc )
2021-01-20 00:37:02 +00:00
return
}
require . NoError ( t , err )
2021-03-12 14:56:34 +00:00
require . NotNil ( t , impersonatorHTTPHandlerFunc )
2021-03-12 00:44:08 +00:00
2021-03-14 01:25:23 +00:00
// this is not a valid way to get a server config, but it is good enough for a unit test
scheme := runtime . NewScheme ( )
metav1 . AddToGroupVersion ( scheme , metav1 . Unversioned )
codecs := serializer . NewCodecFactory ( scheme )
serverConfig := genericapiserver . NewRecommendedConfig ( codecs )
2021-04-20 15:19:58 +00:00
serverConfig . Authentication . Authenticator = tt . authenticator
2021-03-14 01:25:23 +00:00
2021-01-20 00:37:02 +00:00
w := httptest . NewRecorder ( )
2021-03-12 00:44:08 +00:00
2021-03-19 17:39:55 +00:00
r := tt . request
wantKubeAPIServerRequestHeaders := tt . wantKubeAPIServerRequestHeaders
// take the isUpgradeRequest branch randomly to make sure we exercise both branches
forceUpgradeRequest := rand . Int ( ) % 2 == 0 //nolint:gosec // we do not care if this is cryptographically secure
if forceUpgradeRequest && len ( r . Header . Get ( "Upgrade" ) ) == 0 {
r = r . Clone ( r . Context ( ) )
r . Header . Add ( "Connection" , "Upgrade" )
r . Header . Add ( "Upgrade" , "spdy/3.1" )
wantKubeAPIServerRequestHeaders = wantKubeAPIServerRequestHeaders . Clone ( )
if wantKubeAPIServerRequestHeaders == nil {
wantKubeAPIServerRequestHeaders = http . Header { }
}
wantKubeAPIServerRequestHeaders . Add ( "Connection" , "Upgrade" )
wantKubeAPIServerRequestHeaders . Add ( "Upgrade" , "spdy/3.1" )
}
requestBeforeServe := r . Clone ( r . Context ( ) )
impersonatorHTTPHandlerFunc ( & serverConfig . Config ) . ServeHTTP ( w , r )
require . Equal ( t , requestBeforeServe , r , "ServeHTTP() mutated the request, and it should not per http.Handler docs" )
2021-01-20 00:37:02 +00:00
if tt . wantHTTPStatus != 0 {
2021-02-16 14:09:54 +00:00
require . Equalf ( t , tt . wantHTTPStatus , w . Code , "fyi, response body was %q" , w . Body . String ( ) )
2021-01-20 00:37:02 +00:00
}
if tt . wantHTTPBody != "" {
require . Equal ( t , tt . wantHTTPBody , w . Body . String ( ) )
}
2021-02-23 01:23:11 +00:00
2021-03-10 18:30:06 +00:00
if tt . wantHTTPStatus == http . StatusOK || tt . kubeAPIServerStatusCode != http . StatusOK {
2021-03-12 00:44:08 +00:00
require . True ( t , testKubeAPIServerWasCalled , "Should have proxied the request to the Kube API server, but didn't" )
2021-03-19 17:39:55 +00:00
require . Equal ( t , wantKubeAPIServerRequestHeaders , testKubeAPIServerSawHeaders )
2021-02-23 01:23:11 +00:00
} else {
2021-03-12 00:44:08 +00:00
require . False ( t , testKubeAPIServerWasCalled , "Should not have proxied the request to the Kube API server, but did" )
2021-02-23 01:23:11 +00:00
}
2021-01-20 00:37:02 +00:00
} )
}
}
2021-03-12 01:24:52 +00:00
2021-04-20 15:19:58 +00:00
func newRequest ( t * testing . T , h http . Header , userInfo user . Info , event * auditinternal . Event , token string ) * http . Request {
t . Helper ( )
validURL , err := url . Parse ( "http://pinniped.dev/blah" )
require . NoError ( t , err )
ctx := context . Background ( )
if userInfo != nil {
ctx = request . WithUser ( ctx , userInfo )
}
2022-12-15 18:02:04 +00:00
ctx = audit . WithAuditContext ( ctx )
2021-04-20 15:19:58 +00:00
ae := & auditinternal . Event { Level : auditinternal . LevelMetadata }
if event != nil {
ae = event
}
2022-12-15 18:02:04 +00:00
ac := audit . AuditContextFrom ( ctx )
ac . Event = ae
2021-04-20 15:19:58 +00:00
reqInfo := & request . RequestInfo {
IsResourceRequest : false ,
Path : validURL . Path ,
Verb : "get" ,
}
ctx = request . WithRequestInfo ( ctx , reqInfo )
ctx = authenticator . WithAudiences ( ctx , authenticator . Audiences { "must-be-ignored" } )
if len ( token ) != 0 {
ctx = context . WithValue ( ctx , tokenKey , token )
}
var cancel context . CancelFunc
ctx , cancel = context . WithDeadline ( ctx , time . Now ( ) . Add ( time . Hour ) )
t . Cleanup ( cancel )
r , err := http . NewRequestWithContext ( ctx , http . MethodGet , validURL . String ( ) , nil )
require . NoError ( t , err )
r . Header = h
return r
}
func testTokenAuthenticator ( t * testing . T , token string , userInfo user . Info , err error ) authenticator . Request {
t . Helper ( )
return authenticator . RequestFunc ( func ( r * http . Request ) ( * authenticator . Response , bool , error ) {
if auds , ok := authenticator . AudiencesFrom ( r . Context ( ) ) ; ok || len ( auds ) != 0 {
t . Errorf ( "unexpected audiences on request: %v" , auds )
}
if ctxToken := tokenFrom ( r . Context ( ) ) ; len ( ctxToken ) != 0 {
t . Errorf ( "unexpected token on request: %v" , ctxToken )
}
if _ , ok := r . Context ( ) . Deadline ( ) ; ! ok {
t . Error ( "request should always have deadline" )
}
if err != nil {
return nil , false , err
}
var reqToken string
_ , _ , _ = bearertoken . New ( authenticator . TokenFunc ( func ( _ context . Context , token string ) ( * authenticator . Response , bool , error ) {
reqToken = token
return nil , false , nil
} ) ) . AuthenticateRequest ( r )
if reqToken != token {
return nil , false , nil
}
return & authenticator . Response { User : userInfo } , true , nil
} )
}
2021-03-12 01:24:52 +00:00
type clientCert struct {
certPEM , keyPEM [ ] byte
}
func newClientCert ( t * testing . T , ca * certauthority . CA , username string , groups [ ] string ) * clientCert {
2021-04-09 21:52:53 +00:00
t . Helper ( )
2021-03-13 00:09:16 +00:00
certPEM , keyPEM , err := ca . IssueClientCertPEM ( username , groups , time . Hour )
2021-03-12 01:24:52 +00:00
require . NoError ( t , err )
return & clientCert {
certPEM : certPEM ,
keyPEM : keyPEM ,
}
}
func requireCanBindToPort ( t * testing . T , port int ) {
2021-04-09 21:52:53 +00:00
t . Helper ( )
2021-03-12 01:24:52 +00:00
ln , _ , listenErr := genericoptions . CreateListener ( "" , "0.0.0.0:" + strconv . Itoa ( port ) , net . ListenConfig { } )
require . NoError ( t , listenErr )
require . NoError ( t , ln . Close ( ) )
}
2021-04-09 21:52:53 +00:00
2021-04-20 15:19:58 +00:00
func Test_withBearerTokenPreservation ( t * testing . T ) {
tests := [ ] struct {
name string
headers http . Header
want string
} {
{
name : "has bearer token" ,
headers : map [ string ] [ ] string {
"Authorization" : { "Bearer thingy" } ,
} ,
want : "thingy" ,
} ,
{
name : "has bearer token but too many preceding spaces" ,
headers : map [ string ] [ ] string {
"Authorization" : { "Bearer 1" } ,
} ,
want : "" ,
} ,
{
name : "has bearer token with space, only keeps first part" ,
headers : map [ string ] [ ] string {
"Authorization" : { "Bearer panda man" } ,
} ,
want : "panda" ,
} ,
{
name : "has bearer token with surrounding whitespace" ,
headers : map [ string ] [ ] string {
"Authorization" : { " Bearer cool beans " } ,
} ,
want : "cool" ,
} ,
{
name : "has multiple bearer tokens" ,
headers : map [ string ] [ ] string {
"Authorization" : { "Bearer this thing" , "what does this mean?" } ,
} ,
want : "this" ,
} ,
{
name : "no bearer token" ,
headers : map [ string ] [ ] string {
"Not-Authorization" : { "Bearer not a token" } ,
} ,
want : "" ,
} ,
}
for _ , tt := range tests {
tt := tt
t . Run ( tt . name , func ( t * testing . T ) {
inputReq := ( & http . Request { Header : tt . headers } ) . WithContext ( context . Background ( ) )
inputReqCopy := inputReq . Clone ( inputReq . Context ( ) )
var called bool
delegate := http . HandlerFunc ( func ( w http . ResponseWriter , outputReq * http . Request ) {
called = true
require . Nil ( t , w )
// assert only context is mutated
outputReqCopy := outputReq . Clone ( inputReq . Context ( ) )
require . Equal ( t , inputReqCopy , outputReqCopy )
require . Equal ( t , tt . want , tokenFrom ( outputReq . Context ( ) ) )
if len ( tt . want ) == 0 {
require . True ( t , inputReq == outputReq , "expect req to passed through when no token expected" )
}
} )
withBearerTokenPreservation ( delegate ) . ServeHTTP ( nil , inputReq )
require . Equal ( t , inputReqCopy , inputReq ) // assert no mutation occurred
require . True ( t , called )
2021-04-09 21:52:53 +00:00
} )
}
}
2021-06-11 18:03:18 +00:00
type attributeRecorder struct {
lock sync . Mutex
attributes [ ] authorizer . AttributesRecord
}
func ( r * attributeRecorder ) record ( attributes authorizer . Attributes ) {
r . lock . Lock ( )
defer r . lock . Unlock ( )
r . attributes = append ( r . attributes , * attributes . ( * authorizer . AttributesRecord ) )
}