mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-08 11:38:15 +00:00
Merge pull request #115834 from stlaz/remote-uid
RequestHeader authentication: add UID to recognized request headers
This commit is contained in:
commit
9f01cd7b28
@ -214,6 +214,7 @@ func StartTestServer(t ktesting.TB, instanceOptions *TestServerInstanceOptions,
|
|||||||
}
|
}
|
||||||
s.SecureServing.ServerCert.CertDirectory = result.TmpDir
|
s.SecureServing.ServerCert.CertDirectory = result.TmpDir
|
||||||
|
|
||||||
|
reqHeaderFromFlags := s.Authentication.RequestHeader
|
||||||
if instanceOptions.EnableCertAuth {
|
if instanceOptions.EnableCertAuth {
|
||||||
// set up default headers for request header auth
|
// set up default headers for request header auth
|
||||||
reqHeaders := serveroptions.NewDelegatingAuthenticationOptions()
|
reqHeaders := serveroptions.NewDelegatingAuthenticationOptions()
|
||||||
@ -347,6 +348,23 @@ func StartTestServer(t ktesting.TB, instanceOptions *TestServerInstanceOptions,
|
|||||||
return result, err
|
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 {
|
if err := utilversion.DefaultComponentGlobalsRegistry.Set(); err != nil {
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
@ -430,6 +430,7 @@ func TestAddFlags(t *testing.T) {
|
|||||||
ClientCert: apiserveroptions.ClientCertAuthenticationOptions{},
|
ClientCert: apiserveroptions.ClientCertAuthenticationOptions{},
|
||||||
RequestHeader: apiserveroptions.RequestHeaderAuthenticationOptions{
|
RequestHeader: apiserveroptions.RequestHeaderAuthenticationOptions{
|
||||||
UsernameHeaders: []string{"x-remote-user"},
|
UsernameHeaders: []string{"x-remote-user"},
|
||||||
|
UIDHeaders: nil,
|
||||||
GroupHeaders: []string{"x-remote-group"},
|
GroupHeaders: []string{"x-remote-group"},
|
||||||
ExtraHeaderPrefixes: []string{"x-remote-extra-"},
|
ExtraHeaderPrefixes: []string{"x-remote-extra-"},
|
||||||
},
|
},
|
||||||
|
@ -337,6 +337,7 @@ func CreateConfig(
|
|||||||
config.ClusterAuthenticationInfo.RequestHeaderExtraHeaderPrefixes = requestHeaderConfig.ExtraHeaderPrefixes
|
config.ClusterAuthenticationInfo.RequestHeaderExtraHeaderPrefixes = requestHeaderConfig.ExtraHeaderPrefixes
|
||||||
config.ClusterAuthenticationInfo.RequestHeaderGroupHeaders = requestHeaderConfig.GroupHeaders
|
config.ClusterAuthenticationInfo.RequestHeaderGroupHeaders = requestHeaderConfig.GroupHeaders
|
||||||
config.ClusterAuthenticationInfo.RequestHeaderUsernameHeaders = requestHeaderConfig.UsernameHeaders
|
config.ClusterAuthenticationInfo.RequestHeaderUsernameHeaders = requestHeaderConfig.UsernameHeaders
|
||||||
|
config.ClusterAuthenticationInfo.RequestHeaderUIDHeaders = requestHeaderConfig.UIDHeaders
|
||||||
}
|
}
|
||||||
|
|
||||||
// setup admission
|
// setup admission
|
||||||
|
@ -77,6 +77,8 @@ type ClusterAuthenticationInfo struct {
|
|||||||
|
|
||||||
// RequestHeaderUsernameHeaders are the headers used by this kube-apiserver to determine username
|
// RequestHeaderUsernameHeaders are the headers used by this kube-apiserver to determine username
|
||||||
RequestHeaderUsernameHeaders headerrequest.StringSliceProvider
|
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 are the headers used by this kube-apiserver to determine groups
|
||||||
RequestHeaderGroupHeaders headerrequest.StringSliceProvider
|
RequestHeaderGroupHeaders headerrequest.StringSliceProvider
|
||||||
// RequestHeaderExtraHeaderPrefixes are the headers used by this kube-apiserver to determine user.extra
|
// 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),
|
RequestHeaderExtraHeaderPrefixes: combineUniqueStringSlices(lhs.RequestHeaderExtraHeaderPrefixes, rhs.RequestHeaderExtraHeaderPrefixes),
|
||||||
RequestHeaderGroupHeaders: combineUniqueStringSlices(lhs.RequestHeaderGroupHeaders, rhs.RequestHeaderGroupHeaders),
|
RequestHeaderGroupHeaders: combineUniqueStringSlices(lhs.RequestHeaderGroupHeaders, rhs.RequestHeaderGroupHeaders),
|
||||||
RequestHeaderUsernameHeaders: combineUniqueStringSlices(lhs.RequestHeaderUsernameHeaders, rhs.RequestHeaderUsernameHeaders),
|
RequestHeaderUsernameHeaders: combineUniqueStringSlices(lhs.RequestHeaderUsernameHeaders, rhs.RequestHeaderUsernameHeaders),
|
||||||
|
RequestHeaderUIDHeaders: combineUniqueStringSlices(lhs.RequestHeaderUIDHeaders, rhs.RequestHeaderUIDHeaders),
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
@ -259,6 +262,10 @@ func getConfigMapDataFor(authenticationInfo ClusterAuthenticationInfo) (map[stri
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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())
|
data["requestheader-group-headers"], err = jsonSerializeStringSlice(authenticationInfo.RequestHeaderGroupHeaders.Value())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -298,6 +305,10 @@ func getClusterAuthenticationInfoFor(data map[string]string) (ClusterAuthenticat
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return ClusterAuthenticationInfo{}, err
|
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 {
|
if caBundle := data["requestheader-client-ca-file"]; len(caBundle) > 0 {
|
||||||
ret.RequestHeaderCA, err = dynamiccertificates.NewStaticCAContent("existing", []byte(caBundle))
|
ret.RequestHeaderCA, err = dynamiccertificates.NewStaticCAContent("existing", []byte(caBundle))
|
||||||
|
@ -101,6 +101,7 @@ func TestWriteClientCAs(t *testing.T) {
|
|||||||
clusterAuthInfo: ClusterAuthenticationInfo{
|
clusterAuthInfo: ClusterAuthenticationInfo{
|
||||||
ClientCA: someRandomCAProvider,
|
ClientCA: someRandomCAProvider,
|
||||||
RequestHeaderUsernameHeaders: headerrequest.StaticStringSlice{"alfa", "bravo", "charlie"},
|
RequestHeaderUsernameHeaders: headerrequest.StaticStringSlice{"alfa", "bravo", "charlie"},
|
||||||
|
RequestHeaderUIDHeaders: headerrequest.StaticStringSlice{"golf", "hotel", "india"},
|
||||||
RequestHeaderGroupHeaders: headerrequest.StaticStringSlice{"delta"},
|
RequestHeaderGroupHeaders: headerrequest.StaticStringSlice{"delta"},
|
||||||
RequestHeaderExtraHeaderPrefixes: headerrequest.StaticStringSlice{"echo", "foxtrot"},
|
RequestHeaderExtraHeaderPrefixes: headerrequest.StaticStringSlice{"echo", "foxtrot"},
|
||||||
RequestHeaderCA: anotherRandomCAProvider,
|
RequestHeaderCA: anotherRandomCAProvider,
|
||||||
@ -112,6 +113,7 @@ func TestWriteClientCAs(t *testing.T) {
|
|||||||
Data: map[string]string{
|
Data: map[string]string{
|
||||||
"client-ca-file": string(someRandomCA),
|
"client-ca-file": string(someRandomCA),
|
||||||
"requestheader-username-headers": `["alfa","bravo","charlie"]`,
|
"requestheader-username-headers": `["alfa","bravo","charlie"]`,
|
||||||
|
"requestheader-uid-headers": `["golf","hotel","india"]`,
|
||||||
"requestheader-group-headers": `["delta"]`,
|
"requestheader-group-headers": `["delta"]`,
|
||||||
"requestheader-extra-headers-prefix": `["echo","foxtrot"]`,
|
"requestheader-extra-headers-prefix": `["echo","foxtrot"]`,
|
||||||
"requestheader-client-ca-file": string(anotherRandomCA),
|
"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"},
|
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
|
||||||
Data: map[string]string{
|
Data: map[string]string{
|
||||||
"requestheader-username-headers": `[]`,
|
"requestheader-username-headers": `[]`,
|
||||||
|
"requestheader-uid-headers": `[]`,
|
||||||
"requestheader-group-headers": `[]`,
|
"requestheader-group-headers": `[]`,
|
||||||
"requestheader-extra-headers-prefix": `[]`,
|
"requestheader-extra-headers-prefix": `[]`,
|
||||||
"requestheader-client-ca-file": string(anotherRandomCA),
|
"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"},
|
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
|
||||||
Data: map[string]string{
|
Data: map[string]string{
|
||||||
"requestheader-username-headers": `[]`,
|
"requestheader-username-headers": `[]`,
|
||||||
|
"requestheader-uid-headers": `[]`,
|
||||||
"requestheader-group-headers": `[]`,
|
"requestheader-group-headers": `[]`,
|
||||||
"requestheader-extra-headers-prefix": `[]`,
|
"requestheader-extra-headers-prefix": `[]`,
|
||||||
"requestheader-client-ca-file": string(anotherRandomCA),
|
"requestheader-client-ca-file": string(anotherRandomCA),
|
||||||
@ -201,6 +205,7 @@ func TestWriteClientCAs(t *testing.T) {
|
|||||||
name: "overwrite extension-apiserver-authentication requestheader",
|
name: "overwrite extension-apiserver-authentication requestheader",
|
||||||
clusterAuthInfo: ClusterAuthenticationInfo{
|
clusterAuthInfo: ClusterAuthenticationInfo{
|
||||||
RequestHeaderUsernameHeaders: headerrequest.StaticStringSlice{},
|
RequestHeaderUsernameHeaders: headerrequest.StaticStringSlice{},
|
||||||
|
RequestHeaderUIDHeaders: headerrequest.StaticStringSlice{},
|
||||||
RequestHeaderGroupHeaders: headerrequest.StaticStringSlice{},
|
RequestHeaderGroupHeaders: headerrequest.StaticStringSlice{},
|
||||||
RequestHeaderExtraHeaderPrefixes: headerrequest.StaticStringSlice{},
|
RequestHeaderExtraHeaderPrefixes: headerrequest.StaticStringSlice{},
|
||||||
RequestHeaderCA: anotherRandomCAProvider,
|
RequestHeaderCA: anotherRandomCAProvider,
|
||||||
@ -211,6 +216,7 @@ func TestWriteClientCAs(t *testing.T) {
|
|||||||
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
|
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
|
||||||
Data: map[string]string{
|
Data: map[string]string{
|
||||||
"requestheader-username-headers": `[]`,
|
"requestheader-username-headers": `[]`,
|
||||||
|
"requestheader-uid-headers": `[]`,
|
||||||
"requestheader-group-headers": `[]`,
|
"requestheader-group-headers": `[]`,
|
||||||
"requestheader-extra-headers-prefix": `[]`,
|
"requestheader-extra-headers-prefix": `[]`,
|
||||||
"requestheader-client-ca-file": string(someRandomCA),
|
"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"},
|
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
|
||||||
Data: map[string]string{
|
Data: map[string]string{
|
||||||
"requestheader-username-headers": `[]`,
|
"requestheader-username-headers": `[]`,
|
||||||
|
"requestheader-uid-headers": `[]`,
|
||||||
"requestheader-group-headers": `[]`,
|
"requestheader-group-headers": `[]`,
|
||||||
"requestheader-extra-headers-prefix": `[]`,
|
"requestheader-extra-headers-prefix": `[]`,
|
||||||
"requestheader-client-ca-file": string(someRandomCA) + string(anotherRandomCA),
|
"requestheader-client-ca-file": string(someRandomCA) + string(anotherRandomCA),
|
||||||
@ -253,6 +260,7 @@ func TestWriteClientCAs(t *testing.T) {
|
|||||||
name: "skip on no change",
|
name: "skip on no change",
|
||||||
clusterAuthInfo: ClusterAuthenticationInfo{
|
clusterAuthInfo: ClusterAuthenticationInfo{
|
||||||
RequestHeaderUsernameHeaders: headerrequest.StaticStringSlice{},
|
RequestHeaderUsernameHeaders: headerrequest.StaticStringSlice{},
|
||||||
|
RequestHeaderUIDHeaders: headerrequest.StaticStringSlice{},
|
||||||
RequestHeaderGroupHeaders: headerrequest.StaticStringSlice{},
|
RequestHeaderGroupHeaders: headerrequest.StaticStringSlice{},
|
||||||
RequestHeaderExtraHeaderPrefixes: headerrequest.StaticStringSlice{},
|
RequestHeaderExtraHeaderPrefixes: headerrequest.StaticStringSlice{},
|
||||||
RequestHeaderCA: anotherRandomCAProvider,
|
RequestHeaderCA: anotherRandomCAProvider,
|
||||||
@ -263,6 +271,7 @@ func TestWriteClientCAs(t *testing.T) {
|
|||||||
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
|
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
|
||||||
Data: map[string]string{
|
Data: map[string]string{
|
||||||
"requestheader-username-headers": `[]`,
|
"requestheader-username-headers": `[]`,
|
||||||
|
"requestheader-uid-headers": `[]`,
|
||||||
"requestheader-group-headers": `[]`,
|
"requestheader-group-headers": `[]`,
|
||||||
"requestheader-extra-headers-prefix": `[]`,
|
"requestheader-extra-headers-prefix": `[]`,
|
||||||
"requestheader-client-ca-file": string(anotherRandomCA),
|
"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"},
|
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
|
||||||
Data: map[string]string{
|
Data: map[string]string{
|
||||||
"requestheader-username-headers": `[]`,
|
"requestheader-username-headers": `[]`,
|
||||||
|
"requestheader-uid-headers": `[]`,
|
||||||
"requestheader-group-headers": `[]`,
|
"requestheader-group-headers": `[]`,
|
||||||
"requestheader-extra-headers-prefix": `[]`,
|
"requestheader-extra-headers-prefix": `[]`,
|
||||||
"requestheader-client-ca-file": string(anotherRandomCA),
|
"requestheader-client-ca-file": string(anotherRandomCA),
|
||||||
|
@ -110,6 +110,7 @@ func (config Config) New(serverLifecycle context.Context) (authenticator.Request
|
|||||||
config.RequestHeaderConfig.CAContentProvider.VerifyOptions,
|
config.RequestHeaderConfig.CAContentProvider.VerifyOptions,
|
||||||
config.RequestHeaderConfig.AllowedClientNames,
|
config.RequestHeaderConfig.AllowedClientNames,
|
||||||
config.RequestHeaderConfig.UsernameHeaders,
|
config.RequestHeaderConfig.UsernameHeaders,
|
||||||
|
config.RequestHeaderConfig.UIDHeaders,
|
||||||
config.RequestHeaderConfig.GroupHeaders,
|
config.RequestHeaderConfig.GroupHeaders,
|
||||||
config.RequestHeaderConfig.ExtraHeaderPrefixes,
|
config.RequestHeaderConfig.ExtraHeaderPrefixes,
|
||||||
)
|
)
|
||||||
|
@ -303,6 +303,7 @@ func TestToAuthenticationConfig(t *testing.T) {
|
|||||||
},
|
},
|
||||||
RequestHeader: &apiserveroptions.RequestHeaderAuthenticationOptions{
|
RequestHeader: &apiserveroptions.RequestHeaderAuthenticationOptions{
|
||||||
UsernameHeaders: []string{"x-remote-user"},
|
UsernameHeaders: []string{"x-remote-user"},
|
||||||
|
UIDHeaders: []string{"x-remote-uid"},
|
||||||
GroupHeaders: []string{"x-remote-group"},
|
GroupHeaders: []string{"x-remote-group"},
|
||||||
ExtraHeaderPrefixes: []string{"x-remote-extra-"},
|
ExtraHeaderPrefixes: []string{"x-remote-extra-"},
|
||||||
ClientCAFile: "testdata/root.pem",
|
ClientCAFile: "testdata/root.pem",
|
||||||
@ -352,6 +353,7 @@ func TestToAuthenticationConfig(t *testing.T) {
|
|||||||
|
|
||||||
RequestHeaderConfig: &authenticatorfactory.RequestHeaderConfig{
|
RequestHeaderConfig: &authenticatorfactory.RequestHeaderConfig{
|
||||||
UsernameHeaders: headerrequest.StaticStringSlice{"x-remote-user"},
|
UsernameHeaders: headerrequest.StaticStringSlice{"x-remote-user"},
|
||||||
|
UIDHeaders: headerrequest.StaticStringSlice{"x-remote-uid"},
|
||||||
GroupHeaders: headerrequest.StaticStringSlice{"x-remote-group"},
|
GroupHeaders: headerrequest.StaticStringSlice{"x-remote-group"},
|
||||||
ExtraHeaderPrefixes: headerrequest.StaticStringSlice{"x-remote-extra-"},
|
ExtraHeaderPrefixes: headerrequest.StaticStringSlice{"x-remote-extra-"},
|
||||||
CAContentProvider: nil, // this is nil because you can't compare functions
|
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",
|
"--client-ca-file=client-cacert",
|
||||||
"--requestheader-client-ca-file=testdata/root.pem",
|
"--requestheader-client-ca-file=testdata/root.pem",
|
||||||
"--requestheader-username-headers=x-remote-user-custom",
|
"--requestheader-username-headers=x-remote-user-custom",
|
||||||
|
"--requestheader-uid-headers=x-remote-uid-custom",
|
||||||
"--requestheader-group-headers=x-remote-group-custom",
|
"--requestheader-group-headers=x-remote-group-custom",
|
||||||
"--requestheader-allowed-names=kube-aggregator",
|
"--requestheader-allowed-names=kube-aggregator",
|
||||||
"--service-account-key-file=cert",
|
"--service-account-key-file=cert",
|
||||||
@ -430,6 +433,7 @@ func TestBuiltInAuthenticationOptionsAddFlags(t *testing.T) {
|
|||||||
RequestHeader: &apiserveroptions.RequestHeaderAuthenticationOptions{
|
RequestHeader: &apiserveroptions.RequestHeaderAuthenticationOptions{
|
||||||
ClientCAFile: "testdata/root.pem",
|
ClientCAFile: "testdata/root.pem",
|
||||||
UsernameHeaders: []string{"x-remote-user-custom"},
|
UsernameHeaders: []string{"x-remote-user-custom"},
|
||||||
|
UIDHeaders: []string{"x-remote-uid-custom"},
|
||||||
GroupHeaders: []string{"x-remote-group-custom"},
|
GroupHeaders: []string{"x-remote-group-custom"},
|
||||||
AllowedNames: []string{"kube-aggregator"},
|
AllowedNames: []string{"kube-aggregator"},
|
||||||
},
|
},
|
||||||
|
@ -77,6 +77,7 @@ func (c DelegatingAuthenticatorConfig) New() (authenticator.Request, *spec.Secur
|
|||||||
c.RequestHeaderConfig.CAContentProvider.VerifyOptions,
|
c.RequestHeaderConfig.CAContentProvider.VerifyOptions,
|
||||||
c.RequestHeaderConfig.AllowedClientNames,
|
c.RequestHeaderConfig.AllowedClientNames,
|
||||||
c.RequestHeaderConfig.UsernameHeaders,
|
c.RequestHeaderConfig.UsernameHeaders,
|
||||||
|
c.RequestHeaderConfig.UIDHeaders,
|
||||||
c.RequestHeaderConfig.GroupHeaders,
|
c.RequestHeaderConfig.GroupHeaders,
|
||||||
c.RequestHeaderConfig.ExtraHeaderPrefixes,
|
c.RequestHeaderConfig.ExtraHeaderPrefixes,
|
||||||
)
|
)
|
||||||
|
@ -24,6 +24,8 @@ import (
|
|||||||
type RequestHeaderConfig struct {
|
type RequestHeaderConfig struct {
|
||||||
// UsernameHeaders are the headers to check (in order, case-insensitively) for an identity. The first header with a value wins.
|
// UsernameHeaders are the headers to check (in order, case-insensitively) for an identity. The first header with a value wins.
|
||||||
UsernameHeaders headerrequest.StringSliceProvider
|
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 are the headers to check (case-insensitively) for a group names. All values will be used.
|
||||||
GroupHeaders headerrequest.StringSliceProvider
|
GroupHeaders headerrequest.StringSliceProvider
|
||||||
// ExtraHeaderPrefixes are the head prefixes to check (case-insentively) for filling in
|
// ExtraHeaderPrefixes are the head prefixes to check (case-insentively) for filling in
|
||||||
|
@ -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 are the headers to check (in order, case-insensitively) for an identity. The first header with a value wins.
|
||||||
nameHeaders StringSliceProvider
|
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 are the headers to check (case-insensitively) for group membership. All values of all headers will be added.
|
||||||
groupHeaders StringSliceProvider
|
groupHeaders StringSliceProvider
|
||||||
|
|
||||||
@ -61,11 +64,15 @@ type requestHeaderAuthRequestHandler struct {
|
|||||||
extraHeaderPrefixes StringSliceProvider
|
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...)
|
trimmedNameHeaders, err := trimHeaders(nameHeaders...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
trimmedUIDHeaders, err := trimHeaders(uidHeaders...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
trimmedGroupHeaders, err := trimHeaders(groupHeaders...)
|
trimmedGroupHeaders, err := trimHeaders(groupHeaders...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -77,14 +84,16 @@ func New(nameHeaders, groupHeaders, extraHeaderPrefixes []string) (authenticator
|
|||||||
|
|
||||||
return NewDynamic(
|
return NewDynamic(
|
||||||
StaticStringSlice(trimmedNameHeaders),
|
StaticStringSlice(trimmedNameHeaders),
|
||||||
|
StaticStringSlice(trimmedUIDHeaders),
|
||||||
StaticStringSlice(trimmedGroupHeaders),
|
StaticStringSlice(trimmedGroupHeaders),
|
||||||
StaticStringSlice(trimmedExtraHeaderPrefixes),
|
StaticStringSlice(trimmedExtraHeaderPrefixes),
|
||||||
), nil
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDynamic(nameHeaders, groupHeaders, extraHeaderPrefixes StringSliceProvider) authenticator.Request {
|
func NewDynamic(nameHeaders, uidHeaders, groupHeaders, extraHeaderPrefixes StringSliceProvider) authenticator.Request {
|
||||||
return &requestHeaderAuthRequestHandler{
|
return &requestHeaderAuthRequestHandler{
|
||||||
nameHeaders: nameHeaders,
|
nameHeaders: nameHeaders,
|
||||||
|
uidHeaders: uidHeaders,
|
||||||
groupHeaders: groupHeaders,
|
groupHeaders: groupHeaders,
|
||||||
extraHeaderPrefixes: extraHeaderPrefixes,
|
extraHeaderPrefixes: extraHeaderPrefixes,
|
||||||
}
|
}
|
||||||
@ -103,8 +112,8 @@ func trimHeaders(headerNames ...string) ([]string, error) {
|
|||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDynamicVerifyOptionsSecure(verifyOptionFn x509request.VerifyOptionFunc, proxyClientNames, nameHeaders, groupHeaders, extraHeaderPrefixes StringSliceProvider) authenticator.Request {
|
func NewDynamicVerifyOptionsSecure(verifyOptionFn x509request.VerifyOptionFunc, proxyClientNames, nameHeaders, uidHeaders, groupHeaders, extraHeaderPrefixes StringSliceProvider) authenticator.Request {
|
||||||
headerAuthenticator := NewDynamic(nameHeaders, groupHeaders, extraHeaderPrefixes)
|
headerAuthenticator := NewDynamic(nameHeaders, uidHeaders, groupHeaders, extraHeaderPrefixes)
|
||||||
|
|
||||||
return x509request.NewDynamicCAVerifier(verifyOptionFn, headerAuthenticator, proxyClientNames)
|
return x509request.NewDynamicCAVerifier(verifyOptionFn, headerAuthenticator, proxyClientNames)
|
||||||
}
|
}
|
||||||
@ -114,25 +123,30 @@ func (a *requestHeaderAuthRequestHandler) AuthenticateRequest(req *http.Request)
|
|||||||
if len(name) == 0 {
|
if len(name) == 0 {
|
||||||
return nil, false, nil
|
return nil, false, nil
|
||||||
}
|
}
|
||||||
|
uid := headerValue(req.Header, a.uidHeaders.Value())
|
||||||
groups := allHeaderValues(req.Header, a.groupHeaders.Value())
|
groups := allHeaderValues(req.Header, a.groupHeaders.Value())
|
||||||
extra := newExtra(req.Header, a.extraHeaderPrefixes.Value())
|
extra := newExtra(req.Header, a.extraHeaderPrefixes.Value())
|
||||||
|
|
||||||
// clear headers used for authentication
|
// 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{
|
return &authenticator.Response{
|
||||||
User: &user.DefaultInfo{
|
User: &user.DefaultInfo{
|
||||||
Name: name,
|
Name: name,
|
||||||
|
UID: uid,
|
||||||
Groups: groups,
|
Groups: groups,
|
||||||
Extra: extra,
|
Extra: extra,
|
||||||
},
|
},
|
||||||
}, true, nil
|
}, 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() {
|
for _, headerName := range nameHeaders.Value() {
|
||||||
h.Del(headerName)
|
h.Del(headerName)
|
||||||
}
|
}
|
||||||
|
for _, headerName := range uidHeaders.Value() {
|
||||||
|
h.Del(headerName)
|
||||||
|
}
|
||||||
for _, headerName := range groupHeaders.Value() {
|
for _, headerName := range groupHeaders.Value() {
|
||||||
h.Del(headerName)
|
h.Del(headerName)
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,7 @@ const (
|
|||||||
// RequestHeaderAuthRequestProvider a provider that knows how to dynamically fill parts of RequestHeaderConfig struct
|
// RequestHeaderAuthRequestProvider a provider that knows how to dynamically fill parts of RequestHeaderConfig struct
|
||||||
type RequestHeaderAuthRequestProvider interface {
|
type RequestHeaderAuthRequestProvider interface {
|
||||||
UsernameHeaders() []string
|
UsernameHeaders() []string
|
||||||
|
UIDHeaders() []string
|
||||||
GroupHeaders() []string
|
GroupHeaders() []string
|
||||||
ExtraHeaderPrefixes() []string
|
ExtraHeaderPrefixes() []string
|
||||||
AllowedClientNames() []string
|
AllowedClientNames() []string
|
||||||
@ -54,6 +55,7 @@ var _ RequestHeaderAuthRequestProvider = &RequestHeaderAuthRequestController{}
|
|||||||
|
|
||||||
type requestHeaderBundle struct {
|
type requestHeaderBundle struct {
|
||||||
UsernameHeaders []string
|
UsernameHeaders []string
|
||||||
|
UIDHeaders []string
|
||||||
GroupHeaders []string
|
GroupHeaders []string
|
||||||
ExtraHeaderPrefixes []string
|
ExtraHeaderPrefixes []string
|
||||||
AllowedClientNames []string
|
AllowedClientNames []string
|
||||||
@ -80,6 +82,7 @@ type RequestHeaderAuthRequestController struct {
|
|||||||
exportedRequestHeaderBundle atomic.Value
|
exportedRequestHeaderBundle atomic.Value
|
||||||
|
|
||||||
usernameHeadersKey string
|
usernameHeadersKey string
|
||||||
|
uidHeadersKey string
|
||||||
groupHeadersKey string
|
groupHeadersKey string
|
||||||
extraHeaderPrefixesKey string
|
extraHeaderPrefixesKey string
|
||||||
allowedClientNamesKey string
|
allowedClientNamesKey string
|
||||||
@ -90,7 +93,7 @@ func NewRequestHeaderAuthRequestController(
|
|||||||
cmName string,
|
cmName string,
|
||||||
cmNamespace string,
|
cmNamespace string,
|
||||||
client kubernetes.Interface,
|
client kubernetes.Interface,
|
||||||
usernameHeadersKey, groupHeadersKey, extraHeaderPrefixesKey, allowedClientNamesKey string) *RequestHeaderAuthRequestController {
|
usernameHeadersKey, uidHeadersKey, groupHeadersKey, extraHeaderPrefixesKey, allowedClientNamesKey string) *RequestHeaderAuthRequestController {
|
||||||
c := &RequestHeaderAuthRequestController{
|
c := &RequestHeaderAuthRequestController{
|
||||||
name: "RequestHeaderAuthRequestController",
|
name: "RequestHeaderAuthRequestController",
|
||||||
|
|
||||||
@ -100,6 +103,7 @@ func NewRequestHeaderAuthRequestController(
|
|||||||
configmapNamespace: cmNamespace,
|
configmapNamespace: cmNamespace,
|
||||||
|
|
||||||
usernameHeadersKey: usernameHeadersKey,
|
usernameHeadersKey: usernameHeadersKey,
|
||||||
|
uidHeadersKey: uidHeadersKey,
|
||||||
groupHeadersKey: groupHeadersKey,
|
groupHeadersKey: groupHeadersKey,
|
||||||
extraHeaderPrefixesKey: extraHeaderPrefixesKey,
|
extraHeaderPrefixesKey: extraHeaderPrefixesKey,
|
||||||
allowedClientNamesKey: allowedClientNamesKey,
|
allowedClientNamesKey: allowedClientNamesKey,
|
||||||
@ -152,6 +156,10 @@ func (c *RequestHeaderAuthRequestController) UsernameHeaders() []string {
|
|||||||
return c.loadRequestHeaderFor(c.usernameHeadersKey)
|
return c.loadRequestHeaderFor(c.usernameHeadersKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *RequestHeaderAuthRequestController) UIDHeaders() []string {
|
||||||
|
return c.loadRequestHeaderFor(c.uidHeadersKey)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *RequestHeaderAuthRequestController) GroupHeaders() []string {
|
func (c *RequestHeaderAuthRequestController) GroupHeaders() []string {
|
||||||
return c.loadRequestHeaderFor(c.groupHeadersKey)
|
return c.loadRequestHeaderFor(c.groupHeadersKey)
|
||||||
}
|
}
|
||||||
@ -278,6 +286,11 @@ func (c *RequestHeaderAuthRequestController) getRequestHeaderBundleFromConfigMap
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uidHeaderCurrentValue, err := deserializeStrings(cm.Data[c.uidHeadersKey])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
groupHeadersCurrentValue, err := deserializeStrings(cm.Data[c.groupHeadersKey])
|
groupHeadersCurrentValue, err := deserializeStrings(cm.Data[c.groupHeadersKey])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -296,6 +309,7 @@ func (c *RequestHeaderAuthRequestController) getRequestHeaderBundleFromConfigMap
|
|||||||
|
|
||||||
return &requestHeaderBundle{
|
return &requestHeaderBundle{
|
||||||
UsernameHeaders: usernameHeaderCurrentValue,
|
UsernameHeaders: usernameHeaderCurrentValue,
|
||||||
|
UIDHeaders: uidHeaderCurrentValue,
|
||||||
GroupHeaders: groupHeadersCurrentValue,
|
GroupHeaders: groupHeadersCurrentValue,
|
||||||
ExtraHeaderPrefixes: extraHeaderPrefixesCurrentValue,
|
ExtraHeaderPrefixes: extraHeaderPrefixesCurrentValue,
|
||||||
AllowedClientNames: allowedClientNamesCurrentValue,
|
AllowedClientNames: allowedClientNamesCurrentValue,
|
||||||
@ -312,6 +326,8 @@ func (c *RequestHeaderAuthRequestController) loadRequestHeaderFor(key string) []
|
|||||||
switch key {
|
switch key {
|
||||||
case c.usernameHeadersKey:
|
case c.usernameHeadersKey:
|
||||||
return headerBundle.UsernameHeaders
|
return headerBundle.UsernameHeaders
|
||||||
|
case c.uidHeadersKey:
|
||||||
|
return headerBundle.UIDHeaders
|
||||||
case c.groupHeadersKey:
|
case c.groupHeadersKey:
|
||||||
return headerBundle.GroupHeaders
|
return headerBundle.GroupHeaders
|
||||||
case c.extraHeaderPrefixesKey:
|
case c.extraHeaderPrefixesKey:
|
||||||
|
@ -19,10 +19,10 @@ package headerrequest
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"k8s.io/apimachinery/pkg/api/equality"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/api/equality"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/client-go/kubernetes/fake"
|
"k8s.io/client-go/kubernetes/fake"
|
||||||
corev1listers "k8s.io/client-go/listers/core/v1"
|
corev1listers "k8s.io/client-go/listers/core/v1"
|
||||||
@ -34,6 +34,7 @@ const (
|
|||||||
defConfigMapNamespace = "kube-system"
|
defConfigMapNamespace = "kube-system"
|
||||||
|
|
||||||
defUsernameHeadersKey = "user-key"
|
defUsernameHeadersKey = "user-key"
|
||||||
|
defUIDHeadersKey = "uid-key"
|
||||||
defGroupHeadersKey = "group-key"
|
defGroupHeadersKey = "group-key"
|
||||||
defExtraHeaderPrefixesKey = "extra-key"
|
defExtraHeaderPrefixesKey = "extra-key"
|
||||||
defAllowedClientNamesKey = "names-key"
|
defAllowedClientNamesKey = "names-key"
|
||||||
@ -41,6 +42,7 @@ const (
|
|||||||
|
|
||||||
type expectedHeadersHolder struct {
|
type expectedHeadersHolder struct {
|
||||||
usernameHeaders []string
|
usernameHeaders []string
|
||||||
|
uidHeaders []string
|
||||||
groupHeaders []string
|
groupHeaders []string
|
||||||
extraHeaderPrefixes []string
|
extraHeaderPrefixes []string
|
||||||
allowedClientNames []string
|
allowedClientNames []string
|
||||||
@ -55,9 +57,10 @@ func TestRequestHeaderAuthRequestController(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "happy-path: headers values are populated form a config map",
|
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{
|
expectedHeader: expectedHeadersHolder{
|
||||||
usernameHeaders: []string{"user-val"},
|
usernameHeaders: []string{"user-val"},
|
||||||
|
uidHeaders: []string{"uid-val"},
|
||||||
groupHeaders: []string{"group-val"},
|
groupHeaders: []string{"group-val"},
|
||||||
extraHeaderPrefixes: []string{"extra-val"},
|
extraHeaderPrefixes: []string{"extra-val"},
|
||||||
allowedClientNames: []string{"names-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",
|
name: "passing an empty config map doesn't break the controller",
|
||||||
cm: func() *corev1.ConfigMap {
|
cm: func() *corev1.ConfigMap {
|
||||||
c := defaultConfigMap(t, nil, nil, nil, nil)
|
c := defaultConfigMap(t, nil, nil, nil, nil, nil)
|
||||||
c.Data = map[string]string{}
|
c.Data = map[string]string{}
|
||||||
return c
|
return c
|
||||||
}(),
|
}(),
|
||||||
@ -74,7 +77,7 @@ func TestRequestHeaderAuthRequestController(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "an invalid config map produces an error",
|
name: "an invalid config map produces an error",
|
||||||
cm: func() *corev1.ConfigMap {
|
cm: func() *corev1.ConfigMap {
|
||||||
c := defaultConfigMap(t, nil, nil, nil, nil)
|
c := defaultConfigMap(t, nil, nil, nil, nil, nil)
|
||||||
c.Data = map[string]string{
|
c.Data = map[string]string{
|
||||||
defUsernameHeadersKey: "incorrect-json-array",
|
defUsernameHeadersKey: "incorrect-json-array",
|
||||||
}
|
}
|
||||||
@ -119,9 +122,10 @@ func TestRequestHeaderAuthRequestControllerPreserveState(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "scenario 1: headers values are populated form a config map",
|
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{
|
expectedHeader: expectedHeadersHolder{
|
||||||
usernameHeaders: []string{"user-val"},
|
usernameHeaders: []string{"user-val"},
|
||||||
|
uidHeaders: []string{"uid-val"},
|
||||||
groupHeaders: []string{"group-val"},
|
groupHeaders: []string{"group-val"},
|
||||||
extraHeaderPrefixes: []string{"extra-val"},
|
extraHeaderPrefixes: []string{"extra-val"},
|
||||||
allowedClientNames: []string{"names-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)",
|
name: "scenario 2: an invalid config map produces an error but doesn't destroy the state (scenario 1)",
|
||||||
cm: func() *corev1.ConfigMap {
|
cm: func() *corev1.ConfigMap {
|
||||||
c := defaultConfigMap(t, nil, nil, nil, nil)
|
c := defaultConfigMap(t, nil, nil, nil, nil, nil)
|
||||||
c.Data = map[string]string{
|
c.Data = map[string]string{
|
||||||
defUsernameHeadersKey: "incorrect-json-array",
|
defUsernameHeadersKey: "incorrect-json-array",
|
||||||
}
|
}
|
||||||
@ -139,6 +143,7 @@ func TestRequestHeaderAuthRequestControllerPreserveState(t *testing.T) {
|
|||||||
expectErr: true,
|
expectErr: true,
|
||||||
expectedHeader: expectedHeadersHolder{
|
expectedHeader: expectedHeadersHolder{
|
||||||
usernameHeaders: []string{"user-val"},
|
usernameHeaders: []string{"user-val"},
|
||||||
|
uidHeaders: []string{"uid-val"},
|
||||||
groupHeaders: []string{"group-val"},
|
groupHeaders: []string{"group-val"},
|
||||||
extraHeaderPrefixes: []string{"extra-val"},
|
extraHeaderPrefixes: []string{"extra-val"},
|
||||||
allowedClientNames: []string{"names-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)",
|
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{
|
expectedHeader: expectedHeadersHolder{
|
||||||
usernameHeaders: []string{"user-val"},
|
usernameHeaders: []string{"user-val"},
|
||||||
|
uidHeaders: []string{"uid-val"},
|
||||||
groupHeaders: []string{"group-val-scenario-3"},
|
groupHeaders: []string{"group-val-scenario-3"},
|
||||||
extraHeaderPrefixes: []string{"extra-val"},
|
extraHeaderPrefixes: []string{"extra-val"},
|
||||||
allowedClientNames: []string{"names-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)",
|
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{
|
expectedHeader: expectedHeadersHolder{
|
||||||
usernameHeaders: []string{"user-val-scenario-4"},
|
usernameHeaders: []string{"user-val-scenario-4"},
|
||||||
|
uidHeaders: []string{"uid-val-scenario-4"},
|
||||||
groupHeaders: []string{"group-val-scenario-4"},
|
groupHeaders: []string{"group-val-scenario-4"},
|
||||||
extraHeaderPrefixes: []string{"extra-val-scenario-4"},
|
extraHeaderPrefixes: []string{"extra-val-scenario-4"},
|
||||||
allowedClientNames: []string{"names-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",
|
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{
|
expectedHeader: expectedHeadersHolder{
|
||||||
usernameHeaders: []string{"user-val"},
|
usernameHeaders: []string{"user-val"},
|
||||||
|
uidHeaders: []string{"uid-val"},
|
||||||
groupHeaders: []string{"group-val"},
|
groupHeaders: []string{"group-val"},
|
||||||
extraHeaderPrefixes: []string{"extra-val"},
|
extraHeaderPrefixes: []string{"extra-val"},
|
||||||
allowedClientNames: []string{"names-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 {
|
encode := func(val []string) string {
|
||||||
encodedVal, err := json.Marshal(val)
|
encodedVal, err := json.Marshal(val)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -253,6 +261,7 @@ func defaultConfigMap(t *testing.T, usernameHeaderVal, groupHeadersVal, extraHea
|
|||||||
},
|
},
|
||||||
Data: map[string]string{
|
Data: map[string]string{
|
||||||
defUsernameHeadersKey: encode(usernameHeaderVal),
|
defUsernameHeadersKey: encode(usernameHeaderVal),
|
||||||
|
defUIDHeadersKey: encode(uidHeaderVal),
|
||||||
defGroupHeadersKey: encode(groupHeadersVal),
|
defGroupHeadersKey: encode(groupHeadersVal),
|
||||||
defExtraHeaderPrefixesKey: encode(extraHeaderPrefixesVal),
|
defExtraHeaderPrefixesKey: encode(extraHeaderPrefixesVal),
|
||||||
defAllowedClientNamesKey: encode(allowedClientNamesVal),
|
defAllowedClientNamesKey: encode(allowedClientNamesVal),
|
||||||
@ -265,6 +274,7 @@ func newDefaultTarget() *RequestHeaderAuthRequestController {
|
|||||||
configmapName: defConfigMapName,
|
configmapName: defConfigMapName,
|
||||||
configmapNamespace: defConfigMapNamespace,
|
configmapNamespace: defConfigMapNamespace,
|
||||||
usernameHeadersKey: defUsernameHeadersKey,
|
usernameHeadersKey: defUsernameHeadersKey,
|
||||||
|
uidHeadersKey: defUIDHeadersKey,
|
||||||
groupHeadersKey: defGroupHeadersKey,
|
groupHeadersKey: defGroupHeadersKey,
|
||||||
extraHeaderPrefixesKey: defExtraHeaderPrefixesKey,
|
extraHeaderPrefixesKey: defExtraHeaderPrefixesKey,
|
||||||
allowedClientNamesKey: defAllowedClientNamesKey,
|
allowedClientNamesKey: defAllowedClientNamesKey,
|
||||||
|
@ -29,6 +29,7 @@ import (
|
|||||||
func TestRequestHeader(t *testing.T) {
|
func TestRequestHeader(t *testing.T) {
|
||||||
testcases := map[string]struct {
|
testcases := map[string]struct {
|
||||||
nameHeaders []string
|
nameHeaders []string
|
||||||
|
uidHeaders []string
|
||||||
groupHeaders []string
|
groupHeaders []string
|
||||||
extraPrefixHeaders []string
|
extraPrefixHeaders []string
|
||||||
requestHeaders http.Header
|
requestHeaders http.Header
|
||||||
@ -128,13 +129,66 @@ func TestRequestHeader(t *testing.T) {
|
|||||||
},
|
},
|
||||||
expectedOk: true,
|
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": {
|
"extra prefix matches case-insensitive": {
|
||||||
nameHeaders: []string{"X-Remote-User"},
|
nameHeaders: []string{"X-Remote-User"},
|
||||||
|
uidHeaders: []string{"X-Remote-UID"},
|
||||||
groupHeaders: []string{"X-Remote-Group-1", "X-Remote-Group-2"},
|
groupHeaders: []string{"X-Remote-Group-1", "X-Remote-Group-2"},
|
||||||
extraPrefixHeaders: []string{"X-Remote-Extra-1-", "X-Remote-Extra-2-"},
|
extraPrefixHeaders: []string{"X-Remote-Extra-1-", "X-Remote-Extra-2-"},
|
||||||
requestHeaders: http.Header{
|
requestHeaders: http.Header{
|
||||||
"X-Remote-User": {"Bob"},
|
"X-Remote-User": {"Bob"},
|
||||||
|
"X-Remote-Uid": {"2ca80fb0-60ea-4ecf-951c-89af843b0402"},
|
||||||
"X-Remote-Group-1": {"one-a", "one-b"},
|
"X-Remote-Group-1": {"one-a", "one-b"},
|
||||||
"X-Remote-Group-2": {"two-a", "two-b"},
|
"X-Remote-Group-2": {"two-a", "two-b"},
|
||||||
"X-Remote-extra-1-key1": {"alfa", "bravo"},
|
"X-Remote-extra-1-key1": {"alfa", "bravo"},
|
||||||
@ -146,6 +200,7 @@ func TestRequestHeader(t *testing.T) {
|
|||||||
},
|
},
|
||||||
expectedUser: &user.DefaultInfo{
|
expectedUser: &user.DefaultInfo{
|
||||||
Name: "Bob",
|
Name: "Bob",
|
||||||
|
UID: "2ca80fb0-60ea-4ecf-951c-89af843b0402",
|
||||||
Groups: []string{"one-a", "one-b", "two-a", "two-b"},
|
Groups: []string{"one-a", "one-b", "two-a", "two-b"},
|
||||||
Extra: map[string][]string{
|
Extra: map[string][]string{
|
||||||
"key1": {"alfa", "bravo", "echo", "foxtrot"},
|
"key1": {"alfa", "bravo", "echo", "foxtrot"},
|
||||||
@ -191,10 +246,12 @@ func TestRequestHeader(t *testing.T) {
|
|||||||
|
|
||||||
"escaped extra keys": {
|
"escaped extra keys": {
|
||||||
nameHeaders: []string{"X-Remote-User"},
|
nameHeaders: []string{"X-Remote-User"},
|
||||||
|
uidHeaders: []string{"X-Remote-Uid"},
|
||||||
groupHeaders: []string{"X-Remote-Group"},
|
groupHeaders: []string{"X-Remote-Group"},
|
||||||
extraPrefixHeaders: []string{"X-Remote-Extra-"},
|
extraPrefixHeaders: []string{"X-Remote-Extra-"},
|
||||||
requestHeaders: http.Header{
|
requestHeaders: http.Header{
|
||||||
"X-Remote-User": {"Bob"},
|
"X-Remote-User": {"Bob"},
|
||||||
|
"X-Remote-Uid": {"2ca80fb0-60ea-4ecf-951c-89af843b0402"},
|
||||||
"X-Remote-Group": {"one-a", "one-b"},
|
"X-Remote-Group": {"one-a", "one-b"},
|
||||||
"X-Remote-Extra-Alpha": {"alphabetical"},
|
"X-Remote-Extra-Alpha": {"alphabetical"},
|
||||||
"X-Remote-Extra-Alph4num3r1c": {"alphanumeric"},
|
"X-Remote-Extra-Alph4num3r1c": {"alphanumeric"},
|
||||||
@ -206,6 +263,7 @@ func TestRequestHeader(t *testing.T) {
|
|||||||
},
|
},
|
||||||
expectedUser: &user.DefaultInfo{
|
expectedUser: &user.DefaultInfo{
|
||||||
Name: "Bob",
|
Name: "Bob",
|
||||||
|
UID: "2ca80fb0-60ea-4ecf-951c-89af843b0402",
|
||||||
Groups: []string{"one-a", "one-b"},
|
Groups: []string{"one-a", "one-b"},
|
||||||
Extra: map[string][]string{
|
Extra: map[string][]string{
|
||||||
"alpha": {"alphabetical"},
|
"alpha": {"alphabetical"},
|
||||||
@ -223,7 +281,7 @@ func TestRequestHeader(t *testing.T) {
|
|||||||
|
|
||||||
for k, testcase := range testcases {
|
for k, testcase := range testcases {
|
||||||
t.Run(k, func(t *testing.T) {
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -54,6 +54,7 @@ func withAuthentication(handler http.Handler, auth authenticator.Request, failed
|
|||||||
}
|
}
|
||||||
standardRequestHeaderConfig := &authenticatorfactory.RequestHeaderConfig{
|
standardRequestHeaderConfig := &authenticatorfactory.RequestHeaderConfig{
|
||||||
UsernameHeaders: headerrequest.StaticStringSlice{"X-Remote-User"},
|
UsernameHeaders: headerrequest.StaticStringSlice{"X-Remote-User"},
|
||||||
|
UIDHeaders: headerrequest.StaticStringSlice{"X-Remote-Uid"},
|
||||||
GroupHeaders: headerrequest.StaticStringSlice{"X-Remote-Group"},
|
GroupHeaders: headerrequest.StaticStringSlice{"X-Remote-Group"},
|
||||||
ExtraHeaderPrefixes: headerrequest.StaticStringSlice{"X-Remote-Extra-"},
|
ExtraHeaderPrefixes: headerrequest.StaticStringSlice{"X-Remote-Extra-"},
|
||||||
}
|
}
|
||||||
@ -90,6 +91,7 @@ func withAuthentication(handler http.Handler, auth authenticator.Request, failed
|
|||||||
headerrequest.ClearAuthenticationHeaders(
|
headerrequest.ClearAuthenticationHeaders(
|
||||||
req.Header,
|
req.Header,
|
||||||
standardRequestHeaderConfig.UsernameHeaders,
|
standardRequestHeaderConfig.UsernameHeaders,
|
||||||
|
standardRequestHeaderConfig.UIDHeaders,
|
||||||
standardRequestHeaderConfig.GroupHeaders,
|
standardRequestHeaderConfig.GroupHeaders,
|
||||||
standardRequestHeaderConfig.ExtraHeaderPrefixes,
|
standardRequestHeaderConfig.ExtraHeaderPrefixes,
|
||||||
)
|
)
|
||||||
@ -99,6 +101,7 @@ func withAuthentication(handler http.Handler, auth authenticator.Request, failed
|
|||||||
headerrequest.ClearAuthenticationHeaders(
|
headerrequest.ClearAuthenticationHeaders(
|
||||||
req.Header,
|
req.Header,
|
||||||
requestHeaderConfig.UsernameHeaders,
|
requestHeaderConfig.UsernameHeaders,
|
||||||
|
requestHeaderConfig.UIDHeaders,
|
||||||
requestHeaderConfig.GroupHeaders,
|
requestHeaderConfig.GroupHeaders,
|
||||||
requestHeaderConfig.ExtraHeaderPrefixes,
|
requestHeaderConfig.ExtraHeaderPrefixes,
|
||||||
)
|
)
|
||||||
|
@ -285,6 +285,7 @@ func TestAuthenticateRequestError(t *testing.T) {
|
|||||||
func TestAuthenticateRequestClearHeaders(t *testing.T) {
|
func TestAuthenticateRequestClearHeaders(t *testing.T) {
|
||||||
testcases := map[string]struct {
|
testcases := map[string]struct {
|
||||||
nameHeaders []string
|
nameHeaders []string
|
||||||
|
uidHeaders []string
|
||||||
groupHeaders []string
|
groupHeaders []string
|
||||||
extraPrefixHeaders []string
|
extraPrefixHeaders []string
|
||||||
requestHeaders http.Header
|
requestHeaders http.Header
|
||||||
@ -334,13 +335,39 @@ func TestAuthenticateRequestClearHeaders(t *testing.T) {
|
|||||||
"X-Remote-Group": {"Users"},
|
"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": {
|
"extra prefix matches case-insensitive": {
|
||||||
nameHeaders: []string{"X-Remote-User"},
|
nameHeaders: []string{"X-Remote-User"},
|
||||||
|
uidHeaders: []string{"X-Remote-Uid-1"},
|
||||||
groupHeaders: []string{"X-Remote-Group-1", "X-Remote-Group-2"},
|
groupHeaders: []string{"X-Remote-Group-1", "X-Remote-Group-2"},
|
||||||
extraPrefixHeaders: []string{"X-Remote-Extra-1-", "X-Remote-Extra-2-"},
|
extraPrefixHeaders: []string{"X-Remote-Extra-1-", "X-Remote-Extra-2-"},
|
||||||
requestHeaders: http.Header{
|
requestHeaders: http.Header{
|
||||||
"X-Remote-User": {"Bob"},
|
"X-Remote-User": {"Bob"},
|
||||||
|
"X-Remote-Uid-1": {"bobs-uid"},
|
||||||
"X-Remote-Group-1": {"one-a", "one-b"},
|
"X-Remote-Group-1": {"one-a", "one-b"},
|
||||||
"X-Remote-Group-2": {"two-a", "two-b"},
|
"X-Remote-Group-2": {"two-a", "two-b"},
|
||||||
"X-Remote-extra-1-key1": {"alfa", "bravo"},
|
"X-Remote-extra-1-key1": {"alfa", "bravo"},
|
||||||
@ -354,12 +381,15 @@ func TestAuthenticateRequestClearHeaders(t *testing.T) {
|
|||||||
|
|
||||||
"extra prefix matches case-insensitive with unrelated headers": {
|
"extra prefix matches case-insensitive with unrelated headers": {
|
||||||
nameHeaders: []string{"X-Remote-User"},
|
nameHeaders: []string{"X-Remote-User"},
|
||||||
|
uidHeaders: []string{"X-Remote-Uid"},
|
||||||
groupHeaders: []string{"X-Remote-Group-1", "X-Remote-Group-2"},
|
groupHeaders: []string{"X-Remote-Group-1", "X-Remote-Group-2"},
|
||||||
extraPrefixHeaders: []string{"X-Remote-Extra-1-", "X-Remote-Extra-2-"},
|
extraPrefixHeaders: []string{"X-Remote-Extra-1-", "X-Remote-Extra-2-"},
|
||||||
requestHeaders: http.Header{
|
requestHeaders: http.Header{
|
||||||
"X-Group-Remote": {"snorlax"}, // unrelated header
|
"X-Group-Remote": {"snorlax"}, // unrelated header
|
||||||
"X-Group-Bear": {"panda"}, // another unrelated header
|
"X-Group-Bear": {"panda"}, // another unrelated header
|
||||||
|
"X-Uid-Remote": {"bobs-unrelated-uid"},
|
||||||
"X-Remote-User": {"Bob"},
|
"X-Remote-User": {"Bob"},
|
||||||
|
"X-Remote-Uid": {"bobs-uid"},
|
||||||
"X-Remote-Group-1": {"one-a", "one-b"},
|
"X-Remote-Group-1": {"one-a", "one-b"},
|
||||||
"X-Remote-Group-2": {"two-a", "two-b"},
|
"X-Remote-Group-2": {"two-a", "two-b"},
|
||||||
"X-Remote-extra-1-key1": {"alfa", "bravo"},
|
"X-Remote-extra-1-key1": {"alfa", "bravo"},
|
||||||
@ -372,15 +402,18 @@ func TestAuthenticateRequestClearHeaders(t *testing.T) {
|
|||||||
finalHeaders: http.Header{
|
finalHeaders: http.Header{
|
||||||
"X-Group-Remote": {"snorlax"},
|
"X-Group-Remote": {"snorlax"},
|
||||||
"X-Group-Bear": {"panda"},
|
"X-Group-Bear": {"panda"},
|
||||||
|
"X-Uid-Remote": {"bobs-unrelated-uid"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
"custom config but request contains standard headers": {
|
"custom config but request contains standard headers": {
|
||||||
nameHeaders: []string{"foo"},
|
nameHeaders: []string{"foo"},
|
||||||
|
uidHeaders: []string{"footoo"},
|
||||||
groupHeaders: []string{"bar"},
|
groupHeaders: []string{"bar"},
|
||||||
extraPrefixHeaders: []string{"baz"},
|
extraPrefixHeaders: []string{"baz"},
|
||||||
requestHeaders: http.Header{
|
requestHeaders: http.Header{
|
||||||
"X-Remote-User": {"Bob"},
|
"X-Remote-User": {"Bob"},
|
||||||
|
"X-Remote-Uid": {"bobs-uid"},
|
||||||
"X-Remote-Group-1": {"one-a", "one-b"},
|
"X-Remote-Group-1": {"one-a", "one-b"},
|
||||||
"X-Remote-Group-2": {"two-a", "two-b"},
|
"X-Remote-Group-2": {"two-a", "two-b"},
|
||||||
"X-Remote-extra-1-key1": {"alfa", "bravo"},
|
"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": {
|
"custom config but request contains standard and custom headers": {
|
||||||
nameHeaders: []string{"one"},
|
nameHeaders: []string{"one"},
|
||||||
|
uidHeaders: []string{"onetoo"},
|
||||||
groupHeaders: []string{"two"},
|
groupHeaders: []string{"two"},
|
||||||
extraPrefixHeaders: []string{"three-"},
|
extraPrefixHeaders: []string{"three-"},
|
||||||
requestHeaders: http.Header{
|
requestHeaders: http.Header{
|
||||||
"X-Remote-User": {"Bob"},
|
"X-Remote-User": {"Bob"},
|
||||||
|
"X-Remote-Uid": {"bobs-uid"},
|
||||||
"X-Remote-Group-3": {"one-a", "one-b"},
|
"X-Remote-Group-3": {"one-a", "one-b"},
|
||||||
"X-Remote-Group-4": {"two-a", "two-b"},
|
"X-Remote-Group-4": {"two-a", "two-b"},
|
||||||
"X-Remote-extra-1-key1": {"alfa", "bravo"},
|
"X-Remote-extra-1-key1": {"alfa", "bravo"},
|
||||||
@ -422,10 +457,12 @@ func TestAuthenticateRequestClearHeaders(t *testing.T) {
|
|||||||
|
|
||||||
"escaped extra keys": {
|
"escaped extra keys": {
|
||||||
nameHeaders: []string{"X-Remote-User"},
|
nameHeaders: []string{"X-Remote-User"},
|
||||||
|
uidHeaders: []string{"X-Remote-Uid"},
|
||||||
groupHeaders: []string{"X-Remote-Group"},
|
groupHeaders: []string{"X-Remote-Group"},
|
||||||
extraPrefixHeaders: []string{"X-Remote-Extra-"},
|
extraPrefixHeaders: []string{"X-Remote-Extra-"},
|
||||||
requestHeaders: http.Header{
|
requestHeaders: http.Header{
|
||||||
"X-Remote-User": {"Bob"},
|
"X-Remote-User": {"Bob"},
|
||||||
|
"X-Remote-Uid": {"bobs-uid"},
|
||||||
"X-Remote-Group": {"one-a", "one-b"},
|
"X-Remote-Group": {"one-a", "one-b"},
|
||||||
"X-Remote-Extra-Alpha": {"alphabetical"},
|
"X-Remote-Extra-Alpha": {"alphabetical"},
|
||||||
"X-Remote-Extra-Alph4num3r1c": {"alphanumeric"},
|
"X-Remote-Extra-Alph4num3r1c": {"alphanumeric"},
|
||||||
@ -455,6 +492,7 @@ func TestAuthenticateRequestClearHeaders(t *testing.T) {
|
|||||||
nil,
|
nil,
|
||||||
&authenticatorfactory.RequestHeaderConfig{
|
&authenticatorfactory.RequestHeaderConfig{
|
||||||
UsernameHeaders: headerrequest.StaticStringSlice(testcase.nameHeaders),
|
UsernameHeaders: headerrequest.StaticStringSlice(testcase.nameHeaders),
|
||||||
|
UIDHeaders: headerrequest.StaticStringSlice(testcase.uidHeaders),
|
||||||
GroupHeaders: headerrequest.StaticStringSlice(testcase.groupHeaders),
|
GroupHeaders: headerrequest.StaticStringSlice(testcase.groupHeaders),
|
||||||
ExtraHeaderPrefixes: headerrequest.StaticStringSlice(testcase.extraPrefixHeaders),
|
ExtraHeaderPrefixes: headerrequest.StaticStringSlice(testcase.extraPrefixHeaders),
|
||||||
},
|
},
|
||||||
|
@ -56,6 +56,7 @@ type RequestHeaderAuthenticationOptions struct {
|
|||||||
ClientCAFile string
|
ClientCAFile string
|
||||||
|
|
||||||
UsernameHeaders []string
|
UsernameHeaders []string
|
||||||
|
UIDHeaders []string
|
||||||
GroupHeaders []string
|
GroupHeaders []string
|
||||||
ExtraHeaderPrefixes []string
|
ExtraHeaderPrefixes []string
|
||||||
AllowedNames []string
|
AllowedNames []string
|
||||||
@ -67,6 +68,9 @@ func (s *RequestHeaderAuthenticationOptions) Validate() []error {
|
|||||||
if err := checkForWhiteSpaceOnly("requestheader-username-headers", s.UsernameHeaders...); err != nil {
|
if err := checkForWhiteSpaceOnly("requestheader-username-headers", s.UsernameHeaders...); err != nil {
|
||||||
allErrors = append(allErrors, err)
|
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 {
|
if err := checkForWhiteSpaceOnly("requestheader-group-headers", s.GroupHeaders...); err != nil {
|
||||||
allErrors = append(allErrors, err)
|
allErrors = append(allErrors, err)
|
||||||
}
|
}
|
||||||
@ -80,6 +84,10 @@ func (s *RequestHeaderAuthenticationOptions) Validate() []error {
|
|||||||
if len(s.UsernameHeaders) > 0 && !caseInsensitiveHas(s.UsernameHeaders, "X-Remote-User") {
|
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")
|
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") {
|
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")
|
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, ""+
|
fs.StringSliceVar(&s.UsernameHeaders, "requestheader-username-headers", s.UsernameHeaders, ""+
|
||||||
"List of request headers to inspect for usernames. X-Remote-User is common.")
|
"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, ""+
|
fs.StringSliceVar(&s.GroupHeaders, "requestheader-group-headers", s.GroupHeaders, ""+
|
||||||
"List of request headers to inspect for groups. X-Remote-Group is suggested.")
|
"List of request headers to inspect for groups. X-Remote-Group is suggested.")
|
||||||
|
|
||||||
@ -148,6 +159,7 @@ func (s *RequestHeaderAuthenticationOptions) ToAuthenticationRequestHeaderConfig
|
|||||||
|
|
||||||
return &authenticatorfactory.RequestHeaderConfig{
|
return &authenticatorfactory.RequestHeaderConfig{
|
||||||
UsernameHeaders: headerrequest.StaticStringSlice(s.UsernameHeaders),
|
UsernameHeaders: headerrequest.StaticStringSlice(s.UsernameHeaders),
|
||||||
|
UIDHeaders: headerrequest.StaticStringSlice(s.UIDHeaders),
|
||||||
GroupHeaders: headerrequest.StaticStringSlice(s.GroupHeaders),
|
GroupHeaders: headerrequest.StaticStringSlice(s.GroupHeaders),
|
||||||
ExtraHeaderPrefixes: headerrequest.StaticStringSlice(s.ExtraHeaderPrefixes),
|
ExtraHeaderPrefixes: headerrequest.StaticStringSlice(s.ExtraHeaderPrefixes),
|
||||||
CAContentProvider: caBundleProvider,
|
CAContentProvider: caBundleProvider,
|
||||||
@ -233,7 +245,13 @@ func NewDelegatingAuthenticationOptions() *DelegatingAuthenticationOptions {
|
|||||||
CacheTTL: 10 * time.Second,
|
CacheTTL: 10 * time.Second,
|
||||||
ClientCert: ClientCertAuthenticationOptions{},
|
ClientCert: ClientCertAuthenticationOptions{},
|
||||||
RequestHeader: RequestHeaderAuthenticationOptions{
|
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"},
|
GroupHeaders: []string{"x-remote-group"},
|
||||||
ExtraHeaderPrefixes: []string{"x-remote-extra-"},
|
ExtraHeaderPrefixes: []string{"x-remote-extra-"},
|
||||||
},
|
},
|
||||||
@ -423,6 +441,7 @@ func (s *DelegatingAuthenticationOptions) createRequestHeaderConfig(client kuber
|
|||||||
return &authenticatorfactory.RequestHeaderConfig{
|
return &authenticatorfactory.RequestHeaderConfig{
|
||||||
CAContentProvider: dynamicRequestHeaderProvider,
|
CAContentProvider: dynamicRequestHeaderProvider,
|
||||||
UsernameHeaders: headerrequest.StringSliceProvider(headerrequest.StringSliceProviderFunc(dynamicRequestHeaderProvider.UsernameHeaders)),
|
UsernameHeaders: headerrequest.StringSliceProvider(headerrequest.StringSliceProviderFunc(dynamicRequestHeaderProvider.UsernameHeaders)),
|
||||||
|
UIDHeaders: headerrequest.StringSliceProvider(headerrequest.StringSliceProviderFunc(dynamicRequestHeaderProvider.UIDHeaders)),
|
||||||
GroupHeaders: headerrequest.StringSliceProvider(headerrequest.StringSliceProviderFunc(dynamicRequestHeaderProvider.GroupHeaders)),
|
GroupHeaders: headerrequest.StringSliceProvider(headerrequest.StringSliceProviderFunc(dynamicRequestHeaderProvider.GroupHeaders)),
|
||||||
ExtraHeaderPrefixes: headerrequest.StringSliceProvider(headerrequest.StringSliceProviderFunc(dynamicRequestHeaderProvider.ExtraHeaderPrefixes)),
|
ExtraHeaderPrefixes: headerrequest.StringSliceProvider(headerrequest.StringSliceProviderFunc(dynamicRequestHeaderProvider.ExtraHeaderPrefixes)),
|
||||||
AllowedClientNames: headerrequest.StringSliceProvider(headerrequest.StringSliceProviderFunc(dynamicRequestHeaderProvider.AllowedClientNames)),
|
AllowedClientNames: headerrequest.StringSliceProvider(headerrequest.StringSliceProviderFunc(dynamicRequestHeaderProvider.AllowedClientNames)),
|
||||||
|
@ -55,6 +55,7 @@ func newDynamicRequestHeaderController(client kubernetes.Interface) (*DynamicReq
|
|||||||
authenticationConfigMapNamespace,
|
authenticationConfigMapNamespace,
|
||||||
client,
|
client,
|
||||||
"requestheader-username-headers",
|
"requestheader-username-headers",
|
||||||
|
"requestheader-uid-headers",
|
||||||
"requestheader-group-headers",
|
"requestheader-group-headers",
|
||||||
"requestheader-extra-headers-prefix",
|
"requestheader-extra-headers-prefix",
|
||||||
"requestheader-allowed-names",
|
"requestheader-allowed-names",
|
||||||
|
@ -39,6 +39,7 @@ func TestToAuthenticationRequestHeaderConfig(t *testing.T) {
|
|||||||
name: "test when ClientCAFile is nil",
|
name: "test when ClientCAFile is nil",
|
||||||
testOptions: &RequestHeaderAuthenticationOptions{
|
testOptions: &RequestHeaderAuthenticationOptions{
|
||||||
UsernameHeaders: headerrequest.StaticStringSlice{"x-remote-user"},
|
UsernameHeaders: headerrequest.StaticStringSlice{"x-remote-user"},
|
||||||
|
UIDHeaders: headerrequest.StaticStringSlice{"x-remote-uid"},
|
||||||
GroupHeaders: headerrequest.StaticStringSlice{"x-remote-group"},
|
GroupHeaders: headerrequest.StaticStringSlice{"x-remote-group"},
|
||||||
ExtraHeaderPrefixes: headerrequest.StaticStringSlice{"x-remote-extra-"},
|
ExtraHeaderPrefixes: headerrequest.StaticStringSlice{"x-remote-extra-"},
|
||||||
AllowedNames: headerrequest.StaticStringSlice{"kube-aggregator"},
|
AllowedNames: headerrequest.StaticStringSlice{"kube-aggregator"},
|
||||||
@ -49,12 +50,14 @@ func TestToAuthenticationRequestHeaderConfig(t *testing.T) {
|
|||||||
testOptions: &RequestHeaderAuthenticationOptions{
|
testOptions: &RequestHeaderAuthenticationOptions{
|
||||||
ClientCAFile: "testdata/root.pem",
|
ClientCAFile: "testdata/root.pem",
|
||||||
UsernameHeaders: headerrequest.StaticStringSlice{"x-remote-user"},
|
UsernameHeaders: headerrequest.StaticStringSlice{"x-remote-user"},
|
||||||
|
UIDHeaders: headerrequest.StaticStringSlice{"x-remote-uid"},
|
||||||
GroupHeaders: headerrequest.StaticStringSlice{"x-remote-group"},
|
GroupHeaders: headerrequest.StaticStringSlice{"x-remote-group"},
|
||||||
ExtraHeaderPrefixes: headerrequest.StaticStringSlice{"x-remote-extra-"},
|
ExtraHeaderPrefixes: headerrequest.StaticStringSlice{"x-remote-extra-"},
|
||||||
AllowedNames: headerrequest.StaticStringSlice{"kube-aggregator"},
|
AllowedNames: headerrequest.StaticStringSlice{"kube-aggregator"},
|
||||||
},
|
},
|
||||||
expectConfig: &authenticatorfactory.RequestHeaderConfig{
|
expectConfig: &authenticatorfactory.RequestHeaderConfig{
|
||||||
UsernameHeaders: headerrequest.StaticStringSlice{"x-remote-user"},
|
UsernameHeaders: headerrequest.StaticStringSlice{"x-remote-user"},
|
||||||
|
UIDHeaders: headerrequest.StaticStringSlice{"x-remote-uid"},
|
||||||
GroupHeaders: headerrequest.StaticStringSlice{"x-remote-group"},
|
GroupHeaders: headerrequest.StaticStringSlice{"x-remote-group"},
|
||||||
ExtraHeaderPrefixes: headerrequest.StaticStringSlice{"x-remote-extra-"},
|
ExtraHeaderPrefixes: headerrequest.StaticStringSlice{"x-remote-extra-"},
|
||||||
CAContentProvider: nil, // this is nil because you can't compare functions
|
CAContentProvider: nil, // this is nil because you can't compare functions
|
||||||
|
@ -251,7 +251,7 @@ func (h *peerProxyHandler) proxyRequestToDestinationAPIServer(req *http.Request,
|
|||||||
newReq.Header.Add(PeerProxiedHeader, "true")
|
newReq.Header.Add(PeerProxiedHeader, "true")
|
||||||
defer cancelFn()
|
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}
|
delegate := &epmetrics.ResponseWriterDelegator{ResponseWriter: rw}
|
||||||
w := responsewriter.WrapForHTTP1Or2(delegate)
|
w := responsewriter.WrapForHTTP1Or2(delegate)
|
||||||
|
@ -86,6 +86,7 @@ func DebugWrappers(rt http.RoundTripper) http.RoundTripper {
|
|||||||
|
|
||||||
type authProxyRoundTripper struct {
|
type authProxyRoundTripper struct {
|
||||||
username string
|
username string
|
||||||
|
uid string
|
||||||
groups []string
|
groups []string
|
||||||
extra map[string][]string
|
extra map[string][]string
|
||||||
|
|
||||||
@ -98,15 +99,17 @@ var _ utilnet.RoundTripperWrapper = &authProxyRoundTripper{}
|
|||||||
// authentication terminating proxy cases
|
// authentication terminating proxy cases
|
||||||
// assuming you pull the user from the context:
|
// assuming you pull the user from the context:
|
||||||
// username is the user.Info.GetName() of the user
|
// 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
|
// groups is the user.Info.GetGroups() of the user
|
||||||
// extra is the user.Info.GetExtra() of the user
|
// extra is the user.Info.GetExtra() of the user
|
||||||
// extra can contain any additional information that the authenticator
|
// extra can contain any additional information that the authenticator
|
||||||
// thought was interesting, for example authorization scopes.
|
// thought was interesting, for example authorization scopes.
|
||||||
// In order to faithfully round-trip through an impersonation flow, these keys
|
// In order to faithfully round-trip through an impersonation flow, these keys
|
||||||
// MUST be lowercase.
|
// 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{
|
return &authProxyRoundTripper{
|
||||||
username: username,
|
username: username,
|
||||||
|
uid: uid,
|
||||||
groups: groups,
|
groups: groups,
|
||||||
extra: extra,
|
extra: extra,
|
||||||
rt: rt,
|
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) {
|
func (rt *authProxyRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
req = utilnet.CloneRequest(req)
|
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)
|
return rt.rt.RoundTrip(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetAuthProxyHeaders stomps the auth proxy header fields. It mutates its argument.
|
// 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-User")
|
||||||
|
req.Header.Del("X-Remote-Uid")
|
||||||
req.Header.Del("X-Remote-Group")
|
req.Header.Del("X-Remote-Group")
|
||||||
for key := range req.Header {
|
for key := range req.Header {
|
||||||
if strings.HasPrefix(strings.ToLower(key), strings.ToLower("X-Remote-Extra-")) {
|
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)
|
req.Header.Set("X-Remote-User", username)
|
||||||
|
if len(uid) > 0 {
|
||||||
|
req.Header.Set("X-Remote-Uid", uid)
|
||||||
|
}
|
||||||
for _, group := range groups {
|
for _, group := range groups {
|
||||||
req.Header.Add("X-Remote-Group", group)
|
req.Header.Add("X-Remote-Group", group)
|
||||||
}
|
}
|
||||||
|
@ -306,12 +306,14 @@ func TestImpersonationRoundTripper(t *testing.T) {
|
|||||||
func TestAuthProxyRoundTripper(t *testing.T) {
|
func TestAuthProxyRoundTripper(t *testing.T) {
|
||||||
for n, tc := range map[string]struct {
|
for n, tc := range map[string]struct {
|
||||||
username string
|
username string
|
||||||
|
uid string
|
||||||
groups []string
|
groups []string
|
||||||
extra map[string][]string
|
extra map[string][]string
|
||||||
expectedExtra map[string][]string
|
expectedExtra map[string][]string
|
||||||
}{
|
}{
|
||||||
"allfields": {
|
"allfields": {
|
||||||
username: "user",
|
username: "user",
|
||||||
|
uid: "7db46926-e803-4337-9a29-f9c1fab7d34a",
|
||||||
groups: []string{"groupA", "groupB"},
|
groups: []string{"groupA", "groupB"},
|
||||||
extra: map[string][]string{
|
extra: map[string][]string{
|
||||||
"one": {"alpha", "bravo"},
|
"one": {"alpha", "bravo"},
|
||||||
@ -324,6 +326,7 @@ func TestAuthProxyRoundTripper(t *testing.T) {
|
|||||||
},
|
},
|
||||||
"escaped extra": {
|
"escaped extra": {
|
||||||
username: "user",
|
username: "user",
|
||||||
|
uid: "7db46926-e803-4337-9a29-f9c1fab7d34a",
|
||||||
groups: []string{"groupA", "groupB"},
|
groups: []string{"groupA", "groupB"},
|
||||||
extra: map[string][]string{
|
extra: map[string][]string{
|
||||||
"one": {"alpha", "bravo"},
|
"one": {"alpha", "bravo"},
|
||||||
@ -336,6 +339,7 @@ func TestAuthProxyRoundTripper(t *testing.T) {
|
|||||||
},
|
},
|
||||||
"double escaped extra": {
|
"double escaped extra": {
|
||||||
username: "user",
|
username: "user",
|
||||||
|
uid: "7db46926-e803-4337-9a29-f9c1fab7d34a",
|
||||||
groups: []string{"groupA", "groupB"},
|
groups: []string{"groupA", "groupB"},
|
||||||
extra: map[string][]string{
|
extra: map[string][]string{
|
||||||
"one": {"alpha", "bravo"},
|
"one": {"alpha", "bravo"},
|
||||||
@ -349,7 +353,7 @@ func TestAuthProxyRoundTripper(t *testing.T) {
|
|||||||
} {
|
} {
|
||||||
rt := &testRoundTripper{}
|
rt := &testRoundTripper{}
|
||||||
req := &http.Request{}
|
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 {
|
if rt.Request == nil {
|
||||||
t.Errorf("%s: unexpected nil request: %v", n, rt)
|
t.Errorf("%s: unexpected nil request: %v", n, rt)
|
||||||
continue
|
continue
|
||||||
@ -368,6 +372,15 @@ func TestAuthProxyRoundTripper(t *testing.T) {
|
|||||||
t.Errorf("%s expected %v, got %v", n, e, a)
|
t.Errorf("%s expected %v, got %v", n, e, a)
|
||||||
continue
|
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"]
|
actualGroups, ok := rt.Request.Header["X-Remote-Group"]
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Errorf("%s missing value", n)
|
t.Errorf("%s missing value", n)
|
||||||
|
@ -133,6 +133,7 @@ func TestDefaultFlags(t *testing.T) {
|
|||||||
ClientCert: apiserveroptions.ClientCertAuthenticationOptions{},
|
ClientCert: apiserveroptions.ClientCertAuthenticationOptions{},
|
||||||
RequestHeader: apiserveroptions.RequestHeaderAuthenticationOptions{
|
RequestHeader: apiserveroptions.RequestHeaderAuthenticationOptions{
|
||||||
UsernameHeaders: []string{"x-remote-user"},
|
UsernameHeaders: []string{"x-remote-user"},
|
||||||
|
UIDHeaders: nil,
|
||||||
GroupHeaders: []string{"x-remote-group"},
|
GroupHeaders: []string{"x-remote-group"},
|
||||||
ExtraHeaderPrefixes: []string{"x-remote-extra-"},
|
ExtraHeaderPrefixes: []string{"x-remote-extra-"},
|
||||||
},
|
},
|
||||||
@ -293,6 +294,7 @@ func TestAddFlags(t *testing.T) {
|
|||||||
ClientCert: apiserveroptions.ClientCertAuthenticationOptions{},
|
ClientCert: apiserveroptions.ClientCertAuthenticationOptions{},
|
||||||
RequestHeader: apiserveroptions.RequestHeaderAuthenticationOptions{
|
RequestHeader: apiserveroptions.RequestHeaderAuthenticationOptions{
|
||||||
UsernameHeaders: []string{"x-remote-user"},
|
UsernameHeaders: []string{"x-remote-user"},
|
||||||
|
UIDHeaders: nil,
|
||||||
GroupHeaders: []string{"x-remote-group"},
|
GroupHeaders: []string{"x-remote-group"},
|
||||||
ExtraHeaderPrefixes: []string{"x-remote-extra-"},
|
ExtraHeaderPrefixes: []string{"x-remote-extra-"},
|
||||||
},
|
},
|
||||||
|
@ -159,7 +159,7 @@ func (r *proxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||||||
proxyRoundTripper := handlingInfo.proxyRoundTripper
|
proxyRoundTripper := handlingInfo.proxyRoundTripper
|
||||||
upgrade := httpstream.IsUpgradeRequest(req)
|
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 {
|
if utilfeature.DefaultFeatureGate.Enabled(genericfeatures.APIServerTracing) && !upgrade {
|
||||||
tracingWrapper := tracing.WrapperFor(r.tracerProvider)
|
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
|
// 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.
|
// attach the "correct" user headers to the request ahead of time.
|
||||||
if upgrade {
|
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})
|
handler := proxy.NewUpgradeAwareHandler(location, proxyRoundTripper, true, upgrade, &responder{w: w})
|
||||||
|
@ -154,6 +154,7 @@ func TestProxyHandler(t *testing.T) {
|
|||||||
"proxy with user, insecure": {
|
"proxy with user, insecure": {
|
||||||
user: &user.DefaultInfo{
|
user: &user.DefaultInfo{
|
||||||
Name: "username",
|
Name: "username",
|
||||||
|
UID: "6b60d791-1af9-4513-92e5-e4252a1e0a78",
|
||||||
Groups: []string{"one", "two"},
|
Groups: []string{"one", "two"},
|
||||||
},
|
},
|
||||||
path: "/request/path",
|
path: "/request/path",
|
||||||
@ -178,6 +179,7 @@ func TestProxyHandler(t *testing.T) {
|
|||||||
"X-Forwarded-Uri": {"/request/path"},
|
"X-Forwarded-Uri": {"/request/path"},
|
||||||
"X-Forwarded-For": {"127.0.0.1"},
|
"X-Forwarded-For": {"127.0.0.1"},
|
||||||
"X-Remote-User": {"username"},
|
"X-Remote-User": {"username"},
|
||||||
|
"X-Remote-Uid": {"6b60d791-1af9-4513-92e5-e4252a1e0a78"},
|
||||||
"User-Agent": {"Go-http-client/1.1"},
|
"User-Agent": {"Go-http-client/1.1"},
|
||||||
"Accept-Encoding": {"gzip"},
|
"Accept-Encoding": {"gzip"},
|
||||||
"X-Remote-Group": {"one", "two"},
|
"X-Remote-Group": {"one", "two"},
|
||||||
@ -186,6 +188,7 @@ func TestProxyHandler(t *testing.T) {
|
|||||||
"proxy with user, cabundle": {
|
"proxy with user, cabundle": {
|
||||||
user: &user.DefaultInfo{
|
user: &user.DefaultInfo{
|
||||||
Name: "username",
|
Name: "username",
|
||||||
|
UID: "6b60d791-1af9-4513-92e5-e4252a1e0a78",
|
||||||
Groups: []string{"one", "two"},
|
Groups: []string{"one", "two"},
|
||||||
},
|
},
|
||||||
path: "/request/path",
|
path: "/request/path",
|
||||||
@ -210,6 +213,7 @@ func TestProxyHandler(t *testing.T) {
|
|||||||
"X-Forwarded-Uri": {"/request/path"},
|
"X-Forwarded-Uri": {"/request/path"},
|
||||||
"X-Forwarded-For": {"127.0.0.1"},
|
"X-Forwarded-For": {"127.0.0.1"},
|
||||||
"X-Remote-User": {"username"},
|
"X-Remote-User": {"username"},
|
||||||
|
"X-Remote-Uid": {"6b60d791-1af9-4513-92e5-e4252a1e0a78"},
|
||||||
"User-Agent": {"Go-http-client/1.1"},
|
"User-Agent": {"Go-http-client/1.1"},
|
||||||
"Accept-Encoding": {"gzip"},
|
"Accept-Encoding": {"gzip"},
|
||||||
"X-Remote-Group": {"one", "two"},
|
"X-Remote-Group": {"one", "two"},
|
||||||
@ -218,6 +222,7 @@ func TestProxyHandler(t *testing.T) {
|
|||||||
"service unavailable": {
|
"service unavailable": {
|
||||||
user: &user.DefaultInfo{
|
user: &user.DefaultInfo{
|
||||||
Name: "username",
|
Name: "username",
|
||||||
|
UID: "6b60d791-1af9-4513-92e5-e4252a1e0a78",
|
||||||
Groups: []string{"one", "two"},
|
Groups: []string{"one", "two"},
|
||||||
},
|
},
|
||||||
path: "/request/path",
|
path: "/request/path",
|
||||||
@ -240,6 +245,7 @@ func TestProxyHandler(t *testing.T) {
|
|||||||
"service unresolveable": {
|
"service unresolveable": {
|
||||||
user: &user.DefaultInfo{
|
user: &user.DefaultInfo{
|
||||||
Name: "username",
|
Name: "username",
|
||||||
|
UID: "6b60d791-1af9-4513-92e5-e4252a1e0a78",
|
||||||
Groups: []string{"one", "two"},
|
Groups: []string{"one", "two"},
|
||||||
},
|
},
|
||||||
path: "/request/path",
|
path: "/request/path",
|
||||||
@ -263,6 +269,7 @@ func TestProxyHandler(t *testing.T) {
|
|||||||
"fail on bad serving cert": {
|
"fail on bad serving cert": {
|
||||||
user: &user.DefaultInfo{
|
user: &user.DefaultInfo{
|
||||||
Name: "username",
|
Name: "username",
|
||||||
|
UID: "6b60d791-1af9-4513-92e5-e4252a1e0a78",
|
||||||
Groups: []string{"one", "two"},
|
Groups: []string{"one", "two"},
|
||||||
},
|
},
|
||||||
path: "/request/path",
|
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": {
|
"fail on bad serving cert w/o SAN and increase SAN error counter metrics": {
|
||||||
user: &user.DefaultInfo{
|
user: &user.DefaultInfo{
|
||||||
Name: "username",
|
Name: "username",
|
||||||
|
UID: "6b60d791-1af9-4513-92e5-e4252a1e0a78",
|
||||||
Groups: []string{"one", "two"},
|
Groups: []string{"one", "two"},
|
||||||
},
|
},
|
||||||
path: "/request/path",
|
path: "/request/path",
|
||||||
@ -425,6 +433,7 @@ func newBrokenDialerAndSelector() (*mockEgressDialer, *egressselector.EgressSele
|
|||||||
|
|
||||||
func TestProxyUpgrade(t *testing.T) {
|
func TestProxyUpgrade(t *testing.T) {
|
||||||
upgradeUser := "upgradeUser"
|
upgradeUser := "upgradeUser"
|
||||||
|
upgradeUID := "upgradeUser-UID"
|
||||||
testcases := map[string]struct {
|
testcases := map[string]struct {
|
||||||
APIService *apiregistration.APIService
|
APIService *apiregistration.APIService
|
||||||
NewEgressSelector func() (*mockEgressDialer, *egressselector.EgressSelector)
|
NewEgressSelector func() (*mockEgressDialer, *egressselector.EgressSelector)
|
||||||
@ -534,6 +543,10 @@ func TestProxyUpgrade(t *testing.T) {
|
|||||||
if user != upgradeUser {
|
if user != upgradeUser {
|
||||||
t.Errorf("expected user %q, got %q", upgradeUser, user)
|
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)
|
body := make([]byte, 5)
|
||||||
ws.Read(body)
|
ws.Read(body)
|
||||||
ws.Write([]byte("hello " + string(body)))
|
ws.Write([]byte("hello " + string(body)))
|
||||||
@ -576,7 +589,7 @@ func TestProxyUpgrade(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
proxyHandler.updateAPIService(tc.APIService)
|
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()
|
defer aggregator.Close()
|
||||||
|
|
||||||
ws, err := websocket.Dial("ws://"+aggregator.Listener.Addr().String()+path, "", "http://127.0.0.1/")
|
ws, err := websocket.Dial("ws://"+aggregator.Listener.Addr().String()+path, "", "http://127.0.0.1/")
|
||||||
|
@ -305,7 +305,7 @@ func (c *AvailableConditionController) sync(key string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// setting the system-masters identity ensures that we will always have access rights
|
// 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)
|
resp, err := discoveryClient.Do(newReq)
|
||||||
if resp != nil {
|
if resp != nil {
|
||||||
resp.Body.Close()
|
resp.Body.Close()
|
||||||
|
173
test/integration/auth/requestheader_test.go
Normal file
173
test/integration/auth/requestheader_test.go
Normal 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
|
||||||
|
}
|
@ -29,16 +29,22 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
v1 "k8s.io/api/authentication/v1"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/util/version"
|
"k8s.io/apimachinery/pkg/util/version"
|
||||||
"k8s.io/apimachinery/pkg/util/wait"
|
"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"
|
"k8s.io/apiserver/pkg/server/dynamiccertificates"
|
||||||
genericapiserveroptions "k8s.io/apiserver/pkg/server/options"
|
genericapiserveroptions "k8s.io/apiserver/pkg/server/options"
|
||||||
utilversion "k8s.io/apiserver/pkg/util/version"
|
utilversion "k8s.io/apiserver/pkg/util/version"
|
||||||
@ -46,6 +52,7 @@ import (
|
|||||||
"k8s.io/client-go/rest"
|
"k8s.io/client-go/rest"
|
||||||
"k8s.io/client-go/tools/clientcmd"
|
"k8s.io/client-go/tools/clientcmd"
|
||||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||||
|
"k8s.io/client-go/transport"
|
||||||
"k8s.io/client-go/util/cert"
|
"k8s.io/client-go/util/cert"
|
||||||
"k8s.io/component-base/featuregate"
|
"k8s.io/component-base/featuregate"
|
||||||
apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
|
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) {
|
func testAggregatedAPIServer(t *testing.T, setWardleFeatureGate, banFlunder bool, wardleBinaryVersion, wardleEmulationVersion string) {
|
||||||
const testNamespace = "kube-wardle"
|
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.
|
// each wardle binary is bundled with a specific kube binary.
|
||||||
kubeBinaryVersion := sampleserver.WardleVersionToKubeVersion(version.MustParse(wardleBinaryVersion)).String()
|
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)
|
kubeClientConfig := getKubeConfig(testKAS)
|
||||||
|
|
||||||
wardleCertDir, _ := os.MkdirTemp("", "test-integration-wardle-server")
|
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
|
// makes the kube-apiserver very responsive. it's normally a minute
|
||||||
dynamiccertificates.FileRefreshDuration = 1 * time.Second
|
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
|
// 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))))
|
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() })
|
t.Cleanup(func() { testServer.TearDownFn() })
|
||||||
|
|
||||||
_, _ = utilversion.DefaultComponentGlobalsRegistry.ComponentGlobalsOrRegister(
|
_, _ = utilversion.DefaultComponentGlobalsRegistry.ComponentGlobalsOrRegister(
|
||||||
|
Loading…
Reference in New Issue
Block a user