From 2d732bcbbcfc7bfb568a7df6bebd0c0604c54612 Mon Sep 17 00:00:00 2001 From: Andreas Kupries Date: Tue, 8 Oct 2024 04:15:40 +0200 Subject: [PATCH 1/6] Validate LastUsedAt for Token and ClusterAuthToken (#480) Ref: https://github.com/rancher/rancher/issues/45732 Co-authored-by: Peter Matseykanets --- docs.md | 80 ++++++++--- pkg/codegen/docs.go | 9 +- .../v3/clusterauthtoken/ClusterAuthToken.md | 13 ++ .../v3/clusterauthtoken/validator.go | 93 ++++++++++++ .../v3/clusterauthtoken/validator_test.go | 135 ++++++++++++++++++ .../management.cattle.io/v3/token/Token.md | 13 ++ .../v3/token/validator.go | 93 ++++++++++++ .../v3/token/validator_test.go | 135 ++++++++++++++++++ pkg/server/handlers.go | 9 +- 9 files changed, 554 insertions(+), 26 deletions(-) create mode 100644 pkg/resources/cluster.cattle.io/v3/clusterauthtoken/ClusterAuthToken.md create mode 100644 pkg/resources/cluster.cattle.io/v3/clusterauthtoken/validator.go create mode 100644 pkg/resources/cluster.cattle.io/v3/clusterauthtoken/validator_test.go create mode 100644 pkg/resources/management.cattle.io/v3/token/Token.md create mode 100644 pkg/resources/management.cattle.io/v3/token/validator.go create mode 100644 pkg/resources/management.cattle.io/v3/token/validator_test.go diff --git a/docs.md b/docs.md index a83891bb9..bac522aa5 100644 --- a/docs.md +++ b/docs.md @@ -1,6 +1,24 @@ -# core/v1 +# cluster.cattle.io/v3 -## Namespace +## ClusterAuthToken + +### Validation Checks + +#### Invalid Fields - Create + +When a ClusterAuthToken is created, the following checks take place: + +- If set, `lastUsedAt` must be a valid date time according to RFC3339 (e.g. `2023-11-29T00:00:00Z`). + +#### Invalid Fields - Update + +When a ClusterAuthToken is updated, the following checks take place: + +- If set, `lastUsedAt` must be a valid date time according to RFC3339 (e.g. `2023-11-29T00:00:00Z`). + +# core/v1 + +## Namespace ### Validation Checks @@ -24,7 +42,7 @@ The following labels are considered relevant for PSA enforcement: - pod-security.kubernetes.io/warn - pod-security.kubernetes.io/warn-version -## Secret +## Secret ### Validation Checks @@ -43,9 +61,9 @@ places a `field.cattle.io/creatorId` annotation with the name of the user as the Checks if there are any RoleBindings owned by this secret which provide access to a role granting access to this secret. If yes, the webhook redacts the role, so that it only grants a deletion permission. -# management.cattle.io/v3 +# management.cattle.io/v3 -## ClusterProxyConfig +## ClusterProxyConfig ### Validation Checks @@ -54,7 +72,7 @@ If yes, the webhook redacts the role, so that it only grants a deletion permissi When creating a clusterproxyconfig, we check to make sure that one does not already exist for the given cluster. Only 1 clusterproxyconfig per downstream cluster is ever permitted. -## ClusterRoleTemplateBinding +## ClusterRoleTemplateBinding ### Validation Checks @@ -94,7 +112,7 @@ Users can update the following fields if they have not been set, but after they In addition, as in the create validation, both a user subject and a group subject cannot be specified. -## Feature +## Feature ### Validation Checks @@ -103,7 +121,7 @@ In addition, as in the create validation, both a user subject and a group subjec The desired value must not change on new spec unless it's equal to the `lockedValue` or `lockedValue` is nil. Due to the security impact of the `external-rules` feature flag, only users with admin permissions (`*` verbs on `*` resources in `*` APIGroups in all namespaces) can enable or disable this feature flag. -## FleetWorkspace +## FleetWorkspace ### Validation Checks @@ -118,7 +136,7 @@ When a `FleetWorkspace` is created, it will create the following resources: 2. `ClusterRole`. It will create the cluster role that has * permission only to the current workspace. 3. Two `RoleBindings` to bind the current user to fleet-admin roles and `FleetWorkspace` roles. -## GlobalRole +## GlobalRole ### Validation Checks @@ -149,7 +167,7 @@ The `globalroles.builtin` field is immutable, and new builtIn GlobalRoles cannot If `globalroles.builtin` is true then all fields are immutable except `metadata` and `newUserDefault`. If `globalroles.builtin` is true then the GlobalRole can not be deleted. -## GlobalRoleBinding +## GlobalRoleBinding ### Validation Checks @@ -182,7 +200,7 @@ All RoleTemplates which are referred to in the `inheritedClusterRoles` field mus When a GlobalRoleBinding is created an owner reference is created on the binding referring to the backing GlobalRole defined by `globalRoleName`. -## NodeDriver +## NodeDriver ### Validation Checks @@ -192,7 +210,7 @@ Note: checks only run if a node driver is being disabled or deleted This admission webhook prevents the disabling or deletion of a NodeDriver if there are any Nodes that are under management by said driver. If there are _any_ nodes that use the driver the request will be denied. -## Project +## Project ### Validation Checks @@ -221,7 +239,7 @@ Limits for any resource must not be less than requests. Adds the authz.management.cattle.io/creator-role-bindings annotation. -## ProjectRoleTemplateBinding +## ProjectRoleTemplateBinding ### Validation Checks @@ -268,7 +286,7 @@ changed: In addition, as in the create validation, both a user subject and a group subject cannot be specified. -## RoleTemplate +## RoleTemplate ### Validation Checks @@ -307,7 +325,7 @@ If `roletemplates.builtin` is true then all fields are immutable except: RoleTemplate can not be deleted if they are referenced by other RoleTemplates via `roletemplates.roleTemplateNames` or by GlobalRoles via `globalRoles.inheritedClusterRoles` -## Setting +## Setting ### Validation Checks @@ -335,7 +353,23 @@ When a Setting is updated, the following checks take place: have a status condition `AgentTlsStrictCheck` set to `True`, unless the new setting has an overriding annotation `cattle.io/force=true`. -## UserAttribute +## Token + +### Validation Checks + +#### Invalid Fields - Create + +When a Token is created, the following checks take place: + +- If set, `lastUsedAt` must be a valid date time according to RFC3339 (e.g. `2023-11-29T00:00:00Z`). + +#### Invalid Fields - Update + +When a Token is updated, the following checks take place: + +- If set, `lastUsedAt` must be a valid date time according to RFC3339 (e.g. `2023-11-29T00:00:00Z`). + +## UserAttribute ### Validation Checks @@ -355,9 +389,9 @@ When a UserAttribute is updated, the following checks take place: - If set, `disableAfter` must be zero or a positive duration (e.g. `240h`). - If set, `deleteAfter` must be zero or a positive duration (e.g. `240h`). -# provisioning.cattle.io/v1 +# provisioning.cattle.io/v1 -## Cluster +## Cluster ### Validation Checks @@ -410,9 +444,9 @@ perform no mutations. If the value is not present or not `"true"`, compare the v for each `machinePool`, to its' previous value. If the values are not identical, revert the value for the `dynamicSchemaSpec` for the specific `machinePool`, but do not reject the request. -# rbac.authorization.k8s.io/v1 +# rbac.authorization.k8s.io/v1 -## ClusterRole +## ClusterRole ### Validation Checks @@ -420,7 +454,7 @@ for each `machinePool`, to its' previous value. If the values are not identical, Users cannot update or remove the following label after it has been added: - authz.management.cattle.io/gr-owner -## ClusterRoleBinding +## ClusterRoleBinding ### Validation Checks @@ -428,7 +462,7 @@ Users cannot update or remove the following label after it has been added: Users cannot update or remove the following label after it has been added: - authz.management.cattle.io/grb-owner -## Role +## Role ### Validation Checks @@ -436,7 +470,7 @@ Users cannot update or remove the following label after it has been added: Users cannot update or remove the following label after it has been added: - authz.management.cattle.io/gr-owner -## RoleBinding +## RoleBinding ### Validation Checks diff --git a/pkg/codegen/docs.go b/pkg/codegen/docs.go index 91c116c71..a43d9d07c 100644 --- a/pkg/codegen/docs.go +++ b/pkg/codegen/docs.go @@ -39,16 +39,18 @@ func generateDocs(resourcesBaseDir, outputFilePath string) (err error) { if err != nil { return err } + docFiles, err := getDocFiles(resourcesBaseDir) if err != nil { return fmt.Errorf("unable to create documentation: %w", err) } + currentGroup := "" for _, docFile := range docFiles { newGroup := docFile.group if newGroup != currentGroup { // our group has changed, output a new group header - groupFormatString := "# %s/%s \n" + groupFormatString := "# %s/%s\n" if currentGroup != "" { groupFormatString = "\n" + groupFormatString } @@ -59,10 +61,11 @@ func generateDocs(resourcesBaseDir, outputFilePath string) (err error) { currentGroup = newGroup } - _, err = fmt.Fprintf(outputFile, "\n## %s \n\n", docFile.resource) + _, err = fmt.Fprintf(outputFile, "\n## %s\n\n", docFile.resource) if err != nil { return fmt.Errorf("unable to write resource header for %s: %w", docFile.resource, err) } + scanner := bufio.NewScanner(bytes.NewReader(docFile.content)) for scanner.Scan() { line := scanner.Bytes() @@ -81,6 +84,7 @@ func generateDocs(resourcesBaseDir, outputFilePath string) (err error) { return fmt.Errorf("got an error scanning content for %s/%s.%s: %w", docFile.group, docFile.version, docFile.resource, err) } } + return nil } @@ -91,6 +95,7 @@ func getDocFiles(baseDir string) ([]docFile, error) { if err != nil { return nil, fmt.Errorf("unable to list entries in directory %s: %w", baseDir, err) } + var docFiles []docFile for _, entry := range entries { entryPath := filepath.Join(baseDir, entry.Name()) diff --git a/pkg/resources/cluster.cattle.io/v3/clusterauthtoken/ClusterAuthToken.md b/pkg/resources/cluster.cattle.io/v3/clusterauthtoken/ClusterAuthToken.md new file mode 100644 index 000000000..a85ccfded --- /dev/null +++ b/pkg/resources/cluster.cattle.io/v3/clusterauthtoken/ClusterAuthToken.md @@ -0,0 +1,13 @@ +## Validation Checks + +### Invalid Fields - Create + +When a ClusterAuthToken is created, the following checks take place: + +- If set, `lastUsedAt` must be a valid date time according to RFC3339 (e.g. `2023-11-29T00:00:00Z`). + +### Invalid Fields - Update + +When a ClusterAuthToken is updated, the following checks take place: + +- If set, `lastUsedAt` must be a valid date time according to RFC3339 (e.g. `2023-11-29T00:00:00Z`). diff --git a/pkg/resources/cluster.cattle.io/v3/clusterauthtoken/validator.go b/pkg/resources/cluster.cattle.io/v3/clusterauthtoken/validator.go new file mode 100644 index 000000000..e84f002f0 --- /dev/null +++ b/pkg/resources/cluster.cattle.io/v3/clusterauthtoken/validator.go @@ -0,0 +1,93 @@ +package clusterauthtoken + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/rancher/webhook/pkg/admission" + admissionv1 "k8s.io/api/admission/v1" + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/trace" +) + +var gvr = schema.GroupVersionResource{ + Group: "cluster.cattle.io", + Version: "v3", + Resource: "clusterauthtokens", +} + +// Validator validates clusterauthtokens. +type Validator struct { + admitter admitter +} + +// NewValidator returns a new Validator instance. +func NewValidator() *Validator { + return &Validator{ + admitter: admitter{}, + } +} + +// GVR returns the GroupVersionResource. +func (v *Validator) GVR() schema.GroupVersionResource { + return gvr +} + +// Operations returns list of operations handled by the validator. +func (v *Validator) Operations() []admissionregistrationv1.OperationType { + return []admissionregistrationv1.OperationType{admissionregistrationv1.Update, admissionregistrationv1.Create} +} + +// ValidatingWebhook returns the ValidatingWebhook. +func (v *Validator) ValidatingWebhook(clientConfig admissionregistrationv1.WebhookClientConfig) []admissionregistrationv1.ValidatingWebhook { + return []admissionregistrationv1.ValidatingWebhook{ + *admission.NewDefaultValidatingWebhook(v, clientConfig, admissionregistrationv1.ClusterScope, v.Operations()), + } +} + +// Admitters returns the admitter objects. +func (v *Validator) Admitters() []admission.Admitter { + return []admission.Admitter{&v.admitter} +} + +type admitter struct{} + +// Admit handles the webhook admission requests. +func (a *admitter) Admit(request *admission.Request) (*admissionv1.AdmissionResponse, error) { + listTrace := trace.New("clusterAuthTokenValidator Admit", trace.Field{Key: "user", Value: request.UserInfo.Username}) + defer listTrace.LogIfLong(admission.SlowTraceDuration) + + if request.Operation == admissionv1.Create || request.Operation == admissionv1.Update { + err := a.validateTokenFields(request) + if err != nil { + return admission.ResponseBadRequest(err.Error()), nil + } + } + + return admission.ResponseAllowed(), nil +} + +// PartialClusterAuthToken represents raw values of ClusterAuthToken fields. +type PartialClusterAuthToken struct { + LastUsedAt *string `json:"lastUsedAt"` +} + +func (a *admitter) validateTokenFields(request *admission.Request) error { + var partial PartialClusterAuthToken + + err := json.Unmarshal(request.Object.Raw, &partial) + if err != nil { + return fmt.Errorf("failed to get PartialClusterAuthToken from request: %w", err) + } + + if partial.LastUsedAt != nil { + if _, err = time.Parse(time.RFC3339, *partial.LastUsedAt); err != nil { + return field.TypeInvalid(field.NewPath("lastUsedAt"), partial.LastUsedAt, err.Error()) + } + } + + return nil +} diff --git a/pkg/resources/cluster.cattle.io/v3/clusterauthtoken/validator_test.go b/pkg/resources/cluster.cattle.io/v3/clusterauthtoken/validator_test.go new file mode 100644 index 000000000..0fa41fc8b --- /dev/null +++ b/pkg/resources/cluster.cattle.io/v3/clusterauthtoken/validator_test.go @@ -0,0 +1,135 @@ +package clusterauthtoken_test + +import ( + "context" + "encoding/json" + "testing" + "time" + + "github.com/rancher/webhook/pkg/admission" + "github.com/rancher/webhook/pkg/resources/cluster.cattle.io/v3/clusterauthtoken" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" + v1 "k8s.io/api/admission/v1" + authenticationv1 "k8s.io/api/authentication/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/utils/pointer" +) + +type ClusterAuthTokenFieldsSuite struct { + suite.Suite +} + +func TestTokenFieldsValidation(t *testing.T) { + t.Parallel() + suite.Run(t, new(ClusterAuthTokenFieldsSuite)) +} + +var ( + gvk = metav1.GroupVersionKind{Group: "cluster.cattle.io", Version: "v3", Kind: "ClusterAuthToken"} + gvr = metav1.GroupVersionResource{Group: "cluster.cattle.io", Version: "v3", Resource: "clusterauthtokens"} +) + +type tokenFieldsTest struct { + lastUsedAt *string + allowed bool +} + +func (t *tokenFieldsTest) name() string { + return pointer.StringDeref(t.lastUsedAt, "nil") +} + +func (t *tokenFieldsTest) toToken() ([]byte, error) { + return json.Marshal(clusterauthtoken.PartialClusterAuthToken{ + LastUsedAt: t.lastUsedAt, + }) +} + +var tokenFieldsTests = []tokenFieldsTest{ + { + allowed: true, + }, + { + lastUsedAt: pointer.String(time.Now().Format(time.RFC3339)), + allowed: true, + }, + { + lastUsedAt: pointer.String("2024-03-25T21:2:45Z"), // Not a valid RFC3339 time. + }, + { + lastUsedAt: pointer.String("1w"), + }, + { + lastUsedAt: pointer.String("1d"), + }, + { + lastUsedAt: pointer.String("-1h"), + }, + { + lastUsedAt: pointer.String(""), + }, +} + +func (s *ClusterAuthTokenFieldsSuite) TestValidateOnUpdate() { + s.validate(v1.Update) +} + +func (s *ClusterAuthTokenFieldsSuite) TestValidateOnCreate() { + s.validate(v1.Create) +} + +func (s *ClusterAuthTokenFieldsSuite) TestDontValidateOnDelete() { + // Make sure that Token can be deleted without enforcing validation of user token fields. + alwaysAllow := true + s.validate(v1.Delete, alwaysAllow) +} + +func (s *ClusterAuthTokenFieldsSuite) validate(op v1.Operation, allowed ...bool) { + admitter := s.setup() + + for _, test := range tokenFieldsTests { + test := test + s.Run(test.name(), func() { + t := s.T() + t.Parallel() + + objRaw, err := test.toToken() + assert.NoError(t, err, "failed to marshal PartialToken") + + resp, err := admitter.Admit(newRequest(op, objRaw)) + if assert.NoError(t, err, "Admit failed") { + wantAllowed := test.allowed + if len(allowed) > 0 { + wantAllowed = allowed[0] // Apply the override. + } + + assert.Equalf(t, wantAllowed, resp.Allowed, "expected allowed %v got %v message=%v", test.allowed, resp.Allowed, resp.Result) + } + }) + } +} + +func (s *ClusterAuthTokenFieldsSuite) setup() admission.Admitter { + validator := clusterauthtoken.NewValidator() + s.Len(validator.Admitters(), 1, "expected 1 admitter") + + return validator.Admitters()[0] +} + +func newRequest(op v1.Operation, obj []byte) *admission.Request { + return &admission.Request{ + AdmissionRequest: v1.AdmissionRequest{ + UID: "1", + Kind: gvk, + Resource: gvr, + RequestKind: &gvk, + RequestResource: &gvr, + Operation: op, + UserInfo: authenticationv1.UserInfo{Username: "foo", UID: ""}, + Object: runtime.RawExtension{Raw: obj}, + OldObject: runtime.RawExtension{Raw: []byte("{}")}, + }, + Context: context.Background(), + } +} diff --git a/pkg/resources/management.cattle.io/v3/token/Token.md b/pkg/resources/management.cattle.io/v3/token/Token.md new file mode 100644 index 000000000..09eefcb2f --- /dev/null +++ b/pkg/resources/management.cattle.io/v3/token/Token.md @@ -0,0 +1,13 @@ +## Validation Checks + +### Invalid Fields - Create + +When a Token is created, the following checks take place: + +- If set, `lastUsedAt` must be a valid date time according to RFC3339 (e.g. `2023-11-29T00:00:00Z`). + +### Invalid Fields - Update + +When a Token is updated, the following checks take place: + +- If set, `lastUsedAt` must be a valid date time according to RFC3339 (e.g. `2023-11-29T00:00:00Z`). diff --git a/pkg/resources/management.cattle.io/v3/token/validator.go b/pkg/resources/management.cattle.io/v3/token/validator.go new file mode 100644 index 000000000..b4799a366 --- /dev/null +++ b/pkg/resources/management.cattle.io/v3/token/validator.go @@ -0,0 +1,93 @@ +package token + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/rancher/webhook/pkg/admission" + admissionv1 "k8s.io/api/admission/v1" + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/trace" +) + +var gvr = schema.GroupVersionResource{ + Group: "management.cattle.io", + Version: "v3", + Resource: "tokens", +} + +// Validator validates tokens. +type Validator struct { + admitter admitter +} + +// NewValidator returns a new Validator instance. +func NewValidator() *Validator { + return &Validator{ + admitter: admitter{}, + } +} + +// GVR returns the GroupVersionResource. +func (v *Validator) GVR() schema.GroupVersionResource { + return gvr +} + +// Operations returns list of operations handled by the validator. +func (v *Validator) Operations() []admissionregistrationv1.OperationType { + return []admissionregistrationv1.OperationType{admissionregistrationv1.Update, admissionregistrationv1.Create} +} + +// ValidatingWebhook returns the ValidatingWebhook. +func (v *Validator) ValidatingWebhook(clientConfig admissionregistrationv1.WebhookClientConfig) []admissionregistrationv1.ValidatingWebhook { + return []admissionregistrationv1.ValidatingWebhook{ + *admission.NewDefaultValidatingWebhook(v, clientConfig, admissionregistrationv1.ClusterScope, v.Operations()), + } +} + +// Admitters returns the admitter objects. +func (v *Validator) Admitters() []admission.Admitter { + return []admission.Admitter{&v.admitter} +} + +type admitter struct{} + +// Admit handles the webhook admission requests. +func (a *admitter) Admit(request *admission.Request) (*admissionv1.AdmissionResponse, error) { + listTrace := trace.New("tokenValidator Admit", trace.Field{Key: "user", Value: request.UserInfo.Username}) + defer listTrace.LogIfLong(admission.SlowTraceDuration) + + if request.Operation == admissionv1.Create || request.Operation == admissionv1.Update { + err := a.validateTokenFields(request) + if err != nil { + return admission.ResponseBadRequest(err.Error()), nil + } + } + + return admission.ResponseAllowed(), nil +} + +// PartialToken represents raw values of Token fields. +type PartialToken struct { + LastUsedAt *string `json:"lastUsedAt"` +} + +func (a *admitter) validateTokenFields(request *admission.Request) error { + var partial PartialToken + + err := json.Unmarshal(request.Object.Raw, &partial) + if err != nil { + return fmt.Errorf("failed to get PartialToken from request: %w", err) + } + + if partial.LastUsedAt != nil { + if _, err = time.Parse(time.RFC3339, *partial.LastUsedAt); err != nil { + return field.TypeInvalid(field.NewPath("lastUsedAt"), partial.LastUsedAt, err.Error()) + } + } + + return nil +} diff --git a/pkg/resources/management.cattle.io/v3/token/validator_test.go b/pkg/resources/management.cattle.io/v3/token/validator_test.go new file mode 100644 index 000000000..c5d4b8723 --- /dev/null +++ b/pkg/resources/management.cattle.io/v3/token/validator_test.go @@ -0,0 +1,135 @@ +package token_test + +import ( + "context" + "encoding/json" + "testing" + "time" + + "github.com/rancher/webhook/pkg/admission" + "github.com/rancher/webhook/pkg/resources/management.cattle.io/v3/token" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" + v1 "k8s.io/api/admission/v1" + authenticationv1 "k8s.io/api/authentication/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/utils/pointer" +) + +type TokenFieldsSuite struct { + suite.Suite +} + +func TestTokenFieldsValidation(t *testing.T) { + t.Parallel() + suite.Run(t, new(TokenFieldsSuite)) +} + +var ( + gvk = metav1.GroupVersionKind{Group: "management.cattle.io", Version: "v3", Kind: "Token"} + gvr = metav1.GroupVersionResource{Group: "management.cattle.io", Version: "v3", Resource: "tokens"} +) + +type tokenFieldsTest struct { + lastUsedAt *string + allowed bool +} + +func (t *tokenFieldsTest) name() string { + return pointer.StringDeref(t.lastUsedAt, "nil") +} + +func (t *tokenFieldsTest) toToken() ([]byte, error) { + return json.Marshal(token.PartialToken{ + LastUsedAt: t.lastUsedAt, + }) +} + +var tokenFieldsTests = []tokenFieldsTest{ + { + allowed: true, + }, + { + lastUsedAt: pointer.String(time.Now().Format(time.RFC3339)), + allowed: true, + }, + { + lastUsedAt: pointer.String("2024-03-25T21:2:45Z"), // Not a valid RFC3339 time. + }, + { + lastUsedAt: pointer.String("1w"), + }, + { + lastUsedAt: pointer.String("1d"), + }, + { + lastUsedAt: pointer.String("-1h"), + }, + { + lastUsedAt: pointer.String(""), + }, +} + +func (s *TokenFieldsSuite) TestValidateOnUpdate() { + s.validate(v1.Update) +} + +func (s *TokenFieldsSuite) TestValidateOnCreate() { + s.validate(v1.Create) +} + +func (s *TokenFieldsSuite) TestDontValidateOnDelete() { + // Make sure that Token can be deleted without enforcing validation of user token fields. + alwaysAllow := true + s.validate(v1.Delete, alwaysAllow) +} + +func (s *TokenFieldsSuite) validate(op v1.Operation, allowed ...bool) { + admitter := s.setup() + + for _, test := range tokenFieldsTests { + test := test + s.Run(test.name(), func() { + t := s.T() + t.Parallel() + + objRaw, err := test.toToken() + assert.NoError(t, err, "failed to marshal PartialToken") + + resp, err := admitter.Admit(newRequest(op, objRaw)) + if assert.NoError(t, err, "Admit failed") { + wantAllowed := test.allowed + if len(allowed) > 0 { + wantAllowed = allowed[0] // Apply the override. + } + + assert.Equalf(t, wantAllowed, resp.Allowed, "expected allowed %v got %v message=%v", test.allowed, resp.Allowed, resp.Result) + } + }) + } +} + +func (s *TokenFieldsSuite) setup() admission.Admitter { + validator := token.NewValidator() + s.Len(validator.Admitters(), 1, "expected 1 admitter") + + return validator.Admitters()[0] +} + +func newRequest(op v1.Operation, obj []byte) *admission.Request { + return &admission.Request{ + AdmissionRequest: v1.AdmissionRequest{ + UID: "1", + Kind: gvk, + Resource: gvr, + RequestKind: &gvk, + RequestResource: &gvr, + Operation: op, + UserInfo: authenticationv1.UserInfo{Username: "foo", UID: ""}, + Object: runtime.RawExtension{Raw: obj}, + OldObject: runtime.RawExtension{Raw: []byte("{}")}, + }, + Context: context.Background(), + } +} diff --git a/pkg/server/handlers.go b/pkg/server/handlers.go index dba617595..f53a5d3c4 100644 --- a/pkg/server/handlers.go +++ b/pkg/server/handlers.go @@ -4,6 +4,7 @@ import ( "github.com/rancher/webhook/pkg/admission" "github.com/rancher/webhook/pkg/clients" "github.com/rancher/webhook/pkg/resolvers" + "github.com/rancher/webhook/pkg/resources/cluster.cattle.io/v3/clusterauthtoken" nshandler "github.com/rancher/webhook/pkg/resources/core/v1/namespace" "github.com/rancher/webhook/pkg/resources/core/v1/secret" managementCluster "github.com/rancher/webhook/pkg/resources/management.cattle.io/v3/cluster" @@ -19,6 +20,7 @@ import ( "github.com/rancher/webhook/pkg/resources/management.cattle.io/v3/projectroletemplatebinding" "github.com/rancher/webhook/pkg/resources/management.cattle.io/v3/roletemplate" "github.com/rancher/webhook/pkg/resources/management.cattle.io/v3/setting" + "github.com/rancher/webhook/pkg/resources/management.cattle.io/v3/token" "github.com/rancher/webhook/pkg/resources/management.cattle.io/v3/userattribute" provisioningCluster "github.com/rancher/webhook/pkg/resources/provisioning.cattle.io/v1/cluster" "github.com/rancher/webhook/pkg/resources/rbac.authorization.k8s.io/v1/clusterrole" @@ -55,12 +57,17 @@ func Validation(clients *clients.Clients) ([]admission.ValidatingAdmissionHandle roles := role.NewValidator() rolebindings := rolebinding.NewValidator() setting := setting.NewValidator(clients.Management.Cluster().Cache()) + token := token.NewValidator() userAttribute := userattribute.NewValidator() clusterRoles := clusterrole.NewValidator() clusterRoleBindings := clusterrolebinding.NewValidator() - handlers = append(handlers, psact, globalRoles, globalRoleBindings, prtbs, crtbs, roleTemplates, secrets, nodeDriver, projects, roles, rolebindings, clusterRoles, clusterRoleBindings, clusterProxyConfigs, userAttribute, setting) + handlers = append(handlers, psact, globalRoles, globalRoleBindings, prtbs, crtbs, roleTemplates, secrets, nodeDriver, projects, roles, rolebindings, clusterRoles, clusterRoleBindings, clusterProxyConfigs, userAttribute, setting, token) + } else { + clusterAuthTokens := clusterauthtoken.NewValidator() + handlers = append(handlers, clusterAuthTokens) } + return handlers, nil } From 3930f452cc25ebffa826bb8901384d465346afe4 Mon Sep 17 00:00:00 2001 From: Peter Matseykanets Date: Tue, 8 Oct 2024 15:47:21 -0400 Subject: [PATCH 2/6] Revert "Validate LastUsedAt for Token and ClusterAuthToken (#480)" (#519) This reverts commit 2d732bcbbcfc7bfb568a7df6bebd0c0604c54612. --- docs.md | 80 +++-------- pkg/codegen/docs.go | 9 +- .../v3/clusterauthtoken/ClusterAuthToken.md | 13 -- .../v3/clusterauthtoken/validator.go | 93 ------------ .../v3/clusterauthtoken/validator_test.go | 135 ------------------ .../management.cattle.io/v3/token/Token.md | 13 -- .../v3/token/validator.go | 93 ------------ .../v3/token/validator_test.go | 135 ------------------ pkg/server/handlers.go | 9 +- 9 files changed, 26 insertions(+), 554 deletions(-) delete mode 100644 pkg/resources/cluster.cattle.io/v3/clusterauthtoken/ClusterAuthToken.md delete mode 100644 pkg/resources/cluster.cattle.io/v3/clusterauthtoken/validator.go delete mode 100644 pkg/resources/cluster.cattle.io/v3/clusterauthtoken/validator_test.go delete mode 100644 pkg/resources/management.cattle.io/v3/token/Token.md delete mode 100644 pkg/resources/management.cattle.io/v3/token/validator.go delete mode 100644 pkg/resources/management.cattle.io/v3/token/validator_test.go diff --git a/docs.md b/docs.md index bac522aa5..a83891bb9 100644 --- a/docs.md +++ b/docs.md @@ -1,24 +1,6 @@ -# cluster.cattle.io/v3 +# core/v1 -## ClusterAuthToken - -### Validation Checks - -#### Invalid Fields - Create - -When a ClusterAuthToken is created, the following checks take place: - -- If set, `lastUsedAt` must be a valid date time according to RFC3339 (e.g. `2023-11-29T00:00:00Z`). - -#### Invalid Fields - Update - -When a ClusterAuthToken is updated, the following checks take place: - -- If set, `lastUsedAt` must be a valid date time according to RFC3339 (e.g. `2023-11-29T00:00:00Z`). - -# core/v1 - -## Namespace +## Namespace ### Validation Checks @@ -42,7 +24,7 @@ The following labels are considered relevant for PSA enforcement: - pod-security.kubernetes.io/warn - pod-security.kubernetes.io/warn-version -## Secret +## Secret ### Validation Checks @@ -61,9 +43,9 @@ places a `field.cattle.io/creatorId` annotation with the name of the user as the Checks if there are any RoleBindings owned by this secret which provide access to a role granting access to this secret. If yes, the webhook redacts the role, so that it only grants a deletion permission. -# management.cattle.io/v3 +# management.cattle.io/v3 -## ClusterProxyConfig +## ClusterProxyConfig ### Validation Checks @@ -72,7 +54,7 @@ If yes, the webhook redacts the role, so that it only grants a deletion permissi When creating a clusterproxyconfig, we check to make sure that one does not already exist for the given cluster. Only 1 clusterproxyconfig per downstream cluster is ever permitted. -## ClusterRoleTemplateBinding +## ClusterRoleTemplateBinding ### Validation Checks @@ -112,7 +94,7 @@ Users can update the following fields if they have not been set, but after they In addition, as in the create validation, both a user subject and a group subject cannot be specified. -## Feature +## Feature ### Validation Checks @@ -121,7 +103,7 @@ In addition, as in the create validation, both a user subject and a group subjec The desired value must not change on new spec unless it's equal to the `lockedValue` or `lockedValue` is nil. Due to the security impact of the `external-rules` feature flag, only users with admin permissions (`*` verbs on `*` resources in `*` APIGroups in all namespaces) can enable or disable this feature flag. -## FleetWorkspace +## FleetWorkspace ### Validation Checks @@ -136,7 +118,7 @@ When a `FleetWorkspace` is created, it will create the following resources: 2. `ClusterRole`. It will create the cluster role that has * permission only to the current workspace. 3. Two `RoleBindings` to bind the current user to fleet-admin roles and `FleetWorkspace` roles. -## GlobalRole +## GlobalRole ### Validation Checks @@ -167,7 +149,7 @@ The `globalroles.builtin` field is immutable, and new builtIn GlobalRoles cannot If `globalroles.builtin` is true then all fields are immutable except `metadata` and `newUserDefault`. If `globalroles.builtin` is true then the GlobalRole can not be deleted. -## GlobalRoleBinding +## GlobalRoleBinding ### Validation Checks @@ -200,7 +182,7 @@ All RoleTemplates which are referred to in the `inheritedClusterRoles` field mus When a GlobalRoleBinding is created an owner reference is created on the binding referring to the backing GlobalRole defined by `globalRoleName`. -## NodeDriver +## NodeDriver ### Validation Checks @@ -210,7 +192,7 @@ Note: checks only run if a node driver is being disabled or deleted This admission webhook prevents the disabling or deletion of a NodeDriver if there are any Nodes that are under management by said driver. If there are _any_ nodes that use the driver the request will be denied. -## Project +## Project ### Validation Checks @@ -239,7 +221,7 @@ Limits for any resource must not be less than requests. Adds the authz.management.cattle.io/creator-role-bindings annotation. -## ProjectRoleTemplateBinding +## ProjectRoleTemplateBinding ### Validation Checks @@ -286,7 +268,7 @@ changed: In addition, as in the create validation, both a user subject and a group subject cannot be specified. -## RoleTemplate +## RoleTemplate ### Validation Checks @@ -325,7 +307,7 @@ If `roletemplates.builtin` is true then all fields are immutable except: RoleTemplate can not be deleted if they are referenced by other RoleTemplates via `roletemplates.roleTemplateNames` or by GlobalRoles via `globalRoles.inheritedClusterRoles` -## Setting +## Setting ### Validation Checks @@ -353,23 +335,7 @@ When a Setting is updated, the following checks take place: have a status condition `AgentTlsStrictCheck` set to `True`, unless the new setting has an overriding annotation `cattle.io/force=true`. -## Token - -### Validation Checks - -#### Invalid Fields - Create - -When a Token is created, the following checks take place: - -- If set, `lastUsedAt` must be a valid date time according to RFC3339 (e.g. `2023-11-29T00:00:00Z`). - -#### Invalid Fields - Update - -When a Token is updated, the following checks take place: - -- If set, `lastUsedAt` must be a valid date time according to RFC3339 (e.g. `2023-11-29T00:00:00Z`). - -## UserAttribute +## UserAttribute ### Validation Checks @@ -389,9 +355,9 @@ When a UserAttribute is updated, the following checks take place: - If set, `disableAfter` must be zero or a positive duration (e.g. `240h`). - If set, `deleteAfter` must be zero or a positive duration (e.g. `240h`). -# provisioning.cattle.io/v1 +# provisioning.cattle.io/v1 -## Cluster +## Cluster ### Validation Checks @@ -444,9 +410,9 @@ perform no mutations. If the value is not present or not `"true"`, compare the v for each `machinePool`, to its' previous value. If the values are not identical, revert the value for the `dynamicSchemaSpec` for the specific `machinePool`, but do not reject the request. -# rbac.authorization.k8s.io/v1 +# rbac.authorization.k8s.io/v1 -## ClusterRole +## ClusterRole ### Validation Checks @@ -454,7 +420,7 @@ for each `machinePool`, to its' previous value. If the values are not identical, Users cannot update or remove the following label after it has been added: - authz.management.cattle.io/gr-owner -## ClusterRoleBinding +## ClusterRoleBinding ### Validation Checks @@ -462,7 +428,7 @@ Users cannot update or remove the following label after it has been added: Users cannot update or remove the following label after it has been added: - authz.management.cattle.io/grb-owner -## Role +## Role ### Validation Checks @@ -470,7 +436,7 @@ Users cannot update or remove the following label after it has been added: Users cannot update or remove the following label after it has been added: - authz.management.cattle.io/gr-owner -## RoleBinding +## RoleBinding ### Validation Checks diff --git a/pkg/codegen/docs.go b/pkg/codegen/docs.go index a43d9d07c..91c116c71 100644 --- a/pkg/codegen/docs.go +++ b/pkg/codegen/docs.go @@ -39,18 +39,16 @@ func generateDocs(resourcesBaseDir, outputFilePath string) (err error) { if err != nil { return err } - docFiles, err := getDocFiles(resourcesBaseDir) if err != nil { return fmt.Errorf("unable to create documentation: %w", err) } - currentGroup := "" for _, docFile := range docFiles { newGroup := docFile.group if newGroup != currentGroup { // our group has changed, output a new group header - groupFormatString := "# %s/%s\n" + groupFormatString := "# %s/%s \n" if currentGroup != "" { groupFormatString = "\n" + groupFormatString } @@ -61,11 +59,10 @@ func generateDocs(resourcesBaseDir, outputFilePath string) (err error) { currentGroup = newGroup } - _, err = fmt.Fprintf(outputFile, "\n## %s\n\n", docFile.resource) + _, err = fmt.Fprintf(outputFile, "\n## %s \n\n", docFile.resource) if err != nil { return fmt.Errorf("unable to write resource header for %s: %w", docFile.resource, err) } - scanner := bufio.NewScanner(bytes.NewReader(docFile.content)) for scanner.Scan() { line := scanner.Bytes() @@ -84,7 +81,6 @@ func generateDocs(resourcesBaseDir, outputFilePath string) (err error) { return fmt.Errorf("got an error scanning content for %s/%s.%s: %w", docFile.group, docFile.version, docFile.resource, err) } } - return nil } @@ -95,7 +91,6 @@ func getDocFiles(baseDir string) ([]docFile, error) { if err != nil { return nil, fmt.Errorf("unable to list entries in directory %s: %w", baseDir, err) } - var docFiles []docFile for _, entry := range entries { entryPath := filepath.Join(baseDir, entry.Name()) diff --git a/pkg/resources/cluster.cattle.io/v3/clusterauthtoken/ClusterAuthToken.md b/pkg/resources/cluster.cattle.io/v3/clusterauthtoken/ClusterAuthToken.md deleted file mode 100644 index a85ccfded..000000000 --- a/pkg/resources/cluster.cattle.io/v3/clusterauthtoken/ClusterAuthToken.md +++ /dev/null @@ -1,13 +0,0 @@ -## Validation Checks - -### Invalid Fields - Create - -When a ClusterAuthToken is created, the following checks take place: - -- If set, `lastUsedAt` must be a valid date time according to RFC3339 (e.g. `2023-11-29T00:00:00Z`). - -### Invalid Fields - Update - -When a ClusterAuthToken is updated, the following checks take place: - -- If set, `lastUsedAt` must be a valid date time according to RFC3339 (e.g. `2023-11-29T00:00:00Z`). diff --git a/pkg/resources/cluster.cattle.io/v3/clusterauthtoken/validator.go b/pkg/resources/cluster.cattle.io/v3/clusterauthtoken/validator.go deleted file mode 100644 index e84f002f0..000000000 --- a/pkg/resources/cluster.cattle.io/v3/clusterauthtoken/validator.go +++ /dev/null @@ -1,93 +0,0 @@ -package clusterauthtoken - -import ( - "encoding/json" - "fmt" - "time" - - "github.com/rancher/webhook/pkg/admission" - admissionv1 "k8s.io/api/admission/v1" - admissionregistrationv1 "k8s.io/api/admissionregistration/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/utils/trace" -) - -var gvr = schema.GroupVersionResource{ - Group: "cluster.cattle.io", - Version: "v3", - Resource: "clusterauthtokens", -} - -// Validator validates clusterauthtokens. -type Validator struct { - admitter admitter -} - -// NewValidator returns a new Validator instance. -func NewValidator() *Validator { - return &Validator{ - admitter: admitter{}, - } -} - -// GVR returns the GroupVersionResource. -func (v *Validator) GVR() schema.GroupVersionResource { - return gvr -} - -// Operations returns list of operations handled by the validator. -func (v *Validator) Operations() []admissionregistrationv1.OperationType { - return []admissionregistrationv1.OperationType{admissionregistrationv1.Update, admissionregistrationv1.Create} -} - -// ValidatingWebhook returns the ValidatingWebhook. -func (v *Validator) ValidatingWebhook(clientConfig admissionregistrationv1.WebhookClientConfig) []admissionregistrationv1.ValidatingWebhook { - return []admissionregistrationv1.ValidatingWebhook{ - *admission.NewDefaultValidatingWebhook(v, clientConfig, admissionregistrationv1.ClusterScope, v.Operations()), - } -} - -// Admitters returns the admitter objects. -func (v *Validator) Admitters() []admission.Admitter { - return []admission.Admitter{&v.admitter} -} - -type admitter struct{} - -// Admit handles the webhook admission requests. -func (a *admitter) Admit(request *admission.Request) (*admissionv1.AdmissionResponse, error) { - listTrace := trace.New("clusterAuthTokenValidator Admit", trace.Field{Key: "user", Value: request.UserInfo.Username}) - defer listTrace.LogIfLong(admission.SlowTraceDuration) - - if request.Operation == admissionv1.Create || request.Operation == admissionv1.Update { - err := a.validateTokenFields(request) - if err != nil { - return admission.ResponseBadRequest(err.Error()), nil - } - } - - return admission.ResponseAllowed(), nil -} - -// PartialClusterAuthToken represents raw values of ClusterAuthToken fields. -type PartialClusterAuthToken struct { - LastUsedAt *string `json:"lastUsedAt"` -} - -func (a *admitter) validateTokenFields(request *admission.Request) error { - var partial PartialClusterAuthToken - - err := json.Unmarshal(request.Object.Raw, &partial) - if err != nil { - return fmt.Errorf("failed to get PartialClusterAuthToken from request: %w", err) - } - - if partial.LastUsedAt != nil { - if _, err = time.Parse(time.RFC3339, *partial.LastUsedAt); err != nil { - return field.TypeInvalid(field.NewPath("lastUsedAt"), partial.LastUsedAt, err.Error()) - } - } - - return nil -} diff --git a/pkg/resources/cluster.cattle.io/v3/clusterauthtoken/validator_test.go b/pkg/resources/cluster.cattle.io/v3/clusterauthtoken/validator_test.go deleted file mode 100644 index 0fa41fc8b..000000000 --- a/pkg/resources/cluster.cattle.io/v3/clusterauthtoken/validator_test.go +++ /dev/null @@ -1,135 +0,0 @@ -package clusterauthtoken_test - -import ( - "context" - "encoding/json" - "testing" - "time" - - "github.com/rancher/webhook/pkg/admission" - "github.com/rancher/webhook/pkg/resources/cluster.cattle.io/v3/clusterauthtoken" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/suite" - v1 "k8s.io/api/admission/v1" - authenticationv1 "k8s.io/api/authentication/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/utils/pointer" -) - -type ClusterAuthTokenFieldsSuite struct { - suite.Suite -} - -func TestTokenFieldsValidation(t *testing.T) { - t.Parallel() - suite.Run(t, new(ClusterAuthTokenFieldsSuite)) -} - -var ( - gvk = metav1.GroupVersionKind{Group: "cluster.cattle.io", Version: "v3", Kind: "ClusterAuthToken"} - gvr = metav1.GroupVersionResource{Group: "cluster.cattle.io", Version: "v3", Resource: "clusterauthtokens"} -) - -type tokenFieldsTest struct { - lastUsedAt *string - allowed bool -} - -func (t *tokenFieldsTest) name() string { - return pointer.StringDeref(t.lastUsedAt, "nil") -} - -func (t *tokenFieldsTest) toToken() ([]byte, error) { - return json.Marshal(clusterauthtoken.PartialClusterAuthToken{ - LastUsedAt: t.lastUsedAt, - }) -} - -var tokenFieldsTests = []tokenFieldsTest{ - { - allowed: true, - }, - { - lastUsedAt: pointer.String(time.Now().Format(time.RFC3339)), - allowed: true, - }, - { - lastUsedAt: pointer.String("2024-03-25T21:2:45Z"), // Not a valid RFC3339 time. - }, - { - lastUsedAt: pointer.String("1w"), - }, - { - lastUsedAt: pointer.String("1d"), - }, - { - lastUsedAt: pointer.String("-1h"), - }, - { - lastUsedAt: pointer.String(""), - }, -} - -func (s *ClusterAuthTokenFieldsSuite) TestValidateOnUpdate() { - s.validate(v1.Update) -} - -func (s *ClusterAuthTokenFieldsSuite) TestValidateOnCreate() { - s.validate(v1.Create) -} - -func (s *ClusterAuthTokenFieldsSuite) TestDontValidateOnDelete() { - // Make sure that Token can be deleted without enforcing validation of user token fields. - alwaysAllow := true - s.validate(v1.Delete, alwaysAllow) -} - -func (s *ClusterAuthTokenFieldsSuite) validate(op v1.Operation, allowed ...bool) { - admitter := s.setup() - - for _, test := range tokenFieldsTests { - test := test - s.Run(test.name(), func() { - t := s.T() - t.Parallel() - - objRaw, err := test.toToken() - assert.NoError(t, err, "failed to marshal PartialToken") - - resp, err := admitter.Admit(newRequest(op, objRaw)) - if assert.NoError(t, err, "Admit failed") { - wantAllowed := test.allowed - if len(allowed) > 0 { - wantAllowed = allowed[0] // Apply the override. - } - - assert.Equalf(t, wantAllowed, resp.Allowed, "expected allowed %v got %v message=%v", test.allowed, resp.Allowed, resp.Result) - } - }) - } -} - -func (s *ClusterAuthTokenFieldsSuite) setup() admission.Admitter { - validator := clusterauthtoken.NewValidator() - s.Len(validator.Admitters(), 1, "expected 1 admitter") - - return validator.Admitters()[0] -} - -func newRequest(op v1.Operation, obj []byte) *admission.Request { - return &admission.Request{ - AdmissionRequest: v1.AdmissionRequest{ - UID: "1", - Kind: gvk, - Resource: gvr, - RequestKind: &gvk, - RequestResource: &gvr, - Operation: op, - UserInfo: authenticationv1.UserInfo{Username: "foo", UID: ""}, - Object: runtime.RawExtension{Raw: obj}, - OldObject: runtime.RawExtension{Raw: []byte("{}")}, - }, - Context: context.Background(), - } -} diff --git a/pkg/resources/management.cattle.io/v3/token/Token.md b/pkg/resources/management.cattle.io/v3/token/Token.md deleted file mode 100644 index 09eefcb2f..000000000 --- a/pkg/resources/management.cattle.io/v3/token/Token.md +++ /dev/null @@ -1,13 +0,0 @@ -## Validation Checks - -### Invalid Fields - Create - -When a Token is created, the following checks take place: - -- If set, `lastUsedAt` must be a valid date time according to RFC3339 (e.g. `2023-11-29T00:00:00Z`). - -### Invalid Fields - Update - -When a Token is updated, the following checks take place: - -- If set, `lastUsedAt` must be a valid date time according to RFC3339 (e.g. `2023-11-29T00:00:00Z`). diff --git a/pkg/resources/management.cattle.io/v3/token/validator.go b/pkg/resources/management.cattle.io/v3/token/validator.go deleted file mode 100644 index b4799a366..000000000 --- a/pkg/resources/management.cattle.io/v3/token/validator.go +++ /dev/null @@ -1,93 +0,0 @@ -package token - -import ( - "encoding/json" - "fmt" - "time" - - "github.com/rancher/webhook/pkg/admission" - admissionv1 "k8s.io/api/admission/v1" - admissionregistrationv1 "k8s.io/api/admissionregistration/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/utils/trace" -) - -var gvr = schema.GroupVersionResource{ - Group: "management.cattle.io", - Version: "v3", - Resource: "tokens", -} - -// Validator validates tokens. -type Validator struct { - admitter admitter -} - -// NewValidator returns a new Validator instance. -func NewValidator() *Validator { - return &Validator{ - admitter: admitter{}, - } -} - -// GVR returns the GroupVersionResource. -func (v *Validator) GVR() schema.GroupVersionResource { - return gvr -} - -// Operations returns list of operations handled by the validator. -func (v *Validator) Operations() []admissionregistrationv1.OperationType { - return []admissionregistrationv1.OperationType{admissionregistrationv1.Update, admissionregistrationv1.Create} -} - -// ValidatingWebhook returns the ValidatingWebhook. -func (v *Validator) ValidatingWebhook(clientConfig admissionregistrationv1.WebhookClientConfig) []admissionregistrationv1.ValidatingWebhook { - return []admissionregistrationv1.ValidatingWebhook{ - *admission.NewDefaultValidatingWebhook(v, clientConfig, admissionregistrationv1.ClusterScope, v.Operations()), - } -} - -// Admitters returns the admitter objects. -func (v *Validator) Admitters() []admission.Admitter { - return []admission.Admitter{&v.admitter} -} - -type admitter struct{} - -// Admit handles the webhook admission requests. -func (a *admitter) Admit(request *admission.Request) (*admissionv1.AdmissionResponse, error) { - listTrace := trace.New("tokenValidator Admit", trace.Field{Key: "user", Value: request.UserInfo.Username}) - defer listTrace.LogIfLong(admission.SlowTraceDuration) - - if request.Operation == admissionv1.Create || request.Operation == admissionv1.Update { - err := a.validateTokenFields(request) - if err != nil { - return admission.ResponseBadRequest(err.Error()), nil - } - } - - return admission.ResponseAllowed(), nil -} - -// PartialToken represents raw values of Token fields. -type PartialToken struct { - LastUsedAt *string `json:"lastUsedAt"` -} - -func (a *admitter) validateTokenFields(request *admission.Request) error { - var partial PartialToken - - err := json.Unmarshal(request.Object.Raw, &partial) - if err != nil { - return fmt.Errorf("failed to get PartialToken from request: %w", err) - } - - if partial.LastUsedAt != nil { - if _, err = time.Parse(time.RFC3339, *partial.LastUsedAt); err != nil { - return field.TypeInvalid(field.NewPath("lastUsedAt"), partial.LastUsedAt, err.Error()) - } - } - - return nil -} diff --git a/pkg/resources/management.cattle.io/v3/token/validator_test.go b/pkg/resources/management.cattle.io/v3/token/validator_test.go deleted file mode 100644 index c5d4b8723..000000000 --- a/pkg/resources/management.cattle.io/v3/token/validator_test.go +++ /dev/null @@ -1,135 +0,0 @@ -package token_test - -import ( - "context" - "encoding/json" - "testing" - "time" - - "github.com/rancher/webhook/pkg/admission" - "github.com/rancher/webhook/pkg/resources/management.cattle.io/v3/token" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/suite" - v1 "k8s.io/api/admission/v1" - authenticationv1 "k8s.io/api/authentication/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/utils/pointer" -) - -type TokenFieldsSuite struct { - suite.Suite -} - -func TestTokenFieldsValidation(t *testing.T) { - t.Parallel() - suite.Run(t, new(TokenFieldsSuite)) -} - -var ( - gvk = metav1.GroupVersionKind{Group: "management.cattle.io", Version: "v3", Kind: "Token"} - gvr = metav1.GroupVersionResource{Group: "management.cattle.io", Version: "v3", Resource: "tokens"} -) - -type tokenFieldsTest struct { - lastUsedAt *string - allowed bool -} - -func (t *tokenFieldsTest) name() string { - return pointer.StringDeref(t.lastUsedAt, "nil") -} - -func (t *tokenFieldsTest) toToken() ([]byte, error) { - return json.Marshal(token.PartialToken{ - LastUsedAt: t.lastUsedAt, - }) -} - -var tokenFieldsTests = []tokenFieldsTest{ - { - allowed: true, - }, - { - lastUsedAt: pointer.String(time.Now().Format(time.RFC3339)), - allowed: true, - }, - { - lastUsedAt: pointer.String("2024-03-25T21:2:45Z"), // Not a valid RFC3339 time. - }, - { - lastUsedAt: pointer.String("1w"), - }, - { - lastUsedAt: pointer.String("1d"), - }, - { - lastUsedAt: pointer.String("-1h"), - }, - { - lastUsedAt: pointer.String(""), - }, -} - -func (s *TokenFieldsSuite) TestValidateOnUpdate() { - s.validate(v1.Update) -} - -func (s *TokenFieldsSuite) TestValidateOnCreate() { - s.validate(v1.Create) -} - -func (s *TokenFieldsSuite) TestDontValidateOnDelete() { - // Make sure that Token can be deleted without enforcing validation of user token fields. - alwaysAllow := true - s.validate(v1.Delete, alwaysAllow) -} - -func (s *TokenFieldsSuite) validate(op v1.Operation, allowed ...bool) { - admitter := s.setup() - - for _, test := range tokenFieldsTests { - test := test - s.Run(test.name(), func() { - t := s.T() - t.Parallel() - - objRaw, err := test.toToken() - assert.NoError(t, err, "failed to marshal PartialToken") - - resp, err := admitter.Admit(newRequest(op, objRaw)) - if assert.NoError(t, err, "Admit failed") { - wantAllowed := test.allowed - if len(allowed) > 0 { - wantAllowed = allowed[0] // Apply the override. - } - - assert.Equalf(t, wantAllowed, resp.Allowed, "expected allowed %v got %v message=%v", test.allowed, resp.Allowed, resp.Result) - } - }) - } -} - -func (s *TokenFieldsSuite) setup() admission.Admitter { - validator := token.NewValidator() - s.Len(validator.Admitters(), 1, "expected 1 admitter") - - return validator.Admitters()[0] -} - -func newRequest(op v1.Operation, obj []byte) *admission.Request { - return &admission.Request{ - AdmissionRequest: v1.AdmissionRequest{ - UID: "1", - Kind: gvk, - Resource: gvr, - RequestKind: &gvk, - RequestResource: &gvr, - Operation: op, - UserInfo: authenticationv1.UserInfo{Username: "foo", UID: ""}, - Object: runtime.RawExtension{Raw: obj}, - OldObject: runtime.RawExtension{Raw: []byte("{}")}, - }, - Context: context.Background(), - } -} diff --git a/pkg/server/handlers.go b/pkg/server/handlers.go index f53a5d3c4..dba617595 100644 --- a/pkg/server/handlers.go +++ b/pkg/server/handlers.go @@ -4,7 +4,6 @@ import ( "github.com/rancher/webhook/pkg/admission" "github.com/rancher/webhook/pkg/clients" "github.com/rancher/webhook/pkg/resolvers" - "github.com/rancher/webhook/pkg/resources/cluster.cattle.io/v3/clusterauthtoken" nshandler "github.com/rancher/webhook/pkg/resources/core/v1/namespace" "github.com/rancher/webhook/pkg/resources/core/v1/secret" managementCluster "github.com/rancher/webhook/pkg/resources/management.cattle.io/v3/cluster" @@ -20,7 +19,6 @@ import ( "github.com/rancher/webhook/pkg/resources/management.cattle.io/v3/projectroletemplatebinding" "github.com/rancher/webhook/pkg/resources/management.cattle.io/v3/roletemplate" "github.com/rancher/webhook/pkg/resources/management.cattle.io/v3/setting" - "github.com/rancher/webhook/pkg/resources/management.cattle.io/v3/token" "github.com/rancher/webhook/pkg/resources/management.cattle.io/v3/userattribute" provisioningCluster "github.com/rancher/webhook/pkg/resources/provisioning.cattle.io/v1/cluster" "github.com/rancher/webhook/pkg/resources/rbac.authorization.k8s.io/v1/clusterrole" @@ -57,17 +55,12 @@ func Validation(clients *clients.Clients) ([]admission.ValidatingAdmissionHandle roles := role.NewValidator() rolebindings := rolebinding.NewValidator() setting := setting.NewValidator(clients.Management.Cluster().Cache()) - token := token.NewValidator() userAttribute := userattribute.NewValidator() clusterRoles := clusterrole.NewValidator() clusterRoleBindings := clusterrolebinding.NewValidator() - handlers = append(handlers, psact, globalRoles, globalRoleBindings, prtbs, crtbs, roleTemplates, secrets, nodeDriver, projects, roles, rolebindings, clusterRoles, clusterRoleBindings, clusterProxyConfigs, userAttribute, setting, token) - } else { - clusterAuthTokens := clusterauthtoken.NewValidator() - handlers = append(handlers, clusterAuthTokens) + handlers = append(handlers, psact, globalRoles, globalRoleBindings, prtbs, crtbs, roleTemplates, secrets, nodeDriver, projects, roles, rolebindings, clusterRoles, clusterRoleBindings, clusterProxyConfigs, userAttribute, setting) } - return handlers, nil } From 2db7139b780bf8f278c70135c8a733ca713f4064 Mon Sep 17 00:00:00 2001 From: Jonathan Crowther Date: Wed, 30 Oct 2024 14:14:25 -0400 Subject: [PATCH 3/6] [v0.5] Populate backing namespace field for projects (#533) * Populate backing namespace field for projects * Address comments --- docs.md | 13 +- go.mod | 20 +- go.sum | 36 +-- pkg/codegen/main.go | 1 + .../management.cattle.io/v3/interface.go | 5 + .../management.cattle.io/v3/setting.go | 39 +++ .../v3/globalrolebinding/mutator_test.go | 2 +- .../v3/project/Project.md | 12 + .../v3/project/mutator.go | 111 ++++++- .../v3/project/mutator_test.go | 277 +++++++++++++----- .../v3/project/validator.go | 18 +- .../ProjectRoleTemplateBinding.md | 1 - .../projectroletemplatebinding/validator.go | 3 - .../validator_test.go | 16 - pkg/server/handlers.go | 2 +- 15 files changed, 409 insertions(+), 147 deletions(-) create mode 100644 pkg/generated/controllers/management.cattle.io/v3/setting.go diff --git a/docs.md b/docs.md index a83891bb9..7668438c0 100644 --- a/docs.md +++ b/docs.md @@ -200,6 +200,9 @@ This admission webhook prevents the disabling or deletion of a NodeDriver if the ClusterName must be equal to the namespace, and must refer to an existing management.cattle.io/v3.Cluster object. In addition, users cannot update the field after creation. +#### BackingNamespace validation +The `BackingNamespace` field cannot be changed once set. Projects without the `BackingNamespace` field can have it added. + #### Protects system project The system project cannot be deleted. @@ -219,8 +222,17 @@ Limits for any resource must not be less than requests. #### On create +Populates the `BackingNamespace` field by concatenating `Project.ClusterName` and `Project.Name`. + +If the project is using a generated name (ie `GenerateName` is not empty), the generation happens within the mutating webhook. +The reason for this is that the BackingNamespace is made up of the `Project.Name`, and name generation happens after mutating webhooks and before validating webhooks. + Adds the authz.management.cattle.io/creator-role-bindings annotation. +#### On update + +If the `BackingNamespace` field is empty, it's populated with the project name. + ## ProjectRoleTemplateBinding ### Validation Checks @@ -239,7 +251,6 @@ Users cannot create ProjectRoleTemplateBindings that violate the following const - The `ProjectName` field must be: - Provided as a non-empty value - Specified using the format of `clusterName:projectName`; `clusterName` is the `metadata.name` of a cluster, and `projectName` is the `metadata.name` of a project - - The `projectName` part of the field must match the namespace of the ProjectRoleTemplateBinding - Refer to a valid project and cluster (both must exist and project.Spec.ClusterName must equal the cluster) - Either a user subject (through `UserName` or `UserPrincipalName`), or a group subject (through `GroupName` or `GroupPrincipalName`), or a service account subject (through `ServiceAccount`) must be specified. Exactly one diff --git a/go.mod b/go.mod index 9fd36930e..3adaeb0b8 100644 --- a/go.mod +++ b/go.mod @@ -42,14 +42,14 @@ require ( github.com/gorilla/mux v1.8.1 github.com/rancher/dynamiclistener v0.6.0 github.com/rancher/lasso v0.0.0-20240809125800-8da6f11865d5 - github.com/rancher/rancher/pkg/apis v0.0.0-20240918011937-2f9a7509687f - github.com/rancher/rke v1.6.2 + github.com/rancher/rancher/pkg/apis v0.0.0-20241030145218-455fc5625e09 + github.com/rancher/rke v1.6.4-rc.2 github.com/rancher/wrangler/v3 v3.0.0 github.com/robfig/cron v1.2.0 github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.9.0 golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 - golang.org/x/text v0.18.0 + golang.org/x/text v0.19.0 golang.org/x/tools v0.24.0 k8s.io/api v0.30.2 k8s.io/apimachinery v0.30.2 @@ -108,10 +108,10 @@ require ( github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.47.0 // indirect github.com/prometheus/procfs v0.14.0 // indirect - github.com/rancher/aks-operator v1.9.2 // indirect - github.com/rancher/eks-operator v1.9.2 // indirect + github.com/rancher/aks-operator v1.9.3 // indirect + github.com/rancher/eks-operator v1.9.3 // indirect github.com/rancher/fleet/pkg/apis v0.10.0 // indirect - github.com/rancher/gke-operator v1.9.2 // indirect + github.com/rancher/gke-operator v1.9.3 // indirect github.com/rancher/norman v0.0.0-20240708202514-a0127673d1b9 // indirect github.com/spf13/cobra v1.8.0 // indirect github.com/spf13/pflag v1.0.5 // indirect @@ -130,13 +130,13 @@ require ( go.opentelemetry.io/proto/otlp v1.0.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/crypto v0.27.0 // indirect + golang.org/x/crypto v0.28.0 // indirect golang.org/x/mod v0.20.0 // indirect - golang.org/x/net v0.29.0 // indirect + golang.org/x/net v0.30.0 // indirect golang.org/x/oauth2 v0.23.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/term v0.24.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect golang.org/x/time v0.5.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 // indirect diff --git a/go.sum b/go.sum index fae4f97ae..b7f8606f6 100644 --- a/go.sum +++ b/go.sum @@ -154,22 +154,22 @@ github.com/prometheus/common v0.47.0 h1:p5Cz0FNHo7SnWOmWmoRozVcjEp0bIVU8cV7OShpj github.com/prometheus/common v0.47.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= github.com/prometheus/procfs v0.14.0 h1:Lw4VdGGoKEZilJsayHf0B+9YgLGREba2C6xr+Fdfq6s= github.com/prometheus/procfs v0.14.0/go.mod h1:XL+Iwz8k8ZabyZfMFHPiilCniixqQarAy5Mu67pHlNQ= -github.com/rancher/aks-operator v1.9.2 h1:xlEbRO9qjW0/YoZqbovJXXhkvWsv/NQUrHlI6H9APpg= -github.com/rancher/aks-operator v1.9.2/go.mod h1:Aj8LdDJ2L4BleF8qqyU6phkzEJ79Z3niHx2SbyT3Cg8= +github.com/rancher/aks-operator v1.9.3 h1:2xNaXvMCwjLbzFowoOWGa+Vl/e6plc6JizOnBTw+j9Q= +github.com/rancher/aks-operator v1.9.3/go.mod h1:Aj8LdDJ2L4BleF8qqyU6phkzEJ79Z3niHx2SbyT3Cg8= github.com/rancher/dynamiclistener v0.6.0 h1:M7x8Nq+GY0UORULANuW/AH1ocnyZaqlmTuviMQAHL1Q= github.com/rancher/dynamiclistener v0.6.0/go.mod h1:7VNEQhAwzbYJ08S1MYb6B4vili6K7CcrG4cNZXq1j+s= -github.com/rancher/eks-operator v1.9.2 h1:YF8M2Y4Qj0eT8oIcpkS0WWIJjeIUMiY/xFD3XBv7294= -github.com/rancher/eks-operator v1.9.2/go.mod h1:eQDK+i1UFQQpkTiZmSGX64hpkGLZCju6M9nmaMHcFys= +github.com/rancher/eks-operator v1.9.3 h1:QuJrIUUUJwScn+7SCcJXYL5vGioeNGL/0Y44luSOpuc= +github.com/rancher/eks-operator v1.9.3/go.mod h1:bqVuAcd7jsb46U2ysW0ddxnBWaiVEr16dYDsknPmXgY= github.com/rancher/fleet/pkg/apis v0.10.0 h1:0f8OEghEDJNzvUAR2fpg2dw8EnAgfWvkhnwsYFS9G+w= github.com/rancher/fleet/pkg/apis v0.10.0/go.mod h1:mjirthAmgpz0xo+qywUiaJDFpjnmX3xrc2E0/qmk3yc= -github.com/rancher/gke-operator v1.9.2 h1:vPoqYX+E4Pq1qmZU6qRHSj4zm4q0hHyLKaMfakvyQag= -github.com/rancher/gke-operator v1.9.2/go.mod h1:cvks/IUeoYUNzBcqQ24KIKJ3GFcYyerPpLZ/fZ2CpwE= +github.com/rancher/gke-operator v1.9.3 h1:H8/M0kC3CuiG+W2vP1khF2dHikMVMi0G+v7QyhfcPBg= +github.com/rancher/gke-operator v1.9.3/go.mod h1:XJK1G3PhobniVCWkMhSIrZCjUtCr4I8j+ZmWT0K8afE= github.com/rancher/lasso v0.0.0-20240809125800-8da6f11865d5 h1:qlVhaHTT7wwrI5+AGdkYHpveuoe8Ot4TdQr7LtxmVSk= github.com/rancher/lasso v0.0.0-20240809125800-8da6f11865d5/go.mod h1:Efx/+BbH3ivmnTPLu5cA3Gc9wT5oyGS0LBcqEuYTx+A= github.com/rancher/norman v0.0.0-20240708202514-a0127673d1b9 h1:AlRMRs5mHJcdiK83KKJyFVeybPMZ7dOUzC0l3k9aUa8= github.com/rancher/norman v0.0.0-20240708202514-a0127673d1b9/go.mod h1:dyjfXBsNiroPWOdUZe7diUOUSLf6HQ/r2kEpwH/8zas= -github.com/rancher/rancher/pkg/apis v0.0.0-20240918011937-2f9a7509687f h1:roiCuVuYV6nT7ApMVFYyHCDIorUlFVrdZBUisI/d+XQ= -github.com/rancher/rancher/pkg/apis v0.0.0-20240918011937-2f9a7509687f/go.mod h1:npd+q1ySuNMlQ8B1+2Pt917wNDytCJKkNZQG+QcevuI= +github.com/rancher/rancher/pkg/apis v0.0.0-20241030145218-455fc5625e09 h1:svrjHvtTOJeMrSSHA6QBd4KSTVgRz+O/4ilrt+x6PJY= +github.com/rancher/rancher/pkg/apis v0.0.0-20241030145218-455fc5625e09/go.mod h1:nr+5f718biWnS633d2yF9R1fn4goY4C2meg8mA+ZfZs= github.com/rancher/rke v1.6.2 h1:ttGk77t5oe7bsiS7s7SOFmAl3PALYI5M2SQQenjKevk= github.com/rancher/rke v1.6.2/go.mod h1:5xRbf3L8PxqJRhABjYRfaBqbpVqAnqyH3maUNQEuwvk= github.com/rancher/wrangler/v3 v3.0.0 h1:IHHCA+vrghJDPxjtLk4fmeSCFhNe9fFzLFj3m2B0YpA= @@ -250,8 +250,8 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -264,8 +264,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= -golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -281,15 +281,15 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/pkg/codegen/main.go b/pkg/codegen/main.go index adcbdae6b..b1e10887f 100644 --- a/pkg/codegen/main.go +++ b/pkg/codegen/main.go @@ -46,6 +46,7 @@ func main() { v3.Project{}, v3.ClusterProxyConfig{}, v3.Feature{}, + v3.Setting{}, }, }, "provisioning.cattle.io": { diff --git a/pkg/generated/controllers/management.cattle.io/v3/interface.go b/pkg/generated/controllers/management.cattle.io/v3/interface.go index 5ee673fa7..87e5238dd 100644 --- a/pkg/generated/controllers/management.cattle.io/v3/interface.go +++ b/pkg/generated/controllers/management.cattle.io/v3/interface.go @@ -42,6 +42,7 @@ type Interface interface { Project() ProjectController ProjectRoleTemplateBinding() ProjectRoleTemplateBindingController RoleTemplate() RoleTemplateController + Setting() SettingController } func New(controllerFactory controller.SharedControllerFactory) Interface { @@ -97,3 +98,7 @@ func (v *version) ProjectRoleTemplateBinding() ProjectRoleTemplateBindingControl func (v *version) RoleTemplate() RoleTemplateController { return generic.NewNonNamespacedController[*v3.RoleTemplate, *v3.RoleTemplateList](schema.GroupVersionKind{Group: "management.cattle.io", Version: "v3", Kind: "RoleTemplate"}, "roletemplates", v.controllerFactory) } + +func (v *version) Setting() SettingController { + return generic.NewNonNamespacedController[*v3.Setting, *v3.SettingList](schema.GroupVersionKind{Group: "management.cattle.io", Version: "v3", Kind: "Setting"}, "settings", v.controllerFactory) +} diff --git a/pkg/generated/controllers/management.cattle.io/v3/setting.go b/pkg/generated/controllers/management.cattle.io/v3/setting.go new file mode 100644 index 000000000..3ef6b3c0d --- /dev/null +++ b/pkg/generated/controllers/management.cattle.io/v3/setting.go @@ -0,0 +1,39 @@ +/* +Copyright 2024 Rancher Labs, Inc. + +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. +*/ + +// Code generated by codegen. DO NOT EDIT. + +package v3 + +import ( + v3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3" + "github.com/rancher/wrangler/v3/pkg/generic" +) + +// SettingController interface for managing Setting resources. +type SettingController interface { + generic.NonNamespacedControllerInterface[*v3.Setting, *v3.SettingList] +} + +// SettingClient interface for managing Setting resources in Kubernetes. +type SettingClient interface { + generic.NonNamespacedClientInterface[*v3.Setting, *v3.SettingList] +} + +// SettingCache interface for retrieving Setting resources in memory. +type SettingCache interface { + generic.NonNamespacedCacheInterface[*v3.Setting] +} diff --git a/pkg/resources/management.cattle.io/v3/globalrolebinding/mutator_test.go b/pkg/resources/management.cattle.io/v3/globalrolebinding/mutator_test.go index 7b6b8deb0..7675804a6 100644 --- a/pkg/resources/management.cattle.io/v3/globalrolebinding/mutator_test.go +++ b/pkg/resources/management.cattle.io/v3/globalrolebinding/mutator_test.go @@ -358,7 +358,7 @@ func Test_MutatorAdmit(t *testing.T) { gotObj := &apisv3.GlobalRoleBinding{} err = json.Unmarshal(patchedJS, gotObj) - require.NoError(t, err, "failed to unmarshall patched Object") + require.NoError(t, err, "failed to unmarshal patched Object") require.True(t, equality.Semantic.DeepEqual(test.wantGRB(), gotObj), "patched object and desired object are not equivalent wanted=%#v got=%#v", test.wantGRB(), gotObj) } else { diff --git a/pkg/resources/management.cattle.io/v3/project/Project.md b/pkg/resources/management.cattle.io/v3/project/Project.md index e428bdb50..5146439a4 100644 --- a/pkg/resources/management.cattle.io/v3/project/Project.md +++ b/pkg/resources/management.cattle.io/v3/project/Project.md @@ -4,6 +4,9 @@ ClusterName must be equal to the namespace, and must refer to an existing management.cattle.io/v3.Cluster object. In addition, users cannot update the field after creation. +### BackingNamespace validation +The `BackingNamespace` field cannot be changed once set. Projects without the `BackingNamespace` field can have it added. + ### Protects system project The system project cannot be deleted. @@ -23,4 +26,13 @@ Limits for any resource must not be less than requests. ### On create +Populates the `BackingNamespace` field by concatenating `Project.ClusterName` and `Project.Name`. + +If the project is using a generated name (ie `GenerateName` is not empty), the generation happens within the mutating webhook. +The reason for this is that the BackingNamespace is made up of the `Project.Name`, and name generation happens after mutating webhooks and before validating webhooks. + Adds the authz.management.cattle.io/creator-role-bindings annotation. + +### On update + +If the `BackingNamespace` field is empty, it's populated with the project name. diff --git a/pkg/resources/management.cattle.io/v3/project/mutator.go b/pkg/resources/management.cattle.io/v3/project/mutator.go index b8e087d69..a40b024bc 100644 --- a/pkg/resources/management.cattle.io/v3/project/mutator.go +++ b/pkg/resources/management.cattle.io/v3/project/mutator.go @@ -3,16 +3,22 @@ package project import ( "encoding/json" "fmt" + "strings" v3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3" "github.com/rancher/webhook/pkg/admission" ctrlv3 "github.com/rancher/webhook/pkg/generated/controllers/management.cattle.io/v3" objectsv3 "github.com/rancher/webhook/pkg/generated/objects/management.cattle.io/v3" "github.com/rancher/webhook/pkg/patch" + corev1controller "github.com/rancher/wrangler/v3/pkg/generated/controllers/core/v1" + "github.com/rancher/wrangler/v3/pkg/name" "github.com/sirupsen/logrus" admissionv1 "k8s.io/api/admission/v1" admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apiserver/pkg/storage/names" "k8s.io/utils/trace" ) @@ -20,6 +26,7 @@ const ( roleTemplatesRequired = "authz.management.cattle.io/creator-role-bindings" indexKey = "creatorDefaultUnlocked" mutatorCreatorRoleTemplateIndex = "webhook.cattle.io/creator-role-template-index" + projectNamespaceSetting = "use-project-namespace-fix" ) var gvr = schema.GroupVersionResource{ @@ -31,13 +38,19 @@ var gvr = schema.GroupVersionResource{ // Mutator implements admission.MutatingAdmissionWebhook. type Mutator struct { roleTemplateCache ctrlv3.RoleTemplateCache + namespaceClient corev1controller.NamespaceController + projectClient ctrlv3.ProjectClient + settingClient ctrlv3.SettingClient } // NewMutator returns a new mutator which mutates projects -func NewMutator(roleTemplateCache ctrlv3.RoleTemplateCache) *Mutator { +func NewMutator(nsClient corev1controller.NamespaceController, roleTemplateCache ctrlv3.RoleTemplateCache, projectClient ctrlv3.ProjectClient, settingClient ctrlv3.SettingClient) *Mutator { roleTemplateCache.AddIndexer(mutatorCreatorRoleTemplateIndex, creatorRoleTemplateIndexer) return &Mutator{ roleTemplateCache: roleTemplateCache, + namespaceClient: nsClient, + projectClient: projectClient, + settingClient: settingClient, } } @@ -58,6 +71,7 @@ func (m *Mutator) GVR() schema.GroupVersionResource { func (m *Mutator) Operations() []admissionregistrationv1.OperationType { return []admissionregistrationv1.OperationType{ admissionregistrationv1.Create, + admissionregistrationv1.Update, } } @@ -85,13 +99,88 @@ func (m *Mutator) Admit(request *admission.Request) (*admissionv1.AdmissionRespo } switch request.Operation { case admissionv1.Create: - return m.admitCreate(project, request) + project, err = m.createProjectNamespace(project) + if err != nil { + return nil, err + } + project, err = m.addCreatorRoleBindings(project) + if err != nil { + return nil, err + } + case admissionv1.Update: + project = m.updateProjectNamespace(project) default: return nil, fmt.Errorf("operation type %q not handled", request.Operation) } + response := &admissionv1.AdmissionResponse{} + if err := patch.CreatePatch(request.Object.Raw, project, response); err != nil { + return nil, fmt.Errorf("failed to create patch: %w", err) + } + response.Allowed = true + return response, nil } -func (m *Mutator) admitCreate(project *v3.Project, request *admission.Request) (*admissionv1.AdmissionResponse, error) { +func (m *Mutator) createProjectNamespace(project *v3.Project) (*v3.Project, error) { + setting, err := m.settingClient.Get(projectNamespaceSetting, v1.GetOptions{}) + if err != nil { + return nil, err + } + if effectiveValue(*setting) != "true" { + return project, nil + } + + newProject := project.DeepCopy() + backingNamespace := "" + // When the project name is empty, that means we want to generate a name for it + // Name generation happens after mutating webhooks, so in order to have access to the name early + // for the backing namespace, we need to generate it ourselves + if project.Name == "" { + // If err is nil, (meaning "project exists", see below) we need to repeat the generation process to find a project name and backing namespace that isn't taken + for err == nil { + newName := names.SimpleNameGenerator.GenerateName(project.GenerateName) + _, err = m.projectClient.Get(newProject.Spec.ClusterName, newName, v1.GetOptions{}) + if err == nil { + // A project with this name already exists. Generate a new name. + continue + } else if !apierrors.IsNotFound(err) { + return nil, err + } + + backingNamespace = name.SafeConcatName(newProject.Spec.ClusterName, strings.ToLower(newName)) + _, err = m.namespaceClient.Get(backingNamespace, v1.GetOptions{}) + + // If the backing namespace already exists, generate a new project name + if err != nil && !apierrors.IsNotFound(err) { + return nil, err + } + } + } else { + backingNamespace = name.SafeConcatName(newProject.Spec.ClusterName, strings.ToLower(newProject.Name)) + _, err = m.namespaceClient.Get(backingNamespace, v1.GetOptions{}) + if err == nil { + return nil, fmt.Errorf("failed to create project: namespace %s already exists", backingNamespace) + } else if !apierrors.IsNotFound(err) { + return nil, err + } + } + + newProject.Status.BackingNamespace = backingNamespace + return newProject, nil +} + +// updateProjectNamespace fills in BackingNamespace with the project name if it wasn't already set. +// This was the naming convention of project namespaces prior to using the BackingNamespace field. +// Filling it here is just to maintain backwards compatibility. +func (m *Mutator) updateProjectNamespace(project *v3.Project) *v3.Project { + if project.Status.BackingNamespace != "" { + return project + } + newProject := project.DeepCopy() + newProject.Status.BackingNamespace = newProject.Name + return newProject +} + +func (m *Mutator) addCreatorRoleBindings(project *v3.Project) (*v3.Project, error) { logrus.Debugf("[project-mutation] adding creator-role-bindings to project: %v", project.Name) newProject := project.DeepCopy() @@ -103,12 +192,7 @@ func (m *Mutator) admitCreate(project *v3.Project, request *admission.Request) ( return nil, fmt.Errorf("failed to add annotation to project %s: %w", project.Name, err) } newProject.Annotations[roleTemplatesRequired] = annotations - response := &admissionv1.AdmissionResponse{} - if err := patch.CreatePatch(request.Object.Raw, newProject, response); err != nil { - return nil, fmt.Errorf("failed to create patch: %w", err) - } - response.Allowed = true - return response, nil + return newProject, nil } func (m *Mutator) getCreatorRoleTemplateAnnotations() (string, error) { @@ -126,3 +210,12 @@ func (m *Mutator) getCreatorRoleTemplateAnnotations() (string, error) { } return string(annotations), nil } + +func effectiveValue(s v3.Setting) string { + if s.Value != "" { + return s.Value + } else if s.Default != "" { + return s.Default + } + return "" +} diff --git a/pkg/resources/management.cattle.io/v3/project/mutator_test.go b/pkg/resources/management.cattle.io/v3/project/mutator_test.go index e92299c23..5605eb551 100644 --- a/pkg/resources/management.cattle.io/v3/project/mutator_test.go +++ b/pkg/resources/management.cattle.io/v3/project/mutator_test.go @@ -5,12 +5,16 @@ import ( "fmt" "testing" + jsonpatch "github.com/evanphx/json-patch" "github.com/golang/mock/gomock" v3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3" "github.com/rancher/wrangler/v3/pkg/generic/fake" "github.com/stretchr/testify/assert" admissionv1 "k8s.io/api/admission/v1" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" ) const ( @@ -18,112 +22,195 @@ const ( expectedIndexKey = "creatorDefaultUnlocked" ) +var ( + defaultProject = v3.Project{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testproject", + }, + Spec: v3.ProjectSpec{ + ClusterName: "testcluster", + }, + } + emptyProject = func() *v3.Project { + return &v3.Project{} + } +) + func TestAdmit(t *testing.T) { t.Parallel() tests := []struct { - name string - operation admissionv1.Operation - dryRun bool - oldProject *v3.Project - newProject *v3.Project - indexer func() ([]*v3.RoleTemplate, error) - wantPatch []map[string]interface{} - wantErr bool + name string + operation admissionv1.Operation + dryRun bool + projectNamespaceSetting string + oldProject func() *v3.Project + newProject func() *v3.Project + indexer func() ([]*v3.RoleTemplate, error) + wantProject func() *v3.Project + wantErr bool }{ { - name: "dry run returns allowed", - operation: admissionv1.Update, - dryRun: true, - newProject: &v3.Project{}, + name: "dry run returns allowed", + operation: admissionv1.Update, + dryRun: true, + newProject: emptyProject, + projectNamespaceSetting: "true", }, { - name: "failure to decode project returns error", - newProject: nil, - wantErr: true, + name: "failure to decode project returns error", + newProject: nil, + wantErr: true, + projectNamespaceSetting: "true", }, { - name: "delete operation is invalid", - operation: admissionv1.Delete, - newProject: &v3.Project{}, - oldProject: &v3.Project{}, - wantErr: true, + name: "delete operation is invalid", + operation: admissionv1.Delete, + newProject: emptyProject, + oldProject: emptyProject, + wantErr: true, + projectNamespaceSetting: "true", }, { - name: "update operation is invalid", - operation: admissionv1.Update, - newProject: &v3.Project{}, - oldProject: &v3.Project{}, - wantErr: true, + name: "update operation is valid and adds backingNamespace", + operation: admissionv1.Update, + newProject: func() *v3.Project { + return &v3.Project{ + ObjectMeta: metav1.ObjectMeta{ + Name: "p-abc123", + }, + } + }, + oldProject: func() *v3.Project { + return &v3.Project{ + ObjectMeta: metav1.ObjectMeta{ + Name: "p-abc123", + }, + } + }, + wantProject: func() *v3.Project { + return &v3.Project{ + ObjectMeta: metav1.ObjectMeta{ + Name: "p-abc123", + }, + Status: v3.ProjectStatus{ + BackingNamespace: "p-abc123", + }, + } + }, + projectNamespaceSetting: "true", }, { - name: "connect operation is invalid", - operation: admissionv1.Connect, - newProject: &v3.Project{}, - oldProject: &v3.Project{}, - wantErr: true, + name: "update operation is valid and adds backingNamespace, even when setting is false", + operation: admissionv1.Update, + newProject: func() *v3.Project { + return &v3.Project{ + ObjectMeta: metav1.ObjectMeta{ + Name: "p-abc123", + }, + } + }, + oldProject: func() *v3.Project { + return &v3.Project{ + ObjectMeta: metav1.ObjectMeta{ + Name: "p-abc123", + }, + } + }, + wantProject: func() *v3.Project { + return &v3.Project{ + ObjectMeta: metav1.ObjectMeta{ + Name: "p-abc123", + }, + Status: v3.ProjectStatus{ + BackingNamespace: "p-abc123", + }, + } + }, + projectNamespaceSetting: "false", }, { - name: "indexer error", - operation: admissionv1.Create, - newProject: &v3.Project{}, - indexer: func() ([]*v3.RoleTemplate, error) { return nil, fmt.Errorf("indexer error") }, - wantErr: true, + name: "connect operation is invalid", + operation: admissionv1.Connect, + newProject: emptyProject, + oldProject: emptyProject, + wantErr: true, + projectNamespaceSetting: "true", + }, + { + name: "indexer error", + operation: admissionv1.Create, + newProject: emptyProject, + indexer: func() ([]*v3.RoleTemplate, error) { return nil, fmt.Errorf("indexer error") }, + wantErr: true, + projectNamespaceSetting: "true", }, { name: "indexer returns empty", operation: admissionv1.Create, - newProject: &v3.Project{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testproject", - }, + newProject: func() *v3.Project { + return defaultProject.DeepCopy() }, indexer: func() ([]*v3.RoleTemplate, error) { return nil, nil }, - wantPatch: []map[string]interface{}{ - { - "op": "add", - "path": "/metadata/annotations", - "value": map[string]string{ - "authz.management.cattle.io/creator-role-bindings": "{}", - }, - }, + wantProject: func() *v3.Project { + p := defaultProject.DeepCopy() + p.Annotations = map[string]string{ + "authz.management.cattle.io/creator-role-bindings": "{}", + } + p.Status.BackingNamespace = "testcluster-testproject" + return p }, + projectNamespaceSetting: "true", }, { - name: "created project gets annotation added", + name: "created project gets annotation added and backing namespace", operation: admissionv1.Create, - newProject: &v3.Project{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testproject", - }, + newProject: func() *v3.Project { + return defaultProject.DeepCopy() }, - wantPatch: []map[string]interface{}{ - { - "op": "add", - "path": "/metadata/annotations", - "value": map[string]string{ - "authz.management.cattle.io/creator-role-bindings": "{\"required\":[\"project-owner\"]}", - }, - }, + wantProject: func() *v3.Project { + p := defaultProject.DeepCopy() + p.Annotations = map[string]string{ + "authz.management.cattle.io/creator-role-bindings": "{\"required\":[\"project-owner\"]}", + } + p.Status.BackingNamespace = "testcluster-testproject" + return p + }, + projectNamespaceSetting: "true", + }, + { + name: "created project adds annotation but not backing namespace if setting is false", + operation: admissionv1.Create, + newProject: func() *v3.Project { + return defaultProject.DeepCopy() + }, + wantProject: func() *v3.Project { + p := defaultProject.DeepCopy() + p.Annotations = map[string]string{ + "authz.management.cattle.io/creator-role-bindings": "{\"required\":[\"project-owner\"]}", + } + return p }, + projectNamespaceSetting: "false", }, { name: "override user-set annotations", operation: admissionv1.Create, - newProject: &v3.Project{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testproject", - Annotations: map[string]string{ - "authz.management.cattle.io/creator-role-bindings": "my own setting", - }, - }, + newProject: func() *v3.Project { + p := defaultProject.DeepCopy() + p.Annotations = map[string]string{ + "authz.management.cattle.io/creator-role-bindings": "my own setting", + } + return p }, - wantPatch: []map[string]interface{}{ - { - "op": "replace", - "path": "/metadata/annotations/authz.management.cattle.io~1creator-role-bindings", - "value": "{\"required\":[\"project-owner\"]}", - }, + wantProject: func() *v3.Project { + p := defaultProject.DeepCopy() + p.Annotations = map[string]string{ + "authz.management.cattle.io/creator-role-bindings": "{\"required\":[\"project-owner\"]}", + } + p.Status.BackingNamespace = "testcluster-testproject" + return p }, + projectNamespaceSetting: "true", }, } @@ -145,8 +232,14 @@ func TestAdmit(t *testing.T) { test := test t.Run(test.name, func(t *testing.T) { t.Parallel() - req, err := createProjectRequest(test.oldProject, test.newProject, test.operation, test.dryRun) - assert.NoError(t, err) + + ctrl := gomock.NewController(t) + nsMock := fake.NewMockNonNamespacedControllerInterface[*corev1.Namespace, *corev1.NamespaceList](ctrl) + nsMock.EXPECT().Get(gomock.Any(), metav1.GetOptions{}).Return(nil, apierrors.NewNotFound(schema.GroupResource{}, "")).AnyTimes() + projectMock := fake.NewMockClientInterface[*v3.Project, *v3.ProjectList](ctrl) + projectMock.EXPECT().Get(gomock.Any(), gomock.Any(), metav1.GetOptions{}).Return(nil, apierrors.NewNotFound(schema.GroupResource{}, "")).AnyTimes() + settingMock := fake.NewMockNonNamespacedClientInterface[*v3.Setting, *v3.SettingList](ctrl) + settingMock.EXPECT().Get(gomock.Any(), gomock.Any()).Return(&v3.Setting{Default: test.projectNamespaceSetting}, nil).AnyTimes() roleTemplateCache := fake.NewMockNonNamespacedCacheInterface[*v3.RoleTemplate](gomock.NewController(t)) roleTemplateCache.EXPECT().AddIndexer(expectedIndexerName, gomock.Any()) indexer := defaultIndexer @@ -155,19 +248,41 @@ func TestAdmit(t *testing.T) { } returnedRTs, returnedErr := indexer() roleTemplateCache.EXPECT().GetByIndex(expectedIndexerName, expectedIndexKey).Return(returnedRTs, returnedErr).AnyTimes() - m := NewMutator(roleTemplateCache) + + var oldProject, newProject *v3.Project + if test.oldProject != nil { + oldProject = test.oldProject() + } + if test.newProject != nil { + newProject = test.newProject() + } + req, err := createProjectRequest(oldProject, newProject, test.operation, test.dryRun) + assert.NoError(t, err) + m := NewMutator(nsMock, roleTemplateCache, projectMock, settingMock) + resp, err := m.Admit(req) + if test.wantErr { assert.Error(t, err) return } + assert.NoError(t, err, "Admit failed") assert.Equal(t, true, resp.Allowed) - var wantPatch []byte - if test.wantPatch != nil { - wantPatch, err = json.Marshal(test.wantPatch) - assert.NoError(t, err) + if test.wantProject != nil { + patchObj, err := jsonpatch.DecodePatch(resp.Patch) + assert.NoError(t, err, "failed to decode patch from response") + + patchedJS, err := patchObj.Apply(req.Object.Raw) + assert.NoError(t, err, "failed to apply patch to Object") + + gotObj := &v3.Project{} + err = json.Unmarshal(patchedJS, gotObj) + assert.NoError(t, err, "failed to unmarshal patched Object") + + assert.Equal(t, test.wantProject(), gotObj) + } else { + assert.Nil(t, resp.Patch, "unexpected patch request received") } - assert.Equal(t, string(wantPatch), string(resp.Patch)) }) } } diff --git a/pkg/resources/management.cattle.io/v3/project/validator.go b/pkg/resources/management.cattle.io/v3/project/validator.go index 7c59519b7..49111c5d2 100644 --- a/pkg/resources/management.cattle.io/v3/project/validator.go +++ b/pkg/resources/management.cattle.io/v3/project/validator.go @@ -22,11 +22,12 @@ import ( ) const ( - systemProjectLabel = "authz.management.cattle.io/system-project" - projectQuotaField = "resourceQuota" - clusterNameField = "clusterName" - namespaceQuotaField = "namespaceDefaultResourceQuota" - containerLimitField = "containerDefaultResourceLimit" + systemProjectLabel = "authz.management.cattle.io/system-project" + projectQuotaField = "resourceQuota" + clusterNameField = "clusterName" + backingNamespaceField = "backingNamespace" + namespaceQuotaField = "namespaceDefaultResourceQuota" + containerLimitField = "containerDefaultResourceLimit" ) var projectSpecFieldPath = field.NewPath("project").Child("spec") @@ -115,8 +116,13 @@ func (a *admitter) admitCreate(project *v3.Project) (*admissionv1.AdmissionRespo } func (a *admitter) admitUpdate(oldProject, newProject *v3.Project) (*admissionv1.AdmissionResponse, error) { + var fieldErr *field.Error if oldProject.Spec.ClusterName != newProject.Spec.ClusterName { - fieldErr := field.Invalid(projectSpecFieldPath.Child(clusterNameField), newProject.Spec.ClusterName, "field is immutable") + fieldErr = field.Invalid(projectSpecFieldPath.Child(clusterNameField), newProject.Spec.ClusterName, "field is immutable") + } else if oldProject.Status.BackingNamespace != "" && oldProject.Status.BackingNamespace != newProject.Status.BackingNamespace { + fieldErr = field.Invalid(projectSpecFieldPath.Child(backingNamespaceField), newProject.Status.BackingNamespace, "field is immutable") + } + if fieldErr != nil { return admission.ResponseBadRequest(fieldErr.Error()), nil } return a.admitCommonCreateUpdate(oldProject, newProject) diff --git a/pkg/resources/management.cattle.io/v3/projectroletemplatebinding/ProjectRoleTemplateBinding.md b/pkg/resources/management.cattle.io/v3/projectroletemplatebinding/ProjectRoleTemplateBinding.md index dc57d547e..baa225921 100644 --- a/pkg/resources/management.cattle.io/v3/projectroletemplatebinding/ProjectRoleTemplateBinding.md +++ b/pkg/resources/management.cattle.io/v3/projectroletemplatebinding/ProjectRoleTemplateBinding.md @@ -14,7 +14,6 @@ Users cannot create ProjectRoleTemplateBindings that violate the following const - The `ProjectName` field must be: - Provided as a non-empty value - Specified using the format of `clusterName:projectName`; `clusterName` is the `metadata.name` of a cluster, and `projectName` is the `metadata.name` of a project - - The `projectName` part of the field must match the namespace of the ProjectRoleTemplateBinding - Refer to a valid project and cluster (both must exist and project.Spec.ClusterName must equal the cluster) - Either a user subject (through `UserName` or `UserPrincipalName`), or a group subject (through `GroupName` or `GroupPrincipalName`), or a service account subject (through `ServiceAccount`) must be specified. Exactly one diff --git a/pkg/resources/management.cattle.io/v3/projectroletemplatebinding/validator.go b/pkg/resources/management.cattle.io/v3/projectroletemplatebinding/validator.go index 1c80e77d9..745b74da8 100644 --- a/pkg/resources/management.cattle.io/v3/projectroletemplatebinding/validator.go +++ b/pkg/resources/management.cattle.io/v3/projectroletemplatebinding/validator.go @@ -210,9 +210,6 @@ func (a *admitter) validateCreateFields(newPRTB *apisv3.ProjectRoleTemplateBindi if clusterName == "" || projectName == "" { return field.Invalid(fieldPath.Child("projectName"), newPRTB.ProjectName, "projectName must be of the form cluster.metadata.name:project.metadata.name, and both must refer to an existing object") } - if projectName != newPRTB.Namespace { - return field.Forbidden(fieldPath, "namespace and the projectName part of projectName must match") - } cluster, err := a.clusterCache.Get(clusterName) clusterNotFoundErr := field.Invalid(fieldPath.Child("projectName"), newPRTB.ProjectName, fmt.Sprintf("specified cluster %s not found", clusterName)) if err != nil { diff --git a/pkg/resources/management.cattle.io/v3/projectroletemplatebinding/validator_test.go b/pkg/resources/management.cattle.io/v3/projectroletemplatebinding/validator_test.go index bd06e14fc..45ba8a075 100644 --- a/pkg/resources/management.cattle.io/v3/projectroletemplatebinding/validator_test.go +++ b/pkg/resources/management.cattle.io/v3/projectroletemplatebinding/validator_test.go @@ -998,22 +998,6 @@ func (p *ProjectRoleTemplateBindingSuite) TestValidationOnCreate() { }, allowed: false, }, - { - name: "namespace and the project id part of the project name differ", - args: args{ - username: adminUser, - oldPRTB: func() *apisv3.ProjectRoleTemplateBinding { - return nil - }, - newPRTB: func() *apisv3.ProjectRoleTemplateBinding { - basePRTB := newBasePRTB() - basePRTB.ObjectMeta.Namespace = "default" - basePRTB.ProjectName = fmt.Sprintf("%s:%s", clusterID, "p-cgtq4") - return basePRTB - }, - }, - allowed: false, - }, { name: "missing cluster name", args: args{ diff --git a/pkg/server/handlers.go b/pkg/server/handlers.go index dba617595..09fae655b 100644 --- a/pkg/server/handlers.go +++ b/pkg/server/handlers.go @@ -75,7 +75,7 @@ func Mutation(clients *clients.Clients) ([]admission.MutatingAdmissionHandler, e if clients.MultiClusterManagement { secrets := secret.NewMutator(clients.RBAC.Role(), clients.RBAC.RoleBinding()) - projects := project.NewMutator(clients.Management.RoleTemplate().Cache()) + projects := project.NewMutator(clients.Core.Namespace(), clients.Management.RoleTemplate().Cache(), clients.Management.Project(), clients.Management.Setting()) grbs := globalrolebinding.NewMutator(clients.Management.GlobalRole().Cache()) mutators = append(mutators, secrets, projects, grbs) } From cb8e6a49bf1597400653839323b9d9ac54bbc7e9 Mon Sep 17 00:00:00 2001 From: Jonathan Crowther Date: Tue, 5 Nov 2024 09:54:50 -0500 Subject: [PATCH 4/6] Revert "[v0.5] Populate backing namespace field for projects (#533)" (#538) This reverts commit 2db7139b780bf8f278c70135c8a733ca713f4064. --- docs.md | 13 +- go.mod | 20 +- go.sum | 36 +-- pkg/codegen/main.go | 1 - .../management.cattle.io/v3/interface.go | 5 - .../management.cattle.io/v3/setting.go | 39 --- .../v3/globalrolebinding/mutator_test.go | 2 +- .../v3/project/Project.md | 12 - .../v3/project/mutator.go | 111 +------ .../v3/project/mutator_test.go | 277 +++++------------- .../v3/project/validator.go | 18 +- .../ProjectRoleTemplateBinding.md | 1 + .../projectroletemplatebinding/validator.go | 3 + .../validator_test.go | 16 + pkg/server/handlers.go | 2 +- 15 files changed, 147 insertions(+), 409 deletions(-) delete mode 100644 pkg/generated/controllers/management.cattle.io/v3/setting.go diff --git a/docs.md b/docs.md index 7668438c0..a83891bb9 100644 --- a/docs.md +++ b/docs.md @@ -200,9 +200,6 @@ This admission webhook prevents the disabling or deletion of a NodeDriver if the ClusterName must be equal to the namespace, and must refer to an existing management.cattle.io/v3.Cluster object. In addition, users cannot update the field after creation. -#### BackingNamespace validation -The `BackingNamespace` field cannot be changed once set. Projects without the `BackingNamespace` field can have it added. - #### Protects system project The system project cannot be deleted. @@ -222,17 +219,8 @@ Limits for any resource must not be less than requests. #### On create -Populates the `BackingNamespace` field by concatenating `Project.ClusterName` and `Project.Name`. - -If the project is using a generated name (ie `GenerateName` is not empty), the generation happens within the mutating webhook. -The reason for this is that the BackingNamespace is made up of the `Project.Name`, and name generation happens after mutating webhooks and before validating webhooks. - Adds the authz.management.cattle.io/creator-role-bindings annotation. -#### On update - -If the `BackingNamespace` field is empty, it's populated with the project name. - ## ProjectRoleTemplateBinding ### Validation Checks @@ -251,6 +239,7 @@ Users cannot create ProjectRoleTemplateBindings that violate the following const - The `ProjectName` field must be: - Provided as a non-empty value - Specified using the format of `clusterName:projectName`; `clusterName` is the `metadata.name` of a cluster, and `projectName` is the `metadata.name` of a project + - The `projectName` part of the field must match the namespace of the ProjectRoleTemplateBinding - Refer to a valid project and cluster (both must exist and project.Spec.ClusterName must equal the cluster) - Either a user subject (through `UserName` or `UserPrincipalName`), or a group subject (through `GroupName` or `GroupPrincipalName`), or a service account subject (through `ServiceAccount`) must be specified. Exactly one diff --git a/go.mod b/go.mod index 3adaeb0b8..9fd36930e 100644 --- a/go.mod +++ b/go.mod @@ -42,14 +42,14 @@ require ( github.com/gorilla/mux v1.8.1 github.com/rancher/dynamiclistener v0.6.0 github.com/rancher/lasso v0.0.0-20240809125800-8da6f11865d5 - github.com/rancher/rancher/pkg/apis v0.0.0-20241030145218-455fc5625e09 - github.com/rancher/rke v1.6.4-rc.2 + github.com/rancher/rancher/pkg/apis v0.0.0-20240918011937-2f9a7509687f + github.com/rancher/rke v1.6.2 github.com/rancher/wrangler/v3 v3.0.0 github.com/robfig/cron v1.2.0 github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.9.0 golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 - golang.org/x/text v0.19.0 + golang.org/x/text v0.18.0 golang.org/x/tools v0.24.0 k8s.io/api v0.30.2 k8s.io/apimachinery v0.30.2 @@ -108,10 +108,10 @@ require ( github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.47.0 // indirect github.com/prometheus/procfs v0.14.0 // indirect - github.com/rancher/aks-operator v1.9.3 // indirect - github.com/rancher/eks-operator v1.9.3 // indirect + github.com/rancher/aks-operator v1.9.2 // indirect + github.com/rancher/eks-operator v1.9.2 // indirect github.com/rancher/fleet/pkg/apis v0.10.0 // indirect - github.com/rancher/gke-operator v1.9.3 // indirect + github.com/rancher/gke-operator v1.9.2 // indirect github.com/rancher/norman v0.0.0-20240708202514-a0127673d1b9 // indirect github.com/spf13/cobra v1.8.0 // indirect github.com/spf13/pflag v1.0.5 // indirect @@ -130,13 +130,13 @@ require ( go.opentelemetry.io/proto/otlp v1.0.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/crypto v0.28.0 // indirect + golang.org/x/crypto v0.27.0 // indirect golang.org/x/mod v0.20.0 // indirect - golang.org/x/net v0.30.0 // indirect + golang.org/x/net v0.29.0 // indirect golang.org/x/oauth2 v0.23.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.26.0 // indirect - golang.org/x/term v0.25.0 // indirect + golang.org/x/sys v0.25.0 // indirect + golang.org/x/term v0.24.0 // indirect golang.org/x/time v0.5.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 // indirect diff --git a/go.sum b/go.sum index b7f8606f6..fae4f97ae 100644 --- a/go.sum +++ b/go.sum @@ -154,22 +154,22 @@ github.com/prometheus/common v0.47.0 h1:p5Cz0FNHo7SnWOmWmoRozVcjEp0bIVU8cV7OShpj github.com/prometheus/common v0.47.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= github.com/prometheus/procfs v0.14.0 h1:Lw4VdGGoKEZilJsayHf0B+9YgLGREba2C6xr+Fdfq6s= github.com/prometheus/procfs v0.14.0/go.mod h1:XL+Iwz8k8ZabyZfMFHPiilCniixqQarAy5Mu67pHlNQ= -github.com/rancher/aks-operator v1.9.3 h1:2xNaXvMCwjLbzFowoOWGa+Vl/e6plc6JizOnBTw+j9Q= -github.com/rancher/aks-operator v1.9.3/go.mod h1:Aj8LdDJ2L4BleF8qqyU6phkzEJ79Z3niHx2SbyT3Cg8= +github.com/rancher/aks-operator v1.9.2 h1:xlEbRO9qjW0/YoZqbovJXXhkvWsv/NQUrHlI6H9APpg= +github.com/rancher/aks-operator v1.9.2/go.mod h1:Aj8LdDJ2L4BleF8qqyU6phkzEJ79Z3niHx2SbyT3Cg8= github.com/rancher/dynamiclistener v0.6.0 h1:M7x8Nq+GY0UORULANuW/AH1ocnyZaqlmTuviMQAHL1Q= github.com/rancher/dynamiclistener v0.6.0/go.mod h1:7VNEQhAwzbYJ08S1MYb6B4vili6K7CcrG4cNZXq1j+s= -github.com/rancher/eks-operator v1.9.3 h1:QuJrIUUUJwScn+7SCcJXYL5vGioeNGL/0Y44luSOpuc= -github.com/rancher/eks-operator v1.9.3/go.mod h1:bqVuAcd7jsb46U2ysW0ddxnBWaiVEr16dYDsknPmXgY= +github.com/rancher/eks-operator v1.9.2 h1:YF8M2Y4Qj0eT8oIcpkS0WWIJjeIUMiY/xFD3XBv7294= +github.com/rancher/eks-operator v1.9.2/go.mod h1:eQDK+i1UFQQpkTiZmSGX64hpkGLZCju6M9nmaMHcFys= github.com/rancher/fleet/pkg/apis v0.10.0 h1:0f8OEghEDJNzvUAR2fpg2dw8EnAgfWvkhnwsYFS9G+w= github.com/rancher/fleet/pkg/apis v0.10.0/go.mod h1:mjirthAmgpz0xo+qywUiaJDFpjnmX3xrc2E0/qmk3yc= -github.com/rancher/gke-operator v1.9.3 h1:H8/M0kC3CuiG+W2vP1khF2dHikMVMi0G+v7QyhfcPBg= -github.com/rancher/gke-operator v1.9.3/go.mod h1:XJK1G3PhobniVCWkMhSIrZCjUtCr4I8j+ZmWT0K8afE= +github.com/rancher/gke-operator v1.9.2 h1:vPoqYX+E4Pq1qmZU6qRHSj4zm4q0hHyLKaMfakvyQag= +github.com/rancher/gke-operator v1.9.2/go.mod h1:cvks/IUeoYUNzBcqQ24KIKJ3GFcYyerPpLZ/fZ2CpwE= github.com/rancher/lasso v0.0.0-20240809125800-8da6f11865d5 h1:qlVhaHTT7wwrI5+AGdkYHpveuoe8Ot4TdQr7LtxmVSk= github.com/rancher/lasso v0.0.0-20240809125800-8da6f11865d5/go.mod h1:Efx/+BbH3ivmnTPLu5cA3Gc9wT5oyGS0LBcqEuYTx+A= github.com/rancher/norman v0.0.0-20240708202514-a0127673d1b9 h1:AlRMRs5mHJcdiK83KKJyFVeybPMZ7dOUzC0l3k9aUa8= github.com/rancher/norman v0.0.0-20240708202514-a0127673d1b9/go.mod h1:dyjfXBsNiroPWOdUZe7diUOUSLf6HQ/r2kEpwH/8zas= -github.com/rancher/rancher/pkg/apis v0.0.0-20241030145218-455fc5625e09 h1:svrjHvtTOJeMrSSHA6QBd4KSTVgRz+O/4ilrt+x6PJY= -github.com/rancher/rancher/pkg/apis v0.0.0-20241030145218-455fc5625e09/go.mod h1:nr+5f718biWnS633d2yF9R1fn4goY4C2meg8mA+ZfZs= +github.com/rancher/rancher/pkg/apis v0.0.0-20240918011937-2f9a7509687f h1:roiCuVuYV6nT7ApMVFYyHCDIorUlFVrdZBUisI/d+XQ= +github.com/rancher/rancher/pkg/apis v0.0.0-20240918011937-2f9a7509687f/go.mod h1:npd+q1ySuNMlQ8B1+2Pt917wNDytCJKkNZQG+QcevuI= github.com/rancher/rke v1.6.2 h1:ttGk77t5oe7bsiS7s7SOFmAl3PALYI5M2SQQenjKevk= github.com/rancher/rke v1.6.2/go.mod h1:5xRbf3L8PxqJRhABjYRfaBqbpVqAnqyH3maUNQEuwvk= github.com/rancher/wrangler/v3 v3.0.0 h1:IHHCA+vrghJDPxjtLk4fmeSCFhNe9fFzLFj3m2B0YpA= @@ -250,8 +250,8 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= -golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -264,8 +264,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= -golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -281,15 +281,15 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= -golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= +golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/pkg/codegen/main.go b/pkg/codegen/main.go index b1e10887f..adcbdae6b 100644 --- a/pkg/codegen/main.go +++ b/pkg/codegen/main.go @@ -46,7 +46,6 @@ func main() { v3.Project{}, v3.ClusterProxyConfig{}, v3.Feature{}, - v3.Setting{}, }, }, "provisioning.cattle.io": { diff --git a/pkg/generated/controllers/management.cattle.io/v3/interface.go b/pkg/generated/controllers/management.cattle.io/v3/interface.go index 87e5238dd..5ee673fa7 100644 --- a/pkg/generated/controllers/management.cattle.io/v3/interface.go +++ b/pkg/generated/controllers/management.cattle.io/v3/interface.go @@ -42,7 +42,6 @@ type Interface interface { Project() ProjectController ProjectRoleTemplateBinding() ProjectRoleTemplateBindingController RoleTemplate() RoleTemplateController - Setting() SettingController } func New(controllerFactory controller.SharedControllerFactory) Interface { @@ -98,7 +97,3 @@ func (v *version) ProjectRoleTemplateBinding() ProjectRoleTemplateBindingControl func (v *version) RoleTemplate() RoleTemplateController { return generic.NewNonNamespacedController[*v3.RoleTemplate, *v3.RoleTemplateList](schema.GroupVersionKind{Group: "management.cattle.io", Version: "v3", Kind: "RoleTemplate"}, "roletemplates", v.controllerFactory) } - -func (v *version) Setting() SettingController { - return generic.NewNonNamespacedController[*v3.Setting, *v3.SettingList](schema.GroupVersionKind{Group: "management.cattle.io", Version: "v3", Kind: "Setting"}, "settings", v.controllerFactory) -} diff --git a/pkg/generated/controllers/management.cattle.io/v3/setting.go b/pkg/generated/controllers/management.cattle.io/v3/setting.go deleted file mode 100644 index 3ef6b3c0d..000000000 --- a/pkg/generated/controllers/management.cattle.io/v3/setting.go +++ /dev/null @@ -1,39 +0,0 @@ -/* -Copyright 2024 Rancher Labs, Inc. - -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. -*/ - -// Code generated by codegen. DO NOT EDIT. - -package v3 - -import ( - v3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3" - "github.com/rancher/wrangler/v3/pkg/generic" -) - -// SettingController interface for managing Setting resources. -type SettingController interface { - generic.NonNamespacedControllerInterface[*v3.Setting, *v3.SettingList] -} - -// SettingClient interface for managing Setting resources in Kubernetes. -type SettingClient interface { - generic.NonNamespacedClientInterface[*v3.Setting, *v3.SettingList] -} - -// SettingCache interface for retrieving Setting resources in memory. -type SettingCache interface { - generic.NonNamespacedCacheInterface[*v3.Setting] -} diff --git a/pkg/resources/management.cattle.io/v3/globalrolebinding/mutator_test.go b/pkg/resources/management.cattle.io/v3/globalrolebinding/mutator_test.go index 7675804a6..7b6b8deb0 100644 --- a/pkg/resources/management.cattle.io/v3/globalrolebinding/mutator_test.go +++ b/pkg/resources/management.cattle.io/v3/globalrolebinding/mutator_test.go @@ -358,7 +358,7 @@ func Test_MutatorAdmit(t *testing.T) { gotObj := &apisv3.GlobalRoleBinding{} err = json.Unmarshal(patchedJS, gotObj) - require.NoError(t, err, "failed to unmarshal patched Object") + require.NoError(t, err, "failed to unmarshall patched Object") require.True(t, equality.Semantic.DeepEqual(test.wantGRB(), gotObj), "patched object and desired object are not equivalent wanted=%#v got=%#v", test.wantGRB(), gotObj) } else { diff --git a/pkg/resources/management.cattle.io/v3/project/Project.md b/pkg/resources/management.cattle.io/v3/project/Project.md index 5146439a4..e428bdb50 100644 --- a/pkg/resources/management.cattle.io/v3/project/Project.md +++ b/pkg/resources/management.cattle.io/v3/project/Project.md @@ -4,9 +4,6 @@ ClusterName must be equal to the namespace, and must refer to an existing management.cattle.io/v3.Cluster object. In addition, users cannot update the field after creation. -### BackingNamespace validation -The `BackingNamespace` field cannot be changed once set. Projects without the `BackingNamespace` field can have it added. - ### Protects system project The system project cannot be deleted. @@ -26,13 +23,4 @@ Limits for any resource must not be less than requests. ### On create -Populates the `BackingNamespace` field by concatenating `Project.ClusterName` and `Project.Name`. - -If the project is using a generated name (ie `GenerateName` is not empty), the generation happens within the mutating webhook. -The reason for this is that the BackingNamespace is made up of the `Project.Name`, and name generation happens after mutating webhooks and before validating webhooks. - Adds the authz.management.cattle.io/creator-role-bindings annotation. - -### On update - -If the `BackingNamespace` field is empty, it's populated with the project name. diff --git a/pkg/resources/management.cattle.io/v3/project/mutator.go b/pkg/resources/management.cattle.io/v3/project/mutator.go index a40b024bc..b8e087d69 100644 --- a/pkg/resources/management.cattle.io/v3/project/mutator.go +++ b/pkg/resources/management.cattle.io/v3/project/mutator.go @@ -3,22 +3,16 @@ package project import ( "encoding/json" "fmt" - "strings" v3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3" "github.com/rancher/webhook/pkg/admission" ctrlv3 "github.com/rancher/webhook/pkg/generated/controllers/management.cattle.io/v3" objectsv3 "github.com/rancher/webhook/pkg/generated/objects/management.cattle.io/v3" "github.com/rancher/webhook/pkg/patch" - corev1controller "github.com/rancher/wrangler/v3/pkg/generated/controllers/core/v1" - "github.com/rancher/wrangler/v3/pkg/name" "github.com/sirupsen/logrus" admissionv1 "k8s.io/api/admission/v1" admissionregistrationv1 "k8s.io/api/admissionregistration/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apiserver/pkg/storage/names" "k8s.io/utils/trace" ) @@ -26,7 +20,6 @@ const ( roleTemplatesRequired = "authz.management.cattle.io/creator-role-bindings" indexKey = "creatorDefaultUnlocked" mutatorCreatorRoleTemplateIndex = "webhook.cattle.io/creator-role-template-index" - projectNamespaceSetting = "use-project-namespace-fix" ) var gvr = schema.GroupVersionResource{ @@ -38,19 +31,13 @@ var gvr = schema.GroupVersionResource{ // Mutator implements admission.MutatingAdmissionWebhook. type Mutator struct { roleTemplateCache ctrlv3.RoleTemplateCache - namespaceClient corev1controller.NamespaceController - projectClient ctrlv3.ProjectClient - settingClient ctrlv3.SettingClient } // NewMutator returns a new mutator which mutates projects -func NewMutator(nsClient corev1controller.NamespaceController, roleTemplateCache ctrlv3.RoleTemplateCache, projectClient ctrlv3.ProjectClient, settingClient ctrlv3.SettingClient) *Mutator { +func NewMutator(roleTemplateCache ctrlv3.RoleTemplateCache) *Mutator { roleTemplateCache.AddIndexer(mutatorCreatorRoleTemplateIndex, creatorRoleTemplateIndexer) return &Mutator{ roleTemplateCache: roleTemplateCache, - namespaceClient: nsClient, - projectClient: projectClient, - settingClient: settingClient, } } @@ -71,7 +58,6 @@ func (m *Mutator) GVR() schema.GroupVersionResource { func (m *Mutator) Operations() []admissionregistrationv1.OperationType { return []admissionregistrationv1.OperationType{ admissionregistrationv1.Create, - admissionregistrationv1.Update, } } @@ -99,88 +85,13 @@ func (m *Mutator) Admit(request *admission.Request) (*admissionv1.AdmissionRespo } switch request.Operation { case admissionv1.Create: - project, err = m.createProjectNamespace(project) - if err != nil { - return nil, err - } - project, err = m.addCreatorRoleBindings(project) - if err != nil { - return nil, err - } - case admissionv1.Update: - project = m.updateProjectNamespace(project) + return m.admitCreate(project, request) default: return nil, fmt.Errorf("operation type %q not handled", request.Operation) } - response := &admissionv1.AdmissionResponse{} - if err := patch.CreatePatch(request.Object.Raw, project, response); err != nil { - return nil, fmt.Errorf("failed to create patch: %w", err) - } - response.Allowed = true - return response, nil } -func (m *Mutator) createProjectNamespace(project *v3.Project) (*v3.Project, error) { - setting, err := m.settingClient.Get(projectNamespaceSetting, v1.GetOptions{}) - if err != nil { - return nil, err - } - if effectiveValue(*setting) != "true" { - return project, nil - } - - newProject := project.DeepCopy() - backingNamespace := "" - // When the project name is empty, that means we want to generate a name for it - // Name generation happens after mutating webhooks, so in order to have access to the name early - // for the backing namespace, we need to generate it ourselves - if project.Name == "" { - // If err is nil, (meaning "project exists", see below) we need to repeat the generation process to find a project name and backing namespace that isn't taken - for err == nil { - newName := names.SimpleNameGenerator.GenerateName(project.GenerateName) - _, err = m.projectClient.Get(newProject.Spec.ClusterName, newName, v1.GetOptions{}) - if err == nil { - // A project with this name already exists. Generate a new name. - continue - } else if !apierrors.IsNotFound(err) { - return nil, err - } - - backingNamespace = name.SafeConcatName(newProject.Spec.ClusterName, strings.ToLower(newName)) - _, err = m.namespaceClient.Get(backingNamespace, v1.GetOptions{}) - - // If the backing namespace already exists, generate a new project name - if err != nil && !apierrors.IsNotFound(err) { - return nil, err - } - } - } else { - backingNamespace = name.SafeConcatName(newProject.Spec.ClusterName, strings.ToLower(newProject.Name)) - _, err = m.namespaceClient.Get(backingNamespace, v1.GetOptions{}) - if err == nil { - return nil, fmt.Errorf("failed to create project: namespace %s already exists", backingNamespace) - } else if !apierrors.IsNotFound(err) { - return nil, err - } - } - - newProject.Status.BackingNamespace = backingNamespace - return newProject, nil -} - -// updateProjectNamespace fills in BackingNamespace with the project name if it wasn't already set. -// This was the naming convention of project namespaces prior to using the BackingNamespace field. -// Filling it here is just to maintain backwards compatibility. -func (m *Mutator) updateProjectNamespace(project *v3.Project) *v3.Project { - if project.Status.BackingNamespace != "" { - return project - } - newProject := project.DeepCopy() - newProject.Status.BackingNamespace = newProject.Name - return newProject -} - -func (m *Mutator) addCreatorRoleBindings(project *v3.Project) (*v3.Project, error) { +func (m *Mutator) admitCreate(project *v3.Project, request *admission.Request) (*admissionv1.AdmissionResponse, error) { logrus.Debugf("[project-mutation] adding creator-role-bindings to project: %v", project.Name) newProject := project.DeepCopy() @@ -192,7 +103,12 @@ func (m *Mutator) addCreatorRoleBindings(project *v3.Project) (*v3.Project, erro return nil, fmt.Errorf("failed to add annotation to project %s: %w", project.Name, err) } newProject.Annotations[roleTemplatesRequired] = annotations - return newProject, nil + response := &admissionv1.AdmissionResponse{} + if err := patch.CreatePatch(request.Object.Raw, newProject, response); err != nil { + return nil, fmt.Errorf("failed to create patch: %w", err) + } + response.Allowed = true + return response, nil } func (m *Mutator) getCreatorRoleTemplateAnnotations() (string, error) { @@ -210,12 +126,3 @@ func (m *Mutator) getCreatorRoleTemplateAnnotations() (string, error) { } return string(annotations), nil } - -func effectiveValue(s v3.Setting) string { - if s.Value != "" { - return s.Value - } else if s.Default != "" { - return s.Default - } - return "" -} diff --git a/pkg/resources/management.cattle.io/v3/project/mutator_test.go b/pkg/resources/management.cattle.io/v3/project/mutator_test.go index 5605eb551..e92299c23 100644 --- a/pkg/resources/management.cattle.io/v3/project/mutator_test.go +++ b/pkg/resources/management.cattle.io/v3/project/mutator_test.go @@ -5,16 +5,12 @@ import ( "fmt" "testing" - jsonpatch "github.com/evanphx/json-patch" "github.com/golang/mock/gomock" v3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3" "github.com/rancher/wrangler/v3/pkg/generic/fake" "github.com/stretchr/testify/assert" admissionv1 "k8s.io/api/admission/v1" - corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" ) const ( @@ -22,195 +18,112 @@ const ( expectedIndexKey = "creatorDefaultUnlocked" ) -var ( - defaultProject = v3.Project{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testproject", - }, - Spec: v3.ProjectSpec{ - ClusterName: "testcluster", - }, - } - emptyProject = func() *v3.Project { - return &v3.Project{} - } -) - func TestAdmit(t *testing.T) { t.Parallel() tests := []struct { - name string - operation admissionv1.Operation - dryRun bool - projectNamespaceSetting string - oldProject func() *v3.Project - newProject func() *v3.Project - indexer func() ([]*v3.RoleTemplate, error) - wantProject func() *v3.Project - wantErr bool + name string + operation admissionv1.Operation + dryRun bool + oldProject *v3.Project + newProject *v3.Project + indexer func() ([]*v3.RoleTemplate, error) + wantPatch []map[string]interface{} + wantErr bool }{ { - name: "dry run returns allowed", - operation: admissionv1.Update, - dryRun: true, - newProject: emptyProject, - projectNamespaceSetting: "true", + name: "dry run returns allowed", + operation: admissionv1.Update, + dryRun: true, + newProject: &v3.Project{}, }, { - name: "failure to decode project returns error", - newProject: nil, - wantErr: true, - projectNamespaceSetting: "true", + name: "failure to decode project returns error", + newProject: nil, + wantErr: true, }, { - name: "delete operation is invalid", - operation: admissionv1.Delete, - newProject: emptyProject, - oldProject: emptyProject, - wantErr: true, - projectNamespaceSetting: "true", + name: "delete operation is invalid", + operation: admissionv1.Delete, + newProject: &v3.Project{}, + oldProject: &v3.Project{}, + wantErr: true, }, { - name: "update operation is valid and adds backingNamespace", - operation: admissionv1.Update, - newProject: func() *v3.Project { - return &v3.Project{ - ObjectMeta: metav1.ObjectMeta{ - Name: "p-abc123", - }, - } - }, - oldProject: func() *v3.Project { - return &v3.Project{ - ObjectMeta: metav1.ObjectMeta{ - Name: "p-abc123", - }, - } - }, - wantProject: func() *v3.Project { - return &v3.Project{ - ObjectMeta: metav1.ObjectMeta{ - Name: "p-abc123", - }, - Status: v3.ProjectStatus{ - BackingNamespace: "p-abc123", - }, - } - }, - projectNamespaceSetting: "true", + name: "update operation is invalid", + operation: admissionv1.Update, + newProject: &v3.Project{}, + oldProject: &v3.Project{}, + wantErr: true, }, { - name: "update operation is valid and adds backingNamespace, even when setting is false", - operation: admissionv1.Update, - newProject: func() *v3.Project { - return &v3.Project{ - ObjectMeta: metav1.ObjectMeta{ - Name: "p-abc123", - }, - } - }, - oldProject: func() *v3.Project { - return &v3.Project{ - ObjectMeta: metav1.ObjectMeta{ - Name: "p-abc123", - }, - } - }, - wantProject: func() *v3.Project { - return &v3.Project{ - ObjectMeta: metav1.ObjectMeta{ - Name: "p-abc123", - }, - Status: v3.ProjectStatus{ - BackingNamespace: "p-abc123", - }, - } - }, - projectNamespaceSetting: "false", + name: "connect operation is invalid", + operation: admissionv1.Connect, + newProject: &v3.Project{}, + oldProject: &v3.Project{}, + wantErr: true, }, { - name: "connect operation is invalid", - operation: admissionv1.Connect, - newProject: emptyProject, - oldProject: emptyProject, - wantErr: true, - projectNamespaceSetting: "true", - }, - { - name: "indexer error", - operation: admissionv1.Create, - newProject: emptyProject, - indexer: func() ([]*v3.RoleTemplate, error) { return nil, fmt.Errorf("indexer error") }, - wantErr: true, - projectNamespaceSetting: "true", + name: "indexer error", + operation: admissionv1.Create, + newProject: &v3.Project{}, + indexer: func() ([]*v3.RoleTemplate, error) { return nil, fmt.Errorf("indexer error") }, + wantErr: true, }, { name: "indexer returns empty", operation: admissionv1.Create, - newProject: func() *v3.Project { - return defaultProject.DeepCopy() + newProject: &v3.Project{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testproject", + }, }, indexer: func() ([]*v3.RoleTemplate, error) { return nil, nil }, - wantProject: func() *v3.Project { - p := defaultProject.DeepCopy() - p.Annotations = map[string]string{ - "authz.management.cattle.io/creator-role-bindings": "{}", - } - p.Status.BackingNamespace = "testcluster-testproject" - return p - }, - projectNamespaceSetting: "true", - }, - { - name: "created project gets annotation added and backing namespace", - operation: admissionv1.Create, - newProject: func() *v3.Project { - return defaultProject.DeepCopy() - }, - wantProject: func() *v3.Project { - p := defaultProject.DeepCopy() - p.Annotations = map[string]string{ - "authz.management.cattle.io/creator-role-bindings": "{\"required\":[\"project-owner\"]}", - } - p.Status.BackingNamespace = "testcluster-testproject" - return p + wantPatch: []map[string]interface{}{ + { + "op": "add", + "path": "/metadata/annotations", + "value": map[string]string{ + "authz.management.cattle.io/creator-role-bindings": "{}", + }, + }, }, - projectNamespaceSetting: "true", }, { - name: "created project adds annotation but not backing namespace if setting is false", + name: "created project gets annotation added", operation: admissionv1.Create, - newProject: func() *v3.Project { - return defaultProject.DeepCopy() + newProject: &v3.Project{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testproject", + }, }, - wantProject: func() *v3.Project { - p := defaultProject.DeepCopy() - p.Annotations = map[string]string{ - "authz.management.cattle.io/creator-role-bindings": "{\"required\":[\"project-owner\"]}", - } - return p + wantPatch: []map[string]interface{}{ + { + "op": "add", + "path": "/metadata/annotations", + "value": map[string]string{ + "authz.management.cattle.io/creator-role-bindings": "{\"required\":[\"project-owner\"]}", + }, + }, }, - projectNamespaceSetting: "false", }, { name: "override user-set annotations", operation: admissionv1.Create, - newProject: func() *v3.Project { - p := defaultProject.DeepCopy() - p.Annotations = map[string]string{ - "authz.management.cattle.io/creator-role-bindings": "my own setting", - } - return p + newProject: &v3.Project{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testproject", + Annotations: map[string]string{ + "authz.management.cattle.io/creator-role-bindings": "my own setting", + }, + }, }, - wantProject: func() *v3.Project { - p := defaultProject.DeepCopy() - p.Annotations = map[string]string{ - "authz.management.cattle.io/creator-role-bindings": "{\"required\":[\"project-owner\"]}", - } - p.Status.BackingNamespace = "testcluster-testproject" - return p + wantPatch: []map[string]interface{}{ + { + "op": "replace", + "path": "/metadata/annotations/authz.management.cattle.io~1creator-role-bindings", + "value": "{\"required\":[\"project-owner\"]}", + }, }, - projectNamespaceSetting: "true", }, } @@ -232,14 +145,8 @@ func TestAdmit(t *testing.T) { test := test t.Run(test.name, func(t *testing.T) { t.Parallel() - - ctrl := gomock.NewController(t) - nsMock := fake.NewMockNonNamespacedControllerInterface[*corev1.Namespace, *corev1.NamespaceList](ctrl) - nsMock.EXPECT().Get(gomock.Any(), metav1.GetOptions{}).Return(nil, apierrors.NewNotFound(schema.GroupResource{}, "")).AnyTimes() - projectMock := fake.NewMockClientInterface[*v3.Project, *v3.ProjectList](ctrl) - projectMock.EXPECT().Get(gomock.Any(), gomock.Any(), metav1.GetOptions{}).Return(nil, apierrors.NewNotFound(schema.GroupResource{}, "")).AnyTimes() - settingMock := fake.NewMockNonNamespacedClientInterface[*v3.Setting, *v3.SettingList](ctrl) - settingMock.EXPECT().Get(gomock.Any(), gomock.Any()).Return(&v3.Setting{Default: test.projectNamespaceSetting}, nil).AnyTimes() + req, err := createProjectRequest(test.oldProject, test.newProject, test.operation, test.dryRun) + assert.NoError(t, err) roleTemplateCache := fake.NewMockNonNamespacedCacheInterface[*v3.RoleTemplate](gomock.NewController(t)) roleTemplateCache.EXPECT().AddIndexer(expectedIndexerName, gomock.Any()) indexer := defaultIndexer @@ -248,41 +155,19 @@ func TestAdmit(t *testing.T) { } returnedRTs, returnedErr := indexer() roleTemplateCache.EXPECT().GetByIndex(expectedIndexerName, expectedIndexKey).Return(returnedRTs, returnedErr).AnyTimes() - - var oldProject, newProject *v3.Project - if test.oldProject != nil { - oldProject = test.oldProject() - } - if test.newProject != nil { - newProject = test.newProject() - } - req, err := createProjectRequest(oldProject, newProject, test.operation, test.dryRun) - assert.NoError(t, err) - m := NewMutator(nsMock, roleTemplateCache, projectMock, settingMock) - + m := NewMutator(roleTemplateCache) resp, err := m.Admit(req) - if test.wantErr { assert.Error(t, err) return } - assert.NoError(t, err, "Admit failed") assert.Equal(t, true, resp.Allowed) - if test.wantProject != nil { - patchObj, err := jsonpatch.DecodePatch(resp.Patch) - assert.NoError(t, err, "failed to decode patch from response") - - patchedJS, err := patchObj.Apply(req.Object.Raw) - assert.NoError(t, err, "failed to apply patch to Object") - - gotObj := &v3.Project{} - err = json.Unmarshal(patchedJS, gotObj) - assert.NoError(t, err, "failed to unmarshal patched Object") - - assert.Equal(t, test.wantProject(), gotObj) - } else { - assert.Nil(t, resp.Patch, "unexpected patch request received") + var wantPatch []byte + if test.wantPatch != nil { + wantPatch, err = json.Marshal(test.wantPatch) + assert.NoError(t, err) } + assert.Equal(t, string(wantPatch), string(resp.Patch)) }) } } diff --git a/pkg/resources/management.cattle.io/v3/project/validator.go b/pkg/resources/management.cattle.io/v3/project/validator.go index 49111c5d2..7c59519b7 100644 --- a/pkg/resources/management.cattle.io/v3/project/validator.go +++ b/pkg/resources/management.cattle.io/v3/project/validator.go @@ -22,12 +22,11 @@ import ( ) const ( - systemProjectLabel = "authz.management.cattle.io/system-project" - projectQuotaField = "resourceQuota" - clusterNameField = "clusterName" - backingNamespaceField = "backingNamespace" - namespaceQuotaField = "namespaceDefaultResourceQuota" - containerLimitField = "containerDefaultResourceLimit" + systemProjectLabel = "authz.management.cattle.io/system-project" + projectQuotaField = "resourceQuota" + clusterNameField = "clusterName" + namespaceQuotaField = "namespaceDefaultResourceQuota" + containerLimitField = "containerDefaultResourceLimit" ) var projectSpecFieldPath = field.NewPath("project").Child("spec") @@ -116,13 +115,8 @@ func (a *admitter) admitCreate(project *v3.Project) (*admissionv1.AdmissionRespo } func (a *admitter) admitUpdate(oldProject, newProject *v3.Project) (*admissionv1.AdmissionResponse, error) { - var fieldErr *field.Error if oldProject.Spec.ClusterName != newProject.Spec.ClusterName { - fieldErr = field.Invalid(projectSpecFieldPath.Child(clusterNameField), newProject.Spec.ClusterName, "field is immutable") - } else if oldProject.Status.BackingNamespace != "" && oldProject.Status.BackingNamespace != newProject.Status.BackingNamespace { - fieldErr = field.Invalid(projectSpecFieldPath.Child(backingNamespaceField), newProject.Status.BackingNamespace, "field is immutable") - } - if fieldErr != nil { + fieldErr := field.Invalid(projectSpecFieldPath.Child(clusterNameField), newProject.Spec.ClusterName, "field is immutable") return admission.ResponseBadRequest(fieldErr.Error()), nil } return a.admitCommonCreateUpdate(oldProject, newProject) diff --git a/pkg/resources/management.cattle.io/v3/projectroletemplatebinding/ProjectRoleTemplateBinding.md b/pkg/resources/management.cattle.io/v3/projectroletemplatebinding/ProjectRoleTemplateBinding.md index baa225921..dc57d547e 100644 --- a/pkg/resources/management.cattle.io/v3/projectroletemplatebinding/ProjectRoleTemplateBinding.md +++ b/pkg/resources/management.cattle.io/v3/projectroletemplatebinding/ProjectRoleTemplateBinding.md @@ -14,6 +14,7 @@ Users cannot create ProjectRoleTemplateBindings that violate the following const - The `ProjectName` field must be: - Provided as a non-empty value - Specified using the format of `clusterName:projectName`; `clusterName` is the `metadata.name` of a cluster, and `projectName` is the `metadata.name` of a project + - The `projectName` part of the field must match the namespace of the ProjectRoleTemplateBinding - Refer to a valid project and cluster (both must exist and project.Spec.ClusterName must equal the cluster) - Either a user subject (through `UserName` or `UserPrincipalName`), or a group subject (through `GroupName` or `GroupPrincipalName`), or a service account subject (through `ServiceAccount`) must be specified. Exactly one diff --git a/pkg/resources/management.cattle.io/v3/projectroletemplatebinding/validator.go b/pkg/resources/management.cattle.io/v3/projectroletemplatebinding/validator.go index 745b74da8..1c80e77d9 100644 --- a/pkg/resources/management.cattle.io/v3/projectroletemplatebinding/validator.go +++ b/pkg/resources/management.cattle.io/v3/projectroletemplatebinding/validator.go @@ -210,6 +210,9 @@ func (a *admitter) validateCreateFields(newPRTB *apisv3.ProjectRoleTemplateBindi if clusterName == "" || projectName == "" { return field.Invalid(fieldPath.Child("projectName"), newPRTB.ProjectName, "projectName must be of the form cluster.metadata.name:project.metadata.name, and both must refer to an existing object") } + if projectName != newPRTB.Namespace { + return field.Forbidden(fieldPath, "namespace and the projectName part of projectName must match") + } cluster, err := a.clusterCache.Get(clusterName) clusterNotFoundErr := field.Invalid(fieldPath.Child("projectName"), newPRTB.ProjectName, fmt.Sprintf("specified cluster %s not found", clusterName)) if err != nil { diff --git a/pkg/resources/management.cattle.io/v3/projectroletemplatebinding/validator_test.go b/pkg/resources/management.cattle.io/v3/projectroletemplatebinding/validator_test.go index 45ba8a075..bd06e14fc 100644 --- a/pkg/resources/management.cattle.io/v3/projectroletemplatebinding/validator_test.go +++ b/pkg/resources/management.cattle.io/v3/projectroletemplatebinding/validator_test.go @@ -998,6 +998,22 @@ func (p *ProjectRoleTemplateBindingSuite) TestValidationOnCreate() { }, allowed: false, }, + { + name: "namespace and the project id part of the project name differ", + args: args{ + username: adminUser, + oldPRTB: func() *apisv3.ProjectRoleTemplateBinding { + return nil + }, + newPRTB: func() *apisv3.ProjectRoleTemplateBinding { + basePRTB := newBasePRTB() + basePRTB.ObjectMeta.Namespace = "default" + basePRTB.ProjectName = fmt.Sprintf("%s:%s", clusterID, "p-cgtq4") + return basePRTB + }, + }, + allowed: false, + }, { name: "missing cluster name", args: args{ diff --git a/pkg/server/handlers.go b/pkg/server/handlers.go index 09fae655b..dba617595 100644 --- a/pkg/server/handlers.go +++ b/pkg/server/handlers.go @@ -75,7 +75,7 @@ func Mutation(clients *clients.Clients) ([]admission.MutatingAdmissionHandler, e if clients.MultiClusterManagement { secrets := secret.NewMutator(clients.RBAC.Role(), clients.RBAC.RoleBinding()) - projects := project.NewMutator(clients.Core.Namespace(), clients.Management.RoleTemplate().Cache(), clients.Management.Project(), clients.Management.Setting()) + projects := project.NewMutator(clients.Management.RoleTemplate().Cache()) grbs := globalrolebinding.NewMutator(clients.Management.GlobalRole().Cache()) mutators = append(mutators, secrets, projects, grbs) } From 3f416c7e9d956ed6da89728e09e13b3b37c200af Mon Sep 17 00:00:00 2001 From: Tom Lebreux Date: Thu, 7 Nov 2024 11:51:07 -0500 Subject: [PATCH 5/6] Sync dependencies with Rancher (#541) --- go.mod | 24 ++++++++++++------------ go.sum | 46 ++++++++++++++++++++++++---------------------- 2 files changed, 36 insertions(+), 34 deletions(-) diff --git a/go.mod b/go.mod index 9fd36930e..05168ccf7 100644 --- a/go.mod +++ b/go.mod @@ -41,15 +41,15 @@ require ( github.com/golang/mock v1.6.0 github.com/gorilla/mux v1.8.1 github.com/rancher/dynamiclistener v0.6.0 - github.com/rancher/lasso v0.0.0-20240809125800-8da6f11865d5 - github.com/rancher/rancher/pkg/apis v0.0.0-20240918011937-2f9a7509687f - github.com/rancher/rke v1.6.2 + github.com/rancher/lasso v0.0.0-20240923125127-ae858d002589 + github.com/rancher/rancher/pkg/apis v0.0.0-20241106193223-d5c00c4ce801 + github.com/rancher/rke v1.6.4-rc.2 github.com/rancher/wrangler/v3 v3.0.0 github.com/robfig/cron v1.2.0 github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.9.0 golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 - golang.org/x/text v0.18.0 + golang.org/x/text v0.19.0 golang.org/x/tools v0.24.0 k8s.io/api v0.30.2 k8s.io/apimachinery v0.30.2 @@ -69,7 +69,7 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect @@ -108,10 +108,10 @@ require ( github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.47.0 // indirect github.com/prometheus/procfs v0.14.0 // indirect - github.com/rancher/aks-operator v1.9.2 // indirect - github.com/rancher/eks-operator v1.9.2 // indirect + github.com/rancher/aks-operator v1.9.4-rc.2 // indirect + github.com/rancher/eks-operator v1.9.4-rc.2 // indirect github.com/rancher/fleet/pkg/apis v0.10.0 // indirect - github.com/rancher/gke-operator v1.9.2 // indirect + github.com/rancher/gke-operator v1.9.4-rc.1 // indirect github.com/rancher/norman v0.0.0-20240708202514-a0127673d1b9 // indirect github.com/spf13/cobra v1.8.0 // indirect github.com/spf13/pflag v1.0.5 // indirect @@ -130,13 +130,13 @@ require ( go.opentelemetry.io/proto/otlp v1.0.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/crypto v0.27.0 // indirect + golang.org/x/crypto v0.28.0 // indirect golang.org/x/mod v0.20.0 // indirect - golang.org/x/net v0.29.0 // indirect + golang.org/x/net v0.30.0 // indirect golang.org/x/oauth2 v0.23.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/term v0.24.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect golang.org/x/time v0.5.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 // indirect diff --git a/go.sum b/go.sum index fae4f97ae..858ae7759 100644 --- a/go.sum +++ b/go.sum @@ -16,8 +16,8 @@ github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k= github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= @@ -154,22 +154,22 @@ github.com/prometheus/common v0.47.0 h1:p5Cz0FNHo7SnWOmWmoRozVcjEp0bIVU8cV7OShpj github.com/prometheus/common v0.47.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= github.com/prometheus/procfs v0.14.0 h1:Lw4VdGGoKEZilJsayHf0B+9YgLGREba2C6xr+Fdfq6s= github.com/prometheus/procfs v0.14.0/go.mod h1:XL+Iwz8k8ZabyZfMFHPiilCniixqQarAy5Mu67pHlNQ= -github.com/rancher/aks-operator v1.9.2 h1:xlEbRO9qjW0/YoZqbovJXXhkvWsv/NQUrHlI6H9APpg= -github.com/rancher/aks-operator v1.9.2/go.mod h1:Aj8LdDJ2L4BleF8qqyU6phkzEJ79Z3niHx2SbyT3Cg8= +github.com/rancher/aks-operator v1.9.4-rc.2 h1:E4/W4BIeAfNGxnRHDMGyIU+EeCDoIicIsxpSI/9bG3I= +github.com/rancher/aks-operator v1.9.4-rc.2/go.mod h1:C2V1QX76mE29HYOFeqG9LrVLY4AWMqACXyVOzUkkJnA= github.com/rancher/dynamiclistener v0.6.0 h1:M7x8Nq+GY0UORULANuW/AH1ocnyZaqlmTuviMQAHL1Q= github.com/rancher/dynamiclistener v0.6.0/go.mod h1:7VNEQhAwzbYJ08S1MYb6B4vili6K7CcrG4cNZXq1j+s= -github.com/rancher/eks-operator v1.9.2 h1:YF8M2Y4Qj0eT8oIcpkS0WWIJjeIUMiY/xFD3XBv7294= -github.com/rancher/eks-operator v1.9.2/go.mod h1:eQDK+i1UFQQpkTiZmSGX64hpkGLZCju6M9nmaMHcFys= +github.com/rancher/eks-operator v1.9.4-rc.2 h1:d72kX9ey79ia8EPolIYEtm9phhkrL91Sc9dFjV6H75Y= +github.com/rancher/eks-operator v1.9.4-rc.2/go.mod h1:w5/N3U9Ey7Sa3gw18jEmnkbG4ucMALdaGQhXe0BlxUQ= github.com/rancher/fleet/pkg/apis v0.10.0 h1:0f8OEghEDJNzvUAR2fpg2dw8EnAgfWvkhnwsYFS9G+w= github.com/rancher/fleet/pkg/apis v0.10.0/go.mod h1:mjirthAmgpz0xo+qywUiaJDFpjnmX3xrc2E0/qmk3yc= -github.com/rancher/gke-operator v1.9.2 h1:vPoqYX+E4Pq1qmZU6qRHSj4zm4q0hHyLKaMfakvyQag= -github.com/rancher/gke-operator v1.9.2/go.mod h1:cvks/IUeoYUNzBcqQ24KIKJ3GFcYyerPpLZ/fZ2CpwE= -github.com/rancher/lasso v0.0.0-20240809125800-8da6f11865d5 h1:qlVhaHTT7wwrI5+AGdkYHpveuoe8Ot4TdQr7LtxmVSk= -github.com/rancher/lasso v0.0.0-20240809125800-8da6f11865d5/go.mod h1:Efx/+BbH3ivmnTPLu5cA3Gc9wT5oyGS0LBcqEuYTx+A= +github.com/rancher/gke-operator v1.9.4-rc.1 h1:J2Mmq37G5WesIwwgh51CCmfzyYTMCXswQq6VVqUgpt0= +github.com/rancher/gke-operator v1.9.4-rc.1/go.mod h1:DqV7qqCkVNdHPVzFnTW5qXqXOl+CViFWYk+SX8ZHbGE= +github.com/rancher/lasso v0.0.0-20240923125127-ae858d002589 h1:c3IIzpVo5Jhd1T7Dih/kbEueWC21j4xTZ5EQO9rhTUg= +github.com/rancher/lasso v0.0.0-20240923125127-ae858d002589/go.mod h1:+GJaJqWpk1MZYCDrMC+4Jee0M/aScoru5T8V2A//rgg= github.com/rancher/norman v0.0.0-20240708202514-a0127673d1b9 h1:AlRMRs5mHJcdiK83KKJyFVeybPMZ7dOUzC0l3k9aUa8= github.com/rancher/norman v0.0.0-20240708202514-a0127673d1b9/go.mod h1:dyjfXBsNiroPWOdUZe7diUOUSLf6HQ/r2kEpwH/8zas= -github.com/rancher/rancher/pkg/apis v0.0.0-20240918011937-2f9a7509687f h1:roiCuVuYV6nT7ApMVFYyHCDIorUlFVrdZBUisI/d+XQ= -github.com/rancher/rancher/pkg/apis v0.0.0-20240918011937-2f9a7509687f/go.mod h1:npd+q1ySuNMlQ8B1+2Pt917wNDytCJKkNZQG+QcevuI= +github.com/rancher/rancher/pkg/apis v0.0.0-20241106193223-d5c00c4ce801 h1:U8XPGA28s6elj/7E5n0xVnUUm6aOGyaAxWw4nIUABYA= +github.com/rancher/rancher/pkg/apis v0.0.0-20241106193223-d5c00c4ce801/go.mod h1:lAwI5PDsmIWdf8Z8Z3U56Bk2XvZB/xWom0TIIYYtwSE= github.com/rancher/rke v1.6.2 h1:ttGk77t5oe7bsiS7s7SOFmAl3PALYI5M2SQQenjKevk= github.com/rancher/rke v1.6.2/go.mod h1:5xRbf3L8PxqJRhABjYRfaBqbpVqAnqyH3maUNQEuwvk= github.com/rancher/wrangler/v3 v3.0.0 h1:IHHCA+vrghJDPxjtLk4fmeSCFhNe9fFzLFj3m2B0YpA= @@ -243,6 +243,8 @@ go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lI go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= +go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= @@ -250,8 +252,8 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -264,8 +266,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= -golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -281,15 +283,15 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From dc9ea59fe49e3eaf704143b92597c25b2fcc84d6 Mon Sep 17 00:00:00 2001 From: Tom Lebreux Date: Fri, 8 Nov 2024 16:08:18 -0500 Subject: [PATCH 6/6] unRC *-operator (#544) --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 05168ccf7..58c5ed4b3 100644 --- a/go.mod +++ b/go.mod @@ -42,7 +42,7 @@ require ( github.com/gorilla/mux v1.8.1 github.com/rancher/dynamiclistener v0.6.0 github.com/rancher/lasso v0.0.0-20240923125127-ae858d002589 - github.com/rancher/rancher/pkg/apis v0.0.0-20241106193223-d5c00c4ce801 + github.com/rancher/rancher/pkg/apis v0.0.0-20241107190722-17b258997ea9 github.com/rancher/rke v1.6.4-rc.2 github.com/rancher/wrangler/v3 v3.0.0 github.com/robfig/cron v1.2.0 @@ -108,10 +108,10 @@ require ( github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.47.0 // indirect github.com/prometheus/procfs v0.14.0 // indirect - github.com/rancher/aks-operator v1.9.4-rc.2 // indirect - github.com/rancher/eks-operator v1.9.4-rc.2 // indirect + github.com/rancher/aks-operator v1.9.4 // indirect + github.com/rancher/eks-operator v1.9.4 // indirect github.com/rancher/fleet/pkg/apis v0.10.0 // indirect - github.com/rancher/gke-operator v1.9.4-rc.1 // indirect + github.com/rancher/gke-operator v1.9.4 // indirect github.com/rancher/norman v0.0.0-20240708202514-a0127673d1b9 // indirect github.com/spf13/cobra v1.8.0 // indirect github.com/spf13/pflag v1.0.5 // indirect diff --git a/go.sum b/go.sum index 858ae7759..954582837 100644 --- a/go.sum +++ b/go.sum @@ -154,22 +154,22 @@ github.com/prometheus/common v0.47.0 h1:p5Cz0FNHo7SnWOmWmoRozVcjEp0bIVU8cV7OShpj github.com/prometheus/common v0.47.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= github.com/prometheus/procfs v0.14.0 h1:Lw4VdGGoKEZilJsayHf0B+9YgLGREba2C6xr+Fdfq6s= github.com/prometheus/procfs v0.14.0/go.mod h1:XL+Iwz8k8ZabyZfMFHPiilCniixqQarAy5Mu67pHlNQ= -github.com/rancher/aks-operator v1.9.4-rc.2 h1:E4/W4BIeAfNGxnRHDMGyIU+EeCDoIicIsxpSI/9bG3I= -github.com/rancher/aks-operator v1.9.4-rc.2/go.mod h1:C2V1QX76mE29HYOFeqG9LrVLY4AWMqACXyVOzUkkJnA= +github.com/rancher/aks-operator v1.9.4 h1:26XWRdqAryLRImqLEcxY4ZVDSYUGFNaqCoxwX3DQXS8= +github.com/rancher/aks-operator v1.9.4/go.mod h1:C2V1QX76mE29HYOFeqG9LrVLY4AWMqACXyVOzUkkJnA= github.com/rancher/dynamiclistener v0.6.0 h1:M7x8Nq+GY0UORULANuW/AH1ocnyZaqlmTuviMQAHL1Q= github.com/rancher/dynamiclistener v0.6.0/go.mod h1:7VNEQhAwzbYJ08S1MYb6B4vili6K7CcrG4cNZXq1j+s= -github.com/rancher/eks-operator v1.9.4-rc.2 h1:d72kX9ey79ia8EPolIYEtm9phhkrL91Sc9dFjV6H75Y= -github.com/rancher/eks-operator v1.9.4-rc.2/go.mod h1:w5/N3U9Ey7Sa3gw18jEmnkbG4ucMALdaGQhXe0BlxUQ= +github.com/rancher/eks-operator v1.9.4 h1:uAKTP0L1TDLimEK4CwdC+XtRLaQtqKqk+QfihaXNvAY= +github.com/rancher/eks-operator v1.9.4/go.mod h1:w5/N3U9Ey7Sa3gw18jEmnkbG4ucMALdaGQhXe0BlxUQ= github.com/rancher/fleet/pkg/apis v0.10.0 h1:0f8OEghEDJNzvUAR2fpg2dw8EnAgfWvkhnwsYFS9G+w= github.com/rancher/fleet/pkg/apis v0.10.0/go.mod h1:mjirthAmgpz0xo+qywUiaJDFpjnmX3xrc2E0/qmk3yc= -github.com/rancher/gke-operator v1.9.4-rc.1 h1:J2Mmq37G5WesIwwgh51CCmfzyYTMCXswQq6VVqUgpt0= -github.com/rancher/gke-operator v1.9.4-rc.1/go.mod h1:DqV7qqCkVNdHPVzFnTW5qXqXOl+CViFWYk+SX8ZHbGE= +github.com/rancher/gke-operator v1.9.4 h1:k9PkWBTYf8rw0muNVzwczUltK67hwoHsScOFbeNr7Ic= +github.com/rancher/gke-operator v1.9.4/go.mod h1:DqV7qqCkVNdHPVzFnTW5qXqXOl+CViFWYk+SX8ZHbGE= github.com/rancher/lasso v0.0.0-20240923125127-ae858d002589 h1:c3IIzpVo5Jhd1T7Dih/kbEueWC21j4xTZ5EQO9rhTUg= github.com/rancher/lasso v0.0.0-20240923125127-ae858d002589/go.mod h1:+GJaJqWpk1MZYCDrMC+4Jee0M/aScoru5T8V2A//rgg= github.com/rancher/norman v0.0.0-20240708202514-a0127673d1b9 h1:AlRMRs5mHJcdiK83KKJyFVeybPMZ7dOUzC0l3k9aUa8= github.com/rancher/norman v0.0.0-20240708202514-a0127673d1b9/go.mod h1:dyjfXBsNiroPWOdUZe7diUOUSLf6HQ/r2kEpwH/8zas= -github.com/rancher/rancher/pkg/apis v0.0.0-20241106193223-d5c00c4ce801 h1:U8XPGA28s6elj/7E5n0xVnUUm6aOGyaAxWw4nIUABYA= -github.com/rancher/rancher/pkg/apis v0.0.0-20241106193223-d5c00c4ce801/go.mod h1:lAwI5PDsmIWdf8Z8Z3U56Bk2XvZB/xWom0TIIYYtwSE= +github.com/rancher/rancher/pkg/apis v0.0.0-20241107190722-17b258997ea9 h1:okf8EjtIXi8EBR3hPS2d/o8cblAz2VHpIkdEoyXz8mc= +github.com/rancher/rancher/pkg/apis v0.0.0-20241107190722-17b258997ea9/go.mod h1:OnQ8rH8R/+Z1yCiVNSRFNviElp6WdiXAs9m+fyCcl/c= github.com/rancher/rke v1.6.2 h1:ttGk77t5oe7bsiS7s7SOFmAl3PALYI5M2SQQenjKevk= github.com/rancher/rke v1.6.2/go.mod h1:5xRbf3L8PxqJRhABjYRfaBqbpVqAnqyH3maUNQEuwvk= github.com/rancher/wrangler/v3 v3.0.0 h1:IHHCA+vrghJDPxjtLk4fmeSCFhNe9fFzLFj3m2B0YpA=