Browse code

make node auth use tokenreview API

deads2k authored on 2016/07/12 23:56:03
Showing 14 changed files
... ...
@@ -6,6 +6,7 @@ import (
6 6
 
7 7
 	// we have a strong dependency on kube objects for deployments and scale
8 8
 	_ "k8s.io/kubernetes/pkg/api/install"
9
+	_ "k8s.io/kubernetes/pkg/apis/authentication/install"
9 10
 	_ "k8s.io/kubernetes/pkg/apis/autoscaling/install"
10 11
 	_ "k8s.io/kubernetes/pkg/apis/batch/install"
11 12
 	_ "k8s.io/kubernetes/pkg/apis/extensions/install"
12 13
new file mode 100644
... ...
@@ -0,0 +1,52 @@
0
+package remotetokenreview
1
+
2
+import (
3
+	"errors"
4
+
5
+	"k8s.io/kubernetes/pkg/apis/authentication"
6
+	"k8s.io/kubernetes/pkg/auth/user"
7
+	unversionedauthentication "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/authentication/unversioned"
8
+)
9
+
10
+type Authenticator struct {
11
+	authenticationClient unversionedauthentication.TokenReviewsGetter
12
+}
13
+
14
+// NewAuthenticator authenticates by doing a tokenreview
15
+func NewAuthenticator(authenticationClient unversionedauthentication.TokenReviewsGetter) (*Authenticator, error) {
16
+	return &Authenticator{
17
+		authenticationClient: authenticationClient,
18
+	}, nil
19
+}
20
+
21
+func (a *Authenticator) AuthenticateToken(value string) (user.Info, bool, error) {
22
+	if len(value) == 0 {
23
+		return nil, false, nil
24
+	}
25
+	tokenReview := &authentication.TokenReview{}
26
+	tokenReview.Spec.Token = value
27
+
28
+	response, err := a.authenticationClient.TokenReviews().Create(tokenReview)
29
+	if err != nil {
30
+		return nil, false, err
31
+	}
32
+
33
+	if len(response.Status.Error) > 0 {
34
+		return nil, false, errors.New(response.Status.Error)
35
+	}
36
+	if !response.Status.Authenticated {
37
+		return nil, false, nil
38
+	}
39
+
40
+	userInfo := &user.DefaultInfo{
41
+		Name:   response.Status.User.Username,
42
+		UID:    response.Status.User.UID,
43
+		Groups: response.Status.User.Groups,
44
+		Extra:  map[string][]string{},
45
+	}
46
+	for k, v := range response.Status.User.Extra {
47
+		userInfo.Extra[k] = v
48
+	}
49
+
50
+	return userInfo, true, nil
51
+}
... ...
@@ -15,8 +15,7 @@ var _ = kauthorizer.Attributes(AdapterAttributes{})
15 15
 // AdapterAttributes satisfies k8s authorizer.Attributes interfaces
16 16
 type AdapterAttributes struct {
17 17
 	namespace               string
18
-	userName                string
19
-	groups                  []string
18
+	user                    user.Info
20 19
 	authorizationAttributes oauthorizer.AuthorizationAttributes
21 20
 }
22 21
 
... ...
@@ -26,10 +25,7 @@ func OriginAuthorizerAttributes(kattrs kauthorizer.Attributes) (kapi.Context, oa
26 26
 	// Build a context to hold the namespace and user info
27 27
 	ctx := kapi.NewContext()
28 28
 	ctx = kapi.WithNamespace(ctx, kattrs.GetNamespace())
29
-	ctx = kapi.WithUser(ctx, &user.DefaultInfo{
30
-		Name:   kattrs.GetUserName(),
31
-		Groups: kattrs.GetGroups(),
32
-	})
29
+	ctx = kapi.WithUser(ctx, kattrs.GetUser())
33 30
 
34 31
 	// If we recognize the type, use the embedded type.  Do NOT use it directly, because not all things that quack are ducks.
35 32
 	if castAdapterAttributes, ok := kattrs.(AdapterAttributes); ok {
... ...
@@ -59,11 +55,10 @@ func OriginAuthorizerAttributes(kattrs kauthorizer.Attributes) (kapi.Context, oa
59 59
 
60 60
 // KubernetesAuthorizerAttributes adapts Origin authorization attributes to Kubernetes authorization attributes
61 61
 // The returned attributes can be passed to OriginAuthorizerAttributes to access extra information from the Origin attributes interface
62
-func KubernetesAuthorizerAttributes(namespace string, userName string, groups []string, oattrs oauthorizer.AuthorizationAttributes) kauthorizer.Attributes {
62
+func KubernetesAuthorizerAttributes(namespace string, user user.Info, oattrs oauthorizer.AuthorizationAttributes) kauthorizer.Attributes {
63 63
 	return AdapterAttributes{
64
-		namespace:               namespace,
65
-		userName:                userName,
66
-		groups:                  groups,
64
+		namespace: namespace,
65
+		user:      user,
67 66
 		authorizationAttributes: oattrs,
68 67
 	}
69 68
 }
... ...
@@ -108,14 +103,8 @@ func (a AdapterAttributes) GetResource() string {
108 108
 
109 109
 // GetUserName satisfies the kubernetes authorizer.Attributes interface
110 110
 // origin gets this value from the request context
111
-func (a AdapterAttributes) GetUserName() string {
112
-	return a.userName
113
-}
114
-
115
-// GetGroups satisfies the kubernetes authorizer.Attributes interface
116
-// origin gets this value from the request context
117
-func (a AdapterAttributes) GetGroups() []string {
118
-	return a.groups
111
+func (a AdapterAttributes) GetUser() user.Info {
112
+	return a.user
119 113
 }
120 114
 
121 115
 // IsReadOnly satisfies the kubernetes authorizer.Attributes interface based on the verb
... ...
@@ -6,6 +6,7 @@ import (
6 6
 
7 7
 	kapi "k8s.io/kubernetes/pkg/api"
8 8
 	kauthorizer "k8s.io/kubernetes/pkg/auth/authorizer"
9
+	"k8s.io/kubernetes/pkg/auth/user"
9 10
 	"k8s.io/kubernetes/pkg/util/sets"
10 11
 
11 12
 	oauthorizer "github.com/openshift/origin/pkg/authorization/authorizer"
... ...
@@ -28,12 +29,12 @@ func TestRoundTrip(t *testing.T) {
28 28
 	}
29 29
 
30 30
 	// Convert to kube attributes
31
-	kattrs := KubernetesAuthorizerAttributes("ns", "myuser", []string{"mygroup"}, oattrs)
32
-	if kattrs.GetUserName() != "myuser" {
33
-		t.Errorf("Expected %v, got %v", "myuser", kattrs.GetUserName())
31
+	kattrs := KubernetesAuthorizerAttributes("ns", &user.DefaultInfo{Name: "myuser", Groups: []string{"mygroup"}}, oattrs)
32
+	if kattrs.GetUser().GetName() != "myuser" {
33
+		t.Errorf("Expected %v, got %v", "myuser", kattrs.GetUser().GetName())
34 34
 	}
35
-	if !reflect.DeepEqual(kattrs.GetGroups(), []string{"mygroup"}) {
36
-		t.Errorf("Expected %v, got %v", []string{"mygroup"}, kattrs.GetGroups())
35
+	if !reflect.DeepEqual(kattrs.GetUser().GetGroups(), []string{"mygroup"}) {
36
+		t.Errorf("Expected %v, got %v", []string{"mygroup"}, kattrs.GetUser().GetGroups())
37 37
 	}
38 38
 	if kattrs.GetVerb() != "get" {
39 39
 		t.Errorf("Expected %v, got %v", "get", kattrs.GetVerb())
... ...
@@ -104,7 +105,7 @@ func TestAttributeIntersection(t *testing.T) {
104 104
 	// Everything in this list should be used by OriginAuthorizerAttributes or derivative (like IsReadOnly)
105 105
 	expectedKubernetesOnly := sets.NewString(
106 106
 		// used to build context in OriginAuthorizerAttributes
107
-		"GetGroups", "GetUserName", "GetNamespace",
107
+		"GetUser", "GetNamespace",
108 108
 		// Based on verb, derivative
109 109
 		"IsReadOnly",
110 110
 		// Non-matching, but used
... ...
@@ -14,6 +14,7 @@ import (
14 14
 	utilruntime "k8s.io/kubernetes/pkg/util/runtime"
15 15
 	"k8s.io/kubernetes/pkg/util/sets"
16 16
 
17
+	authorizationapi "github.com/openshift/origin/pkg/authorization/api"
17 18
 	"github.com/openshift/origin/pkg/authorization/authorizer"
18 19
 )
19 20
 
... ...
@@ -144,6 +145,7 @@ func cacheKey(ctx kapi.Context, a authorizer.AuthorizationAttributes) (string, e
144 144
 	if user, ok := kapi.UserFrom(ctx); ok {
145 145
 		keyData["user"] = user.GetName()
146 146
 		keyData["groups"] = user.GetGroups()
147
+		keyData["scopes"] = user.GetExtra()[authorizationapi.ScopesKey]
147 148
 	}
148 149
 
149 150
 	key, err := json.Marshal(keyData)
... ...
@@ -48,7 +48,7 @@ func TestCacheKey(t *testing.T) {
48 48
 				NonResourceURL:    true,
49 49
 				URL:               "/abc",
50 50
 			},
51
-			ExpectedKey: `{"apiGroup":"ag","apiVersion":"av","groups":["group1","group2"],"namespace":"myns","nonResourceURL":true,"resource":"r","resourceName":"rn","url":"/abc","user":"me","verb":"v"}`,
51
+			ExpectedKey: `{"apiGroup":"ag","apiVersion":"av","groups":["group1","group2"],"namespace":"myns","nonResourceURL":true,"resource":"r","resourceName":"rn","scopes":null,"url":"/abc","user":"me","verb":"v"}`,
52 52
 		},
53 53
 	}
54 54
 
... ...
@@ -39,21 +39,23 @@ var (
39 39
 	// exposed externally.
40 40
 	DeadOpenShiftStorageVersionLevels = []string{"v1beta1", "v1beta3"}
41 41
 
42
-	APIGroupKube        = ""
43
-	APIGroupExtensions  = "extensions"
44
-	APIGroupAutoscaling = "autoscaling"
45
-	APIGroupBatch       = "batch"
46
-	APIGroupPolicy      = "policy"
47
-	APIGroupApps        = "apps"
48
-	APIGroupFederation  = "federation"
42
+	APIGroupKube           = ""
43
+	APIGroupExtensions     = "extensions"
44
+	APIGroupAutoscaling    = "autoscaling"
45
+	APIGroupAuthentication = "authentication.k8s.io"
46
+	APIGroupBatch          = "batch"
47
+	APIGroupPolicy         = "policy"
48
+	APIGroupApps           = "apps"
49
+	APIGroupFederation     = "federation"
49 50
 
50 51
 	// Map of group names to allowed REST API versions
51 52
 	KubeAPIGroupsToAllowedVersions = map[string][]string{
52
-		APIGroupKube:        {"v1"},
53
-		APIGroupExtensions:  {"v1beta1"},
54
-		APIGroupAutoscaling: {"v1"},
55
-		APIGroupBatch:       {"v1", "v2alpha1"},
56
-		APIGroupApps:        {"v1alpha1"},
53
+		APIGroupKube:           {"v1"},
54
+		APIGroupExtensions:     {"v1beta1"},
55
+		APIGroupAutoscaling:    {"v1"},
56
+		APIGroupAuthentication: {"v1beta1"},
57
+		APIGroupBatch:          {"v1", "v2alpha1"},
58
+		APIGroupApps:           {"v1alpha1"},
57 59
 		// TODO: enable as part of a separate binary
58 60
 		//APIGroupFederation:  {"v1beta1"},
59 61
 	}
... ...
@@ -154,6 +154,7 @@ func GetBootstrapClusterRoles() []authorizationapi.ClusterRole {
154 154
 				// permissions to check access.  These creates are non-mutating
155 155
 				authorizationapi.NewRule("create").Groups(authzGroup).Resources("localresourceaccessreviews", "localsubjectaccessreviews", "resourceaccessreviews",
156 156
 					"selfsubjectrulesreviews", "subjectaccessreviews").RuleOrDie(),
157
+				authorizationapi.NewRule("create").Groups("authentication.k8s.io").Resources("tokenreviews").RuleOrDie(),
157 158
 				// Allow read access to node metrics
158 159
 				authorizationapi.NewRule("get").Groups(kapiGroup).Resources(authorizationapi.NodeMetricsResource).RuleOrDie(),
159 160
 				// Allow read access to stats
... ...
@@ -543,6 +544,7 @@ func GetBootstrapClusterRoles() []authorizationapi.ClusterRole {
543 543
 			},
544 544
 			Rules: []authorizationapi.PolicyRule{
545 545
 				// Needed to check API access.  These creates are non-mutating
546
+				authorizationapi.NewRule("create").Groups("authentication.k8s.io").Resources("tokenreviews").RuleOrDie(),
546 547
 				authorizationapi.NewRule("create").Groups(authzGroup).Resources("subjectaccessreviews", "localsubjectaccessreviews").RuleOrDie(),
547 548
 				// Needed to build serviceLister, to populate env vars for services
548 549
 				authorizationapi.NewRule(read...).Groups(kapiGroup).Resources("services").RuleOrDie(),
... ...
@@ -21,6 +21,7 @@ import (
21 21
 	"k8s.io/kubernetes/pkg/apis/batch"
22 22
 	"k8s.io/kubernetes/pkg/apis/extensions"
23 23
 	"k8s.io/kubernetes/pkg/apiserver"
24
+	"k8s.io/kubernetes/pkg/auth/authenticator"
24 25
 	kclient "k8s.io/kubernetes/pkg/client/unversioned"
25 26
 	"k8s.io/kubernetes/pkg/cloudprovider"
26 27
 	"k8s.io/kubernetes/pkg/genericapiserver"
... ...
@@ -53,7 +54,7 @@ type MasterConfig struct {
53 53
 	Informers shared.InformerFactory
54 54
 }
55 55
 
56
-func BuildKubernetesMasterConfig(options configapi.MasterConfig, requestContextMapper kapi.RequestContextMapper, kubeClient *kclient.Client, informers shared.InformerFactory, admissionControl admission.Interface) (*MasterConfig, error) {
56
+func BuildKubernetesMasterConfig(options configapi.MasterConfig, requestContextMapper kapi.RequestContextMapper, kubeClient *kclient.Client, informers shared.InformerFactory, admissionControl admission.Interface, originAuthenticator authenticator.Request) (*MasterConfig, error) {
57 57
 	if options.KubernetesMasterConfig == nil {
58 58
 		return nil, errors.New("insufficient information to build KubernetesMasterConfig")
59 59
 	}
... ...
@@ -197,6 +198,7 @@ func BuildKubernetesMasterConfig(options configapi.MasterConfig, requestContextM
197 197
 			PublicAddress: publicAddress,
198 198
 			ReadWritePort: port,
199 199
 
200
+			Authenticator:    originAuthenticator, // this is used to fulfill the tokenreviews endpoint which is used by node authentication
200 201
 			Authorizer:       apiserver.NewAlwaysAllowAuthorizer(),
201 202
 			AdmissionControl: admissionControl,
202 203
 
... ...
@@ -11,7 +11,7 @@ import (
11 11
 	"k8s.io/kubernetes/pkg/auth/authenticator"
12 12
 	kauthorizer "k8s.io/kubernetes/pkg/auth/authorizer"
13 13
 	"k8s.io/kubernetes/pkg/auth/user"
14
-	"k8s.io/kubernetes/pkg/client/restclient"
14
+	unversionedauthentication "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/authentication/unversioned"
15 15
 
16 16
 	oauthenticator "github.com/openshift/origin/pkg/auth/authenticator"
17 17
 	"github.com/openshift/origin/pkg/auth/authenticator/anonymous"
... ...
@@ -19,7 +19,7 @@ import (
19 19
 	"github.com/openshift/origin/pkg/auth/authenticator/request/unionrequest"
20 20
 	"github.com/openshift/origin/pkg/auth/authenticator/request/x509request"
21 21
 	authncache "github.com/openshift/origin/pkg/auth/authenticator/token/cache"
22
-	authnremote "github.com/openshift/origin/pkg/auth/authenticator/token/remotemaster"
22
+	authnremote "github.com/openshift/origin/pkg/auth/authenticator/token/remotetokenreview"
23 23
 	"github.com/openshift/origin/pkg/auth/group"
24 24
 	authorizationapi "github.com/openshift/origin/pkg/authorization/api"
25 25
 	oauthorizer "github.com/openshift/origin/pkg/authorization/authorizer"
... ...
@@ -30,7 +30,7 @@ import (
30 30
 	"github.com/openshift/origin/pkg/cmd/server/bootstrappolicy"
31 31
 )
32 32
 
33
-func newAuthenticator(clientCAs *x509.CertPool, anonymousConfig restclient.Config, cacheTTL time.Duration, cacheSize int) (authenticator.Request, error) {
33
+func newAuthenticator(authenticationClient unversionedauthentication.TokenReviewsGetter, clientCAs *x509.CertPool, cacheTTL time.Duration, cacheSize int) (authenticator.Request, error) {
34 34
 	authenticators := []oauthenticator.Request{}
35 35
 
36 36
 	// API token auth
... ...
@@ -39,7 +39,7 @@ func newAuthenticator(clientCAs *x509.CertPool, anonymousConfig restclient.Confi
39 39
 		err                error
40 40
 	)
41 41
 	// Authenticate against the remote master
42
-	tokenAuthenticator, err = authnremote.NewAuthenticator(anonymousConfig)
42
+	tokenAuthenticator, err = authnremote.NewAuthenticator(authenticationClient)
43 43
 	if err != nil {
44 44
 		return nil, err
45 45
 	}
... ...
@@ -107,8 +107,6 @@ func (n NodeAuthorizerAttributesGetter) GetRequestAttributes(u user.Info, r *htt
107 107
 	}
108 108
 
109 109
 	namespace := ""
110
-	userName := u.GetName()
111
-	groups := u.GetGroups()
112 110
 
113 111
 	apiVerb := ""
114 112
 	switch r.Method {
... ...
@@ -139,9 +137,9 @@ func (n NodeAuthorizerAttributesGetter) GetRequestAttributes(u user.Info, r *htt
139 139
 	}
140 140
 	// TODO: handle other things like /healthz/*? not sure if "non-resource" urls on the kubelet make sense to authorize against master non-resource URL policy
141 141
 
142
-	glog.V(2).Infof("Node request attributes: namespace=%s, user=%s, groups=%v, attrs=%#v", namespace, userName, groups, attrs)
142
+	glog.V(2).Infof("Node request attributes: namespace=%s, user=%#v, attrs=%#v", namespace, u, attrs)
143 143
 
144
-	return authzadapter.KubernetesAuthorizerAttributes(namespace, userName, groups, attrs)
144
+	return authzadapter.KubernetesAuthorizerAttributes(namespace, u, attrs)
145 145
 }
146 146
 
147 147
 func newAuthorizer(c *oclient.Client, cacheTTL time.Duration, cacheSize int) (kauthorizer.Authorizer, error) {
... ...
@@ -31,7 +31,6 @@ import (
31 31
 	configapi "github.com/openshift/origin/pkg/cmd/server/api"
32 32
 	"github.com/openshift/origin/pkg/cmd/server/crypto"
33 33
 	cmdutil "github.com/openshift/origin/pkg/cmd/util"
34
-	"github.com/openshift/origin/pkg/cmd/util/clientcmd"
35 34
 	cmdflags "github.com/openshift/origin/pkg/cmd/util/flags"
36 35
 	"github.com/openshift/origin/pkg/cmd/util/variable"
37 36
 	"github.com/openshift/origin/pkg/dns"
... ...
@@ -82,7 +81,7 @@ type NodeConfig struct {
82 82
 }
83 83
 
84 84
 func BuildKubernetesNodeConfig(options configapi.NodeConfig, enableProxy, enableDNS bool) (*NodeConfig, error) {
85
-	originClient, osClientConfig, err := configapi.GetOpenShiftClient(options.MasterKubeConfig)
85
+	originClient, _, err := configapi.GetOpenShiftClient(options.MasterKubeConfig)
86 86
 	if err != nil {
87 87
 		return nil, err
88 88
 	}
... ...
@@ -213,7 +212,7 @@ func BuildKubernetesNodeConfig(options configapi.NodeConfig, enableProxy, enable
213 213
 	if err != nil {
214 214
 		return nil, err
215 215
 	}
216
-	authn, err := newAuthenticator(clientCAs, clientcmd.AnonymousClientConfig(osClientConfig), authnTTL, options.AuthConfig.AuthenticationCacheSize)
216
+	authn, err := newAuthenticator(cfg.KubeClient.Authentication(), clientCAs, authnTTL, options.AuthConfig.AuthenticationCacheSize)
217 217
 	if err != nil {
218 218
 		return nil, err
219 219
 	}
... ...
@@ -336,7 +336,7 @@ func BuildKubernetesMasterConfig(openshiftConfig *origin.MasterConfig) (*kuberne
336 336
 	if openshiftConfig.Options.KubernetesMasterConfig == nil {
337 337
 		return nil, nil
338 338
 	}
339
-	kubeConfig, err := kubernetes.BuildKubernetesMasterConfig(openshiftConfig.Options, openshiftConfig.RequestContextMapper, openshiftConfig.KubeClient(), openshiftConfig.Informers, openshiftConfig.KubeAdmissionControl)
339
+	kubeConfig, err := kubernetes.BuildKubernetesMasterConfig(openshiftConfig.Options, openshiftConfig.RequestContextMapper, openshiftConfig.KubeClient(), openshiftConfig.Informers, openshiftConfig.KubeAdmissionControl, openshiftConfig.Authenticator)
340 340
 	return kubeConfig, err
341 341
 }
342 342
 
... ...
@@ -12,10 +12,13 @@ import (
12 12
 	"k8s.io/kubernetes/pkg/client/restclient"
13 13
 	kubeletclient "k8s.io/kubernetes/pkg/kubelet/client"
14 14
 
15
+	"github.com/openshift/origin/pkg/authorization/authorizer/scope"
15 16
 	"github.com/openshift/origin/pkg/cmd/admin/policy"
16 17
 	configapi "github.com/openshift/origin/pkg/cmd/server/api"
17 18
 	"github.com/openshift/origin/pkg/cmd/server/bootstrappolicy"
19
+	"github.com/openshift/origin/pkg/cmd/server/origin"
18 20
 	"github.com/openshift/origin/pkg/cmd/util/clientcmd"
21
+	oauthapi "github.com/openshift/origin/pkg/oauth/api"
19 22
 	testutil "github.com/openshift/origin/test/util"
20 23
 	testserver "github.com/openshift/origin/test/util/server"
21 24
 )
... ...
@@ -71,6 +74,25 @@ func TestNodeAuth(t *testing.T) {
71 71
 		t.Fatalf("unexpected error: %v", err)
72 72
 	}
73 73
 
74
+	// create a scoped token for bob that is only good for getting user info
75
+	bobUser, err := bobClient.Users().Get("~")
76
+	if err != nil {
77
+		t.Fatalf("unexpected error: %v", err)
78
+	}
79
+	whoamiOnlyBobToken := &oauthapi.OAuthAccessToken{
80
+		ObjectMeta: kapi.ObjectMeta{Name: "whoami-token-plus-some-padding-here-to-make-the-limit"},
81
+		ClientName: origin.OpenShiftCLIClientID,
82
+		ExpiresIn:  200,
83
+		Scopes:     []string{scope.UserIndicator + scope.UserInfo},
84
+		UserName:   bobUser.Name,
85
+		UserUID:    string(bobUser.UID),
86
+	}
87
+	if _, err := originAdminClient.OAuthAccessTokens().Create(whoamiOnlyBobToken); err != nil {
88
+		t.Fatalf("unexpected error: %v", err)
89
+	}
90
+	_, _, bobWhoamiOnlyConfig, err := testutil.GetClientForUser(*adminConfig, "bob")
91
+	bobWhoamiOnlyConfig.BearerToken = whoamiOnlyBobToken.Name
92
+
74 93
 	// Grant sa1 system:cluster-reader, which should let them read metrics and stats
75 94
 	addSA1 := &policy.RoleModificationOptions{
76 95
 		RoleName:            bootstrappolicy.ClusterReaderRoleName,
... ...
@@ -133,6 +155,11 @@ func TestNodeAuth(t *testing.T) {
133 133
 			KubeletClientConfig: kubeletClientConfig(bobConfig),
134 134
 			NodeViewer:          true,
135 135
 		},
136
+		// bob is normally a viewer, but when using a scoped token, he should end up denied
137
+		"bob-scoped": {
138
+			KubeletClientConfig: kubeletClientConfig(bobWhoamiOnlyConfig),
139
+			Forbidden:           true,
140
+		},
136 141
 		"alice": {
137 142
 			KubeletClientConfig: kubeletClientConfig(aliceConfig),
138 143
 			Forbidden:           true,
... ...
@@ -225,7 +252,7 @@ func TestNodeAuth(t *testing.T) {
225 225
 			}
226 226
 			resp.Body.Close()
227 227
 			if resp.StatusCode != r.Result {
228
-				t.Errorf("%s: %s: expected %d, got %d", k, r.Path, r.Result, resp.StatusCode)
228
+				t.Errorf("%s: token=%s %s: expected %d, got %d", k, tc.KubeletClientConfig.BearerToken, r.Path, r.Result, resp.StatusCode)
229 229
 				continue
230 230
 			}
231 231
 		}
... ...
@@ -288,6 +288,13 @@ items:
288 288
     verbs:
289 289
     - create
290 290
   - apiGroups:
291
+    - authentication.k8s.io
292
+    attributeRestrictions: null
293
+    resources:
294
+    - tokenreviews
295
+    verbs:
296
+    - create
297
+  - apiGroups:
291 298
     - ""
292 299
     attributeRestrictions: null
293 300
     resources:
... ...
@@ -1729,6 +1736,13 @@ items:
1729 1729
     name: system:node
1730 1730
   rules:
1731 1731
   - apiGroups:
1732
+    - authentication.k8s.io
1733
+    attributeRestrictions: null
1734
+    resources:
1735
+    - tokenreviews
1736
+    verbs:
1737
+    - create
1738
+  - apiGroups:
1732 1739
     - ""
1733 1740
     attributeRestrictions: null
1734 1741
     resources: