Add generated file

This PR adds generated files under pkg/client and vendor folder.
This commit is contained in:
xing-yang
2018-07-12 10:55:15 -07:00
parent 36b1de0341
commit e213d1890d
17729 changed files with 5090889 additions and 0 deletions

101
vendor/k8s.io/kubernetes/test/integration/auth/BUILD generated vendored Normal file
View 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"],
)

View 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
}
}
}

File diff suppressed because it is too large Load Diff

View 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
}
}
}
}()
}
}

View 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)
}

View 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)
}
}

View 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))
}

View 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)
}
}
}