Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RequestHeader authentication: add UID to recognized request headers #115834

Merged
merged 7 commits into from
Sep 5, 2024
18 changes: 18 additions & 0 deletions cmd/kube-apiserver/app/testing/testserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ func StartTestServer(t ktesting.TB, instanceOptions *TestServerInstanceOptions,
}
s.SecureServing.ServerCert.CertDirectory = result.TmpDir

reqHeaderFromFlags := s.Authentication.RequestHeader
if instanceOptions.EnableCertAuth {
// set up default headers for request header auth
reqHeaders := serveroptions.NewDelegatingAuthenticationOptions()
Expand Down Expand Up @@ -347,6 +348,23 @@ func StartTestServer(t ktesting.TB, instanceOptions *TestServerInstanceOptions,
return result, err
}

// the RequestHeader options pointer gets replaced in the case of EnableCertAuth override
// and so flags are connected to a struct that no longer appears in the ServerOptions struct
// we're using.
// We still want to make it possible to configure the headers config for the RequestHeader authenticator.
if usernameHeaders := reqHeaderFromFlags.UsernameHeaders; len(usernameHeaders) > 0 {
s.Authentication.RequestHeader.UsernameHeaders = usernameHeaders
}
if uidHeaders := reqHeaderFromFlags.UIDHeaders; len(uidHeaders) > 0 {
s.Authentication.RequestHeader.UIDHeaders = uidHeaders
}
if groupHeaders := reqHeaderFromFlags.GroupHeaders; len(groupHeaders) > 0 {
s.Authentication.RequestHeader.GroupHeaders = groupHeaders
}
if extraHeaders := reqHeaderFromFlags.ExtraHeaderPrefixes; len(extraHeaders) > 0 {
s.Authentication.RequestHeader.ExtraHeaderPrefixes = extraHeaders
}

if err := utilversion.DefaultComponentGlobalsRegistry.Set(); err != nil {
return result, err
}
Expand Down
1 change: 1 addition & 0 deletions cmd/kube-controller-manager/app/options/options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,7 @@ func TestAddFlags(t *testing.T) {
ClientCert: apiserveroptions.ClientCertAuthenticationOptions{},
RequestHeader: apiserveroptions.RequestHeaderAuthenticationOptions{
UsernameHeaders: []string{"x-remote-user"},
UIDHeaders: nil,
GroupHeaders: []string{"x-remote-group"},
ExtraHeaderPrefixes: []string{"x-remote-extra-"},
},
Expand Down
1 change: 1 addition & 0 deletions pkg/controlplane/apiserver/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,7 @@ func CreateConfig(
config.ClusterAuthenticationInfo.RequestHeaderExtraHeaderPrefixes = requestHeaderConfig.ExtraHeaderPrefixes
config.ClusterAuthenticationInfo.RequestHeaderGroupHeaders = requestHeaderConfig.GroupHeaders
config.ClusterAuthenticationInfo.RequestHeaderUsernameHeaders = requestHeaderConfig.UsernameHeaders
config.ClusterAuthenticationInfo.RequestHeaderUIDHeaders = requestHeaderConfig.UIDHeaders
}

// setup admission
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ type ClusterAuthenticationInfo struct {

// RequestHeaderUsernameHeaders are the headers used by this kube-apiserver to determine username
RequestHeaderUsernameHeaders headerrequest.StringSliceProvider
// RequestHeaderUIDHeaders are the headers used by this kube-apiserver to determine UID
RequestHeaderUIDHeaders headerrequest.StringSliceProvider
// RequestHeaderGroupHeaders are the headers used by this kube-apiserver to determine groups
RequestHeaderGroupHeaders headerrequest.StringSliceProvider
// RequestHeaderExtraHeaderPrefixes are the headers used by this kube-apiserver to determine user.extra
Expand Down Expand Up @@ -224,6 +226,7 @@ func combinedClusterAuthenticationInfo(lhs, rhs ClusterAuthenticationInfo) (Clus
RequestHeaderExtraHeaderPrefixes: combineUniqueStringSlices(lhs.RequestHeaderExtraHeaderPrefixes, rhs.RequestHeaderExtraHeaderPrefixes),
RequestHeaderGroupHeaders: combineUniqueStringSlices(lhs.RequestHeaderGroupHeaders, rhs.RequestHeaderGroupHeaders),
RequestHeaderUsernameHeaders: combineUniqueStringSlices(lhs.RequestHeaderUsernameHeaders, rhs.RequestHeaderUsernameHeaders),
RequestHeaderUIDHeaders: combineUniqueStringSlices(lhs.RequestHeaderUIDHeaders, rhs.RequestHeaderUIDHeaders),
}

var err error
Expand Down Expand Up @@ -259,6 +262,10 @@ func getConfigMapDataFor(authenticationInfo ClusterAuthenticationInfo) (map[stri
if err != nil {
return nil, err
}
data["requestheader-uid-headers"], err = jsonSerializeStringSlice(authenticationInfo.RequestHeaderUIDHeaders.Value())
if err != nil {
return nil, err
}
data["requestheader-group-headers"], err = jsonSerializeStringSlice(authenticationInfo.RequestHeaderGroupHeaders.Value())
if err != nil {
return nil, err
Expand Down Expand Up @@ -298,6 +305,10 @@ func getClusterAuthenticationInfoFor(data map[string]string) (ClusterAuthenticat
if err != nil {
return ClusterAuthenticationInfo{}, err
}
ret.RequestHeaderUIDHeaders, err = jsonDeserializeStringSlice(data["requestheader-uid-headers"])
if err != nil {
return ClusterAuthenticationInfo{}, err
}

if caBundle := data["requestheader-client-ca-file"]; len(caBundle) > 0 {
ret.RequestHeaderCA, err = dynamiccertificates.NewStaticCAContent("existing", []byte(caBundle))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ func TestWriteClientCAs(t *testing.T) {
clusterAuthInfo: ClusterAuthenticationInfo{
ClientCA: someRandomCAProvider,
RequestHeaderUsernameHeaders: headerrequest.StaticStringSlice{"alfa", "bravo", "charlie"},
RequestHeaderUIDHeaders: headerrequest.StaticStringSlice{"golf", "hotel", "india"},
RequestHeaderGroupHeaders: headerrequest.StaticStringSlice{"delta"},
RequestHeaderExtraHeaderPrefixes: headerrequest.StaticStringSlice{"echo", "foxtrot"},
RequestHeaderCA: anotherRandomCAProvider,
Expand All @@ -112,6 +113,7 @@ func TestWriteClientCAs(t *testing.T) {
Data: map[string]string{
"client-ca-file": string(someRandomCA),
"requestheader-username-headers": `["alfa","bravo","charlie"]`,
"requestheader-uid-headers": `["golf","hotel","india"]`,
"requestheader-group-headers": `["delta"]`,
"requestheader-extra-headers-prefix": `["echo","foxtrot"]`,
"requestheader-client-ca-file": string(anotherRandomCA),
Expand All @@ -132,6 +134,7 @@ func TestWriteClientCAs(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
Data: map[string]string{
"requestheader-username-headers": `[]`,
"requestheader-uid-headers": `[]`,
"requestheader-group-headers": `[]`,
"requestheader-extra-headers-prefix": `[]`,
"requestheader-client-ca-file": string(anotherRandomCA),
Expand Down Expand Up @@ -166,6 +169,7 @@ func TestWriteClientCAs(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
Data: map[string]string{
"requestheader-username-headers": `[]`,
"requestheader-uid-headers": `[]`,
"requestheader-group-headers": `[]`,
"requestheader-extra-headers-prefix": `[]`,
"requestheader-client-ca-file": string(anotherRandomCA),
Expand Down Expand Up @@ -201,6 +205,7 @@ func TestWriteClientCAs(t *testing.T) {
name: "overwrite extension-apiserver-authentication requestheader",
clusterAuthInfo: ClusterAuthenticationInfo{
RequestHeaderUsernameHeaders: headerrequest.StaticStringSlice{},
RequestHeaderUIDHeaders: headerrequest.StaticStringSlice{},
RequestHeaderGroupHeaders: headerrequest.StaticStringSlice{},
RequestHeaderExtraHeaderPrefixes: headerrequest.StaticStringSlice{},
RequestHeaderCA: anotherRandomCAProvider,
Expand All @@ -211,6 +216,7 @@ func TestWriteClientCAs(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
Data: map[string]string{
"requestheader-username-headers": `[]`,
"requestheader-uid-headers": `[]`,
"requestheader-group-headers": `[]`,
"requestheader-extra-headers-prefix": `[]`,
"requestheader-client-ca-file": string(someRandomCA),
Expand All @@ -223,6 +229,7 @@ func TestWriteClientCAs(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
Data: map[string]string{
"requestheader-username-headers": `[]`,
"requestheader-uid-headers": `[]`,
"requestheader-group-headers": `[]`,
"requestheader-extra-headers-prefix": `[]`,
"requestheader-client-ca-file": string(someRandomCA) + string(anotherRandomCA),
Expand Down Expand Up @@ -253,6 +260,7 @@ func TestWriteClientCAs(t *testing.T) {
name: "skip on no change",
clusterAuthInfo: ClusterAuthenticationInfo{
RequestHeaderUsernameHeaders: headerrequest.StaticStringSlice{},
RequestHeaderUIDHeaders: headerrequest.StaticStringSlice{},
RequestHeaderGroupHeaders: headerrequest.StaticStringSlice{},
RequestHeaderExtraHeaderPrefixes: headerrequest.StaticStringSlice{},
RequestHeaderCA: anotherRandomCAProvider,
Expand All @@ -263,6 +271,7 @@ func TestWriteClientCAs(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
Data: map[string]string{
"requestheader-username-headers": `[]`,
"requestheader-uid-headers": `[]`,
"requestheader-group-headers": `[]`,
"requestheader-extra-headers-prefix": `[]`,
"requestheader-client-ca-file": string(anotherRandomCA),
Expand Down Expand Up @@ -332,6 +341,7 @@ func TestWriteConfigMapDeleted(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
Data: map[string]string{
"requestheader-username-headers": `[]`,
"requestheader-uid-headers": `[]`,
"requestheader-group-headers": `[]`,
"requestheader-extra-headers-prefix": `[]`,
"requestheader-client-ca-file": string(anotherRandomCA),
Expand Down
1 change: 1 addition & 0 deletions pkg/kubeapiserver/authenticator/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ func (config Config) New(serverLifecycle context.Context) (authenticator.Request
config.RequestHeaderConfig.CAContentProvider.VerifyOptions,
config.RequestHeaderConfig.AllowedClientNames,
config.RequestHeaderConfig.UsernameHeaders,
config.RequestHeaderConfig.UIDHeaders,
config.RequestHeaderConfig.GroupHeaders,
config.RequestHeaderConfig.ExtraHeaderPrefixes,
)
Expand Down
4 changes: 4 additions & 0 deletions pkg/kubeapiserver/options/authentication_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,7 @@ func TestToAuthenticationConfig(t *testing.T) {
},
RequestHeader: &apiserveroptions.RequestHeaderAuthenticationOptions{
UsernameHeaders: []string{"x-remote-user"},
UIDHeaders: []string{"x-remote-uid"},
GroupHeaders: []string{"x-remote-group"},
ExtraHeaderPrefixes: []string{"x-remote-extra-"},
ClientCAFile: "testdata/root.pem",
Expand Down Expand Up @@ -352,6 +353,7 @@ func TestToAuthenticationConfig(t *testing.T) {

RequestHeaderConfig: &authenticatorfactory.RequestHeaderConfig{
UsernameHeaders: headerrequest.StaticStringSlice{"x-remote-user"},
UIDHeaders: headerrequest.StaticStringSlice{"x-remote-uid"},
GroupHeaders: headerrequest.StaticStringSlice{"x-remote-group"},
ExtraHeaderPrefixes: headerrequest.StaticStringSlice{"x-remote-extra-"},
CAContentProvider: nil, // this is nil because you can't compare functions
Expand Down Expand Up @@ -397,6 +399,7 @@ func TestBuiltInAuthenticationOptionsAddFlags(t *testing.T) {
"--client-ca-file=client-cacert",
"--requestheader-client-ca-file=testdata/root.pem",
"--requestheader-username-headers=x-remote-user-custom",
"--requestheader-uid-headers=x-remote-uid-custom",
"--requestheader-group-headers=x-remote-group-custom",
"--requestheader-allowed-names=kube-aggregator",
"--service-account-key-file=cert",
Expand Down Expand Up @@ -430,6 +433,7 @@ func TestBuiltInAuthenticationOptionsAddFlags(t *testing.T) {
RequestHeader: &apiserveroptions.RequestHeaderAuthenticationOptions{
ClientCAFile: "testdata/root.pem",
UsernameHeaders: []string{"x-remote-user-custom"},
UIDHeaders: []string{"x-remote-uid-custom"},
GroupHeaders: []string{"x-remote-group-custom"},
AllowedNames: []string{"kube-aggregator"},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ func (c DelegatingAuthenticatorConfig) New() (authenticator.Request, *spec.Secur
c.RequestHeaderConfig.CAContentProvider.VerifyOptions,
c.RequestHeaderConfig.AllowedClientNames,
c.RequestHeaderConfig.UsernameHeaders,
c.RequestHeaderConfig.UIDHeaders,
c.RequestHeaderConfig.GroupHeaders,
c.RequestHeaderConfig.ExtraHeaderPrefixes,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import (
type RequestHeaderConfig struct {
// UsernameHeaders are the headers to check (in order, case-insensitively) for an identity. The first header with a value wins.
UsernameHeaders headerrequest.StringSliceProvider
// UsernameHeaders are the headers to check (in order, case-insensitively) for an identity UID. The first header with a value wins.
UIDHeaders headerrequest.StringSliceProvider
// GroupHeaders are the headers to check (case-insensitively) for a group names. All values will be used.
GroupHeaders headerrequest.StringSliceProvider
// ExtraHeaderPrefixes are the head prefixes to check (case-insentively) for filling in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ type requestHeaderAuthRequestHandler struct {
// nameHeaders are the headers to check (in order, case-insensitively) for an identity. The first header with a value wins.
nameHeaders StringSliceProvider

// nameHeaders are the headers to check (in order, case-insensitively) for an identity UID. The first header with a value wins.
uidHeaders StringSliceProvider

// groupHeaders are the headers to check (case-insensitively) for group membership. All values of all headers will be added.
groupHeaders StringSliceProvider

Expand All @@ -61,11 +64,15 @@ type requestHeaderAuthRequestHandler struct {
extraHeaderPrefixes StringSliceProvider
}

func New(nameHeaders, groupHeaders, extraHeaderPrefixes []string) (authenticator.Request, error) {
func New(nameHeaders, uidHeaders, groupHeaders, extraHeaderPrefixes []string) (authenticator.Request, error) {
trimmedNameHeaders, err := trimHeaders(nameHeaders...)
if err != nil {
return nil, err
}
trimmedUIDHeaders, err := trimHeaders(uidHeaders...)
if err != nil {
return nil, err
}
trimmedGroupHeaders, err := trimHeaders(groupHeaders...)
if err != nil {
return nil, err
Expand All @@ -77,14 +84,16 @@ func New(nameHeaders, groupHeaders, extraHeaderPrefixes []string) (authenticator

return NewDynamic(
StaticStringSlice(trimmedNameHeaders),
StaticStringSlice(trimmedUIDHeaders),
StaticStringSlice(trimmedGroupHeaders),
StaticStringSlice(trimmedExtraHeaderPrefixes),
), nil
}

func NewDynamic(nameHeaders, groupHeaders, extraHeaderPrefixes StringSliceProvider) authenticator.Request {
func NewDynamic(nameHeaders, uidHeaders, groupHeaders, extraHeaderPrefixes StringSliceProvider) authenticator.Request {
return &requestHeaderAuthRequestHandler{
nameHeaders: nameHeaders,
uidHeaders: uidHeaders,
groupHeaders: groupHeaders,
extraHeaderPrefixes: extraHeaderPrefixes,
}
Expand All @@ -103,8 +112,8 @@ func trimHeaders(headerNames ...string) ([]string, error) {
return ret, nil
}

func NewDynamicVerifyOptionsSecure(verifyOptionFn x509request.VerifyOptionFunc, proxyClientNames, nameHeaders, groupHeaders, extraHeaderPrefixes StringSliceProvider) authenticator.Request {
headerAuthenticator := NewDynamic(nameHeaders, groupHeaders, extraHeaderPrefixes)
func NewDynamicVerifyOptionsSecure(verifyOptionFn x509request.VerifyOptionFunc, proxyClientNames, nameHeaders, uidHeaders, groupHeaders, extraHeaderPrefixes StringSliceProvider) authenticator.Request {
headerAuthenticator := NewDynamic(nameHeaders, uidHeaders, groupHeaders, extraHeaderPrefixes)

return x509request.NewDynamicCAVerifier(verifyOptionFn, headerAuthenticator, proxyClientNames)
}
Expand All @@ -114,25 +123,30 @@ func (a *requestHeaderAuthRequestHandler) AuthenticateRequest(req *http.Request)
if len(name) == 0 {
return nil, false, nil
}
uid := headerValue(req.Header, a.uidHeaders.Value())
groups := allHeaderValues(req.Header, a.groupHeaders.Value())
extra := newExtra(req.Header, a.extraHeaderPrefixes.Value())

// clear headers used for authentication
ClearAuthenticationHeaders(req.Header, a.nameHeaders, a.groupHeaders, a.extraHeaderPrefixes)
ClearAuthenticationHeaders(req.Header, a.nameHeaders, a.uidHeaders, a.groupHeaders, a.extraHeaderPrefixes)

return &authenticator.Response{
User: &user.DefaultInfo{
Name: name,
UID: uid,
Groups: groups,
Extra: extra,
},
}, true, nil
}

func ClearAuthenticationHeaders(h http.Header, nameHeaders, groupHeaders, extraHeaderPrefixes StringSliceProvider) {
func ClearAuthenticationHeaders(h http.Header, nameHeaders, uidHeaders, groupHeaders, extraHeaderPrefixes StringSliceProvider) {
for _, headerName := range nameHeaders.Value() {
h.Del(headerName)
}
for _, headerName := range uidHeaders.Value() {
h.Del(headerName)
}
for _, headerName := range groupHeaders.Value() {
h.Del(headerName)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ const (
// RequestHeaderAuthRequestProvider a provider that knows how to dynamically fill parts of RequestHeaderConfig struct
type RequestHeaderAuthRequestProvider interface {
UsernameHeaders() []string
UIDHeaders() []string
GroupHeaders() []string
ExtraHeaderPrefixes() []string
AllowedClientNames() []string
Expand All @@ -54,6 +55,7 @@ var _ RequestHeaderAuthRequestProvider = &RequestHeaderAuthRequestController{}

type requestHeaderBundle struct {
UsernameHeaders []string
UIDHeaders []string
GroupHeaders []string
ExtraHeaderPrefixes []string
AllowedClientNames []string
Expand All @@ -80,6 +82,7 @@ type RequestHeaderAuthRequestController struct {
exportedRequestHeaderBundle atomic.Value

usernameHeadersKey string
uidHeadersKey string
groupHeadersKey string
extraHeaderPrefixesKey string
allowedClientNamesKey string
Expand All @@ -90,7 +93,7 @@ func NewRequestHeaderAuthRequestController(
cmName string,
cmNamespace string,
client kubernetes.Interface,
usernameHeadersKey, groupHeadersKey, extraHeaderPrefixesKey, allowedClientNamesKey string) *RequestHeaderAuthRequestController {
usernameHeadersKey, uidHeadersKey, groupHeadersKey, extraHeaderPrefixesKey, allowedClientNamesKey string) *RequestHeaderAuthRequestController {
c := &RequestHeaderAuthRequestController{
name: "RequestHeaderAuthRequestController",

Expand All @@ -100,6 +103,7 @@ func NewRequestHeaderAuthRequestController(
configmapNamespace: cmNamespace,

usernameHeadersKey: usernameHeadersKey,
uidHeadersKey: uidHeadersKey,
groupHeadersKey: groupHeadersKey,
extraHeaderPrefixesKey: extraHeaderPrefixesKey,
allowedClientNamesKey: allowedClientNamesKey,
Expand Down Expand Up @@ -152,6 +156,10 @@ func (c *RequestHeaderAuthRequestController) UsernameHeaders() []string {
return c.loadRequestHeaderFor(c.usernameHeadersKey)
}

func (c *RequestHeaderAuthRequestController) UIDHeaders() []string {
return c.loadRequestHeaderFor(c.uidHeadersKey)
}

func (c *RequestHeaderAuthRequestController) GroupHeaders() []string {
return c.loadRequestHeaderFor(c.groupHeadersKey)
}
Expand Down Expand Up @@ -278,6 +286,11 @@ func (c *RequestHeaderAuthRequestController) getRequestHeaderBundleFromConfigMap
return nil, err
}

uidHeaderCurrentValue, err := deserializeStrings(cm.Data[c.uidHeadersKey])
if err != nil {
return nil, err
}

groupHeadersCurrentValue, err := deserializeStrings(cm.Data[c.groupHeadersKey])
if err != nil {
return nil, err
Expand All @@ -296,6 +309,7 @@ func (c *RequestHeaderAuthRequestController) getRequestHeaderBundleFromConfigMap

return &requestHeaderBundle{
UsernameHeaders: usernameHeaderCurrentValue,
UIDHeaders: uidHeaderCurrentValue,
GroupHeaders: groupHeadersCurrentValue,
ExtraHeaderPrefixes: extraHeaderPrefixesCurrentValue,
AllowedClientNames: allowedClientNamesCurrentValue,
Expand All @@ -312,6 +326,8 @@ func (c *RequestHeaderAuthRequestController) loadRequestHeaderFor(key string) []
switch key {
case c.usernameHeadersKey:
return headerBundle.UsernameHeaders
case c.uidHeadersKey:
return headerBundle.UIDHeaders
case c.groupHeadersKey:
return headerBundle.GroupHeaders
case c.extraHeaderPrefixesKey:
Expand Down
Loading