Skip to content

Commit 1504f10

Browse files
authored
Merge pull request kubernetes#129081 from stlaz/fg_remote_uid
featuregate UID in RequestHeader authenticator
2 parents 6fc64a2 + 779d761 commit 1504f10

File tree

13 files changed

+345
-65
lines changed

13 files changed

+345
-65
lines changed

pkg/controlplane/controller/clusterauthenticationtrust/cluster_authentication_trust_controller.go

+14-6
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@ import (
3535
"k8s.io/apimachinery/pkg/util/sets"
3636
"k8s.io/apimachinery/pkg/util/wait"
3737
"k8s.io/apiserver/pkg/authentication/request/headerrequest"
38+
"k8s.io/apiserver/pkg/features"
3839
"k8s.io/apiserver/pkg/server/dynamiccertificates"
40+
utilfeature "k8s.io/apiserver/pkg/util/feature"
3941
corev1informers "k8s.io/client-go/informers/core/v1"
4042
"k8s.io/client-go/kubernetes"
4143
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
@@ -262,10 +264,13 @@ func getConfigMapDataFor(authenticationInfo ClusterAuthenticationInfo) (map[stri
262264
if err != nil {
263265
return nil, err
264266
}
265-
data["requestheader-uid-headers"], err = jsonSerializeStringSlice(authenticationInfo.RequestHeaderUIDHeaders.Value())
266-
if err != nil {
267-
return nil, err
267+
if utilfeature.DefaultFeatureGate.Enabled(features.RemoteRequestHeaderUID) && len(authenticationInfo.RequestHeaderUIDHeaders.Value()) > 0 {
268+
data["requestheader-uid-headers"], err = jsonSerializeStringSlice(authenticationInfo.RequestHeaderUIDHeaders.Value())
269+
if err != nil {
270+
return nil, err
271+
}
268272
}
273+
269274
data["requestheader-group-headers"], err = jsonSerializeStringSlice(authenticationInfo.RequestHeaderGroupHeaders.Value())
270275
if err != nil {
271276
return nil, err
@@ -305,9 +310,12 @@ func getClusterAuthenticationInfoFor(data map[string]string) (ClusterAuthenticat
305310
if err != nil {
306311
return ClusterAuthenticationInfo{}, err
307312
}
308-
ret.RequestHeaderUIDHeaders, err = jsonDeserializeStringSlice(data["requestheader-uid-headers"])
309-
if err != nil {
310-
return ClusterAuthenticationInfo{}, err
313+
314+
if utilfeature.DefaultFeatureGate.Enabled(features.RemoteRequestHeaderUID) {
315+
ret.RequestHeaderUIDHeaders, err = jsonDeserializeStringSlice(data["requestheader-uid-headers"])
316+
if err != nil {
317+
return ClusterAuthenticationInfo{}, err
318+
}
311319
}
312320

313321
if caBundle := data["requestheader-client-ca-file"]; len(caBundle) > 0 {

pkg/controlplane/controller/clusterauthenticationtrust/cluster_authentication_trust_controller_test.go

+147-8
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,14 @@ import (
3030
"k8s.io/apimachinery/pkg/util/dump"
3131
"k8s.io/apimachinery/pkg/util/validation/field"
3232
"k8s.io/apiserver/pkg/authentication/request/headerrequest"
33+
"k8s.io/apiserver/pkg/features"
3334
"k8s.io/apiserver/pkg/server/dynamiccertificates"
35+
utilfeature "k8s.io/apiserver/pkg/util/feature"
3436
"k8s.io/client-go/kubernetes/fake"
3537
corev1listers "k8s.io/client-go/listers/core/v1"
3638
clienttesting "k8s.io/client-go/testing"
3739
"k8s.io/client-go/tools/cache"
40+
featuregatetesting "k8s.io/component-base/featuregate/testing"
3841
)
3942

4043
var (
@@ -95,6 +98,7 @@ func TestWriteClientCAs(t *testing.T) {
9598
preexistingObjs []runtime.Object
9699
expectedConfigMaps map[string]*corev1.ConfigMap
97100
expectCreate bool
101+
uidGate bool
98102
}{
99103
{
100104
name: "basic",
@@ -107,6 +111,32 @@ func TestWriteClientCAs(t *testing.T) {
107111
RequestHeaderCA: anotherRandomCAProvider,
108112
RequestHeaderAllowedNames: headerrequest.StaticStringSlice{"first", "second"},
109113
},
114+
expectedConfigMaps: map[string]*corev1.ConfigMap{
115+
"extension-apiserver-authentication": {
116+
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
117+
Data: map[string]string{
118+
"client-ca-file": string(someRandomCA),
119+
"requestheader-username-headers": `["alfa","bravo","charlie"]`,
120+
"requestheader-group-headers": `["delta"]`,
121+
"requestheader-extra-headers-prefix": `["echo","foxtrot"]`,
122+
"requestheader-client-ca-file": string(anotherRandomCA),
123+
"requestheader-allowed-names": `["first","second"]`,
124+
},
125+
},
126+
},
127+
expectCreate: true,
128+
},
129+
{
130+
name: "basic with feature gate",
131+
clusterAuthInfo: ClusterAuthenticationInfo{
132+
ClientCA: someRandomCAProvider,
133+
RequestHeaderUsernameHeaders: headerrequest.StaticStringSlice{"alfa", "bravo", "charlie"},
134+
RequestHeaderUIDHeaders: headerrequest.StaticStringSlice{"golf", "hotel", "india"},
135+
RequestHeaderGroupHeaders: headerrequest.StaticStringSlice{"delta"},
136+
RequestHeaderExtraHeaderPrefixes: headerrequest.StaticStringSlice{"echo", "foxtrot"},
137+
RequestHeaderCA: anotherRandomCAProvider,
138+
RequestHeaderAllowedNames: headerrequest.StaticStringSlice{"first", "second"},
139+
},
110140
expectedConfigMaps: map[string]*corev1.ConfigMap{
111141
"extension-apiserver-authentication": {
112142
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
@@ -122,6 +152,7 @@ func TestWriteClientCAs(t *testing.T) {
122152
},
123153
},
124154
expectCreate: true,
155+
uidGate: true,
125156
},
126157
{
127158
name: "skip extension-apiserver-authentication",
@@ -134,7 +165,6 @@ func TestWriteClientCAs(t *testing.T) {
134165
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
135166
Data: map[string]string{
136167
"requestheader-username-headers": `[]`,
137-
"requestheader-uid-headers": `[]`,
138168
"requestheader-group-headers": `[]`,
139169
"requestheader-extra-headers-prefix": `[]`,
140170
"requestheader-client-ca-file": string(anotherRandomCA),
@@ -169,7 +199,6 @@ func TestWriteClientCAs(t *testing.T) {
169199
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
170200
Data: map[string]string{
171201
"requestheader-username-headers": `[]`,
172-
"requestheader-uid-headers": `[]`,
173202
"requestheader-group-headers": `[]`,
174203
"requestheader-extra-headers-prefix": `[]`,
175204
"requestheader-client-ca-file": string(anotherRandomCA),
@@ -205,7 +234,6 @@ func TestWriteClientCAs(t *testing.T) {
205234
name: "overwrite extension-apiserver-authentication requestheader",
206235
clusterAuthInfo: ClusterAuthenticationInfo{
207236
RequestHeaderUsernameHeaders: headerrequest.StaticStringSlice{},
208-
RequestHeaderUIDHeaders: headerrequest.StaticStringSlice{},
209237
RequestHeaderGroupHeaders: headerrequest.StaticStringSlice{},
210238
RequestHeaderExtraHeaderPrefixes: headerrequest.StaticStringSlice{},
211239
RequestHeaderCA: anotherRandomCAProvider,
@@ -216,7 +244,6 @@ func TestWriteClientCAs(t *testing.T) {
216244
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
217245
Data: map[string]string{
218246
"requestheader-username-headers": `[]`,
219-
"requestheader-uid-headers": `[]`,
220247
"requestheader-group-headers": `[]`,
221248
"requestheader-extra-headers-prefix": `[]`,
222249
"requestheader-client-ca-file": string(someRandomCA),
@@ -229,7 +256,6 @@ func TestWriteClientCAs(t *testing.T) {
229256
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
230257
Data: map[string]string{
231258
"requestheader-username-headers": `[]`,
232-
"requestheader-uid-headers": `[]`,
233259
"requestheader-group-headers": `[]`,
234260
"requestheader-extra-headers-prefix": `[]`,
235261
"requestheader-client-ca-file": string(someRandomCA) + string(anotherRandomCA),
@@ -260,7 +286,6 @@ func TestWriteClientCAs(t *testing.T) {
260286
name: "skip on no change",
261287
clusterAuthInfo: ClusterAuthenticationInfo{
262288
RequestHeaderUsernameHeaders: headerrequest.StaticStringSlice{},
263-
RequestHeaderUIDHeaders: headerrequest.StaticStringSlice{},
264289
RequestHeaderGroupHeaders: headerrequest.StaticStringSlice{},
265290
RequestHeaderExtraHeaderPrefixes: headerrequest.StaticStringSlice{},
266291
RequestHeaderCA: anotherRandomCAProvider,
@@ -271,7 +296,6 @@ func TestWriteClientCAs(t *testing.T) {
271296
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
272297
Data: map[string]string{
273298
"requestheader-username-headers": `[]`,
274-
"requestheader-uid-headers": `[]`,
275299
"requestheader-group-headers": `[]`,
276300
"requestheader-extra-headers-prefix": `[]`,
277301
"requestheader-client-ca-file": string(anotherRandomCA),
@@ -282,10 +306,126 @@ func TestWriteClientCAs(t *testing.T) {
282306
expectedConfigMaps: map[string]*corev1.ConfigMap{},
283307
expectCreate: false,
284308
},
309+
{
310+
name: "drop uid without feature gate",
311+
clusterAuthInfo: ClusterAuthenticationInfo{
312+
RequestHeaderUsernameHeaders: headerrequest.StaticStringSlice{},
313+
RequestHeaderUIDHeaders: headerrequest.StaticStringSlice{"panda"},
314+
RequestHeaderGroupHeaders: headerrequest.StaticStringSlice{},
315+
RequestHeaderExtraHeaderPrefixes: headerrequest.StaticStringSlice{},
316+
RequestHeaderCA: anotherRandomCAProvider,
317+
RequestHeaderAllowedNames: headerrequest.StaticStringSlice{},
318+
},
319+
preexistingObjs: []runtime.Object{
320+
&corev1.ConfigMap{
321+
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
322+
Data: map[string]string{
323+
"requestheader-username-headers": `[]`,
324+
"requestheader-uid-headers": `["snorlax"]`,
325+
"requestheader-group-headers": `[]`,
326+
"requestheader-extra-headers-prefix": `[]`,
327+
"requestheader-client-ca-file": string(anotherRandomCA),
328+
"requestheader-allowed-names": `[]`,
329+
},
330+
},
331+
},
332+
expectedConfigMaps: map[string]*corev1.ConfigMap{
333+
"extension-apiserver-authentication": {
334+
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
335+
Data: map[string]string{
336+
"requestheader-username-headers": `[]`,
337+
"requestheader-group-headers": `[]`,
338+
"requestheader-extra-headers-prefix": `[]`,
339+
"requestheader-client-ca-file": string(anotherRandomCA),
340+
"requestheader-allowed-names": `[]`,
341+
},
342+
},
343+
},
344+
expectCreate: false,
345+
},
346+
{
347+
name: "add uid with feature gate",
348+
clusterAuthInfo: ClusterAuthenticationInfo{
349+
RequestHeaderUsernameHeaders: headerrequest.StaticStringSlice{},
350+
RequestHeaderUIDHeaders: headerrequest.StaticStringSlice{"panda"},
351+
RequestHeaderGroupHeaders: headerrequest.StaticStringSlice{},
352+
RequestHeaderExtraHeaderPrefixes: headerrequest.StaticStringSlice{},
353+
RequestHeaderCA: anotherRandomCAProvider,
354+
RequestHeaderAllowedNames: headerrequest.StaticStringSlice{},
355+
},
356+
preexistingObjs: []runtime.Object{
357+
&corev1.ConfigMap{
358+
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
359+
Data: map[string]string{
360+
"requestheader-username-headers": `[]`,
361+
"requestheader-group-headers": `[]`,
362+
"requestheader-extra-headers-prefix": `[]`,
363+
"requestheader-client-ca-file": string(anotherRandomCA),
364+
"requestheader-allowed-names": `[]`,
365+
},
366+
},
367+
},
368+
expectedConfigMaps: map[string]*corev1.ConfigMap{
369+
"extension-apiserver-authentication": {
370+
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
371+
Data: map[string]string{
372+
"requestheader-username-headers": `[]`,
373+
"requestheader-uid-headers": `["panda"]`,
374+
"requestheader-group-headers": `[]`,
375+
"requestheader-extra-headers-prefix": `[]`,
376+
"requestheader-client-ca-file": string(anotherRandomCA),
377+
"requestheader-allowed-names": `[]`,
378+
},
379+
},
380+
},
381+
expectCreate: false,
382+
uidGate: true,
383+
},
384+
{
385+
name: "append uid with feature gate",
386+
clusterAuthInfo: ClusterAuthenticationInfo{
387+
RequestHeaderUsernameHeaders: headerrequest.StaticStringSlice{},
388+
RequestHeaderUIDHeaders: headerrequest.StaticStringSlice{"panda"},
389+
RequestHeaderGroupHeaders: headerrequest.StaticStringSlice{},
390+
RequestHeaderExtraHeaderPrefixes: headerrequest.StaticStringSlice{},
391+
RequestHeaderCA: anotherRandomCAProvider,
392+
RequestHeaderAllowedNames: headerrequest.StaticStringSlice{},
393+
},
394+
preexistingObjs: []runtime.Object{
395+
&corev1.ConfigMap{
396+
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
397+
Data: map[string]string{
398+
"requestheader-username-headers": `[]`,
399+
"requestheader-uid-headers": `["snorlax"]`,
400+
"requestheader-group-headers": `[]`,
401+
"requestheader-extra-headers-prefix": `[]`,
402+
"requestheader-client-ca-file": string(anotherRandomCA),
403+
"requestheader-allowed-names": `[]`,
404+
},
405+
},
406+
},
407+
expectedConfigMaps: map[string]*corev1.ConfigMap{
408+
"extension-apiserver-authentication": {
409+
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
410+
Data: map[string]string{
411+
"requestheader-username-headers": `[]`,
412+
"requestheader-uid-headers": `["snorlax","panda"]`,
413+
"requestheader-group-headers": `[]`,
414+
"requestheader-extra-headers-prefix": `[]`,
415+
"requestheader-client-ca-file": string(anotherRandomCA),
416+
"requestheader-allowed-names": `[]`,
417+
},
418+
},
419+
},
420+
expectCreate: false,
421+
uidGate: true,
422+
},
285423
}
286424

287425
for _, test := range tests {
288426
t.Run(test.name, func(t *testing.T) {
427+
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.RemoteRequestHeaderUID, test.uidGate)
428+
289429
client := fake.NewSimpleClientset(test.preexistingObjs...)
290430
configMapIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc})
291431
for _, obj := range test.preexistingObjs {
@@ -341,7 +481,6 @@ func TestWriteConfigMapDeleted(t *testing.T) {
341481
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
342482
Data: map[string]string{
343483
"requestheader-username-headers": `[]`,
344-
"requestheader-uid-headers": `[]`,
345484
"requestheader-group-headers": `[]`,
346485
"requestheader-extra-headers-prefix": `[]`,
347486
"requestheader-client-ca-file": string(anotherRandomCA),

pkg/features/versioned_kube_features.go

+4
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,10 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate
306306
{Version: version.MustParse("1.29"), Default: true, PreRelease: featuregate.GA, LockToDefault: true},
307307
},
308308

309+
genericfeatures.RemoteRequestHeaderUID: {
310+
{Version: version.MustParse("1.32"), Default: false, PreRelease: featuregate.Alpha},
311+
},
312+
309313
genericfeatures.ResilientWatchCacheInitialization: {
310314
{Version: version.MustParse("1.31"), Default: true, PreRelease: featuregate.Beta},
311315
},

staging/src/k8s.io/apiserver/pkg/features/kube_features.go

+11
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,13 @@ const (
149149
// to a chunking list request.
150150
RemainingItemCount featuregate.Feature = "RemainingItemCount"
151151

152+
// owner: @stlaz
153+
//
154+
// Enable kube-apiserver to accept UIDs via request header authentication.
155+
// This will also make the kube-apiserver's API aggregator add UIDs via standard
156+
// headers when forwarding requests to the servers serving the aggregated API.
157+
RemoteRequestHeaderUID featuregate.Feature = "RemoteRequestHeaderUID"
158+
152159
// owner: @wojtek-t
153160
//
154161
// Enables resilient watchcache initialization to avoid controlplane
@@ -359,6 +366,10 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate
359366
{Version: version.MustParse("1.29"), Default: true, PreRelease: featuregate.GA, LockToDefault: true},
360367
},
361368

369+
RemoteRequestHeaderUID: {
370+
{Version: version.MustParse("1.32"), Default: false, PreRelease: featuregate.Alpha},
371+
},
372+
362373
ResilientWatchCacheInitialization: {
363374
{Version: version.MustParse("1.31"), Default: true, PreRelease: featuregate.Beta},
364375
},

staging/src/k8s.io/apiserver/pkg/server/options/authentication.go

+17-8
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,10 @@ import (
2929
"k8s.io/apiserver/pkg/apis/apiserver"
3030
"k8s.io/apiserver/pkg/authentication/authenticatorfactory"
3131
"k8s.io/apiserver/pkg/authentication/request/headerrequest"
32+
"k8s.io/apiserver/pkg/features"
3233
"k8s.io/apiserver/pkg/server"
3334
"k8s.io/apiserver/pkg/server/dynamiccertificates"
35+
utilfeature "k8s.io/apiserver/pkg/util/feature"
3436
"k8s.io/client-go/kubernetes"
3537
"k8s.io/client-go/rest"
3638
"k8s.io/client-go/tools/clientcmd"
@@ -68,9 +70,6 @@ func (s *RequestHeaderAuthenticationOptions) Validate() []error {
6870
if err := checkForWhiteSpaceOnly("requestheader-username-headers", s.UsernameHeaders...); err != nil {
6971
allErrors = append(allErrors, err)
7072
}
71-
if err := checkForWhiteSpaceOnly("requestheader-uid-headers", s.UIDHeaders...); err != nil {
72-
allErrors = append(allErrors, err)
73-
}
7473
if err := checkForWhiteSpaceOnly("requestheader-group-headers", s.GroupHeaders...); err != nil {
7574
allErrors = append(allErrors, err)
7675
}
@@ -84,17 +83,27 @@ func (s *RequestHeaderAuthenticationOptions) Validate() []error {
8483
if len(s.UsernameHeaders) > 0 && !caseInsensitiveHas(s.UsernameHeaders, "X-Remote-User") {
8584
klog.Warningf("--requestheader-username-headers is set without specifying the standard X-Remote-User header - API aggregation will not work")
8685
}
87-
if len(s.UIDHeaders) > 0 && !caseInsensitiveHas(s.UIDHeaders, "X-Remote-Uid") {
88-
// this was added later and so we are able to error out
89-
allErrors = append(allErrors, fmt.Errorf("--requestheader-uid-headers is set without specifying the standard X-Remote-Uid header - API aggregation will not work"))
90-
}
9186
if len(s.GroupHeaders) > 0 && !caseInsensitiveHas(s.GroupHeaders, "X-Remote-Group") {
9287
klog.Warningf("--requestheader-group-headers is set without specifying the standard X-Remote-Group header - API aggregation will not work")
9388
}
9489
if len(s.ExtraHeaderPrefixes) > 0 && !caseInsensitiveHas(s.ExtraHeaderPrefixes, "X-Remote-Extra-") {
9590
klog.Warningf("--requestheader-extra-headers-prefix is set without specifying the standard X-Remote-Extra- header prefix - API aggregation will not work")
9691
}
9792

93+
if !utilfeature.DefaultFeatureGate.Enabled(features.RemoteRequestHeaderUID) {
94+
if len(s.UIDHeaders) > 0 {
95+
allErrors = append(allErrors, fmt.Errorf("--requestheader-uid-headers requires the %q feature to be enabled", features.RemoteRequestHeaderUID))
96+
}
97+
} else {
98+
if err := checkForWhiteSpaceOnly("requestheader-uid-headers", s.UIDHeaders...); err != nil {
99+
allErrors = append(allErrors, err)
100+
}
101+
if len(s.UIDHeaders) > 0 && !caseInsensitiveHas(s.UIDHeaders, "X-Remote-Uid") {
102+
// this was added later and so we are able to error out
103+
allErrors = append(allErrors, fmt.Errorf("--requestheader-uid-headers is set without specifying the standard X-Remote-Uid header - API aggregation will not work"))
104+
}
105+
}
106+
98107
return allErrors
99108
}
100109

@@ -126,7 +135,7 @@ func (s *RequestHeaderAuthenticationOptions) AddFlags(fs *pflag.FlagSet) {
126135
"List of request headers to inspect for usernames. X-Remote-User is common.")
127136

128137
fs.StringSliceVar(&s.UIDHeaders, "requestheader-uid-headers", s.UIDHeaders, ""+
129-
"List of request headers to inspect for UIDs. X-Remote-Uid is suggested.")
138+
"List of request headers to inspect for UIDs. X-Remote-Uid is suggested. Requires the RemoteRequestHeaderUID feature to be enabled.")
130139

131140
fs.StringSliceVar(&s.GroupHeaders, "requestheader-group-headers", s.GroupHeaders, ""+
132141
"List of request headers to inspect for groups. X-Remote-Group is suggested.")

0 commit comments

Comments
 (0)