k8s.io/apiserver@v0.31.1/pkg/authentication/request/headerrequest/requestheader_controller_test.go (about) 1 /* 2 Copyright 2020 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 headerrequest 18 19 import ( 20 "context" 21 "encoding/json" 22 "k8s.io/apimachinery/pkg/api/equality" 23 "testing" 24 25 corev1 "k8s.io/api/core/v1" 26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 "k8s.io/client-go/kubernetes/fake" 28 corev1listers "k8s.io/client-go/listers/core/v1" 29 "k8s.io/client-go/tools/cache" 30 ) 31 32 const ( 33 defConfigMapName = "extension-apiserver-authentication" 34 defConfigMapNamespace = "kube-system" 35 36 defUsernameHeadersKey = "user-key" 37 defGroupHeadersKey = "group-key" 38 defExtraHeaderPrefixesKey = "extra-key" 39 defAllowedClientNamesKey = "names-key" 40 ) 41 42 type expectedHeadersHolder struct { 43 usernameHeaders []string 44 groupHeaders []string 45 extraHeaderPrefixes []string 46 allowedClientNames []string 47 } 48 49 func TestRequestHeaderAuthRequestController(t *testing.T) { 50 scenarios := []struct { 51 name string 52 cm *corev1.ConfigMap 53 expectedHeader expectedHeadersHolder 54 expectErr bool 55 }{ 56 { 57 name: "happy-path: headers values are populated form a config map", 58 cm: defaultConfigMap(t, []string{"user-val"}, []string{"group-val"}, []string{"extra-val"}, []string{"names-val"}), 59 expectedHeader: expectedHeadersHolder{ 60 usernameHeaders: []string{"user-val"}, 61 groupHeaders: []string{"group-val"}, 62 extraHeaderPrefixes: []string{"extra-val"}, 63 allowedClientNames: []string{"names-val"}, 64 }, 65 }, 66 { 67 name: "passing an empty config map doesn't break the controller", 68 cm: func() *corev1.ConfigMap { 69 c := defaultConfigMap(t, nil, nil, nil, nil) 70 c.Data = map[string]string{} 71 return c 72 }(), 73 }, 74 { 75 name: "an invalid config map produces an error", 76 cm: func() *corev1.ConfigMap { 77 c := defaultConfigMap(t, nil, nil, nil, nil) 78 c.Data = map[string]string{ 79 defUsernameHeadersKey: "incorrect-json-array", 80 } 81 return c 82 }(), 83 expectErr: true, 84 }, 85 } 86 87 for _, scenario := range scenarios { 88 t.Run(scenario.name, func(t *testing.T) { 89 // test data 90 indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{}) 91 if err := indexer.Add(scenario.cm); err != nil { 92 t.Fatal(err.Error()) 93 } 94 target := newDefaultTarget() 95 target.configmapLister = corev1listers.NewConfigMapLister(indexer).ConfigMaps(defConfigMapNamespace) 96 97 // act 98 err := target.sync() 99 100 if err != nil && !scenario.expectErr { 101 t.Errorf("got unexpected error %v", err) 102 } 103 if err == nil && scenario.expectErr { 104 t.Error("expected an error but didn't get one") 105 } 106 107 // validate 108 validateExpectedHeaders(t, target, scenario.expectedHeader) 109 }) 110 } 111 } 112 113 func TestRequestHeaderAuthRequestControllerPreserveState(t *testing.T) { 114 scenarios := []struct { 115 name string 116 cm *corev1.ConfigMap 117 expectedHeader expectedHeadersHolder 118 expectErr bool 119 }{ 120 { 121 name: "scenario 1: headers values are populated form a config map", 122 cm: defaultConfigMap(t, []string{"user-val"}, []string{"group-val"}, []string{"extra-val"}, []string{"names-val"}), 123 expectedHeader: expectedHeadersHolder{ 124 usernameHeaders: []string{"user-val"}, 125 groupHeaders: []string{"group-val"}, 126 extraHeaderPrefixes: []string{"extra-val"}, 127 allowedClientNames: []string{"names-val"}, 128 }, 129 }, 130 { 131 name: "scenario 2: an invalid config map produces an error but doesn't destroy the state (scenario 1)", 132 cm: func() *corev1.ConfigMap { 133 c := defaultConfigMap(t, nil, nil, nil, nil) 134 c.Data = map[string]string{ 135 defUsernameHeadersKey: "incorrect-json-array", 136 } 137 return c 138 }(), 139 expectErr: true, 140 expectedHeader: expectedHeadersHolder{ 141 usernameHeaders: []string{"user-val"}, 142 groupHeaders: []string{"group-val"}, 143 extraHeaderPrefixes: []string{"extra-val"}, 144 allowedClientNames: []string{"names-val"}, 145 }, 146 }, 147 { 148 name: "scenario 3: some headers values have changed (prev set by scenario 1)", 149 cm: defaultConfigMap(t, []string{"user-val"}, []string{"group-val-scenario-3"}, []string{"extra-val"}, []string{"names-val"}), 150 expectedHeader: expectedHeadersHolder{ 151 usernameHeaders: []string{"user-val"}, 152 groupHeaders: []string{"group-val-scenario-3"}, 153 extraHeaderPrefixes: []string{"extra-val"}, 154 allowedClientNames: []string{"names-val"}, 155 }, 156 }, 157 { 158 name: "scenario 4: all headers values have changed (prev set by scenario 3)", 159 cm: defaultConfigMap(t, []string{"user-val-scenario-4"}, []string{"group-val-scenario-4"}, []string{"extra-val-scenario-4"}, []string{"names-val-scenario-4"}), 160 expectedHeader: expectedHeadersHolder{ 161 usernameHeaders: []string{"user-val-scenario-4"}, 162 groupHeaders: []string{"group-val-scenario-4"}, 163 extraHeaderPrefixes: []string{"extra-val-scenario-4"}, 164 allowedClientNames: []string{"names-val-scenario-4"}, 165 }, 166 }, 167 } 168 169 target := newDefaultTarget() 170 171 for _, scenario := range scenarios { 172 t.Run(scenario.name, func(t *testing.T) { 173 // test data 174 if scenario.cm != nil { 175 indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{}) 176 if err := indexer.Add(scenario.cm); err != nil { 177 t.Fatal(err.Error()) 178 } 179 target.configmapLister = corev1listers.NewConfigMapLister(indexer).ConfigMaps(defConfigMapNamespace) 180 } 181 182 // act 183 err := target.sync() 184 185 if err != nil && !scenario.expectErr { 186 t.Errorf("got unexpected error %v", err) 187 } 188 if err == nil && scenario.expectErr { 189 t.Error("expected an error but didn't get one") 190 } 191 192 // validate 193 validateExpectedHeaders(t, target, scenario.expectedHeader) 194 }) 195 } 196 } 197 198 func TestRequestHeaderAuthRequestControllerSyncOnce(t *testing.T) { 199 scenarios := []struct { 200 name string 201 cm *corev1.ConfigMap 202 expectedHeader expectedHeadersHolder 203 expectErr bool 204 }{ 205 { 206 name: "headers values are populated form a config map", 207 cm: defaultConfigMap(t, []string{"user-val"}, []string{"group-val"}, []string{"extra-val"}, []string{"names-val"}), 208 expectedHeader: expectedHeadersHolder{ 209 usernameHeaders: []string{"user-val"}, 210 groupHeaders: []string{"group-val"}, 211 extraHeaderPrefixes: []string{"extra-val"}, 212 allowedClientNames: []string{"names-val"}, 213 }, 214 }, 215 } 216 217 for _, scenario := range scenarios { 218 t.Run(scenario.name, func(t *testing.T) { 219 // test data 220 target := newDefaultTarget() 221 fakeKubeClient := fake.NewSimpleClientset(scenario.cm) 222 target.client = fakeKubeClient 223 224 // act 225 ctx := context.TODO() 226 err := target.RunOnce(ctx) 227 228 if err != nil && !scenario.expectErr { 229 t.Errorf("got unexpected error %v", err) 230 } 231 if err == nil && scenario.expectErr { 232 t.Error("expected an error but didn't get one") 233 } 234 235 // validate 236 validateExpectedHeaders(t, target, scenario.expectedHeader) 237 }) 238 } 239 } 240 241 func defaultConfigMap(t *testing.T, usernameHeaderVal, groupHeadersVal, extraHeaderPrefixesVal, allowedClientNamesVal []string) *corev1.ConfigMap { 242 encode := func(val []string) string { 243 encodedVal, err := json.Marshal(val) 244 if err != nil { 245 t.Fatalf("unable to marshal %q , due to %v", usernameHeaderVal, err) 246 } 247 return string(encodedVal) 248 } 249 return &corev1.ConfigMap{ 250 ObjectMeta: metav1.ObjectMeta{ 251 Name: defConfigMapName, 252 Namespace: defConfigMapNamespace, 253 }, 254 Data: map[string]string{ 255 defUsernameHeadersKey: encode(usernameHeaderVal), 256 defGroupHeadersKey: encode(groupHeadersVal), 257 defExtraHeaderPrefixesKey: encode(extraHeaderPrefixesVal), 258 defAllowedClientNamesKey: encode(allowedClientNamesVal), 259 }, 260 } 261 } 262 263 func newDefaultTarget() *RequestHeaderAuthRequestController { 264 return &RequestHeaderAuthRequestController{ 265 configmapName: defConfigMapName, 266 configmapNamespace: defConfigMapNamespace, 267 usernameHeadersKey: defUsernameHeadersKey, 268 groupHeadersKey: defGroupHeadersKey, 269 extraHeaderPrefixesKey: defExtraHeaderPrefixesKey, 270 allowedClientNamesKey: defAllowedClientNamesKey, 271 } 272 } 273 274 func validateExpectedHeaders(t *testing.T, target *RequestHeaderAuthRequestController, expected expectedHeadersHolder) { 275 if !equality.Semantic.DeepEqual(target.UsernameHeaders(), expected.usernameHeaders) { 276 t.Fatalf("incorrect usernameHeaders, got %v, wanted %v", target.UsernameHeaders(), expected.usernameHeaders) 277 } 278 if !equality.Semantic.DeepEqual(target.GroupHeaders(), expected.groupHeaders) { 279 t.Fatalf("incorrect groupHeaders, got %v, wanted %v", target.GroupHeaders(), expected.groupHeaders) 280 } 281 if !equality.Semantic.DeepEqual(target.ExtraHeaderPrefixes(), expected.extraHeaderPrefixes) { 282 t.Fatalf("incorrect extraheaderPrefixes, got %v, wanted %v", target.ExtraHeaderPrefixes(), expected.extraHeaderPrefixes) 283 } 284 if !equality.Semantic.DeepEqual(target.AllowedClientNames(), expected.allowedClientNames) { 285 t.Fatalf("incorrect expectedAllowedClientNames, got %v, wanted %v", target.AllowedClientNames(), expected.allowedClientNames) 286 } 287 }