Merge pull request #115834 from stlaz/remote-uid

RequestHeader authentication: add UID to recognized request headers
This commit is contained in:
Kubernetes Prow Robot 2024-09-05 14:53:37 +01:00 committed by GitHub
commit 9f01cd7b28
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 601 additions and 32 deletions

View File

@ -214,6 +214,7 @@ func StartTestServer(t ktesting.TB, instanceOptions *TestServerInstanceOptions,
}
s.SecureServing.ServerCert.CertDirectory = result.TmpDir
reqHeaderFromFlags := s.Authentication.RequestHeader
if instanceOptions.EnableCertAuth {
// set up default headers for request header auth
reqHeaders := serveroptions.NewDelegatingAuthenticationOptions()
@ -347,6 +348,23 @@ func StartTestServer(t ktesting.TB, instanceOptions *TestServerInstanceOptions,
return result, err
}
// the RequestHeader options pointer gets replaced in the case of EnableCertAuth override
// and so flags are connected to a struct that no longer appears in the ServerOptions struct
// we're using.
// We still want to make it possible to configure the headers config for the RequestHeader authenticator.
if usernameHeaders := reqHeaderFromFlags.UsernameHeaders; len(usernameHeaders) > 0 {
s.Authentication.RequestHeader.UsernameHeaders = usernameHeaders
}
if uidHeaders := reqHeaderFromFlags.UIDHeaders; len(uidHeaders) > 0 {
s.Authentication.RequestHeader.UIDHeaders = uidHeaders
}
if groupHeaders := reqHeaderFromFlags.GroupHeaders; len(groupHeaders) > 0 {
s.Authentication.RequestHeader.GroupHeaders = groupHeaders
}
if extraHeaders := reqHeaderFromFlags.ExtraHeaderPrefixes; len(extraHeaders) > 0 {
s.Authentication.RequestHeader.ExtraHeaderPrefixes = extraHeaders
}
if err := utilversion.DefaultComponentGlobalsRegistry.Set(); err != nil {
return result, err
}

View File

@ -430,6 +430,7 @@ func TestAddFlags(t *testing.T) {
ClientCert: apiserveroptions.ClientCertAuthenticationOptions{},
RequestHeader: apiserveroptions.RequestHeaderAuthenticationOptions{
UsernameHeaders: []string{"x-remote-user"},
UIDHeaders: nil,
GroupHeaders: []string{"x-remote-group"},
ExtraHeaderPrefixes: []string{"x-remote-extra-"},
},

View File

@ -337,6 +337,7 @@ func CreateConfig(
config.ClusterAuthenticationInfo.RequestHeaderExtraHeaderPrefixes = requestHeaderConfig.ExtraHeaderPrefixes
config.ClusterAuthenticationInfo.RequestHeaderGroupHeaders = requestHeaderConfig.GroupHeaders
config.ClusterAuthenticationInfo.RequestHeaderUsernameHeaders = requestHeaderConfig.UsernameHeaders
config.ClusterAuthenticationInfo.RequestHeaderUIDHeaders = requestHeaderConfig.UIDHeaders
}
// setup admission

View File

@ -77,6 +77,8 @@ type ClusterAuthenticationInfo struct {
// RequestHeaderUsernameHeaders are the headers used by this kube-apiserver to determine username
RequestHeaderUsernameHeaders headerrequest.StringSliceProvider
// RequestHeaderUIDHeaders are the headers used by this kube-apiserver to determine UID
RequestHeaderUIDHeaders headerrequest.StringSliceProvider
// RequestHeaderGroupHeaders are the headers used by this kube-apiserver to determine groups
RequestHeaderGroupHeaders headerrequest.StringSliceProvider
// RequestHeaderExtraHeaderPrefixes are the headers used by this kube-apiserver to determine user.extra
@ -224,6 +226,7 @@ func combinedClusterAuthenticationInfo(lhs, rhs ClusterAuthenticationInfo) (Clus
RequestHeaderExtraHeaderPrefixes: combineUniqueStringSlices(lhs.RequestHeaderExtraHeaderPrefixes, rhs.RequestHeaderExtraHeaderPrefixes),
RequestHeaderGroupHeaders: combineUniqueStringSlices(lhs.RequestHeaderGroupHeaders, rhs.RequestHeaderGroupHeaders),
RequestHeaderUsernameHeaders: combineUniqueStringSlices(lhs.RequestHeaderUsernameHeaders, rhs.RequestHeaderUsernameHeaders),
RequestHeaderUIDHeaders: combineUniqueStringSlices(lhs.RequestHeaderUIDHeaders, rhs.RequestHeaderUIDHeaders),
}
var err error
@ -259,6 +262,10 @@ func getConfigMapDataFor(authenticationInfo ClusterAuthenticationInfo) (map[stri
if err != nil {
return nil, err
}
data["requestheader-uid-headers"], err = jsonSerializeStringSlice(authenticationInfo.RequestHeaderUIDHeaders.Value())
if err != nil {
return nil, err
}
data["requestheader-group-headers"], err = jsonSerializeStringSlice(authenticationInfo.RequestHeaderGroupHeaders.Value())
if err != nil {
return nil, err
@ -298,6 +305,10 @@ func getClusterAuthenticationInfoFor(data map[string]string) (ClusterAuthenticat
if err != nil {
return ClusterAuthenticationInfo{}, err
}
ret.RequestHeaderUIDHeaders, err = jsonDeserializeStringSlice(data["requestheader-uid-headers"])
if err != nil {
return ClusterAuthenticationInfo{}, err
}
if caBundle := data["requestheader-client-ca-file"]; len(caBundle) > 0 {
ret.RequestHeaderCA, err = dynamiccertificates.NewStaticCAContent("existing", []byte(caBundle))

View File

@ -101,6 +101,7 @@ func TestWriteClientCAs(t *testing.T) {
clusterAuthInfo: ClusterAuthenticationInfo{
ClientCA: someRandomCAProvider,
RequestHeaderUsernameHeaders: headerrequest.StaticStringSlice{"alfa", "bravo", "charlie"},
RequestHeaderUIDHeaders: headerrequest.StaticStringSlice{"golf", "hotel", "india"},
RequestHeaderGroupHeaders: headerrequest.StaticStringSlice{"delta"},
RequestHeaderExtraHeaderPrefixes: headerrequest.StaticStringSlice{"echo", "foxtrot"},
RequestHeaderCA: anotherRandomCAProvider,
@ -112,6 +113,7 @@ func TestWriteClientCAs(t *testing.T) {
Data: map[string]string{
"client-ca-file": string(someRandomCA),
"requestheader-username-headers": `["alfa","bravo","charlie"]`,
"requestheader-uid-headers": `["golf","hotel","india"]`,
"requestheader-group-headers": `["delta"]`,
"requestheader-extra-headers-prefix": `["echo","foxtrot"]`,
"requestheader-client-ca-file": string(anotherRandomCA),
@ -132,6 +134,7 @@ func TestWriteClientCAs(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
Data: map[string]string{
"requestheader-username-headers": `[]`,
"requestheader-uid-headers": `[]`,
"requestheader-group-headers": `[]`,
"requestheader-extra-headers-prefix": `[]`,
"requestheader-client-ca-file": string(anotherRandomCA),
@ -166,6 +169,7 @@ func TestWriteClientCAs(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
Data: map[string]string{
"requestheader-username-headers": `[]`,
"requestheader-uid-headers": `[]`,
"requestheader-group-headers": `[]`,
"requestheader-extra-headers-prefix": `[]`,
"requestheader-client-ca-file": string(anotherRandomCA),
@ -201,6 +205,7 @@ func TestWriteClientCAs(t *testing.T) {
name: "overwrite extension-apiserver-authentication requestheader",
clusterAuthInfo: ClusterAuthenticationInfo{
RequestHeaderUsernameHeaders: headerrequest.StaticStringSlice{},
RequestHeaderUIDHeaders: headerrequest.StaticStringSlice{},
RequestHeaderGroupHeaders: headerrequest.StaticStringSlice{},
RequestHeaderExtraHeaderPrefixes: headerrequest.StaticStringSlice{},
RequestHeaderCA: anotherRandomCAProvider,
@ -211,6 +216,7 @@ func TestWriteClientCAs(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
Data: map[string]string{
"requestheader-username-headers": `[]`,
"requestheader-uid-headers": `[]`,
"requestheader-group-headers": `[]`,
"requestheader-extra-headers-prefix": `[]`,
"requestheader-client-ca-file": string(someRandomCA),
@ -223,6 +229,7 @@ func TestWriteClientCAs(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
Data: map[string]string{
"requestheader-username-headers": `[]`,
"requestheader-uid-headers": `[]`,
"requestheader-group-headers": `[]`,
"requestheader-extra-headers-prefix": `[]`,
"requestheader-client-ca-file": string(someRandomCA) + string(anotherRandomCA),
@ -253,6 +260,7 @@ func TestWriteClientCAs(t *testing.T) {
name: "skip on no change",
clusterAuthInfo: ClusterAuthenticationInfo{
RequestHeaderUsernameHeaders: headerrequest.StaticStringSlice{},
RequestHeaderUIDHeaders: headerrequest.StaticStringSlice{},
RequestHeaderGroupHeaders: headerrequest.StaticStringSlice{},
RequestHeaderExtraHeaderPrefixes: headerrequest.StaticStringSlice{},
RequestHeaderCA: anotherRandomCAProvider,
@ -263,6 +271,7 @@ func TestWriteClientCAs(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
Data: map[string]string{
"requestheader-username-headers": `[]`,
"requestheader-uid-headers": `[]`,
"requestheader-group-headers": `[]`,
"requestheader-extra-headers-prefix": `[]`,
"requestheader-client-ca-file": string(anotherRandomCA),
@ -332,6 +341,7 @@ func TestWriteConfigMapDeleted(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
Data: map[string]string{
"requestheader-username-headers": `[]`,
"requestheader-uid-headers": `[]`,
"requestheader-group-headers": `[]`,
"requestheader-extra-headers-prefix": `[]`,
"requestheader-client-ca-file": string(anotherRandomCA),

View File

@ -110,6 +110,7 @@ func (config Config) New(serverLifecycle context.Context) (authenticator.Request
config.RequestHeaderConfig.CAContentProvider.VerifyOptions,
config.RequestHeaderConfig.AllowedClientNames,
config.RequestHeaderConfig.UsernameHeaders,
config.RequestHeaderConfig.UIDHeaders,
config.RequestHeaderConfig.GroupHeaders,
config.RequestHeaderConfig.ExtraHeaderPrefixes,
)

View File

@ -303,6 +303,7 @@ func TestToAuthenticationConfig(t *testing.T) {
},
RequestHeader: &apiserveroptions.RequestHeaderAuthenticationOptions{
UsernameHeaders: []string{"x-remote-user"},
UIDHeaders: []string{"x-remote-uid"},
GroupHeaders: []string{"x-remote-group"},
ExtraHeaderPrefixes: []string{"x-remote-extra-"},
ClientCAFile: "testdata/root.pem",
@ -352,6 +353,7 @@ func TestToAuthenticationConfig(t *testing.T) {
RequestHeaderConfig: &authenticatorfactory.RequestHeaderConfig{
UsernameHeaders: headerrequest.StaticStringSlice{"x-remote-user"},
UIDHeaders: headerrequest.StaticStringSlice{"x-remote-uid"},
GroupHeaders: headerrequest.StaticStringSlice{"x-remote-group"},
ExtraHeaderPrefixes: headerrequest.StaticStringSlice{"x-remote-extra-"},
CAContentProvider: nil, // this is nil because you can't compare functions
@ -397,6 +399,7 @@ func TestBuiltInAuthenticationOptionsAddFlags(t *testing.T) {
"--client-ca-file=client-cacert",
"--requestheader-client-ca-file=testdata/root.pem",
"--requestheader-username-headers=x-remote-user-custom",
"--requestheader-uid-headers=x-remote-uid-custom",
"--requestheader-group-headers=x-remote-group-custom",
"--requestheader-allowed-names=kube-aggregator",
"--service-account-key-file=cert",
@ -430,6 +433,7 @@ func TestBuiltInAuthenticationOptionsAddFlags(t *testing.T) {
RequestHeader: &apiserveroptions.RequestHeaderAuthenticationOptions{
ClientCAFile: "testdata/root.pem",
UsernameHeaders: []string{"x-remote-user-custom"},
UIDHeaders: []string{"x-remote-uid-custom"},
GroupHeaders: []string{"x-remote-group-custom"},
AllowedNames: []string{"kube-aggregator"},
},

View File

@ -77,6 +77,7 @@ func (c DelegatingAuthenticatorConfig) New() (authenticator.Request, *spec.Secur
c.RequestHeaderConfig.CAContentProvider.VerifyOptions,
c.RequestHeaderConfig.AllowedClientNames,
c.RequestHeaderConfig.UsernameHeaders,
c.RequestHeaderConfig.UIDHeaders,
c.RequestHeaderConfig.GroupHeaders,
c.RequestHeaderConfig.ExtraHeaderPrefixes,
)

View File

@ -24,6 +24,8 @@ import (
type RequestHeaderConfig struct {
// UsernameHeaders are the headers to check (in order, case-insensitively) for an identity. The first header with a value wins.
UsernameHeaders headerrequest.StringSliceProvider
// UsernameHeaders are the headers to check (in order, case-insensitively) for an identity UID. The first header with a value wins.
UIDHeaders headerrequest.StringSliceProvider
// GroupHeaders are the headers to check (case-insensitively) for a group names. All values will be used.
GroupHeaders headerrequest.StringSliceProvider
// ExtraHeaderPrefixes are the head prefixes to check (case-insentively) for filling in

View File

@ -53,6 +53,9 @@ type requestHeaderAuthRequestHandler struct {
// nameHeaders are the headers to check (in order, case-insensitively) for an identity. The first header with a value wins.
nameHeaders StringSliceProvider
// nameHeaders are the headers to check (in order, case-insensitively) for an identity UID. The first header with a value wins.
uidHeaders StringSliceProvider
// groupHeaders are the headers to check (case-insensitively) for group membership. All values of all headers will be added.
groupHeaders StringSliceProvider
@ -61,11 +64,15 @@ type requestHeaderAuthRequestHandler struct {
extraHeaderPrefixes StringSliceProvider
}
func New(nameHeaders, groupHeaders, extraHeaderPrefixes []string) (authenticator.Request, error) {
func New(nameHeaders, uidHeaders, groupHeaders, extraHeaderPrefixes []string) (authenticator.Request, error) {
trimmedNameHeaders, err := trimHeaders(nameHeaders...)
if err != nil {
return nil, err
}
trimmedUIDHeaders, err := trimHeaders(uidHeaders...)
if err != nil {
return nil, err
}
trimmedGroupHeaders, err := trimHeaders(groupHeaders...)
if err != nil {
return nil, err
@ -77,14 +84,16 @@ func New(nameHeaders, groupHeaders, extraHeaderPrefixes []string) (authenticator
return NewDynamic(
StaticStringSlice(trimmedNameHeaders),
StaticStringSlice(trimmedUIDHeaders),
StaticStringSlice(trimmedGroupHeaders),
StaticStringSlice(trimmedExtraHeaderPrefixes),
), nil
}
func NewDynamic(nameHeaders, groupHeaders, extraHeaderPrefixes StringSliceProvider) authenticator.Request {
func NewDynamic(nameHeaders, uidHeaders, groupHeaders, extraHeaderPrefixes StringSliceProvider) authenticator.Request {
return &requestHeaderAuthRequestHandler{
nameHeaders: nameHeaders,
uidHeaders: uidHeaders,
groupHeaders: groupHeaders,
extraHeaderPrefixes: extraHeaderPrefixes,
}
@ -103,8 +112,8 @@ func trimHeaders(headerNames ...string) ([]string, error) {
return ret, nil
}
func NewDynamicVerifyOptionsSecure(verifyOptionFn x509request.VerifyOptionFunc, proxyClientNames, nameHeaders, groupHeaders, extraHeaderPrefixes StringSliceProvider) authenticator.Request {
headerAuthenticator := NewDynamic(nameHeaders, groupHeaders, extraHeaderPrefixes)
func NewDynamicVerifyOptionsSecure(verifyOptionFn x509request.VerifyOptionFunc, proxyClientNames, nameHeaders, uidHeaders, groupHeaders, extraHeaderPrefixes StringSliceProvider) authenticator.Request {
headerAuthenticator := NewDynamic(nameHeaders, uidHeaders, groupHeaders, extraHeaderPrefixes)
return x509request.NewDynamicCAVerifier(verifyOptionFn, headerAuthenticator, proxyClientNames)
}
@ -114,25 +123,30 @@ func (a *requestHeaderAuthRequestHandler) AuthenticateRequest(req *http.Request)
if len(name) == 0 {
return nil, false, nil
}
uid := headerValue(req.Header, a.uidHeaders.Value())
groups := allHeaderValues(req.Header, a.groupHeaders.Value())
extra := newExtra(req.Header, a.extraHeaderPrefixes.Value())
// clear headers used for authentication
ClearAuthenticationHeaders(req.Header, a.nameHeaders, a.groupHeaders, a.extraHeaderPrefixes)
ClearAuthenticationHeaders(req.Header, a.nameHeaders, a.uidHeaders, a.groupHeaders, a.extraHeaderPrefixes)
return &authenticator.Response{
User: &user.DefaultInfo{
Name: name,
UID: uid,
Groups: groups,
Extra: extra,
},
}, true, nil
}
func ClearAuthenticationHeaders(h http.Header, nameHeaders, groupHeaders, extraHeaderPrefixes StringSliceProvider) {
func ClearAuthenticationHeaders(h http.Header, nameHeaders, uidHeaders, groupHeaders, extraHeaderPrefixes StringSliceProvider) {
for _, headerName := range nameHeaders.Value() {
h.Del(headerName)
}
for _, headerName := range uidHeaders.Value() {
h.Del(headerName)
}
for _, headerName := range groupHeaders.Value() {
h.Del(headerName)
}

View File

@ -45,6 +45,7 @@ const (
// RequestHeaderAuthRequestProvider a provider that knows how to dynamically fill parts of RequestHeaderConfig struct
type RequestHeaderAuthRequestProvider interface {
UsernameHeaders() []string
UIDHeaders() []string
GroupHeaders() []string
ExtraHeaderPrefixes() []string
AllowedClientNames() []string
@ -54,6 +55,7 @@ var _ RequestHeaderAuthRequestProvider = &RequestHeaderAuthRequestController{}
type requestHeaderBundle struct {
UsernameHeaders []string
UIDHeaders []string
GroupHeaders []string
ExtraHeaderPrefixes []string
AllowedClientNames []string
@ -80,6 +82,7 @@ type RequestHeaderAuthRequestController struct {
exportedRequestHeaderBundle atomic.Value
usernameHeadersKey string
uidHeadersKey string
groupHeadersKey string
extraHeaderPrefixesKey string
allowedClientNamesKey string
@ -90,7 +93,7 @@ func NewRequestHeaderAuthRequestController(
cmName string,
cmNamespace string,
client kubernetes.Interface,
usernameHeadersKey, groupHeadersKey, extraHeaderPrefixesKey, allowedClientNamesKey string) *RequestHeaderAuthRequestController {
usernameHeadersKey, uidHeadersKey, groupHeadersKey, extraHeaderPrefixesKey, allowedClientNamesKey string) *RequestHeaderAuthRequestController {
c := &RequestHeaderAuthRequestController{
name: "RequestHeaderAuthRequestController",
@ -100,6 +103,7 @@ func NewRequestHeaderAuthRequestController(
configmapNamespace: cmNamespace,
usernameHeadersKey: usernameHeadersKey,
uidHeadersKey: uidHeadersKey,
groupHeadersKey: groupHeadersKey,
extraHeaderPrefixesKey: extraHeaderPrefixesKey,
allowedClientNamesKey: allowedClientNamesKey,
@ -152,6 +156,10 @@ func (c *RequestHeaderAuthRequestController) UsernameHeaders() []string {
return c.loadRequestHeaderFor(c.usernameHeadersKey)
}
func (c *RequestHeaderAuthRequestController) UIDHeaders() []string {
return c.loadRequestHeaderFor(c.uidHeadersKey)
}
func (c *RequestHeaderAuthRequestController) GroupHeaders() []string {
return c.loadRequestHeaderFor(c.groupHeadersKey)
}
@ -278,6 +286,11 @@ func (c *RequestHeaderAuthRequestController) getRequestHeaderBundleFromConfigMap
return nil, err
}
uidHeaderCurrentValue, err := deserializeStrings(cm.Data[c.uidHeadersKey])
if err != nil {
return nil, err
}
groupHeadersCurrentValue, err := deserializeStrings(cm.Data[c.groupHeadersKey])
if err != nil {
return nil, err
@ -296,6 +309,7 @@ func (c *RequestHeaderAuthRequestController) getRequestHeaderBundleFromConfigMap
return &requestHeaderBundle{
UsernameHeaders: usernameHeaderCurrentValue,
UIDHeaders: uidHeaderCurrentValue,
GroupHeaders: groupHeadersCurrentValue,
ExtraHeaderPrefixes: extraHeaderPrefixesCurrentValue,
AllowedClientNames: allowedClientNamesCurrentValue,
@ -312,6 +326,8 @@ func (c *RequestHeaderAuthRequestController) loadRequestHeaderFor(key string) []
switch key {
case c.usernameHeadersKey:
return headerBundle.UsernameHeaders
case c.uidHeadersKey:
return headerBundle.UIDHeaders
case c.groupHeadersKey:
return headerBundle.GroupHeaders
case c.extraHeaderPrefixesKey:

View File

@ -19,10 +19,10 @@ package headerrequest
import (
"context"
"encoding/json"
"k8s.io/apimachinery/pkg/api/equality"
"testing"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/equality"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"
corev1listers "k8s.io/client-go/listers/core/v1"
@ -34,6 +34,7 @@ const (
defConfigMapNamespace = "kube-system"
defUsernameHeadersKey = "user-key"
defUIDHeadersKey = "uid-key"
defGroupHeadersKey = "group-key"
defExtraHeaderPrefixesKey = "extra-key"
defAllowedClientNamesKey = "names-key"
@ -41,6 +42,7 @@ const (
type expectedHeadersHolder struct {
usernameHeaders []string
uidHeaders []string
groupHeaders []string
extraHeaderPrefixes []string
allowedClientNames []string
@ -55,9 +57,10 @@ func TestRequestHeaderAuthRequestController(t *testing.T) {
}{
{
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"}),
cm: defaultConfigMap(t, []string{"user-val"}, []string{"uid-val"}, []string{"group-val"}, []string{"extra-val"}, []string{"names-val"}),
expectedHeader: expectedHeadersHolder{
usernameHeaders: []string{"user-val"},
uidHeaders: []string{"uid-val"},
groupHeaders: []string{"group-val"},
extraHeaderPrefixes: []string{"extra-val"},
allowedClientNames: []string{"names-val"},
@ -66,7 +69,7 @@ func TestRequestHeaderAuthRequestController(t *testing.T) {
{
name: "passing an empty config map doesn't break the controller",
cm: func() *corev1.ConfigMap {
c := defaultConfigMap(t, nil, nil, nil, nil)
c := defaultConfigMap(t, nil, nil, nil, nil, nil)
c.Data = map[string]string{}
return c
}(),
@ -74,7 +77,7 @@ func TestRequestHeaderAuthRequestController(t *testing.T) {
{
name: "an invalid config map produces an error",
cm: func() *corev1.ConfigMap {
c := defaultConfigMap(t, nil, nil, nil, nil)
c := defaultConfigMap(t, nil, nil, nil, nil, nil)
c.Data = map[string]string{
defUsernameHeadersKey: "incorrect-json-array",
}
@ -119,9 +122,10 @@ func TestRequestHeaderAuthRequestControllerPreserveState(t *testing.T) {
}{
{
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"}),
cm: defaultConfigMap(t, []string{"user-val"}, []string{"uid-val"}, []string{"group-val"}, []string{"extra-val"}, []string{"names-val"}),
expectedHeader: expectedHeadersHolder{
usernameHeaders: []string{"user-val"},
uidHeaders: []string{"uid-val"},
groupHeaders: []string{"group-val"},
extraHeaderPrefixes: []string{"extra-val"},
allowedClientNames: []string{"names-val"},
@ -130,7 +134,7 @@ func TestRequestHeaderAuthRequestControllerPreserveState(t *testing.T) {
{
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 := defaultConfigMap(t, nil, nil, nil, nil, nil)
c.Data = map[string]string{
defUsernameHeadersKey: "incorrect-json-array",
}
@ -139,6 +143,7 @@ func TestRequestHeaderAuthRequestControllerPreserveState(t *testing.T) {
expectErr: true,
expectedHeader: expectedHeadersHolder{
usernameHeaders: []string{"user-val"},
uidHeaders: []string{"uid-val"},
groupHeaders: []string{"group-val"},
extraHeaderPrefixes: []string{"extra-val"},
allowedClientNames: []string{"names-val"},
@ -146,9 +151,10 @@ func TestRequestHeaderAuthRequestControllerPreserveState(t *testing.T) {
},
{
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"}),
cm: defaultConfigMap(t, []string{"user-val"}, []string{"uid-val"}, []string{"group-val-scenario-3"}, []string{"extra-val"}, []string{"names-val"}),
expectedHeader: expectedHeadersHolder{
usernameHeaders: []string{"user-val"},
uidHeaders: []string{"uid-val"},
groupHeaders: []string{"group-val-scenario-3"},
extraHeaderPrefixes: []string{"extra-val"},
allowedClientNames: []string{"names-val"},
@ -156,9 +162,10 @@ func TestRequestHeaderAuthRequestControllerPreserveState(t *testing.T) {
},
{
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"}),
cm: defaultConfigMap(t, []string{"user-val-scenario-4"}, []string{"uid-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"},
uidHeaders: []string{"uid-val-scenario-4"},
groupHeaders: []string{"group-val-scenario-4"},
extraHeaderPrefixes: []string{"extra-val-scenario-4"},
allowedClientNames: []string{"names-val-scenario-4"},
@ -204,9 +211,10 @@ func TestRequestHeaderAuthRequestControllerSyncOnce(t *testing.T) {
}{
{
name: "headers values are populated form a config map",
cm: defaultConfigMap(t, []string{"user-val"}, []string{"group-val"}, []string{"extra-val"}, []string{"names-val"}),
cm: defaultConfigMap(t, []string{"user-val"}, []string{"uid-val"}, []string{"group-val"}, []string{"extra-val"}, []string{"names-val"}),
expectedHeader: expectedHeadersHolder{
usernameHeaders: []string{"user-val"},
uidHeaders: []string{"uid-val"},
groupHeaders: []string{"group-val"},
extraHeaderPrefixes: []string{"extra-val"},
allowedClientNames: []string{"names-val"},
@ -238,7 +246,7 @@ func TestRequestHeaderAuthRequestControllerSyncOnce(t *testing.T) {
}
}
func defaultConfigMap(t *testing.T, usernameHeaderVal, groupHeadersVal, extraHeaderPrefixesVal, allowedClientNamesVal []string) *corev1.ConfigMap {
func defaultConfigMap(t *testing.T, usernameHeaderVal, uidHeaderVal, groupHeadersVal, extraHeaderPrefixesVal, allowedClientNamesVal []string) *corev1.ConfigMap {
encode := func(val []string) string {
encodedVal, err := json.Marshal(val)
if err != nil {
@ -253,6 +261,7 @@ func defaultConfigMap(t *testing.T, usernameHeaderVal, groupHeadersVal, extraHea
},
Data: map[string]string{
defUsernameHeadersKey: encode(usernameHeaderVal),
defUIDHeadersKey: encode(uidHeaderVal),
defGroupHeadersKey: encode(groupHeadersVal),
defExtraHeaderPrefixesKey: encode(extraHeaderPrefixesVal),
defAllowedClientNamesKey: encode(allowedClientNamesVal),
@ -265,6 +274,7 @@ func newDefaultTarget() *RequestHeaderAuthRequestController {
configmapName: defConfigMapName,
configmapNamespace: defConfigMapNamespace,
usernameHeadersKey: defUsernameHeadersKey,
uidHeadersKey: defUIDHeadersKey,
groupHeadersKey: defGroupHeadersKey,
extraHeaderPrefixesKey: defExtraHeaderPrefixesKey,
allowedClientNamesKey: defAllowedClientNamesKey,

View File

@ -29,6 +29,7 @@ import (
func TestRequestHeader(t *testing.T) {
testcases := map[string]struct {
nameHeaders []string
uidHeaders []string
groupHeaders []string
extraPrefixHeaders []string
requestHeaders http.Header
@ -128,13 +129,66 @@ func TestRequestHeader(t *testing.T) {
},
expectedOk: true,
},
"uid none": {
nameHeaders: []string{"X-Remote-User"},
uidHeaders: []string{"X-Remote-Uid"},
requestHeaders: http.Header{
"X-Remote-User": {"Bob"},
},
expectedUser: &user.DefaultInfo{
Name: "Bob",
UID: "",
Groups: []string{},
Extra: map[string][]string{},
},
expectedOk: true,
},
"uid exact match": {
nameHeaders: []string{"X-Remote-User"},
uidHeaders: []string{"X-Remote-Uid"},
requestHeaders: http.Header{
"X-Remote-User": {"Bob"},
// The keys in http.Header MUST be http.CanonicalHeaderKey.
// Hence X-Remote-Uid-1 instead of X-Remote-UID-1.
"X-Remote-Uid-1": {"8f5ea9d1-a5ed-4d02-80a2-26709216350b"},
"X-Remote-Uid-2": {"c7644180-c774-4a9b-81e5-3eef76f087ab"},
},
finalHeaders: http.Header{
"X-Remote-Uid-1": {"8f5ea9d1-a5ed-4d02-80a2-26709216350b"},
"X-Remote-Uid-2": {"c7644180-c774-4a9b-81e5-3eef76f087ab"},
},
expectedUser: &user.DefaultInfo{
Name: "Bob",
UID: "",
Groups: []string{},
Extra: map[string][]string{},
},
expectedOk: true,
},
"uid first match": {
nameHeaders: []string{"X-Remote-User"},
uidHeaders: []string{"X-Remote-Uid-1", "X-Remote-Uid-2"},
requestHeaders: http.Header{
"X-Remote-User": {"Bob"},
"X-Remote-Uid-1": {"8f5ea9d1-a5ed-4d02-80a2-26709216350b"},
"X-Remote-Uid-2": {"c7644180-c774-4a9b-81e5-3eef76f087ab"},
},
expectedUser: &user.DefaultInfo{
Name: "Bob",
UID: "8f5ea9d1-a5ed-4d02-80a2-26709216350b",
Groups: []string{},
Extra: map[string][]string{},
},
expectedOk: true,
},
"extra prefix matches case-insensitive": {
nameHeaders: []string{"X-Remote-User"},
uidHeaders: []string{"X-Remote-UID"},
groupHeaders: []string{"X-Remote-Group-1", "X-Remote-Group-2"},
extraPrefixHeaders: []string{"X-Remote-Extra-1-", "X-Remote-Extra-2-"},
requestHeaders: http.Header{
"X-Remote-User": {"Bob"},
"X-Remote-Uid": {"2ca80fb0-60ea-4ecf-951c-89af843b0402"},
"X-Remote-Group-1": {"one-a", "one-b"},
"X-Remote-Group-2": {"two-a", "two-b"},
"X-Remote-extra-1-key1": {"alfa", "bravo"},
@ -146,6 +200,7 @@ func TestRequestHeader(t *testing.T) {
},
expectedUser: &user.DefaultInfo{
Name: "Bob",
UID: "2ca80fb0-60ea-4ecf-951c-89af843b0402",
Groups: []string{"one-a", "one-b", "two-a", "two-b"},
Extra: map[string][]string{
"key1": {"alfa", "bravo", "echo", "foxtrot"},
@ -191,10 +246,12 @@ func TestRequestHeader(t *testing.T) {
"escaped extra keys": {
nameHeaders: []string{"X-Remote-User"},
uidHeaders: []string{"X-Remote-Uid"},
groupHeaders: []string{"X-Remote-Group"},
extraPrefixHeaders: []string{"X-Remote-Extra-"},
requestHeaders: http.Header{
"X-Remote-User": {"Bob"},
"X-Remote-Uid": {"2ca80fb0-60ea-4ecf-951c-89af843b0402"},
"X-Remote-Group": {"one-a", "one-b"},
"X-Remote-Extra-Alpha": {"alphabetical"},
"X-Remote-Extra-Alph4num3r1c": {"alphanumeric"},
@ -206,6 +263,7 @@ func TestRequestHeader(t *testing.T) {
},
expectedUser: &user.DefaultInfo{
Name: "Bob",
UID: "2ca80fb0-60ea-4ecf-951c-89af843b0402",
Groups: []string{"one-a", "one-b"},
Extra: map[string][]string{
"alpha": {"alphabetical"},
@ -223,7 +281,7 @@ func TestRequestHeader(t *testing.T) {
for k, testcase := range testcases {
t.Run(k, func(t *testing.T) {
auth, err := New(testcase.nameHeaders, testcase.groupHeaders, testcase.extraPrefixHeaders)
auth, err := New(testcase.nameHeaders, testcase.uidHeaders, testcase.groupHeaders, testcase.extraPrefixHeaders)
if err != nil {
t.Fatal(err)
}

View File

@ -54,6 +54,7 @@ func withAuthentication(handler http.Handler, auth authenticator.Request, failed
}
standardRequestHeaderConfig := &authenticatorfactory.RequestHeaderConfig{
UsernameHeaders: headerrequest.StaticStringSlice{"X-Remote-User"},
UIDHeaders: headerrequest.StaticStringSlice{"X-Remote-Uid"},
GroupHeaders: headerrequest.StaticStringSlice{"X-Remote-Group"},
ExtraHeaderPrefixes: headerrequest.StaticStringSlice{"X-Remote-Extra-"},
}
@ -90,6 +91,7 @@ func withAuthentication(handler http.Handler, auth authenticator.Request, failed
headerrequest.ClearAuthenticationHeaders(
req.Header,
standardRequestHeaderConfig.UsernameHeaders,
standardRequestHeaderConfig.UIDHeaders,
standardRequestHeaderConfig.GroupHeaders,
standardRequestHeaderConfig.ExtraHeaderPrefixes,
)
@ -99,6 +101,7 @@ func withAuthentication(handler http.Handler, auth authenticator.Request, failed
headerrequest.ClearAuthenticationHeaders(
req.Header,
requestHeaderConfig.UsernameHeaders,
requestHeaderConfig.UIDHeaders,
requestHeaderConfig.GroupHeaders,
requestHeaderConfig.ExtraHeaderPrefixes,
)

View File

@ -285,6 +285,7 @@ func TestAuthenticateRequestError(t *testing.T) {
func TestAuthenticateRequestClearHeaders(t *testing.T) {
testcases := map[string]struct {
nameHeaders []string
uidHeaders []string
groupHeaders []string
extraPrefixHeaders []string
requestHeaders http.Header
@ -334,13 +335,39 @@ func TestAuthenticateRequestClearHeaders(t *testing.T) {
"X-Remote-Group": {"Users"},
},
},
"uid none": {
nameHeaders: []string{"X-Remote-User"},
uidHeaders: []string{"X-Remote-Uid"},
requestHeaders: http.Header{
"X-Remote-User": {"Alice"},
},
},
"uid all matches": {
nameHeaders: []string{"X-Remote-User"},
uidHeaders: []string{"X-Remote-Uid-1", "X-Remote-Uid-2"},
requestHeaders: http.Header{
"X-Remote-User": {"Alice"},
"X-Remote-Uid-1": {"one"},
"X-Remote-Uid-2": {"two", "three"},
},
},
"uid case-insensitive": {
nameHeaders: []string{"X-Remote-USER"},
uidHeaders: []string{"X-REMOTE-UID-1"},
requestHeaders: http.Header{
"X-Remote-User": {"Alice"},
"X-Remote-Uid-1": {"one"},
},
},
"extra prefix matches case-insensitive": {
nameHeaders: []string{"X-Remote-User"},
uidHeaders: []string{"X-Remote-Uid-1"},
groupHeaders: []string{"X-Remote-Group-1", "X-Remote-Group-2"},
extraPrefixHeaders: []string{"X-Remote-Extra-1-", "X-Remote-Extra-2-"},
requestHeaders: http.Header{
"X-Remote-User": {"Bob"},
"X-Remote-Uid-1": {"bobs-uid"},
"X-Remote-Group-1": {"one-a", "one-b"},
"X-Remote-Group-2": {"two-a", "two-b"},
"X-Remote-extra-1-key1": {"alfa", "bravo"},
@ -354,12 +381,15 @@ func TestAuthenticateRequestClearHeaders(t *testing.T) {
"extra prefix matches case-insensitive with unrelated headers": {
nameHeaders: []string{"X-Remote-User"},
uidHeaders: []string{"X-Remote-Uid"},
groupHeaders: []string{"X-Remote-Group-1", "X-Remote-Group-2"},
extraPrefixHeaders: []string{"X-Remote-Extra-1-", "X-Remote-Extra-2-"},
requestHeaders: http.Header{
"X-Group-Remote": {"snorlax"}, // unrelated header
"X-Group-Bear": {"panda"}, // another unrelated header
"X-Uid-Remote": {"bobs-unrelated-uid"},
"X-Remote-User": {"Bob"},
"X-Remote-Uid": {"bobs-uid"},
"X-Remote-Group-1": {"one-a", "one-b"},
"X-Remote-Group-2": {"two-a", "two-b"},
"X-Remote-extra-1-key1": {"alfa", "bravo"},
@ -372,15 +402,18 @@ func TestAuthenticateRequestClearHeaders(t *testing.T) {
finalHeaders: http.Header{
"X-Group-Remote": {"snorlax"},
"X-Group-Bear": {"panda"},
"X-Uid-Remote": {"bobs-unrelated-uid"},
},
},
"custom config but request contains standard headers": {
nameHeaders: []string{"foo"},
uidHeaders: []string{"footoo"},
groupHeaders: []string{"bar"},
extraPrefixHeaders: []string{"baz"},
requestHeaders: http.Header{
"X-Remote-User": {"Bob"},
"X-Remote-Uid": {"bobs-uid"},
"X-Remote-Group-1": {"one-a", "one-b"},
"X-Remote-Group-2": {"two-a", "two-b"},
"X-Remote-extra-1-key1": {"alfa", "bravo"},
@ -398,10 +431,12 @@ func TestAuthenticateRequestClearHeaders(t *testing.T) {
"custom config but request contains standard and custom headers": {
nameHeaders: []string{"one"},
uidHeaders: []string{"onetoo"},
groupHeaders: []string{"two"},
extraPrefixHeaders: []string{"three-"},
requestHeaders: http.Header{
"X-Remote-User": {"Bob"},
"X-Remote-Uid": {"bobs-uid"},
"X-Remote-Group-3": {"one-a", "one-b"},
"X-Remote-Group-4": {"two-a", "two-b"},
"X-Remote-extra-1-key1": {"alfa", "bravo"},
@ -422,10 +457,12 @@ func TestAuthenticateRequestClearHeaders(t *testing.T) {
"escaped extra keys": {
nameHeaders: []string{"X-Remote-User"},
uidHeaders: []string{"X-Remote-Uid"},
groupHeaders: []string{"X-Remote-Group"},
extraPrefixHeaders: []string{"X-Remote-Extra-"},
requestHeaders: http.Header{
"X-Remote-User": {"Bob"},
"X-Remote-Uid": {"bobs-uid"},
"X-Remote-Group": {"one-a", "one-b"},
"X-Remote-Extra-Alpha": {"alphabetical"},
"X-Remote-Extra-Alph4num3r1c": {"alphanumeric"},
@ -455,6 +492,7 @@ func TestAuthenticateRequestClearHeaders(t *testing.T) {
nil,
&authenticatorfactory.RequestHeaderConfig{
UsernameHeaders: headerrequest.StaticStringSlice(testcase.nameHeaders),
UIDHeaders: headerrequest.StaticStringSlice(testcase.uidHeaders),
GroupHeaders: headerrequest.StaticStringSlice(testcase.groupHeaders),
ExtraHeaderPrefixes: headerrequest.StaticStringSlice(testcase.extraPrefixHeaders),
},

View File

@ -56,6 +56,7 @@ type RequestHeaderAuthenticationOptions struct {
ClientCAFile string
UsernameHeaders []string
UIDHeaders []string
GroupHeaders []string
ExtraHeaderPrefixes []string
AllowedNames []string
@ -67,6 +68,9 @@ func (s *RequestHeaderAuthenticationOptions) Validate() []error {
if err := checkForWhiteSpaceOnly("requestheader-username-headers", s.UsernameHeaders...); err != nil {
allErrors = append(allErrors, err)
}
if err := checkForWhiteSpaceOnly("requestheader-uid-headers", s.UIDHeaders...); err != nil {
allErrors = append(allErrors, err)
}
if err := checkForWhiteSpaceOnly("requestheader-group-headers", s.GroupHeaders...); err != nil {
allErrors = append(allErrors, err)
}
@ -80,6 +84,10 @@ func (s *RequestHeaderAuthenticationOptions) Validate() []error {
if len(s.UsernameHeaders) > 0 && !caseInsensitiveHas(s.UsernameHeaders, "X-Remote-User") {
klog.Warningf("--requestheader-username-headers is set without specifying the standard X-Remote-User header - API aggregation will not work")
}
if len(s.UIDHeaders) > 0 && !caseInsensitiveHas(s.UIDHeaders, "X-Remote-Uid") {
// this was added later and so we are able to error out
allErrors = append(allErrors, fmt.Errorf("--requestheader-uid-headers is set without specifying the standard X-Remote-Uid header - API aggregation will not work"))
}
if len(s.GroupHeaders) > 0 && !caseInsensitiveHas(s.GroupHeaders, "X-Remote-Group") {
klog.Warningf("--requestheader-group-headers is set without specifying the standard X-Remote-Group header - API aggregation will not work")
}
@ -117,6 +125,9 @@ func (s *RequestHeaderAuthenticationOptions) AddFlags(fs *pflag.FlagSet) {
fs.StringSliceVar(&s.UsernameHeaders, "requestheader-username-headers", s.UsernameHeaders, ""+
"List of request headers to inspect for usernames. X-Remote-User is common.")
fs.StringSliceVar(&s.UIDHeaders, "requestheader-uid-headers", s.UIDHeaders, ""+
"List of request headers to inspect for UIDs. X-Remote-Uid is suggested.")
fs.StringSliceVar(&s.GroupHeaders, "requestheader-group-headers", s.GroupHeaders, ""+
"List of request headers to inspect for groups. X-Remote-Group is suggested.")
@ -148,6 +159,7 @@ func (s *RequestHeaderAuthenticationOptions) ToAuthenticationRequestHeaderConfig
return &authenticatorfactory.RequestHeaderConfig{
UsernameHeaders: headerrequest.StaticStringSlice(s.UsernameHeaders),
UIDHeaders: headerrequest.StaticStringSlice(s.UIDHeaders),
GroupHeaders: headerrequest.StaticStringSlice(s.GroupHeaders),
ExtraHeaderPrefixes: headerrequest.StaticStringSlice(s.ExtraHeaderPrefixes),
CAContentProvider: caBundleProvider,
@ -233,7 +245,13 @@ func NewDelegatingAuthenticationOptions() *DelegatingAuthenticationOptions {
CacheTTL: 10 * time.Second,
ClientCert: ClientCertAuthenticationOptions{},
RequestHeader: RequestHeaderAuthenticationOptions{
UsernameHeaders: []string{"x-remote-user"},
UsernameHeaders: []string{"x-remote-user"},
// we specifically don't default UID headers as these were introduced
// later (kube 1.32) and we don't want 3rd parties to be trusting the default headers
// before we can safely say that all KAS instances know they should
// remove them from an incoming request in its WithAuthentication handler.
// The defaulting will be enabled in a future (1.33+) version.
UIDHeaders: nil,
GroupHeaders: []string{"x-remote-group"},
ExtraHeaderPrefixes: []string{"x-remote-extra-"},
},
@ -423,6 +441,7 @@ func (s *DelegatingAuthenticationOptions) createRequestHeaderConfig(client kuber
return &authenticatorfactory.RequestHeaderConfig{
CAContentProvider: dynamicRequestHeaderProvider,
UsernameHeaders: headerrequest.StringSliceProvider(headerrequest.StringSliceProviderFunc(dynamicRequestHeaderProvider.UsernameHeaders)),
UIDHeaders: headerrequest.StringSliceProvider(headerrequest.StringSliceProviderFunc(dynamicRequestHeaderProvider.UIDHeaders)),
GroupHeaders: headerrequest.StringSliceProvider(headerrequest.StringSliceProviderFunc(dynamicRequestHeaderProvider.GroupHeaders)),
ExtraHeaderPrefixes: headerrequest.StringSliceProvider(headerrequest.StringSliceProviderFunc(dynamicRequestHeaderProvider.ExtraHeaderPrefixes)),
AllowedClientNames: headerrequest.StringSliceProvider(headerrequest.StringSliceProviderFunc(dynamicRequestHeaderProvider.AllowedClientNames)),

View File

@ -55,6 +55,7 @@ func newDynamicRequestHeaderController(client kubernetes.Interface) (*DynamicReq
authenticationConfigMapNamespace,
client,
"requestheader-username-headers",
"requestheader-uid-headers",
"requestheader-group-headers",
"requestheader-extra-headers-prefix",
"requestheader-allowed-names",

View File

@ -39,6 +39,7 @@ func TestToAuthenticationRequestHeaderConfig(t *testing.T) {
name: "test when ClientCAFile is nil",
testOptions: &RequestHeaderAuthenticationOptions{
UsernameHeaders: headerrequest.StaticStringSlice{"x-remote-user"},
UIDHeaders: headerrequest.StaticStringSlice{"x-remote-uid"},
GroupHeaders: headerrequest.StaticStringSlice{"x-remote-group"},
ExtraHeaderPrefixes: headerrequest.StaticStringSlice{"x-remote-extra-"},
AllowedNames: headerrequest.StaticStringSlice{"kube-aggregator"},
@ -49,12 +50,14 @@ func TestToAuthenticationRequestHeaderConfig(t *testing.T) {
testOptions: &RequestHeaderAuthenticationOptions{
ClientCAFile: "testdata/root.pem",
UsernameHeaders: headerrequest.StaticStringSlice{"x-remote-user"},
UIDHeaders: headerrequest.StaticStringSlice{"x-remote-uid"},
GroupHeaders: headerrequest.StaticStringSlice{"x-remote-group"},
ExtraHeaderPrefixes: headerrequest.StaticStringSlice{"x-remote-extra-"},
AllowedNames: headerrequest.StaticStringSlice{"kube-aggregator"},
},
expectConfig: &authenticatorfactory.RequestHeaderConfig{
UsernameHeaders: headerrequest.StaticStringSlice{"x-remote-user"},
UIDHeaders: headerrequest.StaticStringSlice{"x-remote-uid"},
GroupHeaders: headerrequest.StaticStringSlice{"x-remote-group"},
ExtraHeaderPrefixes: headerrequest.StaticStringSlice{"x-remote-extra-"},
CAContentProvider: nil, // this is nil because you can't compare functions

View File

@ -251,7 +251,7 @@ func (h *peerProxyHandler) proxyRequestToDestinationAPIServer(req *http.Request,
newReq.Header.Add(PeerProxiedHeader, "true")
defer cancelFn()
proxyRoundTripper := transport.NewAuthProxyRoundTripper(user.GetName(), user.GetGroups(), user.GetExtra(), h.proxyTransport)
proxyRoundTripper := transport.NewAuthProxyRoundTripper(user.GetName(), user.GetUID(), user.GetGroups(), user.GetExtra(), h.proxyTransport)
delegate := &epmetrics.ResponseWriterDelegator{ResponseWriter: rw}
w := responsewriter.WrapForHTTP1Or2(delegate)

View File

@ -86,6 +86,7 @@ func DebugWrappers(rt http.RoundTripper) http.RoundTripper {
type authProxyRoundTripper struct {
username string
uid string
groups []string
extra map[string][]string
@ -98,15 +99,17 @@ var _ utilnet.RoundTripperWrapper = &authProxyRoundTripper{}
// authentication terminating proxy cases
// assuming you pull the user from the context:
// username is the user.Info.GetName() of the user
// uid is the user.Info.GetUID() of the user
// groups is the user.Info.GetGroups() of the user
// extra is the user.Info.GetExtra() of the user
// extra can contain any additional information that the authenticator
// thought was interesting, for example authorization scopes.
// In order to faithfully round-trip through an impersonation flow, these keys
// MUST be lowercase.
func NewAuthProxyRoundTripper(username string, groups []string, extra map[string][]string, rt http.RoundTripper) http.RoundTripper {
func NewAuthProxyRoundTripper(username, uid string, groups []string, extra map[string][]string, rt http.RoundTripper) http.RoundTripper {
return &authProxyRoundTripper{
username: username,
uid: uid,
groups: groups,
extra: extra,
rt: rt,
@ -115,14 +118,15 @@ func NewAuthProxyRoundTripper(username string, groups []string, extra map[string
func (rt *authProxyRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
req = utilnet.CloneRequest(req)
SetAuthProxyHeaders(req, rt.username, rt.groups, rt.extra)
SetAuthProxyHeaders(req, rt.username, rt.uid, rt.groups, rt.extra)
return rt.rt.RoundTrip(req)
}
// SetAuthProxyHeaders stomps the auth proxy header fields. It mutates its argument.
func SetAuthProxyHeaders(req *http.Request, username string, groups []string, extra map[string][]string) {
func SetAuthProxyHeaders(req *http.Request, username, uid string, groups []string, extra map[string][]string) {
req.Header.Del("X-Remote-User")
req.Header.Del("X-Remote-Uid")
req.Header.Del("X-Remote-Group")
for key := range req.Header {
if strings.HasPrefix(strings.ToLower(key), strings.ToLower("X-Remote-Extra-")) {
@ -131,6 +135,9 @@ func SetAuthProxyHeaders(req *http.Request, username string, groups []string, ex
}
req.Header.Set("X-Remote-User", username)
if len(uid) > 0 {
req.Header.Set("X-Remote-Uid", uid)
}
for _, group := range groups {
req.Header.Add("X-Remote-Group", group)
}

View File

@ -306,12 +306,14 @@ func TestImpersonationRoundTripper(t *testing.T) {
func TestAuthProxyRoundTripper(t *testing.T) {
for n, tc := range map[string]struct {
username string
uid string
groups []string
extra map[string][]string
expectedExtra map[string][]string
}{
"allfields": {
username: "user",
uid: "7db46926-e803-4337-9a29-f9c1fab7d34a",
groups: []string{"groupA", "groupB"},
extra: map[string][]string{
"one": {"alpha", "bravo"},
@ -324,6 +326,7 @@ func TestAuthProxyRoundTripper(t *testing.T) {
},
"escaped extra": {
username: "user",
uid: "7db46926-e803-4337-9a29-f9c1fab7d34a",
groups: []string{"groupA", "groupB"},
extra: map[string][]string{
"one": {"alpha", "bravo"},
@ -336,6 +339,7 @@ func TestAuthProxyRoundTripper(t *testing.T) {
},
"double escaped extra": {
username: "user",
uid: "7db46926-e803-4337-9a29-f9c1fab7d34a",
groups: []string{"groupA", "groupB"},
extra: map[string][]string{
"one": {"alpha", "bravo"},
@ -349,7 +353,7 @@ func TestAuthProxyRoundTripper(t *testing.T) {
} {
rt := &testRoundTripper{}
req := &http.Request{}
NewAuthProxyRoundTripper(tc.username, tc.groups, tc.extra, rt).RoundTrip(req)
_, _ = NewAuthProxyRoundTripper(tc.username, tc.uid, tc.groups, tc.extra, rt).RoundTrip(req)
if rt.Request == nil {
t.Errorf("%s: unexpected nil request: %v", n, rt)
continue
@ -368,6 +372,15 @@ func TestAuthProxyRoundTripper(t *testing.T) {
t.Errorf("%s expected %v, got %v", n, e, a)
continue
}
actualUID, ok := rt.Request.Header["X-Remote-Uid"]
if !ok {
t.Errorf("%s missing value", n)
continue
}
if e, a := []string{tc.uid}, actualUID; !reflect.DeepEqual(e, a) {
t.Errorf("%s expected %v, got %v", n, e, a)
continue
}
actualGroups, ok := rt.Request.Header["X-Remote-Group"]
if !ok {
t.Errorf("%s missing value", n)

View File

@ -133,6 +133,7 @@ func TestDefaultFlags(t *testing.T) {
ClientCert: apiserveroptions.ClientCertAuthenticationOptions{},
RequestHeader: apiserveroptions.RequestHeaderAuthenticationOptions{
UsernameHeaders: []string{"x-remote-user"},
UIDHeaders: nil,
GroupHeaders: []string{"x-remote-group"},
ExtraHeaderPrefixes: []string{"x-remote-extra-"},
},
@ -293,6 +294,7 @@ func TestAddFlags(t *testing.T) {
ClientCert: apiserveroptions.ClientCertAuthenticationOptions{},
RequestHeader: apiserveroptions.RequestHeaderAuthenticationOptions{
UsernameHeaders: []string{"x-remote-user"},
UIDHeaders: nil,
GroupHeaders: []string{"x-remote-group"},
ExtraHeaderPrefixes: []string{"x-remote-extra-"},
},

View File

@ -159,7 +159,7 @@ func (r *proxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
proxyRoundTripper := handlingInfo.proxyRoundTripper
upgrade := httpstream.IsUpgradeRequest(req)
proxyRoundTripper = transport.NewAuthProxyRoundTripper(user.GetName(), user.GetGroups(), user.GetExtra(), proxyRoundTripper)
proxyRoundTripper = transport.NewAuthProxyRoundTripper(user.GetName(), user.GetUID(), user.GetGroups(), user.GetExtra(), proxyRoundTripper)
if utilfeature.DefaultFeatureGate.Enabled(genericfeatures.APIServerTracing) && !upgrade {
tracingWrapper := tracing.WrapperFor(r.tracerProvider)
@ -170,7 +170,7 @@ func (r *proxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// NOT use the proxyRoundTripper. It's a direct dial that bypasses the proxyRoundTripper. This means that we have to
// attach the "correct" user headers to the request ahead of time.
if upgrade {
transport.SetAuthProxyHeaders(newReq, user.GetName(), user.GetGroups(), user.GetExtra())
transport.SetAuthProxyHeaders(newReq, user.GetName(), user.GetUID(), user.GetGroups(), user.GetExtra())
}
handler := proxy.NewUpgradeAwareHandler(location, proxyRoundTripper, true, upgrade, &responder{w: w})

View File

@ -154,6 +154,7 @@ func TestProxyHandler(t *testing.T) {
"proxy with user, insecure": {
user: &user.DefaultInfo{
Name: "username",
UID: "6b60d791-1af9-4513-92e5-e4252a1e0a78",
Groups: []string{"one", "two"},
},
path: "/request/path",
@ -178,6 +179,7 @@ func TestProxyHandler(t *testing.T) {
"X-Forwarded-Uri": {"/request/path"},
"X-Forwarded-For": {"127.0.0.1"},
"X-Remote-User": {"username"},
"X-Remote-Uid": {"6b60d791-1af9-4513-92e5-e4252a1e0a78"},
"User-Agent": {"Go-http-client/1.1"},
"Accept-Encoding": {"gzip"},
"X-Remote-Group": {"one", "two"},
@ -186,6 +188,7 @@ func TestProxyHandler(t *testing.T) {
"proxy with user, cabundle": {
user: &user.DefaultInfo{
Name: "username",
UID: "6b60d791-1af9-4513-92e5-e4252a1e0a78",
Groups: []string{"one", "two"},
},
path: "/request/path",
@ -210,6 +213,7 @@ func TestProxyHandler(t *testing.T) {
"X-Forwarded-Uri": {"/request/path"},
"X-Forwarded-For": {"127.0.0.1"},
"X-Remote-User": {"username"},
"X-Remote-Uid": {"6b60d791-1af9-4513-92e5-e4252a1e0a78"},
"User-Agent": {"Go-http-client/1.1"},
"Accept-Encoding": {"gzip"},
"X-Remote-Group": {"one", "two"},
@ -218,6 +222,7 @@ func TestProxyHandler(t *testing.T) {
"service unavailable": {
user: &user.DefaultInfo{
Name: "username",
UID: "6b60d791-1af9-4513-92e5-e4252a1e0a78",
Groups: []string{"one", "two"},
},
path: "/request/path",
@ -240,6 +245,7 @@ func TestProxyHandler(t *testing.T) {
"service unresolveable": {
user: &user.DefaultInfo{
Name: "username",
UID: "6b60d791-1af9-4513-92e5-e4252a1e0a78",
Groups: []string{"one", "two"},
},
path: "/request/path",
@ -263,6 +269,7 @@ func TestProxyHandler(t *testing.T) {
"fail on bad serving cert": {
user: &user.DefaultInfo{
Name: "username",
UID: "6b60d791-1af9-4513-92e5-e4252a1e0a78",
Groups: []string{"one", "two"},
},
path: "/request/path",
@ -284,6 +291,7 @@ func TestProxyHandler(t *testing.T) {
"fail on bad serving cert w/o SAN and increase SAN error counter metrics": {
user: &user.DefaultInfo{
Name: "username",
UID: "6b60d791-1af9-4513-92e5-e4252a1e0a78",
Groups: []string{"one", "two"},
},
path: "/request/path",
@ -425,6 +433,7 @@ func newBrokenDialerAndSelector() (*mockEgressDialer, *egressselector.EgressSele
func TestProxyUpgrade(t *testing.T) {
upgradeUser := "upgradeUser"
upgradeUID := "upgradeUser-UID"
testcases := map[string]struct {
APIService *apiregistration.APIService
NewEgressSelector func() (*mockEgressDialer, *egressselector.EgressSelector)
@ -534,6 +543,10 @@ func TestProxyUpgrade(t *testing.T) {
if user != upgradeUser {
t.Errorf("expected user %q, got %q", upgradeUser, user)
}
uid := req.Header.Get("X-Remote-Uid")
if uid != upgradeUID {
t.Errorf("expected UID %q, got %q", upgradeUID, uid)
}
body := make([]byte, 5)
ws.Read(body)
ws.Write([]byte("hello " + string(body)))
@ -576,7 +589,7 @@ func TestProxyUpgrade(t *testing.T) {
}
proxyHandler.updateAPIService(tc.APIService)
aggregator := httptest.NewServer(contextHandler(proxyHandler, &user.DefaultInfo{Name: upgradeUser}))
aggregator := httptest.NewServer(contextHandler(proxyHandler, &user.DefaultInfo{Name: upgradeUser, UID: upgradeUID}))
defer aggregator.Close()
ws, err := websocket.Dial("ws://"+aggregator.Listener.Addr().String()+path, "", "http://127.0.0.1/")

View File

@ -305,7 +305,7 @@ func (c *AvailableConditionController) sync(key string) error {
}
// setting the system-masters identity ensures that we will always have access rights
transport.SetAuthProxyHeaders(newReq, "system:kube-aggregator", []string{"system:masters"}, nil)
transport.SetAuthProxyHeaders(newReq, "system:kube-aggregator", "", []string{"system:masters"}, nil)
resp, err := discoveryClient.Do(newReq)
if resp != nil {
resp.Body.Close()

View File

@ -0,0 +1,173 @@
/*
Copyright 2024 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 auth
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"os"
"testing"
"time"
authnv1 "k8s.io/api/authentication/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
certutil "k8s.io/client-go/util/cert"
"k8s.io/client-go/util/keyutil"
"k8s.io/kubernetes/cmd/kube-apiserver/app/options"
"k8s.io/kubernetes/test/integration/framework"
testutils "k8s.io/kubernetes/test/utils"
"k8s.io/kubernetes/test/utils/ktesting"
)
func TestAuthnToKAS(t *testing.T) {
tCtx := ktesting.Init(t)
frontProxyCA, frontProxyClient, frontProxyKey, err := newTestCAWithClient(
pkix.Name{
CommonName: "test-front-proxy-ca",
},
pkix.Name{
CommonName: "test-aggregated-apiserver",
},
)
if err != nil {
t.Fatal(err)
}
modifyOpts := func(setUIDHeaders bool) func(opts *options.ServerRunOptions) {
return func(opts *options.ServerRunOptions) {
opts.GenericServerRunOptions.MaxRequestBodyBytes = 1024 * 1024
// rewrite the client + request header CA certs with our own content
frontProxyCAFilename := opts.Authentication.RequestHeader.ClientCAFile
if err := os.WriteFile(frontProxyCAFilename, frontProxyCA, 0644); err != nil {
t.Fatal(err)
}
opts.Authentication.RequestHeader.AllowedNames = append(opts.Authentication.RequestHeader.AllowedNames, "test-aggregated-apiserver")
if setUIDHeaders {
opts.Authentication.RequestHeader.UIDHeaders = []string{"X-Remote-Uid"}
}
}
}
for _, tt := range []struct {
name string
setUID bool
}{
{
name: "KAS without UID config",
setUID: false,
},
{
name: "KAS with UID config",
setUID: true,
},
} {
t.Run(tt.name, func(t *testing.T) {
_, kubeConfig, tearDownFn := framework.StartTestServer(tCtx, t, framework.TestServerSetup{
ModifyServerRunOptions: modifyOpts(tt.setUID),
})
defer tearDownFn()
// Test an aggregated apiserver client (signed by the new front proxy CA) is authorized
extensionApiserverClient, err := kubernetes.NewForConfig(&rest.Config{
Host: kubeConfig.Host,
TLSClientConfig: rest.TLSClientConfig{
CAData: kubeConfig.TLSClientConfig.CAData,
CAFile: kubeConfig.TLSClientConfig.CAFile,
ServerName: kubeConfig.TLSClientConfig.ServerName,
KeyData: frontProxyKey,
CertData: frontProxyClient,
},
})
if err != nil {
t.Fatal(err)
}
selfInfo := &authnv1.SelfSubjectReview{}
err = extensionApiserverClient.AuthenticationV1().RESTClient().
Post().
Resource("selfsubjectreviews").
VersionedParams(&metav1.CreateOptions{}, scheme.ParameterCodec).
Body(&authnv1.SelfSubjectReview{}).
SetHeader("X-Remote-Uid", "test-uid").
SetHeader("X-Remote-User", "testuser").
SetHeader("X-Remote-Groups", "group1", "group2").
Do(tCtx).
Into(selfInfo)
if err != nil {
t.Fatalf("failed to retrieve self-info: %v", err)
}
if selfUID := selfInfo.Status.UserInfo.UID; (len(selfUID) != 0) != tt.setUID {
t.Errorf("UID should be set: %v, but we got %v", tt.setUID, selfUID)
}
})
}
}
func newTestCAWithClient(caSubject pkix.Name, clientSubject pkix.Name) (caPEMBytes, clientCertPEMBytes, clientKeyPEMBytes []byte, err error) {
caPrivateKey, err := rsa.GenerateKey(rand.Reader, 4096)
if err != nil {
return nil, nil, nil, err
}
newCA, err := certutil.NewSelfSignedCACert(certutil.Config{
CommonName: caSubject.CommonName,
Organization: caSubject.Organization,
NotBefore: time.Now().Add(-time.Minute),
}, caPrivateKey)
if err != nil {
return nil, nil, nil, err
}
clientCertPrivateKey, err := rsa.GenerateKey(rand.Reader, 4096)
if err != nil {
return nil, nil, nil, err
}
clientCertPrivateKeyPEM, err := keyutil.MarshalPrivateKeyToPEM(clientCertPrivateKey)
if err != nil {
return nil, nil, nil, err
}
clientCert, err := testutils.NewSignedCert(&certutil.Config{
CommonName: clientSubject.CommonName,
Organization: clientSubject.Organization,
NotBefore: time.Now().Add(-time.Minute),
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
}, clientCertPrivateKey, newCA, caPrivateKey)
if err != nil {
return nil, nil, nil, err
}
caPEMBytes = testutils.EncodeCertPEM(newCA)
clientCertPEMBytes = testutils.EncodeCertPEM(clientCert)
return caPEMBytes,
clientCertPEMBytes,
clientCertPrivateKeyPEM,
nil
}

View File

@ -29,16 +29,22 @@ import (
"reflect"
"sort"
"strings"
"sync/atomic"
"testing"
"time"
"github.com/stretchr/testify/assert"
v1 "k8s.io/api/authentication/v1"
corev1 "k8s.io/api/core/v1"
apiequality "k8s.io/apimachinery/pkg/api/equality"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/version"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apiserver/pkg/authentication/serviceaccount"
"k8s.io/apiserver/pkg/authentication/user"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/server/dynamiccertificates"
genericapiserveroptions "k8s.io/apiserver/pkg/server/options"
utilversion "k8s.io/apiserver/pkg/util/version"
@ -46,6 +52,7 @@ import (
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
"k8s.io/client-go/transport"
"k8s.io/client-go/util/cert"
"k8s.io/component-base/featuregate"
apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
@ -248,6 +255,143 @@ func TestAggregatedAPIServer(t *testing.T) {
})
}
func TestFrontProxyConfig(t *testing.T) {
t.Run("WithoutUID", func(t *testing.T) {
testFrontProxyConfig(t, false)
})
t.Run("WithUID", func(t *testing.T) {
testFrontProxyConfig(t, true)
})
}
// TestFrontProxyConfig tests that the RequestHeader configuration is consumed
// correctly by the aggregated API servers.
func testFrontProxyConfig(t *testing.T, withUID bool) {
const testNamespace = "integration-test-front-proxy-config"
const wardleBinaryVersion = "1.1"
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
t.Cleanup(cancel)
var extraKASFlags []string
if withUID {
extraKASFlags = []string{"--requestheader-uid-headers=x-remote-uid"}
}
// each wardle binary is bundled with a specific kube binary.
kubeBinaryVersion := sampleserver.WardleVersionToKubeVersion(version.MustParse(wardleBinaryVersion)).String()
// start up the KAS and prepare the options for the wardle API server
testKAS, wardleOptions, wardlePort := prepareAggregatedWardleAPIServer(ctx, t, testNamespace, kubeBinaryVersion, wardleBinaryVersion, extraKASFlags)
kubeConfig := getKubeConfig(testKAS)
// create the SA that we will use to query the aggregated API
kubeClient := client.NewForConfigOrDie(kubeConfig)
expectedSA, err := kubeClient.CoreV1().ServiceAccounts(testNamespace).Create(ctx, &corev1.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Name: "wardle-client-sa",
},
}, metav1.CreateOptions{})
if err != nil {
t.Fatal(err)
}
saTokenReq, err := kubeClient.CoreV1().ServiceAccounts(testNamespace).CreateToken(ctx, "wardle-client-sa", &v1.TokenRequest{}, metav1.CreateOptions{})
if err != nil {
t.Fatal(err)
}
saToken := saTokenReq.Status.Token
if len(saToken) == 0 {
t.Fatal("empty SA token in token request response")
}
saClientConfig := rest.AnonymousClientConfig(kubeConfig)
saClientConfig.BearerToken = saToken
saKubeClient := client.NewForConfigOrDie(saClientConfig)
saDetails, err := saKubeClient.AuthenticationV1().SelfSubjectReviews().Create(ctx, &v1.SelfSubjectReview{}, metav1.CreateOptions{})
if err != nil {
t.Fatalf("failed to retrieve details about the SA: %v", err)
}
saUserInfo := serviceaccount.UserInfo(expectedSA.Namespace, expectedSA.Name, string(expectedSA.UID))
expectedSAUserInfo := user.DefaultInfo{
Name: saUserInfo.GetName(),
Groups: append(saUserInfo.GetGroups(), user.AllAuthenticated),
Extra: saUserInfo.GetExtra(),
}
if withUID {
expectedSAUserInfo.UID = saUserInfo.GetUID()
}
if expectedSAUserInfo.Extra == nil {
expectedSAUserInfo.Extra = map[string][]string{}
}
expectedSAUserInfo.Extra[user.CredentialIDKey] = saDetails.Status.UserInfo.Extra[user.CredentialIDKey]
var checksProcessed atomic.Uint32
// wrap the authz round tripper to catch the request for our SA SAR to the KAS
wardleOptions.RecommendedOptions.Authorization.WithCustomRoundTripper(
// adding a round tripper wrapper to test default RequestHeader configuration
transport.WrapperFunc(func(rt http.RoundTripper) http.RoundTripper {
return roundTripperFunc(func(req *http.Request) (*http.Response, error) {
gotUser, ok := genericapirequest.UserFrom(req.Context())
if !ok || gotUser.GetName() == "system:anonymous" {
return nil, fmt.Errorf("got an unauthenticated request")
}
// this is likely the KAS checking the OpenAPI endpoints
if gotUser.GetName() == "system:anonymous" || gotUser.GetName() == "system:aggregator" {
return rt.RoundTrip(req)
}
if got, expected := gotUser.GetUID(), expectedSAUserInfo.GetUID(); expected != got {
t.Errorf("expected UID: %q, got: %q", expected, got)
}
if got, expected := gotUser.GetName(), expectedSAUserInfo.GetName(); expected != got {
t.Errorf("expected name: %q, got: %q", expected, got)
}
if got, expected := gotUser.GetGroups(), expectedSAUserInfo.GetGroups(); !reflect.DeepEqual(expected, got) {
t.Errorf("expected groups: %v, got: %v", expected, got)
}
if got, expected := gotUser.GetExtra(), expectedSAUserInfo.GetExtra(); !apiequality.Semantic.DeepEqual(expected, got) {
t.Errorf("expected extra to be %v, but got %v", expected, got)
}
checksProcessed.Add(1)
return rt.RoundTrip(req)
})
}),
)
wardleCertDir, _ := os.MkdirTemp("", "test-integration-wardle-server")
defer os.RemoveAll(wardleCertDir)
runPreparedWardleServer(ctx, t, wardleOptions, wardleCertDir, wardlePort, false, true, wardleBinaryVersion, kubeConfig)
waitForWardleAPIServiceReady(ctx, t, kubeConfig, wardleCertDir, testNamespace)
// get the wardle API client using our SA token
wardleClientConfig := rest.AnonymousClientConfig(kubeConfig)
wardleClientConfig.BearerToken = saToken
wardleClient := wardlev1alpha1client.NewForConfigOrDie(wardleClientConfig)
_, err = wardleClient.Flunders(metav1.NamespaceSystem).List(ctx, metav1.ListOptions{})
if err != nil {
t.Fatal(err)
}
if checksProcessed.Load() != 1 {
t.Errorf("the request is in fact not being tested")
}
}
type roundTripperFunc func(*http.Request) (*http.Response, error)
func (f roundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error) {
return f(req)
}
func testAggregatedAPIServer(t *testing.T, setWardleFeatureGate, banFlunder bool, wardleBinaryVersion, wardleEmulationVersion string) {
const testNamespace = "kube-wardle"
@ -257,7 +401,7 @@ func testAggregatedAPIServer(t *testing.T, setWardleFeatureGate, banFlunder bool
// each wardle binary is bundled with a specific kube binary.
kubeBinaryVersion := sampleserver.WardleVersionToKubeVersion(version.MustParse(wardleBinaryVersion)).String()
testKAS, wardleOptions, wardlePort := prepareAggregatedWardleAPIServer(ctx, t, testNamespace, kubeBinaryVersion, wardleBinaryVersion)
testKAS, wardleOptions, wardlePort := prepareAggregatedWardleAPIServer(ctx, t, testNamespace, kubeBinaryVersion, wardleBinaryVersion, nil)
kubeClientConfig := getKubeConfig(testKAS)
wardleCertDir, _ := os.MkdirTemp("", "test-integration-wardle-server")
@ -537,7 +681,7 @@ func TestAggregatedAPIServerRejectRedirectResponse(t *testing.T) {
}
}
func prepareAggregatedWardleAPIServer(ctx context.Context, t *testing.T, namespace, kubebinaryVersion, wardleBinaryVersion string) (*kastesting.TestServer, *sampleserver.WardleServerOptions, int) {
func prepareAggregatedWardleAPIServer(ctx context.Context, t *testing.T, namespace, kubebinaryVersion, wardleBinaryVersion string, kubeAPIServerFlags []string) (*kastesting.TestServer, *sampleserver.WardleServerOptions, int) {
// makes the kube-apiserver very responsive. it's normally a minute
dynamiccertificates.FileRefreshDuration = 1 * time.Second
@ -549,7 +693,13 @@ func prepareAggregatedWardleAPIServer(ctx context.Context, t *testing.T, namespa
// endpoints cannot have loopback IPs so we need to override the resolver itself
t.Cleanup(app.SetServiceResolverForTests(staticURLServiceResolver(fmt.Sprintf("https://127.0.0.1:%d", wardlePort))))
testServer := kastesting.StartTestServerOrDie(t, &kastesting.TestServerInstanceOptions{EnableCertAuth: true, BinaryVersion: kubebinaryVersion}, nil, framework.SharedEtcd())
testServer := kastesting.StartTestServerOrDie(t,
&kastesting.TestServerInstanceOptions{
EnableCertAuth: true,
BinaryVersion: kubebinaryVersion,
},
kubeAPIServerFlags,
framework.SharedEtcd())
t.Cleanup(func() { testServer.TearDownFn() })
_, _ = utilversion.DefaultComponentGlobalsRegistry.ComponentGlobalsOrRegister(