diff --git a/docs.md b/docs.md index b92e141db3..1b910f1fae 100644 --- a/docs.md +++ b/docs.md @@ -710,6 +710,8 @@ When settings are created or updated, the following common checks take place: - The `auth-user-session-idle-ttl-minutes` must be a positive integer and can't be greater than `auth-user-session-ttl-minutes`. - The `auth-user-info-max-age-seconds` must be a valid duration value. - The `auth-user-info-resync-cron` must be a valid cron expression. +- If set, `crt-default-ttl-minutes` must be a valid integer no less than `30` and greater than `crt-default-grace-period-minutes` if that value is set. +- If set, `crt-default-grace-period-minutes` must be a valid integer no less than `10` and less than `crt-default-ttl-minutes` if that value is set. #### Update @@ -717,7 +719,7 @@ When settings are updated, the following additional checks take place: - Settings with a source of `env` may not be updated -- Read only settings like `cacerts` must not be updated. +- Read only settings like `cacerts` must not be updated, unless the request is generated by the Rancher service account. - The new value for the setting must not be "" diff --git a/pkg/resources/management.cattle.io/v3/setting/Setting.md b/pkg/resources/management.cattle.io/v3/setting/Setting.md index a988ed6a8a..cc0bf94d45 100644 --- a/pkg/resources/management.cattle.io/v3/setting/Setting.md +++ b/pkg/resources/management.cattle.io/v3/setting/Setting.md @@ -21,7 +21,7 @@ When settings are updated, the following additional checks take place: - Settings with a source of `env` may not be updated -- Read only settings like `cacerts` must not be updated. +- Read only settings like `cacerts` must not be updated, unless the request is generated by the Rancher service account. - The new value for the setting must not be "" diff --git a/pkg/resources/management.cattle.io/v3/setting/validator.go b/pkg/resources/management.cattle.io/v3/setting/validator.go index 3425f7fdbf..6e7c37beda 100644 --- a/pkg/resources/management.cattle.io/v3/setting/validator.go +++ b/pkg/resources/management.cattle.io/v3/setting/validator.go @@ -117,7 +117,7 @@ func (a *admitter) Admit(request *admission.Request) (*admissionv1.AdmissionResp case admissionv1.Create: return a.admitCreate(newSetting) case admissionv1.Update: - return a.admitUpdate(oldSetting, newSetting) + return a.admitUpdate(oldSetting, newSetting, request.UserInfo.Username) default: return admission.ResponseAllowed(), nil } @@ -127,16 +127,17 @@ func (a *admitter) admitCreate(newSetting *v3.Setting) (*admissionv1.AdmissionRe return a.admitCommonCreateUpdate(nil, newSetting) } +var bypassServiceAccount = "system:serviceaccount:cattle-system:rancher" var ReadOnlySettings = []string{ "cacerts", } -func (a *admitter) admitUpdate(oldSetting, newSetting *v3.Setting) (*admissionv1.AdmissionResponse, error) { +func (a *admitter) admitUpdate(oldSetting, newSetting *v3.Setting, username string) (*admissionv1.AdmissionResponse, error) { if oldSetting.Source == "env" && newSetting.Source != "env" { return admission.ResponseBadRequest(fmt.Sprintf("setting with source \"%s\" cannot update setting with source \"env\"", newSetting.Source)), nil } - if slices.Contains(ReadOnlySettings, oldSetting.Name) { + if slices.Contains(ReadOnlySettings, oldSetting.Name) && username != bypassServiceAccount { return admission.ResponseBadRequest("setting is read only"), nil } diff --git a/pkg/resources/management.cattle.io/v3/setting/validator_test.go b/pkg/resources/management.cattle.io/v3/setting/validator_test.go index 7e138ea990..7966d1efad 100644 --- a/pkg/resources/management.cattle.io/v3/setting/validator_test.go +++ b/pkg/resources/management.cattle.io/v3/setting/validator_test.go @@ -42,6 +42,7 @@ func (s *SettingSuite) TestAdmitUpdateGuards() { name string oldSetting *v3.Setting newSetting *v3.Setting + username string allowed bool }{ { @@ -78,6 +79,18 @@ func (s *SettingSuite) TestAdmitUpdateGuards() { Value: "new-certs", }, }, + { + name: "allow read-only setting frombypass service account", + oldSetting: &v3.Setting{ + ObjectMeta: metav1.ObjectMeta{Name: "cacerts"}, + }, + newSetting: &v3.Setting{ + ObjectMeta: metav1.ObjectMeta{Name: "cacerts"}, + Value: "new-certs", + }, + username: "system:serviceaccount:cattle-system:rancher", + allowed: true, + }, { name: "allow normal valid update", oldSetting: &v3.Setting{ @@ -97,7 +110,7 @@ func (s *SettingSuite) TestAdmitUpdateGuards() { t.Parallel() validator := setting.NewValidator(nil, nil) - s.testAdmit(t, validator, test.oldSetting, test.newSetting, v1.Update, test.allowed) + s.testAdmit(t, validator, test.oldSetting, test.newSetting, v1.Update, test.username, test.allowed) }) } } @@ -197,7 +210,7 @@ func (s *SettingSuite) validateDisableInactiveUserAfter(op v1.Operation) { Name: setting.DisableInactiveUserAfter, }, Value: test.value, - }, op, test.allowed) + }, op, "foo", test.allowed) }) } } @@ -301,7 +314,7 @@ func (s *SettingSuite) validateDeleteInactiveUserAfter(op v1.Operation) { Name: setting.DeleteInactiveUserAfter, }, Value: test.value, - }, op, test.allowed) + }, op, "foo", test.allowed) }) } } @@ -355,7 +368,7 @@ func (s *SettingSuite) validateUserRetentionCron(op v1.Operation) { Name: setting.UserRetentionCron, }, Value: test.value, - }, op, test.allowed) + }, op, "foo", test.allowed) }) } } @@ -413,7 +426,7 @@ func (s *SettingSuite) validateAuthUserInfoMaxAgeSeconds(op v1.Operation) { Name: setting.AuthUserInfoMaxAgeSeconds, }, Value: test.value, - }, op, test.allowed) + }, op, "foo", test.allowed) }) } } @@ -462,7 +475,7 @@ func (s *SettingSuite) validateAuthUserInfoResyncCron(op v1.Operation) { Name: setting.AuthUserInfoResyncCron, }, Value: test.value, - }, op, test.allowed) + }, op, "foo", test.allowed) }) } } @@ -542,7 +555,7 @@ func (s *SettingSuite) validateCRTDefaultTTL(op v1.Operation) { Name: setting.CRTDefaultTTL, }, Value: test.value, - }, op, test.allowed) + }, op, "foo", test.allowed) }) } } @@ -622,7 +635,7 @@ func (s *SettingSuite) validateCRTDefaultGracePeriod(op v1.Operation) { Name: setting.CRTDefaultGracePeriod, }, Value: test.value, - }, op, test.allowed) + }, op, "foo", test.allowed) }) } } @@ -668,7 +681,7 @@ func (s *SettingSuite) validateUserLastLoginDefault(op v1.Operation) { Name: setting.UserLastLoginDefault, }, Value: test.value, - }, op, test.allowed) + }, op, "foo", test.allowed) }) } } @@ -766,7 +779,7 @@ func (s *SettingSuite) TestValidateClusterAgentSchedulingPriorityClass() { Name: settingName, }, Value: test.newValue, - }, v1.Update, test.allowed) + }, v1.Update, "foo", test.allowed) }) } } @@ -885,7 +898,7 @@ func (s *SettingSuite) TestValidateAgentSchedulingPodDisruptionBudget() { Name: settingName, }, Value: test.newValue, - }, v1.Update, test.allowed) + }, v1.Update, "foo", test.allowed) }) } } @@ -1061,19 +1074,19 @@ func (s *SettingSuite) validateAuthUserSessionTTLMinutes(op v1.Operation) { Name: setting.AuthUserSessionTTLMinutes, }, Value: test.value, - }, op, test.allowed) + }, op, "foo", test.allowed) }) } } -func (s *SettingSuite) testAdmit(t *testing.T, validator *setting.Validator, oldSetting, newSetting *v3.Setting, op v1.Operation, allowed bool) { +func (s *SettingSuite) testAdmit(t *testing.T, validator *setting.Validator, oldSetting, newSetting *v3.Setting, op v1.Operation, username string, allowed bool) { oldObjRaw, err := json.Marshal(oldSetting) require.NoError(t, err, "failed to marshal old Setting") objRaw, err := json.Marshal(newSetting) require.NoError(t, err, "failed to marshal Setting") - resp, err := validator.Admitters()[0].Admit(newRequest(op, objRaw, oldObjRaw)) + resp, err := validator.Admitters()[0].Admit(newRequest(op, objRaw, oldObjRaw, username)) require.NoError(t, err) assert.Equal(t, allowed, resp.Allowed) } @@ -1088,7 +1101,7 @@ func (s *SettingSuite) TestValidatingWebhookFailurePolicy() { require.Equal(t, &ignorePolicy, webhook[0].FailurePolicy) } -func newRequest(op v1.Operation, obj, oldObj []byte) *admission.Request { +func newRequest(op v1.Operation, obj, oldObj []byte, username string) *admission.Request { return &admission.Request{ AdmissionRequest: v1.AdmissionRequest{ UID: "1", @@ -1097,7 +1110,7 @@ func newRequest(op v1.Operation, obj, oldObj []byte) *admission.Request { RequestKind: &gvk, RequestResource: &gvr, Operation: op, - UserInfo: authenticationv1.UserInfo{Username: "foo", UID: ""}, + UserInfo: authenticationv1.UserInfo{Username: username, UID: ""}, Object: runtime.RawExtension{Raw: obj}, OldObject: runtime.RawExtension{Raw: oldObj}, }, @@ -1666,7 +1679,7 @@ func (s *SettingSuite) validateAuthUserSessionTTLIdleMinutes(op v1.Operation, t Name: setting.AuthUserSessionIdleTTLMinutes, }, Value: tt.value, - }, op, tt.allowed) + }, op, "foo", tt.allowed) }) } }