2015-05-06 20:05:00 +00:00
|
|
|
/*
|
2016-06-03 00:25:58 +00:00
|
|
|
Copyright 2014 The Kubernetes Authors.
|
2015-05-06 20:05:00 +00:00
|
|
|
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
2016-06-08 18:44:10 +00:00
|
|
|
package serviceaccount
|
2015-05-06 20:05:00 +00:00
|
|
|
|
|
|
|
// This file tests authentication and (soon) authorization of HTTP requests to a master object.
|
|
|
|
// It does not use the client in pkg/client/... because authentication and authorization needs
|
|
|
|
// to work for any client of the HTTP interface.
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/rand"
|
|
|
|
"crypto/rsa"
|
|
|
|
"fmt"
|
|
|
|
"net/http"
|
|
|
|
"net/http/httptest"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
2017-06-22 18:24:23 +00:00
|
|
|
"k8s.io/api/core/v1"
|
2017-01-25 13:39:54 +00:00
|
|
|
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
2017-01-13 17:48:50 +00:00
|
|
|
"k8s.io/apimachinery/pkg/api/errors"
|
2017-01-11 14:09:48 +00:00
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
|
"k8s.io/apimachinery/pkg/util/sets"
|
|
|
|
"k8s.io/apimachinery/pkg/util/wait"
|
2017-01-04 15:39:05 +00:00
|
|
|
"k8s.io/apiserver/pkg/authentication/authenticator"
|
2017-01-05 19:47:14 +00:00
|
|
|
"k8s.io/apiserver/pkg/authentication/request/bearertoken"
|
|
|
|
"k8s.io/apiserver/pkg/authentication/request/union"
|
2017-01-16 11:43:56 +00:00
|
|
|
serviceaccountapiserver "k8s.io/apiserver/pkg/authentication/serviceaccount"
|
2017-01-04 14:31:53 +00:00
|
|
|
"k8s.io/apiserver/pkg/authentication/user"
|
2017-01-04 15:39:05 +00:00
|
|
|
"k8s.io/apiserver/pkg/authorization/authorizer"
|
2017-01-19 18:27:59 +00:00
|
|
|
restclient "k8s.io/client-go/rest"
|
2016-11-19 23:32:10 +00:00
|
|
|
"k8s.io/kubernetes/pkg/api"
|
2017-01-10 08:49:34 +00:00
|
|
|
"k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
|
2016-12-14 03:39:50 +00:00
|
|
|
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
2017-02-14 18:25:54 +00:00
|
|
|
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/externalversions"
|
2017-02-15 19:00:29 +00:00
|
|
|
internalinformers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
|
2016-10-20 19:08:49 +00:00
|
|
|
"k8s.io/kubernetes/pkg/controller"
|
2015-12-24 21:54:40 +00:00
|
|
|
serviceaccountcontroller "k8s.io/kubernetes/pkg/controller/serviceaccount"
|
|
|
|
"k8s.io/kubernetes/pkg/serviceaccount"
|
2015-08-05 22:03:47 +00:00
|
|
|
serviceaccountadmission "k8s.io/kubernetes/plugin/pkg/admission/serviceaccount"
|
2015-10-15 21:16:08 +00:00
|
|
|
"k8s.io/kubernetes/test/integration/framework"
|
2015-05-06 20:05:00 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
rootUserName = "root"
|
|
|
|
rootToken = "root-user-token"
|
|
|
|
|
|
|
|
readOnlyServiceAccountName = "ro"
|
|
|
|
readWriteServiceAccountName = "rw"
|
|
|
|
)
|
|
|
|
|
|
|
|
func TestServiceAccountAutoCreate(t *testing.T) {
|
|
|
|
c, _, stopFunc := startServiceAccountTestServer(t)
|
|
|
|
defer stopFunc()
|
|
|
|
|
|
|
|
ns := "test-service-account-creation"
|
|
|
|
|
|
|
|
// Create namespace
|
2017-01-17 03:38:19 +00:00
|
|
|
_, err := c.Core().Namespaces().Create(&v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: ns}})
|
2015-05-06 20:05:00 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("could not create namespace: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get service account
|
|
|
|
defaultUser, err := getServiceAccount(c, ns, "default", true)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Default serviceaccount not created: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Delete service account
|
2016-02-03 21:21:05 +00:00
|
|
|
err = c.Core().ServiceAccounts(ns).Delete(defaultUser.Name, nil)
|
2015-05-06 20:05:00 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Could not delete default serviceaccount: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get recreated service account
|
|
|
|
defaultUser2, err := getServiceAccount(c, ns, "default", true)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Default serviceaccount not created: %v", err)
|
|
|
|
}
|
|
|
|
if defaultUser2.UID == defaultUser.UID {
|
|
|
|
t.Fatalf("Expected different UID with recreated serviceaccount")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestServiceAccountTokenAutoCreate(t *testing.T) {
|
|
|
|
c, _, stopFunc := startServiceAccountTestServer(t)
|
|
|
|
defer stopFunc()
|
|
|
|
|
|
|
|
ns := "test-service-account-token-creation"
|
|
|
|
name := "my-service-account"
|
|
|
|
|
|
|
|
// Create namespace
|
2017-01-17 03:38:19 +00:00
|
|
|
_, err := c.Core().Namespaces().Create(&v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: ns}})
|
2015-05-06 20:05:00 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("could not create namespace: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create service account
|
2017-01-17 03:38:19 +00:00
|
|
|
serviceAccount, err := c.Core().ServiceAccounts(ns).Create(&v1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Name: name}})
|
2015-05-06 20:05:00 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Service Account not created: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get token
|
|
|
|
token1Name, token1, err := getReferencedServiceAccountToken(c, ns, name, true)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Delete token
|
2016-02-03 21:21:05 +00:00
|
|
|
err = c.Core().Secrets(ns).Delete(token1Name, nil)
|
2015-05-06 20:05:00 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Could not delete token: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get recreated token
|
|
|
|
token2Name, token2, err := getReferencedServiceAccountToken(c, ns, name, true)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if token1Name == token2Name {
|
|
|
|
t.Fatalf("Expected new auto-created token name")
|
|
|
|
}
|
|
|
|
if token1 == token2 {
|
|
|
|
t.Fatalf("Expected new auto-created token value")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Trigger creation of a new referenced token
|
2016-12-07 14:40:26 +00:00
|
|
|
serviceAccount, err = c.Core().ServiceAccounts(ns).Get(name, metav1.GetOptions{})
|
2015-05-06 20:05:00 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2016-11-18 20:55:32 +00:00
|
|
|
serviceAccount.Secrets = []v1.ObjectReference{}
|
2016-02-03 21:21:05 +00:00
|
|
|
_, err = c.Core().ServiceAccounts(ns).Update(serviceAccount)
|
2015-05-06 20:05:00 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get rotated token
|
|
|
|
token3Name, token3, err := getReferencedServiceAccountToken(c, ns, name, true)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if token3Name == token2Name {
|
|
|
|
t.Fatalf("Expected new auto-created token name")
|
|
|
|
}
|
|
|
|
if token3 == token2 {
|
|
|
|
t.Fatalf("Expected new auto-created token value")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Delete service account
|
2016-02-03 21:21:05 +00:00
|
|
|
err = c.Core().ServiceAccounts(ns).Delete(name, nil)
|
2015-05-06 20:05:00 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wait for tokens to be deleted
|
2015-09-09 17:45:01 +00:00
|
|
|
tokensToCleanup := sets.NewString(token1Name, token2Name, token3Name)
|
2015-05-06 20:05:00 +00:00
|
|
|
err = wait.Poll(time.Second, 10*time.Second, func() (bool, error) {
|
|
|
|
// Get all secrets in the namespace
|
2017-01-22 03:36:02 +00:00
|
|
|
secrets, err := c.Core().Secrets(ns).List(metav1.ListOptions{})
|
2015-05-06 20:05:00 +00:00
|
|
|
// Retrieval errors should fail
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
for _, s := range secrets.Items {
|
|
|
|
if tokensToCleanup.Has(s.Name) {
|
|
|
|
// Still waiting for tokens to be cleaned up
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// All clean
|
|
|
|
return true, nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Error waiting for tokens to be deleted: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestServiceAccountTokenAutoMount(t *testing.T) {
|
|
|
|
c, _, stopFunc := startServiceAccountTestServer(t)
|
|
|
|
defer stopFunc()
|
|
|
|
|
|
|
|
ns := "auto-mount-ns"
|
|
|
|
|
|
|
|
// Create "my" namespace
|
2017-01-17 03:38:19 +00:00
|
|
|
_, err := c.Core().Namespaces().Create(&v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: ns}})
|
2015-05-06 20:05:00 +00:00
|
|
|
if err != nil && !errors.IsAlreadyExists(err) {
|
|
|
|
t.Fatalf("could not create namespace: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get default token
|
|
|
|
defaultTokenName, _, err := getReferencedServiceAccountToken(c, ns, serviceaccountadmission.DefaultServiceAccountName, true)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Pod to create
|
2016-11-18 20:55:32 +00:00
|
|
|
protoPod := v1.Pod{
|
2017-01-17 03:38:19 +00:00
|
|
|
ObjectMeta: metav1.ObjectMeta{Name: "protopod"},
|
2016-11-18 20:55:32 +00:00
|
|
|
Spec: v1.PodSpec{
|
|
|
|
Containers: []v1.Container{
|
2015-05-06 20:05:00 +00:00
|
|
|
{
|
|
|
|
Name: "container-1",
|
|
|
|
Image: "container-1-image",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "container-2",
|
|
|
|
Image: "container-2-image",
|
2016-11-18 20:55:32 +00:00
|
|
|
VolumeMounts: []v1.VolumeMount{
|
2015-05-06 20:05:00 +00:00
|
|
|
{Name: "empty-dir", MountPath: serviceaccountadmission.DefaultAPITokenMountPath},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2016-11-18 20:55:32 +00:00
|
|
|
Volumes: []v1.Volume{
|
2015-05-06 20:05:00 +00:00
|
|
|
{
|
|
|
|
Name: "empty-dir",
|
2016-11-18 20:55:32 +00:00
|
|
|
VolumeSource: v1.VolumeSource{EmptyDir: &v1.EmptyDirVolumeSource{}},
|
2015-05-06 20:05:00 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
// Pod we expect to get created
|
2016-07-11 00:48:28 +00:00
|
|
|
defaultMode := int32(0644)
|
2015-05-06 20:05:00 +00:00
|
|
|
expectedServiceAccount := serviceaccountadmission.DefaultServiceAccountName
|
2016-11-18 20:55:32 +00:00
|
|
|
expectedVolumes := append(protoPod.Spec.Volumes, v1.Volume{
|
2015-05-06 20:05:00 +00:00
|
|
|
Name: defaultTokenName,
|
2016-11-18 20:55:32 +00:00
|
|
|
VolumeSource: v1.VolumeSource{
|
|
|
|
Secret: &v1.SecretVolumeSource{
|
2016-07-11 00:48:28 +00:00
|
|
|
SecretName: defaultTokenName,
|
|
|
|
DefaultMode: &defaultMode,
|
2015-05-06 20:05:00 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
2016-11-18 20:55:32 +00:00
|
|
|
expectedContainer1VolumeMounts := []v1.VolumeMount{
|
2015-05-06 20:05:00 +00:00
|
|
|
{Name: defaultTokenName, MountPath: serviceaccountadmission.DefaultAPITokenMountPath, ReadOnly: true},
|
|
|
|
}
|
|
|
|
expectedContainer2VolumeMounts := protoPod.Spec.Containers[1].VolumeMounts
|
|
|
|
|
2016-02-03 21:21:05 +00:00
|
|
|
createdPod, err := c.Core().Pods(ns).Create(&protoPod)
|
2015-05-06 20:05:00 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2015-06-19 17:57:46 +00:00
|
|
|
if createdPod.Spec.ServiceAccountName != expectedServiceAccount {
|
|
|
|
t.Fatalf("Expected %s, got %s", expectedServiceAccount, createdPod.Spec.ServiceAccountName)
|
2015-05-06 20:05:00 +00:00
|
|
|
}
|
2017-01-25 13:39:54 +00:00
|
|
|
if !apiequality.Semantic.DeepEqual(&expectedVolumes, &createdPod.Spec.Volumes) {
|
2015-05-06 20:05:00 +00:00
|
|
|
t.Fatalf("Expected\n\t%#v\n\tgot\n\t%#v", expectedVolumes, createdPod.Spec.Volumes)
|
|
|
|
}
|
2017-01-25 13:39:54 +00:00
|
|
|
if !apiequality.Semantic.DeepEqual(&expectedContainer1VolumeMounts, &createdPod.Spec.Containers[0].VolumeMounts) {
|
2015-05-06 20:05:00 +00:00
|
|
|
t.Fatalf("Expected\n\t%#v\n\tgot\n\t%#v", expectedContainer1VolumeMounts, createdPod.Spec.Containers[0].VolumeMounts)
|
|
|
|
}
|
2017-01-25 13:39:54 +00:00
|
|
|
if !apiequality.Semantic.DeepEqual(&expectedContainer2VolumeMounts, &createdPod.Spec.Containers[1].VolumeMounts) {
|
2015-05-06 20:05:00 +00:00
|
|
|
t.Fatalf("Expected\n\t%#v\n\tgot\n\t%#v", expectedContainer2VolumeMounts, createdPod.Spec.Containers[1].VolumeMounts)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestServiceAccountTokenAuthentication(t *testing.T) {
|
|
|
|
c, config, stopFunc := startServiceAccountTestServer(t)
|
|
|
|
defer stopFunc()
|
|
|
|
|
|
|
|
myns := "auth-ns"
|
|
|
|
otherns := "other-ns"
|
|
|
|
|
|
|
|
// Create "my" namespace
|
2017-01-17 03:38:19 +00:00
|
|
|
_, err := c.Core().Namespaces().Create(&v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: myns}})
|
2015-05-06 20:05:00 +00:00
|
|
|
if err != nil && !errors.IsAlreadyExists(err) {
|
|
|
|
t.Fatalf("could not create namespace: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create "other" namespace
|
2017-01-17 03:38:19 +00:00
|
|
|
_, err = c.Core().Namespaces().Create(&v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: otherns}})
|
2015-05-06 20:05:00 +00:00
|
|
|
if err != nil && !errors.IsAlreadyExists(err) {
|
|
|
|
t.Fatalf("could not create namespace: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create "ro" user in myns
|
2017-01-17 03:38:19 +00:00
|
|
|
_, err = c.Core().ServiceAccounts(myns).Create(&v1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Name: readOnlyServiceAccountName}})
|
2015-05-06 20:05:00 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Service Account not created: %v", err)
|
|
|
|
}
|
|
|
|
roTokenName, roToken, err := getReferencedServiceAccountToken(c, myns, readOnlyServiceAccountName, true)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
roClientConfig := config
|
|
|
|
roClientConfig.BearerToken = roToken
|
2016-02-01 22:30:47 +00:00
|
|
|
roClient := clientset.NewForConfigOrDie(&roClientConfig)
|
2015-05-06 20:05:00 +00:00
|
|
|
doServiceAccountAPIRequests(t, roClient, myns, true, true, false)
|
|
|
|
doServiceAccountAPIRequests(t, roClient, otherns, true, false, false)
|
2016-02-03 21:21:05 +00:00
|
|
|
err = c.Core().Secrets(myns).Delete(roTokenName, nil)
|
2015-05-06 20:05:00 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("could not delete token: %v", err)
|
|
|
|
}
|
|
|
|
doServiceAccountAPIRequests(t, roClient, myns, false, false, false)
|
|
|
|
|
|
|
|
// Create "rw" user in myns
|
2017-01-17 03:38:19 +00:00
|
|
|
_, err = c.Core().ServiceAccounts(myns).Create(&v1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Name: readWriteServiceAccountName}})
|
2015-05-06 20:05:00 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Service Account not created: %v", err)
|
|
|
|
}
|
|
|
|
_, rwToken, err := getReferencedServiceAccountToken(c, myns, readWriteServiceAccountName, true)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
rwClientConfig := config
|
|
|
|
rwClientConfig.BearerToken = rwToken
|
2016-02-01 22:30:47 +00:00
|
|
|
rwClient := clientset.NewForConfigOrDie(&rwClientConfig)
|
2015-05-06 20:05:00 +00:00
|
|
|
doServiceAccountAPIRequests(t, rwClient, myns, true, true, true)
|
|
|
|
doServiceAccountAPIRequests(t, rwClient, otherns, true, false, false)
|
|
|
|
|
|
|
|
// Get default user and token which should have been automatically created
|
|
|
|
_, defaultToken, err := getReferencedServiceAccountToken(c, myns, "default", true)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("could not get default user and token: %v", err)
|
|
|
|
}
|
|
|
|
defaultClientConfig := config
|
|
|
|
defaultClientConfig.BearerToken = defaultToken
|
2016-02-01 22:30:47 +00:00
|
|
|
defaultClient := clientset.NewForConfigOrDie(&defaultClientConfig)
|
2015-05-06 20:05:00 +00:00
|
|
|
doServiceAccountAPIRequests(t, defaultClient, myns, true, false, false)
|
|
|
|
}
|
|
|
|
|
|
|
|
// startServiceAccountTestServer returns a started server
|
|
|
|
// It is the responsibility of the caller to ensure the returned stopFunc is called
|
2016-02-12 18:58:43 +00:00
|
|
|
func startServiceAccountTestServer(t *testing.T) (*clientset.Clientset, restclient.Config, func()) {
|
2015-05-06 20:05:00 +00:00
|
|
|
// Listener
|
2016-10-03 15:31:00 +00:00
|
|
|
h := &framework.MasterHolder{Initialized: make(chan struct{})}
|
2015-05-06 20:05:00 +00:00
|
|
|
apiServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
2016-10-03 15:31:00 +00:00
|
|
|
<-h.Initialized
|
2016-09-30 16:16:32 +00:00
|
|
|
h.M.GenericAPIServer.Handler.ServeHTTP(w, req)
|
2015-05-06 20:05:00 +00:00
|
|
|
}))
|
|
|
|
|
|
|
|
// Anonymous client config
|
2017-01-12 18:17:43 +00:00
|
|
|
clientConfig := restclient.Config{Host: apiServer.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}
|
2015-05-06 20:05:00 +00:00
|
|
|
// Root client
|
2016-01-29 06:34:08 +00:00
|
|
|
// TODO: remove rootClient after we refactor pkg/admission to use the clientset.
|
2017-01-12 18:17:43 +00:00
|
|
|
rootClientset := clientset.NewForConfigOrDie(&restclient.Config{Host: apiServer.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}, BearerToken: rootToken})
|
|
|
|
internalRootClientset := internalclientset.NewForConfigOrDie(&restclient.Config{Host: apiServer.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}, BearerToken: rootToken})
|
2015-05-06 20:05:00 +00:00
|
|
|
// Set up two authenticators:
|
|
|
|
// 1. A token authenticator that maps the rootToken to the "root" user
|
|
|
|
// 2. A ServiceAccountToken authenticator that validates ServiceAccount tokens
|
|
|
|
rootTokenAuth := authenticator.TokenFunc(func(token string) (user.Info, bool, error) {
|
|
|
|
if token == rootToken {
|
2016-03-29 12:55:38 +00:00
|
|
|
return &user.DefaultInfo{Name: rootUserName}, true, nil
|
2015-05-06 20:05:00 +00:00
|
|
|
}
|
|
|
|
return nil, false, nil
|
|
|
|
})
|
2015-12-02 09:46:27 +00:00
|
|
|
serviceAccountKey, _ := rsa.GenerateKey(rand.Reader, 2048)
|
2016-01-29 06:34:08 +00:00
|
|
|
serviceAccountTokenGetter := serviceaccountcontroller.NewGetterFromClient(rootClientset)
|
2016-09-27 05:44:33 +00:00
|
|
|
serviceAccountTokenAuth := serviceaccount.JWTTokenAuthenticator([]interface{}{&serviceAccountKey.PublicKey}, true, serviceAccountTokenGetter)
|
2015-05-06 20:05:00 +00:00
|
|
|
authenticator := union.New(
|
|
|
|
bearertoken.New(rootTokenAuth),
|
|
|
|
bearertoken.New(serviceAccountTokenAuth),
|
|
|
|
)
|
|
|
|
|
|
|
|
// Set up a stub authorizer:
|
|
|
|
// 1. The "root" user is allowed to do anything
|
|
|
|
// 2. ServiceAccounts named "ro" are allowed read-only operations in their namespace
|
|
|
|
// 3. ServiceAccounts named "rw" are allowed any operation in their namespace
|
2016-06-29 14:20:31 +00:00
|
|
|
authorizer := authorizer.AuthorizerFunc(func(attrs authorizer.Attributes) (bool, string, error) {
|
2016-07-12 17:25:07 +00:00
|
|
|
username := ""
|
|
|
|
if user := attrs.GetUser(); user != nil {
|
|
|
|
username = user.GetName()
|
|
|
|
}
|
2015-05-06 20:05:00 +00:00
|
|
|
ns := attrs.GetNamespace()
|
|
|
|
|
|
|
|
// If the user is "root"...
|
|
|
|
if username == rootUserName {
|
|
|
|
// allow them to do anything
|
2016-06-29 14:20:31 +00:00
|
|
|
return true, "", nil
|
2015-05-06 20:05:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// If the user is a service account...
|
2017-01-16 11:43:56 +00:00
|
|
|
if serviceAccountNamespace, serviceAccountName, err := serviceaccountapiserver.SplitUsername(username); err == nil {
|
2015-05-06 20:05:00 +00:00
|
|
|
// Limit them to their own namespace
|
|
|
|
if serviceAccountNamespace == ns {
|
|
|
|
switch serviceAccountName {
|
|
|
|
case readOnlyServiceAccountName:
|
|
|
|
if attrs.IsReadOnly() {
|
2016-06-29 14:20:31 +00:00
|
|
|
return true, "", nil
|
2015-05-06 20:05:00 +00:00
|
|
|
}
|
|
|
|
case readWriteServiceAccountName:
|
2016-06-29 14:20:31 +00:00
|
|
|
return true, "", nil
|
2015-05-06 20:05:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-29 14:20:31 +00:00
|
|
|
return false, fmt.Sprintf("User %s is denied (ns=%s, readonly=%v, resource=%s)", username, ns, attrs.IsReadOnly(), attrs.GetResource()), nil
|
2015-05-06 20:05:00 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
// Set up admission plugin to auto-assign serviceaccounts to pods
|
2016-12-21 21:16:16 +00:00
|
|
|
serviceAccountAdmission := serviceaccountadmission.NewServiceAccount()
|
2017-03-06 14:12:58 +00:00
|
|
|
serviceAccountAdmission.SetInternalKubeClientSet(internalRootClientset)
|
2017-02-15 19:00:29 +00:00
|
|
|
internalInformers := internalinformers.NewSharedInformerFactory(internalRootClientset, controller.NoResyncPeriodFunc())
|
2017-03-06 14:12:58 +00:00
|
|
|
serviceAccountAdmission.SetInternalKubeInformerFactory(internalInformers)
|
2017-05-17 04:01:50 +00:00
|
|
|
informers := informers.NewSharedInformerFactory(rootClientset, controller.NoResyncPeriodFunc())
|
2015-05-06 20:05:00 +00:00
|
|
|
|
2015-12-02 09:46:27 +00:00
|
|
|
masterConfig := framework.NewMasterConfig()
|
2016-09-28 18:52:28 +00:00
|
|
|
masterConfig.GenericConfig.EnableIndex = true
|
|
|
|
masterConfig.GenericConfig.Authenticator = authenticator
|
|
|
|
masterConfig.GenericConfig.Authorizer = authorizer
|
|
|
|
masterConfig.GenericConfig.AdmissionControl = serviceAccountAdmission
|
2016-10-03 15:31:00 +00:00
|
|
|
framework.RunAMasterUsingServer(masterConfig, apiServer, h)
|
2015-05-06 20:05:00 +00:00
|
|
|
|
|
|
|
// Start the service account and service account token controllers
|
2016-05-23 16:51:02 +00:00
|
|
|
stopCh := make(chan struct{})
|
2017-05-17 04:01:50 +00:00
|
|
|
tokenController := serviceaccountcontroller.NewTokensController(
|
|
|
|
informers.Core().V1().ServiceAccounts(),
|
|
|
|
informers.Core().V1().Secrets(),
|
|
|
|
rootClientset,
|
|
|
|
serviceaccountcontroller.TokensControllerOptions{TokenGenerator: serviceaccount.JWTTokenGenerator(serviceAccountKey)},
|
|
|
|
)
|
2016-05-23 16:51:02 +00:00
|
|
|
go tokenController.Run(1, stopCh)
|
2016-10-20 19:08:49 +00:00
|
|
|
|
2017-02-14 18:25:54 +00:00
|
|
|
serviceAccountController := serviceaccountcontroller.NewServiceAccountsController(
|
|
|
|
informers.Core().V1().ServiceAccounts(),
|
|
|
|
informers.Core().V1().Namespaces(),
|
|
|
|
rootClientset,
|
|
|
|
serviceaccountcontroller.DefaultServiceAccountsControllerOptions(),
|
|
|
|
)
|
2016-10-20 19:08:49 +00:00
|
|
|
informers.Start(stopCh)
|
2017-02-15 19:00:29 +00:00
|
|
|
internalInformers.Start(stopCh)
|
2016-10-20 19:08:49 +00:00
|
|
|
go serviceAccountController.Run(5, stopCh)
|
2015-05-06 20:05:00 +00:00
|
|
|
|
|
|
|
stop := func() {
|
2016-05-23 16:51:02 +00:00
|
|
|
close(stopCh)
|
2016-04-21 11:50:55 +00:00
|
|
|
apiServer.Close()
|
2015-05-06 20:05:00 +00:00
|
|
|
}
|
|
|
|
|
2016-02-01 22:30:47 +00:00
|
|
|
return rootClientset, clientConfig, stop
|
2015-05-06 20:05:00 +00:00
|
|
|
}
|
|
|
|
|
2016-11-18 20:55:32 +00:00
|
|
|
func getServiceAccount(c *clientset.Clientset, ns string, name string, shouldWait bool) (*v1.ServiceAccount, error) {
|
2015-05-06 20:05:00 +00:00
|
|
|
if !shouldWait {
|
2016-12-07 14:40:26 +00:00
|
|
|
return c.Core().ServiceAccounts(ns).Get(name, metav1.GetOptions{})
|
2015-05-06 20:05:00 +00:00
|
|
|
}
|
|
|
|
|
2016-11-18 20:55:32 +00:00
|
|
|
var user *v1.ServiceAccount
|
2015-05-06 20:05:00 +00:00
|
|
|
var err error
|
|
|
|
err = wait.Poll(time.Second, 10*time.Second, func() (bool, error) {
|
2016-12-07 14:40:26 +00:00
|
|
|
user, err = c.Core().ServiceAccounts(ns).Get(name, metav1.GetOptions{})
|
2015-05-06 20:05:00 +00:00
|
|
|
if errors.IsNotFound(err) {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
return true, nil
|
|
|
|
})
|
|
|
|
return user, err
|
|
|
|
}
|
|
|
|
|
2016-02-01 22:30:47 +00:00
|
|
|
func getReferencedServiceAccountToken(c *clientset.Clientset, ns string, name string, shouldWait bool) (string, string, error) {
|
2015-05-06 20:05:00 +00:00
|
|
|
tokenName := ""
|
|
|
|
token := ""
|
|
|
|
|
|
|
|
findToken := func() (bool, error) {
|
2016-12-07 14:40:26 +00:00
|
|
|
user, err := c.Core().ServiceAccounts(ns).Get(name, metav1.GetOptions{})
|
2015-05-06 20:05:00 +00:00
|
|
|
if errors.IsNotFound(err) {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, ref := range user.Secrets {
|
2016-12-07 14:40:26 +00:00
|
|
|
secret, err := c.Core().Secrets(ns).Get(ref.Name, metav1.GetOptions{})
|
2015-05-06 20:05:00 +00:00
|
|
|
if errors.IsNotFound(err) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
2016-11-18 20:55:32 +00:00
|
|
|
if secret.Type != v1.SecretTypeServiceAccountToken {
|
2015-05-06 20:05:00 +00:00
|
|
|
continue
|
|
|
|
}
|
2016-11-18 20:55:32 +00:00
|
|
|
name := secret.Annotations[v1.ServiceAccountNameKey]
|
|
|
|
uid := secret.Annotations[v1.ServiceAccountUIDKey]
|
|
|
|
tokenData := secret.Data[v1.ServiceAccountTokenKey]
|
2015-05-06 20:05:00 +00:00
|
|
|
if name == user.Name && uid == string(user.UID) && len(tokenData) > 0 {
|
|
|
|
tokenName = secret.Name
|
|
|
|
token = string(tokenData)
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if shouldWait {
|
|
|
|
err := wait.Poll(time.Second, 10*time.Second, findToken)
|
|
|
|
if err != nil {
|
|
|
|
return "", "", err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
ok, err := findToken()
|
|
|
|
if err != nil {
|
|
|
|
return "", "", err
|
|
|
|
}
|
|
|
|
if !ok {
|
|
|
|
return "", "", fmt.Errorf("No token found for %s/%s", ns, name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return tokenName, token, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type testOperation func() error
|
|
|
|
|
2016-02-01 22:30:47 +00:00
|
|
|
func doServiceAccountAPIRequests(t *testing.T, c *clientset.Clientset, ns string, authenticated bool, canRead bool, canWrite bool) {
|
2016-11-18 20:55:32 +00:00
|
|
|
testSecret := &v1.Secret{
|
2017-01-17 03:38:19 +00:00
|
|
|
ObjectMeta: metav1.ObjectMeta{Name: "testSecret"},
|
2015-05-06 20:05:00 +00:00
|
|
|
Data: map[string][]byte{"test": []byte("data")},
|
|
|
|
}
|
|
|
|
|
|
|
|
readOps := []testOperation{
|
2015-11-26 10:06:01 +00:00
|
|
|
func() error {
|
2017-01-22 03:36:02 +00:00
|
|
|
_, err := c.Core().Secrets(ns).List(metav1.ListOptions{})
|
2015-11-26 10:06:01 +00:00
|
|
|
return err
|
|
|
|
},
|
|
|
|
func() error {
|
2017-01-22 03:36:02 +00:00
|
|
|
_, err := c.Core().Pods(ns).List(metav1.ListOptions{})
|
2015-11-26 10:06:01 +00:00
|
|
|
return err
|
|
|
|
},
|
2015-05-06 20:05:00 +00:00
|
|
|
}
|
|
|
|
writeOps := []testOperation{
|
2016-02-03 21:21:05 +00:00
|
|
|
func() error { _, err := c.Core().Secrets(ns).Create(testSecret); return err },
|
|
|
|
func() error { return c.Core().Secrets(ns).Delete(testSecret.Name, nil) },
|
2015-05-06 20:05:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, op := range readOps {
|
|
|
|
err := op()
|
|
|
|
unauthorizedError := errors.IsUnauthorized(err)
|
|
|
|
forbiddenError := errors.IsForbidden(err)
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case !authenticated && !unauthorizedError:
|
|
|
|
t.Fatalf("expected unauthorized error, got %v", err)
|
|
|
|
case authenticated && unauthorizedError:
|
|
|
|
t.Fatalf("unexpected unauthorized error: %v", err)
|
|
|
|
case authenticated && canRead && forbiddenError:
|
|
|
|
t.Fatalf("unexpected forbidden error: %v", err)
|
|
|
|
case authenticated && !canRead && !forbiddenError:
|
|
|
|
t.Fatalf("expected forbidden error, got: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, op := range writeOps {
|
|
|
|
err := op()
|
|
|
|
unauthorizedError := errors.IsUnauthorized(err)
|
|
|
|
forbiddenError := errors.IsForbidden(err)
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case !authenticated && !unauthorizedError:
|
|
|
|
t.Fatalf("expected unauthorized error, got %v", err)
|
|
|
|
case authenticated && unauthorizedError:
|
|
|
|
t.Fatalf("unexpected unauthorized error: %v", err)
|
|
|
|
case authenticated && canWrite && forbiddenError:
|
|
|
|
t.Fatalf("unexpected forbidden error: %v", err)
|
|
|
|
case authenticated && !canWrite && !forbiddenError:
|
|
|
|
t.Fatalf("expected forbidden error, got: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|