apiserver/pkg/authentication/request/headerrequest/requestheader_controller_te...

288 lines
9.3 KiB
Go

/*
Copyright 2020 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 headerrequest
import (
"context"
"encoding/json"
"k8s.io/apimachinery/pkg/api/equality"
"testing"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"
corev1listers "k8s.io/client-go/listers/core/v1"
"k8s.io/client-go/tools/cache"
)
const (
defConfigMapName = "extension-apiserver-authentication"
defConfigMapNamespace = "kube-system"
defUsernameHeadersKey = "user-key"
defGroupHeadersKey = "group-key"
defExtraHeaderPrefixesKey = "extra-key"
defAllowedClientNamesKey = "names-key"
)
type expectedHeadersHolder struct {
usernameHeaders []string
groupHeaders []string
extraHeaderPrefixes []string
allowedClientNames []string
}
func TestRequestHeaderAuthRequestController(t *testing.T) {
scenarios := []struct {
name string
cm *corev1.ConfigMap
expectedHeader expectedHeadersHolder
expectErr bool
}{
{
name: "happy-path: headers values are populated form a config map",
cm: defaultConfigMap(t, []string{"user-val"}, []string{"group-val"}, []string{"extra-val"}, []string{"names-val"}),
expectedHeader: expectedHeadersHolder{
usernameHeaders: []string{"user-val"},
groupHeaders: []string{"group-val"},
extraHeaderPrefixes: []string{"extra-val"},
allowedClientNames: []string{"names-val"},
},
},
{
name: "passing an empty config map doesn't break the controller",
cm: func() *corev1.ConfigMap {
c := defaultConfigMap(t, nil, nil, nil, nil)
c.Data = map[string]string{}
return c
}(),
},
{
name: "an invalid config map produces an error",
cm: func() *corev1.ConfigMap {
c := defaultConfigMap(t, nil, nil, nil, nil)
c.Data = map[string]string{
defUsernameHeadersKey: "incorrect-json-array",
}
return c
}(),
expectErr: true,
},
}
for _, scenario := range scenarios {
t.Run(scenario.name, func(t *testing.T) {
// test data
indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{})
if err := indexer.Add(scenario.cm); err != nil {
t.Fatal(err.Error())
}
target := newDefaultTarget()
target.configmapLister = corev1listers.NewConfigMapLister(indexer).ConfigMaps(defConfigMapNamespace)
// act
err := target.sync()
if err != nil && !scenario.expectErr {
t.Errorf("got unexpected error %v", err)
}
if err == nil && scenario.expectErr {
t.Error("expected an error but didn't get one")
}
// validate
validateExpectedHeaders(t, target, scenario.expectedHeader)
})
}
}
func TestRequestHeaderAuthRequestControllerPreserveState(t *testing.T) {
scenarios := []struct {
name string
cm *corev1.ConfigMap
expectedHeader expectedHeadersHolder
expectErr bool
}{
{
name: "scenario 1: headers values are populated form a config map",
cm: defaultConfigMap(t, []string{"user-val"}, []string{"group-val"}, []string{"extra-val"}, []string{"names-val"}),
expectedHeader: expectedHeadersHolder{
usernameHeaders: []string{"user-val"},
groupHeaders: []string{"group-val"},
extraHeaderPrefixes: []string{"extra-val"},
allowedClientNames: []string{"names-val"},
},
},
{
name: "scenario 2: an invalid config map produces an error but doesn't destroy the state (scenario 1)",
cm: func() *corev1.ConfigMap {
c := defaultConfigMap(t, nil, nil, nil, nil)
c.Data = map[string]string{
defUsernameHeadersKey: "incorrect-json-array",
}
return c
}(),
expectErr: true,
expectedHeader: expectedHeadersHolder{
usernameHeaders: []string{"user-val"},
groupHeaders: []string{"group-val"},
extraHeaderPrefixes: []string{"extra-val"},
allowedClientNames: []string{"names-val"},
},
},
{
name: "scenario 3: some headers values have changed (prev set by scenario 1)",
cm: defaultConfigMap(t, []string{"user-val"}, []string{"group-val-scenario-3"}, []string{"extra-val"}, []string{"names-val"}),
expectedHeader: expectedHeadersHolder{
usernameHeaders: []string{"user-val"},
groupHeaders: []string{"group-val-scenario-3"},
extraHeaderPrefixes: []string{"extra-val"},
allowedClientNames: []string{"names-val"},
},
},
{
name: "scenario 4: all headers values have changed (prev set by scenario 3)",
cm: defaultConfigMap(t, []string{"user-val-scenario-4"}, []string{"group-val-scenario-4"}, []string{"extra-val-scenario-4"}, []string{"names-val-scenario-4"}),
expectedHeader: expectedHeadersHolder{
usernameHeaders: []string{"user-val-scenario-4"},
groupHeaders: []string{"group-val-scenario-4"},
extraHeaderPrefixes: []string{"extra-val-scenario-4"},
allowedClientNames: []string{"names-val-scenario-4"},
},
},
}
target := newDefaultTarget()
for _, scenario := range scenarios {
t.Run(scenario.name, func(t *testing.T) {
// test data
if scenario.cm != nil {
indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{})
if err := indexer.Add(scenario.cm); err != nil {
t.Fatal(err.Error())
}
target.configmapLister = corev1listers.NewConfigMapLister(indexer).ConfigMaps(defConfigMapNamespace)
}
// act
err := target.sync()
if err != nil && !scenario.expectErr {
t.Errorf("got unexpected error %v", err)
}
if err == nil && scenario.expectErr {
t.Error("expected an error but didn't get one")
}
// validate
validateExpectedHeaders(t, target, scenario.expectedHeader)
})
}
}
func TestRequestHeaderAuthRequestControllerSyncOnce(t *testing.T) {
scenarios := []struct {
name string
cm *corev1.ConfigMap
expectedHeader expectedHeadersHolder
expectErr bool
}{
{
name: "headers values are populated form a config map",
cm: defaultConfigMap(t, []string{"user-val"}, []string{"group-val"}, []string{"extra-val"}, []string{"names-val"}),
expectedHeader: expectedHeadersHolder{
usernameHeaders: []string{"user-val"},
groupHeaders: []string{"group-val"},
extraHeaderPrefixes: []string{"extra-val"},
allowedClientNames: []string{"names-val"},
},
},
}
for _, scenario := range scenarios {
t.Run(scenario.name, func(t *testing.T) {
// test data
target := newDefaultTarget()
fakeKubeClient := fake.NewSimpleClientset(scenario.cm)
target.client = fakeKubeClient
// act
ctx := context.TODO()
err := target.RunOnce(ctx)
if err != nil && !scenario.expectErr {
t.Errorf("got unexpected error %v", err)
}
if err == nil && scenario.expectErr {
t.Error("expected an error but didn't get one")
}
// validate
validateExpectedHeaders(t, target, scenario.expectedHeader)
})
}
}
func defaultConfigMap(t *testing.T, usernameHeaderVal, groupHeadersVal, extraHeaderPrefixesVal, allowedClientNamesVal []string) *corev1.ConfigMap {
encode := func(val []string) string {
encodedVal, err := json.Marshal(val)
if err != nil {
t.Fatalf("unable to marshal %q , due to %v", usernameHeaderVal, err)
}
return string(encodedVal)
}
return &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: defConfigMapName,
Namespace: defConfigMapNamespace,
},
Data: map[string]string{
defUsernameHeadersKey: encode(usernameHeaderVal),
defGroupHeadersKey: encode(groupHeadersVal),
defExtraHeaderPrefixesKey: encode(extraHeaderPrefixesVal),
defAllowedClientNamesKey: encode(allowedClientNamesVal),
},
}
}
func newDefaultTarget() *RequestHeaderAuthRequestController {
return &RequestHeaderAuthRequestController{
configmapName: defConfigMapName,
configmapNamespace: defConfigMapNamespace,
usernameHeadersKey: defUsernameHeadersKey,
groupHeadersKey: defGroupHeadersKey,
extraHeaderPrefixesKey: defExtraHeaderPrefixesKey,
allowedClientNamesKey: defAllowedClientNamesKey,
}
}
func validateExpectedHeaders(t *testing.T, target *RequestHeaderAuthRequestController, expected expectedHeadersHolder) {
if !equality.Semantic.DeepEqual(target.UsernameHeaders(), expected.usernameHeaders) {
t.Fatalf("incorrect usernameHeaders, got %v, wanted %v", target.UsernameHeaders(), expected.usernameHeaders)
}
if !equality.Semantic.DeepEqual(target.GroupHeaders(), expected.groupHeaders) {
t.Fatalf("incorrect groupHeaders, got %v, wanted %v", target.GroupHeaders(), expected.groupHeaders)
}
if !equality.Semantic.DeepEqual(target.ExtraHeaderPrefixes(), expected.extraHeaderPrefixes) {
t.Fatalf("incorrect extraheaderPrefixes, got %v, wanted %v", target.ExtraHeaderPrefixes(), expected.extraHeaderPrefixes)
}
if !equality.Semantic.DeepEqual(target.AllowedClientNames(), expected.allowedClientNames) {
t.Fatalf("incorrect expectedAllowedClientNames, got %v, wanted %v", target.AllowedClientNames(), expected.allowedClientNames)
}
}