Add generated file
This PR adds generated files under pkg/client and vendor folder.
This commit is contained in:
101
vendor/k8s.io/kubernetes/test/integration/auth/BUILD
generated
vendored
Normal file
101
vendor/k8s.io/kubernetes/test/integration/auth/BUILD
generated
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
size = "large",
|
||||
srcs = [
|
||||
"accessreview_test.go",
|
||||
"auth_test.go",
|
||||
"bootstraptoken_test.go",
|
||||
"main_test.go",
|
||||
"node_test.go",
|
||||
"rbac_test.go",
|
||||
"svcaccttoken_test.go",
|
||||
],
|
||||
tags = ["integration"],
|
||||
deps = [
|
||||
"//pkg/api/legacyscheme:go_default_library",
|
||||
"//pkg/api/testapi:go_default_library",
|
||||
"//pkg/apis/authorization:go_default_library",
|
||||
"//pkg/apis/autoscaling:go_default_library",
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/apis/extensions:go_default_library",
|
||||
"//pkg/apis/policy:go_default_library",
|
||||
"//pkg/apis/rbac:go_default_library",
|
||||
"//pkg/auth/authorizer/abac:go_default_library",
|
||||
"//pkg/auth/nodeidentifier:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
"//pkg/client/informers/informers_generated/internalversion:go_default_library",
|
||||
"//pkg/controller/serviceaccount:go_default_library",
|
||||
"//pkg/features:go_default_library",
|
||||
"//pkg/kubeapiserver/authorizer:go_default_library",
|
||||
"//pkg/master:go_default_library",
|
||||
"//pkg/registry/rbac/clusterrole:go_default_library",
|
||||
"//pkg/registry/rbac/clusterrole/storage:go_default_library",
|
||||
"//pkg/registry/rbac/clusterrolebinding:go_default_library",
|
||||
"//pkg/registry/rbac/clusterrolebinding/storage:go_default_library",
|
||||
"//pkg/registry/rbac/role:go_default_library",
|
||||
"//pkg/registry/rbac/role/storage:go_default_library",
|
||||
"//pkg/registry/rbac/rolebinding:go_default_library",
|
||||
"//pkg/registry/rbac/rolebinding/storage:go_default_library",
|
||||
"//pkg/serviceaccount:go_default_library",
|
||||
"//plugin/pkg/admission/noderestriction:go_default_library",
|
||||
"//plugin/pkg/auth/authenticator/token/bootstrap:go_default_library",
|
||||
"//plugin/pkg/auth/authorizer/rbac:go_default_library",
|
||||
"//test/e2e/lifecycle/bootstrap:go_default_library",
|
||||
"//test/integration:go_default_library",
|
||||
"//test/integration/framework:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/gopkg.in/square/go-jose.v2/jwt:go_default_library",
|
||||
"//vendor/k8s.io/api/authentication/v1:go_default_library",
|
||||
"//vendor/k8s.io/api/authentication/v1beta1:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/api/storage/v1beta1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/authentication/authenticator:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/authentication/group:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/authentication/request/bearertoken:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/authentication/serviceaccount:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/authentication/token/tokenfile:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/authorization/authorizerfactory:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/util/feature/testing:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/plugin/pkg/authenticator/token/tokentest:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/plugin/pkg/authenticator/token/webhook:go_default_library",
|
||||
"//vendor/k8s.io/client-go/informers:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
|
||||
"//vendor/k8s.io/client-go/rest:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/bootstrap/token/api:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/clientcmd/api/v1:go_default_library",
|
||||
"//vendor/k8s.io/client-go/transport:go_default_library",
|
||||
"//vendor/k8s.io/client-go/util/cert:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
343
vendor/k8s.io/kubernetes/test/integration/auth/accessreview_test.go
generated
vendored
Normal file
343
vendor/k8s.io/kubernetes/test/integration/auth/accessreview_test.go
generated
vendored
Normal file
@@ -0,0 +1,343 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/kubernetes/pkg/api/testapi"
|
||||
authorizationapi "k8s.io/kubernetes/pkg/apis/authorization"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
"k8s.io/kubernetes/test/integration/framework"
|
||||
)
|
||||
|
||||
// Inject into master an authorizer that uses user info.
|
||||
// TODO(etune): remove this test once a more comprehensive built-in authorizer is implemented.
|
||||
type sarAuthorizer struct{}
|
||||
|
||||
func (sarAuthorizer) Authorize(a authorizer.Attributes) (authorizer.Decision, string, error) {
|
||||
if a.GetUser().GetName() == "dave" {
|
||||
return authorizer.DecisionNoOpinion, "no", errors.New("I'm sorry, Dave")
|
||||
}
|
||||
|
||||
return authorizer.DecisionAllow, "you're not dave", nil
|
||||
}
|
||||
|
||||
func alwaysAlice(req *http.Request) (user.Info, bool, error) {
|
||||
return &user.DefaultInfo{
|
||||
Name: "alice",
|
||||
}, true, nil
|
||||
}
|
||||
|
||||
func TestSubjectAccessReview(t *testing.T) {
|
||||
masterConfig := framework.NewIntegrationTestMasterConfig()
|
||||
masterConfig.GenericConfig.Authentication.Authenticator = authenticator.RequestFunc(alwaysAlice)
|
||||
masterConfig.GenericConfig.Authorization.Authorizer = sarAuthorizer{}
|
||||
_, s, closeFn := framework.RunAMaster(masterConfig)
|
||||
defer closeFn()
|
||||
|
||||
clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: s.URL, ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Groups[api.GroupName].GroupVersion()}})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
sar *authorizationapi.SubjectAccessReview
|
||||
expectedError string
|
||||
expectedStatus authorizationapi.SubjectAccessReviewStatus
|
||||
}{
|
||||
{
|
||||
name: "simple allow",
|
||||
sar: &authorizationapi.SubjectAccessReview{
|
||||
Spec: authorizationapi.SubjectAccessReviewSpec{
|
||||
ResourceAttributes: &authorizationapi.ResourceAttributes{
|
||||
Verb: "list",
|
||||
Group: api.GroupName,
|
||||
Version: "v1",
|
||||
Resource: "pods",
|
||||
},
|
||||
User: "alice",
|
||||
},
|
||||
},
|
||||
expectedStatus: authorizationapi.SubjectAccessReviewStatus{
|
||||
Allowed: true,
|
||||
Reason: "you're not dave",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "simple deny",
|
||||
sar: &authorizationapi.SubjectAccessReview{
|
||||
Spec: authorizationapi.SubjectAccessReviewSpec{
|
||||
ResourceAttributes: &authorizationapi.ResourceAttributes{
|
||||
Verb: "list",
|
||||
Group: api.GroupName,
|
||||
Version: "v1",
|
||||
Resource: "pods",
|
||||
},
|
||||
User: "dave",
|
||||
},
|
||||
},
|
||||
expectedStatus: authorizationapi.SubjectAccessReviewStatus{
|
||||
Allowed: false,
|
||||
Reason: "no",
|
||||
EvaluationError: "I'm sorry, Dave",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "simple error",
|
||||
sar: &authorizationapi.SubjectAccessReview{
|
||||
Spec: authorizationapi.SubjectAccessReviewSpec{
|
||||
ResourceAttributes: &authorizationapi.ResourceAttributes{
|
||||
Verb: "list",
|
||||
Group: api.GroupName,
|
||||
Version: "v1",
|
||||
Resource: "pods",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedError: "at least one of user or group must be specified",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
response, err := clientset.Authorization().SubjectAccessReviews().Create(test.sar)
|
||||
switch {
|
||||
case err == nil && len(test.expectedError) == 0:
|
||||
|
||||
case err != nil && strings.Contains(err.Error(), test.expectedError):
|
||||
continue
|
||||
|
||||
case err != nil && len(test.expectedError) != 0:
|
||||
t.Errorf("%s: unexpected error: %v", test.name, err)
|
||||
continue
|
||||
default:
|
||||
t.Errorf("%s: expected %v, got %v", test.name, test.expectedError, err)
|
||||
continue
|
||||
}
|
||||
if response.Status != test.expectedStatus {
|
||||
t.Errorf("%s: expected %v, got %v", test.name, test.expectedStatus, response.Status)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelfSubjectAccessReview(t *testing.T) {
|
||||
username := "alice"
|
||||
masterConfig := framework.NewIntegrationTestMasterConfig()
|
||||
masterConfig.GenericConfig.Authentication.Authenticator = authenticator.RequestFunc(func(req *http.Request) (user.Info, bool, error) {
|
||||
return &user.DefaultInfo{Name: username}, true, nil
|
||||
})
|
||||
masterConfig.GenericConfig.Authorization.Authorizer = sarAuthorizer{}
|
||||
_, s, closeFn := framework.RunAMaster(masterConfig)
|
||||
defer closeFn()
|
||||
|
||||
clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: s.URL, ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Groups[api.GroupName].GroupVersion()}})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
username string
|
||||
sar *authorizationapi.SelfSubjectAccessReview
|
||||
expectedError string
|
||||
expectedStatus authorizationapi.SubjectAccessReviewStatus
|
||||
}{
|
||||
{
|
||||
name: "simple allow",
|
||||
username: "alice",
|
||||
sar: &authorizationapi.SelfSubjectAccessReview{
|
||||
Spec: authorizationapi.SelfSubjectAccessReviewSpec{
|
||||
ResourceAttributes: &authorizationapi.ResourceAttributes{
|
||||
Verb: "list",
|
||||
Group: api.GroupName,
|
||||
Version: "v1",
|
||||
Resource: "pods",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedStatus: authorizationapi.SubjectAccessReviewStatus{
|
||||
Allowed: true,
|
||||
Reason: "you're not dave",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "simple deny",
|
||||
username: "dave",
|
||||
sar: &authorizationapi.SelfSubjectAccessReview{
|
||||
Spec: authorizationapi.SelfSubjectAccessReviewSpec{
|
||||
ResourceAttributes: &authorizationapi.ResourceAttributes{
|
||||
Verb: "list",
|
||||
Group: api.GroupName,
|
||||
Version: "v1",
|
||||
Resource: "pods",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedStatus: authorizationapi.SubjectAccessReviewStatus{
|
||||
Allowed: false,
|
||||
Reason: "no",
|
||||
EvaluationError: "I'm sorry, Dave",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
username = test.username
|
||||
|
||||
response, err := clientset.Authorization().SelfSubjectAccessReviews().Create(test.sar)
|
||||
switch {
|
||||
case err == nil && len(test.expectedError) == 0:
|
||||
|
||||
case err != nil && strings.Contains(err.Error(), test.expectedError):
|
||||
continue
|
||||
|
||||
case err != nil && len(test.expectedError) != 0:
|
||||
t.Errorf("%s: unexpected error: %v", test.name, err)
|
||||
continue
|
||||
default:
|
||||
t.Errorf("%s: expected %v, got %v", test.name, test.expectedError, err)
|
||||
continue
|
||||
}
|
||||
if response.Status != test.expectedStatus {
|
||||
t.Errorf("%s: expected %v, got %v", test.name, test.expectedStatus, response.Status)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLocalSubjectAccessReview(t *testing.T) {
|
||||
masterConfig := framework.NewIntegrationTestMasterConfig()
|
||||
masterConfig.GenericConfig.Authentication.Authenticator = authenticator.RequestFunc(alwaysAlice)
|
||||
masterConfig.GenericConfig.Authorization.Authorizer = sarAuthorizer{}
|
||||
_, s, closeFn := framework.RunAMaster(masterConfig)
|
||||
defer closeFn()
|
||||
|
||||
clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: s.URL, ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Groups[api.GroupName].GroupVersion()}})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
namespace string
|
||||
sar *authorizationapi.LocalSubjectAccessReview
|
||||
expectedError string
|
||||
expectedStatus authorizationapi.SubjectAccessReviewStatus
|
||||
}{
|
||||
{
|
||||
name: "simple allow",
|
||||
namespace: "foo",
|
||||
sar: &authorizationapi.LocalSubjectAccessReview{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: "foo"},
|
||||
Spec: authorizationapi.SubjectAccessReviewSpec{
|
||||
ResourceAttributes: &authorizationapi.ResourceAttributes{
|
||||
Verb: "list",
|
||||
Group: api.GroupName,
|
||||
Version: "v1",
|
||||
Resource: "pods",
|
||||
Namespace: "foo",
|
||||
},
|
||||
User: "alice",
|
||||
},
|
||||
},
|
||||
expectedStatus: authorizationapi.SubjectAccessReviewStatus{
|
||||
Allowed: true,
|
||||
Reason: "you're not dave",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "simple deny",
|
||||
namespace: "foo",
|
||||
sar: &authorizationapi.LocalSubjectAccessReview{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: "foo"},
|
||||
Spec: authorizationapi.SubjectAccessReviewSpec{
|
||||
ResourceAttributes: &authorizationapi.ResourceAttributes{
|
||||
Verb: "list",
|
||||
Group: api.GroupName,
|
||||
Version: "v1",
|
||||
Resource: "pods",
|
||||
Namespace: "foo",
|
||||
},
|
||||
User: "dave",
|
||||
},
|
||||
},
|
||||
expectedStatus: authorizationapi.SubjectAccessReviewStatus{
|
||||
Allowed: false,
|
||||
Reason: "no",
|
||||
EvaluationError: "I'm sorry, Dave",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "conflicting namespace",
|
||||
namespace: "foo",
|
||||
sar: &authorizationapi.LocalSubjectAccessReview{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: "foo"},
|
||||
Spec: authorizationapi.SubjectAccessReviewSpec{
|
||||
ResourceAttributes: &authorizationapi.ResourceAttributes{
|
||||
Verb: "list",
|
||||
Group: api.GroupName,
|
||||
Version: "v1",
|
||||
Resource: "pods",
|
||||
Namespace: "bar",
|
||||
},
|
||||
User: "dave",
|
||||
},
|
||||
},
|
||||
expectedError: "must match metadata.namespace",
|
||||
},
|
||||
{
|
||||
name: "missing namespace",
|
||||
namespace: "foo",
|
||||
sar: &authorizationapi.LocalSubjectAccessReview{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: "foo"},
|
||||
Spec: authorizationapi.SubjectAccessReviewSpec{
|
||||
ResourceAttributes: &authorizationapi.ResourceAttributes{
|
||||
Verb: "list",
|
||||
Group: api.GroupName,
|
||||
Version: "v1",
|
||||
Resource: "pods",
|
||||
},
|
||||
User: "dave",
|
||||
},
|
||||
},
|
||||
expectedError: "must match metadata.namespace",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
response, err := clientset.Authorization().LocalSubjectAccessReviews(test.namespace).Create(test.sar)
|
||||
switch {
|
||||
case err == nil && len(test.expectedError) == 0:
|
||||
|
||||
case err != nil && strings.Contains(err.Error(), test.expectedError):
|
||||
continue
|
||||
|
||||
case err != nil && len(test.expectedError) != 0:
|
||||
t.Errorf("%s: unexpected error: %v", test.name, err)
|
||||
continue
|
||||
default:
|
||||
t.Errorf("%s: expected %v, got %v", test.name, test.expectedError, err)
|
||||
continue
|
||||
}
|
||||
if response.Status != test.expectedStatus {
|
||||
t.Errorf("%s: expected %#v, got %#v", test.name, test.expectedStatus, response.Status)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
1288
vendor/k8s.io/kubernetes/test/integration/auth/auth_test.go
generated
vendored
Normal file
1288
vendor/k8s.io/kubernetes/test/integration/auth/auth_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
186
vendor/k8s.io/kubernetes/test/integration/auth/bootstraptoken_test.go
generated
vendored
Normal file
186
vendor/k8s.io/kubernetes/test/integration/auth/bootstraptoken_test.go
generated
vendored
Normal file
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apiserver/pkg/authentication/request/bearertoken"
|
||||
bootstrapapi "k8s.io/client-go/tools/bootstrap/token/api"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/plugin/pkg/auth/authenticator/token/bootstrap"
|
||||
bootstraputil "k8s.io/kubernetes/test/e2e/lifecycle/bootstrap"
|
||||
"k8s.io/kubernetes/test/integration"
|
||||
"k8s.io/kubernetes/test/integration/framework"
|
||||
)
|
||||
|
||||
type bootstrapSecrets []*api.Secret
|
||||
|
||||
func (b bootstrapSecrets) List(selector labels.Selector) (ret []*api.Secret, err error) {
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (b bootstrapSecrets) Get(name string) (*api.Secret, error) {
|
||||
return b[0], nil
|
||||
}
|
||||
|
||||
// TestBootstrapTokenAuth tests the bootstrap token auth provider
|
||||
func TestBootstrapTokenAuth(t *testing.T) {
|
||||
tokenId, err := bootstraputil.GenerateTokenId()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
secret, err := bootstraputil.GenerateTokenSecret()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
var bootstrapSecretValid = &api.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: metav1.NamespaceSystem,
|
||||
Name: bootstrapapi.BootstrapTokenSecretPrefix,
|
||||
},
|
||||
Type: api.SecretTypeBootstrapToken,
|
||||
Data: map[string][]byte{
|
||||
bootstrapapi.BootstrapTokenIDKey: []byte(tokenId),
|
||||
bootstrapapi.BootstrapTokenSecretKey: []byte(secret),
|
||||
bootstrapapi.BootstrapTokenUsageAuthentication: []byte("true"),
|
||||
},
|
||||
}
|
||||
var bootstrapSecretInvalid = &api.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: metav1.NamespaceSystem,
|
||||
Name: bootstrapapi.BootstrapTokenSecretPrefix,
|
||||
},
|
||||
Type: api.SecretTypeBootstrapToken,
|
||||
Data: map[string][]byte{
|
||||
bootstrapapi.BootstrapTokenIDKey: []byte(tokenId),
|
||||
bootstrapapi.BootstrapTokenSecretKey: []byte("invalid"),
|
||||
bootstrapapi.BootstrapTokenUsageAuthentication: []byte("true"),
|
||||
},
|
||||
}
|
||||
var expiredBootstrapToken = &api.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: metav1.NamespaceSystem,
|
||||
Name: bootstrapapi.BootstrapTokenSecretPrefix,
|
||||
},
|
||||
Type: api.SecretTypeBootstrapToken,
|
||||
Data: map[string][]byte{
|
||||
bootstrapapi.BootstrapTokenIDKey: []byte(tokenId),
|
||||
bootstrapapi.BootstrapTokenSecretKey: []byte("invalid"),
|
||||
bootstrapapi.BootstrapTokenUsageAuthentication: []byte("true"),
|
||||
bootstrapapi.BootstrapTokenExpirationKey: []byte(bootstraputil.TimeStringFromNow(-time.Hour)),
|
||||
},
|
||||
}
|
||||
type request struct {
|
||||
verb string
|
||||
URL string
|
||||
body string
|
||||
statusCodes map[int]bool // Set of expected resp.StatusCode if all goes well.
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
request request
|
||||
secret *api.Secret
|
||||
}{
|
||||
{
|
||||
name: "valid token",
|
||||
request: request{verb: "GET", URL: path("pods", "", ""), body: "", statusCodes: integration.Code200},
|
||||
secret: bootstrapSecretValid,
|
||||
},
|
||||
{
|
||||
name: "invalid token format",
|
||||
request: request{verb: "GET", URL: path("pods", "", ""), body: "", statusCodes: integration.Code401},
|
||||
secret: bootstrapSecretInvalid,
|
||||
},
|
||||
{
|
||||
name: "invalid token expired",
|
||||
request: request{verb: "GET", URL: path("pods", "", ""), body: "", statusCodes: integration.Code401},
|
||||
secret: expiredBootstrapToken,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
|
||||
authenticator := bearertoken.New(bootstrap.NewTokenAuthenticator(bootstrapSecrets{test.secret}))
|
||||
// Set up a master
|
||||
masterConfig := framework.NewIntegrationTestMasterConfig()
|
||||
masterConfig.GenericConfig.Authentication.Authenticator = authenticator
|
||||
_, s, closeFn := framework.RunAMaster(masterConfig)
|
||||
defer closeFn()
|
||||
|
||||
ns := framework.CreateTestingNamespace("auth-bootstrap-token", s, t)
|
||||
defer framework.DeleteTestingNamespace(ns, s, t)
|
||||
|
||||
previousResourceVersion := make(map[string]float64)
|
||||
transport := http.DefaultTransport
|
||||
|
||||
token := tokenId + "." + secret
|
||||
var bodyStr string
|
||||
if test.request.body != "" {
|
||||
sub := ""
|
||||
if test.request.verb == "PUT" {
|
||||
// For update operations, insert previous resource version
|
||||
if resVersion := previousResourceVersion[getPreviousResourceVersionKey(test.request.URL, "")]; resVersion != 0 {
|
||||
sub += fmt.Sprintf(",\r\n\"resourceVersion\": \"%v\"", resVersion)
|
||||
}
|
||||
sub += fmt.Sprintf(",\r\n\"namespace\": %q", ns.Name)
|
||||
}
|
||||
bodyStr = fmt.Sprintf(test.request.body, sub)
|
||||
}
|
||||
test.request.body = bodyStr
|
||||
bodyBytes := bytes.NewReader([]byte(bodyStr))
|
||||
req, err := http.NewRequest(test.request.verb, s.URL+test.request.URL, bodyBytes)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||
if test.request.verb == "PATCH" {
|
||||
req.Header.Set("Content-Type", "application/merge-patch+json")
|
||||
}
|
||||
|
||||
func() {
|
||||
resp, err := transport.RoundTrip(req)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
t.Logf("case %v", test.name)
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
b, _ := ioutil.ReadAll(resp.Body)
|
||||
if _, ok := test.request.statusCodes[resp.StatusCode]; !ok {
|
||||
t.Logf("case %v", test.name)
|
||||
t.Errorf("Expected status one of %v, but got %v", test.request.statusCodes, resp.StatusCode)
|
||||
t.Errorf("Body: %v", string(b))
|
||||
} else {
|
||||
if test.request.verb == "POST" {
|
||||
// For successful create operations, extract resourceVersion
|
||||
id, currentResourceVersion, err := parseResourceVersion(b)
|
||||
if err == nil {
|
||||
key := getPreviousResourceVersionKey(test.request.URL, id)
|
||||
previousResourceVersion[key] = currentResourceVersion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}()
|
||||
}
|
||||
}
|
27
vendor/k8s.io/kubernetes/test/integration/auth/main_test.go
generated
vendored
Normal file
27
vendor/k8s.io/kubernetes/test/integration/auth/main_test.go
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/kubernetes/test/integration/framework"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
framework.EtcdMain(m.Run)
|
||||
}
|
551
vendor/k8s.io/kubernetes/test/integration/auth/node_test.go
generated
vendored
Normal file
551
vendor/k8s.io/kubernetes/test/integration/auth/node_test.go
generated
vendored
Normal file
@@ -0,0 +1,551 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
storagev1beta1 "k8s.io/api/storage/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apiserver/pkg/authentication/request/bearertoken"
|
||||
"k8s.io/apiserver/pkg/authentication/token/tokenfile"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
utilfeaturetesting "k8s.io/apiserver/pkg/util/feature/testing"
|
||||
versionedinformers "k8s.io/client-go/informers"
|
||||
externalclientset "k8s.io/client-go/kubernetes"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/apis/policy"
|
||||
"k8s.io/kubernetes/pkg/auth/nodeidentifier"
|
||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
"k8s.io/kubernetes/pkg/kubeapiserver/authorizer"
|
||||
"k8s.io/kubernetes/plugin/pkg/admission/noderestriction"
|
||||
"k8s.io/kubernetes/test/integration/framework"
|
||||
)
|
||||
|
||||
func TestNodeAuthorizer(t *testing.T) {
|
||||
// Start the server so we know the address
|
||||
h := &framework.MasterHolder{Initialized: make(chan struct{})}
|
||||
apiServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
<-h.Initialized
|
||||
h.M.GenericAPIServer.Handler.ServeHTTP(w, req)
|
||||
}))
|
||||
|
||||
const (
|
||||
// Define credentials
|
||||
tokenMaster = "master-token"
|
||||
tokenNodeUnknown = "unknown-token"
|
||||
tokenNode1 = "node1-token"
|
||||
tokenNode2 = "node2-token"
|
||||
)
|
||||
|
||||
authenticator := bearertoken.New(tokenfile.New(map[string]*user.DefaultInfo{
|
||||
tokenMaster: {Name: "admin", Groups: []string{"system:masters"}},
|
||||
tokenNodeUnknown: {Name: "unknown", Groups: []string{"system:nodes"}},
|
||||
tokenNode1: {Name: "system:node:node1", Groups: []string{"system:nodes"}},
|
||||
tokenNode2: {Name: "system:node:node2", Groups: []string{"system:nodes"}},
|
||||
}))
|
||||
|
||||
// Build client config, clientset, and informers
|
||||
clientConfig := &restclient.Config{Host: apiServer.URL, ContentConfig: restclient.ContentConfig{NegotiatedSerializer: legacyscheme.Codecs}}
|
||||
superuserClient, superuserClientExternal := clientsetForToken(tokenMaster, clientConfig)
|
||||
informerFactory := informers.NewSharedInformerFactory(superuserClient, time.Minute)
|
||||
versionedInformerFactory := versionedinformers.NewSharedInformerFactory(superuserClientExternal, time.Minute)
|
||||
|
||||
// Enabled CSIPersistentVolume feature at startup so volumeattachments get watched
|
||||
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIPersistentVolume, true)()
|
||||
|
||||
// Enable DynamicKubeletConfig feature so that Node.Spec.ConfigSource can be set
|
||||
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DynamicKubeletConfig, true)()
|
||||
|
||||
// Set up Node+RBAC authorizer
|
||||
authorizerConfig := &authorizer.AuthorizationConfig{
|
||||
AuthorizationModes: []string{"Node", "RBAC"},
|
||||
InformerFactory: informerFactory,
|
||||
VersionedInformerFactory: versionedInformerFactory,
|
||||
}
|
||||
nodeRBACAuthorizer, _, err := authorizerConfig.New()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Set up NodeRestriction admission
|
||||
nodeRestrictionAdmission := noderestriction.NewPlugin(nodeidentifier.NewDefaultNodeIdentifier())
|
||||
nodeRestrictionAdmission.SetInternalKubeInformerFactory(informerFactory)
|
||||
if err := nodeRestrictionAdmission.ValidateInitialization(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Start the server
|
||||
masterConfig := framework.NewIntegrationTestMasterConfig()
|
||||
masterConfig.GenericConfig.Authentication.Authenticator = authenticator
|
||||
masterConfig.GenericConfig.Authorization.Authorizer = nodeRBACAuthorizer
|
||||
masterConfig.GenericConfig.AdmissionControl = nodeRestrictionAdmission
|
||||
|
||||
_, _, closeFn := framework.RunAMasterUsingServer(masterConfig, apiServer, h)
|
||||
defer closeFn()
|
||||
|
||||
// Start the informers
|
||||
stopCh := make(chan struct{})
|
||||
defer close(stopCh)
|
||||
informerFactory.Start(stopCh)
|
||||
versionedInformerFactory.Start(stopCh)
|
||||
|
||||
// Wait for a healthy server
|
||||
for {
|
||||
result := superuserClient.Core().RESTClient().Get().AbsPath("/healthz").Do()
|
||||
_, err := result.Raw()
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
t.Log(err)
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
|
||||
// Create objects
|
||||
if _, err := superuserClient.Core().Secrets("ns").Create(&api.Secret{ObjectMeta: metav1.ObjectMeta{Name: "mysecret"}}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := superuserClient.Core().Secrets("ns").Create(&api.Secret{ObjectMeta: metav1.ObjectMeta{Name: "mypvsecret"}}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := superuserClient.Core().ConfigMaps("ns").Create(&api.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "myconfigmap"}}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := superuserClient.Core().ConfigMaps("ns").Create(&api.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "myconfigmapconfigsource"}}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pvName := "mypv"
|
||||
if _, err := superuserClientExternal.StorageV1beta1().VolumeAttachments().Create(&storagev1beta1.VolumeAttachment{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "myattachment"},
|
||||
Spec: storagev1beta1.VolumeAttachmentSpec{
|
||||
Attacher: "foo",
|
||||
Source: storagev1beta1.VolumeAttachmentSource{PersistentVolumeName: &pvName},
|
||||
NodeName: "node2",
|
||||
},
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := superuserClient.Core().PersistentVolumeClaims("ns").Create(&api.PersistentVolumeClaim{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "mypvc"},
|
||||
Spec: api.PersistentVolumeClaimSpec{
|
||||
AccessModes: []api.PersistentVolumeAccessMode{api.ReadOnlyMany},
|
||||
Resources: api.ResourceRequirements{Requests: api.ResourceList{api.ResourceStorage: resource.MustParse("1")}},
|
||||
},
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := superuserClient.Core().PersistentVolumes().Create(&api.PersistentVolume{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "mypv"},
|
||||
Spec: api.PersistentVolumeSpec{
|
||||
AccessModes: []api.PersistentVolumeAccessMode{api.ReadOnlyMany},
|
||||
Capacity: api.ResourceList{api.ResourceStorage: resource.MustParse("1")},
|
||||
ClaimRef: &api.ObjectReference{Namespace: "ns", Name: "mypvc"},
|
||||
PersistentVolumeSource: api.PersistentVolumeSource{AzureFile: &api.AzureFilePersistentVolumeSource{ShareName: "default", SecretName: "mypvsecret"}},
|
||||
},
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
getSecret := func(client clientset.Interface) func() error {
|
||||
return func() error {
|
||||
_, err := client.Core().Secrets("ns").Get("mysecret", metav1.GetOptions{})
|
||||
return err
|
||||
}
|
||||
}
|
||||
getPVSecret := func(client clientset.Interface) func() error {
|
||||
return func() error {
|
||||
_, err := client.Core().Secrets("ns").Get("mypvsecret", metav1.GetOptions{})
|
||||
return err
|
||||
}
|
||||
}
|
||||
getConfigMap := func(client clientset.Interface) func() error {
|
||||
return func() error {
|
||||
_, err := client.Core().ConfigMaps("ns").Get("myconfigmap", metav1.GetOptions{})
|
||||
return err
|
||||
}
|
||||
}
|
||||
getConfigMapConfigSource := func(client clientset.Interface) func() error {
|
||||
return func() error {
|
||||
_, err := client.Core().ConfigMaps("ns").Get("myconfigmapconfigsource", metav1.GetOptions{})
|
||||
return err
|
||||
}
|
||||
}
|
||||
getPVC := func(client clientset.Interface) func() error {
|
||||
return func() error {
|
||||
_, err := client.Core().PersistentVolumeClaims("ns").Get("mypvc", metav1.GetOptions{})
|
||||
return err
|
||||
}
|
||||
}
|
||||
getPV := func(client clientset.Interface) func() error {
|
||||
return func() error {
|
||||
_, err := client.Core().PersistentVolumes().Get("mypv", metav1.GetOptions{})
|
||||
return err
|
||||
}
|
||||
}
|
||||
getVolumeAttachment := func(client externalclientset.Interface) func() error {
|
||||
return func() error {
|
||||
_, err := client.StorageV1beta1().VolumeAttachments().Get("myattachment", metav1.GetOptions{})
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
createNode2NormalPod := func(client clientset.Interface) func() error {
|
||||
return func() error {
|
||||
_, err := client.Core().Pods("ns").Create(&api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "node2normalpod"},
|
||||
Spec: api.PodSpec{
|
||||
NodeName: "node2",
|
||||
Containers: []api.Container{{Name: "image", Image: "busybox"}},
|
||||
Volumes: []api.Volume{
|
||||
{Name: "secret", VolumeSource: api.VolumeSource{Secret: &api.SecretVolumeSource{SecretName: "mysecret"}}},
|
||||
{Name: "cm", VolumeSource: api.VolumeSource{ConfigMap: &api.ConfigMapVolumeSource{LocalObjectReference: api.LocalObjectReference{Name: "myconfigmap"}}}},
|
||||
{Name: "pvc", VolumeSource: api.VolumeSource{PersistentVolumeClaim: &api.PersistentVolumeClaimVolumeSource{ClaimName: "mypvc"}}},
|
||||
},
|
||||
},
|
||||
})
|
||||
return err
|
||||
}
|
||||
}
|
||||
updateNode2NormalPodStatus := func(client clientset.Interface) func() error {
|
||||
return func() error {
|
||||
startTime := metav1.NewTime(time.Now())
|
||||
_, err := client.Core().Pods("ns").UpdateStatus(&api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "node2normalpod"},
|
||||
Status: api.PodStatus{StartTime: &startTime},
|
||||
})
|
||||
return err
|
||||
}
|
||||
}
|
||||
deleteNode2NormalPod := func(client clientset.Interface) func() error {
|
||||
return func() error {
|
||||
zero := int64(0)
|
||||
return client.Core().Pods("ns").Delete("node2normalpod", &metav1.DeleteOptions{GracePeriodSeconds: &zero})
|
||||
}
|
||||
}
|
||||
|
||||
createNode2MirrorPod := func(client clientset.Interface) func() error {
|
||||
return func() error {
|
||||
_, err := client.Core().Pods("ns").Create(&api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "node2mirrorpod",
|
||||
Annotations: map[string]string{api.MirrorPodAnnotationKey: "true"},
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
NodeName: "node2",
|
||||
Containers: []api.Container{{Name: "image", Image: "busybox"}},
|
||||
},
|
||||
})
|
||||
return err
|
||||
}
|
||||
}
|
||||
deleteNode2MirrorPod := func(client clientset.Interface) func() error {
|
||||
return func() error {
|
||||
zero := int64(0)
|
||||
return client.Core().Pods("ns").Delete("node2mirrorpod", &metav1.DeleteOptions{GracePeriodSeconds: &zero})
|
||||
}
|
||||
}
|
||||
|
||||
createNode2 := func(client clientset.Interface) func() error {
|
||||
return func() error {
|
||||
_, err := client.Core().Nodes().Create(&api.Node{ObjectMeta: metav1.ObjectMeta{Name: "node2"}})
|
||||
return err
|
||||
}
|
||||
}
|
||||
setNode2ConfigSource := func(client clientset.Interface) func() error {
|
||||
return func() error {
|
||||
node2, err := client.Core().Nodes().Get("node2", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
node2.Spec.ConfigSource = &api.NodeConfigSource{
|
||||
ConfigMap: &api.ConfigMapNodeConfigSource{
|
||||
Namespace: "ns",
|
||||
Name: "myconfigmapconfigsource",
|
||||
KubeletConfigKey: "kubelet",
|
||||
},
|
||||
}
|
||||
_, err = client.Core().Nodes().Update(node2)
|
||||
return err
|
||||
}
|
||||
}
|
||||
unsetNode2ConfigSource := func(client clientset.Interface) func() error {
|
||||
return func() error {
|
||||
node2, err := client.Core().Nodes().Get("node2", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
node2.Spec.ConfigSource = nil
|
||||
_, err = client.Core().Nodes().Update(node2)
|
||||
return err
|
||||
}
|
||||
}
|
||||
updateNode2Status := func(client clientset.Interface) func() error {
|
||||
return func() error {
|
||||
_, err := client.Core().Nodes().UpdateStatus(&api.Node{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "node2"},
|
||||
Status: api.NodeStatus{},
|
||||
})
|
||||
return err
|
||||
}
|
||||
}
|
||||
deleteNode2 := func(client clientset.Interface) func() error {
|
||||
return func() error {
|
||||
return client.Core().Nodes().Delete("node2", nil)
|
||||
}
|
||||
}
|
||||
createNode2NormalPodEviction := func(client clientset.Interface) func() error {
|
||||
return func() error {
|
||||
return client.Policy().Evictions("ns").Evict(&policy.Eviction{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "policy/v1beta1",
|
||||
Kind: "Eviction",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "node2normalpod",
|
||||
Namespace: "ns",
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
createNode2MirrorPodEviction := func(client clientset.Interface) func() error {
|
||||
return func() error {
|
||||
return client.Policy().Evictions("ns").Evict(&policy.Eviction{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "policy/v1beta1",
|
||||
Kind: "Eviction",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "node2mirrorpod",
|
||||
Namespace: "ns",
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
capacity := 50
|
||||
updatePVCCapacity := func(client clientset.Interface) func() error {
|
||||
return func() error {
|
||||
capacity++
|
||||
statusString := fmt.Sprintf("{\"status\": {\"capacity\": {\"storage\": \"%dG\"}}}", capacity)
|
||||
patchBytes := []byte(statusString)
|
||||
_, err := client.Core().PersistentVolumeClaims("ns").Patch("mypvc", types.StrategicMergePatchType, patchBytes, "status")
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
updatePVCPhase := func(client clientset.Interface) func() error {
|
||||
return func() error {
|
||||
patchBytes := []byte(`{"status":{"phase": "Bound"}}`)
|
||||
_, err := client.Core().PersistentVolumeClaims("ns").Patch("mypvc", types.StrategicMergePatchType, patchBytes, "status")
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
nodeanonClient, _ := clientsetForToken(tokenNodeUnknown, clientConfig)
|
||||
node1Client, node1ClientExternal := clientsetForToken(tokenNode1, clientConfig)
|
||||
node2Client, node2ClientExternal := clientsetForToken(tokenNode2, clientConfig)
|
||||
|
||||
// all node requests from node1 and unknown node fail
|
||||
expectForbidden(t, getSecret(nodeanonClient))
|
||||
expectForbidden(t, getPVSecret(nodeanonClient))
|
||||
expectForbidden(t, getConfigMap(nodeanonClient))
|
||||
expectForbidden(t, getPVC(nodeanonClient))
|
||||
expectForbidden(t, getPV(nodeanonClient))
|
||||
expectForbidden(t, createNode2NormalPod(nodeanonClient))
|
||||
expectForbidden(t, createNode2MirrorPod(nodeanonClient))
|
||||
expectForbidden(t, deleteNode2NormalPod(nodeanonClient))
|
||||
expectForbidden(t, deleteNode2MirrorPod(nodeanonClient))
|
||||
expectForbidden(t, createNode2MirrorPodEviction(nodeanonClient))
|
||||
expectForbidden(t, createNode2(nodeanonClient))
|
||||
expectForbidden(t, updateNode2Status(nodeanonClient))
|
||||
expectForbidden(t, deleteNode2(nodeanonClient))
|
||||
|
||||
expectForbidden(t, getSecret(node1Client))
|
||||
expectForbidden(t, getPVSecret(node1Client))
|
||||
expectForbidden(t, getConfigMap(node1Client))
|
||||
expectForbidden(t, getPVC(node1Client))
|
||||
expectForbidden(t, getPV(node1Client))
|
||||
expectForbidden(t, createNode2NormalPod(nodeanonClient))
|
||||
expectForbidden(t, createNode2MirrorPod(node1Client))
|
||||
expectNotFound(t, deleteNode2MirrorPod(node1Client))
|
||||
expectNotFound(t, createNode2MirrorPodEviction(node1Client))
|
||||
expectForbidden(t, createNode2(node1Client))
|
||||
expectForbidden(t, updateNode2Status(node1Client))
|
||||
expectForbidden(t, deleteNode2(node1Client))
|
||||
|
||||
// related object requests from node2 fail
|
||||
expectForbidden(t, getSecret(node2Client))
|
||||
expectForbidden(t, getPVSecret(node2Client))
|
||||
expectForbidden(t, getConfigMap(node2Client))
|
||||
expectForbidden(t, getPVC(node2Client))
|
||||
expectForbidden(t, getPV(node2Client))
|
||||
|
||||
expectForbidden(t, createNode2NormalPod(nodeanonClient))
|
||||
// mirror pod and self node lifecycle is allowed
|
||||
expectAllowed(t, createNode2MirrorPod(node2Client))
|
||||
expectAllowed(t, deleteNode2MirrorPod(node2Client))
|
||||
expectAllowed(t, createNode2MirrorPod(node2Client))
|
||||
expectAllowed(t, createNode2MirrorPodEviction(node2Client))
|
||||
expectAllowed(t, createNode2(node2Client))
|
||||
expectAllowed(t, updateNode2Status(node2Client))
|
||||
expectAllowed(t, deleteNode2(node2Client))
|
||||
|
||||
// create a pod as an admin to add object references
|
||||
expectAllowed(t, createNode2NormalPod(superuserClient))
|
||||
|
||||
// unidentifiable node and node1 are still forbidden
|
||||
expectForbidden(t, getSecret(nodeanonClient))
|
||||
expectForbidden(t, getPVSecret(nodeanonClient))
|
||||
expectForbidden(t, getConfigMap(nodeanonClient))
|
||||
expectForbidden(t, getPVC(nodeanonClient))
|
||||
expectForbidden(t, getPV(nodeanonClient))
|
||||
expectForbidden(t, createNode2NormalPod(nodeanonClient))
|
||||
expectForbidden(t, updateNode2NormalPodStatus(nodeanonClient))
|
||||
expectForbidden(t, deleteNode2NormalPod(nodeanonClient))
|
||||
expectForbidden(t, createNode2NormalPodEviction(nodeanonClient))
|
||||
expectForbidden(t, createNode2MirrorPod(nodeanonClient))
|
||||
expectForbidden(t, deleteNode2MirrorPod(nodeanonClient))
|
||||
expectForbidden(t, createNode2MirrorPodEviction(nodeanonClient))
|
||||
|
||||
expectForbidden(t, getSecret(node1Client))
|
||||
expectForbidden(t, getPVSecret(node1Client))
|
||||
expectForbidden(t, getConfigMap(node1Client))
|
||||
expectForbidden(t, getPVC(node1Client))
|
||||
expectForbidden(t, getPV(node1Client))
|
||||
expectForbidden(t, createNode2NormalPod(node1Client))
|
||||
expectForbidden(t, updateNode2NormalPodStatus(node1Client))
|
||||
expectForbidden(t, deleteNode2NormalPod(node1Client))
|
||||
expectForbidden(t, createNode2NormalPodEviction(node1Client))
|
||||
expectForbidden(t, createNode2MirrorPod(node1Client))
|
||||
expectNotFound(t, deleteNode2MirrorPod(node1Client))
|
||||
expectNotFound(t, createNode2MirrorPodEviction(node1Client))
|
||||
|
||||
// node2 can get referenced objects now
|
||||
expectAllowed(t, getSecret(node2Client))
|
||||
expectAllowed(t, getPVSecret(node2Client))
|
||||
expectAllowed(t, getConfigMap(node2Client))
|
||||
expectAllowed(t, getPVC(node2Client))
|
||||
expectAllowed(t, getPV(node2Client))
|
||||
|
||||
expectForbidden(t, createNode2NormalPod(node2Client))
|
||||
expectAllowed(t, updateNode2NormalPodStatus(node2Client))
|
||||
expectAllowed(t, deleteNode2NormalPod(node2Client))
|
||||
expectAllowed(t, createNode2MirrorPod(node2Client))
|
||||
expectAllowed(t, deleteNode2MirrorPod(node2Client))
|
||||
|
||||
// recreate as an admin to test eviction
|
||||
expectAllowed(t, createNode2NormalPod(superuserClient))
|
||||
expectAllowed(t, createNode2MirrorPod(superuserClient))
|
||||
expectAllowed(t, createNode2NormalPodEviction(node2Client))
|
||||
expectAllowed(t, createNode2MirrorPodEviction(node2Client))
|
||||
|
||||
// re-create a pod as an admin to add object references
|
||||
expectAllowed(t, createNode2NormalPod(superuserClient))
|
||||
|
||||
// ExpandPersistentVolumes feature disabled
|
||||
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ExpandPersistentVolumes, false)()
|
||||
expectForbidden(t, updatePVCCapacity(node1Client))
|
||||
expectForbidden(t, updatePVCCapacity(node2Client))
|
||||
|
||||
// ExpandPersistentVolumes feature enabled
|
||||
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ExpandPersistentVolumes, true)()
|
||||
expectForbidden(t, updatePVCCapacity(node1Client))
|
||||
expectAllowed(t, updatePVCCapacity(node2Client))
|
||||
expectForbidden(t, updatePVCPhase(node2Client))
|
||||
|
||||
// Disabled CSIPersistentVolume feature
|
||||
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIPersistentVolume, false)()
|
||||
expectForbidden(t, getVolumeAttachment(node1ClientExternal))
|
||||
expectForbidden(t, getVolumeAttachment(node2ClientExternal))
|
||||
// Enabled CSIPersistentVolume feature
|
||||
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIPersistentVolume, true)()
|
||||
expectForbidden(t, getVolumeAttachment(node1ClientExternal))
|
||||
expectAllowed(t, getVolumeAttachment(node2ClientExternal))
|
||||
|
||||
// create node2 again
|
||||
expectAllowed(t, createNode2(node2Client))
|
||||
// node2 can not set its own config source
|
||||
expectForbidden(t, setNode2ConfigSource(node2Client))
|
||||
// node2 can not access the configmap config source yet
|
||||
expectForbidden(t, getConfigMapConfigSource(node2Client))
|
||||
// superuser can access the configmap config source
|
||||
expectAllowed(t, getConfigMapConfigSource(superuserClient))
|
||||
// superuser can set node2's config source
|
||||
expectAllowed(t, setNode2ConfigSource(superuserClient))
|
||||
// node2 can now get the configmap assigned as its config source
|
||||
expectAllowed(t, getConfigMapConfigSource(node2Client))
|
||||
// superuser can unset node2's config source
|
||||
expectAllowed(t, unsetNode2ConfigSource(superuserClient))
|
||||
// node2 can no longer get the configmap after it is unassigned as its config source
|
||||
expectForbidden(t, getConfigMapConfigSource(node2Client))
|
||||
// clean up node2
|
||||
expectAllowed(t, deleteNode2(node2Client))
|
||||
|
||||
//TODO(mikedanese): integration test node restriction of TokenRequest
|
||||
}
|
||||
|
||||
// expect executes a function a set number of times until it either returns the
|
||||
// expected error or executes too many times. It returns if the retries timed
|
||||
// out and the last error returned by the method.
|
||||
func expect(t *testing.T, f func() error, wantErr func(error) bool) (timeout bool, lastErr error) {
|
||||
t.Helper()
|
||||
err := wait.PollImmediate(time.Second, 30*time.Second, func() (bool, error) {
|
||||
t.Helper()
|
||||
lastErr = f()
|
||||
if wantErr(lastErr) {
|
||||
return true, nil
|
||||
}
|
||||
t.Logf("unexpected response, will retry: %v", lastErr)
|
||||
return false, nil
|
||||
})
|
||||
return err == nil, lastErr
|
||||
}
|
||||
|
||||
func expectForbidden(t *testing.T, f func() error) {
|
||||
t.Helper()
|
||||
if ok, err := expect(t, f, errors.IsForbidden); !ok {
|
||||
t.Errorf("Expected forbidden error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func expectNotFound(t *testing.T, f func() error) {
|
||||
t.Helper()
|
||||
if ok, err := expect(t, f, errors.IsNotFound); !ok {
|
||||
t.Errorf("Expected notfound error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func expectAllowed(t *testing.T, f func() error) {
|
||||
t.Helper()
|
||||
if ok, err := expect(t, f, func(e error) bool { return e == nil }); !ok {
|
||||
t.Errorf("Expected no error, got %v", err)
|
||||
}
|
||||
}
|
564
vendor/k8s.io/kubernetes/test/integration/auth/rbac_test.go
generated
vendored
Normal file
564
vendor/k8s.io/kubernetes/test/integration/auth/rbac_test.go
generated
vendored
Normal file
@@ -0,0 +1,564 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/apiserver/pkg/authentication/request/bearertoken"
|
||||
"k8s.io/apiserver/pkg/authentication/token/tokenfile"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/apiserver/pkg/registry/generic"
|
||||
externalclientset "k8s.io/client-go/kubernetes"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/transport"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
"k8s.io/kubernetes/pkg/api/testapi"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
rbacapi "k8s.io/kubernetes/pkg/apis/rbac"
|
||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
"k8s.io/kubernetes/pkg/master"
|
||||
"k8s.io/kubernetes/pkg/registry/rbac/clusterrole"
|
||||
clusterrolestore "k8s.io/kubernetes/pkg/registry/rbac/clusterrole/storage"
|
||||
"k8s.io/kubernetes/pkg/registry/rbac/clusterrolebinding"
|
||||
clusterrolebindingstore "k8s.io/kubernetes/pkg/registry/rbac/clusterrolebinding/storage"
|
||||
"k8s.io/kubernetes/pkg/registry/rbac/role"
|
||||
rolestore "k8s.io/kubernetes/pkg/registry/rbac/role/storage"
|
||||
"k8s.io/kubernetes/pkg/registry/rbac/rolebinding"
|
||||
rolebindingstore "k8s.io/kubernetes/pkg/registry/rbac/rolebinding/storage"
|
||||
"k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac"
|
||||
"k8s.io/kubernetes/test/integration/framework"
|
||||
)
|
||||
|
||||
func clientForToken(user string) *http.Client {
|
||||
return &http.Client{
|
||||
Transport: transport.NewBearerAuthRoundTripper(
|
||||
user,
|
||||
transport.DebugWrappers(http.DefaultTransport),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func clientsetForToken(user string, config *restclient.Config) (clientset.Interface, externalclientset.Interface) {
|
||||
configCopy := *config
|
||||
configCopy.BearerToken = user
|
||||
return clientset.NewForConfigOrDie(&configCopy), externalclientset.NewForConfigOrDie(&configCopy)
|
||||
}
|
||||
|
||||
type testRESTOptionsGetter struct {
|
||||
config *master.Config
|
||||
}
|
||||
|
||||
func (getter *testRESTOptionsGetter) GetRESTOptions(resource schema.GroupResource) (generic.RESTOptions, error) {
|
||||
storageConfig, err := getter.config.ExtraConfig.StorageFactory.NewConfig(resource)
|
||||
if err != nil {
|
||||
return generic.RESTOptions{}, fmt.Errorf("failed to get storage: %v", err)
|
||||
}
|
||||
return generic.RESTOptions{StorageConfig: storageConfig, Decorator: generic.UndecoratedStorage, ResourcePrefix: resource.Resource}, nil
|
||||
}
|
||||
|
||||
func newRBACAuthorizer(config *master.Config) authorizer.Authorizer {
|
||||
optsGetter := &testRESTOptionsGetter{config}
|
||||
roleRegistry := role.AuthorizerAdapter{Registry: role.NewRegistry(rolestore.NewREST(optsGetter))}
|
||||
roleBindingRegistry := rolebinding.AuthorizerAdapter{Registry: rolebinding.NewRegistry(rolebindingstore.NewREST(optsGetter))}
|
||||
clusterRoleRegistry := clusterrole.AuthorizerAdapter{Registry: clusterrole.NewRegistry(clusterrolestore.NewREST(optsGetter))}
|
||||
clusterRoleBindingRegistry := clusterrolebinding.AuthorizerAdapter{Registry: clusterrolebinding.NewRegistry(clusterrolebindingstore.NewREST(optsGetter))}
|
||||
return rbac.New(roleRegistry, roleBindingRegistry, clusterRoleRegistry, clusterRoleBindingRegistry)
|
||||
}
|
||||
|
||||
// bootstrapRoles are a set of RBAC roles which will be populated before the test.
|
||||
type bootstrapRoles struct {
|
||||
roles []rbacapi.Role
|
||||
roleBindings []rbacapi.RoleBinding
|
||||
clusterRoles []rbacapi.ClusterRole
|
||||
clusterRoleBindings []rbacapi.ClusterRoleBinding
|
||||
}
|
||||
|
||||
// bootstrap uses the provided client to create the bootstrap roles and role bindings.
|
||||
//
|
||||
// client should be authenticated as the RBAC super user.
|
||||
func (b bootstrapRoles) bootstrap(client clientset.Interface) error {
|
||||
for _, r := range b.clusterRoles {
|
||||
_, err := client.Rbac().ClusterRoles().Create(&r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to make request: %v", err)
|
||||
}
|
||||
}
|
||||
for _, r := range b.roles {
|
||||
_, err := client.Rbac().Roles(r.Namespace).Create(&r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to make request: %v", err)
|
||||
}
|
||||
}
|
||||
for _, r := range b.clusterRoleBindings {
|
||||
_, err := client.Rbac().ClusterRoleBindings().Create(&r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to make request: %v", err)
|
||||
}
|
||||
}
|
||||
for _, r := range b.roleBindings {
|
||||
_, err := client.Rbac().RoleBindings(r.Namespace).Create(&r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to make request: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// request is a test case which can.
|
||||
type request struct {
|
||||
// The bearer token sent as part of the request
|
||||
token string
|
||||
|
||||
// Resource metadata
|
||||
verb string
|
||||
apiGroup string
|
||||
resource string
|
||||
namespace string
|
||||
name string
|
||||
|
||||
// The actual resource.
|
||||
body string
|
||||
|
||||
// The expected return status of this request.
|
||||
expectedStatus int
|
||||
}
|
||||
|
||||
func (r request) String() string {
|
||||
return fmt.Sprintf("%s %s %s", r.token, r.verb, r.resource)
|
||||
}
|
||||
|
||||
type statusCode int
|
||||
|
||||
func (s statusCode) String() string {
|
||||
return fmt.Sprintf("%d %s", int(s), http.StatusText(int(s)))
|
||||
}
|
||||
|
||||
// Declare a set of raw objects to use.
|
||||
var (
|
||||
// Make a role binding with the version enabled in testapi.Rbac
|
||||
// This assumes testapi is using rbac.authorization.k8s.io/v1beta1 or rbac.authorization.k8s.io/v1, which are identical in structure.
|
||||
// TODO: rework or remove testapi usage to allow writing integration tests that don't depend on envvars
|
||||
writeJobsRoleBinding = `
|
||||
{
|
||||
"apiVersion": "` + testapi.Rbac.GroupVersion().String() + `",
|
||||
"kind": "RoleBinding",
|
||||
"metadata": {
|
||||
"name": "pi"%s
|
||||
},
|
||||
"roleRef": {
|
||||
"apiGroup": "rbac.authorization.k8s.io",
|
||||
"kind": "ClusterRole",
|
||||
"name": "write-jobs"
|
||||
},
|
||||
"subjects": [{
|
||||
"apiGroup": "rbac.authorization.k8s.io",
|
||||
"kind": "User",
|
||||
"name": "admin"
|
||||
}]
|
||||
}`
|
||||
|
||||
aJob = `
|
||||
{
|
||||
"apiVersion": "batch/v1",
|
||||
"kind": "Job",
|
||||
"metadata": {
|
||||
"name": "pi"%s
|
||||
},
|
||||
"spec": {
|
||||
"template": {
|
||||
"metadata": {
|
||||
"name": "a",
|
||||
"labels": {
|
||||
"name": "pijob"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"containers": [
|
||||
{
|
||||
"name": "pi",
|
||||
"image": "perl",
|
||||
"command": [
|
||||
"perl",
|
||||
"-Mbignum=bpi",
|
||||
"-wle",
|
||||
"print bpi(2000)"
|
||||
]
|
||||
}
|
||||
],
|
||||
"restartPolicy": "Never"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
podNamespace = `
|
||||
{
|
||||
"apiVersion": "` + testapi.Groups[api.GroupName].GroupVersion().String() + `",
|
||||
"kind": "Namespace",
|
||||
"metadata": {
|
||||
"name": "pod-namespace"%s
|
||||
}
|
||||
}
|
||||
`
|
||||
jobNamespace = `
|
||||
{
|
||||
"apiVersion": "` + testapi.Groups[api.GroupName].GroupVersion().String() + `",
|
||||
"kind": "Namespace",
|
||||
"metadata": {
|
||||
"name": "job-namespace"%s
|
||||
}
|
||||
}
|
||||
`
|
||||
forbiddenNamespace = `
|
||||
{
|
||||
"apiVersion": "` + testapi.Groups[api.GroupName].GroupVersion().String() + `",
|
||||
"kind": "Namespace",
|
||||
"metadata": {
|
||||
"name": "forbidden-namespace"%s
|
||||
}
|
||||
}
|
||||
`
|
||||
)
|
||||
|
||||
// Declare some PolicyRules beforehand.
|
||||
var (
|
||||
ruleAllowAll = rbacapi.NewRule("*").Groups("*").Resources("*").RuleOrDie()
|
||||
ruleReadPods = rbacapi.NewRule("list", "get", "watch").Groups("").Resources("pods").RuleOrDie()
|
||||
ruleWriteJobs = rbacapi.NewRule("*").Groups("batch").Resources("*").RuleOrDie()
|
||||
)
|
||||
|
||||
func TestRBAC(t *testing.T) {
|
||||
superUser := "admin/system:masters"
|
||||
|
||||
tests := []struct {
|
||||
bootstrapRoles bootstrapRoles
|
||||
|
||||
requests []request
|
||||
}{
|
||||
{
|
||||
bootstrapRoles: bootstrapRoles{
|
||||
clusterRoles: []rbacapi.ClusterRole{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "allow-all"},
|
||||
Rules: []rbacapi.PolicyRule{ruleAllowAll},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "read-pods"},
|
||||
Rules: []rbacapi.PolicyRule{ruleReadPods},
|
||||
},
|
||||
},
|
||||
clusterRoleBindings: []rbacapi.ClusterRoleBinding{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "read-pods"},
|
||||
Subjects: []rbacapi.Subject{
|
||||
{Kind: "User", Name: "pod-reader"},
|
||||
},
|
||||
RoleRef: rbacapi.RoleRef{Kind: "ClusterRole", Name: "read-pods"},
|
||||
},
|
||||
},
|
||||
},
|
||||
requests: []request{
|
||||
// Create the namespace used later in the test
|
||||
{superUser, "POST", "", "namespaces", "", "", podNamespace, http.StatusCreated},
|
||||
|
||||
{superUser, "GET", "", "pods", "", "", "", http.StatusOK},
|
||||
{superUser, "GET", "", "pods", "pod-namespace", "a", "", http.StatusNotFound},
|
||||
{superUser, "POST", "", "pods", "pod-namespace", "", aPod, http.StatusCreated},
|
||||
{superUser, "GET", "", "pods", "pod-namespace", "a", "", http.StatusOK},
|
||||
|
||||
{"bob", "GET", "", "pods", "", "", "", http.StatusForbidden},
|
||||
{"bob", "GET", "", "pods", "pod-namespace", "a", "", http.StatusForbidden},
|
||||
|
||||
{"pod-reader", "GET", "", "pods", "", "", "", http.StatusOK},
|
||||
{"pod-reader", "POST", "", "pods", "pod-namespace", "", aPod, http.StatusForbidden},
|
||||
},
|
||||
},
|
||||
{
|
||||
bootstrapRoles: bootstrapRoles{
|
||||
clusterRoles: []rbacapi.ClusterRole{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "write-jobs"},
|
||||
Rules: []rbacapi.PolicyRule{ruleWriteJobs},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "create-rolebindings"},
|
||||
Rules: []rbacapi.PolicyRule{
|
||||
rbacapi.NewRule("create").Groups("rbac.authorization.k8s.io").Resources("rolebindings").RuleOrDie(),
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "bind-any-clusterrole"},
|
||||
Rules: []rbacapi.PolicyRule{
|
||||
rbacapi.NewRule("bind").Groups("rbac.authorization.k8s.io").Resources("clusterroles").RuleOrDie(),
|
||||
},
|
||||
},
|
||||
},
|
||||
clusterRoleBindings: []rbacapi.ClusterRoleBinding{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "write-jobs"},
|
||||
Subjects: []rbacapi.Subject{{Kind: "User", Name: "job-writer"}},
|
||||
RoleRef: rbacapi.RoleRef{Kind: "ClusterRole", Name: "write-jobs"},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "create-rolebindings"},
|
||||
Subjects: []rbacapi.Subject{
|
||||
{Kind: "User", Name: "job-writer"},
|
||||
{Kind: "User", Name: "nonescalating-rolebinding-writer"},
|
||||
{Kind: "User", Name: "any-rolebinding-writer"},
|
||||
},
|
||||
RoleRef: rbacapi.RoleRef{Kind: "ClusterRole", Name: "create-rolebindings"},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "bind-any-clusterrole"},
|
||||
Subjects: []rbacapi.Subject{{Kind: "User", Name: "any-rolebinding-writer"}},
|
||||
RoleRef: rbacapi.RoleRef{Kind: "ClusterRole", Name: "bind-any-clusterrole"},
|
||||
},
|
||||
},
|
||||
roleBindings: []rbacapi.RoleBinding{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "write-jobs", Namespace: "job-namespace"},
|
||||
Subjects: []rbacapi.Subject{{Kind: "User", Name: "job-writer-namespace"}},
|
||||
RoleRef: rbacapi.RoleRef{Kind: "ClusterRole", Name: "write-jobs"},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "create-rolebindings", Namespace: "job-namespace"},
|
||||
Subjects: []rbacapi.Subject{
|
||||
{Kind: "User", Name: "job-writer-namespace"},
|
||||
{Kind: "User", Name: "any-rolebinding-writer-namespace"},
|
||||
},
|
||||
RoleRef: rbacapi.RoleRef{Kind: "ClusterRole", Name: "create-rolebindings"},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "bind-any-clusterrole", Namespace: "job-namespace"},
|
||||
Subjects: []rbacapi.Subject{{Kind: "User", Name: "any-rolebinding-writer-namespace"}},
|
||||
RoleRef: rbacapi.RoleRef{Kind: "ClusterRole", Name: "bind-any-clusterrole"},
|
||||
},
|
||||
},
|
||||
},
|
||||
requests: []request{
|
||||
// Create the namespace used later in the test
|
||||
{superUser, "POST", "", "namespaces", "", "", jobNamespace, http.StatusCreated},
|
||||
{superUser, "POST", "", "namespaces", "", "", forbiddenNamespace, http.StatusCreated},
|
||||
|
||||
{"user-with-no-permissions", "POST", "batch", "jobs", "job-namespace", "", aJob, http.StatusForbidden},
|
||||
{"user-with-no-permissions", "GET", "batch", "jobs", "job-namespace", "pi", "", http.StatusForbidden},
|
||||
|
||||
// job-writer-namespace cannot write to the "forbidden-namespace"
|
||||
{"job-writer-namespace", "GET", "batch", "jobs", "forbidden-namespace", "", "", http.StatusForbidden},
|
||||
{"job-writer-namespace", "GET", "batch", "jobs", "forbidden-namespace", "pi", "", http.StatusForbidden},
|
||||
{"job-writer-namespace", "POST", "batch", "jobs", "forbidden-namespace", "", aJob, http.StatusForbidden},
|
||||
{"job-writer-namespace", "GET", "batch", "jobs", "forbidden-namespace", "pi", "", http.StatusForbidden},
|
||||
|
||||
// job-writer can write to any namespace
|
||||
{"job-writer", "GET", "batch", "jobs", "forbidden-namespace", "", "", http.StatusOK},
|
||||
{"job-writer", "GET", "batch", "jobs", "forbidden-namespace", "pi", "", http.StatusNotFound},
|
||||
{"job-writer", "POST", "batch", "jobs", "forbidden-namespace", "", aJob, http.StatusCreated},
|
||||
{"job-writer", "GET", "batch", "jobs", "forbidden-namespace", "pi", "", http.StatusOK},
|
||||
|
||||
{"job-writer-namespace", "GET", "batch", "jobs", "job-namespace", "", "", http.StatusOK},
|
||||
{"job-writer-namespace", "GET", "batch", "jobs", "job-namespace", "pi", "", http.StatusNotFound},
|
||||
{"job-writer-namespace", "POST", "batch", "jobs", "job-namespace", "", aJob, http.StatusCreated},
|
||||
{"job-writer-namespace", "GET", "batch", "jobs", "job-namespace", "pi", "", http.StatusOK},
|
||||
|
||||
// cannot bind role anywhere
|
||||
{"user-with-no-permissions", "POST", "rbac.authorization.k8s.io", "rolebindings", "job-namespace", "", writeJobsRoleBinding, http.StatusForbidden},
|
||||
// can only bind role in namespace where they have explicit bind permission
|
||||
{"any-rolebinding-writer-namespace", "POST", "rbac.authorization.k8s.io", "rolebindings", "forbidden-namespace", "", writeJobsRoleBinding, http.StatusForbidden},
|
||||
// can only bind role in namespace where they have covering permissions
|
||||
{"job-writer-namespace", "POST", "rbac.authorization.k8s.io", "rolebindings", "forbidden-namespace", "", writeJobsRoleBinding, http.StatusForbidden},
|
||||
{"job-writer-namespace", "POST", "rbac.authorization.k8s.io", "rolebindings", "job-namespace", "", writeJobsRoleBinding, http.StatusCreated},
|
||||
{superUser, "DELETE", "rbac.authorization.k8s.io", "rolebindings", "job-namespace", "pi", "", http.StatusOK},
|
||||
// can bind role in any namespace where they have covering permissions
|
||||
{"job-writer", "POST", "rbac.authorization.k8s.io", "rolebindings", "forbidden-namespace", "", writeJobsRoleBinding, http.StatusCreated},
|
||||
{superUser, "DELETE", "rbac.authorization.k8s.io", "rolebindings", "forbidden-namespace", "pi", "", http.StatusOK},
|
||||
// cannot bind role because they don't have covering permissions
|
||||
{"nonescalating-rolebinding-writer", "POST", "rbac.authorization.k8s.io", "rolebindings", "job-namespace", "", writeJobsRoleBinding, http.StatusForbidden},
|
||||
// can bind role because they have explicit bind permission
|
||||
{"any-rolebinding-writer", "POST", "rbac.authorization.k8s.io", "rolebindings", "job-namespace", "", writeJobsRoleBinding, http.StatusCreated},
|
||||
{superUser, "DELETE", "rbac.authorization.k8s.io", "rolebindings", "job-namespace", "pi", "", http.StatusOK},
|
||||
{"any-rolebinding-writer-namespace", "POST", "rbac.authorization.k8s.io", "rolebindings", "job-namespace", "", writeJobsRoleBinding, http.StatusCreated},
|
||||
{superUser, "DELETE", "rbac.authorization.k8s.io", "rolebindings", "job-namespace", "pi", "", http.StatusOK},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range tests {
|
||||
// Create an API Server.
|
||||
masterConfig := framework.NewIntegrationTestMasterConfig()
|
||||
masterConfig.GenericConfig.Authorization.Authorizer = newRBACAuthorizer(masterConfig)
|
||||
masterConfig.GenericConfig.Authentication.Authenticator = bearertoken.New(tokenfile.New(map[string]*user.DefaultInfo{
|
||||
superUser: {Name: "admin", Groups: []string{"system:masters"}},
|
||||
"any-rolebinding-writer": {Name: "any-rolebinding-writer"},
|
||||
"any-rolebinding-writer-namespace": {Name: "any-rolebinding-writer-namespace"},
|
||||
"bob": {Name: "bob"},
|
||||
"job-writer": {Name: "job-writer"},
|
||||
"job-writer-namespace": {Name: "job-writer-namespace"},
|
||||
"nonescalating-rolebinding-writer": {Name: "nonescalating-rolebinding-writer"},
|
||||
"pod-reader": {Name: "pod-reader"},
|
||||
"user-with-no-permissions": {Name: "user-with-no-permissions"},
|
||||
}))
|
||||
_, s, closeFn := framework.RunAMaster(masterConfig)
|
||||
defer closeFn()
|
||||
|
||||
clientConfig := &restclient.Config{Host: s.URL, ContentConfig: restclient.ContentConfig{NegotiatedSerializer: legacyscheme.Codecs}}
|
||||
|
||||
// Bootstrap the API Server with the test case's initial roles.
|
||||
superuserClient, _ := clientsetForToken(superUser, clientConfig)
|
||||
if err := tc.bootstrapRoles.bootstrap(superuserClient); err != nil {
|
||||
t.Errorf("case %d: failed to apply initial roles: %v", i, err)
|
||||
continue
|
||||
}
|
||||
previousResourceVersion := make(map[string]float64)
|
||||
|
||||
for j, r := range tc.requests {
|
||||
testGroup, ok := testapi.Groups[r.apiGroup]
|
||||
if !ok {
|
||||
t.Errorf("case %d %d: unknown api group %q, %s", i, j, r.apiGroup, r)
|
||||
continue
|
||||
}
|
||||
path := testGroup.ResourcePath(r.resource, r.namespace, r.name)
|
||||
|
||||
var body io.Reader
|
||||
if r.body != "" {
|
||||
sub := ""
|
||||
if r.verb == "PUT" {
|
||||
// For update operations, insert previous resource version
|
||||
if resVersion := previousResourceVersion[getPreviousResourceVersionKey(path, "")]; resVersion != 0 {
|
||||
sub += fmt.Sprintf(",\"resourceVersion\": \"%v\"", resVersion)
|
||||
}
|
||||
}
|
||||
body = strings.NewReader(fmt.Sprintf(r.body, sub))
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(r.verb, s.URL+path, body)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create request: %v", err)
|
||||
}
|
||||
|
||||
func() {
|
||||
reqDump, err := httputil.DumpRequest(req, true)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to dump request: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := clientForToken(r.token).Do(req)
|
||||
if err != nil {
|
||||
t.Errorf("case %d, req %d: failed to make request: %v", i, j, err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respDump, err := httputil.DumpResponse(resp, true)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to dump response: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if resp.StatusCode != r.expectedStatus {
|
||||
// When debugging is on, dump the entire request and response. Very helpful for
|
||||
// debugging malformed test cases.
|
||||
//
|
||||
// To turn on debugging, use the '-args' flag.
|
||||
//
|
||||
// go test -v -tags integration -run RBAC -args -v 10
|
||||
//
|
||||
glog.V(8).Infof("case %d, req %d: %s\n%s\n", i, j, reqDump, respDump)
|
||||
t.Errorf("case %d, req %d: %s expected %q got %q", i, j, r, statusCode(r.expectedStatus), statusCode(resp.StatusCode))
|
||||
}
|
||||
|
||||
b, _ := ioutil.ReadAll(resp.Body)
|
||||
|
||||
if r.verb == "POST" && (resp.StatusCode/100) == 2 {
|
||||
// For successful create operations, extract resourceVersion
|
||||
id, currentResourceVersion, err := parseResourceVersion(b)
|
||||
if err == nil {
|
||||
key := getPreviousResourceVersionKey(path, id)
|
||||
previousResourceVersion[key] = currentResourceVersion
|
||||
} else {
|
||||
t.Logf("error in trying to extract resource version: %s", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBootstrapping(t *testing.T) {
|
||||
superUser := "admin/system:masters"
|
||||
|
||||
masterConfig := framework.NewIntegrationTestMasterConfig()
|
||||
masterConfig.GenericConfig.Authorization.Authorizer = newRBACAuthorizer(masterConfig)
|
||||
masterConfig.GenericConfig.Authentication.Authenticator = bearertoken.New(tokenfile.New(map[string]*user.DefaultInfo{
|
||||
superUser: {Name: "admin", Groups: []string{"system:masters"}},
|
||||
}))
|
||||
_, s, closeFn := framework.RunAMaster(masterConfig)
|
||||
defer closeFn()
|
||||
|
||||
clientset := clientset.NewForConfigOrDie(&restclient.Config{BearerToken: superUser, Host: s.URL, ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Groups[api.GroupName].GroupVersion()}})
|
||||
|
||||
watcher, err := clientset.Rbac().ClusterRoles().Watch(metav1.ListOptions{ResourceVersion: "0"})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
_, err = watch.Until(30*time.Second, watcher, func(event watch.Event) (bool, error) {
|
||||
if event.Type != watch.Added {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
clusterRoles, err := clientset.Rbac().ClusterRoles().List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(clusterRoles.Items) == 0 {
|
||||
t.Fatalf("missing cluster roles")
|
||||
}
|
||||
|
||||
for _, clusterRole := range clusterRoles.Items {
|
||||
if clusterRole.Name == "cluster-admin" {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
t.Errorf("missing cluster-admin: %v", clusterRoles)
|
||||
|
||||
healthBytes, err := clientset.Discovery().RESTClient().Get().AbsPath("/healthz/poststarthook/rbac/bootstrap-roles").DoRaw()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
t.Errorf("error bootstrapping roles: %s", string(healthBytes))
|
||||
}
|
558
vendor/k8s.io/kubernetes/test/integration/auth/svcaccttoken_test.go
generated
vendored
Normal file
558
vendor/k8s.io/kubernetes/test/integration/auth/svcaccttoken_test.go
generated
vendored
Normal file
@@ -0,0 +1,558 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gopkg.in/square/go-jose.v2/jwt"
|
||||
authenticationv1 "k8s.io/api/authentication/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apiserver/pkg/authentication/request/bearertoken"
|
||||
apiserverserviceaccount "k8s.io/apiserver/pkg/authentication/serviceaccount"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizerfactory"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
utilfeaturetesting "k8s.io/apiserver/pkg/util/feature/testing"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
externalclientset "k8s.io/client-go/kubernetes"
|
||||
certutil "k8s.io/client-go/util/cert"
|
||||
"k8s.io/kubernetes/pkg/apis/core"
|
||||
serviceaccountgetter "k8s.io/kubernetes/pkg/controller/serviceaccount"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
"k8s.io/kubernetes/pkg/serviceaccount"
|
||||
"k8s.io/kubernetes/test/integration/framework"
|
||||
)
|
||||
|
||||
const ecdsaPrivateKey = `-----BEGIN EC PRIVATE KEY-----
|
||||
MHcCAQEEIEZmTmUhuanLjPA2CLquXivuwBDHTt5XYwgIr/kA1LtRoAoGCCqGSM49
|
||||
AwEHoUQDQgAEH6cuzP8XuD5wal6wf9M6xDljTOPLX2i8uIp/C/ASqiIGUeeKQtX0
|
||||
/IR3qCXyThP/dbCiHrF3v1cuhBOHY8CLVg==
|
||||
-----END EC PRIVATE KEY-----`
|
||||
|
||||
func TestServiceAccountTokenCreate(t *testing.T) {
|
||||
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.TokenRequest, true)()
|
||||
|
||||
// Build client config, clientset, and informers
|
||||
sk, err := certutil.ParsePrivateKeyPEM([]byte(ecdsaPrivateKey))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
pk := sk.(*ecdsa.PrivateKey).PublicKey
|
||||
|
||||
const iss = "https://foo.bar.example.com"
|
||||
aud := []string{"api"}
|
||||
|
||||
gcs := &clientset.Clientset{}
|
||||
|
||||
// Start the server
|
||||
masterConfig := framework.NewIntegrationTestMasterConfig()
|
||||
masterConfig.GenericConfig.Authorization.Authorizer = authorizerfactory.NewAlwaysAllowAuthorizer()
|
||||
masterConfig.GenericConfig.Authentication.Authenticator = bearertoken.New(
|
||||
serviceaccount.JWTTokenAuthenticator(
|
||||
iss,
|
||||
[]interface{}{&pk},
|
||||
serviceaccount.NewValidator(aud, serviceaccountgetter.NewGetterFromClient(gcs)),
|
||||
),
|
||||
)
|
||||
masterConfig.ExtraConfig.ServiceAccountIssuer = serviceaccount.JWTTokenGenerator(iss, sk)
|
||||
masterConfig.ExtraConfig.ServiceAccountAPIAudiences = aud
|
||||
|
||||
master, _, closeFn := framework.RunAMaster(masterConfig)
|
||||
defer closeFn()
|
||||
|
||||
cs, err := clientset.NewForConfig(master.GenericAPIServer.LoopbackClientConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
*gcs = *cs
|
||||
|
||||
var (
|
||||
sa = &v1.ServiceAccount{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-svcacct",
|
||||
Namespace: "myns",
|
||||
},
|
||||
}
|
||||
pod = &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-pod",
|
||||
Namespace: sa.Namespace,
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
ServiceAccountName: sa.Name,
|
||||
Containers: []v1.Container{{Name: "test-container", Image: "nginx"}},
|
||||
},
|
||||
}
|
||||
otherpod = &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "other-test-pod",
|
||||
Namespace: sa.Namespace,
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
ServiceAccountName: "other-" + sa.Name,
|
||||
Containers: []v1.Container{{Name: "test-container", Image: "nginx"}},
|
||||
},
|
||||
}
|
||||
secret = &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-secret",
|
||||
Namespace: sa.Namespace,
|
||||
},
|
||||
}
|
||||
|
||||
wrongUID = types.UID("wrong")
|
||||
noUID = types.UID("")
|
||||
)
|
||||
|
||||
t.Run("bound to service account", func(t *testing.T) {
|
||||
treq := &authenticationv1.TokenRequest{
|
||||
Spec: authenticationv1.TokenRequestSpec{
|
||||
Audiences: []string{"api"},
|
||||
},
|
||||
}
|
||||
|
||||
if resp, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(sa.Name, treq); err == nil {
|
||||
t.Fatalf("expected err creating token for nonexistant svcacct but got: %#v", resp)
|
||||
}
|
||||
sa, delSvcAcct := createDeleteSvcAcct(t, cs, sa)
|
||||
defer delSvcAcct()
|
||||
|
||||
treq, err = cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(sa.Name, treq)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
checkPayload(t, treq.Status.Token, `"system:serviceaccount:myns:test-svcacct"`, "sub")
|
||||
checkPayload(t, treq.Status.Token, `["api"]`, "aud")
|
||||
checkPayload(t, treq.Status.Token, "null", "kubernetes.io", "pod")
|
||||
checkPayload(t, treq.Status.Token, "null", "kubernetes.io", "secret")
|
||||
checkPayload(t, treq.Status.Token, `"myns"`, "kubernetes.io", "namespace")
|
||||
checkPayload(t, treq.Status.Token, `"test-svcacct"`, "kubernetes.io", "serviceaccount", "name")
|
||||
|
||||
doTokenReview(t, cs, treq, false)
|
||||
delSvcAcct()
|
||||
doTokenReview(t, cs, treq, true)
|
||||
})
|
||||
|
||||
t.Run("bound to service account and pod", func(t *testing.T) {
|
||||
treq := &authenticationv1.TokenRequest{
|
||||
Spec: authenticationv1.TokenRequestSpec{
|
||||
Audiences: []string{"api"},
|
||||
BoundObjectRef: &authenticationv1.BoundObjectReference{
|
||||
Kind: "Pod",
|
||||
APIVersion: "v1",
|
||||
Name: pod.Name,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if resp, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(sa.Name, treq); err == nil {
|
||||
t.Fatalf("expected err creating token for nonexistant svcacct but got: %#v", resp)
|
||||
}
|
||||
sa, del := createDeleteSvcAcct(t, cs, sa)
|
||||
defer del()
|
||||
|
||||
if resp, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(sa.Name, treq); err == nil {
|
||||
t.Fatalf("expected err creating token bound to nonexistant pod but got: %#v", resp)
|
||||
}
|
||||
pod, delPod := createDeletePod(t, cs, pod)
|
||||
defer delPod()
|
||||
|
||||
// right uid
|
||||
treq.Spec.BoundObjectRef.UID = pod.UID
|
||||
if _, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(sa.Name, treq); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
// wrong uid
|
||||
treq.Spec.BoundObjectRef.UID = wrongUID
|
||||
if resp, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(sa.Name, treq); err == nil {
|
||||
t.Fatalf("expected err creating token bound to pod with wrong uid but got: %#v", resp)
|
||||
}
|
||||
// no uid
|
||||
treq.Spec.BoundObjectRef.UID = noUID
|
||||
treq, err = cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(sa.Name, treq)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
checkPayload(t, treq.Status.Token, `"system:serviceaccount:myns:test-svcacct"`, "sub")
|
||||
checkPayload(t, treq.Status.Token, `["api"]`, "aud")
|
||||
checkPayload(t, treq.Status.Token, `"test-pod"`, "kubernetes.io", "pod", "name")
|
||||
checkPayload(t, treq.Status.Token, "null", "kubernetes.io", "secret")
|
||||
checkPayload(t, treq.Status.Token, `"myns"`, "kubernetes.io", "namespace")
|
||||
checkPayload(t, treq.Status.Token, `"test-svcacct"`, "kubernetes.io", "serviceaccount", "name")
|
||||
|
||||
doTokenReview(t, cs, treq, false)
|
||||
delPod()
|
||||
doTokenReview(t, cs, treq, true)
|
||||
})
|
||||
|
||||
t.Run("bound to service account and secret", func(t *testing.T) {
|
||||
treq := &authenticationv1.TokenRequest{
|
||||
Spec: authenticationv1.TokenRequestSpec{
|
||||
Audiences: []string{"api"},
|
||||
BoundObjectRef: &authenticationv1.BoundObjectReference{
|
||||
Kind: "Secret",
|
||||
APIVersion: "v1",
|
||||
Name: secret.Name,
|
||||
UID: secret.UID,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if resp, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(sa.Name, treq); err == nil {
|
||||
t.Fatalf("expected err creating token for nonexistant svcacct but got: %#v", resp)
|
||||
}
|
||||
sa, del := createDeleteSvcAcct(t, cs, sa)
|
||||
defer del()
|
||||
|
||||
if resp, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(sa.Name, treq); err == nil {
|
||||
t.Fatalf("expected err creating token bound to nonexistant secret but got: %#v", resp)
|
||||
}
|
||||
secret, delSecret := createDeleteSecret(t, cs, secret)
|
||||
defer delSecret()
|
||||
|
||||
// right uid
|
||||
treq.Spec.BoundObjectRef.UID = secret.UID
|
||||
if _, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(sa.Name, treq); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
// wrong uid
|
||||
treq.Spec.BoundObjectRef.UID = wrongUID
|
||||
if resp, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(sa.Name, treq); err == nil {
|
||||
t.Fatalf("expected err creating token bound to secret with wrong uid but got: %#v", resp)
|
||||
}
|
||||
// no uid
|
||||
treq.Spec.BoundObjectRef.UID = noUID
|
||||
treq, err = cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(sa.Name, treq)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
checkPayload(t, treq.Status.Token, `"system:serviceaccount:myns:test-svcacct"`, "sub")
|
||||
checkPayload(t, treq.Status.Token, `["api"]`, "aud")
|
||||
checkPayload(t, treq.Status.Token, `null`, "kubernetes.io", "pod")
|
||||
checkPayload(t, treq.Status.Token, `"test-secret"`, "kubernetes.io", "secret", "name")
|
||||
checkPayload(t, treq.Status.Token, `"myns"`, "kubernetes.io", "namespace")
|
||||
checkPayload(t, treq.Status.Token, `"test-svcacct"`, "kubernetes.io", "serviceaccount", "name")
|
||||
|
||||
doTokenReview(t, cs, treq, false)
|
||||
delSecret()
|
||||
doTokenReview(t, cs, treq, true)
|
||||
})
|
||||
|
||||
t.Run("bound to service account and pod running as different service account", func(t *testing.T) {
|
||||
treq := &authenticationv1.TokenRequest{
|
||||
Spec: authenticationv1.TokenRequestSpec{
|
||||
Audiences: []string{"api"},
|
||||
BoundObjectRef: &authenticationv1.BoundObjectReference{
|
||||
Kind: "Pod",
|
||||
APIVersion: "v1",
|
||||
Name: otherpod.Name,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
sa, del := createDeleteSvcAcct(t, cs, sa)
|
||||
defer del()
|
||||
_, del = createDeletePod(t, cs, otherpod)
|
||||
defer del()
|
||||
|
||||
if resp, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(sa.Name, treq); err == nil {
|
||||
t.Fatalf("expected err but got: %#v", resp)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("expired token", func(t *testing.T) {
|
||||
treq := &authenticationv1.TokenRequest{
|
||||
Spec: authenticationv1.TokenRequestSpec{
|
||||
Audiences: []string{"api"},
|
||||
},
|
||||
}
|
||||
|
||||
sa, del := createDeleteSvcAcct(t, cs, sa)
|
||||
defer del()
|
||||
|
||||
treq, err = cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(sa.Name, treq)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
doTokenReview(t, cs, treq, false)
|
||||
|
||||
// backdate the token
|
||||
then := time.Now().Add(-2 * time.Hour)
|
||||
sc := &jwt.Claims{
|
||||
Subject: apiserverserviceaccount.MakeUsername(sa.Namespace, sa.Name),
|
||||
Audience: jwt.Audience([]string{"api"}),
|
||||
IssuedAt: jwt.NewNumericDate(then),
|
||||
NotBefore: jwt.NewNumericDate(then),
|
||||
Expiry: jwt.NewNumericDate(then.Add(time.Duration(60*60) * time.Second)),
|
||||
}
|
||||
coresa := core.ServiceAccount{
|
||||
ObjectMeta: sa.ObjectMeta,
|
||||
}
|
||||
_, pc := serviceaccount.Claims(coresa, nil, nil, 0, nil)
|
||||
tok, err := masterConfig.ExtraConfig.ServiceAccountIssuer.GenerateToken(sc, pc)
|
||||
if err != nil {
|
||||
t.Fatalf("err signing expired token: %v", err)
|
||||
}
|
||||
|
||||
treq.Status.Token = tok
|
||||
doTokenReview(t, cs, treq, true)
|
||||
})
|
||||
|
||||
t.Run("a token without an api audience is invalid", func(t *testing.T) {
|
||||
treq := &authenticationv1.TokenRequest{
|
||||
Spec: authenticationv1.TokenRequestSpec{
|
||||
Audiences: []string{"not-the-api"},
|
||||
},
|
||||
}
|
||||
|
||||
sa, del := createDeleteSvcAcct(t, cs, sa)
|
||||
defer del()
|
||||
|
||||
treq, err = cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(sa.Name, treq)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
doTokenReview(t, cs, treq, true)
|
||||
})
|
||||
|
||||
t.Run("a tokenrequest without an audience is valid against the api", func(t *testing.T) {
|
||||
treq := &authenticationv1.TokenRequest{
|
||||
Spec: authenticationv1.TokenRequestSpec{},
|
||||
}
|
||||
|
||||
sa, del := createDeleteSvcAcct(t, cs, sa)
|
||||
defer del()
|
||||
|
||||
treq, err = cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(sa.Name, treq)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
checkPayload(t, treq.Status.Token, `["api"]`, "aud")
|
||||
|
||||
doTokenReview(t, cs, treq, false)
|
||||
})
|
||||
|
||||
t.Run("a token should be invalid after recreating same name pod", func(t *testing.T) {
|
||||
treq := &authenticationv1.TokenRequest{
|
||||
Spec: authenticationv1.TokenRequestSpec{
|
||||
Audiences: []string{"api"},
|
||||
BoundObjectRef: &authenticationv1.BoundObjectReference{
|
||||
Kind: "Pod",
|
||||
APIVersion: "v1",
|
||||
Name: pod.Name,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
sa, del := createDeleteSvcAcct(t, cs, sa)
|
||||
defer del()
|
||||
originalPod, originalDelPod := createDeletePod(t, cs, pod)
|
||||
defer originalDelPod()
|
||||
|
||||
treq.Spec.BoundObjectRef.UID = originalPod.UID
|
||||
if treq, err = cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(sa.Name, treq); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
checkPayload(t, treq.Status.Token, `"system:serviceaccount:myns:test-svcacct"`, "sub")
|
||||
checkPayload(t, treq.Status.Token, `["api"]`, "aud")
|
||||
checkPayload(t, treq.Status.Token, `"test-pod"`, "kubernetes.io", "pod", "name")
|
||||
checkPayload(t, treq.Status.Token, "null", "kubernetes.io", "secret")
|
||||
checkPayload(t, treq.Status.Token, `"myns"`, "kubernetes.io", "namespace")
|
||||
checkPayload(t, treq.Status.Token, `"test-svcacct"`, "kubernetes.io", "serviceaccount", "name")
|
||||
|
||||
doTokenReview(t, cs, treq, false)
|
||||
originalDelPod()
|
||||
doTokenReview(t, cs, treq, true)
|
||||
|
||||
_, recreateDelPod := createDeletePod(t, cs, pod)
|
||||
defer recreateDelPod()
|
||||
|
||||
doTokenReview(t, cs, treq, true)
|
||||
})
|
||||
|
||||
t.Run("a token should be invalid after recreating same name secret", func(t *testing.T) {
|
||||
treq := &authenticationv1.TokenRequest{
|
||||
Spec: authenticationv1.TokenRequestSpec{
|
||||
Audiences: []string{"api"},
|
||||
BoundObjectRef: &authenticationv1.BoundObjectReference{
|
||||
Kind: "Secret",
|
||||
APIVersion: "v1",
|
||||
Name: secret.Name,
|
||||
UID: secret.UID,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
sa, del := createDeleteSvcAcct(t, cs, sa)
|
||||
defer del()
|
||||
|
||||
originalSecret, originalDelSecret := createDeleteSecret(t, cs, secret)
|
||||
defer originalDelSecret()
|
||||
|
||||
treq.Spec.BoundObjectRef.UID = originalSecret.UID
|
||||
if treq, err = cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(sa.Name, treq); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
checkPayload(t, treq.Status.Token, `"system:serviceaccount:myns:test-svcacct"`, "sub")
|
||||
checkPayload(t, treq.Status.Token, `["api"]`, "aud")
|
||||
checkPayload(t, treq.Status.Token, `null`, "kubernetes.io", "pod")
|
||||
checkPayload(t, treq.Status.Token, `"test-secret"`, "kubernetes.io", "secret", "name")
|
||||
checkPayload(t, treq.Status.Token, `"myns"`, "kubernetes.io", "namespace")
|
||||
checkPayload(t, treq.Status.Token, `"test-svcacct"`, "kubernetes.io", "serviceaccount", "name")
|
||||
|
||||
doTokenReview(t, cs, treq, false)
|
||||
originalDelSecret()
|
||||
doTokenReview(t, cs, treq, true)
|
||||
|
||||
_, recreateDelSecret := createDeleteSecret(t, cs, secret)
|
||||
defer recreateDelSecret()
|
||||
|
||||
doTokenReview(t, cs, treq, true)
|
||||
})
|
||||
}
|
||||
|
||||
func doTokenReview(t *testing.T, cs externalclientset.Interface, treq *authenticationv1.TokenRequest, expectErr bool) {
|
||||
t.Helper()
|
||||
trev, err := cs.AuthenticationV1().TokenReviews().Create(&authenticationv1.TokenReview{
|
||||
Spec: authenticationv1.TokenReviewSpec{
|
||||
Token: treq.Status.Token,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
t.Logf("status: %+v", trev.Status)
|
||||
if (trev.Status.Error != "") && !expectErr {
|
||||
t.Fatalf("expected no error but got: %v", trev.Status.Error)
|
||||
}
|
||||
if (trev.Status.Error == "") && expectErr {
|
||||
t.Fatalf("expected error but got: %+v", trev.Status)
|
||||
}
|
||||
if !trev.Status.Authenticated && !expectErr {
|
||||
t.Fatal("expected token to be authenticated but it wasn't")
|
||||
}
|
||||
}
|
||||
|
||||
func checkPayload(t *testing.T, tok string, want string, parts ...string) {
|
||||
t.Helper()
|
||||
got := getSubObject(t, getPayload(t, tok), parts...)
|
||||
if got != want {
|
||||
t.Errorf("unexpected payload.\nsaw:\t%v\nwant:\t%v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func getSubObject(t *testing.T, b string, parts ...string) string {
|
||||
t.Helper()
|
||||
var obj interface{}
|
||||
obj = make(map[string]interface{})
|
||||
if err := json.Unmarshal([]byte(b), &obj); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
for _, part := range parts {
|
||||
obj = obj.(map[string]interface{})[part]
|
||||
}
|
||||
out, err := json.Marshal(obj)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
return string(out)
|
||||
}
|
||||
|
||||
func getPayload(t *testing.T, b string) string {
|
||||
t.Helper()
|
||||
parts := strings.Split(b, ".")
|
||||
if len(parts) != 3 {
|
||||
t.Fatalf("token did not have three parts: %v", b)
|
||||
}
|
||||
payload, err := base64.RawURLEncoding.DecodeString(parts[1])
|
||||
if err != nil {
|
||||
t.Fatalf("failed to base64 decode token: %v", err)
|
||||
}
|
||||
return string(payload)
|
||||
}
|
||||
|
||||
func createDeleteSvcAcct(t *testing.T, cs clientset.Interface, sa *v1.ServiceAccount) (*v1.ServiceAccount, func()) {
|
||||
t.Helper()
|
||||
sa, err := cs.CoreV1().ServiceAccounts(sa.Namespace).Create(sa)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
done := false
|
||||
return sa, func() {
|
||||
t.Helper()
|
||||
if done {
|
||||
return
|
||||
}
|
||||
done = true
|
||||
if err := cs.CoreV1().ServiceAccounts(sa.Namespace).Delete(sa.Name, nil); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func createDeletePod(t *testing.T, cs clientset.Interface, pod *v1.Pod) (*v1.Pod, func()) {
|
||||
t.Helper()
|
||||
pod, err := cs.CoreV1().Pods(pod.Namespace).Create(pod)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
done := false
|
||||
return pod, func() {
|
||||
t.Helper()
|
||||
if done {
|
||||
return
|
||||
}
|
||||
done = true
|
||||
if err := cs.CoreV1().Pods(pod.Namespace).Delete(pod.Name, nil); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func createDeleteSecret(t *testing.T, cs clientset.Interface, sec *v1.Secret) (*v1.Secret, func()) {
|
||||
t.Helper()
|
||||
sec, err := cs.CoreV1().Secrets(sec.Namespace).Create(sec)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
done := false
|
||||
return sec, func() {
|
||||
t.Helper()
|
||||
if done {
|
||||
return
|
||||
}
|
||||
done = true
|
||||
if err := cs.CoreV1().Secrets(sec.Namespace).Delete(sec.Name, nil); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user