...

Source file src/k8s.io/kubernetes/pkg/controlplane/controller/clusterauthenticationtrust/cluster_authentication_trust_controller_test.go

Documentation: k8s.io/kubernetes/pkg/controlplane/controller/clusterauthenticationtrust

     1  /*
     2  Copyright 2019 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package clusterauthenticationtrust
    18  
    19  import (
    20  	"reflect"
    21  	"testing"
    22  
    23  	"github.com/google/go-cmp/cmp"
    24  
    25  	corev1 "k8s.io/api/core/v1"
    26  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"k8s.io/apimachinery/pkg/runtime"
    29  	"k8s.io/apimachinery/pkg/runtime/schema"
    30  	"k8s.io/apimachinery/pkg/util/dump"
    31  	"k8s.io/apimachinery/pkg/util/validation/field"
    32  	"k8s.io/apiserver/pkg/authentication/request/headerrequest"
    33  	"k8s.io/apiserver/pkg/server/dynamiccertificates"
    34  	"k8s.io/client-go/kubernetes/fake"
    35  	corev1listers "k8s.io/client-go/listers/core/v1"
    36  	clienttesting "k8s.io/client-go/testing"
    37  	"k8s.io/client-go/tools/cache"
    38  )
    39  
    40  var (
    41  	someRandomCA = []byte(`-----BEGIN CERTIFICATE-----
    42  MIIBqDCCAU2gAwIBAgIUfbqeieihh/oERbfvRm38XvS/xHAwCgYIKoZIzj0EAwIw
    43  GjEYMBYGA1UEAxMPSW50ZXJtZWRpYXRlLUNBMCAXDTE2MTAxMTA1MDYwMFoYDzIx
    44  MTYwOTE3MDUwNjAwWjAUMRIwEAYDVQQDEwlNeSBDbGllbnQwWTATBgcqhkjOPQIB
    45  BggqhkjOPQMBBwNCAARv6N4R/sjMR65iMFGNLN1GC/vd7WhDW6J4X/iAjkRLLnNb
    46  KbRG/AtOUZ+7upJ3BWIRKYbOabbQGQe2BbKFiap4o3UwczAOBgNVHQ8BAf8EBAMC
    47  BaAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU
    48  K/pZOWpNcYai6eHFpmJEeFpeQlEwHwYDVR0jBBgwFoAUX6nQlxjfWnP6aM1meO/Q
    49  a6b3a9kwCgYIKoZIzj0EAwIDSQAwRgIhAIWTKw/sjJITqeuNzJDAKU4xo1zL+xJ5
    50  MnVCuBwfwDXCAiEAw/1TA+CjPq9JC5ek1ifR0FybTURjeQqYkKpve1dveps=
    51  -----END CERTIFICATE-----
    52  `)
    53  	anotherRandomCA = []byte(`-----BEGIN CERTIFICATE-----
    54  MIIDQDCCAiigAwIBAgIJANWw74P5KJk2MA0GCSqGSIb3DQEBCwUAMDQxMjAwBgNV
    55  BAMMKWdlbmVyaWNfd2ViaG9va19hZG1pc3Npb25fcGx1Z2luX3Rlc3RzX2NhMCAX
    56  DTE3MTExNjAwMDUzOVoYDzIyOTEwOTAxMDAwNTM5WjAjMSEwHwYDVQQDExh3ZWJo
    57  b29rLXRlc3QuZGVmYXVsdC5zdmMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
    58  AoIBAQDXd/nQ89a5H8ifEsigmMd01Ib6NVR3bkJjtkvYnTbdfYEBj7UzqOQtHoLa
    59  dIVmefny5uIHvj93WD8WDVPB3jX2JHrXkDTXd/6o6jIXHcsUfFTVLp6/bZ+Anqe0
    60  r/7hAPkzA2A7APyTWM3ZbEeo1afXogXhOJ1u/wz0DflgcB21gNho4kKTONXO3NHD
    61  XLpspFqSkxfEfKVDJaYAoMnYZJtFNsa2OvsmLnhYF8bjeT3i07lfwrhUZvP+7Gsp
    62  7UgUwc06WuNHjfx1s5e6ySzH0QioMD1rjYneqOvk0pKrMIhuAEWXqq7jlXcDtx1E
    63  j+wnYbVqqVYheHZ8BCJoVAAQGs9/AgMBAAGjZDBiMAkGA1UdEwQCMAAwCwYDVR0P
    64  BAQDAgXgMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATApBgNVHREEIjAg
    65  hwR/AAABghh3ZWJob29rLXRlc3QuZGVmYXVsdC5zdmMwDQYJKoZIhvcNAQELBQAD
    66  ggEBAD/GKSPNyQuAOw/jsYZesb+RMedbkzs18sSwlxAJQMUrrXwlVdHrA8q5WhE6
    67  ABLqU1b8lQ8AWun07R8k5tqTmNvCARrAPRUqls/ryER+3Y9YEcxEaTc3jKNZFLbc
    68  T6YtcnkdhxsiO136wtiuatpYL91RgCmuSpR8+7jEHhuFU01iaASu7ypFrUzrKHTF
    69  bKwiLRQi1cMzVcLErq5CDEKiKhUkoDucyARFszrGt9vNIl/YCcBOkcNvM3c05Hn3
    70  M++C29JwS3Hwbubg6WO3wjFjoEhpCwU6qRYUz3MRp4tHO4kxKXx+oQnUiFnR7vW0
    71  YkNtGc1RUDHwecCTFpJtPb7Yu/E=
    72  -----END CERTIFICATE-----
    73  `)
    74  
    75  	someRandomCAProvider    dynamiccertificates.CAContentProvider
    76  	anotherRandomCAProvider dynamiccertificates.CAContentProvider
    77  )
    78  
    79  func init() {
    80  	var err error
    81  	someRandomCAProvider, err = dynamiccertificates.NewStaticCAContent("foo", someRandomCA)
    82  	if err != nil {
    83  		panic(err)
    84  	}
    85  	anotherRandomCAProvider, err = dynamiccertificates.NewStaticCAContent("bar", anotherRandomCA)
    86  	if err != nil {
    87  		panic(err)
    88  	}
    89  }
    90  
    91  func TestWriteClientCAs(t *testing.T) {
    92  	tests := []struct {
    93  		name               string
    94  		clusterAuthInfo    ClusterAuthenticationInfo
    95  		preexistingObjs    []runtime.Object
    96  		expectedConfigMaps map[string]*corev1.ConfigMap
    97  		expectCreate       bool
    98  	}{
    99  		{
   100  			name: "basic",
   101  			clusterAuthInfo: ClusterAuthenticationInfo{
   102  				ClientCA:                         someRandomCAProvider,
   103  				RequestHeaderUsernameHeaders:     headerrequest.StaticStringSlice{"alfa", "bravo", "charlie"},
   104  				RequestHeaderGroupHeaders:        headerrequest.StaticStringSlice{"delta"},
   105  				RequestHeaderExtraHeaderPrefixes: headerrequest.StaticStringSlice{"echo", "foxtrot"},
   106  				RequestHeaderCA:                  anotherRandomCAProvider,
   107  				RequestHeaderAllowedNames:        headerrequest.StaticStringSlice{"first", "second"},
   108  			},
   109  			expectedConfigMaps: map[string]*corev1.ConfigMap{
   110  				"extension-apiserver-authentication": {
   111  					ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
   112  					Data: map[string]string{
   113  						"client-ca-file":                     string(someRandomCA),
   114  						"requestheader-username-headers":     `["alfa","bravo","charlie"]`,
   115  						"requestheader-group-headers":        `["delta"]`,
   116  						"requestheader-extra-headers-prefix": `["echo","foxtrot"]`,
   117  						"requestheader-client-ca-file":       string(anotherRandomCA),
   118  						"requestheader-allowed-names":        `["first","second"]`,
   119  					},
   120  				},
   121  			},
   122  			expectCreate: true,
   123  		},
   124  		{
   125  			name: "skip extension-apiserver-authentication",
   126  			clusterAuthInfo: ClusterAuthenticationInfo{
   127  				RequestHeaderCA:           anotherRandomCAProvider,
   128  				RequestHeaderAllowedNames: headerrequest.StaticStringSlice{"first", "second"},
   129  			},
   130  			expectedConfigMaps: map[string]*corev1.ConfigMap{
   131  				"extension-apiserver-authentication": {
   132  					ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
   133  					Data: map[string]string{
   134  						"requestheader-username-headers":     `[]`,
   135  						"requestheader-group-headers":        `[]`,
   136  						"requestheader-extra-headers-prefix": `[]`,
   137  						"requestheader-client-ca-file":       string(anotherRandomCA),
   138  						"requestheader-allowed-names":        `["first","second"]`,
   139  					},
   140  				},
   141  			},
   142  			expectCreate: true,
   143  		},
   144  		{
   145  			name: "skip extension-apiserver-authentication",
   146  			clusterAuthInfo: ClusterAuthenticationInfo{
   147  				ClientCA: someRandomCAProvider,
   148  			},
   149  			expectedConfigMaps: map[string]*corev1.ConfigMap{
   150  				"extension-apiserver-authentication": {
   151  					ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
   152  					Data: map[string]string{
   153  						"client-ca-file": string(someRandomCA),
   154  					},
   155  				},
   156  			},
   157  			expectCreate: true,
   158  		},
   159  		{
   160  			name: "empty allowed names",
   161  			clusterAuthInfo: ClusterAuthenticationInfo{
   162  				RequestHeaderCA: anotherRandomCAProvider,
   163  			},
   164  			expectedConfigMaps: map[string]*corev1.ConfigMap{
   165  				"extension-apiserver-authentication": {
   166  					ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
   167  					Data: map[string]string{
   168  						"requestheader-username-headers":     `[]`,
   169  						"requestheader-group-headers":        `[]`,
   170  						"requestheader-extra-headers-prefix": `[]`,
   171  						"requestheader-client-ca-file":       string(anotherRandomCA),
   172  						"requestheader-allowed-names":        `[]`,
   173  					},
   174  				},
   175  			},
   176  			expectCreate: true,
   177  		},
   178  		{
   179  			name: "overwrite extension-apiserver-authentication",
   180  			clusterAuthInfo: ClusterAuthenticationInfo{
   181  				ClientCA: someRandomCAProvider,
   182  			},
   183  			preexistingObjs: []runtime.Object{
   184  				&corev1.ConfigMap{
   185  					ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
   186  					Data: map[string]string{
   187  						"client-ca-file": string(anotherRandomCA),
   188  					},
   189  				},
   190  			},
   191  			expectedConfigMaps: map[string]*corev1.ConfigMap{
   192  				"extension-apiserver-authentication": {
   193  					ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
   194  					Data: map[string]string{
   195  						"client-ca-file": string(anotherRandomCA) + string(someRandomCA),
   196  					},
   197  				},
   198  			},
   199  		},
   200  		{
   201  			name: "overwrite extension-apiserver-authentication requestheader",
   202  			clusterAuthInfo: ClusterAuthenticationInfo{
   203  				RequestHeaderUsernameHeaders:     headerrequest.StaticStringSlice{},
   204  				RequestHeaderGroupHeaders:        headerrequest.StaticStringSlice{},
   205  				RequestHeaderExtraHeaderPrefixes: headerrequest.StaticStringSlice{},
   206  				RequestHeaderCA:                  anotherRandomCAProvider,
   207  				RequestHeaderAllowedNames:        headerrequest.StaticStringSlice{},
   208  			},
   209  			preexistingObjs: []runtime.Object{
   210  				&corev1.ConfigMap{
   211  					ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
   212  					Data: map[string]string{
   213  						"requestheader-username-headers":     `[]`,
   214  						"requestheader-group-headers":        `[]`,
   215  						"requestheader-extra-headers-prefix": `[]`,
   216  						"requestheader-client-ca-file":       string(someRandomCA),
   217  						"requestheader-allowed-names":        `[]`,
   218  					},
   219  				},
   220  			},
   221  			expectedConfigMaps: map[string]*corev1.ConfigMap{
   222  				"extension-apiserver-authentication": {
   223  					ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
   224  					Data: map[string]string{
   225  						"requestheader-username-headers":     `[]`,
   226  						"requestheader-group-headers":        `[]`,
   227  						"requestheader-extra-headers-prefix": `[]`,
   228  						"requestheader-client-ca-file":       string(someRandomCA) + string(anotherRandomCA),
   229  						"requestheader-allowed-names":        `[]`,
   230  					},
   231  				},
   232  			},
   233  		},
   234  		{
   235  			name: "namespace exists",
   236  			clusterAuthInfo: ClusterAuthenticationInfo{
   237  				ClientCA: someRandomCAProvider,
   238  			},
   239  			preexistingObjs: []runtime.Object{
   240  				&corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: metav1.NamespaceSystem}},
   241  			},
   242  			expectedConfigMaps: map[string]*corev1.ConfigMap{
   243  				"extension-apiserver-authentication": {
   244  					ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
   245  					Data: map[string]string{
   246  						"client-ca-file": string(someRandomCA),
   247  					},
   248  				},
   249  			},
   250  			expectCreate: true,
   251  		},
   252  		{
   253  			name: "skip on no change",
   254  			clusterAuthInfo: ClusterAuthenticationInfo{
   255  				RequestHeaderUsernameHeaders:     headerrequest.StaticStringSlice{},
   256  				RequestHeaderGroupHeaders:        headerrequest.StaticStringSlice{},
   257  				RequestHeaderExtraHeaderPrefixes: headerrequest.StaticStringSlice{},
   258  				RequestHeaderCA:                  anotherRandomCAProvider,
   259  				RequestHeaderAllowedNames:        headerrequest.StaticStringSlice{},
   260  			},
   261  			preexistingObjs: []runtime.Object{
   262  				&corev1.ConfigMap{
   263  					ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
   264  					Data: map[string]string{
   265  						"requestheader-username-headers":     `[]`,
   266  						"requestheader-group-headers":        `[]`,
   267  						"requestheader-extra-headers-prefix": `[]`,
   268  						"requestheader-client-ca-file":       string(anotherRandomCA),
   269  						"requestheader-allowed-names":        `[]`,
   270  					},
   271  				},
   272  			},
   273  			expectedConfigMaps: map[string]*corev1.ConfigMap{},
   274  			expectCreate:       false,
   275  		},
   276  	}
   277  
   278  	for _, test := range tests {
   279  		t.Run(test.name, func(t *testing.T) {
   280  			client := fake.NewSimpleClientset(test.preexistingObjs...)
   281  			configMapIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc})
   282  			for _, obj := range test.preexistingObjs {
   283  				configMapIndexer.Add(obj)
   284  			}
   285  			configmapLister := corev1listers.NewConfigMapLister(configMapIndexer)
   286  
   287  			c := &Controller{
   288  				configMapLister:            configmapLister,
   289  				configMapClient:            client.CoreV1(),
   290  				namespaceClient:            client.CoreV1(),
   291  				requiredAuthenticationData: test.clusterAuthInfo,
   292  			}
   293  
   294  			err := c.syncConfigMap()
   295  			if err != nil {
   296  				t.Fatal(err)
   297  			}
   298  
   299  			actualConfigMaps, updated := getFinalConfigMaps(t, client)
   300  			if !reflect.DeepEqual(test.expectedConfigMaps, actualConfigMaps) {
   301  				t.Fatalf("%s: %v", test.name, cmp.Diff(test.expectedConfigMaps, actualConfigMaps))
   302  			}
   303  			if test.expectCreate != updated {
   304  				t.Fatalf("%s: expected %v, got %v", test.name, test.expectCreate, updated)
   305  			}
   306  		})
   307  	}
   308  }
   309  
   310  func getFinalConfigMaps(t *testing.T, client *fake.Clientset) (map[string]*corev1.ConfigMap, bool) {
   311  	ret := map[string]*corev1.ConfigMap{}
   312  	created := false
   313  
   314  	for _, action := range client.Actions() {
   315  		t.Log(dump.Pretty(action))
   316  		if action.Matches("create", "configmaps") {
   317  			created = true
   318  			obj := action.(clienttesting.CreateAction).GetObject().(*corev1.ConfigMap)
   319  			ret[obj.Name] = obj
   320  		}
   321  		if action.Matches("update", "configmaps") {
   322  			obj := action.(clienttesting.UpdateAction).GetObject().(*corev1.ConfigMap)
   323  			ret[obj.Name] = obj
   324  		}
   325  	}
   326  	return ret, created
   327  }
   328  
   329  func TestWriteConfigMapDeleted(t *testing.T) {
   330  	// the basics are tested above, this checks the deletion logic when the ca bundles are too large
   331  	cm := &corev1.ConfigMap{
   332  		ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
   333  		Data: map[string]string{
   334  			"requestheader-username-headers":     `[]`,
   335  			"requestheader-group-headers":        `[]`,
   336  			"requestheader-extra-headers-prefix": `[]`,
   337  			"requestheader-client-ca-file":       string(anotherRandomCA),
   338  			"requestheader-allowed-names":        `[]`,
   339  		},
   340  	}
   341  
   342  	t.Run("request entity too large", func(t *testing.T) {
   343  		client := fake.NewSimpleClientset()
   344  		client.PrependReactor("update", "configmaps", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
   345  			return true, nil, apierrors.NewRequestEntityTooLargeError("way too big")
   346  		})
   347  		client.PrependReactor("delete", "configmaps", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
   348  			return true, nil, nil
   349  		})
   350  
   351  		err := writeConfigMap(client.CoreV1(), cm)
   352  		if err == nil || err.Error() != "Request entity too large: way too big" {
   353  			t.Fatal(err)
   354  		}
   355  		if len(client.Actions()) != 2 {
   356  			t.Fatal(client.Actions())
   357  		}
   358  		_, ok := client.Actions()[1].(clienttesting.DeleteAction)
   359  		if !ok {
   360  			t.Fatal(client.Actions())
   361  		}
   362  	})
   363  
   364  	t.Run("ca bundle too large", func(t *testing.T) {
   365  		client := fake.NewSimpleClientset()
   366  		client.PrependReactor("update", "configmaps", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
   367  			return true, nil, apierrors.NewInvalid(schema.GroupKind{Kind: "ConfigMap"}, cm.Name, field.ErrorList{field.TooLong(field.NewPath(""), cm, corev1.MaxSecretSize)})
   368  		})
   369  		client.PrependReactor("delete", "configmaps", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
   370  			return true, nil, nil
   371  		})
   372  
   373  		err := writeConfigMap(client.CoreV1(), cm)
   374  		if err == nil || err.Error() != `ConfigMap "extension-apiserver-authentication" is invalid: []: Too long: must have at most 1048576 bytes` {
   375  			t.Fatal(err)
   376  		}
   377  		if len(client.Actions()) != 2 {
   378  			t.Fatal(client.Actions())
   379  		}
   380  		_, ok := client.Actions()[1].(clienttesting.DeleteAction)
   381  		if !ok {
   382  			t.Fatal(client.Actions())
   383  		}
   384  	})
   385  
   386  }
   387  

View as plain text