Skip to content

Commit e4c99d6

Browse files
committed
add MinimumKubeletVersion authorization-mode if feature is on
Signed-off-by: Peter Hunt <[email protected]>
1 parent 607e4fb commit e4c99d6

File tree

6 files changed

+171
-26
lines changed

6 files changed

+171
-26
lines changed

pkg/cmd/render/render.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"github.com/openshift/cluster-kube-apiserver-operator/bindata"
2323
"github.com/openshift/cluster-kube-apiserver-operator/pkg/operator/configobservation/apienablement"
2424
"github.com/openshift/cluster-kube-apiserver-operator/pkg/operator/configobservation/auth"
25+
"github.com/openshift/cluster-kube-apiserver-operator/pkg/operator/configobservation/node"
2526
libgoaudit "github.com/openshift/library-go/pkg/operator/apiserver/audit"
2627
"github.com/openshift/library-go/pkg/operator/configobserver/featuregates"
2728
genericrender "github.com/openshift/library-go/pkg/operator/render"
@@ -357,6 +358,12 @@ func bootstrapDefaultConfig(featureGates featuregates.FeatureGate) ([]byte, erro
357358
}
358359
}
359360

361+
if featureGates.Enabled(features.FeatureGateMinimumKubeletVersion) {
362+
if err := node.SetAPIServerArgumentsToEnforceMinimumKubeletVersion(defaultConfig, true); err != nil {
363+
return nil, err
364+
}
365+
}
366+
360367
defaultConfigRaw, err := json.Marshal(defaultConfig)
361368
if err != nil {
362369
return nil, fmt.Errorf("failed to marshal default config - %s", err)

pkg/cmd/render/render_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ func TestRenderCommand(t *testing.T) {
264264
if !ok {
265265
return fmt.Errorf("missing \"feature-gates\" entry in APIServerArguments")
266266
}
267-
expectedGates := []string{"Bar=false", "Foo=true", "OpenShiftPodSecurityAdmission=true"}
267+
expectedGates := []string{"Bar=false", "Foo=true", "OpenShiftPodSecurityAdmission=true", "MinimumKubeletVersion=true"}
268268
if len(actualGates) != len(expectedGates) {
269269
return fmt.Errorf("expected to get exactly %d feature gates but found %d: expected=%v got=%v", len(expectedGates), len(actualGates), expectedGates, actualGates)
270270
}
@@ -675,7 +675,7 @@ spec:
675675
}
676676

677677
func TestGetDefaultConfigWithAuditPolicy(t *testing.T) {
678-
raw, err := bootstrapDefaultConfig(featuregates.NewFeatureGate([]configv1.FeatureGateName{features.FeatureGateOpenShiftPodSecurityAdmission}, nil))
678+
raw, err := bootstrapDefaultConfig(featuregates.NewFeatureGate([]configv1.FeatureGateName{features.FeatureGateOpenShiftPodSecurityAdmission, features.FeatureGateMinimumKubeletVersion}, nil))
679679
require.NoError(t, err)
680680
require.True(t, len(raw) > 0)
681681

pkg/cmd/render/testdata/rendered/default-fg/featuregate.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ status:
88
enabled:
99
- name: Foo
1010
- name: OpenShiftPodSecurityAdmission
11+
- name: MinimumKubeletVersion
1112
disabled:
1213
- name: Bar

pkg/operator/configobservation/configobservercontroller/observe_config_controller.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ func NewConfigObserver(operatorClient v1helpers.StaticPodOperatorClient, kubeInf
158158
},
159159
),
160160
node.NewMinimumKubeletVersionObserver(featureGateAccessor),
161+
node.NewAuthorizationModeObserver(featureGateAccessor),
161162
proxy.NewProxyObserveFunc([]string{"targetconfigcontroller", "proxy"}),
162163
images.ObserveInternalRegistryHostname,
163164
images.ObserveExternalRegistryHostnames,

pkg/operator/configobservation/node/observe_minimum_kubelet_version.go

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package node
22

33
import (
4+
"sort"
5+
46
configv1 "github.com/openshift/api/config/v1"
57
"github.com/openshift/api/features"
68
"github.com/openshift/library-go/pkg/operator/configobserver"
@@ -11,7 +13,16 @@ import (
1113
"k8s.io/klog/v2"
1214
)
1315

14-
var minimumKubeletVersionConfigPath = "minimumKubeletVersion"
16+
var (
17+
ModeMinimumKubeletVersion = "MinimumKubeletVersion"
18+
minimumKubeletVersionConfigPath = "minimumKubeletVersion"
19+
authModeFlag = "authorization-mode"
20+
apiServerArgs = "apiServerArguments"
21+
authModePath = []string{apiServerArgs, authModeFlag}
22+
// The default value for apiServerArguments.authorization-mode.
23+
// Should be synced with bindata/assets/config/defaultconfig.yaml
24+
DefaultAuthorizationModes = []string{"Scope", "SystemMasters", "RBAC", "Node"}
25+
)
1526

1627
type minimumKubeletVersionObserver struct {
1728
featureGateAccessor featuregates.FeatureGateAccess
@@ -31,7 +42,6 @@ func (o *minimumKubeletVersionObserver) ObserveMinimumKubeletVersion(genericList
3142
}()
3243

3344
if !o.featureGateAccessor.AreInitialFeatureGatesObserved() {
34-
// if we haven't observed featuregates yet, return the existing
3545
return existingConfig, nil
3646
}
3747

@@ -63,7 +73,7 @@ func (o *minimumKubeletVersionObserver) ObserveMinimumKubeletVersion(genericList
6373
// return empty set of configs, this helps to unset the config
6474
// values related to the minimumKubeletVersion.
6575
// Also, ensures that this observer doesn't break cluster upgrades/downgrades
66-
return map[string]interface{}{}, errs
76+
return ret, errs
6777
}
6878

6979
if err := unstructured.SetNestedField(ret, configNode.Spec.MinimumKubeletVersion, minimumKubeletVersionConfigPath); err != nil {
@@ -72,3 +82,51 @@ func (o *minimumKubeletVersionObserver) ObserveMinimumKubeletVersion(genericList
7282

7383
return ret, errs
7484
}
85+
86+
type authorizationModeObserver struct {
87+
featureGateAccessor featuregates.FeatureGateAccess
88+
}
89+
90+
func NewAuthorizationModeObserver(featureGateAccessor featuregates.FeatureGateAccess) configobserver.ObserveConfigFunc {
91+
return (&authorizationModeObserver{
92+
featureGateAccessor: featureGateAccessor,
93+
}).ObserveAuthorizationMode
94+
}
95+
96+
// ObserveAuthorizationMode watches the featuregate configuration and generates the apiServerArguments.authorization-mode
97+
// It currently hardcodes the default set and adds MinimumKubeletVersion if the feature is set to on.
98+
func (o *authorizationModeObserver) ObserveAuthorizationMode(genericListers configobserver.Listers, _ events.Recorder, existingConfig map[string]interface{}) (ret map[string]interface{}, errs []error) {
99+
ret = map[string]interface{}{}
100+
if !o.featureGateAccessor.AreInitialFeatureGatesObserved() {
101+
return existingConfig, nil
102+
}
103+
104+
featureGates, err := o.featureGateAccessor.CurrentFeatureGates()
105+
if err != nil {
106+
return existingConfig, append(errs, err)
107+
}
108+
109+
defer func() {
110+
// Prune the observed config so that it only contains minimumKubeletVersion field.
111+
ret = configobserver.Pruned(ret, authModePath)
112+
}()
113+
114+
if err := SetAPIServerArgumentsToEnforceMinimumKubeletVersion(ret, featureGates.Enabled(features.FeatureGateMinimumKubeletVersion)); err != nil {
115+
return existingConfig, append(errs, err)
116+
}
117+
return ret, nil
118+
}
119+
120+
// SetAPIServerArgumentsToEnforceMinimumKubeletVersion modifies the passed in config
121+
// to add the "authorization-mode": "MinimumKubeletVersion" if the feature is on. If it's off, it
122+
// removes it instead.
123+
func SetAPIServerArgumentsToEnforceMinimumKubeletVersion(newConfig map[string]interface{}, on bool) error {
124+
defaultSet := DefaultAuthorizationModes
125+
if on {
126+
defaultSet = append(defaultSet, ModeMinimumKubeletVersion)
127+
}
128+
sort.Sort(sort.StringSlice(defaultSet))
129+
130+
unstructured.RemoveNestedField(newConfig, authModePath...)
131+
return unstructured.SetNestedStringSlice(newConfig, defaultSet, authModePath...)
132+
}

pkg/operator/configobservation/node/observe_minimum_kubelet_version_test.go

Lines changed: 99 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17,48 +17,48 @@ import (
1717

1818
func TestObserveKubeletMinimumVersion(t *testing.T) {
1919
type Test struct {
20-
name string
21-
existingConfig map[string]interface{}
22-
expectedObservedConfig map[string]interface{}
23-
minimumKubeletVersion string
24-
featureOn bool
20+
name string
21+
existingConfig map[string]interface{}
22+
expectedConfig map[string]interface{}
23+
minimumKubeletVersion string
24+
featureOn bool
2525
}
2626
tests := []Test{
2727
{
28-
name: "feature off",
29-
existingConfig: map[string]interface{}{},
30-
expectedObservedConfig: map[string]interface{}{},
31-
minimumKubeletVersion: "1.30.0",
32-
featureOn: false,
28+
name: "feature off",
29+
existingConfig: map[string]interface{}{},
30+
expectedConfig: map[string]interface{}{},
31+
minimumKubeletVersion: "1.30.0",
32+
featureOn: false,
3333
},
3434
{
35-
name: "empty minimumKubeletVersion",
36-
expectedObservedConfig: map[string]interface{}{},
37-
minimumKubeletVersion: "",
38-
featureOn: true,
35+
name: "empty minimumKubeletVersion",
36+
expectedConfig: map[string]interface{}{},
37+
minimumKubeletVersion: "",
38+
featureOn: true,
3939
},
4040
{
4141
name: "set minimumKubeletVersion",
42-
expectedObservedConfig: map[string]interface{}{
42+
expectedConfig: map[string]interface{}{
4343
"minimumKubeletVersion": string("1.30.0"),
4444
},
4545
minimumKubeletVersion: "1.30.0",
4646
featureOn: true,
4747
},
4848
{
4949
name: "existing minimumKubeletVersion",
50-
expectedObservedConfig: map[string]interface{}{
51-
"minimumKubeletVersion": string("1.30.0"),
52-
},
5350
existingConfig: map[string]interface{}{
5451
"minimumKubeletVersion": string("1.29.0"),
5552
},
53+
expectedConfig: map[string]interface{}{
54+
"minimumKubeletVersion": string("1.30.0"),
55+
},
5656
minimumKubeletVersion: "1.30.0",
5757
featureOn: true,
5858
},
5959
{
60-
name: "existing minimumKubeletVersion unset",
61-
expectedObservedConfig: map[string]interface{}{},
60+
name: "existing minimumKubeletVersion unset",
61+
expectedConfig: map[string]any{},
6262
existingConfig: map[string]interface{}{
6363
"minimumKubeletVersion": string("1.29.0"),
6464
},
@@ -91,9 +91,87 @@ func TestObserveKubeletMinimumVersion(t *testing.T) {
9191
if len(errs) > 0 {
9292
t.Fatal(errs)
9393
}
94-
if diff := cmp.Diff(test.expectedObservedConfig, actualObservedConfig); diff != "" {
94+
if diff := cmp.Diff(test.expectedConfig, actualObservedConfig); diff != "" {
9595
t.Fatalf("unexpected configuration, diff = %v", diff)
9696
}
9797
})
9898
}
9999
}
100+
101+
func TestSetAPIServerArgumentsToEnforceMinimumKubeletVersion(t *testing.T) {
102+
for _, on := range []bool{false, true} {
103+
expectedSet := []any{"Node", "RBAC", "Scope", "SystemMasters"}
104+
if on {
105+
expectedSet = append([]any{ModeMinimumKubeletVersion}, expectedSet...)
106+
}
107+
for _, tc := range []struct {
108+
name string
109+
existingConfig map[string]interface{}
110+
expectedConfig map[string]interface{}
111+
}{
112+
{
113+
name: "should not fail if apiServerArguments not present",
114+
existingConfig: map[string]interface{}{
115+
"fakeconfig": "fake",
116+
},
117+
expectedConfig: map[string]interface{}{
118+
"fakeconfig": "fake",
119+
"apiServerArguments": map[string]any{"authorization-mode": expectedSet},
120+
},
121+
},
122+
{
123+
name: "should not fail if authorization-mode not present",
124+
existingConfig: map[string]interface{}{
125+
"apiServerArguments": map[string]any{"fake": []any{"fake"}},
126+
},
127+
expectedConfig: map[string]interface{}{
128+
"apiServerArguments": map[string]any{"fake": []any{"fake"}, "authorization-mode": expectedSet},
129+
},
130+
},
131+
{
132+
name: "should clobber value if not expected",
133+
existingConfig: map[string]interface{}{
134+
"apiServerArguments": map[string]any{"authorization-mode": []any{"fake"}},
135+
},
136+
expectedConfig: map[string]interface{}{
137+
"apiServerArguments": map[string]any{"authorization-mode": expectedSet},
138+
},
139+
},
140+
{
141+
name: "should not fail if MinimumKubeletVersion already present",
142+
existingConfig: map[string]interface{}{
143+
"apiServerArguments": map[string]any{"authorization-mode": []any{"MinimumKubeletVersion"}},
144+
},
145+
expectedConfig: map[string]interface{}{
146+
"apiServerArguments": map[string]any{"authorization-mode": expectedSet},
147+
},
148+
},
149+
{
150+
name: "should not fail if apiServerArguments not present",
151+
existingConfig: map[string]interface{}{
152+
"fakeconfig": "fake",
153+
},
154+
expectedConfig: map[string]interface{}{
155+
"fakeconfig": "fake",
156+
"apiServerArguments": map[string]any{"authorization-mode": expectedSet},
157+
},
158+
},
159+
} {
160+
name := tc.name + " when feature is "
161+
if on {
162+
name += "on"
163+
} else {
164+
name += "off"
165+
}
166+
t.Run(name, func(t *testing.T) {
167+
if err := SetAPIServerArgumentsToEnforceMinimumKubeletVersion(tc.existingConfig, on); err != nil {
168+
t.Fatal(err)
169+
}
170+
171+
if diff := cmp.Diff(tc.expectedConfig, tc.existingConfig); diff != "" {
172+
t.Errorf("unexpected config:\n%s", diff)
173+
}
174+
})
175+
}
176+
}
177+
}

0 commit comments

Comments
 (0)