288 lines
9.3 KiB
Go
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)
|
|
}
|
|
}
|