From 8f2f0fdf586d08fc50d010395a0e60829d5fc489 Mon Sep 17 00:00:00 2001 From: Joe Lanford Date: Tue, 25 Feb 2025 08:17:05 -0500 Subject: [PATCH 01/67] permissions preflight: copy necessary kubernetes libs Signed-off-by: Joe Lanford --- .golangci.yaml | 4 + codecov.yml | 5 +- go.mod | 1 + .../kubernetes/pkg/apis/rbac/helpers.go | 375 +++++++++++++++ .../kubernetes/pkg/apis/rbac/register.go | 60 +++ .../kubernetes/pkg/apis/rbac/types.go | 210 ++++++++ .../kubernetes/pkg/apis/rbac/v1/defaults.go | 49 ++ .../pkg/apis/rbac/v1/evaluation_helpers.go | 144 ++++++ .../kubernetes/pkg/apis/rbac/v1/helpers.go | 231 +++++++++ .../kubernetes/pkg/apis/rbac/v1/register.go | 44 ++ .../apis/rbac/v1/zz_generated.conversion.go | 450 ++++++++++++++++++ .../pkg/apis/rbac/v1/zz_generated.defaults.go | 68 +++ .../pkg/apis/rbac/zz_generated.deepcopy.go | 412 ++++++++++++++++ .../pkg/registry/rbac/escalation_check.go | 146 ++++++ .../rbac/validation/policy_compact.go | 89 ++++ .../pkg/registry/rbac/validation/rule.go | 368 ++++++++++++++ .../plugin/pkg/auth/authorizer/rbac/rbac.go | 225 +++++++++ 17 files changed, 2880 insertions(+), 1 deletion(-) create mode 100644 internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/helpers.go create mode 100644 internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/register.go create mode 100644 internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/types.go create mode 100644 internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1/defaults.go create mode 100644 internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1/evaluation_helpers.go create mode 100644 internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1/helpers.go create mode 100644 internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1/register.go create mode 100644 internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1/zz_generated.conversion.go create mode 100644 internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1/zz_generated.defaults.go create mode 100644 internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/zz_generated.deepcopy.go create mode 100644 internal/operator-controller/authorization/internal/kubernetes/pkg/registry/rbac/escalation_check.go create mode 100644 internal/operator-controller/authorization/internal/kubernetes/pkg/registry/rbac/validation/policy_compact.go create mode 100644 internal/operator-controller/authorization/internal/kubernetes/pkg/registry/rbac/validation/rule.go create mode 100644 internal/operator-controller/authorization/internal/kubernetes/plugin/pkg/auth/authorizer/rbac/rbac.go diff --git a/.golangci.yaml b/.golangci.yaml index 7f64bc040..cf46e72fd 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -16,6 +16,10 @@ run: # Default timeout is 1m, up to give more room timeout: 4m +issues: + exclude-dirs: + - internal/operator-controller/authorization/internal/kubernetes + linters: enable: - asciicheck diff --git a/codecov.yml b/codecov.yml index a3bfabd61..5370b84e1 100644 --- a/codecov.yml +++ b/codecov.yml @@ -8,4 +8,7 @@ coverage: paths: - "api/" - "cmd/" - - "internal/" \ No newline at end of file + - "internal/" + +ignore: + - "internal/operator-controller/authorization/internal/kubernetes" diff --git a/go.mod b/go.mod index 246e5a447..638251bd5 100644 --- a/go.mod +++ b/go.mod @@ -36,6 +36,7 @@ require ( k8s.io/cli-runtime v0.32.3 k8s.io/client-go v0.32.3 k8s.io/component-base v0.32.3 + k8s.io/component-helpers v0.32.1 k8s.io/klog/v2 v2.130.1 k8s.io/utils v0.0.0-20241210054802-24370beab758 sigs.k8s.io/controller-runtime v0.20.2 diff --git a/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/helpers.go b/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/helpers.go new file mode 100644 index 000000000..00aa4cae1 --- /dev/null +++ b/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/helpers.go @@ -0,0 +1,375 @@ +/* +Copyright 2016 The Kubernetes Authors. + +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. +*/ + +package rbac + +import ( + "fmt" + "strings" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/sets" +) + +// ResourceMatches returns the result of the rule.Resources matching. +func ResourceMatches(rule *PolicyRule, combinedRequestedResource, requestedSubresource string) bool { + for _, ruleResource := range rule.Resources { + // if everything is allowed, we match + if ruleResource == ResourceAll { + return true + } + // if we have an exact match, we match + if ruleResource == combinedRequestedResource { + return true + } + + // We can also match a */subresource. + // if there isn't a subresource, then continue + if len(requestedSubresource) == 0 { + continue + } + // if the rule isn't in the format */subresource, then we don't match, continue + if len(ruleResource) == len(requestedSubresource)+2 && + strings.HasPrefix(ruleResource, "*/") && + strings.HasSuffix(ruleResource, requestedSubresource) { + return true + + } + } + + return false +} + +// SubjectsStrings returns users, groups, serviceaccounts, unknown for display purposes. +func SubjectsStrings(subjects []Subject) ([]string, []string, []string, []string) { + users := []string{} + groups := []string{} + sas := []string{} + others := []string{} + + for _, subject := range subjects { + switch subject.Kind { + case ServiceAccountKind: + sas = append(sas, fmt.Sprintf("%s/%s", subject.Namespace, subject.Name)) + + case UserKind: + users = append(users, subject.Name) + + case GroupKind: + groups = append(groups, subject.Name) + + default: + others = append(others, fmt.Sprintf("%s/%s/%s", subject.Kind, subject.Namespace, subject.Name)) + } + } + + return users, groups, sas, others +} + +func (r PolicyRule) String() string { + return "PolicyRule" + r.CompactString() +} + +// CompactString exposes a compact string representation for use in escalation error messages +func (r PolicyRule) CompactString() string { + formatStringParts := []string{} + formatArgs := []interface{}{} + if len(r.APIGroups) > 0 { + formatStringParts = append(formatStringParts, "APIGroups:%q") + formatArgs = append(formatArgs, r.APIGroups) + } + if len(r.Resources) > 0 { + formatStringParts = append(formatStringParts, "Resources:%q") + formatArgs = append(formatArgs, r.Resources) + } + if len(r.NonResourceURLs) > 0 { + formatStringParts = append(formatStringParts, "NonResourceURLs:%q") + formatArgs = append(formatArgs, r.NonResourceURLs) + } + if len(r.ResourceNames) > 0 { + formatStringParts = append(formatStringParts, "ResourceNames:%q") + formatArgs = append(formatArgs, r.ResourceNames) + } + if len(r.Verbs) > 0 { + formatStringParts = append(formatStringParts, "Verbs:%q") + formatArgs = append(formatArgs, r.Verbs) + } + formatString := "{" + strings.Join(formatStringParts, ", ") + "}" + return fmt.Sprintf(formatString, formatArgs...) +} + +// PolicyRuleBuilder let's us attach methods. A no-no for API types. +// We use it to construct rules in code. It's more compact than trying to write them +// out in a literal and allows us to perform some basic checking during construction +// +k8s:deepcopy-gen=false +type PolicyRuleBuilder struct { + PolicyRule PolicyRule +} + +// NewRule returns new PolicyRule made by input verbs. +func NewRule(verbs ...string) *PolicyRuleBuilder { + return &PolicyRuleBuilder{ + PolicyRule: PolicyRule{Verbs: sets.NewString(verbs...).List()}, + } +} + +// Groups combines the PolicyRule.APIGroups and input groups. +func (r *PolicyRuleBuilder) Groups(groups ...string) *PolicyRuleBuilder { + r.PolicyRule.APIGroups = combine(r.PolicyRule.APIGroups, groups) + return r +} + +// Resources combines the PolicyRule.Rule and input resources. +func (r *PolicyRuleBuilder) Resources(resources ...string) *PolicyRuleBuilder { + r.PolicyRule.Resources = combine(r.PolicyRule.Resources, resources) + return r +} + +// Names combines the PolicyRule.ResourceNames and input names. +func (r *PolicyRuleBuilder) Names(names ...string) *PolicyRuleBuilder { + r.PolicyRule.ResourceNames = combine(r.PolicyRule.ResourceNames, names) + return r +} + +// URLs combines the PolicyRule.NonResourceURLs and input urls. +func (r *PolicyRuleBuilder) URLs(urls ...string) *PolicyRuleBuilder { + r.PolicyRule.NonResourceURLs = combine(r.PolicyRule.NonResourceURLs, urls) + return r +} + +// RuleOrDie calls the binding method and panics if there is an error. +func (r *PolicyRuleBuilder) RuleOrDie() PolicyRule { + ret, err := r.Rule() + if err != nil { + panic(err) + } + return ret +} + +func combine(s1, s2 []string) []string { + s := sets.NewString(s1...) + s.Insert(s2...) + return s.List() +} + +// Rule returns PolicyRule and error. +func (r *PolicyRuleBuilder) Rule() (PolicyRule, error) { + if len(r.PolicyRule.Verbs) == 0 { + return PolicyRule{}, fmt.Errorf("verbs are required: %#v", r.PolicyRule) + } + + switch { + case len(r.PolicyRule.NonResourceURLs) > 0: + if len(r.PolicyRule.APIGroups) != 0 || len(r.PolicyRule.Resources) != 0 || len(r.PolicyRule.ResourceNames) != 0 { + return PolicyRule{}, fmt.Errorf("non-resource rule may not have apiGroups, resources, or resourceNames: %#v", r.PolicyRule) + } + case len(r.PolicyRule.Resources) > 0: + // resource rule may not have nonResourceURLs + + if len(r.PolicyRule.APIGroups) == 0 { + // this a common bug + return PolicyRule{}, fmt.Errorf("resource rule must have apiGroups: %#v", r.PolicyRule) + } + // if resource names are set, then the verb must not be list, watch, create, or deletecollection + // since verbs are largely opaque, we don't want to accidentally prevent things like "impersonate", so + // we will backlist common mistakes, not whitelist acceptable options. + if len(r.PolicyRule.ResourceNames) != 0 { + illegalVerbs := []string{} + for _, verb := range r.PolicyRule.Verbs { + switch verb { + case "list", "watch", "create", "deletecollection": + illegalVerbs = append(illegalVerbs, verb) + } + } + if len(illegalVerbs) > 0 { + return PolicyRule{}, fmt.Errorf("verbs %v do not have names available: %#v", illegalVerbs, r.PolicyRule) + } + } + + default: + return PolicyRule{}, fmt.Errorf("a rule must have either nonResourceURLs or resources: %#v", r.PolicyRule) + } + + return r.PolicyRule, nil +} + +// ClusterRoleBindingBuilder let's us attach methods. A no-no for API types. +// We use it to construct bindings in code. It's more compact than trying to write them +// out in a literal. +// +k8s:deepcopy-gen=false +type ClusterRoleBindingBuilder struct { + ClusterRoleBinding ClusterRoleBinding +} + +// NewClusterBinding creates a ClusterRoleBinding builder that can be used +// to define the subjects of a cluster role binding. At least one of +// the `Groups`, `Users` or `SAs` method must be called before +// calling the `Binding*` methods. +func NewClusterBinding(clusterRoleName string) *ClusterRoleBindingBuilder { + return &ClusterRoleBindingBuilder{ + ClusterRoleBinding: ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{Name: clusterRoleName}, + RoleRef: RoleRef{ + APIGroup: GroupName, + Kind: "ClusterRole", + Name: clusterRoleName, + }, + }, + } +} + +// Groups adds the specified groups as the subjects of the ClusterRoleBinding. +func (r *ClusterRoleBindingBuilder) Groups(groups ...string) *ClusterRoleBindingBuilder { + for _, group := range groups { + r.ClusterRoleBinding.Subjects = append(r.ClusterRoleBinding.Subjects, Subject{Kind: GroupKind, APIGroup: GroupName, Name: group}) + } + return r +} + +// Users adds the specified users as the subjects of the ClusterRoleBinding. +func (r *ClusterRoleBindingBuilder) Users(users ...string) *ClusterRoleBindingBuilder { + for _, user := range users { + r.ClusterRoleBinding.Subjects = append(r.ClusterRoleBinding.Subjects, Subject{Kind: UserKind, APIGroup: GroupName, Name: user}) + } + return r +} + +// SAs adds the specified sas as the subjects of the ClusterRoleBinding. +func (r *ClusterRoleBindingBuilder) SAs(namespace string, serviceAccountNames ...string) *ClusterRoleBindingBuilder { + for _, saName := range serviceAccountNames { + r.ClusterRoleBinding.Subjects = append(r.ClusterRoleBinding.Subjects, Subject{Kind: ServiceAccountKind, Namespace: namespace, Name: saName}) + } + return r +} + +// BindingOrDie calls the binding method and panics if there is an error. +func (r *ClusterRoleBindingBuilder) BindingOrDie() ClusterRoleBinding { + ret, err := r.Binding() + if err != nil { + panic(err) + } + return ret +} + +// Binding builds and returns the ClusterRoleBinding API object from the builder +// object. +func (r *ClusterRoleBindingBuilder) Binding() (ClusterRoleBinding, error) { + if len(r.ClusterRoleBinding.Subjects) == 0 { + return ClusterRoleBinding{}, fmt.Errorf("subjects are required: %#v", r.ClusterRoleBinding) + } + + return r.ClusterRoleBinding, nil +} + +// RoleBindingBuilder let's us attach methods. It is similar to +// ClusterRoleBindingBuilder above. +// +k8s:deepcopy-gen=false +type RoleBindingBuilder struct { + RoleBinding RoleBinding +} + +// NewRoleBinding creates a RoleBinding builder that can be used +// to define the subjects of a role binding. At least one of +// the `Groups`, `Users` or `SAs` method must be called before +// calling the `Binding*` methods. +func NewRoleBinding(roleName, namespace string) *RoleBindingBuilder { + return &RoleBindingBuilder{ + RoleBinding: RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: roleName, + Namespace: namespace, + }, + RoleRef: RoleRef{ + APIGroup: GroupName, + Kind: "Role", + Name: roleName, + }, + }, + } +} + +// NewRoleBindingForClusterRole creates a RoleBinding builder that can be used +// to define the subjects of a cluster role binding. At least one of +// the `Groups`, `Users` or `SAs` method must be called before +// calling the `Binding*` methods. +func NewRoleBindingForClusterRole(roleName, namespace string) *RoleBindingBuilder { + return &RoleBindingBuilder{ + RoleBinding: RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: roleName, + Namespace: namespace, + }, + RoleRef: RoleRef{ + APIGroup: GroupName, + Kind: "ClusterRole", + Name: roleName, + }, + }, + } +} + +// Groups adds the specified groups as the subjects of the RoleBinding. +func (r *RoleBindingBuilder) Groups(groups ...string) *RoleBindingBuilder { + for _, group := range groups { + r.RoleBinding.Subjects = append(r.RoleBinding.Subjects, Subject{Kind: GroupKind, APIGroup: GroupName, Name: group}) + } + return r +} + +// Users adds the specified users as the subjects of the RoleBinding. +func (r *RoleBindingBuilder) Users(users ...string) *RoleBindingBuilder { + for _, user := range users { + r.RoleBinding.Subjects = append(r.RoleBinding.Subjects, Subject{Kind: UserKind, APIGroup: GroupName, Name: user}) + } + return r +} + +// SAs adds the specified service accounts as the subjects of the +// RoleBinding. +func (r *RoleBindingBuilder) SAs(namespace string, serviceAccountNames ...string) *RoleBindingBuilder { + for _, saName := range serviceAccountNames { + r.RoleBinding.Subjects = append(r.RoleBinding.Subjects, Subject{Kind: ServiceAccountKind, Namespace: namespace, Name: saName}) + } + return r +} + +// BindingOrDie calls the binding method and panics if there is an error. +func (r *RoleBindingBuilder) BindingOrDie() RoleBinding { + ret, err := r.Binding() + if err != nil { + panic(err) + } + return ret +} + +// Binding builds and returns the RoleBinding API object from the builder +// object. +func (r *RoleBindingBuilder) Binding() (RoleBinding, error) { + if len(r.RoleBinding.Subjects) == 0 { + return RoleBinding{}, fmt.Errorf("subjects are required: %#v", r.RoleBinding) + } + + return r.RoleBinding, nil +} + +// SortableRuleSlice is the slice of PolicyRule. +type SortableRuleSlice []PolicyRule + +func (s SortableRuleSlice) Len() int { return len(s) } +func (s SortableRuleSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +func (s SortableRuleSlice) Less(i, j int) bool { + return strings.Compare(s[i].String(), s[j].String()) < 0 +} diff --git a/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/register.go b/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/register.go new file mode 100644 index 000000000..48f5f6e74 --- /dev/null +++ b/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/register.go @@ -0,0 +1,60 @@ +/* +Copyright 2016 The Kubernetes Authors. + +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. +*/ + +package rbac + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// GroupName is the name of this API group. +const GroupName = "rbac.authorization.k8s.io" + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal} + +// Kind takes an unqualified kind and returns a Group qualified GroupKind +func Kind(kind string) schema.GroupKind { + return SchemeGroupVersion.WithKind(kind).GroupKind() +} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +// SchemeBuilder is a function that calls Register for you. +var ( + SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) + AddToScheme = SchemeBuilder.AddToScheme +) + +// Adds the list of known types to the given scheme. +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &Role{}, + &RoleBinding{}, + &RoleBindingList{}, + &RoleList{}, + + &ClusterRole{}, + &ClusterRoleBinding{}, + &ClusterRoleBindingList{}, + &ClusterRoleList{}, + ) + return nil +} diff --git a/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/types.go b/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/types.go new file mode 100644 index 000000000..357e8d8bc --- /dev/null +++ b/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/types.go @@ -0,0 +1,210 @@ +/* +Copyright 2016 The Kubernetes Authors. + +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. +*/ + +package rbac + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// Authorization is calculated against +// 1. evaluation of ClusterRoleBindings - short circuit on match +// 2. evaluation of RoleBindings in the namespace requested - short circuit on match +// 3. deny by default + +// APIGroupAll and these consts are default values for rbac authorization. +const ( + APIGroupAll = "*" + ResourceAll = "*" + VerbAll = "*" + NonResourceAll = "*" + + GroupKind = "Group" + ServiceAccountKind = "ServiceAccount" + UserKind = "User" + + // AutoUpdateAnnotationKey is the name of an annotation which prevents reconciliation if set to "false" + AutoUpdateAnnotationKey = "rbac.authorization.kubernetes.io/autoupdate" +) + +// PolicyRule holds information that describes a policy rule, but does not contain information +// about who the rule applies to or which namespace the rule applies to. +type PolicyRule struct { + // Verbs is a list of Verbs that apply to ALL the ResourceKinds contained in this rule. '*' represents all verbs. + Verbs []string + + // APIGroups is the name of the APIGroup that contains the resources. + // If multiple API groups are specified, any action requested against one of the enumerated resources in any API group will be allowed. "" represents the core API group and "*" represents all API groups. + APIGroups []string + // Resources is a list of resources this rule applies to. '*' represents all resources in the specified apiGroups. + // '*/foo' represents the subresource 'foo' for all resources in the specified apiGroups. + Resources []string + // ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. + ResourceNames []string + + // NonResourceURLs is a set of partial urls that a user should have access to. *s are allowed, but only as the full, final step in the path + // If an action is not a resource API request, then the URL is split on '/' and is checked against the NonResourceURLs to look for a match. + // Since non-resource URLs are not namespaced, this field is only applicable for ClusterRoles referenced from a ClusterRoleBinding. + // Rules can either apply to API resources (such as "pods" or "secrets") or non-resource URL paths (such as "/api"), but not both. + NonResourceURLs []string +} + +// Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, +// or a value for non-objects such as user and group names. +type Subject struct { + // Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". + // If the Authorizer does not recognized the kind value, the Authorizer should report an error. + Kind string + // APIGroup holds the API group of the referenced subject. + // Defaults to "" for ServiceAccount subjects. + // Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + APIGroup string + // Name of the object being referenced. + Name string + // Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty + // the Authorizer should report an error. + Namespace string +} + +// RoleRef contains information that points to the role being used +type RoleRef struct { + // APIGroup is the group for the resource being referenced + APIGroup string + // Kind is the type of resource being referenced + Kind string + // Name is the name of resource being referenced + Name string +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// Role is a namespaced, logical grouping of PolicyRules that can be referenced as a unit by a RoleBinding. +type Role struct { + metav1.TypeMeta + // Standard object's metadata. + metav1.ObjectMeta + + // Rules holds all the PolicyRules for this Role + Rules []PolicyRule +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// RoleBinding references a role, but does not contain it. It can reference a Role in the same namespace or a ClusterRole in the global namespace. +// It adds who information via Subjects and namespace information by which namespace it exists in. RoleBindings in a given +// namespace only have effect in that namespace. +type RoleBinding struct { + metav1.TypeMeta + metav1.ObjectMeta + + // Subjects holds references to the objects the role applies to. + Subjects []Subject + + // RoleRef can reference a Role in the current namespace or a ClusterRole in the global namespace. + // If the RoleRef cannot be resolved, the Authorizer must return an error. + // This field is immutable. + RoleRef RoleRef +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// RoleBindingList is a collection of RoleBindings +type RoleBindingList struct { + metav1.TypeMeta + // Standard object's metadata. + metav1.ListMeta + + // Items is a list of roleBindings + Items []RoleBinding +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// RoleList is a collection of Roles +type RoleList struct { + metav1.TypeMeta + // Standard object's metadata. + metav1.ListMeta + + // Items is a list of roles + Items []Role +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ClusterRole is a cluster level, logical grouping of PolicyRules that can be referenced as a unit by a RoleBinding or ClusterRoleBinding. +type ClusterRole struct { + metav1.TypeMeta + // Standard object's metadata. + metav1.ObjectMeta + + // Rules holds all the PolicyRules for this ClusterRole + Rules []PolicyRule + + // AggregationRule is an optional field that describes how to build the Rules for this ClusterRole. + // If AggregationRule is set, then the Rules are controller managed and direct changes to Rules will be + // stomped by the controller. + AggregationRule *AggregationRule +} + +// AggregationRule describes how to locate ClusterRoles to aggregate into the ClusterRole +type AggregationRule struct { + // ClusterRoleSelectors holds a list of selectors which will be used to find ClusterRoles and create the rules. + // If any of the selectors match, then the ClusterRole's permissions will be added + ClusterRoleSelectors []metav1.LabelSelector +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ClusterRoleBinding references a ClusterRole, but not contain it. It can reference a ClusterRole in the global namespace, +// and adds who information via Subject. +type ClusterRoleBinding struct { + metav1.TypeMeta + // Standard object's metadata. + metav1.ObjectMeta + + // Subjects holds references to the objects the role applies to. + Subjects []Subject + + // RoleRef can only reference a ClusterRole in the global namespace. + // If the RoleRef cannot be resolved, the Authorizer must return an error. + // This field is immutable. + RoleRef RoleRef +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ClusterRoleBindingList is a collection of ClusterRoleBindings +type ClusterRoleBindingList struct { + metav1.TypeMeta + // Standard object's metadata. + metav1.ListMeta + + // Items is a list of ClusterRoleBindings + Items []ClusterRoleBinding +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ClusterRoleList is a collection of ClusterRoles +type ClusterRoleList struct { + metav1.TypeMeta + // Standard object's metadata. + metav1.ListMeta + + // Items is a list of ClusterRoles + Items []ClusterRole +} diff --git a/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1/defaults.go b/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1/defaults.go new file mode 100644 index 000000000..7d285a857 --- /dev/null +++ b/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1/defaults.go @@ -0,0 +1,49 @@ +/* +Copyright 2017 The Kubernetes Authors. + +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. +*/ + +package v1 + +import ( + rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +func addDefaultingFuncs(scheme *runtime.Scheme) error { + return RegisterDefaults(scheme) +} + +func SetDefaults_ClusterRoleBinding(obj *rbacv1.ClusterRoleBinding) { + if len(obj.RoleRef.APIGroup) == 0 { + obj.RoleRef.APIGroup = GroupName + } +} +func SetDefaults_RoleBinding(obj *rbacv1.RoleBinding) { + if len(obj.RoleRef.APIGroup) == 0 { + obj.RoleRef.APIGroup = GroupName + } +} +func SetDefaults_Subject(obj *rbacv1.Subject) { + if len(obj.APIGroup) == 0 { + switch obj.Kind { + case rbacv1.ServiceAccountKind: + obj.APIGroup = "" + case rbacv1.UserKind: + obj.APIGroup = GroupName + case rbacv1.GroupKind: + obj.APIGroup = GroupName + } + } +} diff --git a/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1/evaluation_helpers.go b/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1/evaluation_helpers.go new file mode 100644 index 000000000..5f5edaff1 --- /dev/null +++ b/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1/evaluation_helpers.go @@ -0,0 +1,144 @@ +/* +Copyright 2018 The Kubernetes Authors. + +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. +*/ + +package v1 + +import ( + "fmt" + "strings" + + rbacv1 "k8s.io/api/rbac/v1" +) + +func VerbMatches(rule *rbacv1.PolicyRule, requestedVerb string) bool { + for _, ruleVerb := range rule.Verbs { + if ruleVerb == rbacv1.VerbAll { + return true + } + if ruleVerb == requestedVerb { + return true + } + } + + return false +} + +func APIGroupMatches(rule *rbacv1.PolicyRule, requestedGroup string) bool { + for _, ruleGroup := range rule.APIGroups { + if ruleGroup == rbacv1.APIGroupAll { + return true + } + if ruleGroup == requestedGroup { + return true + } + } + + return false +} + +func ResourceMatches(rule *rbacv1.PolicyRule, combinedRequestedResource, requestedSubresource string) bool { + for _, ruleResource := range rule.Resources { + // if everything is allowed, we match + if ruleResource == rbacv1.ResourceAll { + return true + } + // if we have an exact match, we match + if ruleResource == combinedRequestedResource { + return true + } + + // We can also match a */subresource. + // if there isn't a subresource, then continue + if len(requestedSubresource) == 0 { + continue + } + // if the rule isn't in the format */subresource, then we don't match, continue + if len(ruleResource) == len(requestedSubresource)+2 && + strings.HasPrefix(ruleResource, "*/") && + strings.HasSuffix(ruleResource, requestedSubresource) { + return true + + } + } + + return false +} + +func ResourceNameMatches(rule *rbacv1.PolicyRule, requestedName string) bool { + if len(rule.ResourceNames) == 0 { + return true + } + + for _, ruleName := range rule.ResourceNames { + if ruleName == requestedName { + return true + } + } + + return false +} + +func NonResourceURLMatches(rule *rbacv1.PolicyRule, requestedURL string) bool { + for _, ruleURL := range rule.NonResourceURLs { + if ruleURL == rbacv1.NonResourceAll { + return true + } + if ruleURL == requestedURL { + return true + } + if strings.HasSuffix(ruleURL, "*") && strings.HasPrefix(requestedURL, strings.TrimRight(ruleURL, "*")) { + return true + } + } + + return false +} + +// CompactString exposes a compact string representation for use in escalation error messages +func CompactString(r rbacv1.PolicyRule) string { + formatStringParts := []string{} + formatArgs := []interface{}{} + if len(r.APIGroups) > 0 { + formatStringParts = append(formatStringParts, "APIGroups:%q") + formatArgs = append(formatArgs, r.APIGroups) + } + if len(r.Resources) > 0 { + formatStringParts = append(formatStringParts, "Resources:%q") + formatArgs = append(formatArgs, r.Resources) + } + if len(r.NonResourceURLs) > 0 { + formatStringParts = append(formatStringParts, "NonResourceURLs:%q") + formatArgs = append(formatArgs, r.NonResourceURLs) + } + if len(r.ResourceNames) > 0 { + formatStringParts = append(formatStringParts, "ResourceNames:%q") + formatArgs = append(formatArgs, r.ResourceNames) + } + if len(r.Verbs) > 0 { + formatStringParts = append(formatStringParts, "Verbs:%q") + formatArgs = append(formatArgs, r.Verbs) + } + formatString := "{" + strings.Join(formatStringParts, ", ") + "}" + return fmt.Sprintf(formatString, formatArgs...) +} + +type SortableRuleSlice []rbacv1.PolicyRule + +func (s SortableRuleSlice) Len() int { return len(s) } +func (s SortableRuleSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +func (s SortableRuleSlice) Less(i, j int) bool { + return strings.Compare(s[i].String(), s[j].String()) < 0 +} diff --git a/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1/helpers.go b/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1/helpers.go new file mode 100644 index 000000000..669e48c8a --- /dev/null +++ b/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1/helpers.go @@ -0,0 +1,231 @@ +/* +Copyright 2017 The Kubernetes Authors. + +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. +*/ + +package v1 + +import ( + "fmt" + + rbacv1 "k8s.io/api/rbac/v1" + + "sort" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +k8s:deepcopy-gen=false + +// PolicyRuleBuilder let's us attach methods. A no-no for API types. +// We use it to construct rules in code. It's more compact than trying to write them +// out in a literal and allows us to perform some basic checking during construction +type PolicyRuleBuilder struct { + PolicyRule rbacv1.PolicyRule `protobuf:"bytes,1,opt,name=policyRule"` +} + +func NewRule(verbs ...string) *PolicyRuleBuilder { + return &PolicyRuleBuilder{ + PolicyRule: rbacv1.PolicyRule{Verbs: verbs}, + } +} + +func (r *PolicyRuleBuilder) Groups(groups ...string) *PolicyRuleBuilder { + r.PolicyRule.APIGroups = append(r.PolicyRule.APIGroups, groups...) + return r +} + +func (r *PolicyRuleBuilder) Resources(resources ...string) *PolicyRuleBuilder { + r.PolicyRule.Resources = append(r.PolicyRule.Resources, resources...) + return r +} + +func (r *PolicyRuleBuilder) Names(names ...string) *PolicyRuleBuilder { + r.PolicyRule.ResourceNames = append(r.PolicyRule.ResourceNames, names...) + return r +} + +func (r *PolicyRuleBuilder) URLs(urls ...string) *PolicyRuleBuilder { + r.PolicyRule.NonResourceURLs = append(r.PolicyRule.NonResourceURLs, urls...) + return r +} + +func (r *PolicyRuleBuilder) RuleOrDie() rbacv1.PolicyRule { + ret, err := r.Rule() + if err != nil { + panic(err) + } + return ret +} + +func (r *PolicyRuleBuilder) Rule() (rbacv1.PolicyRule, error) { + if len(r.PolicyRule.Verbs) == 0 { + return rbacv1.PolicyRule{}, fmt.Errorf("verbs are required: %#v", r.PolicyRule) + } + + switch { + case len(r.PolicyRule.NonResourceURLs) > 0: + if len(r.PolicyRule.APIGroups) != 0 || len(r.PolicyRule.Resources) != 0 || len(r.PolicyRule.ResourceNames) != 0 { + return rbacv1.PolicyRule{}, fmt.Errorf("non-resource rule may not have apiGroups, resources, or resourceNames: %#v", r.PolicyRule) + } + case len(r.PolicyRule.Resources) > 0: + if len(r.PolicyRule.NonResourceURLs) != 0 { + return rbacv1.PolicyRule{}, fmt.Errorf("resource rule may not have nonResourceURLs: %#v", r.PolicyRule) + } + if len(r.PolicyRule.APIGroups) == 0 { + // this a common bug + return rbacv1.PolicyRule{}, fmt.Errorf("resource rule must have apiGroups: %#v", r.PolicyRule) + } + default: + return rbacv1.PolicyRule{}, fmt.Errorf("a rule must have either nonResourceURLs or resources: %#v", r.PolicyRule) + } + + sort.Strings(r.PolicyRule.Resources) + sort.Strings(r.PolicyRule.ResourceNames) + sort.Strings(r.PolicyRule.APIGroups) + sort.Strings(r.PolicyRule.NonResourceURLs) + sort.Strings(r.PolicyRule.Verbs) + return r.PolicyRule, nil +} + +// +k8s:deepcopy-gen=false + +// ClusterRoleBindingBuilder let's us attach methods. A no-no for API types. +// We use it to construct bindings in code. It's more compact than trying to write them +// out in a literal. +type ClusterRoleBindingBuilder struct { + ClusterRoleBinding rbacv1.ClusterRoleBinding `protobuf:"bytes,1,opt,name=clusterRoleBinding"` +} + +func NewClusterBinding(clusterRoleName string) *ClusterRoleBindingBuilder { + return &ClusterRoleBindingBuilder{ + ClusterRoleBinding: rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{Name: clusterRoleName}, + RoleRef: rbacv1.RoleRef{ + APIGroup: GroupName, + Kind: "ClusterRole", + Name: clusterRoleName, + }, + }, + } +} + +func (r *ClusterRoleBindingBuilder) Groups(groups ...string) *ClusterRoleBindingBuilder { + for _, group := range groups { + r.ClusterRoleBinding.Subjects = append(r.ClusterRoleBinding.Subjects, rbacv1.Subject{APIGroup: rbacv1.GroupName, Kind: rbacv1.GroupKind, Name: group}) + } + return r +} + +func (r *ClusterRoleBindingBuilder) Users(users ...string) *ClusterRoleBindingBuilder { + for _, user := range users { + r.ClusterRoleBinding.Subjects = append(r.ClusterRoleBinding.Subjects, rbacv1.Subject{APIGroup: rbacv1.GroupName, Kind: rbacv1.UserKind, Name: user}) + } + return r +} + +func (r *ClusterRoleBindingBuilder) SAs(namespace string, serviceAccountNames ...string) *ClusterRoleBindingBuilder { + for _, saName := range serviceAccountNames { + r.ClusterRoleBinding.Subjects = append(r.ClusterRoleBinding.Subjects, rbacv1.Subject{Kind: rbacv1.ServiceAccountKind, Namespace: namespace, Name: saName}) + } + return r +} + +func (r *ClusterRoleBindingBuilder) BindingOrDie() rbacv1.ClusterRoleBinding { + ret, err := r.Binding() + if err != nil { + panic(err) + } + return ret +} + +func (r *ClusterRoleBindingBuilder) Binding() (rbacv1.ClusterRoleBinding, error) { + if len(r.ClusterRoleBinding.Subjects) == 0 { + return rbacv1.ClusterRoleBinding{}, fmt.Errorf("subjects are required: %#v", r.ClusterRoleBinding) + } + + return r.ClusterRoleBinding, nil +} + +// +k8s:deepcopy-gen=false + +// RoleBindingBuilder let's us attach methods. It is similar to +// ClusterRoleBindingBuilder above. +type RoleBindingBuilder struct { + RoleBinding rbacv1.RoleBinding +} + +// NewRoleBinding creates a RoleBinding builder that can be used +// to define the subjects of a role binding. At least one of +// the `Groups`, `Users` or `SAs` method must be called before +// calling the `Binding*` methods. +func NewRoleBinding(roleName, namespace string) *RoleBindingBuilder { + return &RoleBindingBuilder{ + RoleBinding: rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: roleName, + Namespace: namespace, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: GroupName, + Kind: "Role", + Name: roleName, + }, + }, + } +} + +// Groups adds the specified groups as the subjects of the RoleBinding. +func (r *RoleBindingBuilder) Groups(groups ...string) *RoleBindingBuilder { + for _, group := range groups { + r.RoleBinding.Subjects = append(r.RoleBinding.Subjects, rbacv1.Subject{Kind: rbacv1.GroupKind, APIGroup: GroupName, Name: group}) + } + return r +} + +// Users adds the specified users as the subjects of the RoleBinding. +func (r *RoleBindingBuilder) Users(users ...string) *RoleBindingBuilder { + for _, user := range users { + r.RoleBinding.Subjects = append(r.RoleBinding.Subjects, rbacv1.Subject{Kind: rbacv1.UserKind, APIGroup: GroupName, Name: user}) + } + return r +} + +// SAs adds the specified service accounts as the subjects of the +// RoleBinding. +func (r *RoleBindingBuilder) SAs(namespace string, serviceAccountNames ...string) *RoleBindingBuilder { + for _, saName := range serviceAccountNames { + r.RoleBinding.Subjects = append(r.RoleBinding.Subjects, rbacv1.Subject{Kind: rbacv1.ServiceAccountKind, Namespace: namespace, Name: saName}) + } + return r +} + +// BindingOrDie calls the binding method and panics if there is an error. +func (r *RoleBindingBuilder) BindingOrDie() rbacv1.RoleBinding { + ret, err := r.Binding() + if err != nil { + panic(err) + } + return ret +} + +// Binding builds and returns the RoleBinding API object from the builder +// object. +func (r *RoleBindingBuilder) Binding() (rbacv1.RoleBinding, error) { + if len(r.RoleBinding.Subjects) == 0 { + return rbacv1.RoleBinding{}, fmt.Errorf("subjects are required: %#v", r.RoleBinding) + } + + return r.RoleBinding, nil +} diff --git a/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1/register.go b/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1/register.go new file mode 100644 index 000000000..ae138c888 --- /dev/null +++ b/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1/register.go @@ -0,0 +1,44 @@ +/* +Copyright 2017 The Kubernetes Authors. + +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. +*/ + +package v1 + +import ( + rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +const GroupName = "rbac.authorization.k8s.io" + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1"} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +var ( + localSchemeBuilder = &rbacv1.SchemeBuilder + AddToScheme = localSchemeBuilder.AddToScheme +) + +func init() { + // We only register manually written functions here. The registration of the + // generated functions takes place in the generated files. The separation + // makes the code compile even when the generated files are missing. + localSchemeBuilder.Register(addDefaultingFuncs) +} diff --git a/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1/zz_generated.conversion.go b/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1/zz_generated.conversion.go new file mode 100644 index 000000000..66f0d13c3 --- /dev/null +++ b/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1/zz_generated.conversion.go @@ -0,0 +1,450 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright The Kubernetes Authors. + +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 conversion-gen. DO NOT EDIT. + +package v1 + +import ( + unsafe "unsafe" + + rbac "github.com/operator-framework/operator-controller/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac" + rbacv1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + conversion "k8s.io/apimachinery/pkg/conversion" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +func init() { + localSchemeBuilder.Register(RegisterConversions) +} + +// RegisterConversions adds conversion functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterConversions(s *runtime.Scheme) error { + if err := s.AddGeneratedConversionFunc((*rbacv1.AggregationRule)(nil), (*rbac.AggregationRule)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_AggregationRule_To_rbac_AggregationRule(a.(*rbacv1.AggregationRule), b.(*rbac.AggregationRule), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*rbac.AggregationRule)(nil), (*rbacv1.AggregationRule)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_rbac_AggregationRule_To_v1_AggregationRule(a.(*rbac.AggregationRule), b.(*rbacv1.AggregationRule), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*rbacv1.ClusterRole)(nil), (*rbac.ClusterRole)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_ClusterRole_To_rbac_ClusterRole(a.(*rbacv1.ClusterRole), b.(*rbac.ClusterRole), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*rbac.ClusterRole)(nil), (*rbacv1.ClusterRole)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_rbac_ClusterRole_To_v1_ClusterRole(a.(*rbac.ClusterRole), b.(*rbacv1.ClusterRole), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*rbacv1.ClusterRoleBinding)(nil), (*rbac.ClusterRoleBinding)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_ClusterRoleBinding_To_rbac_ClusterRoleBinding(a.(*rbacv1.ClusterRoleBinding), b.(*rbac.ClusterRoleBinding), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*rbac.ClusterRoleBinding)(nil), (*rbacv1.ClusterRoleBinding)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_rbac_ClusterRoleBinding_To_v1_ClusterRoleBinding(a.(*rbac.ClusterRoleBinding), b.(*rbacv1.ClusterRoleBinding), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*rbacv1.ClusterRoleBindingList)(nil), (*rbac.ClusterRoleBindingList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_ClusterRoleBindingList_To_rbac_ClusterRoleBindingList(a.(*rbacv1.ClusterRoleBindingList), b.(*rbac.ClusterRoleBindingList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*rbac.ClusterRoleBindingList)(nil), (*rbacv1.ClusterRoleBindingList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_rbac_ClusterRoleBindingList_To_v1_ClusterRoleBindingList(a.(*rbac.ClusterRoleBindingList), b.(*rbacv1.ClusterRoleBindingList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*rbacv1.ClusterRoleList)(nil), (*rbac.ClusterRoleList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_ClusterRoleList_To_rbac_ClusterRoleList(a.(*rbacv1.ClusterRoleList), b.(*rbac.ClusterRoleList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*rbac.ClusterRoleList)(nil), (*rbacv1.ClusterRoleList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_rbac_ClusterRoleList_To_v1_ClusterRoleList(a.(*rbac.ClusterRoleList), b.(*rbacv1.ClusterRoleList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*rbacv1.PolicyRule)(nil), (*rbac.PolicyRule)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_PolicyRule_To_rbac_PolicyRule(a.(*rbacv1.PolicyRule), b.(*rbac.PolicyRule), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*rbac.PolicyRule)(nil), (*rbacv1.PolicyRule)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_rbac_PolicyRule_To_v1_PolicyRule(a.(*rbac.PolicyRule), b.(*rbacv1.PolicyRule), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*rbacv1.Role)(nil), (*rbac.Role)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_Role_To_rbac_Role(a.(*rbacv1.Role), b.(*rbac.Role), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*rbac.Role)(nil), (*rbacv1.Role)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_rbac_Role_To_v1_Role(a.(*rbac.Role), b.(*rbacv1.Role), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*rbacv1.RoleBinding)(nil), (*rbac.RoleBinding)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_RoleBinding_To_rbac_RoleBinding(a.(*rbacv1.RoleBinding), b.(*rbac.RoleBinding), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*rbac.RoleBinding)(nil), (*rbacv1.RoleBinding)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_rbac_RoleBinding_To_v1_RoleBinding(a.(*rbac.RoleBinding), b.(*rbacv1.RoleBinding), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*rbacv1.RoleBindingList)(nil), (*rbac.RoleBindingList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_RoleBindingList_To_rbac_RoleBindingList(a.(*rbacv1.RoleBindingList), b.(*rbac.RoleBindingList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*rbac.RoleBindingList)(nil), (*rbacv1.RoleBindingList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_rbac_RoleBindingList_To_v1_RoleBindingList(a.(*rbac.RoleBindingList), b.(*rbacv1.RoleBindingList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*rbacv1.RoleList)(nil), (*rbac.RoleList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_RoleList_To_rbac_RoleList(a.(*rbacv1.RoleList), b.(*rbac.RoleList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*rbac.RoleList)(nil), (*rbacv1.RoleList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_rbac_RoleList_To_v1_RoleList(a.(*rbac.RoleList), b.(*rbacv1.RoleList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*rbacv1.RoleRef)(nil), (*rbac.RoleRef)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_RoleRef_To_rbac_RoleRef(a.(*rbacv1.RoleRef), b.(*rbac.RoleRef), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*rbac.RoleRef)(nil), (*rbacv1.RoleRef)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_rbac_RoleRef_To_v1_RoleRef(a.(*rbac.RoleRef), b.(*rbacv1.RoleRef), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*rbacv1.Subject)(nil), (*rbac.Subject)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_Subject_To_rbac_Subject(a.(*rbacv1.Subject), b.(*rbac.Subject), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*rbac.Subject)(nil), (*rbacv1.Subject)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_rbac_Subject_To_v1_Subject(a.(*rbac.Subject), b.(*rbacv1.Subject), scope) + }); err != nil { + return err + } + return nil +} + +func autoConvert_v1_AggregationRule_To_rbac_AggregationRule(in *rbacv1.AggregationRule, out *rbac.AggregationRule, s conversion.Scope) error { + out.ClusterRoleSelectors = *(*[]metav1.LabelSelector)(unsafe.Pointer(&in.ClusterRoleSelectors)) + return nil +} + +// Convert_v1_AggregationRule_To_rbac_AggregationRule is an autogenerated conversion function. +func Convert_v1_AggregationRule_To_rbac_AggregationRule(in *rbacv1.AggregationRule, out *rbac.AggregationRule, s conversion.Scope) error { + return autoConvert_v1_AggregationRule_To_rbac_AggregationRule(in, out, s) +} + +func autoConvert_rbac_AggregationRule_To_v1_AggregationRule(in *rbac.AggregationRule, out *rbacv1.AggregationRule, s conversion.Scope) error { + out.ClusterRoleSelectors = *(*[]metav1.LabelSelector)(unsafe.Pointer(&in.ClusterRoleSelectors)) + return nil +} + +// Convert_rbac_AggregationRule_To_v1_AggregationRule is an autogenerated conversion function. +func Convert_rbac_AggregationRule_To_v1_AggregationRule(in *rbac.AggregationRule, out *rbacv1.AggregationRule, s conversion.Scope) error { + return autoConvert_rbac_AggregationRule_To_v1_AggregationRule(in, out, s) +} + +func autoConvert_v1_ClusterRole_To_rbac_ClusterRole(in *rbacv1.ClusterRole, out *rbac.ClusterRole, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + out.Rules = *(*[]rbac.PolicyRule)(unsafe.Pointer(&in.Rules)) + out.AggregationRule = (*rbac.AggregationRule)(unsafe.Pointer(in.AggregationRule)) + return nil +} + +// Convert_v1_ClusterRole_To_rbac_ClusterRole is an autogenerated conversion function. +func Convert_v1_ClusterRole_To_rbac_ClusterRole(in *rbacv1.ClusterRole, out *rbac.ClusterRole, s conversion.Scope) error { + return autoConvert_v1_ClusterRole_To_rbac_ClusterRole(in, out, s) +} + +func autoConvert_rbac_ClusterRole_To_v1_ClusterRole(in *rbac.ClusterRole, out *rbacv1.ClusterRole, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + out.Rules = *(*[]rbacv1.PolicyRule)(unsafe.Pointer(&in.Rules)) + out.AggregationRule = (*rbacv1.AggregationRule)(unsafe.Pointer(in.AggregationRule)) + return nil +} + +// Convert_rbac_ClusterRole_To_v1_ClusterRole is an autogenerated conversion function. +func Convert_rbac_ClusterRole_To_v1_ClusterRole(in *rbac.ClusterRole, out *rbacv1.ClusterRole, s conversion.Scope) error { + return autoConvert_rbac_ClusterRole_To_v1_ClusterRole(in, out, s) +} + +func autoConvert_v1_ClusterRoleBinding_To_rbac_ClusterRoleBinding(in *rbacv1.ClusterRoleBinding, out *rbac.ClusterRoleBinding, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + out.Subjects = *(*[]rbac.Subject)(unsafe.Pointer(&in.Subjects)) + if err := Convert_v1_RoleRef_To_rbac_RoleRef(&in.RoleRef, &out.RoleRef, s); err != nil { + return err + } + return nil +} + +// Convert_v1_ClusterRoleBinding_To_rbac_ClusterRoleBinding is an autogenerated conversion function. +func Convert_v1_ClusterRoleBinding_To_rbac_ClusterRoleBinding(in *rbacv1.ClusterRoleBinding, out *rbac.ClusterRoleBinding, s conversion.Scope) error { + return autoConvert_v1_ClusterRoleBinding_To_rbac_ClusterRoleBinding(in, out, s) +} + +func autoConvert_rbac_ClusterRoleBinding_To_v1_ClusterRoleBinding(in *rbac.ClusterRoleBinding, out *rbacv1.ClusterRoleBinding, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + out.Subjects = *(*[]rbacv1.Subject)(unsafe.Pointer(&in.Subjects)) + if err := Convert_rbac_RoleRef_To_v1_RoleRef(&in.RoleRef, &out.RoleRef, s); err != nil { + return err + } + return nil +} + +// Convert_rbac_ClusterRoleBinding_To_v1_ClusterRoleBinding is an autogenerated conversion function. +func Convert_rbac_ClusterRoleBinding_To_v1_ClusterRoleBinding(in *rbac.ClusterRoleBinding, out *rbacv1.ClusterRoleBinding, s conversion.Scope) error { + return autoConvert_rbac_ClusterRoleBinding_To_v1_ClusterRoleBinding(in, out, s) +} + +func autoConvert_v1_ClusterRoleBindingList_To_rbac_ClusterRoleBindingList(in *rbacv1.ClusterRoleBindingList, out *rbac.ClusterRoleBindingList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]rbac.ClusterRoleBinding)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_v1_ClusterRoleBindingList_To_rbac_ClusterRoleBindingList is an autogenerated conversion function. +func Convert_v1_ClusterRoleBindingList_To_rbac_ClusterRoleBindingList(in *rbacv1.ClusterRoleBindingList, out *rbac.ClusterRoleBindingList, s conversion.Scope) error { + return autoConvert_v1_ClusterRoleBindingList_To_rbac_ClusterRoleBindingList(in, out, s) +} + +func autoConvert_rbac_ClusterRoleBindingList_To_v1_ClusterRoleBindingList(in *rbac.ClusterRoleBindingList, out *rbacv1.ClusterRoleBindingList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]rbacv1.ClusterRoleBinding)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_rbac_ClusterRoleBindingList_To_v1_ClusterRoleBindingList is an autogenerated conversion function. +func Convert_rbac_ClusterRoleBindingList_To_v1_ClusterRoleBindingList(in *rbac.ClusterRoleBindingList, out *rbacv1.ClusterRoleBindingList, s conversion.Scope) error { + return autoConvert_rbac_ClusterRoleBindingList_To_v1_ClusterRoleBindingList(in, out, s) +} + +func autoConvert_v1_ClusterRoleList_To_rbac_ClusterRoleList(in *rbacv1.ClusterRoleList, out *rbac.ClusterRoleList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]rbac.ClusterRole)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_v1_ClusterRoleList_To_rbac_ClusterRoleList is an autogenerated conversion function. +func Convert_v1_ClusterRoleList_To_rbac_ClusterRoleList(in *rbacv1.ClusterRoleList, out *rbac.ClusterRoleList, s conversion.Scope) error { + return autoConvert_v1_ClusterRoleList_To_rbac_ClusterRoleList(in, out, s) +} + +func autoConvert_rbac_ClusterRoleList_To_v1_ClusterRoleList(in *rbac.ClusterRoleList, out *rbacv1.ClusterRoleList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]rbacv1.ClusterRole)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_rbac_ClusterRoleList_To_v1_ClusterRoleList is an autogenerated conversion function. +func Convert_rbac_ClusterRoleList_To_v1_ClusterRoleList(in *rbac.ClusterRoleList, out *rbacv1.ClusterRoleList, s conversion.Scope) error { + return autoConvert_rbac_ClusterRoleList_To_v1_ClusterRoleList(in, out, s) +} + +func autoConvert_v1_PolicyRule_To_rbac_PolicyRule(in *rbacv1.PolicyRule, out *rbac.PolicyRule, s conversion.Scope) error { + out.Verbs = *(*[]string)(unsafe.Pointer(&in.Verbs)) + out.APIGroups = *(*[]string)(unsafe.Pointer(&in.APIGroups)) + out.Resources = *(*[]string)(unsafe.Pointer(&in.Resources)) + out.ResourceNames = *(*[]string)(unsafe.Pointer(&in.ResourceNames)) + out.NonResourceURLs = *(*[]string)(unsafe.Pointer(&in.NonResourceURLs)) + return nil +} + +// Convert_v1_PolicyRule_To_rbac_PolicyRule is an autogenerated conversion function. +func Convert_v1_PolicyRule_To_rbac_PolicyRule(in *rbacv1.PolicyRule, out *rbac.PolicyRule, s conversion.Scope) error { + return autoConvert_v1_PolicyRule_To_rbac_PolicyRule(in, out, s) +} + +func autoConvert_rbac_PolicyRule_To_v1_PolicyRule(in *rbac.PolicyRule, out *rbacv1.PolicyRule, s conversion.Scope) error { + out.Verbs = *(*[]string)(unsafe.Pointer(&in.Verbs)) + out.APIGroups = *(*[]string)(unsafe.Pointer(&in.APIGroups)) + out.Resources = *(*[]string)(unsafe.Pointer(&in.Resources)) + out.ResourceNames = *(*[]string)(unsafe.Pointer(&in.ResourceNames)) + out.NonResourceURLs = *(*[]string)(unsafe.Pointer(&in.NonResourceURLs)) + return nil +} + +// Convert_rbac_PolicyRule_To_v1_PolicyRule is an autogenerated conversion function. +func Convert_rbac_PolicyRule_To_v1_PolicyRule(in *rbac.PolicyRule, out *rbacv1.PolicyRule, s conversion.Scope) error { + return autoConvert_rbac_PolicyRule_To_v1_PolicyRule(in, out, s) +} + +func autoConvert_v1_Role_To_rbac_Role(in *rbacv1.Role, out *rbac.Role, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + out.Rules = *(*[]rbac.PolicyRule)(unsafe.Pointer(&in.Rules)) + return nil +} + +// Convert_v1_Role_To_rbac_Role is an autogenerated conversion function. +func Convert_v1_Role_To_rbac_Role(in *rbacv1.Role, out *rbac.Role, s conversion.Scope) error { + return autoConvert_v1_Role_To_rbac_Role(in, out, s) +} + +func autoConvert_rbac_Role_To_v1_Role(in *rbac.Role, out *rbacv1.Role, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + out.Rules = *(*[]rbacv1.PolicyRule)(unsafe.Pointer(&in.Rules)) + return nil +} + +// Convert_rbac_Role_To_v1_Role is an autogenerated conversion function. +func Convert_rbac_Role_To_v1_Role(in *rbac.Role, out *rbacv1.Role, s conversion.Scope) error { + return autoConvert_rbac_Role_To_v1_Role(in, out, s) +} + +func autoConvert_v1_RoleBinding_To_rbac_RoleBinding(in *rbacv1.RoleBinding, out *rbac.RoleBinding, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + out.Subjects = *(*[]rbac.Subject)(unsafe.Pointer(&in.Subjects)) + if err := Convert_v1_RoleRef_To_rbac_RoleRef(&in.RoleRef, &out.RoleRef, s); err != nil { + return err + } + return nil +} + +// Convert_v1_RoleBinding_To_rbac_RoleBinding is an autogenerated conversion function. +func Convert_v1_RoleBinding_To_rbac_RoleBinding(in *rbacv1.RoleBinding, out *rbac.RoleBinding, s conversion.Scope) error { + return autoConvert_v1_RoleBinding_To_rbac_RoleBinding(in, out, s) +} + +func autoConvert_rbac_RoleBinding_To_v1_RoleBinding(in *rbac.RoleBinding, out *rbacv1.RoleBinding, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + out.Subjects = *(*[]rbacv1.Subject)(unsafe.Pointer(&in.Subjects)) + if err := Convert_rbac_RoleRef_To_v1_RoleRef(&in.RoleRef, &out.RoleRef, s); err != nil { + return err + } + return nil +} + +// Convert_rbac_RoleBinding_To_v1_RoleBinding is an autogenerated conversion function. +func Convert_rbac_RoleBinding_To_v1_RoleBinding(in *rbac.RoleBinding, out *rbacv1.RoleBinding, s conversion.Scope) error { + return autoConvert_rbac_RoleBinding_To_v1_RoleBinding(in, out, s) +} + +func autoConvert_v1_RoleBindingList_To_rbac_RoleBindingList(in *rbacv1.RoleBindingList, out *rbac.RoleBindingList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]rbac.RoleBinding)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_v1_RoleBindingList_To_rbac_RoleBindingList is an autogenerated conversion function. +func Convert_v1_RoleBindingList_To_rbac_RoleBindingList(in *rbacv1.RoleBindingList, out *rbac.RoleBindingList, s conversion.Scope) error { + return autoConvert_v1_RoleBindingList_To_rbac_RoleBindingList(in, out, s) +} + +func autoConvert_rbac_RoleBindingList_To_v1_RoleBindingList(in *rbac.RoleBindingList, out *rbacv1.RoleBindingList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]rbacv1.RoleBinding)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_rbac_RoleBindingList_To_v1_RoleBindingList is an autogenerated conversion function. +func Convert_rbac_RoleBindingList_To_v1_RoleBindingList(in *rbac.RoleBindingList, out *rbacv1.RoleBindingList, s conversion.Scope) error { + return autoConvert_rbac_RoleBindingList_To_v1_RoleBindingList(in, out, s) +} + +func autoConvert_v1_RoleList_To_rbac_RoleList(in *rbacv1.RoleList, out *rbac.RoleList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]rbac.Role)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_v1_RoleList_To_rbac_RoleList is an autogenerated conversion function. +func Convert_v1_RoleList_To_rbac_RoleList(in *rbacv1.RoleList, out *rbac.RoleList, s conversion.Scope) error { + return autoConvert_v1_RoleList_To_rbac_RoleList(in, out, s) +} + +func autoConvert_rbac_RoleList_To_v1_RoleList(in *rbac.RoleList, out *rbacv1.RoleList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]rbacv1.Role)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_rbac_RoleList_To_v1_RoleList is an autogenerated conversion function. +func Convert_rbac_RoleList_To_v1_RoleList(in *rbac.RoleList, out *rbacv1.RoleList, s conversion.Scope) error { + return autoConvert_rbac_RoleList_To_v1_RoleList(in, out, s) +} + +func autoConvert_v1_RoleRef_To_rbac_RoleRef(in *rbacv1.RoleRef, out *rbac.RoleRef, s conversion.Scope) error { + out.APIGroup = in.APIGroup + out.Kind = in.Kind + out.Name = in.Name + return nil +} + +// Convert_v1_RoleRef_To_rbac_RoleRef is an autogenerated conversion function. +func Convert_v1_RoleRef_To_rbac_RoleRef(in *rbacv1.RoleRef, out *rbac.RoleRef, s conversion.Scope) error { + return autoConvert_v1_RoleRef_To_rbac_RoleRef(in, out, s) +} + +func autoConvert_rbac_RoleRef_To_v1_RoleRef(in *rbac.RoleRef, out *rbacv1.RoleRef, s conversion.Scope) error { + out.APIGroup = in.APIGroup + out.Kind = in.Kind + out.Name = in.Name + return nil +} + +// Convert_rbac_RoleRef_To_v1_RoleRef is an autogenerated conversion function. +func Convert_rbac_RoleRef_To_v1_RoleRef(in *rbac.RoleRef, out *rbacv1.RoleRef, s conversion.Scope) error { + return autoConvert_rbac_RoleRef_To_v1_RoleRef(in, out, s) +} + +func autoConvert_v1_Subject_To_rbac_Subject(in *rbacv1.Subject, out *rbac.Subject, s conversion.Scope) error { + out.Kind = in.Kind + out.APIGroup = in.APIGroup + out.Name = in.Name + out.Namespace = in.Namespace + return nil +} + +// Convert_v1_Subject_To_rbac_Subject is an autogenerated conversion function. +func Convert_v1_Subject_To_rbac_Subject(in *rbacv1.Subject, out *rbac.Subject, s conversion.Scope) error { + return autoConvert_v1_Subject_To_rbac_Subject(in, out, s) +} + +func autoConvert_rbac_Subject_To_v1_Subject(in *rbac.Subject, out *rbacv1.Subject, s conversion.Scope) error { + out.Kind = in.Kind + out.APIGroup = in.APIGroup + out.Name = in.Name + out.Namespace = in.Namespace + return nil +} + +// Convert_rbac_Subject_To_v1_Subject is an autogenerated conversion function. +func Convert_rbac_Subject_To_v1_Subject(in *rbac.Subject, out *rbacv1.Subject, s conversion.Scope) error { + return autoConvert_rbac_Subject_To_v1_Subject(in, out, s) +} diff --git a/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1/zz_generated.defaults.go b/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1/zz_generated.defaults.go new file mode 100644 index 000000000..734e76f3e --- /dev/null +++ b/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1/zz_generated.defaults.go @@ -0,0 +1,68 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright The Kubernetes Authors. + +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 defaulter-gen. DO NOT EDIT. + +package v1 + +import ( + rbacv1 "k8s.io/api/rbac/v1" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// RegisterDefaults adds defaulters functions to the given scheme. +// Public to allow building arbitrary schemes. +// All generated defaulters are covering - they call all nested defaulters. +func RegisterDefaults(scheme *runtime.Scheme) error { + scheme.AddTypeDefaultingFunc(&rbacv1.ClusterRoleBinding{}, func(obj interface{}) { SetObjectDefaults_ClusterRoleBinding(obj.(*rbacv1.ClusterRoleBinding)) }) + scheme.AddTypeDefaultingFunc(&rbacv1.ClusterRoleBindingList{}, func(obj interface{}) { SetObjectDefaults_ClusterRoleBindingList(obj.(*rbacv1.ClusterRoleBindingList)) }) + scheme.AddTypeDefaultingFunc(&rbacv1.RoleBinding{}, func(obj interface{}) { SetObjectDefaults_RoleBinding(obj.(*rbacv1.RoleBinding)) }) + scheme.AddTypeDefaultingFunc(&rbacv1.RoleBindingList{}, func(obj interface{}) { SetObjectDefaults_RoleBindingList(obj.(*rbacv1.RoleBindingList)) }) + return nil +} + +func SetObjectDefaults_ClusterRoleBinding(in *rbacv1.ClusterRoleBinding) { + SetDefaults_ClusterRoleBinding(in) + for i := range in.Subjects { + a := &in.Subjects[i] + SetDefaults_Subject(a) + } +} + +func SetObjectDefaults_ClusterRoleBindingList(in *rbacv1.ClusterRoleBindingList) { + for i := range in.Items { + a := &in.Items[i] + SetObjectDefaults_ClusterRoleBinding(a) + } +} + +func SetObjectDefaults_RoleBinding(in *rbacv1.RoleBinding) { + SetDefaults_RoleBinding(in) + for i := range in.Subjects { + a := &in.Subjects[i] + SetDefaults_Subject(a) + } +} + +func SetObjectDefaults_RoleBindingList(in *rbacv1.RoleBindingList) { + for i := range in.Items { + a := &in.Items[i] + SetObjectDefaults_RoleBinding(a) + } +} diff --git a/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/zz_generated.deepcopy.go b/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/zz_generated.deepcopy.go new file mode 100644 index 000000000..0f7023a2d --- /dev/null +++ b/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/zz_generated.deepcopy.go @@ -0,0 +1,412 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright The Kubernetes Authors. + +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 deepcopy-gen. DO NOT EDIT. + +package rbac + +import ( + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AggregationRule) DeepCopyInto(out *AggregationRule) { + *out = *in + if in.ClusterRoleSelectors != nil { + in, out := &in.ClusterRoleSelectors, &out.ClusterRoleSelectors + *out = make([]v1.LabelSelector, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AggregationRule. +func (in *AggregationRule) DeepCopy() *AggregationRule { + if in == nil { + return nil + } + out := new(AggregationRule) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterRole) DeepCopyInto(out *ClusterRole) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + if in.Rules != nil { + in, out := &in.Rules, &out.Rules + *out = make([]PolicyRule, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.AggregationRule != nil { + in, out := &in.AggregationRule, &out.AggregationRule + *out = new(AggregationRule) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterRole. +func (in *ClusterRole) DeepCopy() *ClusterRole { + if in == nil { + return nil + } + out := new(ClusterRole) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ClusterRole) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterRoleBinding) DeepCopyInto(out *ClusterRoleBinding) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + if in.Subjects != nil { + in, out := &in.Subjects, &out.Subjects + *out = make([]Subject, len(*in)) + copy(*out, *in) + } + out.RoleRef = in.RoleRef + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterRoleBinding. +func (in *ClusterRoleBinding) DeepCopy() *ClusterRoleBinding { + if in == nil { + return nil + } + out := new(ClusterRoleBinding) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ClusterRoleBinding) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterRoleBindingList) DeepCopyInto(out *ClusterRoleBindingList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ClusterRoleBinding, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterRoleBindingList. +func (in *ClusterRoleBindingList) DeepCopy() *ClusterRoleBindingList { + if in == nil { + return nil + } + out := new(ClusterRoleBindingList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ClusterRoleBindingList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterRoleList) DeepCopyInto(out *ClusterRoleList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ClusterRole, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterRoleList. +func (in *ClusterRoleList) DeepCopy() *ClusterRoleList { + if in == nil { + return nil + } + out := new(ClusterRoleList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ClusterRoleList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PolicyRule) DeepCopyInto(out *PolicyRule) { + *out = *in + if in.Verbs != nil { + in, out := &in.Verbs, &out.Verbs + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.APIGroups != nil { + in, out := &in.APIGroups, &out.APIGroups + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Resources != nil { + in, out := &in.Resources, &out.Resources + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.ResourceNames != nil { + in, out := &in.ResourceNames, &out.ResourceNames + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.NonResourceURLs != nil { + in, out := &in.NonResourceURLs, &out.NonResourceURLs + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PolicyRule. +func (in *PolicyRule) DeepCopy() *PolicyRule { + if in == nil { + return nil + } + out := new(PolicyRule) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Role) DeepCopyInto(out *Role) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + if in.Rules != nil { + in, out := &in.Rules, &out.Rules + *out = make([]PolicyRule, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Role. +func (in *Role) DeepCopy() *Role { + if in == nil { + return nil + } + out := new(Role) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Role) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RoleBinding) DeepCopyInto(out *RoleBinding) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + if in.Subjects != nil { + in, out := &in.Subjects, &out.Subjects + *out = make([]Subject, len(*in)) + copy(*out, *in) + } + out.RoleRef = in.RoleRef + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RoleBinding. +func (in *RoleBinding) DeepCopy() *RoleBinding { + if in == nil { + return nil + } + out := new(RoleBinding) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *RoleBinding) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RoleBindingList) DeepCopyInto(out *RoleBindingList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]RoleBinding, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RoleBindingList. +func (in *RoleBindingList) DeepCopy() *RoleBindingList { + if in == nil { + return nil + } + out := new(RoleBindingList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *RoleBindingList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RoleList) DeepCopyInto(out *RoleList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Role, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RoleList. +func (in *RoleList) DeepCopy() *RoleList { + if in == nil { + return nil + } + out := new(RoleList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *RoleList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RoleRef) DeepCopyInto(out *RoleRef) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RoleRef. +func (in *RoleRef) DeepCopy() *RoleRef { + if in == nil { + return nil + } + out := new(RoleRef) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in SortableRuleSlice) DeepCopyInto(out *SortableRuleSlice) { + { + in := &in + *out = make(SortableRuleSlice, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + return + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SortableRuleSlice. +func (in SortableRuleSlice) DeepCopy() SortableRuleSlice { + if in == nil { + return nil + } + out := new(SortableRuleSlice) + in.DeepCopyInto(out) + return *out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Subject) DeepCopyInto(out *Subject) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Subject. +func (in *Subject) DeepCopy() *Subject { + if in == nil { + return nil + } + out := new(Subject) + in.DeepCopyInto(out) + return out +} diff --git a/internal/operator-controller/authorization/internal/kubernetes/pkg/registry/rbac/escalation_check.go b/internal/operator-controller/authorization/internal/kubernetes/pkg/registry/rbac/escalation_check.go new file mode 100644 index 000000000..1534f43cc --- /dev/null +++ b/internal/operator-controller/authorization/internal/kubernetes/pkg/registry/rbac/escalation_check.go @@ -0,0 +1,146 @@ +/* +Copyright 2016 The Kubernetes Authors. + +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. +*/ + +package rbac + +import ( + "context" + "fmt" + + "github.com/operator-framework/operator-controller/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac" + "k8s.io/apimachinery/pkg/runtime/schema" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apiserver/pkg/authentication/user" + "k8s.io/apiserver/pkg/authorization/authorizer" + genericapirequest "k8s.io/apiserver/pkg/endpoints/request" +) + +// EscalationAllowed checks if the user associated with the context is a superuser +func EscalationAllowed(ctx context.Context) bool { + u, ok := genericapirequest.UserFrom(ctx) + if !ok { + return false + } + + // system:masters is special because the API server uses it for privileged loopback connections + // therefore we know that a member of system:masters can always do anything + for _, group := range u.GetGroups() { + if group == user.SystemPrivilegedGroup { + return true + } + } + + return false +} + +var roleResources = map[schema.GroupResource]bool{ + rbac.SchemeGroupVersion.WithResource("clusterroles").GroupResource(): true, + rbac.SchemeGroupVersion.WithResource("roles").GroupResource(): true, +} + +// RoleEscalationAuthorized checks if the user associated with the context is explicitly authorized to escalate the role resource associated with the context +func RoleEscalationAuthorized(ctx context.Context, a authorizer.Authorizer) bool { + if a == nil { + return false + } + + user, ok := genericapirequest.UserFrom(ctx) + if !ok { + return false + } + + requestInfo, ok := genericapirequest.RequestInfoFrom(ctx) + if !ok { + return false + } + + if !requestInfo.IsResourceRequest { + return false + } + + requestResource := schema.GroupResource{Group: requestInfo.APIGroup, Resource: requestInfo.Resource} + if !roleResources[requestResource] { + return false + } + + attrs := authorizer.AttributesRecord{ + User: user, + Verb: "escalate", + APIGroup: requestInfo.APIGroup, + APIVersion: "*", + Resource: requestInfo.Resource, + Name: requestInfo.Name, + Namespace: requestInfo.Namespace, + ResourceRequest: true, + } + + decision, _, err := a.Authorize(ctx, attrs) + if err != nil { + utilruntime.HandleError(fmt.Errorf( + "error authorizing user %#v to escalate %#v named %q in namespace %q: %v", + user, requestResource, requestInfo.Name, requestInfo.Namespace, err, + )) + } + return decision == authorizer.DecisionAllow +} + +// BindingAuthorized returns true if the user associated with the context is explicitly authorized to bind the specified roleRef +func BindingAuthorized(ctx context.Context, roleRef rbac.RoleRef, bindingNamespace string, a authorizer.Authorizer) bool { + if a == nil { + return false + } + + user, ok := genericapirequest.UserFrom(ctx) + if !ok { + return false + } + + attrs := authorizer.AttributesRecord{ + User: user, + Verb: "bind", + // check against the namespace where the binding is being created (or the empty namespace for clusterrolebindings). + // this allows delegation to bind particular clusterroles in rolebindings within particular namespaces, + // and to authorize binding a clusterrole across all namespaces in a clusterrolebinding. + Namespace: bindingNamespace, + ResourceRequest: true, + } + + // This occurs after defaulting and conversion, so values pulled from the roleRef won't change + // Invalid APIGroup or Name values will fail validation + switch roleRef.Kind { + case "ClusterRole": + attrs.APIGroup = roleRef.APIGroup + attrs.APIVersion = "*" + attrs.Resource = "clusterroles" + attrs.Name = roleRef.Name + case "Role": + attrs.APIGroup = roleRef.APIGroup + attrs.APIVersion = "*" + attrs.Resource = "roles" + attrs.Name = roleRef.Name + default: + return false + } + + decision, _, err := a.Authorize(ctx, attrs) + if err != nil { + utilruntime.HandleError(fmt.Errorf( + "error authorizing user %#v to bind %#v in namespace %s: %v", + user, roleRef, bindingNamespace, err, + )) + } + return decision == authorizer.DecisionAllow +} diff --git a/internal/operator-controller/authorization/internal/kubernetes/pkg/registry/rbac/validation/policy_compact.go b/internal/operator-controller/authorization/internal/kubernetes/pkg/registry/rbac/validation/policy_compact.go new file mode 100644 index 000000000..182657b1c --- /dev/null +++ b/internal/operator-controller/authorization/internal/kubernetes/pkg/registry/rbac/validation/policy_compact.go @@ -0,0 +1,89 @@ +/* +Copyright 2017 The Kubernetes Authors. + +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. +*/ + +package validation + +import ( + "reflect" + + rbacv1 "k8s.io/api/rbac/v1" +) + +type simpleResource struct { + Group string + Resource string + ResourceNameExist bool + ResourceName string +} + +// CompactRules combines rules that contain a single APIGroup/Resource, differ only by verb, and contain no other attributes. +// this is a fast check, and works well with the decomposed "missing rules" list from a Covers check. +func CompactRules(rules []rbacv1.PolicyRule) ([]rbacv1.PolicyRule, error) { + compacted := make([]rbacv1.PolicyRule, 0, len(rules)) + + simpleRules := map[simpleResource]*rbacv1.PolicyRule{} + for _, rule := range rules { + if resource, isSimple := isSimpleResourceRule(&rule); isSimple { + if existingRule, ok := simpleRules[resource]; ok { + // Add the new verbs to the existing simple resource rule + if existingRule.Verbs == nil { + existingRule.Verbs = []string{} + } + existingRule.Verbs = append(existingRule.Verbs, rule.Verbs...) + } else { + // Copy the rule to accumulate matching simple resource rules into + simpleRules[resource] = rule.DeepCopy() + } + } else { + compacted = append(compacted, rule) + } + } + + // Once we've consolidated the simple resource rules, add them to the compacted list + for _, simpleRule := range simpleRules { + compacted = append(compacted, *simpleRule) + } + + return compacted, nil +} + +// isSimpleResourceRule returns true if the given rule contains verbs, a single resource, a single API group, at most one Resource Name, and no other values +func isSimpleResourceRule(rule *rbacv1.PolicyRule) (simpleResource, bool) { + resource := simpleResource{} + + // If we have "complex" rule attributes, return early without allocations or expensive comparisons + if len(rule.ResourceNames) > 1 || len(rule.NonResourceURLs) > 0 { + return resource, false + } + // If we have multiple api groups or resources, return early + if len(rule.APIGroups) != 1 || len(rule.Resources) != 1 { + return resource, false + } + + // Test if this rule only contains APIGroups/Resources/Verbs/ResourceNames + simpleRule := &rbacv1.PolicyRule{APIGroups: rule.APIGroups, Resources: rule.Resources, Verbs: rule.Verbs, ResourceNames: rule.ResourceNames} + if !reflect.DeepEqual(simpleRule, rule) { + return resource, false + } + + if len(rule.ResourceNames) == 0 { + resource = simpleResource{Group: rule.APIGroups[0], Resource: rule.Resources[0], ResourceNameExist: false} + } else { + resource = simpleResource{Group: rule.APIGroups[0], Resource: rule.Resources[0], ResourceNameExist: true, ResourceName: rule.ResourceNames[0]} + } + + return resource, true +} diff --git a/internal/operator-controller/authorization/internal/kubernetes/pkg/registry/rbac/validation/rule.go b/internal/operator-controller/authorization/internal/kubernetes/pkg/registry/rbac/validation/rule.go new file mode 100644 index 000000000..8847adc5d --- /dev/null +++ b/internal/operator-controller/authorization/internal/kubernetes/pkg/registry/rbac/validation/rule.go @@ -0,0 +1,368 @@ +/* +Copyright 2016 The Kubernetes Authors. + +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. +*/ + +package validation + +import ( + "context" + "errors" + "fmt" + "strings" + + "k8s.io/klog/v2" + + rbacv1helpers "github.com/operator-framework/operator-controller/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1" + rbacv1 "k8s.io/api/rbac/v1" + utilerrors "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apiserver/pkg/authentication/serviceaccount" + "k8s.io/apiserver/pkg/authentication/user" + genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/component-helpers/auth/rbac/validation" +) + +type AuthorizationRuleResolver interface { + // GetRoleReferenceRules attempts to resolve the role reference of a RoleBinding or ClusterRoleBinding. The passed namespace should be the namespace + // of the role binding, the empty string if a cluster role binding. + GetRoleReferenceRules(ctx context.Context, roleRef rbacv1.RoleRef, namespace string) ([]rbacv1.PolicyRule, error) + + // RulesFor returns the list of rules that apply to a given user in a given namespace and error. If an error is returned, the slice of + // PolicyRules may not be complete, but it contains all retrievable rules. This is done because policy rules are purely additive and policy determinations + // can be made on the basis of those rules that are found. + RulesFor(ctx context.Context, user user.Info, namespace string) ([]rbacv1.PolicyRule, error) + + // VisitRulesFor invokes visitor() with each rule that applies to a given user in a given namespace, and each error encountered resolving those rules. + // If visitor() returns false, visiting is short-circuited. + VisitRulesFor(ctx context.Context, user user.Info, namespace string, visitor func(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool) +} + +// ConfirmNoEscalation determines if the roles for a given user in a given namespace encompass the provided role. +func ConfirmNoEscalation(ctx context.Context, ruleResolver AuthorizationRuleResolver, rules []rbacv1.PolicyRule) error { + ruleResolutionErrors := []error{} + + user, ok := genericapirequest.UserFrom(ctx) + if !ok { + return fmt.Errorf("no user on context") + } + namespace, _ := genericapirequest.NamespaceFrom(ctx) + + ownerRules, err := ruleResolver.RulesFor(ctx, user, namespace) + if err != nil { + // As per AuthorizationRuleResolver contract, this may return a non fatal error with an incomplete list of policies. Log the error and continue. + klog.V(1).Infof("non-fatal error getting local rules for %v: %v", user, err) + ruleResolutionErrors = append(ruleResolutionErrors, err) + } + + ownerRightsCover, missingRights := validation.Covers(ownerRules, rules) + if !ownerRightsCover { + compactMissingRights := missingRights + if compact, err := CompactRules(missingRights); err == nil { + compactMissingRights = compact + } + + missingDescriptions := sets.NewString() + for _, missing := range compactMissingRights { + missingDescriptions.Insert(rbacv1helpers.CompactString(missing)) + } + + msg := fmt.Sprintf("user %q (groups=%q) is attempting to grant RBAC permissions not currently held:\n%s", user.GetName(), user.GetGroups(), strings.Join(missingDescriptions.List(), "\n")) + if len(ruleResolutionErrors) > 0 { + msg = msg + fmt.Sprintf("; resolution errors: %v", ruleResolutionErrors) + } + + return errors.New(msg) + } + return nil +} + +type DefaultRuleResolver struct { + roleGetter RoleGetter + roleBindingLister RoleBindingLister + clusterRoleGetter ClusterRoleGetter + clusterRoleBindingLister ClusterRoleBindingLister +} + +func NewDefaultRuleResolver(roleGetter RoleGetter, roleBindingLister RoleBindingLister, clusterRoleGetter ClusterRoleGetter, clusterRoleBindingLister ClusterRoleBindingLister) *DefaultRuleResolver { + return &DefaultRuleResolver{roleGetter, roleBindingLister, clusterRoleGetter, clusterRoleBindingLister} +} + +type RoleGetter interface { + GetRole(ctx context.Context, namespace, name string) (*rbacv1.Role, error) +} + +type RoleBindingLister interface { + ListRoleBindings(ctx context.Context, namespace string) ([]*rbacv1.RoleBinding, error) +} + +type ClusterRoleGetter interface { + GetClusterRole(ctx context.Context, name string) (*rbacv1.ClusterRole, error) +} + +type ClusterRoleBindingLister interface { + ListClusterRoleBindings(ctx context.Context) ([]*rbacv1.ClusterRoleBinding, error) +} + +func (r *DefaultRuleResolver) RulesFor(ctx context.Context, user user.Info, namespace string) ([]rbacv1.PolicyRule, error) { + visitor := &ruleAccumulator{} + r.VisitRulesFor(ctx, user, namespace, visitor.visit) + return visitor.rules, utilerrors.NewAggregate(visitor.errors) +} + +type ruleAccumulator struct { + rules []rbacv1.PolicyRule + errors []error +} + +func (r *ruleAccumulator) visit(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool { + if rule != nil { + r.rules = append(r.rules, *rule) + } + if err != nil { + r.errors = append(r.errors, err) + } + return true +} + +func describeSubject(s *rbacv1.Subject, bindingNamespace string) string { + switch s.Kind { + case rbacv1.ServiceAccountKind: + if len(s.Namespace) > 0 { + return fmt.Sprintf("%s %q", s.Kind, s.Name+"/"+s.Namespace) + } + return fmt.Sprintf("%s %q", s.Kind, s.Name+"/"+bindingNamespace) + default: + return fmt.Sprintf("%s %q", s.Kind, s.Name) + } +} + +type clusterRoleBindingDescriber struct { + binding *rbacv1.ClusterRoleBinding + subject *rbacv1.Subject +} + +func (d *clusterRoleBindingDescriber) String() string { + return fmt.Sprintf("ClusterRoleBinding %q of %s %q to %s", + d.binding.Name, + d.binding.RoleRef.Kind, + d.binding.RoleRef.Name, + describeSubject(d.subject, ""), + ) +} + +type roleBindingDescriber struct { + binding *rbacv1.RoleBinding + subject *rbacv1.Subject +} + +func (d *roleBindingDescriber) String() string { + return fmt.Sprintf("RoleBinding %q of %s %q to %s", + d.binding.Name+"/"+d.binding.Namespace, + d.binding.RoleRef.Kind, + d.binding.RoleRef.Name, + describeSubject(d.subject, d.binding.Namespace), + ) +} + +func (r *DefaultRuleResolver) VisitRulesFor(ctx context.Context, user user.Info, namespace string, visitor func(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool) { + if clusterRoleBindings, err := r.clusterRoleBindingLister.ListClusterRoleBindings(ctx); err != nil { + if !visitor(nil, nil, err) { + return + } + } else { + sourceDescriber := &clusterRoleBindingDescriber{} + for _, clusterRoleBinding := range clusterRoleBindings { + subjectIndex, applies := appliesTo(user, clusterRoleBinding.Subjects, "") + if !applies { + continue + } + rules, err := r.GetRoleReferenceRules(ctx, clusterRoleBinding.RoleRef, "") + if err != nil { + if !visitor(nil, nil, err) { + return + } + continue + } + sourceDescriber.binding = clusterRoleBinding + sourceDescriber.subject = &clusterRoleBinding.Subjects[subjectIndex] + for i := range rules { + if !visitor(sourceDescriber, &rules[i], nil) { + return + } + } + } + } + + if len(namespace) > 0 { + if roleBindings, err := r.roleBindingLister.ListRoleBindings(ctx, namespace); err != nil { + if !visitor(nil, nil, err) { + return + } + } else { + sourceDescriber := &roleBindingDescriber{} + for _, roleBinding := range roleBindings { + subjectIndex, applies := appliesTo(user, roleBinding.Subjects, namespace) + if !applies { + continue + } + rules, err := r.GetRoleReferenceRules(ctx, roleBinding.RoleRef, namespace) + if err != nil { + if !visitor(nil, nil, err) { + return + } + continue + } + sourceDescriber.binding = roleBinding + sourceDescriber.subject = &roleBinding.Subjects[subjectIndex] + for i := range rules { + if !visitor(sourceDescriber, &rules[i], nil) { + return + } + } + } + } + } +} + +// GetRoleReferenceRules attempts to resolve the RoleBinding or ClusterRoleBinding. +func (r *DefaultRuleResolver) GetRoleReferenceRules(ctx context.Context, roleRef rbacv1.RoleRef, bindingNamespace string) ([]rbacv1.PolicyRule, error) { + switch roleRef.Kind { + case "Role": + role, err := r.roleGetter.GetRole(ctx, bindingNamespace, roleRef.Name) + if err != nil { + return nil, err + } + return role.Rules, nil + + case "ClusterRole": + clusterRole, err := r.clusterRoleGetter.GetClusterRole(ctx, roleRef.Name) + if err != nil { + return nil, err + } + return clusterRole.Rules, nil + + default: + return nil, fmt.Errorf("unsupported role reference kind: %q", roleRef.Kind) + } +} + +// appliesTo returns whether any of the bindingSubjects applies to the specified subject, +// and if true, the index of the first subject that applies +func appliesTo(user user.Info, bindingSubjects []rbacv1.Subject, namespace string) (int, bool) { + for i, bindingSubject := range bindingSubjects { + if appliesToUser(user, bindingSubject, namespace) { + return i, true + } + } + return 0, false +} + +func has(set []string, ele string) bool { + for _, s := range set { + if s == ele { + return true + } + } + return false +} + +func appliesToUser(user user.Info, subject rbacv1.Subject, namespace string) bool { + switch subject.Kind { + case rbacv1.UserKind: + return user.GetName() == subject.Name + + case rbacv1.GroupKind: + return has(user.GetGroups(), subject.Name) + + case rbacv1.ServiceAccountKind: + // default the namespace to namespace we're working in if its available. This allows rolebindings that reference + // SAs in th local namespace to avoid having to qualify them. + saNamespace := namespace + if len(subject.Namespace) > 0 { + saNamespace = subject.Namespace + } + if len(saNamespace) == 0 { + return false + } + // use a more efficient comparison for RBAC checking + return serviceaccount.MatchesUsername(saNamespace, subject.Name, user.GetName()) + default: + return false + } +} + +// NewTestRuleResolver returns a rule resolver from lists of role objects. +func NewTestRuleResolver(roles []*rbacv1.Role, roleBindings []*rbacv1.RoleBinding, clusterRoles []*rbacv1.ClusterRole, clusterRoleBindings []*rbacv1.ClusterRoleBinding) (AuthorizationRuleResolver, *StaticRoles) { + r := StaticRoles{ + roles: roles, + roleBindings: roleBindings, + clusterRoles: clusterRoles, + clusterRoleBindings: clusterRoleBindings, + } + return newMockRuleResolver(&r), &r +} + +func newMockRuleResolver(r *StaticRoles) AuthorizationRuleResolver { + return NewDefaultRuleResolver(r, r, r, r) +} + +// StaticRoles is a rule resolver that resolves from lists of role objects. +type StaticRoles struct { + roles []*rbacv1.Role + roleBindings []*rbacv1.RoleBinding + clusterRoles []*rbacv1.ClusterRole + clusterRoleBindings []*rbacv1.ClusterRoleBinding +} + +func (r *StaticRoles) GetRole(ctx context.Context, namespace, name string) (*rbacv1.Role, error) { + if len(namespace) == 0 { + return nil, errors.New("must provide namespace when getting role") + } + for _, role := range r.roles { + if role.Namespace == namespace && role.Name == name { + return role, nil + } + } + return nil, errors.New("role not found") +} + +func (r *StaticRoles) GetClusterRole(ctx context.Context, name string) (*rbacv1.ClusterRole, error) { + for _, clusterRole := range r.clusterRoles { + if clusterRole.Name == name { + return clusterRole, nil + } + } + return nil, errors.New("clusterrole not found") +} + +func (r *StaticRoles) ListRoleBindings(ctx context.Context, namespace string) ([]*rbacv1.RoleBinding, error) { + if len(namespace) == 0 { + return nil, errors.New("must provide namespace when listing role bindings") + } + + roleBindingList := []*rbacv1.RoleBinding{} + for _, roleBinding := range r.roleBindings { + if roleBinding.Namespace != namespace { + continue + } + // TODO(ericchiang): need to implement label selectors? + roleBindingList = append(roleBindingList, roleBinding) + } + return roleBindingList, nil +} + +func (r *StaticRoles) ListClusterRoleBindings(ctx context.Context) ([]*rbacv1.ClusterRoleBinding, error) { + return r.clusterRoleBindings, nil +} diff --git a/internal/operator-controller/authorization/internal/kubernetes/plugin/pkg/auth/authorizer/rbac/rbac.go b/internal/operator-controller/authorization/internal/kubernetes/plugin/pkg/auth/authorizer/rbac/rbac.go new file mode 100644 index 000000000..d350848d5 --- /dev/null +++ b/internal/operator-controller/authorization/internal/kubernetes/plugin/pkg/auth/authorizer/rbac/rbac.go @@ -0,0 +1,225 @@ +/* +Copyright 2016 The Kubernetes Authors. + +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. +*/ + +// Package rbac implements the authorizer.Authorizer interface using roles base access control. +package rbac + +import ( + "bytes" + "context" + "fmt" + + "k8s.io/klog/v2" + + rbacv1helpers "github.com/operator-framework/operator-controller/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1" + rbacregistryvalidation "github.com/operator-framework/operator-controller/internal/operator-controller/authorization/internal/kubernetes/pkg/registry/rbac/validation" + rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/labels" + utilerrors "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/apiserver/pkg/authentication/user" + "k8s.io/apiserver/pkg/authorization/authorizer" + rbaclisters "k8s.io/client-go/listers/rbac/v1" +) + +type RequestToRuleMapper interface { + // RulesFor returns all known PolicyRules and any errors that happened while locating those rules. + // Any rule returned is still valid, since rules are deny by default. If you can pass with the rules + // supplied, you do not have to fail the request. If you cannot, you should indicate the error along + // with your denial. + RulesFor(ctx context.Context, subject user.Info, namespace string) ([]rbacv1.PolicyRule, error) + + // VisitRulesFor invokes visitor() with each rule that applies to a given user in a given namespace, + // and each error encountered resolving those rules. Rule may be nil if err is non-nil. + // If visitor() returns false, visiting is short-circuited. + VisitRulesFor(ctx context.Context, user user.Info, namespace string, visitor func(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool) +} + +type RBACAuthorizer struct { + authorizationRuleResolver RequestToRuleMapper +} + +// authorizingVisitor short-circuits once allowed, and collects any resolution errors encountered +type authorizingVisitor struct { + requestAttributes authorizer.Attributes + + allowed bool + reason string + errors []error +} + +func (v *authorizingVisitor) visit(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool { + if rule != nil && RuleAllows(v.requestAttributes, rule) { + v.allowed = true + v.reason = fmt.Sprintf("RBAC: allowed by %s", source.String()) + return false + } + if err != nil { + v.errors = append(v.errors, err) + } + return true +} + +func (r *RBACAuthorizer) Authorize(ctx context.Context, requestAttributes authorizer.Attributes) (authorizer.Decision, string, error) { + ruleCheckingVisitor := &authorizingVisitor{requestAttributes: requestAttributes} + + r.authorizationRuleResolver.VisitRulesFor(ctx, requestAttributes.GetUser(), requestAttributes.GetNamespace(), ruleCheckingVisitor.visit) + if ruleCheckingVisitor.allowed { + return authorizer.DecisionAllow, ruleCheckingVisitor.reason, nil + } + + // Build a detailed log of the denial. + // Make the whole block conditional so we don't do a lot of string-building we won't use. + if klogV := klog.V(5); klogV.Enabled() { + var operation string + if requestAttributes.IsResourceRequest() { + b := &bytes.Buffer{} + b.WriteString(`"`) + b.WriteString(requestAttributes.GetVerb()) + b.WriteString(`" resource "`) + b.WriteString(requestAttributes.GetResource()) + if len(requestAttributes.GetAPIGroup()) > 0 { + b.WriteString(`.`) + b.WriteString(requestAttributes.GetAPIGroup()) + } + if len(requestAttributes.GetSubresource()) > 0 { + b.WriteString(`/`) + b.WriteString(requestAttributes.GetSubresource()) + } + b.WriteString(`"`) + if len(requestAttributes.GetName()) > 0 { + b.WriteString(` named "`) + b.WriteString(requestAttributes.GetName()) + b.WriteString(`"`) + } + operation = b.String() + } else { + operation = fmt.Sprintf("%q nonResourceURL %q", requestAttributes.GetVerb(), requestAttributes.GetPath()) + } + + var scope string + if ns := requestAttributes.GetNamespace(); len(ns) > 0 { + scope = fmt.Sprintf("in namespace %q", ns) + } else { + scope = "cluster-wide" + } + + klogV.Infof("RBAC: no rules authorize user %q with groups %q to %s %s", requestAttributes.GetUser().GetName(), requestAttributes.GetUser().GetGroups(), operation, scope) + } + + reason := "" + if len(ruleCheckingVisitor.errors) > 0 { + reason = fmt.Sprintf("RBAC: %v", utilerrors.NewAggregate(ruleCheckingVisitor.errors)) + } + return authorizer.DecisionNoOpinion, reason, nil +} + +func (r *RBACAuthorizer) RulesFor(ctx context.Context, user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) { + var ( + resourceRules []authorizer.ResourceRuleInfo + nonResourceRules []authorizer.NonResourceRuleInfo + ) + + policyRules, err := r.authorizationRuleResolver.RulesFor(ctx, user, namespace) + for _, policyRule := range policyRules { + if len(policyRule.Resources) > 0 { + r := authorizer.DefaultResourceRuleInfo{ + Verbs: policyRule.Verbs, + APIGroups: policyRule.APIGroups, + Resources: policyRule.Resources, + ResourceNames: policyRule.ResourceNames, + } + var resourceRule authorizer.ResourceRuleInfo = &r + resourceRules = append(resourceRules, resourceRule) + } + if len(policyRule.NonResourceURLs) > 0 { + r := authorizer.DefaultNonResourceRuleInfo{ + Verbs: policyRule.Verbs, + NonResourceURLs: policyRule.NonResourceURLs, + } + var nonResourceRule authorizer.NonResourceRuleInfo = &r + nonResourceRules = append(nonResourceRules, nonResourceRule) + } + } + return resourceRules, nonResourceRules, false, err +} + +func New(roles rbacregistryvalidation.RoleGetter, roleBindings rbacregistryvalidation.RoleBindingLister, clusterRoles rbacregistryvalidation.ClusterRoleGetter, clusterRoleBindings rbacregistryvalidation.ClusterRoleBindingLister) *RBACAuthorizer { + authorizer := &RBACAuthorizer{ + authorizationRuleResolver: rbacregistryvalidation.NewDefaultRuleResolver( + roles, roleBindings, clusterRoles, clusterRoleBindings, + ), + } + return authorizer +} + +func RulesAllow(requestAttributes authorizer.Attributes, rules ...rbacv1.PolicyRule) bool { + for i := range rules { + if RuleAllows(requestAttributes, &rules[i]) { + return true + } + } + + return false +} + +func RuleAllows(requestAttributes authorizer.Attributes, rule *rbacv1.PolicyRule) bool { + if requestAttributes.IsResourceRequest() { + combinedResource := requestAttributes.GetResource() + if len(requestAttributes.GetSubresource()) > 0 { + combinedResource = requestAttributes.GetResource() + "/" + requestAttributes.GetSubresource() + } + + return rbacv1helpers.VerbMatches(rule, requestAttributes.GetVerb()) && + rbacv1helpers.APIGroupMatches(rule, requestAttributes.GetAPIGroup()) && + rbacv1helpers.ResourceMatches(rule, combinedResource, requestAttributes.GetSubresource()) && + rbacv1helpers.ResourceNameMatches(rule, requestAttributes.GetName()) + } + + return rbacv1helpers.VerbMatches(rule, requestAttributes.GetVerb()) && + rbacv1helpers.NonResourceURLMatches(rule, requestAttributes.GetPath()) +} + +type RoleGetter struct { + Lister rbaclisters.RoleLister +} + +func (g *RoleGetter) GetRole(ctx context.Context, namespace, name string) (*rbacv1.Role, error) { + return g.Lister.Roles(namespace).Get(name) +} + +type RoleBindingLister struct { + Lister rbaclisters.RoleBindingLister +} + +func (l *RoleBindingLister) ListRoleBindings(ctx context.Context, namespace string) ([]*rbacv1.RoleBinding, error) { + return l.Lister.RoleBindings(namespace).List(labels.Everything()) +} + +type ClusterRoleGetter struct { + Lister rbaclisters.ClusterRoleLister +} + +func (g *ClusterRoleGetter) GetClusterRole(ctx context.Context, name string) (*rbacv1.ClusterRole, error) { + return g.Lister.Get(name) +} + +type ClusterRoleBindingLister struct { + Lister rbaclisters.ClusterRoleBindingLister +} + +func (l *ClusterRoleBindingLister) ListClusterRoleBindings(ctx context.Context) ([]*rbacv1.ClusterRoleBinding, error) { + return l.Lister.List(labels.Everything()) +} From b49c1717842ef762e0399865f0cee76d7ed67171 Mon Sep 17 00:00:00 2001 From: Joe Lanford Date: Tue, 25 Feb 2025 08:06:48 -0500 Subject: [PATCH 02/67] permissions preflight: kubernetes rbac code modifications Signed-off-by: Joe Lanford --- .../rbac/validation/policy_compact.go | 7 ++++ .../pkg/registry/rbac/validation/rule.go | 42 +++++++++++++------ 2 files changed, 36 insertions(+), 13 deletions(-) diff --git a/internal/operator-controller/authorization/internal/kubernetes/pkg/registry/rbac/validation/policy_compact.go b/internal/operator-controller/authorization/internal/kubernetes/pkg/registry/rbac/validation/policy_compact.go index 182657b1c..60adcf9e8 100644 --- a/internal/operator-controller/authorization/internal/kubernetes/pkg/registry/rbac/validation/policy_compact.go +++ b/internal/operator-controller/authorization/internal/kubernetes/pkg/registry/rbac/validation/policy_compact.go @@ -20,6 +20,7 @@ import ( "reflect" rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/util/sets" ) type simpleResource struct { @@ -54,6 +55,12 @@ func CompactRules(rules []rbacv1.PolicyRule) ([]rbacv1.PolicyRule, error) { // Once we've consolidated the simple resource rules, add them to the compacted list for _, simpleRule := range simpleRules { + verbSet := sets.New[string](simpleRule.Verbs...) + if verbSet.Has("*") { + simpleRule.Verbs = []string{"*"} + } else { + simpleRule.Verbs = sets.List(verbSet) + } compacted = append(compacted, *simpleRule) } diff --git a/internal/operator-controller/authorization/internal/kubernetes/pkg/registry/rbac/validation/rule.go b/internal/operator-controller/authorization/internal/kubernetes/pkg/registry/rbac/validation/rule.go index 8847adc5d..ad79998fd 100644 --- a/internal/operator-controller/authorization/internal/kubernetes/pkg/registry/rbac/validation/rule.go +++ b/internal/operator-controller/authorization/internal/kubernetes/pkg/registry/rbac/validation/rule.go @@ -22,9 +22,6 @@ import ( "fmt" "strings" - "k8s.io/klog/v2" - - rbacv1helpers "github.com/operator-framework/operator-controller/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1" rbacv1 "k8s.io/api/rbac/v1" utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apimachinery/pkg/util/sets" @@ -32,6 +29,9 @@ import ( "k8s.io/apiserver/pkg/authentication/user" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/component-helpers/auth/rbac/validation" + "k8s.io/klog/v2" + + rbacv1helpers "github.com/operator-framework/operator-controller/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1" ) type AuthorizationRuleResolver interface { @@ -49,6 +49,27 @@ type AuthorizationRuleResolver interface { VisitRulesFor(ctx context.Context, user user.Info, namespace string, visitor func(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool) } +type PrivilegeEscalationError struct { + User user.Info + Namespace string + MissingRules []rbacv1.PolicyRule + RuleResolutionErrors []error +} + +func (e *PrivilegeEscalationError) Error() string { + missingDescriptions := sets.NewString() + for _, missing := range e.MissingRules { + missingDescriptions.Insert(rbacv1helpers.CompactString(missing)) + } + + msg := fmt.Sprintf("user %q (groups=%q) is attempting to grant RBAC permissions not currently held:\n%s", e.User.GetName(), e.User.GetGroups(), strings.Join(missingDescriptions.List(), "\n")) + if len(e.RuleResolutionErrors) > 0 { + msg = msg + fmt.Sprintf("; resolution errors: %v", e.RuleResolutionErrors) + } + + return msg +} + // ConfirmNoEscalation determines if the roles for a given user in a given namespace encompass the provided role. func ConfirmNoEscalation(ctx context.Context, ruleResolver AuthorizationRuleResolver, rules []rbacv1.PolicyRule) error { ruleResolutionErrors := []error{} @@ -73,17 +94,12 @@ func ConfirmNoEscalation(ctx context.Context, ruleResolver AuthorizationRuleReso compactMissingRights = compact } - missingDescriptions := sets.NewString() - for _, missing := range compactMissingRights { - missingDescriptions.Insert(rbacv1helpers.CompactString(missing)) - } - - msg := fmt.Sprintf("user %q (groups=%q) is attempting to grant RBAC permissions not currently held:\n%s", user.GetName(), user.GetGroups(), strings.Join(missingDescriptions.List(), "\n")) - if len(ruleResolutionErrors) > 0 { - msg = msg + fmt.Sprintf("; resolution errors: %v", ruleResolutionErrors) + return &PrivilegeEscalationError{ + User: user, + Namespace: namespace, + MissingRules: compactMissingRights, + RuleResolutionErrors: ruleResolutionErrors, } - - return errors.New(msg) } return nil } From 2ddc0d1dedcdd1ef7fda7873e7337d938cf82d83 Mon Sep 17 00:00:00 2001 From: Joe Lanford Date: Tue, 25 Feb 2025 08:09:52 -0500 Subject: [PATCH 03/67] permissions preflight: add preauth implementation Signed-off-by: Joe Lanford --- .../operator-controller/authorization/rbac.go | 469 ++++++++++++++++++ 1 file changed, 469 insertions(+) create mode 100644 internal/operator-controller/authorization/rbac.go diff --git a/internal/operator-controller/authorization/rbac.go b/internal/operator-controller/authorization/rbac.go new file mode 100644 index 000000000..7ec0bb0a6 --- /dev/null +++ b/internal/operator-controller/authorization/rbac.go @@ -0,0 +1,469 @@ +package authorization + +import ( + "context" + "errors" + "fmt" + "io" + "maps" + "sort" + + rbacv1 "k8s.io/api/rbac/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" + apimachyaml "k8s.io/apimachinery/pkg/util/yaml" + "k8s.io/apiserver/pkg/authentication/user" + "k8s.io/apiserver/pkg/authorization/authorizer" + "k8s.io/apiserver/pkg/endpoints/request" + "sigs.k8s.io/controller-runtime/pkg/client" + + rbacinternal "github.com/operator-framework/operator-controller/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac" + rbacv1helpers "github.com/operator-framework/operator-controller/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1" + rbacregistry "github.com/operator-framework/operator-controller/internal/operator-controller/authorization/internal/kubernetes/pkg/registry/rbac" + "github.com/operator-framework/operator-controller/internal/operator-controller/authorization/internal/kubernetes/pkg/registry/rbac/validation" + "github.com/operator-framework/operator-controller/internal/operator-controller/authorization/internal/kubernetes/plugin/pkg/auth/authorizer/rbac" +) + +type PreAuthorizer interface { + PreAuthorize(ctx context.Context, manifestManager user.Info, manifestReader io.Reader) (map[string][]rbacv1.PolicyRule, error) +} + +var ( + collectionVerbs = []string{"list", "watch", "create"} + objectVerbs = []string{"get", "patch", "update", "delete"} +) + +type rbacPreAuthorizer struct { + authorizer authorizer.Authorizer + ruleResolver validation.AuthorizationRuleResolver + restMapper meta.RESTMapper +} + +func NewRBACPreAuthorizer(cl client.Client) PreAuthorizer { + return &rbacPreAuthorizer{ + authorizer: newRBACAuthorizer(cl), + ruleResolver: newRBACRulesResolver(cl), + restMapper: cl.RESTMapper(), + } +} + +func (a *rbacPreAuthorizer) PreAuthorize(ctx context.Context, manifestManager user.Info, manifestReader io.Reader) (map[string][]rbacv1.PolicyRule, error) { + dm, err := a.decodeManifest(manifestReader) + if err != nil { + return nil, err + } + attributesRecords := dm.asAuthorizationAttributesRecordsForUser(manifestManager) + + var preAuthEvaluationErrors []error + missingRules, err := a.authorizeAttributesRecords(ctx, attributesRecords) + if err != nil { + preAuthEvaluationErrors = append(preAuthEvaluationErrors, err) + } + + ec := escalationChecker{ + authorizer: a.authorizer, + ruleResolver: a.ruleResolver, + extraClusterRoles: dm.clusterRoles, + extraRoles: dm.roles, + } + + for _, obj := range dm.rbacObjects() { + if err := ec.checkEscalation(ctx, manifestManager, obj); err != nil { + var peErr *validation.PrivilegeEscalationError + if errors.As(err, &peErr) { + missingRules[peErr.Namespace] = append(missingRules[peErr.Namespace], peErr.MissingRules...) + preAuthEvaluationErrors = append(preAuthEvaluationErrors, peErr.RuleResolutionErrors...) + } else { + preAuthEvaluationErrors = append(preAuthEvaluationErrors, err) + } + } + } + + for ns, nsMissingRules := range missingRules { + if compactMissingRules, err := validation.CompactRules(nsMissingRules); err == nil { + missingRules[ns] = compactMissingRules + } + sortableRules := rbacv1helpers.SortableRuleSlice(missingRules[ns]) + sort.Sort(sortableRules) + } + + if len(preAuthEvaluationErrors) > 0 { + return missingRules, fmt.Errorf("authorization evaluation errors: %w", errors.Join(preAuthEvaluationErrors...)) + } + return missingRules, nil +} + +func (a *rbacPreAuthorizer) decodeManifest(manifestReader io.Reader) (*decodedManifest, error) { + dm := &decodedManifest{ + gvrs: map[schema.GroupVersionResource][]types.NamespacedName{}, + clusterRoles: map[client.ObjectKey]rbacv1.ClusterRole{}, + roles: map[client.ObjectKey]rbacv1.Role{}, + clusterRoleBindings: map[client.ObjectKey]rbacv1.ClusterRoleBinding{}, + roleBindings: map[client.ObjectKey]rbacv1.RoleBinding{}, + } + var ( + i int + errs []error + decoder = apimachyaml.NewYAMLOrJSONDecoder(manifestReader, 1024) + ) + for { + var uObj unstructured.Unstructured + err := decoder.Decode(&uObj) + if errors.Is(err, io.EOF) { + break + } + if err != nil { + errs = append(errs, fmt.Errorf("could not decode object %d in manifest: %w", i, err)) + continue + } + gvk := uObj.GroupVersionKind() + restMapping, err := a.restMapper.RESTMapping(gvk.GroupKind(), gvk.Version) + if err != nil { + errs = append(errs, fmt.Errorf("could not get REST mapping for object %d in manifest with GVK %s: %w", i, gvk, err)) + continue + } + + gvr := restMapping.Resource + dm.gvrs[gvr] = append(dm.gvrs[gvr], client.ObjectKeyFromObject(&uObj)) + + switch restMapping.Resource.GroupResource() { + case schema.GroupResource{Group: rbacv1.GroupName, Resource: "clusterroles"}: + obj := &rbacv1.ClusterRole{} + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(uObj.UnstructuredContent(), obj); err != nil { + errs = append(errs, fmt.Errorf("could not decode object %d in manifest as ClusterRole: %w", i, err)) + continue + } + dm.clusterRoles[client.ObjectKeyFromObject(obj)] = *obj + case schema.GroupResource{Group: rbacv1.GroupName, Resource: "clusterrolebindings"}: + obj := &rbacv1.ClusterRoleBinding{} + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(uObj.UnstructuredContent(), obj); err != nil { + errs = append(errs, fmt.Errorf("could not decode object %d in manifest as ClusterRoleBinding: %w", i, err)) + continue + } + dm.clusterRoleBindings[client.ObjectKeyFromObject(obj)] = *obj + case schema.GroupResource{Group: rbacv1.GroupName, Resource: "roles"}: + obj := &rbacv1.Role{} + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(uObj.UnstructuredContent(), obj); err != nil { + errs = append(errs, fmt.Errorf("could not decode object %d in manifest as Role: %w", i, err)) + continue + } + dm.roles[client.ObjectKeyFromObject(obj)] = *obj + case schema.GroupResource{Group: rbacv1.GroupName, Resource: "rolebindings"}: + obj := &rbacv1.RoleBinding{} + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(uObj.UnstructuredContent(), obj); err != nil { + errs = append(errs, fmt.Errorf("could not decode object %d in manifest as RoleBinding: %w", i, err)) + continue + } + dm.roleBindings[client.ObjectKeyFromObject(obj)] = *obj + } + } + if len(errs) > 0 { + return nil, errors.Join(errs...) + } + return dm, nil +} + +func (a *rbacPreAuthorizer) authorizeAttributesRecords(ctx context.Context, attributesRecords []authorizer.AttributesRecord) (map[string][]rbacv1.PolicyRule, error) { + var ( + missingRules = map[string][]rbacv1.PolicyRule{} + errs []error + ) + for _, ar := range attributesRecords { + allow, err := a.attributesAllowed(ctx, ar) + if err != nil { + errs = append(errs, err) + continue + } + if !allow { + missingRules[ar.Namespace] = append(missingRules[ar.Namespace], policyRuleFromAttributesRecord(ar)) + } + } + return missingRules, errors.Join(errs...) +} + +func (a *rbacPreAuthorizer) attributesAllowed(ctx context.Context, attributesRecord authorizer.AttributesRecord) (bool, error) { + decision, _, err := a.authorizer.Authorize(ctx, attributesRecord) + if err != nil { + return false, err + } + return decision == authorizer.DecisionAllow, nil +} + +func policyRuleFromAttributesRecord(attributesRecord authorizer.AttributesRecord) rbacv1.PolicyRule { + pr := rbacv1.PolicyRule{} + if attributesRecord.Verb != "" { + pr.Verbs = []string{attributesRecord.Verb} + } + if !attributesRecord.ResourceRequest { + pr.NonResourceURLs = []string{attributesRecord.Path} + return pr + } + + pr.APIGroups = []string{attributesRecord.APIGroup} + if attributesRecord.Name != "" { + pr.ResourceNames = []string{attributesRecord.Name} + } + + r := attributesRecord.Resource + if attributesRecord.Subresource != "" { + r += "/" + attributesRecord.Subresource + } + pr.Resources = []string{r} + + return pr +} + +type decodedManifest struct { + gvrs map[schema.GroupVersionResource][]types.NamespacedName + clusterRoles map[client.ObjectKey]rbacv1.ClusterRole + roles map[client.ObjectKey]rbacv1.Role + clusterRoleBindings map[client.ObjectKey]rbacv1.ClusterRoleBinding + roleBindings map[client.ObjectKey]rbacv1.RoleBinding +} + +func (dm *decodedManifest) rbacObjects() []client.Object { + objects := make([]client.Object, 0, len(dm.clusterRoles)+len(dm.roles)+len(dm.clusterRoleBindings)+len(dm.roleBindings)) + for obj := range maps.Values(dm.clusterRoles) { + objects = append(objects, &obj) + } + for obj := range maps.Values(dm.roles) { + objects = append(objects, &obj) + } + for obj := range maps.Values(dm.clusterRoleBindings) { + objects = append(objects, &obj) + } + for obj := range maps.Values(dm.roleBindings) { + objects = append(objects, &obj) + } + return objects +} + +func (dm *decodedManifest) asAuthorizationAttributesRecordsForUser(manifestManager user.Info) []authorizer.AttributesRecord { + var attributeRecords []authorizer.AttributesRecord + for gvr, keys := range dm.gvrs { + namespaces := sets.New[string]() + for _, k := range keys { + namespaces.Insert(k.Namespace) + for _, v := range objectVerbs { + attributeRecords = append(attributeRecords, authorizer.AttributesRecord{ + User: manifestManager, + Namespace: k.Namespace, + Name: k.Name, + APIGroup: gvr.Group, + APIVersion: gvr.Version, + Resource: gvr.Resource, + ResourceRequest: true, + Verb: v, + }) + } + } + for _, ns := range sets.List(namespaces) { + for _, v := range collectionVerbs { + attributeRecords = append(attributeRecords, authorizer.AttributesRecord{ + User: manifestManager, + Namespace: ns, + APIGroup: gvr.Group, + APIVersion: gvr.Version, + Resource: gvr.Resource, + ResourceRequest: true, + Verb: v, + }) + } + } + } + return attributeRecords +} + +func newRBACAuthorizer(cl client.Client) authorizer.Authorizer { + rg := &rbacGetter{cl: cl} + return rbac.New(rg, rg, rg, rg) +} + +type rbacGetter struct { + cl client.Client +} + +func (r rbacGetter) ListClusterRoleBindings(ctx context.Context) ([]*rbacv1.ClusterRoleBinding, error) { + var clusterRoleBindingsList rbacv1.ClusterRoleBindingList + if err := r.cl.List(ctx, &clusterRoleBindingsList); err != nil { + return nil, err + } + return toPtrSlice(clusterRoleBindingsList.Items), nil +} + +func (r rbacGetter) GetClusterRole(ctx context.Context, name string) (*rbacv1.ClusterRole, error) { + var clusterRole rbacv1.ClusterRole + if err := r.cl.Get(ctx, client.ObjectKey{Name: name}, &clusterRole); err != nil { + return nil, err + } + return &clusterRole, nil +} + +func (r rbacGetter) ListRoleBindings(ctx context.Context, namespace string) ([]*rbacv1.RoleBinding, error) { + var roleBindingsList rbacv1.RoleBindingList + if err := r.cl.List(ctx, &roleBindingsList, client.InNamespace(namespace)); err != nil { + return nil, err + } + return toPtrSlice(roleBindingsList.Items), nil +} + +func (r rbacGetter) GetRole(ctx context.Context, namespace, name string) (*rbacv1.Role, error) { + var role rbacv1.Role + if err := r.cl.Get(ctx, client.ObjectKey{Name: name, Namespace: namespace}, &role); err != nil { + return nil, err + } + return &role, nil +} + +func newRBACRulesResolver(cl client.Client) validation.AuthorizationRuleResolver { + rg := &rbacGetter{cl: cl} + return validation.NewDefaultRuleResolver(rg, rg, rg, rg) +} + +type escalationChecker struct { + authorizer authorizer.Authorizer + ruleResolver validation.AuthorizationRuleResolver + extraRoles map[types.NamespacedName]rbacv1.Role + extraClusterRoles map[types.NamespacedName]rbacv1.ClusterRole +} + +func (ec *escalationChecker) checkEscalation(ctx context.Context, manifestManager user.Info, obj client.Object) error { + ctx = request.WithUser(request.WithNamespace(ctx, obj.GetNamespace()), manifestManager) + switch v := obj.(type) { + case *rbacv1.Role: + ctx = request.WithRequestInfo(ctx, &request.RequestInfo{APIGroup: rbacv1.GroupName, Resource: "roles", IsResourceRequest: true}) + return ec.checkRoleEscalation(ctx, v) + case *rbacv1.RoleBinding: + ctx = request.WithRequestInfo(ctx, &request.RequestInfo{APIGroup: rbacv1.GroupName, Resource: "rolebindings", IsResourceRequest: true}) + return ec.checkRoleBindingEscalation(ctx, v) + case *rbacv1.ClusterRole: + ctx = request.WithRequestInfo(ctx, &request.RequestInfo{APIGroup: rbacv1.GroupName, Resource: "clusterroles", IsResourceRequest: true}) + return ec.checkClusterRoleEscalation(ctx, v) + case *rbacv1.ClusterRoleBinding: + ctx = request.WithRequestInfo(ctx, &request.RequestInfo{APIGroup: rbacv1.GroupName, Resource: "clusterrolebindings", IsResourceRequest: true}) + return ec.checkClusterRoleBindingEscalation(ctx, v) + default: + return fmt.Errorf("unknown object type %T", v) + } +} + +func (ec *escalationChecker) checkClusterRoleEscalation(ctx context.Context, clusterRole *rbacv1.ClusterRole) error { + if rbacregistry.EscalationAllowed(ctx) || rbacregistry.RoleEscalationAuthorized(ctx, ec.authorizer) { + return nil + } + + // to set the aggregation rule, since it can gather anything, requires * on *.* + if hasAggregationRule(clusterRole) { + if err := validation.ConfirmNoEscalation(ctx, ec.ruleResolver, fullAuthority); err != nil { + return fmt.Errorf("must have cluster-admin privileges to use an aggregationRule: %w", err) + } + } + + if err := validation.ConfirmNoEscalation(ctx, ec.ruleResolver, clusterRole.Rules); err != nil { + return err + } + return nil +} + +func (ec *escalationChecker) checkClusterRoleBindingEscalation(ctx context.Context, clusterRoleBinding *rbacv1.ClusterRoleBinding) error { + if rbacregistry.EscalationAllowed(ctx) { + return nil + } + + roleRef := rbacinternal.RoleRef{} + err := rbacv1helpers.Convert_v1_RoleRef_To_rbac_RoleRef(&clusterRoleBinding.RoleRef, &roleRef, nil) + if err != nil { + return err + } + + if rbacregistry.BindingAuthorized(ctx, roleRef, metav1.NamespaceNone, ec.authorizer) { + return nil + } + + rules, err := ec.ruleResolver.GetRoleReferenceRules(ctx, clusterRoleBinding.RoleRef, metav1.NamespaceNone) + if err != nil && !apierrors.IsNotFound(err) { + return err + } + + if clusterRoleBinding.RoleRef.Kind == "ClusterRole" { + if manifestClusterRole, ok := ec.extraClusterRoles[types.NamespacedName{Name: clusterRoleBinding.RoleRef.Name}]; ok { + rules = append(rules, manifestClusterRole.Rules...) + } + } + + if err := validation.ConfirmNoEscalation(ctx, ec.ruleResolver, rules); err != nil { + return err + } + return nil +} + +func (ec *escalationChecker) checkRoleEscalation(ctx context.Context, role *rbacv1.Role) error { + if rbacregistry.EscalationAllowed(ctx) || rbacregistry.RoleEscalationAuthorized(ctx, ec.authorizer) { + return nil + } + + rules := role.Rules + if err := validation.ConfirmNoEscalation(ctx, ec.ruleResolver, rules); err != nil { + return err + } + return nil +} + +func (ec *escalationChecker) checkRoleBindingEscalation(ctx context.Context, roleBinding *rbacv1.RoleBinding) error { + if rbacregistry.EscalationAllowed(ctx) { + return nil + } + + roleRef := rbacinternal.RoleRef{} + err := rbacv1helpers.Convert_v1_RoleRef_To_rbac_RoleRef(&roleBinding.RoleRef, &roleRef, nil) + if err != nil { + return err + } + if rbacregistry.BindingAuthorized(ctx, roleRef, roleBinding.Namespace, ec.authorizer) { + return nil + } + + rules, err := ec.ruleResolver.GetRoleReferenceRules(ctx, roleBinding.RoleRef, roleBinding.Namespace) + if err != nil && !apierrors.IsNotFound(err) { + return err + } + + switch roleRef.Kind { + case "ClusterRole": + if manifestClusterRole, ok := ec.extraClusterRoles[types.NamespacedName{Name: roleBinding.RoleRef.Name}]; ok { + rules = append(rules, manifestClusterRole.Rules...) + } + case "Role": + if manifestRole, ok := ec.extraRoles[types.NamespacedName{Namespace: roleBinding.Namespace, Name: roleBinding.RoleRef.Name}]; ok { + rules = append(rules, manifestRole.Rules...) + } + } + + if err := validation.ConfirmNoEscalation(ctx, ec.ruleResolver, rules); err != nil { + return err + } + return nil +} + +var fullAuthority = []rbacv1.PolicyRule{ + {Verbs: []string{"*"}, APIGroups: []string{"*"}, Resources: []string{"*"}}, + {Verbs: []string{"*"}, NonResourceURLs: []string{"*"}}, +} + +func hasAggregationRule(clusterRole *rbacv1.ClusterRole) bool { + return clusterRole.AggregationRule != nil && len(clusterRole.AggregationRule.ClusterRoleSelectors) > 0 +} + +func toPtrSlice[V any](in []V) []*V { + out := make([]*V, len(in)) + for i := range in { + out[i] = &in[i] + } + return out +} From 5ca669f759130be102dd07db29fc461aec8e7192 Mon Sep 17 00:00:00 2001 From: Joe Lanford Date: Tue, 25 Feb 2025 08:46:53 -0500 Subject: [PATCH 04/67] permissions preflight: enable implementation behind feature gate Signed-off-by: Joe Lanford --- cmd/operator-controller/main.go | 11 ++- .../base/operator-controller/rbac/role.yaml | 10 ++ internal/operator-controller/applier/helm.go | 97 +++++++++++++++++-- .../clusterextension_controller.go | 1 + 4 files changed, 108 insertions(+), 11 deletions(-) diff --git a/cmd/operator-controller/main.go b/cmd/operator-controller/main.go index 80ab0e4e6..0176651a6 100644 --- a/cmd/operator-controller/main.go +++ b/cmd/operator-controller/main.go @@ -32,6 +32,7 @@ import ( "github.com/sirupsen/logrus" "github.com/spf13/cobra" corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" apiextensionsv1client "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1" "k8s.io/apimachinery/pkg/fields" k8slabels "k8s.io/apimachinery/pkg/labels" @@ -58,6 +59,7 @@ import ( "github.com/operator-framework/operator-controller/internal/operator-controller/action" "github.com/operator-framework/operator-controller/internal/operator-controller/applier" "github.com/operator-framework/operator-controller/internal/operator-controller/authentication" + "github.com/operator-framework/operator-controller/internal/operator-controller/authorization" "github.com/operator-framework/operator-controller/internal/operator-controller/catalogmetadata/cache" catalogclient "github.com/operator-framework/operator-controller/internal/operator-controller/catalogmetadata/client" "github.com/operator-framework/operator-controller/internal/operator-controller/contentmanager" @@ -204,8 +206,12 @@ func run() error { setupLog.Info("set up manager") cacheOptions := crcache.Options{ ByObject: map[client.Object]crcache.ByObject{ - &ocv1.ClusterExtension{}: {Label: k8slabels.Everything()}, - &ocv1.ClusterCatalog{}: {Label: k8slabels.Everything()}, + &ocv1.ClusterExtension{}: {Label: k8slabels.Everything()}, + &ocv1.ClusterCatalog{}: {Label: k8slabels.Everything()}, + &rbacv1.ClusterRole{}: {Label: k8slabels.Everything()}, + &rbacv1.ClusterRoleBinding{}: {Label: k8slabels.Everything()}, + &rbacv1.Role{}: {Namespaces: map[string]crcache.Config{}, Label: k8slabels.Everything()}, + &rbacv1.RoleBinding{}: {Namespaces: map[string]crcache.Config{}, Label: k8slabels.Everything()}, }, DefaultNamespaces: map[string]crcache.Config{ cfg.systemNamespace: {LabelSelector: k8slabels.Everything()}, @@ -414,6 +420,7 @@ func run() error { ActionClientGetter: acg, Preflights: preflights, BundleToHelmChartFn: convert.RegistryV1ToHelmChart, + PreAuthorizer: authorization.NewRBACPreAuthorizer(mgr.GetClient()), } cm := contentmanager.NewManager(clientRestConfigMapper, mgr.GetConfig(), mgr.GetRESTMapper()) diff --git a/config/base/operator-controller/rbac/role.yaml b/config/base/operator-controller/rbac/role.yaml index a929e78e9..be89deec1 100644 --- a/config/base/operator-controller/rbac/role.yaml +++ b/config/base/operator-controller/rbac/role.yaml @@ -47,6 +47,16 @@ rules: verbs: - patch - update +- apiGroups: + - rbac.authorization.k8s.io + resources: + - clusterrolebindings + - clusterroles + - rolebindings + - roles + verbs: + - list + - watch --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role diff --git a/internal/operator-controller/applier/helm.go b/internal/operator-controller/applier/helm.go index 60a03477a..0de66e038 100644 --- a/internal/operator-controller/applier/helm.go +++ b/internal/operator-controller/applier/helm.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "io/fs" + "slices" "strings" "helm.sh/helm/v3/pkg/action" @@ -15,14 +16,17 @@ import ( "helm.sh/helm/v3/pkg/postrender" "helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/storage/driver" + rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" apimachyaml "k8s.io/apimachinery/pkg/util/yaml" + "k8s.io/apiserver/pkg/authentication/user" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" helmclient "github.com/operator-framework/helm-operator-plugins/pkg/client" ocv1 "github.com/operator-framework/operator-controller/api/v1" + "github.com/operator-framework/operator-controller/internal/operator-controller/authorization" "github.com/operator-framework/operator-controller/internal/operator-controller/features" "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/preflights/crdupgradesafety" "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/util" @@ -56,6 +60,7 @@ type BundleToHelmChartFn func(rv1 fs.FS, installNamespace string, watchNamespace type Helm struct { ActionClientGetter helmclient.ActionClientGetter Preflights []Preflight + PreAuthorizer authorization.PreAuthorizer BundleToHelmChartFn BundleToHelmChartFn } @@ -86,18 +91,46 @@ func (h *Helm) Apply(ctx context.Context, contentFS fs.FS, ext *ocv1.ClusterExte } values := chartutil.Values{} + post := &postrenderer{ + labels: objectLabels, + } + + if features.OperatorControllerFeatureGate.Enabled(features.PreflightPermissions) { + tmplRel, err := h.template(ctx, ext, chrt, values, post) + if err != nil { + return nil, "", fmt.Errorf("failed to get release state using client-only dry-run: %w", err) + } + + ceServiceAccount := user.DefaultInfo{Name: fmt.Sprintf("system:serviceaccount:%s:%s", ext.Spec.Namespace, ext.Spec.ServiceAccount.Name)} + missingRules, err := h.PreAuthorizer.PreAuthorize(ctx, &ceServiceAccount, strings.NewReader(tmplRel.Manifest)) + + var preAuthErrors []error + if len(missingRules) > 0 { + var missingRuleDescriptions []string + for ns, policyRules := range missingRules { + for _, rule := range policyRules { + missingRuleDescriptions = append(missingRuleDescriptions, ruleDescription(ns, rule)) + } + } + slices.Sort(missingRuleDescriptions) + preAuthErrors = append(preAuthErrors, fmt.Errorf("service account lacks permission to manage cluster extension:\n %s", strings.Join(missingRuleDescriptions, "\n "))) + } + if err != nil { + preAuthErrors = append(preAuthErrors, fmt.Errorf("authorization evaluation error: %w", err)) + } + if len(preAuthErrors) > 0 { + return nil, "", fmt.Errorf("pre-authorization failed: %v", preAuthErrors) + } + } + ac, err := h.ActionClientGetter.ActionClientFor(ctx, ext) if err != nil { return nil, "", err } - post := &postrenderer{ - labels: objectLabels, - } - rel, desiredRel, state, err := h.getReleaseState(ac, ext, chrt, values, post) if err != nil { - return nil, "", err + return nil, "", fmt.Errorf("failed to get release state using server-side dry-run: %w", err) } for _, preflight := range h.Preflights { @@ -164,6 +197,34 @@ func (h *Helm) buildHelmChart(bundleFS fs.FS, ext *ocv1.ClusterExtension) (*char return h.BundleToHelmChartFn(bundleFS, ext.Spec.Namespace, watchNamespace) } +func (h *Helm) template(ctx context.Context, ext *ocv1.ClusterExtension, chrt *chart.Chart, values chartutil.Values, post postrender.PostRenderer) (*release.Release, error) { + // We need to get a separate action client because our template call below + // permanently modifies the underlying action.Configuration for ClientOnly mode. + ac, err := h.ActionClientGetter.ActionClientFor(ctx, ext) + if err != nil { + return nil, err + } + + isUpgrade := false + currentRelease, err := ac.Get(ext.GetName()) + if err != nil && !errors.Is(err, driver.ErrReleaseNotFound) { + return nil, err + } + if currentRelease != nil { + isUpgrade = true + } + + return ac.Install(ext.GetName(), ext.Spec.Namespace, chrt, values, func(i *action.Install) error { + i.DryRun = true + i.ReleaseName = ext.GetName() + i.Replace = true + i.ClientOnly = true + i.IncludeCRDs = true + i.IsUpgrade = isUpgrade + return nil + }, helmclient.AppendInstallPostRenderer(post)) +} + func (h *Helm) getReleaseState(cl helmclient.ActionInterface, ext *ocv1.ClusterExtension, chrt *chart.Chart, values chartutil.Values, post postrender.PostRenderer) (*release.Release, *release.Release, string, error) { currentRelease, err := cl.Get(ext.GetName()) if errors.Is(err, driver.ErrReleaseNotFound) { @@ -173,10 +234,6 @@ func (h *Helm) getReleaseState(cl helmclient.ActionInterface, ext *ocv1.ClusterE return nil }, helmclient.AppendInstallPostRenderer(post)) if err != nil { - if features.OperatorControllerFeatureGate.Enabled(features.PreflightPermissions) { - _ = struct{}{} // minimal no-op to satisfy linter - // probably need to break out this error as it's the one for helm dry-run as opposed to any returned later - } return nil, nil, StateError, err } return nil, desiredRelease, StateNeedsInstall, nil @@ -232,3 +289,25 @@ func (p *postrenderer) Run(renderedManifests *bytes.Buffer) (*bytes.Buffer, erro } return &buf, nil } + +func ruleDescription(ns string, rule rbacv1.PolicyRule) string { + var sb strings.Builder + sb.WriteString(fmt.Sprintf("Namespace:%q", ns)) + + if len(rule.APIGroups) > 0 { + sb.WriteString(fmt.Sprintf(" APIGroups:[%s]", strings.Join(rule.APIGroups, ","))) + } + if len(rule.Resources) > 0 { + sb.WriteString(fmt.Sprintf(" Resources:[%s]", strings.Join(rule.Resources, ","))) + } + if len(rule.ResourceNames) > 0 { + sb.WriteString(fmt.Sprintf(" ResourceNames:[%s]", strings.Join(rule.ResourceNames, ","))) + } + if len(rule.Verbs) > 0 { + sb.WriteString(fmt.Sprintf(" Verbs:[%s]", strings.Join(rule.Verbs, ","))) + } + if len(rule.NonResourceURLs) > 0 { + sb.WriteString(fmt.Sprintf(" NonResourceURLs:[%s]", strings.Join(rule.NonResourceURLs, ","))) + } + return sb.String() +} diff --git a/internal/operator-controller/controllers/clusterextension_controller.go b/internal/operator-controller/controllers/clusterextension_controller.go index 5cbb670b6..07f54b94f 100644 --- a/internal/operator-controller/controllers/clusterextension_controller.go +++ b/internal/operator-controller/controllers/clusterextension_controller.go @@ -96,6 +96,7 @@ type InstalledBundleGetter interface { //+kubebuilder:rbac:namespace=system,groups=core,resources=secrets,verbs=create;update;patch;delete;deletecollection;get;list;watch //+kubebuilder:rbac:groups=core,resources=serviceaccounts/token,verbs=create //+kubebuilder:rbac:groups=apiextensions.k8s.io,resources=customresourcedefinitions,verbs=get +//+kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=clusterroles;clusterrolebindings;roles;rolebindings,verbs=list;watch //+kubebuilder:rbac:groups=olm.operatorframework.io,resources=clustercatalogs,verbs=list;watch From c84e87657b7864c124579fa04a2542ffab5ba5f3 Mon Sep 17 00:00:00 2001 From: Brett Tofel Date: Wed, 5 Mar 2025 15:56:00 -0500 Subject: [PATCH 05/67] Rm k8s.io/kubernetes copypasta & import/replace This is the manual version. Needed to change rbac.go a bit to allow for v1.32.2 code changes, but basically as-was Signed-off-by: Brett Tofel --- go.mod | 26 + .../kubernetes/pkg/apis/rbac/helpers.go | 375 --------------- .../kubernetes/pkg/apis/rbac/register.go | 60 --- .../kubernetes/pkg/apis/rbac/types.go | 210 -------- .../kubernetes/pkg/apis/rbac/v1/defaults.go | 49 -- .../pkg/apis/rbac/v1/evaluation_helpers.go | 144 ------ .../kubernetes/pkg/apis/rbac/v1/helpers.go | 231 --------- .../kubernetes/pkg/apis/rbac/v1/register.go | 44 -- .../apis/rbac/v1/zz_generated.conversion.go | 450 ------------------ .../pkg/apis/rbac/v1/zz_generated.defaults.go | 68 --- .../pkg/apis/rbac/zz_generated.deepcopy.go | 412 ---------------- .../pkg/registry/rbac/escalation_check.go | 146 ------ .../rbac/validation/policy_compact.go | 96 ---- .../pkg/registry/rbac/validation/rule.go | 384 --------------- .../plugin/pkg/auth/authorizer/rbac/rbac.go | 225 --------- .../operator-controller/authorization/rbac.go | 20 +- 16 files changed, 34 insertions(+), 2906 deletions(-) delete mode 100644 internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/helpers.go delete mode 100644 internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/register.go delete mode 100644 internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/types.go delete mode 100644 internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1/defaults.go delete mode 100644 internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1/evaluation_helpers.go delete mode 100644 internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1/helpers.go delete mode 100644 internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1/register.go delete mode 100644 internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1/zz_generated.conversion.go delete mode 100644 internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1/zz_generated.defaults.go delete mode 100644 internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/zz_generated.deepcopy.go delete mode 100644 internal/operator-controller/authorization/internal/kubernetes/pkg/registry/rbac/escalation_check.go delete mode 100644 internal/operator-controller/authorization/internal/kubernetes/pkg/registry/rbac/validation/policy_compact.go delete mode 100644 internal/operator-controller/authorization/internal/kubernetes/pkg/registry/rbac/validation/rule.go delete mode 100644 internal/operator-controller/authorization/internal/kubernetes/plugin/pkg/auth/authorizer/rbac/rbac.go diff --git a/go.mod b/go.mod index 638251bd5..dfc8c7a2d 100644 --- a/go.mod +++ b/go.mod @@ -38,11 +38,36 @@ require ( k8s.io/component-base v0.32.3 k8s.io/component-helpers v0.32.1 k8s.io/klog/v2 v2.130.1 + k8s.io/kubernetes v1.32.3 k8s.io/utils v0.0.0-20241210054802-24370beab758 sigs.k8s.io/controller-runtime v0.20.2 sigs.k8s.io/yaml v1.4.0 ) +replace ( + k8s.io/api => k8s.io/api v0.32.2 + k8s.io/apimachinery => k8s.io/apimachinery v0.32.2 + k8s.io/apiserver => k8s.io/apiserver v0.32.2 + k8s.io/client-go => k8s.io/client-go v0.32.2 + + k8s.io/cloud-provider => k8s.io/cloud-provider v0.32.2 + k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.32.2 + k8s.io/controller-manager => k8s.io/controller-manager v0.32.2 + k8s.io/cri-client => k8s.io/cri-client v0.32.2 + k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.32.2 + k8s.io/dynamic-resource-allocation => k8s.io/dynamic-resource-allocation v0.32.2 + k8s.io/endpointslice => k8s.io/endpointslice v0.32.2 + k8s.io/externaljwt => k8s.io/externaljwt v0.32.2 + k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.32.2 + k8s.io/kube-proxy => k8s.io/kube-proxy v0.32.2 + k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.32.2 + k8s.io/kubelet => k8s.io/kubelet v0.32.2 + k8s.io/kubernetes => k8s.io/kubernetes v1.32.2 + k8s.io/mount-utils => k8s.io/mount-utils v0.32.2 + k8s.io/pod-security-admission => k8s.io/pod-security-admission v0.32.2 + k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.32.2 +) + require ( cel.dev/expr v0.19.0 // indirect dario.cat/mergo v1.0.1 // indirect @@ -233,6 +258,7 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/component-helpers v0.32.1 // indirect k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7 // indirect k8s.io/kubectl v0.32.2 // indirect oras.land/oras-go v1.2.5 // indirect diff --git a/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/helpers.go b/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/helpers.go deleted file mode 100644 index 00aa4cae1..000000000 --- a/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/helpers.go +++ /dev/null @@ -1,375 +0,0 @@ -/* -Copyright 2016 The Kubernetes Authors. - -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. -*/ - -package rbac - -import ( - "fmt" - "strings" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/sets" -) - -// ResourceMatches returns the result of the rule.Resources matching. -func ResourceMatches(rule *PolicyRule, combinedRequestedResource, requestedSubresource string) bool { - for _, ruleResource := range rule.Resources { - // if everything is allowed, we match - if ruleResource == ResourceAll { - return true - } - // if we have an exact match, we match - if ruleResource == combinedRequestedResource { - return true - } - - // We can also match a */subresource. - // if there isn't a subresource, then continue - if len(requestedSubresource) == 0 { - continue - } - // if the rule isn't in the format */subresource, then we don't match, continue - if len(ruleResource) == len(requestedSubresource)+2 && - strings.HasPrefix(ruleResource, "*/") && - strings.HasSuffix(ruleResource, requestedSubresource) { - return true - - } - } - - return false -} - -// SubjectsStrings returns users, groups, serviceaccounts, unknown for display purposes. -func SubjectsStrings(subjects []Subject) ([]string, []string, []string, []string) { - users := []string{} - groups := []string{} - sas := []string{} - others := []string{} - - for _, subject := range subjects { - switch subject.Kind { - case ServiceAccountKind: - sas = append(sas, fmt.Sprintf("%s/%s", subject.Namespace, subject.Name)) - - case UserKind: - users = append(users, subject.Name) - - case GroupKind: - groups = append(groups, subject.Name) - - default: - others = append(others, fmt.Sprintf("%s/%s/%s", subject.Kind, subject.Namespace, subject.Name)) - } - } - - return users, groups, sas, others -} - -func (r PolicyRule) String() string { - return "PolicyRule" + r.CompactString() -} - -// CompactString exposes a compact string representation for use in escalation error messages -func (r PolicyRule) CompactString() string { - formatStringParts := []string{} - formatArgs := []interface{}{} - if len(r.APIGroups) > 0 { - formatStringParts = append(formatStringParts, "APIGroups:%q") - formatArgs = append(formatArgs, r.APIGroups) - } - if len(r.Resources) > 0 { - formatStringParts = append(formatStringParts, "Resources:%q") - formatArgs = append(formatArgs, r.Resources) - } - if len(r.NonResourceURLs) > 0 { - formatStringParts = append(formatStringParts, "NonResourceURLs:%q") - formatArgs = append(formatArgs, r.NonResourceURLs) - } - if len(r.ResourceNames) > 0 { - formatStringParts = append(formatStringParts, "ResourceNames:%q") - formatArgs = append(formatArgs, r.ResourceNames) - } - if len(r.Verbs) > 0 { - formatStringParts = append(formatStringParts, "Verbs:%q") - formatArgs = append(formatArgs, r.Verbs) - } - formatString := "{" + strings.Join(formatStringParts, ", ") + "}" - return fmt.Sprintf(formatString, formatArgs...) -} - -// PolicyRuleBuilder let's us attach methods. A no-no for API types. -// We use it to construct rules in code. It's more compact than trying to write them -// out in a literal and allows us to perform some basic checking during construction -// +k8s:deepcopy-gen=false -type PolicyRuleBuilder struct { - PolicyRule PolicyRule -} - -// NewRule returns new PolicyRule made by input verbs. -func NewRule(verbs ...string) *PolicyRuleBuilder { - return &PolicyRuleBuilder{ - PolicyRule: PolicyRule{Verbs: sets.NewString(verbs...).List()}, - } -} - -// Groups combines the PolicyRule.APIGroups and input groups. -func (r *PolicyRuleBuilder) Groups(groups ...string) *PolicyRuleBuilder { - r.PolicyRule.APIGroups = combine(r.PolicyRule.APIGroups, groups) - return r -} - -// Resources combines the PolicyRule.Rule and input resources. -func (r *PolicyRuleBuilder) Resources(resources ...string) *PolicyRuleBuilder { - r.PolicyRule.Resources = combine(r.PolicyRule.Resources, resources) - return r -} - -// Names combines the PolicyRule.ResourceNames and input names. -func (r *PolicyRuleBuilder) Names(names ...string) *PolicyRuleBuilder { - r.PolicyRule.ResourceNames = combine(r.PolicyRule.ResourceNames, names) - return r -} - -// URLs combines the PolicyRule.NonResourceURLs and input urls. -func (r *PolicyRuleBuilder) URLs(urls ...string) *PolicyRuleBuilder { - r.PolicyRule.NonResourceURLs = combine(r.PolicyRule.NonResourceURLs, urls) - return r -} - -// RuleOrDie calls the binding method and panics if there is an error. -func (r *PolicyRuleBuilder) RuleOrDie() PolicyRule { - ret, err := r.Rule() - if err != nil { - panic(err) - } - return ret -} - -func combine(s1, s2 []string) []string { - s := sets.NewString(s1...) - s.Insert(s2...) - return s.List() -} - -// Rule returns PolicyRule and error. -func (r *PolicyRuleBuilder) Rule() (PolicyRule, error) { - if len(r.PolicyRule.Verbs) == 0 { - return PolicyRule{}, fmt.Errorf("verbs are required: %#v", r.PolicyRule) - } - - switch { - case len(r.PolicyRule.NonResourceURLs) > 0: - if len(r.PolicyRule.APIGroups) != 0 || len(r.PolicyRule.Resources) != 0 || len(r.PolicyRule.ResourceNames) != 0 { - return PolicyRule{}, fmt.Errorf("non-resource rule may not have apiGroups, resources, or resourceNames: %#v", r.PolicyRule) - } - case len(r.PolicyRule.Resources) > 0: - // resource rule may not have nonResourceURLs - - if len(r.PolicyRule.APIGroups) == 0 { - // this a common bug - return PolicyRule{}, fmt.Errorf("resource rule must have apiGroups: %#v", r.PolicyRule) - } - // if resource names are set, then the verb must not be list, watch, create, or deletecollection - // since verbs are largely opaque, we don't want to accidentally prevent things like "impersonate", so - // we will backlist common mistakes, not whitelist acceptable options. - if len(r.PolicyRule.ResourceNames) != 0 { - illegalVerbs := []string{} - for _, verb := range r.PolicyRule.Verbs { - switch verb { - case "list", "watch", "create", "deletecollection": - illegalVerbs = append(illegalVerbs, verb) - } - } - if len(illegalVerbs) > 0 { - return PolicyRule{}, fmt.Errorf("verbs %v do not have names available: %#v", illegalVerbs, r.PolicyRule) - } - } - - default: - return PolicyRule{}, fmt.Errorf("a rule must have either nonResourceURLs or resources: %#v", r.PolicyRule) - } - - return r.PolicyRule, nil -} - -// ClusterRoleBindingBuilder let's us attach methods. A no-no for API types. -// We use it to construct bindings in code. It's more compact than trying to write them -// out in a literal. -// +k8s:deepcopy-gen=false -type ClusterRoleBindingBuilder struct { - ClusterRoleBinding ClusterRoleBinding -} - -// NewClusterBinding creates a ClusterRoleBinding builder that can be used -// to define the subjects of a cluster role binding. At least one of -// the `Groups`, `Users` or `SAs` method must be called before -// calling the `Binding*` methods. -func NewClusterBinding(clusterRoleName string) *ClusterRoleBindingBuilder { - return &ClusterRoleBindingBuilder{ - ClusterRoleBinding: ClusterRoleBinding{ - ObjectMeta: metav1.ObjectMeta{Name: clusterRoleName}, - RoleRef: RoleRef{ - APIGroup: GroupName, - Kind: "ClusterRole", - Name: clusterRoleName, - }, - }, - } -} - -// Groups adds the specified groups as the subjects of the ClusterRoleBinding. -func (r *ClusterRoleBindingBuilder) Groups(groups ...string) *ClusterRoleBindingBuilder { - for _, group := range groups { - r.ClusterRoleBinding.Subjects = append(r.ClusterRoleBinding.Subjects, Subject{Kind: GroupKind, APIGroup: GroupName, Name: group}) - } - return r -} - -// Users adds the specified users as the subjects of the ClusterRoleBinding. -func (r *ClusterRoleBindingBuilder) Users(users ...string) *ClusterRoleBindingBuilder { - for _, user := range users { - r.ClusterRoleBinding.Subjects = append(r.ClusterRoleBinding.Subjects, Subject{Kind: UserKind, APIGroup: GroupName, Name: user}) - } - return r -} - -// SAs adds the specified sas as the subjects of the ClusterRoleBinding. -func (r *ClusterRoleBindingBuilder) SAs(namespace string, serviceAccountNames ...string) *ClusterRoleBindingBuilder { - for _, saName := range serviceAccountNames { - r.ClusterRoleBinding.Subjects = append(r.ClusterRoleBinding.Subjects, Subject{Kind: ServiceAccountKind, Namespace: namespace, Name: saName}) - } - return r -} - -// BindingOrDie calls the binding method and panics if there is an error. -func (r *ClusterRoleBindingBuilder) BindingOrDie() ClusterRoleBinding { - ret, err := r.Binding() - if err != nil { - panic(err) - } - return ret -} - -// Binding builds and returns the ClusterRoleBinding API object from the builder -// object. -func (r *ClusterRoleBindingBuilder) Binding() (ClusterRoleBinding, error) { - if len(r.ClusterRoleBinding.Subjects) == 0 { - return ClusterRoleBinding{}, fmt.Errorf("subjects are required: %#v", r.ClusterRoleBinding) - } - - return r.ClusterRoleBinding, nil -} - -// RoleBindingBuilder let's us attach methods. It is similar to -// ClusterRoleBindingBuilder above. -// +k8s:deepcopy-gen=false -type RoleBindingBuilder struct { - RoleBinding RoleBinding -} - -// NewRoleBinding creates a RoleBinding builder that can be used -// to define the subjects of a role binding. At least one of -// the `Groups`, `Users` or `SAs` method must be called before -// calling the `Binding*` methods. -func NewRoleBinding(roleName, namespace string) *RoleBindingBuilder { - return &RoleBindingBuilder{ - RoleBinding: RoleBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: roleName, - Namespace: namespace, - }, - RoleRef: RoleRef{ - APIGroup: GroupName, - Kind: "Role", - Name: roleName, - }, - }, - } -} - -// NewRoleBindingForClusterRole creates a RoleBinding builder that can be used -// to define the subjects of a cluster role binding. At least one of -// the `Groups`, `Users` or `SAs` method must be called before -// calling the `Binding*` methods. -func NewRoleBindingForClusterRole(roleName, namespace string) *RoleBindingBuilder { - return &RoleBindingBuilder{ - RoleBinding: RoleBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: roleName, - Namespace: namespace, - }, - RoleRef: RoleRef{ - APIGroup: GroupName, - Kind: "ClusterRole", - Name: roleName, - }, - }, - } -} - -// Groups adds the specified groups as the subjects of the RoleBinding. -func (r *RoleBindingBuilder) Groups(groups ...string) *RoleBindingBuilder { - for _, group := range groups { - r.RoleBinding.Subjects = append(r.RoleBinding.Subjects, Subject{Kind: GroupKind, APIGroup: GroupName, Name: group}) - } - return r -} - -// Users adds the specified users as the subjects of the RoleBinding. -func (r *RoleBindingBuilder) Users(users ...string) *RoleBindingBuilder { - for _, user := range users { - r.RoleBinding.Subjects = append(r.RoleBinding.Subjects, Subject{Kind: UserKind, APIGroup: GroupName, Name: user}) - } - return r -} - -// SAs adds the specified service accounts as the subjects of the -// RoleBinding. -func (r *RoleBindingBuilder) SAs(namespace string, serviceAccountNames ...string) *RoleBindingBuilder { - for _, saName := range serviceAccountNames { - r.RoleBinding.Subjects = append(r.RoleBinding.Subjects, Subject{Kind: ServiceAccountKind, Namespace: namespace, Name: saName}) - } - return r -} - -// BindingOrDie calls the binding method and panics if there is an error. -func (r *RoleBindingBuilder) BindingOrDie() RoleBinding { - ret, err := r.Binding() - if err != nil { - panic(err) - } - return ret -} - -// Binding builds and returns the RoleBinding API object from the builder -// object. -func (r *RoleBindingBuilder) Binding() (RoleBinding, error) { - if len(r.RoleBinding.Subjects) == 0 { - return RoleBinding{}, fmt.Errorf("subjects are required: %#v", r.RoleBinding) - } - - return r.RoleBinding, nil -} - -// SortableRuleSlice is the slice of PolicyRule. -type SortableRuleSlice []PolicyRule - -func (s SortableRuleSlice) Len() int { return len(s) } -func (s SortableRuleSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } -func (s SortableRuleSlice) Less(i, j int) bool { - return strings.Compare(s[i].String(), s[j].String()) < 0 -} diff --git a/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/register.go b/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/register.go deleted file mode 100644 index 48f5f6e74..000000000 --- a/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/register.go +++ /dev/null @@ -1,60 +0,0 @@ -/* -Copyright 2016 The Kubernetes Authors. - -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. -*/ - -package rbac - -import ( - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" -) - -// GroupName is the name of this API group. -const GroupName = "rbac.authorization.k8s.io" - -// SchemeGroupVersion is group version used to register these objects -var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal} - -// Kind takes an unqualified kind and returns a Group qualified GroupKind -func Kind(kind string) schema.GroupKind { - return SchemeGroupVersion.WithKind(kind).GroupKind() -} - -// Resource takes an unqualified resource and returns a Group qualified GroupResource -func Resource(resource string) schema.GroupResource { - return SchemeGroupVersion.WithResource(resource).GroupResource() -} - -// SchemeBuilder is a function that calls Register for you. -var ( - SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) - AddToScheme = SchemeBuilder.AddToScheme -) - -// Adds the list of known types to the given scheme. -func addKnownTypes(scheme *runtime.Scheme) error { - scheme.AddKnownTypes(SchemeGroupVersion, - &Role{}, - &RoleBinding{}, - &RoleBindingList{}, - &RoleList{}, - - &ClusterRole{}, - &ClusterRoleBinding{}, - &ClusterRoleBindingList{}, - &ClusterRoleList{}, - ) - return nil -} diff --git a/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/types.go b/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/types.go deleted file mode 100644 index 357e8d8bc..000000000 --- a/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/types.go +++ /dev/null @@ -1,210 +0,0 @@ -/* -Copyright 2016 The Kubernetes Authors. - -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. -*/ - -package rbac - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// Authorization is calculated against -// 1. evaluation of ClusterRoleBindings - short circuit on match -// 2. evaluation of RoleBindings in the namespace requested - short circuit on match -// 3. deny by default - -// APIGroupAll and these consts are default values for rbac authorization. -const ( - APIGroupAll = "*" - ResourceAll = "*" - VerbAll = "*" - NonResourceAll = "*" - - GroupKind = "Group" - ServiceAccountKind = "ServiceAccount" - UserKind = "User" - - // AutoUpdateAnnotationKey is the name of an annotation which prevents reconciliation if set to "false" - AutoUpdateAnnotationKey = "rbac.authorization.kubernetes.io/autoupdate" -) - -// PolicyRule holds information that describes a policy rule, but does not contain information -// about who the rule applies to or which namespace the rule applies to. -type PolicyRule struct { - // Verbs is a list of Verbs that apply to ALL the ResourceKinds contained in this rule. '*' represents all verbs. - Verbs []string - - // APIGroups is the name of the APIGroup that contains the resources. - // If multiple API groups are specified, any action requested against one of the enumerated resources in any API group will be allowed. "" represents the core API group and "*" represents all API groups. - APIGroups []string - // Resources is a list of resources this rule applies to. '*' represents all resources in the specified apiGroups. - // '*/foo' represents the subresource 'foo' for all resources in the specified apiGroups. - Resources []string - // ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. - ResourceNames []string - - // NonResourceURLs is a set of partial urls that a user should have access to. *s are allowed, but only as the full, final step in the path - // If an action is not a resource API request, then the URL is split on '/' and is checked against the NonResourceURLs to look for a match. - // Since non-resource URLs are not namespaced, this field is only applicable for ClusterRoles referenced from a ClusterRoleBinding. - // Rules can either apply to API resources (such as "pods" or "secrets") or non-resource URL paths (such as "/api"), but not both. - NonResourceURLs []string -} - -// Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, -// or a value for non-objects such as user and group names. -type Subject struct { - // Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". - // If the Authorizer does not recognized the kind value, the Authorizer should report an error. - Kind string - // APIGroup holds the API group of the referenced subject. - // Defaults to "" for ServiceAccount subjects. - // Defaults to "rbac.authorization.k8s.io" for User and Group subjects. - APIGroup string - // Name of the object being referenced. - Name string - // Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty - // the Authorizer should report an error. - Namespace string -} - -// RoleRef contains information that points to the role being used -type RoleRef struct { - // APIGroup is the group for the resource being referenced - APIGroup string - // Kind is the type of resource being referenced - Kind string - // Name is the name of resource being referenced - Name string -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// Role is a namespaced, logical grouping of PolicyRules that can be referenced as a unit by a RoleBinding. -type Role struct { - metav1.TypeMeta - // Standard object's metadata. - metav1.ObjectMeta - - // Rules holds all the PolicyRules for this Role - Rules []PolicyRule -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// RoleBinding references a role, but does not contain it. It can reference a Role in the same namespace or a ClusterRole in the global namespace. -// It adds who information via Subjects and namespace information by which namespace it exists in. RoleBindings in a given -// namespace only have effect in that namespace. -type RoleBinding struct { - metav1.TypeMeta - metav1.ObjectMeta - - // Subjects holds references to the objects the role applies to. - Subjects []Subject - - // RoleRef can reference a Role in the current namespace or a ClusterRole in the global namespace. - // If the RoleRef cannot be resolved, the Authorizer must return an error. - // This field is immutable. - RoleRef RoleRef -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// RoleBindingList is a collection of RoleBindings -type RoleBindingList struct { - metav1.TypeMeta - // Standard object's metadata. - metav1.ListMeta - - // Items is a list of roleBindings - Items []RoleBinding -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// RoleList is a collection of Roles -type RoleList struct { - metav1.TypeMeta - // Standard object's metadata. - metav1.ListMeta - - // Items is a list of roles - Items []Role -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// ClusterRole is a cluster level, logical grouping of PolicyRules that can be referenced as a unit by a RoleBinding or ClusterRoleBinding. -type ClusterRole struct { - metav1.TypeMeta - // Standard object's metadata. - metav1.ObjectMeta - - // Rules holds all the PolicyRules for this ClusterRole - Rules []PolicyRule - - // AggregationRule is an optional field that describes how to build the Rules for this ClusterRole. - // If AggregationRule is set, then the Rules are controller managed and direct changes to Rules will be - // stomped by the controller. - AggregationRule *AggregationRule -} - -// AggregationRule describes how to locate ClusterRoles to aggregate into the ClusterRole -type AggregationRule struct { - // ClusterRoleSelectors holds a list of selectors which will be used to find ClusterRoles and create the rules. - // If any of the selectors match, then the ClusterRole's permissions will be added - ClusterRoleSelectors []metav1.LabelSelector -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// ClusterRoleBinding references a ClusterRole, but not contain it. It can reference a ClusterRole in the global namespace, -// and adds who information via Subject. -type ClusterRoleBinding struct { - metav1.TypeMeta - // Standard object's metadata. - metav1.ObjectMeta - - // Subjects holds references to the objects the role applies to. - Subjects []Subject - - // RoleRef can only reference a ClusterRole in the global namespace. - // If the RoleRef cannot be resolved, the Authorizer must return an error. - // This field is immutable. - RoleRef RoleRef -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// ClusterRoleBindingList is a collection of ClusterRoleBindings -type ClusterRoleBindingList struct { - metav1.TypeMeta - // Standard object's metadata. - metav1.ListMeta - - // Items is a list of ClusterRoleBindings - Items []ClusterRoleBinding -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// ClusterRoleList is a collection of ClusterRoles -type ClusterRoleList struct { - metav1.TypeMeta - // Standard object's metadata. - metav1.ListMeta - - // Items is a list of ClusterRoles - Items []ClusterRole -} diff --git a/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1/defaults.go b/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1/defaults.go deleted file mode 100644 index 7d285a857..000000000 --- a/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1/defaults.go +++ /dev/null @@ -1,49 +0,0 @@ -/* -Copyright 2017 The Kubernetes Authors. - -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. -*/ - -package v1 - -import ( - rbacv1 "k8s.io/api/rbac/v1" - "k8s.io/apimachinery/pkg/runtime" -) - -func addDefaultingFuncs(scheme *runtime.Scheme) error { - return RegisterDefaults(scheme) -} - -func SetDefaults_ClusterRoleBinding(obj *rbacv1.ClusterRoleBinding) { - if len(obj.RoleRef.APIGroup) == 0 { - obj.RoleRef.APIGroup = GroupName - } -} -func SetDefaults_RoleBinding(obj *rbacv1.RoleBinding) { - if len(obj.RoleRef.APIGroup) == 0 { - obj.RoleRef.APIGroup = GroupName - } -} -func SetDefaults_Subject(obj *rbacv1.Subject) { - if len(obj.APIGroup) == 0 { - switch obj.Kind { - case rbacv1.ServiceAccountKind: - obj.APIGroup = "" - case rbacv1.UserKind: - obj.APIGroup = GroupName - case rbacv1.GroupKind: - obj.APIGroup = GroupName - } - } -} diff --git a/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1/evaluation_helpers.go b/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1/evaluation_helpers.go deleted file mode 100644 index 5f5edaff1..000000000 --- a/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1/evaluation_helpers.go +++ /dev/null @@ -1,144 +0,0 @@ -/* -Copyright 2018 The Kubernetes Authors. - -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. -*/ - -package v1 - -import ( - "fmt" - "strings" - - rbacv1 "k8s.io/api/rbac/v1" -) - -func VerbMatches(rule *rbacv1.PolicyRule, requestedVerb string) bool { - for _, ruleVerb := range rule.Verbs { - if ruleVerb == rbacv1.VerbAll { - return true - } - if ruleVerb == requestedVerb { - return true - } - } - - return false -} - -func APIGroupMatches(rule *rbacv1.PolicyRule, requestedGroup string) bool { - for _, ruleGroup := range rule.APIGroups { - if ruleGroup == rbacv1.APIGroupAll { - return true - } - if ruleGroup == requestedGroup { - return true - } - } - - return false -} - -func ResourceMatches(rule *rbacv1.PolicyRule, combinedRequestedResource, requestedSubresource string) bool { - for _, ruleResource := range rule.Resources { - // if everything is allowed, we match - if ruleResource == rbacv1.ResourceAll { - return true - } - // if we have an exact match, we match - if ruleResource == combinedRequestedResource { - return true - } - - // We can also match a */subresource. - // if there isn't a subresource, then continue - if len(requestedSubresource) == 0 { - continue - } - // if the rule isn't in the format */subresource, then we don't match, continue - if len(ruleResource) == len(requestedSubresource)+2 && - strings.HasPrefix(ruleResource, "*/") && - strings.HasSuffix(ruleResource, requestedSubresource) { - return true - - } - } - - return false -} - -func ResourceNameMatches(rule *rbacv1.PolicyRule, requestedName string) bool { - if len(rule.ResourceNames) == 0 { - return true - } - - for _, ruleName := range rule.ResourceNames { - if ruleName == requestedName { - return true - } - } - - return false -} - -func NonResourceURLMatches(rule *rbacv1.PolicyRule, requestedURL string) bool { - for _, ruleURL := range rule.NonResourceURLs { - if ruleURL == rbacv1.NonResourceAll { - return true - } - if ruleURL == requestedURL { - return true - } - if strings.HasSuffix(ruleURL, "*") && strings.HasPrefix(requestedURL, strings.TrimRight(ruleURL, "*")) { - return true - } - } - - return false -} - -// CompactString exposes a compact string representation for use in escalation error messages -func CompactString(r rbacv1.PolicyRule) string { - formatStringParts := []string{} - formatArgs := []interface{}{} - if len(r.APIGroups) > 0 { - formatStringParts = append(formatStringParts, "APIGroups:%q") - formatArgs = append(formatArgs, r.APIGroups) - } - if len(r.Resources) > 0 { - formatStringParts = append(formatStringParts, "Resources:%q") - formatArgs = append(formatArgs, r.Resources) - } - if len(r.NonResourceURLs) > 0 { - formatStringParts = append(formatStringParts, "NonResourceURLs:%q") - formatArgs = append(formatArgs, r.NonResourceURLs) - } - if len(r.ResourceNames) > 0 { - formatStringParts = append(formatStringParts, "ResourceNames:%q") - formatArgs = append(formatArgs, r.ResourceNames) - } - if len(r.Verbs) > 0 { - formatStringParts = append(formatStringParts, "Verbs:%q") - formatArgs = append(formatArgs, r.Verbs) - } - formatString := "{" + strings.Join(formatStringParts, ", ") + "}" - return fmt.Sprintf(formatString, formatArgs...) -} - -type SortableRuleSlice []rbacv1.PolicyRule - -func (s SortableRuleSlice) Len() int { return len(s) } -func (s SortableRuleSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } -func (s SortableRuleSlice) Less(i, j int) bool { - return strings.Compare(s[i].String(), s[j].String()) < 0 -} diff --git a/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1/helpers.go b/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1/helpers.go deleted file mode 100644 index 669e48c8a..000000000 --- a/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1/helpers.go +++ /dev/null @@ -1,231 +0,0 @@ -/* -Copyright 2017 The Kubernetes Authors. - -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. -*/ - -package v1 - -import ( - "fmt" - - rbacv1 "k8s.io/api/rbac/v1" - - "sort" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// +k8s:deepcopy-gen=false - -// PolicyRuleBuilder let's us attach methods. A no-no for API types. -// We use it to construct rules in code. It's more compact than trying to write them -// out in a literal and allows us to perform some basic checking during construction -type PolicyRuleBuilder struct { - PolicyRule rbacv1.PolicyRule `protobuf:"bytes,1,opt,name=policyRule"` -} - -func NewRule(verbs ...string) *PolicyRuleBuilder { - return &PolicyRuleBuilder{ - PolicyRule: rbacv1.PolicyRule{Verbs: verbs}, - } -} - -func (r *PolicyRuleBuilder) Groups(groups ...string) *PolicyRuleBuilder { - r.PolicyRule.APIGroups = append(r.PolicyRule.APIGroups, groups...) - return r -} - -func (r *PolicyRuleBuilder) Resources(resources ...string) *PolicyRuleBuilder { - r.PolicyRule.Resources = append(r.PolicyRule.Resources, resources...) - return r -} - -func (r *PolicyRuleBuilder) Names(names ...string) *PolicyRuleBuilder { - r.PolicyRule.ResourceNames = append(r.PolicyRule.ResourceNames, names...) - return r -} - -func (r *PolicyRuleBuilder) URLs(urls ...string) *PolicyRuleBuilder { - r.PolicyRule.NonResourceURLs = append(r.PolicyRule.NonResourceURLs, urls...) - return r -} - -func (r *PolicyRuleBuilder) RuleOrDie() rbacv1.PolicyRule { - ret, err := r.Rule() - if err != nil { - panic(err) - } - return ret -} - -func (r *PolicyRuleBuilder) Rule() (rbacv1.PolicyRule, error) { - if len(r.PolicyRule.Verbs) == 0 { - return rbacv1.PolicyRule{}, fmt.Errorf("verbs are required: %#v", r.PolicyRule) - } - - switch { - case len(r.PolicyRule.NonResourceURLs) > 0: - if len(r.PolicyRule.APIGroups) != 0 || len(r.PolicyRule.Resources) != 0 || len(r.PolicyRule.ResourceNames) != 0 { - return rbacv1.PolicyRule{}, fmt.Errorf("non-resource rule may not have apiGroups, resources, or resourceNames: %#v", r.PolicyRule) - } - case len(r.PolicyRule.Resources) > 0: - if len(r.PolicyRule.NonResourceURLs) != 0 { - return rbacv1.PolicyRule{}, fmt.Errorf("resource rule may not have nonResourceURLs: %#v", r.PolicyRule) - } - if len(r.PolicyRule.APIGroups) == 0 { - // this a common bug - return rbacv1.PolicyRule{}, fmt.Errorf("resource rule must have apiGroups: %#v", r.PolicyRule) - } - default: - return rbacv1.PolicyRule{}, fmt.Errorf("a rule must have either nonResourceURLs or resources: %#v", r.PolicyRule) - } - - sort.Strings(r.PolicyRule.Resources) - sort.Strings(r.PolicyRule.ResourceNames) - sort.Strings(r.PolicyRule.APIGroups) - sort.Strings(r.PolicyRule.NonResourceURLs) - sort.Strings(r.PolicyRule.Verbs) - return r.PolicyRule, nil -} - -// +k8s:deepcopy-gen=false - -// ClusterRoleBindingBuilder let's us attach methods. A no-no for API types. -// We use it to construct bindings in code. It's more compact than trying to write them -// out in a literal. -type ClusterRoleBindingBuilder struct { - ClusterRoleBinding rbacv1.ClusterRoleBinding `protobuf:"bytes,1,opt,name=clusterRoleBinding"` -} - -func NewClusterBinding(clusterRoleName string) *ClusterRoleBindingBuilder { - return &ClusterRoleBindingBuilder{ - ClusterRoleBinding: rbacv1.ClusterRoleBinding{ - ObjectMeta: metav1.ObjectMeta{Name: clusterRoleName}, - RoleRef: rbacv1.RoleRef{ - APIGroup: GroupName, - Kind: "ClusterRole", - Name: clusterRoleName, - }, - }, - } -} - -func (r *ClusterRoleBindingBuilder) Groups(groups ...string) *ClusterRoleBindingBuilder { - for _, group := range groups { - r.ClusterRoleBinding.Subjects = append(r.ClusterRoleBinding.Subjects, rbacv1.Subject{APIGroup: rbacv1.GroupName, Kind: rbacv1.GroupKind, Name: group}) - } - return r -} - -func (r *ClusterRoleBindingBuilder) Users(users ...string) *ClusterRoleBindingBuilder { - for _, user := range users { - r.ClusterRoleBinding.Subjects = append(r.ClusterRoleBinding.Subjects, rbacv1.Subject{APIGroup: rbacv1.GroupName, Kind: rbacv1.UserKind, Name: user}) - } - return r -} - -func (r *ClusterRoleBindingBuilder) SAs(namespace string, serviceAccountNames ...string) *ClusterRoleBindingBuilder { - for _, saName := range serviceAccountNames { - r.ClusterRoleBinding.Subjects = append(r.ClusterRoleBinding.Subjects, rbacv1.Subject{Kind: rbacv1.ServiceAccountKind, Namespace: namespace, Name: saName}) - } - return r -} - -func (r *ClusterRoleBindingBuilder) BindingOrDie() rbacv1.ClusterRoleBinding { - ret, err := r.Binding() - if err != nil { - panic(err) - } - return ret -} - -func (r *ClusterRoleBindingBuilder) Binding() (rbacv1.ClusterRoleBinding, error) { - if len(r.ClusterRoleBinding.Subjects) == 0 { - return rbacv1.ClusterRoleBinding{}, fmt.Errorf("subjects are required: %#v", r.ClusterRoleBinding) - } - - return r.ClusterRoleBinding, nil -} - -// +k8s:deepcopy-gen=false - -// RoleBindingBuilder let's us attach methods. It is similar to -// ClusterRoleBindingBuilder above. -type RoleBindingBuilder struct { - RoleBinding rbacv1.RoleBinding -} - -// NewRoleBinding creates a RoleBinding builder that can be used -// to define the subjects of a role binding. At least one of -// the `Groups`, `Users` or `SAs` method must be called before -// calling the `Binding*` methods. -func NewRoleBinding(roleName, namespace string) *RoleBindingBuilder { - return &RoleBindingBuilder{ - RoleBinding: rbacv1.RoleBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: roleName, - Namespace: namespace, - }, - RoleRef: rbacv1.RoleRef{ - APIGroup: GroupName, - Kind: "Role", - Name: roleName, - }, - }, - } -} - -// Groups adds the specified groups as the subjects of the RoleBinding. -func (r *RoleBindingBuilder) Groups(groups ...string) *RoleBindingBuilder { - for _, group := range groups { - r.RoleBinding.Subjects = append(r.RoleBinding.Subjects, rbacv1.Subject{Kind: rbacv1.GroupKind, APIGroup: GroupName, Name: group}) - } - return r -} - -// Users adds the specified users as the subjects of the RoleBinding. -func (r *RoleBindingBuilder) Users(users ...string) *RoleBindingBuilder { - for _, user := range users { - r.RoleBinding.Subjects = append(r.RoleBinding.Subjects, rbacv1.Subject{Kind: rbacv1.UserKind, APIGroup: GroupName, Name: user}) - } - return r -} - -// SAs adds the specified service accounts as the subjects of the -// RoleBinding. -func (r *RoleBindingBuilder) SAs(namespace string, serviceAccountNames ...string) *RoleBindingBuilder { - for _, saName := range serviceAccountNames { - r.RoleBinding.Subjects = append(r.RoleBinding.Subjects, rbacv1.Subject{Kind: rbacv1.ServiceAccountKind, Namespace: namespace, Name: saName}) - } - return r -} - -// BindingOrDie calls the binding method and panics if there is an error. -func (r *RoleBindingBuilder) BindingOrDie() rbacv1.RoleBinding { - ret, err := r.Binding() - if err != nil { - panic(err) - } - return ret -} - -// Binding builds and returns the RoleBinding API object from the builder -// object. -func (r *RoleBindingBuilder) Binding() (rbacv1.RoleBinding, error) { - if len(r.RoleBinding.Subjects) == 0 { - return rbacv1.RoleBinding{}, fmt.Errorf("subjects are required: %#v", r.RoleBinding) - } - - return r.RoleBinding, nil -} diff --git a/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1/register.go b/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1/register.go deleted file mode 100644 index ae138c888..000000000 --- a/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1/register.go +++ /dev/null @@ -1,44 +0,0 @@ -/* -Copyright 2017 The Kubernetes Authors. - -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. -*/ - -package v1 - -import ( - rbacv1 "k8s.io/api/rbac/v1" - "k8s.io/apimachinery/pkg/runtime/schema" -) - -const GroupName = "rbac.authorization.k8s.io" - -// SchemeGroupVersion is group version used to register these objects -var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1"} - -// Resource takes an unqualified resource and returns a Group qualified GroupResource -func Resource(resource string) schema.GroupResource { - return SchemeGroupVersion.WithResource(resource).GroupResource() -} - -var ( - localSchemeBuilder = &rbacv1.SchemeBuilder - AddToScheme = localSchemeBuilder.AddToScheme -) - -func init() { - // We only register manually written functions here. The registration of the - // generated functions takes place in the generated files. The separation - // makes the code compile even when the generated files are missing. - localSchemeBuilder.Register(addDefaultingFuncs) -} diff --git a/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1/zz_generated.conversion.go b/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1/zz_generated.conversion.go deleted file mode 100644 index 66f0d13c3..000000000 --- a/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1/zz_generated.conversion.go +++ /dev/null @@ -1,450 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -/* -Copyright The Kubernetes Authors. - -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 conversion-gen. DO NOT EDIT. - -package v1 - -import ( - unsafe "unsafe" - - rbac "github.com/operator-framework/operator-controller/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac" - rbacv1 "k8s.io/api/rbac/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - conversion "k8s.io/apimachinery/pkg/conversion" - runtime "k8s.io/apimachinery/pkg/runtime" -) - -func init() { - localSchemeBuilder.Register(RegisterConversions) -} - -// RegisterConversions adds conversion functions to the given scheme. -// Public to allow building arbitrary schemes. -func RegisterConversions(s *runtime.Scheme) error { - if err := s.AddGeneratedConversionFunc((*rbacv1.AggregationRule)(nil), (*rbac.AggregationRule)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_AggregationRule_To_rbac_AggregationRule(a.(*rbacv1.AggregationRule), b.(*rbac.AggregationRule), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*rbac.AggregationRule)(nil), (*rbacv1.AggregationRule)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_rbac_AggregationRule_To_v1_AggregationRule(a.(*rbac.AggregationRule), b.(*rbacv1.AggregationRule), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*rbacv1.ClusterRole)(nil), (*rbac.ClusterRole)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_ClusterRole_To_rbac_ClusterRole(a.(*rbacv1.ClusterRole), b.(*rbac.ClusterRole), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*rbac.ClusterRole)(nil), (*rbacv1.ClusterRole)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_rbac_ClusterRole_To_v1_ClusterRole(a.(*rbac.ClusterRole), b.(*rbacv1.ClusterRole), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*rbacv1.ClusterRoleBinding)(nil), (*rbac.ClusterRoleBinding)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_ClusterRoleBinding_To_rbac_ClusterRoleBinding(a.(*rbacv1.ClusterRoleBinding), b.(*rbac.ClusterRoleBinding), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*rbac.ClusterRoleBinding)(nil), (*rbacv1.ClusterRoleBinding)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_rbac_ClusterRoleBinding_To_v1_ClusterRoleBinding(a.(*rbac.ClusterRoleBinding), b.(*rbacv1.ClusterRoleBinding), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*rbacv1.ClusterRoleBindingList)(nil), (*rbac.ClusterRoleBindingList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_ClusterRoleBindingList_To_rbac_ClusterRoleBindingList(a.(*rbacv1.ClusterRoleBindingList), b.(*rbac.ClusterRoleBindingList), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*rbac.ClusterRoleBindingList)(nil), (*rbacv1.ClusterRoleBindingList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_rbac_ClusterRoleBindingList_To_v1_ClusterRoleBindingList(a.(*rbac.ClusterRoleBindingList), b.(*rbacv1.ClusterRoleBindingList), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*rbacv1.ClusterRoleList)(nil), (*rbac.ClusterRoleList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_ClusterRoleList_To_rbac_ClusterRoleList(a.(*rbacv1.ClusterRoleList), b.(*rbac.ClusterRoleList), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*rbac.ClusterRoleList)(nil), (*rbacv1.ClusterRoleList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_rbac_ClusterRoleList_To_v1_ClusterRoleList(a.(*rbac.ClusterRoleList), b.(*rbacv1.ClusterRoleList), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*rbacv1.PolicyRule)(nil), (*rbac.PolicyRule)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_PolicyRule_To_rbac_PolicyRule(a.(*rbacv1.PolicyRule), b.(*rbac.PolicyRule), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*rbac.PolicyRule)(nil), (*rbacv1.PolicyRule)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_rbac_PolicyRule_To_v1_PolicyRule(a.(*rbac.PolicyRule), b.(*rbacv1.PolicyRule), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*rbacv1.Role)(nil), (*rbac.Role)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_Role_To_rbac_Role(a.(*rbacv1.Role), b.(*rbac.Role), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*rbac.Role)(nil), (*rbacv1.Role)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_rbac_Role_To_v1_Role(a.(*rbac.Role), b.(*rbacv1.Role), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*rbacv1.RoleBinding)(nil), (*rbac.RoleBinding)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_RoleBinding_To_rbac_RoleBinding(a.(*rbacv1.RoleBinding), b.(*rbac.RoleBinding), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*rbac.RoleBinding)(nil), (*rbacv1.RoleBinding)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_rbac_RoleBinding_To_v1_RoleBinding(a.(*rbac.RoleBinding), b.(*rbacv1.RoleBinding), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*rbacv1.RoleBindingList)(nil), (*rbac.RoleBindingList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_RoleBindingList_To_rbac_RoleBindingList(a.(*rbacv1.RoleBindingList), b.(*rbac.RoleBindingList), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*rbac.RoleBindingList)(nil), (*rbacv1.RoleBindingList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_rbac_RoleBindingList_To_v1_RoleBindingList(a.(*rbac.RoleBindingList), b.(*rbacv1.RoleBindingList), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*rbacv1.RoleList)(nil), (*rbac.RoleList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_RoleList_To_rbac_RoleList(a.(*rbacv1.RoleList), b.(*rbac.RoleList), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*rbac.RoleList)(nil), (*rbacv1.RoleList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_rbac_RoleList_To_v1_RoleList(a.(*rbac.RoleList), b.(*rbacv1.RoleList), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*rbacv1.RoleRef)(nil), (*rbac.RoleRef)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_RoleRef_To_rbac_RoleRef(a.(*rbacv1.RoleRef), b.(*rbac.RoleRef), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*rbac.RoleRef)(nil), (*rbacv1.RoleRef)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_rbac_RoleRef_To_v1_RoleRef(a.(*rbac.RoleRef), b.(*rbacv1.RoleRef), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*rbacv1.Subject)(nil), (*rbac.Subject)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_Subject_To_rbac_Subject(a.(*rbacv1.Subject), b.(*rbac.Subject), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*rbac.Subject)(nil), (*rbacv1.Subject)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_rbac_Subject_To_v1_Subject(a.(*rbac.Subject), b.(*rbacv1.Subject), scope) - }); err != nil { - return err - } - return nil -} - -func autoConvert_v1_AggregationRule_To_rbac_AggregationRule(in *rbacv1.AggregationRule, out *rbac.AggregationRule, s conversion.Scope) error { - out.ClusterRoleSelectors = *(*[]metav1.LabelSelector)(unsafe.Pointer(&in.ClusterRoleSelectors)) - return nil -} - -// Convert_v1_AggregationRule_To_rbac_AggregationRule is an autogenerated conversion function. -func Convert_v1_AggregationRule_To_rbac_AggregationRule(in *rbacv1.AggregationRule, out *rbac.AggregationRule, s conversion.Scope) error { - return autoConvert_v1_AggregationRule_To_rbac_AggregationRule(in, out, s) -} - -func autoConvert_rbac_AggregationRule_To_v1_AggregationRule(in *rbac.AggregationRule, out *rbacv1.AggregationRule, s conversion.Scope) error { - out.ClusterRoleSelectors = *(*[]metav1.LabelSelector)(unsafe.Pointer(&in.ClusterRoleSelectors)) - return nil -} - -// Convert_rbac_AggregationRule_To_v1_AggregationRule is an autogenerated conversion function. -func Convert_rbac_AggregationRule_To_v1_AggregationRule(in *rbac.AggregationRule, out *rbacv1.AggregationRule, s conversion.Scope) error { - return autoConvert_rbac_AggregationRule_To_v1_AggregationRule(in, out, s) -} - -func autoConvert_v1_ClusterRole_To_rbac_ClusterRole(in *rbacv1.ClusterRole, out *rbac.ClusterRole, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - out.Rules = *(*[]rbac.PolicyRule)(unsafe.Pointer(&in.Rules)) - out.AggregationRule = (*rbac.AggregationRule)(unsafe.Pointer(in.AggregationRule)) - return nil -} - -// Convert_v1_ClusterRole_To_rbac_ClusterRole is an autogenerated conversion function. -func Convert_v1_ClusterRole_To_rbac_ClusterRole(in *rbacv1.ClusterRole, out *rbac.ClusterRole, s conversion.Scope) error { - return autoConvert_v1_ClusterRole_To_rbac_ClusterRole(in, out, s) -} - -func autoConvert_rbac_ClusterRole_To_v1_ClusterRole(in *rbac.ClusterRole, out *rbacv1.ClusterRole, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - out.Rules = *(*[]rbacv1.PolicyRule)(unsafe.Pointer(&in.Rules)) - out.AggregationRule = (*rbacv1.AggregationRule)(unsafe.Pointer(in.AggregationRule)) - return nil -} - -// Convert_rbac_ClusterRole_To_v1_ClusterRole is an autogenerated conversion function. -func Convert_rbac_ClusterRole_To_v1_ClusterRole(in *rbac.ClusterRole, out *rbacv1.ClusterRole, s conversion.Scope) error { - return autoConvert_rbac_ClusterRole_To_v1_ClusterRole(in, out, s) -} - -func autoConvert_v1_ClusterRoleBinding_To_rbac_ClusterRoleBinding(in *rbacv1.ClusterRoleBinding, out *rbac.ClusterRoleBinding, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - out.Subjects = *(*[]rbac.Subject)(unsafe.Pointer(&in.Subjects)) - if err := Convert_v1_RoleRef_To_rbac_RoleRef(&in.RoleRef, &out.RoleRef, s); err != nil { - return err - } - return nil -} - -// Convert_v1_ClusterRoleBinding_To_rbac_ClusterRoleBinding is an autogenerated conversion function. -func Convert_v1_ClusterRoleBinding_To_rbac_ClusterRoleBinding(in *rbacv1.ClusterRoleBinding, out *rbac.ClusterRoleBinding, s conversion.Scope) error { - return autoConvert_v1_ClusterRoleBinding_To_rbac_ClusterRoleBinding(in, out, s) -} - -func autoConvert_rbac_ClusterRoleBinding_To_v1_ClusterRoleBinding(in *rbac.ClusterRoleBinding, out *rbacv1.ClusterRoleBinding, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - out.Subjects = *(*[]rbacv1.Subject)(unsafe.Pointer(&in.Subjects)) - if err := Convert_rbac_RoleRef_To_v1_RoleRef(&in.RoleRef, &out.RoleRef, s); err != nil { - return err - } - return nil -} - -// Convert_rbac_ClusterRoleBinding_To_v1_ClusterRoleBinding is an autogenerated conversion function. -func Convert_rbac_ClusterRoleBinding_To_v1_ClusterRoleBinding(in *rbac.ClusterRoleBinding, out *rbacv1.ClusterRoleBinding, s conversion.Scope) error { - return autoConvert_rbac_ClusterRoleBinding_To_v1_ClusterRoleBinding(in, out, s) -} - -func autoConvert_v1_ClusterRoleBindingList_To_rbac_ClusterRoleBindingList(in *rbacv1.ClusterRoleBindingList, out *rbac.ClusterRoleBindingList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - out.Items = *(*[]rbac.ClusterRoleBinding)(unsafe.Pointer(&in.Items)) - return nil -} - -// Convert_v1_ClusterRoleBindingList_To_rbac_ClusterRoleBindingList is an autogenerated conversion function. -func Convert_v1_ClusterRoleBindingList_To_rbac_ClusterRoleBindingList(in *rbacv1.ClusterRoleBindingList, out *rbac.ClusterRoleBindingList, s conversion.Scope) error { - return autoConvert_v1_ClusterRoleBindingList_To_rbac_ClusterRoleBindingList(in, out, s) -} - -func autoConvert_rbac_ClusterRoleBindingList_To_v1_ClusterRoleBindingList(in *rbac.ClusterRoleBindingList, out *rbacv1.ClusterRoleBindingList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - out.Items = *(*[]rbacv1.ClusterRoleBinding)(unsafe.Pointer(&in.Items)) - return nil -} - -// Convert_rbac_ClusterRoleBindingList_To_v1_ClusterRoleBindingList is an autogenerated conversion function. -func Convert_rbac_ClusterRoleBindingList_To_v1_ClusterRoleBindingList(in *rbac.ClusterRoleBindingList, out *rbacv1.ClusterRoleBindingList, s conversion.Scope) error { - return autoConvert_rbac_ClusterRoleBindingList_To_v1_ClusterRoleBindingList(in, out, s) -} - -func autoConvert_v1_ClusterRoleList_To_rbac_ClusterRoleList(in *rbacv1.ClusterRoleList, out *rbac.ClusterRoleList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - out.Items = *(*[]rbac.ClusterRole)(unsafe.Pointer(&in.Items)) - return nil -} - -// Convert_v1_ClusterRoleList_To_rbac_ClusterRoleList is an autogenerated conversion function. -func Convert_v1_ClusterRoleList_To_rbac_ClusterRoleList(in *rbacv1.ClusterRoleList, out *rbac.ClusterRoleList, s conversion.Scope) error { - return autoConvert_v1_ClusterRoleList_To_rbac_ClusterRoleList(in, out, s) -} - -func autoConvert_rbac_ClusterRoleList_To_v1_ClusterRoleList(in *rbac.ClusterRoleList, out *rbacv1.ClusterRoleList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - out.Items = *(*[]rbacv1.ClusterRole)(unsafe.Pointer(&in.Items)) - return nil -} - -// Convert_rbac_ClusterRoleList_To_v1_ClusterRoleList is an autogenerated conversion function. -func Convert_rbac_ClusterRoleList_To_v1_ClusterRoleList(in *rbac.ClusterRoleList, out *rbacv1.ClusterRoleList, s conversion.Scope) error { - return autoConvert_rbac_ClusterRoleList_To_v1_ClusterRoleList(in, out, s) -} - -func autoConvert_v1_PolicyRule_To_rbac_PolicyRule(in *rbacv1.PolicyRule, out *rbac.PolicyRule, s conversion.Scope) error { - out.Verbs = *(*[]string)(unsafe.Pointer(&in.Verbs)) - out.APIGroups = *(*[]string)(unsafe.Pointer(&in.APIGroups)) - out.Resources = *(*[]string)(unsafe.Pointer(&in.Resources)) - out.ResourceNames = *(*[]string)(unsafe.Pointer(&in.ResourceNames)) - out.NonResourceURLs = *(*[]string)(unsafe.Pointer(&in.NonResourceURLs)) - return nil -} - -// Convert_v1_PolicyRule_To_rbac_PolicyRule is an autogenerated conversion function. -func Convert_v1_PolicyRule_To_rbac_PolicyRule(in *rbacv1.PolicyRule, out *rbac.PolicyRule, s conversion.Scope) error { - return autoConvert_v1_PolicyRule_To_rbac_PolicyRule(in, out, s) -} - -func autoConvert_rbac_PolicyRule_To_v1_PolicyRule(in *rbac.PolicyRule, out *rbacv1.PolicyRule, s conversion.Scope) error { - out.Verbs = *(*[]string)(unsafe.Pointer(&in.Verbs)) - out.APIGroups = *(*[]string)(unsafe.Pointer(&in.APIGroups)) - out.Resources = *(*[]string)(unsafe.Pointer(&in.Resources)) - out.ResourceNames = *(*[]string)(unsafe.Pointer(&in.ResourceNames)) - out.NonResourceURLs = *(*[]string)(unsafe.Pointer(&in.NonResourceURLs)) - return nil -} - -// Convert_rbac_PolicyRule_To_v1_PolicyRule is an autogenerated conversion function. -func Convert_rbac_PolicyRule_To_v1_PolicyRule(in *rbac.PolicyRule, out *rbacv1.PolicyRule, s conversion.Scope) error { - return autoConvert_rbac_PolicyRule_To_v1_PolicyRule(in, out, s) -} - -func autoConvert_v1_Role_To_rbac_Role(in *rbacv1.Role, out *rbac.Role, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - out.Rules = *(*[]rbac.PolicyRule)(unsafe.Pointer(&in.Rules)) - return nil -} - -// Convert_v1_Role_To_rbac_Role is an autogenerated conversion function. -func Convert_v1_Role_To_rbac_Role(in *rbacv1.Role, out *rbac.Role, s conversion.Scope) error { - return autoConvert_v1_Role_To_rbac_Role(in, out, s) -} - -func autoConvert_rbac_Role_To_v1_Role(in *rbac.Role, out *rbacv1.Role, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - out.Rules = *(*[]rbacv1.PolicyRule)(unsafe.Pointer(&in.Rules)) - return nil -} - -// Convert_rbac_Role_To_v1_Role is an autogenerated conversion function. -func Convert_rbac_Role_To_v1_Role(in *rbac.Role, out *rbacv1.Role, s conversion.Scope) error { - return autoConvert_rbac_Role_To_v1_Role(in, out, s) -} - -func autoConvert_v1_RoleBinding_To_rbac_RoleBinding(in *rbacv1.RoleBinding, out *rbac.RoleBinding, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - out.Subjects = *(*[]rbac.Subject)(unsafe.Pointer(&in.Subjects)) - if err := Convert_v1_RoleRef_To_rbac_RoleRef(&in.RoleRef, &out.RoleRef, s); err != nil { - return err - } - return nil -} - -// Convert_v1_RoleBinding_To_rbac_RoleBinding is an autogenerated conversion function. -func Convert_v1_RoleBinding_To_rbac_RoleBinding(in *rbacv1.RoleBinding, out *rbac.RoleBinding, s conversion.Scope) error { - return autoConvert_v1_RoleBinding_To_rbac_RoleBinding(in, out, s) -} - -func autoConvert_rbac_RoleBinding_To_v1_RoleBinding(in *rbac.RoleBinding, out *rbacv1.RoleBinding, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - out.Subjects = *(*[]rbacv1.Subject)(unsafe.Pointer(&in.Subjects)) - if err := Convert_rbac_RoleRef_To_v1_RoleRef(&in.RoleRef, &out.RoleRef, s); err != nil { - return err - } - return nil -} - -// Convert_rbac_RoleBinding_To_v1_RoleBinding is an autogenerated conversion function. -func Convert_rbac_RoleBinding_To_v1_RoleBinding(in *rbac.RoleBinding, out *rbacv1.RoleBinding, s conversion.Scope) error { - return autoConvert_rbac_RoleBinding_To_v1_RoleBinding(in, out, s) -} - -func autoConvert_v1_RoleBindingList_To_rbac_RoleBindingList(in *rbacv1.RoleBindingList, out *rbac.RoleBindingList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - out.Items = *(*[]rbac.RoleBinding)(unsafe.Pointer(&in.Items)) - return nil -} - -// Convert_v1_RoleBindingList_To_rbac_RoleBindingList is an autogenerated conversion function. -func Convert_v1_RoleBindingList_To_rbac_RoleBindingList(in *rbacv1.RoleBindingList, out *rbac.RoleBindingList, s conversion.Scope) error { - return autoConvert_v1_RoleBindingList_To_rbac_RoleBindingList(in, out, s) -} - -func autoConvert_rbac_RoleBindingList_To_v1_RoleBindingList(in *rbac.RoleBindingList, out *rbacv1.RoleBindingList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - out.Items = *(*[]rbacv1.RoleBinding)(unsafe.Pointer(&in.Items)) - return nil -} - -// Convert_rbac_RoleBindingList_To_v1_RoleBindingList is an autogenerated conversion function. -func Convert_rbac_RoleBindingList_To_v1_RoleBindingList(in *rbac.RoleBindingList, out *rbacv1.RoleBindingList, s conversion.Scope) error { - return autoConvert_rbac_RoleBindingList_To_v1_RoleBindingList(in, out, s) -} - -func autoConvert_v1_RoleList_To_rbac_RoleList(in *rbacv1.RoleList, out *rbac.RoleList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - out.Items = *(*[]rbac.Role)(unsafe.Pointer(&in.Items)) - return nil -} - -// Convert_v1_RoleList_To_rbac_RoleList is an autogenerated conversion function. -func Convert_v1_RoleList_To_rbac_RoleList(in *rbacv1.RoleList, out *rbac.RoleList, s conversion.Scope) error { - return autoConvert_v1_RoleList_To_rbac_RoleList(in, out, s) -} - -func autoConvert_rbac_RoleList_To_v1_RoleList(in *rbac.RoleList, out *rbacv1.RoleList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - out.Items = *(*[]rbacv1.Role)(unsafe.Pointer(&in.Items)) - return nil -} - -// Convert_rbac_RoleList_To_v1_RoleList is an autogenerated conversion function. -func Convert_rbac_RoleList_To_v1_RoleList(in *rbac.RoleList, out *rbacv1.RoleList, s conversion.Scope) error { - return autoConvert_rbac_RoleList_To_v1_RoleList(in, out, s) -} - -func autoConvert_v1_RoleRef_To_rbac_RoleRef(in *rbacv1.RoleRef, out *rbac.RoleRef, s conversion.Scope) error { - out.APIGroup = in.APIGroup - out.Kind = in.Kind - out.Name = in.Name - return nil -} - -// Convert_v1_RoleRef_To_rbac_RoleRef is an autogenerated conversion function. -func Convert_v1_RoleRef_To_rbac_RoleRef(in *rbacv1.RoleRef, out *rbac.RoleRef, s conversion.Scope) error { - return autoConvert_v1_RoleRef_To_rbac_RoleRef(in, out, s) -} - -func autoConvert_rbac_RoleRef_To_v1_RoleRef(in *rbac.RoleRef, out *rbacv1.RoleRef, s conversion.Scope) error { - out.APIGroup = in.APIGroup - out.Kind = in.Kind - out.Name = in.Name - return nil -} - -// Convert_rbac_RoleRef_To_v1_RoleRef is an autogenerated conversion function. -func Convert_rbac_RoleRef_To_v1_RoleRef(in *rbac.RoleRef, out *rbacv1.RoleRef, s conversion.Scope) error { - return autoConvert_rbac_RoleRef_To_v1_RoleRef(in, out, s) -} - -func autoConvert_v1_Subject_To_rbac_Subject(in *rbacv1.Subject, out *rbac.Subject, s conversion.Scope) error { - out.Kind = in.Kind - out.APIGroup = in.APIGroup - out.Name = in.Name - out.Namespace = in.Namespace - return nil -} - -// Convert_v1_Subject_To_rbac_Subject is an autogenerated conversion function. -func Convert_v1_Subject_To_rbac_Subject(in *rbacv1.Subject, out *rbac.Subject, s conversion.Scope) error { - return autoConvert_v1_Subject_To_rbac_Subject(in, out, s) -} - -func autoConvert_rbac_Subject_To_v1_Subject(in *rbac.Subject, out *rbacv1.Subject, s conversion.Scope) error { - out.Kind = in.Kind - out.APIGroup = in.APIGroup - out.Name = in.Name - out.Namespace = in.Namespace - return nil -} - -// Convert_rbac_Subject_To_v1_Subject is an autogenerated conversion function. -func Convert_rbac_Subject_To_v1_Subject(in *rbac.Subject, out *rbacv1.Subject, s conversion.Scope) error { - return autoConvert_rbac_Subject_To_v1_Subject(in, out, s) -} diff --git a/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1/zz_generated.defaults.go b/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1/zz_generated.defaults.go deleted file mode 100644 index 734e76f3e..000000000 --- a/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1/zz_generated.defaults.go +++ /dev/null @@ -1,68 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -/* -Copyright The Kubernetes Authors. - -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 defaulter-gen. DO NOT EDIT. - -package v1 - -import ( - rbacv1 "k8s.io/api/rbac/v1" - runtime "k8s.io/apimachinery/pkg/runtime" -) - -// RegisterDefaults adds defaulters functions to the given scheme. -// Public to allow building arbitrary schemes. -// All generated defaulters are covering - they call all nested defaulters. -func RegisterDefaults(scheme *runtime.Scheme) error { - scheme.AddTypeDefaultingFunc(&rbacv1.ClusterRoleBinding{}, func(obj interface{}) { SetObjectDefaults_ClusterRoleBinding(obj.(*rbacv1.ClusterRoleBinding)) }) - scheme.AddTypeDefaultingFunc(&rbacv1.ClusterRoleBindingList{}, func(obj interface{}) { SetObjectDefaults_ClusterRoleBindingList(obj.(*rbacv1.ClusterRoleBindingList)) }) - scheme.AddTypeDefaultingFunc(&rbacv1.RoleBinding{}, func(obj interface{}) { SetObjectDefaults_RoleBinding(obj.(*rbacv1.RoleBinding)) }) - scheme.AddTypeDefaultingFunc(&rbacv1.RoleBindingList{}, func(obj interface{}) { SetObjectDefaults_RoleBindingList(obj.(*rbacv1.RoleBindingList)) }) - return nil -} - -func SetObjectDefaults_ClusterRoleBinding(in *rbacv1.ClusterRoleBinding) { - SetDefaults_ClusterRoleBinding(in) - for i := range in.Subjects { - a := &in.Subjects[i] - SetDefaults_Subject(a) - } -} - -func SetObjectDefaults_ClusterRoleBindingList(in *rbacv1.ClusterRoleBindingList) { - for i := range in.Items { - a := &in.Items[i] - SetObjectDefaults_ClusterRoleBinding(a) - } -} - -func SetObjectDefaults_RoleBinding(in *rbacv1.RoleBinding) { - SetDefaults_RoleBinding(in) - for i := range in.Subjects { - a := &in.Subjects[i] - SetDefaults_Subject(a) - } -} - -func SetObjectDefaults_RoleBindingList(in *rbacv1.RoleBindingList) { - for i := range in.Items { - a := &in.Items[i] - SetObjectDefaults_RoleBinding(a) - } -} diff --git a/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/zz_generated.deepcopy.go b/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/zz_generated.deepcopy.go deleted file mode 100644 index 0f7023a2d..000000000 --- a/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/zz_generated.deepcopy.go +++ /dev/null @@ -1,412 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -/* -Copyright The Kubernetes Authors. - -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 deepcopy-gen. DO NOT EDIT. - -package rbac - -import ( - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - runtime "k8s.io/apimachinery/pkg/runtime" -) - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AggregationRule) DeepCopyInto(out *AggregationRule) { - *out = *in - if in.ClusterRoleSelectors != nil { - in, out := &in.ClusterRoleSelectors, &out.ClusterRoleSelectors - *out = make([]v1.LabelSelector, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AggregationRule. -func (in *AggregationRule) DeepCopy() *AggregationRule { - if in == nil { - return nil - } - out := new(AggregationRule) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ClusterRole) DeepCopyInto(out *ClusterRole) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - if in.Rules != nil { - in, out := &in.Rules, &out.Rules - *out = make([]PolicyRule, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.AggregationRule != nil { - in, out := &in.AggregationRule, &out.AggregationRule - *out = new(AggregationRule) - (*in).DeepCopyInto(*out) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterRole. -func (in *ClusterRole) DeepCopy() *ClusterRole { - if in == nil { - return nil - } - out := new(ClusterRole) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ClusterRole) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ClusterRoleBinding) DeepCopyInto(out *ClusterRoleBinding) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - if in.Subjects != nil { - in, out := &in.Subjects, &out.Subjects - *out = make([]Subject, len(*in)) - copy(*out, *in) - } - out.RoleRef = in.RoleRef - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterRoleBinding. -func (in *ClusterRoleBinding) DeepCopy() *ClusterRoleBinding { - if in == nil { - return nil - } - out := new(ClusterRoleBinding) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ClusterRoleBinding) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ClusterRoleBindingList) DeepCopyInto(out *ClusterRoleBindingList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]ClusterRoleBinding, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterRoleBindingList. -func (in *ClusterRoleBindingList) DeepCopy() *ClusterRoleBindingList { - if in == nil { - return nil - } - out := new(ClusterRoleBindingList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ClusterRoleBindingList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ClusterRoleList) DeepCopyInto(out *ClusterRoleList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]ClusterRole, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterRoleList. -func (in *ClusterRoleList) DeepCopy() *ClusterRoleList { - if in == nil { - return nil - } - out := new(ClusterRoleList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ClusterRoleList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PolicyRule) DeepCopyInto(out *PolicyRule) { - *out = *in - if in.Verbs != nil { - in, out := &in.Verbs, &out.Verbs - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.APIGroups != nil { - in, out := &in.APIGroups, &out.APIGroups - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.Resources != nil { - in, out := &in.Resources, &out.Resources - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.ResourceNames != nil { - in, out := &in.ResourceNames, &out.ResourceNames - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.NonResourceURLs != nil { - in, out := &in.NonResourceURLs, &out.NonResourceURLs - *out = make([]string, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PolicyRule. -func (in *PolicyRule) DeepCopy() *PolicyRule { - if in == nil { - return nil - } - out := new(PolicyRule) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Role) DeepCopyInto(out *Role) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - if in.Rules != nil { - in, out := &in.Rules, &out.Rules - *out = make([]PolicyRule, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Role. -func (in *Role) DeepCopy() *Role { - if in == nil { - return nil - } - out := new(Role) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *Role) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RoleBinding) DeepCopyInto(out *RoleBinding) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - if in.Subjects != nil { - in, out := &in.Subjects, &out.Subjects - *out = make([]Subject, len(*in)) - copy(*out, *in) - } - out.RoleRef = in.RoleRef - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RoleBinding. -func (in *RoleBinding) DeepCopy() *RoleBinding { - if in == nil { - return nil - } - out := new(RoleBinding) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *RoleBinding) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RoleBindingList) DeepCopyInto(out *RoleBindingList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]RoleBinding, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RoleBindingList. -func (in *RoleBindingList) DeepCopy() *RoleBindingList { - if in == nil { - return nil - } - out := new(RoleBindingList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *RoleBindingList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RoleList) DeepCopyInto(out *RoleList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]Role, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RoleList. -func (in *RoleList) DeepCopy() *RoleList { - if in == nil { - return nil - } - out := new(RoleList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *RoleList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RoleRef) DeepCopyInto(out *RoleRef) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RoleRef. -func (in *RoleRef) DeepCopy() *RoleRef { - if in == nil { - return nil - } - out := new(RoleRef) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in SortableRuleSlice) DeepCopyInto(out *SortableRuleSlice) { - { - in := &in - *out = make(SortableRuleSlice, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - return - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SortableRuleSlice. -func (in SortableRuleSlice) DeepCopy() SortableRuleSlice { - if in == nil { - return nil - } - out := new(SortableRuleSlice) - in.DeepCopyInto(out) - return *out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Subject) DeepCopyInto(out *Subject) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Subject. -func (in *Subject) DeepCopy() *Subject { - if in == nil { - return nil - } - out := new(Subject) - in.DeepCopyInto(out) - return out -} diff --git a/internal/operator-controller/authorization/internal/kubernetes/pkg/registry/rbac/escalation_check.go b/internal/operator-controller/authorization/internal/kubernetes/pkg/registry/rbac/escalation_check.go deleted file mode 100644 index 1534f43cc..000000000 --- a/internal/operator-controller/authorization/internal/kubernetes/pkg/registry/rbac/escalation_check.go +++ /dev/null @@ -1,146 +0,0 @@ -/* -Copyright 2016 The Kubernetes Authors. - -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. -*/ - -package rbac - -import ( - "context" - "fmt" - - "github.com/operator-framework/operator-controller/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac" - "k8s.io/apimachinery/pkg/runtime/schema" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" - "k8s.io/apiserver/pkg/authentication/user" - "k8s.io/apiserver/pkg/authorization/authorizer" - genericapirequest "k8s.io/apiserver/pkg/endpoints/request" -) - -// EscalationAllowed checks if the user associated with the context is a superuser -func EscalationAllowed(ctx context.Context) bool { - u, ok := genericapirequest.UserFrom(ctx) - if !ok { - return false - } - - // system:masters is special because the API server uses it for privileged loopback connections - // therefore we know that a member of system:masters can always do anything - for _, group := range u.GetGroups() { - if group == user.SystemPrivilegedGroup { - return true - } - } - - return false -} - -var roleResources = map[schema.GroupResource]bool{ - rbac.SchemeGroupVersion.WithResource("clusterroles").GroupResource(): true, - rbac.SchemeGroupVersion.WithResource("roles").GroupResource(): true, -} - -// RoleEscalationAuthorized checks if the user associated with the context is explicitly authorized to escalate the role resource associated with the context -func RoleEscalationAuthorized(ctx context.Context, a authorizer.Authorizer) bool { - if a == nil { - return false - } - - user, ok := genericapirequest.UserFrom(ctx) - if !ok { - return false - } - - requestInfo, ok := genericapirequest.RequestInfoFrom(ctx) - if !ok { - return false - } - - if !requestInfo.IsResourceRequest { - return false - } - - requestResource := schema.GroupResource{Group: requestInfo.APIGroup, Resource: requestInfo.Resource} - if !roleResources[requestResource] { - return false - } - - attrs := authorizer.AttributesRecord{ - User: user, - Verb: "escalate", - APIGroup: requestInfo.APIGroup, - APIVersion: "*", - Resource: requestInfo.Resource, - Name: requestInfo.Name, - Namespace: requestInfo.Namespace, - ResourceRequest: true, - } - - decision, _, err := a.Authorize(ctx, attrs) - if err != nil { - utilruntime.HandleError(fmt.Errorf( - "error authorizing user %#v to escalate %#v named %q in namespace %q: %v", - user, requestResource, requestInfo.Name, requestInfo.Namespace, err, - )) - } - return decision == authorizer.DecisionAllow -} - -// BindingAuthorized returns true if the user associated with the context is explicitly authorized to bind the specified roleRef -func BindingAuthorized(ctx context.Context, roleRef rbac.RoleRef, bindingNamespace string, a authorizer.Authorizer) bool { - if a == nil { - return false - } - - user, ok := genericapirequest.UserFrom(ctx) - if !ok { - return false - } - - attrs := authorizer.AttributesRecord{ - User: user, - Verb: "bind", - // check against the namespace where the binding is being created (or the empty namespace for clusterrolebindings). - // this allows delegation to bind particular clusterroles in rolebindings within particular namespaces, - // and to authorize binding a clusterrole across all namespaces in a clusterrolebinding. - Namespace: bindingNamespace, - ResourceRequest: true, - } - - // This occurs after defaulting and conversion, so values pulled from the roleRef won't change - // Invalid APIGroup or Name values will fail validation - switch roleRef.Kind { - case "ClusterRole": - attrs.APIGroup = roleRef.APIGroup - attrs.APIVersion = "*" - attrs.Resource = "clusterroles" - attrs.Name = roleRef.Name - case "Role": - attrs.APIGroup = roleRef.APIGroup - attrs.APIVersion = "*" - attrs.Resource = "roles" - attrs.Name = roleRef.Name - default: - return false - } - - decision, _, err := a.Authorize(ctx, attrs) - if err != nil { - utilruntime.HandleError(fmt.Errorf( - "error authorizing user %#v to bind %#v in namespace %s: %v", - user, roleRef, bindingNamespace, err, - )) - } - return decision == authorizer.DecisionAllow -} diff --git a/internal/operator-controller/authorization/internal/kubernetes/pkg/registry/rbac/validation/policy_compact.go b/internal/operator-controller/authorization/internal/kubernetes/pkg/registry/rbac/validation/policy_compact.go deleted file mode 100644 index 60adcf9e8..000000000 --- a/internal/operator-controller/authorization/internal/kubernetes/pkg/registry/rbac/validation/policy_compact.go +++ /dev/null @@ -1,96 +0,0 @@ -/* -Copyright 2017 The Kubernetes Authors. - -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. -*/ - -package validation - -import ( - "reflect" - - rbacv1 "k8s.io/api/rbac/v1" - "k8s.io/apimachinery/pkg/util/sets" -) - -type simpleResource struct { - Group string - Resource string - ResourceNameExist bool - ResourceName string -} - -// CompactRules combines rules that contain a single APIGroup/Resource, differ only by verb, and contain no other attributes. -// this is a fast check, and works well with the decomposed "missing rules" list from a Covers check. -func CompactRules(rules []rbacv1.PolicyRule) ([]rbacv1.PolicyRule, error) { - compacted := make([]rbacv1.PolicyRule, 0, len(rules)) - - simpleRules := map[simpleResource]*rbacv1.PolicyRule{} - for _, rule := range rules { - if resource, isSimple := isSimpleResourceRule(&rule); isSimple { - if existingRule, ok := simpleRules[resource]; ok { - // Add the new verbs to the existing simple resource rule - if existingRule.Verbs == nil { - existingRule.Verbs = []string{} - } - existingRule.Verbs = append(existingRule.Verbs, rule.Verbs...) - } else { - // Copy the rule to accumulate matching simple resource rules into - simpleRules[resource] = rule.DeepCopy() - } - } else { - compacted = append(compacted, rule) - } - } - - // Once we've consolidated the simple resource rules, add them to the compacted list - for _, simpleRule := range simpleRules { - verbSet := sets.New[string](simpleRule.Verbs...) - if verbSet.Has("*") { - simpleRule.Verbs = []string{"*"} - } else { - simpleRule.Verbs = sets.List(verbSet) - } - compacted = append(compacted, *simpleRule) - } - - return compacted, nil -} - -// isSimpleResourceRule returns true if the given rule contains verbs, a single resource, a single API group, at most one Resource Name, and no other values -func isSimpleResourceRule(rule *rbacv1.PolicyRule) (simpleResource, bool) { - resource := simpleResource{} - - // If we have "complex" rule attributes, return early without allocations or expensive comparisons - if len(rule.ResourceNames) > 1 || len(rule.NonResourceURLs) > 0 { - return resource, false - } - // If we have multiple api groups or resources, return early - if len(rule.APIGroups) != 1 || len(rule.Resources) != 1 { - return resource, false - } - - // Test if this rule only contains APIGroups/Resources/Verbs/ResourceNames - simpleRule := &rbacv1.PolicyRule{APIGroups: rule.APIGroups, Resources: rule.Resources, Verbs: rule.Verbs, ResourceNames: rule.ResourceNames} - if !reflect.DeepEqual(simpleRule, rule) { - return resource, false - } - - if len(rule.ResourceNames) == 0 { - resource = simpleResource{Group: rule.APIGroups[0], Resource: rule.Resources[0], ResourceNameExist: false} - } else { - resource = simpleResource{Group: rule.APIGroups[0], Resource: rule.Resources[0], ResourceNameExist: true, ResourceName: rule.ResourceNames[0]} - } - - return resource, true -} diff --git a/internal/operator-controller/authorization/internal/kubernetes/pkg/registry/rbac/validation/rule.go b/internal/operator-controller/authorization/internal/kubernetes/pkg/registry/rbac/validation/rule.go deleted file mode 100644 index ad79998fd..000000000 --- a/internal/operator-controller/authorization/internal/kubernetes/pkg/registry/rbac/validation/rule.go +++ /dev/null @@ -1,384 +0,0 @@ -/* -Copyright 2016 The Kubernetes Authors. - -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. -*/ - -package validation - -import ( - "context" - "errors" - "fmt" - "strings" - - rbacv1 "k8s.io/api/rbac/v1" - utilerrors "k8s.io/apimachinery/pkg/util/errors" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/apiserver/pkg/authentication/serviceaccount" - "k8s.io/apiserver/pkg/authentication/user" - genericapirequest "k8s.io/apiserver/pkg/endpoints/request" - "k8s.io/component-helpers/auth/rbac/validation" - "k8s.io/klog/v2" - - rbacv1helpers "github.com/operator-framework/operator-controller/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1" -) - -type AuthorizationRuleResolver interface { - // GetRoleReferenceRules attempts to resolve the role reference of a RoleBinding or ClusterRoleBinding. The passed namespace should be the namespace - // of the role binding, the empty string if a cluster role binding. - GetRoleReferenceRules(ctx context.Context, roleRef rbacv1.RoleRef, namespace string) ([]rbacv1.PolicyRule, error) - - // RulesFor returns the list of rules that apply to a given user in a given namespace and error. If an error is returned, the slice of - // PolicyRules may not be complete, but it contains all retrievable rules. This is done because policy rules are purely additive and policy determinations - // can be made on the basis of those rules that are found. - RulesFor(ctx context.Context, user user.Info, namespace string) ([]rbacv1.PolicyRule, error) - - // VisitRulesFor invokes visitor() with each rule that applies to a given user in a given namespace, and each error encountered resolving those rules. - // If visitor() returns false, visiting is short-circuited. - VisitRulesFor(ctx context.Context, user user.Info, namespace string, visitor func(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool) -} - -type PrivilegeEscalationError struct { - User user.Info - Namespace string - MissingRules []rbacv1.PolicyRule - RuleResolutionErrors []error -} - -func (e *PrivilegeEscalationError) Error() string { - missingDescriptions := sets.NewString() - for _, missing := range e.MissingRules { - missingDescriptions.Insert(rbacv1helpers.CompactString(missing)) - } - - msg := fmt.Sprintf("user %q (groups=%q) is attempting to grant RBAC permissions not currently held:\n%s", e.User.GetName(), e.User.GetGroups(), strings.Join(missingDescriptions.List(), "\n")) - if len(e.RuleResolutionErrors) > 0 { - msg = msg + fmt.Sprintf("; resolution errors: %v", e.RuleResolutionErrors) - } - - return msg -} - -// ConfirmNoEscalation determines if the roles for a given user in a given namespace encompass the provided role. -func ConfirmNoEscalation(ctx context.Context, ruleResolver AuthorizationRuleResolver, rules []rbacv1.PolicyRule) error { - ruleResolutionErrors := []error{} - - user, ok := genericapirequest.UserFrom(ctx) - if !ok { - return fmt.Errorf("no user on context") - } - namespace, _ := genericapirequest.NamespaceFrom(ctx) - - ownerRules, err := ruleResolver.RulesFor(ctx, user, namespace) - if err != nil { - // As per AuthorizationRuleResolver contract, this may return a non fatal error with an incomplete list of policies. Log the error and continue. - klog.V(1).Infof("non-fatal error getting local rules for %v: %v", user, err) - ruleResolutionErrors = append(ruleResolutionErrors, err) - } - - ownerRightsCover, missingRights := validation.Covers(ownerRules, rules) - if !ownerRightsCover { - compactMissingRights := missingRights - if compact, err := CompactRules(missingRights); err == nil { - compactMissingRights = compact - } - - return &PrivilegeEscalationError{ - User: user, - Namespace: namespace, - MissingRules: compactMissingRights, - RuleResolutionErrors: ruleResolutionErrors, - } - } - return nil -} - -type DefaultRuleResolver struct { - roleGetter RoleGetter - roleBindingLister RoleBindingLister - clusterRoleGetter ClusterRoleGetter - clusterRoleBindingLister ClusterRoleBindingLister -} - -func NewDefaultRuleResolver(roleGetter RoleGetter, roleBindingLister RoleBindingLister, clusterRoleGetter ClusterRoleGetter, clusterRoleBindingLister ClusterRoleBindingLister) *DefaultRuleResolver { - return &DefaultRuleResolver{roleGetter, roleBindingLister, clusterRoleGetter, clusterRoleBindingLister} -} - -type RoleGetter interface { - GetRole(ctx context.Context, namespace, name string) (*rbacv1.Role, error) -} - -type RoleBindingLister interface { - ListRoleBindings(ctx context.Context, namespace string) ([]*rbacv1.RoleBinding, error) -} - -type ClusterRoleGetter interface { - GetClusterRole(ctx context.Context, name string) (*rbacv1.ClusterRole, error) -} - -type ClusterRoleBindingLister interface { - ListClusterRoleBindings(ctx context.Context) ([]*rbacv1.ClusterRoleBinding, error) -} - -func (r *DefaultRuleResolver) RulesFor(ctx context.Context, user user.Info, namespace string) ([]rbacv1.PolicyRule, error) { - visitor := &ruleAccumulator{} - r.VisitRulesFor(ctx, user, namespace, visitor.visit) - return visitor.rules, utilerrors.NewAggregate(visitor.errors) -} - -type ruleAccumulator struct { - rules []rbacv1.PolicyRule - errors []error -} - -func (r *ruleAccumulator) visit(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool { - if rule != nil { - r.rules = append(r.rules, *rule) - } - if err != nil { - r.errors = append(r.errors, err) - } - return true -} - -func describeSubject(s *rbacv1.Subject, bindingNamespace string) string { - switch s.Kind { - case rbacv1.ServiceAccountKind: - if len(s.Namespace) > 0 { - return fmt.Sprintf("%s %q", s.Kind, s.Name+"/"+s.Namespace) - } - return fmt.Sprintf("%s %q", s.Kind, s.Name+"/"+bindingNamespace) - default: - return fmt.Sprintf("%s %q", s.Kind, s.Name) - } -} - -type clusterRoleBindingDescriber struct { - binding *rbacv1.ClusterRoleBinding - subject *rbacv1.Subject -} - -func (d *clusterRoleBindingDescriber) String() string { - return fmt.Sprintf("ClusterRoleBinding %q of %s %q to %s", - d.binding.Name, - d.binding.RoleRef.Kind, - d.binding.RoleRef.Name, - describeSubject(d.subject, ""), - ) -} - -type roleBindingDescriber struct { - binding *rbacv1.RoleBinding - subject *rbacv1.Subject -} - -func (d *roleBindingDescriber) String() string { - return fmt.Sprintf("RoleBinding %q of %s %q to %s", - d.binding.Name+"/"+d.binding.Namespace, - d.binding.RoleRef.Kind, - d.binding.RoleRef.Name, - describeSubject(d.subject, d.binding.Namespace), - ) -} - -func (r *DefaultRuleResolver) VisitRulesFor(ctx context.Context, user user.Info, namespace string, visitor func(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool) { - if clusterRoleBindings, err := r.clusterRoleBindingLister.ListClusterRoleBindings(ctx); err != nil { - if !visitor(nil, nil, err) { - return - } - } else { - sourceDescriber := &clusterRoleBindingDescriber{} - for _, clusterRoleBinding := range clusterRoleBindings { - subjectIndex, applies := appliesTo(user, clusterRoleBinding.Subjects, "") - if !applies { - continue - } - rules, err := r.GetRoleReferenceRules(ctx, clusterRoleBinding.RoleRef, "") - if err != nil { - if !visitor(nil, nil, err) { - return - } - continue - } - sourceDescriber.binding = clusterRoleBinding - sourceDescriber.subject = &clusterRoleBinding.Subjects[subjectIndex] - for i := range rules { - if !visitor(sourceDescriber, &rules[i], nil) { - return - } - } - } - } - - if len(namespace) > 0 { - if roleBindings, err := r.roleBindingLister.ListRoleBindings(ctx, namespace); err != nil { - if !visitor(nil, nil, err) { - return - } - } else { - sourceDescriber := &roleBindingDescriber{} - for _, roleBinding := range roleBindings { - subjectIndex, applies := appliesTo(user, roleBinding.Subjects, namespace) - if !applies { - continue - } - rules, err := r.GetRoleReferenceRules(ctx, roleBinding.RoleRef, namespace) - if err != nil { - if !visitor(nil, nil, err) { - return - } - continue - } - sourceDescriber.binding = roleBinding - sourceDescriber.subject = &roleBinding.Subjects[subjectIndex] - for i := range rules { - if !visitor(sourceDescriber, &rules[i], nil) { - return - } - } - } - } - } -} - -// GetRoleReferenceRules attempts to resolve the RoleBinding or ClusterRoleBinding. -func (r *DefaultRuleResolver) GetRoleReferenceRules(ctx context.Context, roleRef rbacv1.RoleRef, bindingNamespace string) ([]rbacv1.PolicyRule, error) { - switch roleRef.Kind { - case "Role": - role, err := r.roleGetter.GetRole(ctx, bindingNamespace, roleRef.Name) - if err != nil { - return nil, err - } - return role.Rules, nil - - case "ClusterRole": - clusterRole, err := r.clusterRoleGetter.GetClusterRole(ctx, roleRef.Name) - if err != nil { - return nil, err - } - return clusterRole.Rules, nil - - default: - return nil, fmt.Errorf("unsupported role reference kind: %q", roleRef.Kind) - } -} - -// appliesTo returns whether any of the bindingSubjects applies to the specified subject, -// and if true, the index of the first subject that applies -func appliesTo(user user.Info, bindingSubjects []rbacv1.Subject, namespace string) (int, bool) { - for i, bindingSubject := range bindingSubjects { - if appliesToUser(user, bindingSubject, namespace) { - return i, true - } - } - return 0, false -} - -func has(set []string, ele string) bool { - for _, s := range set { - if s == ele { - return true - } - } - return false -} - -func appliesToUser(user user.Info, subject rbacv1.Subject, namespace string) bool { - switch subject.Kind { - case rbacv1.UserKind: - return user.GetName() == subject.Name - - case rbacv1.GroupKind: - return has(user.GetGroups(), subject.Name) - - case rbacv1.ServiceAccountKind: - // default the namespace to namespace we're working in if its available. This allows rolebindings that reference - // SAs in th local namespace to avoid having to qualify them. - saNamespace := namespace - if len(subject.Namespace) > 0 { - saNamespace = subject.Namespace - } - if len(saNamespace) == 0 { - return false - } - // use a more efficient comparison for RBAC checking - return serviceaccount.MatchesUsername(saNamespace, subject.Name, user.GetName()) - default: - return false - } -} - -// NewTestRuleResolver returns a rule resolver from lists of role objects. -func NewTestRuleResolver(roles []*rbacv1.Role, roleBindings []*rbacv1.RoleBinding, clusterRoles []*rbacv1.ClusterRole, clusterRoleBindings []*rbacv1.ClusterRoleBinding) (AuthorizationRuleResolver, *StaticRoles) { - r := StaticRoles{ - roles: roles, - roleBindings: roleBindings, - clusterRoles: clusterRoles, - clusterRoleBindings: clusterRoleBindings, - } - return newMockRuleResolver(&r), &r -} - -func newMockRuleResolver(r *StaticRoles) AuthorizationRuleResolver { - return NewDefaultRuleResolver(r, r, r, r) -} - -// StaticRoles is a rule resolver that resolves from lists of role objects. -type StaticRoles struct { - roles []*rbacv1.Role - roleBindings []*rbacv1.RoleBinding - clusterRoles []*rbacv1.ClusterRole - clusterRoleBindings []*rbacv1.ClusterRoleBinding -} - -func (r *StaticRoles) GetRole(ctx context.Context, namespace, name string) (*rbacv1.Role, error) { - if len(namespace) == 0 { - return nil, errors.New("must provide namespace when getting role") - } - for _, role := range r.roles { - if role.Namespace == namespace && role.Name == name { - return role, nil - } - } - return nil, errors.New("role not found") -} - -func (r *StaticRoles) GetClusterRole(ctx context.Context, name string) (*rbacv1.ClusterRole, error) { - for _, clusterRole := range r.clusterRoles { - if clusterRole.Name == name { - return clusterRole, nil - } - } - return nil, errors.New("clusterrole not found") -} - -func (r *StaticRoles) ListRoleBindings(ctx context.Context, namespace string) ([]*rbacv1.RoleBinding, error) { - if len(namespace) == 0 { - return nil, errors.New("must provide namespace when listing role bindings") - } - - roleBindingList := []*rbacv1.RoleBinding{} - for _, roleBinding := range r.roleBindings { - if roleBinding.Namespace != namespace { - continue - } - // TODO(ericchiang): need to implement label selectors? - roleBindingList = append(roleBindingList, roleBinding) - } - return roleBindingList, nil -} - -func (r *StaticRoles) ListClusterRoleBindings(ctx context.Context) ([]*rbacv1.ClusterRoleBinding, error) { - return r.clusterRoleBindings, nil -} diff --git a/internal/operator-controller/authorization/internal/kubernetes/plugin/pkg/auth/authorizer/rbac/rbac.go b/internal/operator-controller/authorization/internal/kubernetes/plugin/pkg/auth/authorizer/rbac/rbac.go deleted file mode 100644 index d350848d5..000000000 --- a/internal/operator-controller/authorization/internal/kubernetes/plugin/pkg/auth/authorizer/rbac/rbac.go +++ /dev/null @@ -1,225 +0,0 @@ -/* -Copyright 2016 The Kubernetes Authors. - -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. -*/ - -// Package rbac implements the authorizer.Authorizer interface using roles base access control. -package rbac - -import ( - "bytes" - "context" - "fmt" - - "k8s.io/klog/v2" - - rbacv1helpers "github.com/operator-framework/operator-controller/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1" - rbacregistryvalidation "github.com/operator-framework/operator-controller/internal/operator-controller/authorization/internal/kubernetes/pkg/registry/rbac/validation" - rbacv1 "k8s.io/api/rbac/v1" - "k8s.io/apimachinery/pkg/labels" - utilerrors "k8s.io/apimachinery/pkg/util/errors" - "k8s.io/apiserver/pkg/authentication/user" - "k8s.io/apiserver/pkg/authorization/authorizer" - rbaclisters "k8s.io/client-go/listers/rbac/v1" -) - -type RequestToRuleMapper interface { - // RulesFor returns all known PolicyRules and any errors that happened while locating those rules. - // Any rule returned is still valid, since rules are deny by default. If you can pass with the rules - // supplied, you do not have to fail the request. If you cannot, you should indicate the error along - // with your denial. - RulesFor(ctx context.Context, subject user.Info, namespace string) ([]rbacv1.PolicyRule, error) - - // VisitRulesFor invokes visitor() with each rule that applies to a given user in a given namespace, - // and each error encountered resolving those rules. Rule may be nil if err is non-nil. - // If visitor() returns false, visiting is short-circuited. - VisitRulesFor(ctx context.Context, user user.Info, namespace string, visitor func(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool) -} - -type RBACAuthorizer struct { - authorizationRuleResolver RequestToRuleMapper -} - -// authorizingVisitor short-circuits once allowed, and collects any resolution errors encountered -type authorizingVisitor struct { - requestAttributes authorizer.Attributes - - allowed bool - reason string - errors []error -} - -func (v *authorizingVisitor) visit(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool { - if rule != nil && RuleAllows(v.requestAttributes, rule) { - v.allowed = true - v.reason = fmt.Sprintf("RBAC: allowed by %s", source.String()) - return false - } - if err != nil { - v.errors = append(v.errors, err) - } - return true -} - -func (r *RBACAuthorizer) Authorize(ctx context.Context, requestAttributes authorizer.Attributes) (authorizer.Decision, string, error) { - ruleCheckingVisitor := &authorizingVisitor{requestAttributes: requestAttributes} - - r.authorizationRuleResolver.VisitRulesFor(ctx, requestAttributes.GetUser(), requestAttributes.GetNamespace(), ruleCheckingVisitor.visit) - if ruleCheckingVisitor.allowed { - return authorizer.DecisionAllow, ruleCheckingVisitor.reason, nil - } - - // Build a detailed log of the denial. - // Make the whole block conditional so we don't do a lot of string-building we won't use. - if klogV := klog.V(5); klogV.Enabled() { - var operation string - if requestAttributes.IsResourceRequest() { - b := &bytes.Buffer{} - b.WriteString(`"`) - b.WriteString(requestAttributes.GetVerb()) - b.WriteString(`" resource "`) - b.WriteString(requestAttributes.GetResource()) - if len(requestAttributes.GetAPIGroup()) > 0 { - b.WriteString(`.`) - b.WriteString(requestAttributes.GetAPIGroup()) - } - if len(requestAttributes.GetSubresource()) > 0 { - b.WriteString(`/`) - b.WriteString(requestAttributes.GetSubresource()) - } - b.WriteString(`"`) - if len(requestAttributes.GetName()) > 0 { - b.WriteString(` named "`) - b.WriteString(requestAttributes.GetName()) - b.WriteString(`"`) - } - operation = b.String() - } else { - operation = fmt.Sprintf("%q nonResourceURL %q", requestAttributes.GetVerb(), requestAttributes.GetPath()) - } - - var scope string - if ns := requestAttributes.GetNamespace(); len(ns) > 0 { - scope = fmt.Sprintf("in namespace %q", ns) - } else { - scope = "cluster-wide" - } - - klogV.Infof("RBAC: no rules authorize user %q with groups %q to %s %s", requestAttributes.GetUser().GetName(), requestAttributes.GetUser().GetGroups(), operation, scope) - } - - reason := "" - if len(ruleCheckingVisitor.errors) > 0 { - reason = fmt.Sprintf("RBAC: %v", utilerrors.NewAggregate(ruleCheckingVisitor.errors)) - } - return authorizer.DecisionNoOpinion, reason, nil -} - -func (r *RBACAuthorizer) RulesFor(ctx context.Context, user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) { - var ( - resourceRules []authorizer.ResourceRuleInfo - nonResourceRules []authorizer.NonResourceRuleInfo - ) - - policyRules, err := r.authorizationRuleResolver.RulesFor(ctx, user, namespace) - for _, policyRule := range policyRules { - if len(policyRule.Resources) > 0 { - r := authorizer.DefaultResourceRuleInfo{ - Verbs: policyRule.Verbs, - APIGroups: policyRule.APIGroups, - Resources: policyRule.Resources, - ResourceNames: policyRule.ResourceNames, - } - var resourceRule authorizer.ResourceRuleInfo = &r - resourceRules = append(resourceRules, resourceRule) - } - if len(policyRule.NonResourceURLs) > 0 { - r := authorizer.DefaultNonResourceRuleInfo{ - Verbs: policyRule.Verbs, - NonResourceURLs: policyRule.NonResourceURLs, - } - var nonResourceRule authorizer.NonResourceRuleInfo = &r - nonResourceRules = append(nonResourceRules, nonResourceRule) - } - } - return resourceRules, nonResourceRules, false, err -} - -func New(roles rbacregistryvalidation.RoleGetter, roleBindings rbacregistryvalidation.RoleBindingLister, clusterRoles rbacregistryvalidation.ClusterRoleGetter, clusterRoleBindings rbacregistryvalidation.ClusterRoleBindingLister) *RBACAuthorizer { - authorizer := &RBACAuthorizer{ - authorizationRuleResolver: rbacregistryvalidation.NewDefaultRuleResolver( - roles, roleBindings, clusterRoles, clusterRoleBindings, - ), - } - return authorizer -} - -func RulesAllow(requestAttributes authorizer.Attributes, rules ...rbacv1.PolicyRule) bool { - for i := range rules { - if RuleAllows(requestAttributes, &rules[i]) { - return true - } - } - - return false -} - -func RuleAllows(requestAttributes authorizer.Attributes, rule *rbacv1.PolicyRule) bool { - if requestAttributes.IsResourceRequest() { - combinedResource := requestAttributes.GetResource() - if len(requestAttributes.GetSubresource()) > 0 { - combinedResource = requestAttributes.GetResource() + "/" + requestAttributes.GetSubresource() - } - - return rbacv1helpers.VerbMatches(rule, requestAttributes.GetVerb()) && - rbacv1helpers.APIGroupMatches(rule, requestAttributes.GetAPIGroup()) && - rbacv1helpers.ResourceMatches(rule, combinedResource, requestAttributes.GetSubresource()) && - rbacv1helpers.ResourceNameMatches(rule, requestAttributes.GetName()) - } - - return rbacv1helpers.VerbMatches(rule, requestAttributes.GetVerb()) && - rbacv1helpers.NonResourceURLMatches(rule, requestAttributes.GetPath()) -} - -type RoleGetter struct { - Lister rbaclisters.RoleLister -} - -func (g *RoleGetter) GetRole(ctx context.Context, namespace, name string) (*rbacv1.Role, error) { - return g.Lister.Roles(namespace).Get(name) -} - -type RoleBindingLister struct { - Lister rbaclisters.RoleBindingLister -} - -func (l *RoleBindingLister) ListRoleBindings(ctx context.Context, namespace string) ([]*rbacv1.RoleBinding, error) { - return l.Lister.RoleBindings(namespace).List(labels.Everything()) -} - -type ClusterRoleGetter struct { - Lister rbaclisters.ClusterRoleLister -} - -func (g *ClusterRoleGetter) GetClusterRole(ctx context.Context, name string) (*rbacv1.ClusterRole, error) { - return g.Lister.Get(name) -} - -type ClusterRoleBindingLister struct { - Lister rbaclisters.ClusterRoleBindingLister -} - -func (l *ClusterRoleBindingLister) ListClusterRoleBindings(ctx context.Context) ([]*rbacv1.ClusterRoleBinding, error) { - return l.Lister.List(labels.Everything()) -} diff --git a/internal/operator-controller/authorization/rbac.go b/internal/operator-controller/authorization/rbac.go index 7ec0bb0a6..3707d82a2 100644 --- a/internal/operator-controller/authorization/rbac.go +++ b/internal/operator-controller/authorization/rbac.go @@ -23,11 +23,11 @@ import ( "k8s.io/apiserver/pkg/endpoints/request" "sigs.k8s.io/controller-runtime/pkg/client" - rbacinternal "github.com/operator-framework/operator-controller/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac" - rbacv1helpers "github.com/operator-framework/operator-controller/internal/operator-controller/authorization/internal/kubernetes/pkg/apis/rbac/v1" - rbacregistry "github.com/operator-framework/operator-controller/internal/operator-controller/authorization/internal/kubernetes/pkg/registry/rbac" - "github.com/operator-framework/operator-controller/internal/operator-controller/authorization/internal/kubernetes/pkg/registry/rbac/validation" - "github.com/operator-framework/operator-controller/internal/operator-controller/authorization/internal/kubernetes/plugin/pkg/auth/authorizer/rbac" + rbacinternal "k8s.io/kubernetes/pkg/apis/rbac" + rbacv1helpers "k8s.io/kubernetes/pkg/apis/rbac/v1" + rbacregistry "k8s.io/kubernetes/pkg/registry/rbac" + "k8s.io/kubernetes/pkg/registry/rbac/validation" + rbac "k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac" ) type PreAuthorizer interface { @@ -75,13 +75,9 @@ func (a *rbacPreAuthorizer) PreAuthorize(ctx context.Context, manifestManager us for _, obj := range dm.rbacObjects() { if err := ec.checkEscalation(ctx, manifestManager, obj); err != nil { - var peErr *validation.PrivilegeEscalationError - if errors.As(err, &peErr) { - missingRules[peErr.Namespace] = append(missingRules[peErr.Namespace], peErr.MissingRules...) - preAuthEvaluationErrors = append(preAuthEvaluationErrors, peErr.RuleResolutionErrors...) - } else { - preAuthEvaluationErrors = append(preAuthEvaluationErrors, err) - } + // In Kubernetes 1.32.2 the specialized PrivilegeEscalationError is gone. + // Instead, we simply collect the error. + preAuthEvaluationErrors = append(preAuthEvaluationErrors, err) } } From d9f39afa373c881e28cc51738eb1340e2aae7d58 Mon Sep 17 00:00:00 2001 From: Brett Tofel Date: Thu, 6 Mar 2025 11:30:05 -0500 Subject: [PATCH 06/67] Adds k8s.io/ lib maintainer tool go.mod made in the current form the tool generates Signed-off-by: Brett Tofel --- go.mod | 97 +++++++--- hack/tools/k8sMaintainer.go | 319 ++++++++++++++++++++++++++++++++ hack/tools/k8sMaintainer_doc.md | 44 +++++ 3 files changed, 435 insertions(+), 25 deletions(-) create mode 100644 hack/tools/k8sMaintainer.go create mode 100644 hack/tools/k8sMaintainer_doc.md diff --git a/go.mod b/go.mod index dfc8c7a2d..c2e7ba6a2 100644 --- a/go.mod +++ b/go.mod @@ -25,6 +25,7 @@ require ( github.com/spf13/cobra v1.9.1 github.com/stretchr/testify v1.10.0 golang.org/x/exp v0.0.0-20250228200357-dead58393ab7 + golang.org/x/mod v0.23.0 golang.org/x/sync v0.13.0 golang.org/x/tools v0.32.0 gopkg.in/yaml.v2 v2.4.0 @@ -44,29 +45,7 @@ require ( sigs.k8s.io/yaml v1.4.0 ) -replace ( - k8s.io/api => k8s.io/api v0.32.2 - k8s.io/apimachinery => k8s.io/apimachinery v0.32.2 - k8s.io/apiserver => k8s.io/apiserver v0.32.2 - k8s.io/client-go => k8s.io/client-go v0.32.2 - - k8s.io/cloud-provider => k8s.io/cloud-provider v0.32.2 - k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.32.2 - k8s.io/controller-manager => k8s.io/controller-manager v0.32.2 - k8s.io/cri-client => k8s.io/cri-client v0.32.2 - k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.32.2 - k8s.io/dynamic-resource-allocation => k8s.io/dynamic-resource-allocation v0.32.2 - k8s.io/endpointslice => k8s.io/endpointslice v0.32.2 - k8s.io/externaljwt => k8s.io/externaljwt v0.32.2 - k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.32.2 - k8s.io/kube-proxy => k8s.io/kube-proxy v0.32.2 - k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.32.2 - k8s.io/kubelet => k8s.io/kubelet v0.32.2 - k8s.io/kubernetes => k8s.io/kubernetes v1.32.2 - k8s.io/mount-utils => k8s.io/mount-utils v0.32.2 - k8s.io/pod-security-admission => k8s.io/pod-security-admission v0.32.2 - k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.32.2 -) +require k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect require ( cel.dev/expr v0.19.0 // indirect @@ -258,9 +237,17 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/component-helpers v0.32.1 // indirect - k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7 // indirect + k8s.io/api v0.32.2 + k8s.io/apiextensions-apiserver v0.32.2 + k8s.io/apimachinery v0.32.2 + k8s.io/apiserver v0.32.2 + k8s.io/cli-runtime v0.32.2 + k8s.io/client-go v0.32.2 + k8s.io/component-base v0.32.2 + k8s.io/component-helpers v0.32.2 // indirect + k8s.io/controller-manager v0.32.2 // indirect k8s.io/kubectl v0.32.2 // indirect + k8s.io/kubernetes v1.32.2 oras.land/oras-go v1.2.5 // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0 // indirect sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect @@ -268,3 +255,63 @@ require ( sigs.k8s.io/kustomize/kyaml v0.18.1 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.5.0 // indirect ) + +replace k8s.io/api => k8s.io/api v0.32.2 + +replace k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.32.2 + +replace k8s.io/apimachinery => k8s.io/apimachinery v0.32.2 + +replace k8s.io/apiserver => k8s.io/apiserver v0.32.2 + +replace k8s.io/cli-runtime => k8s.io/cli-runtime v0.32.2 + +replace k8s.io/client-go => k8s.io/client-go v0.32.2 + +replace k8s.io/cloud-provider => k8s.io/cloud-provider v0.32.2 + +replace k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.32.2 + +replace k8s.io/code-generator => k8s.io/code-generator v0.32.2 + +replace k8s.io/component-base => k8s.io/component-base v0.32.2 + +replace k8s.io/component-helpers => k8s.io/component-helpers v0.32.2 + +replace k8s.io/controller-manager => k8s.io/controller-manager v0.32.2 + +replace k8s.io/cri-api => k8s.io/cri-api v0.32.2 + +replace k8s.io/cri-client => k8s.io/cri-client v0.32.2 + +replace k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.32.2 + +replace k8s.io/dynamic-resource-allocation => k8s.io/dynamic-resource-allocation v0.32.2 + +replace k8s.io/endpointslice => k8s.io/endpointslice v0.32.2 + +replace k8s.io/externaljwt => k8s.io/externaljwt v0.32.2 + +replace k8s.io/kms => k8s.io/kms v0.32.2 + +replace k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.32.2 + +replace k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.32.2 + +replace k8s.io/kube-proxy => k8s.io/kube-proxy v0.32.2 + +replace k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.32.2 + +replace k8s.io/kubectl => k8s.io/kubectl v0.32.2 + +replace k8s.io/kubelet => k8s.io/kubelet v0.32.2 + +replace k8s.io/metrics => k8s.io/metrics v0.32.2 + +replace k8s.io/mount-utils => k8s.io/mount-utils v0.32.2 + +replace k8s.io/pod-security-admission => k8s.io/pod-security-admission v0.32.2 + +replace k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.32.2 + +replace k8s.io/kubernetes => k8s.io/kubernetes v1.32.2 diff --git a/hack/tools/k8sMaintainer.go b/hack/tools/k8sMaintainer.go new file mode 100644 index 000000000..4cd4a8aa9 --- /dev/null +++ b/hack/tools/k8sMaintainer.go @@ -0,0 +1,319 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "os" + "os/exec" + "sort" + "strings" + + "golang.org/x/mod/modfile" +) + +// debug controls whether we print extra statements. +var debug = true + +// moduleInfo is the partial output of `go list -m -json all`. +type moduleInfo struct { + Path string `json:"Path"` + Version string `json:"Version"` +} + +func main() { + if err := fixGoMod("go.mod"); err != nil { + fmt.Fprintf(os.Stderr, "fixGoMod failed: %v\n", err) + os.Exit(1) + } +} + +// fixGoMod is the main entrypoint. It does a 2‐phase approach: +// +// Remove old k8s.io/* replace lines, rewrite + tidy so they’re really gone. +// Parse again, unify staging modules in require + replace to the new patch version, rewrite + tidy. +func fixGoMod(goModPath string) error { + // parse & remove old lines, write, go mod tidy + mf1, err := parseMod(goModPath) + if err != nil { + return err + } + pruneK8sReplaces(mf1) + mf1.SortBlocks() + mf1.Cleanup() + + if err := writeModFile(mf1, goModPath); err != nil { + return err + } + if err := runCmd("go", "mod", "tidy"); err != nil { + return fmt.Errorf("go mod tidy failed: %w", err) + } + //parse again, unify everything to derived patch version in both require + replace blocks, write, go mod tidy + mf2, err := parseMod(goModPath) + if err != nil { + return err + } + + k8sVer := findKubernetesVersion(mf2) + if k8sVer == "" { + return fmt.Errorf("did not find k8s.io/kubernetes in require block") + } + fmt.Printf("Found k8s.io/kubernetes version: %s\n", k8sVer) + + published := toPublishedVersion(k8sVer) + if published == "" { + return fmt.Errorf("cannot derive staging version from %s", k8sVer) + } + fmt.Printf("Unifying staging modules to: %s (from %s)\n", published, k8sVer) + + // forcibly unify the REQUIRE items for all staging modules + forceRequireStaging(mf2, published) + + // discover all k8s.io/* modules in the graph and unify them with new replace lines + listOut, errOut, err := runGoList() + if err != nil { + return fmt.Errorf("go list: %v\nStderr:\n%s", err, errOut) + } + stagingPins := discoverPinsAlways(listOut, published) + applyReplacements(mf2, stagingPins) + + // also ensure we have a replace for k8s.io/kubernetes => same version + ensureKubernetesReplace(mf2, k8sVer) + + mf2.SortBlocks() + mf2.Cleanup() + + if err := writeModFile(mf2, goModPath); err != nil { + return err + } + if err := runCmd("go", "mod", "tidy"); err != nil { + return fmt.Errorf("final tidy failed: %w", err) + } + if err := runCmd("go", "mod", "download", "k8s.io/kubernetes"); err != nil { + return fmt.Errorf("final: go mod download k8s.io/kubernetes failed: %w", err) + } + + // final check + finalOut, err := exec.Command("go", "list", "-m", "all").Output() + if err != nil { + return fmt.Errorf("running final go list: %w", err) + } + if bytes.Contains(finalOut, []byte("v0.0.0")) { + fmt.Println("Warning: Some modules remain at v0.0.0, possibly no valid tags.") + } else { + fmt.Println("Success: staging modules pinned to", published) + } + return nil +} + +// parseMod reads go.mod into memory as a modfile.File +func parseMod(path string) (*modfile.File, error) { + data, err := ioutil.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("reading %s: %w", path, err) + } + mf, err := modfile.Parse(path, data, nil) + if err != nil { + return nil, fmt.Errorf("parsing %s: %w", path, err) + } + return mf, nil +} + +// writeModFile formats and writes the modfile back to disk +func writeModFile(mf *modfile.File, path string) error { + formatted, err := mf.Format() + if err != nil { + return fmt.Errorf("formatting modfile: %w", err) + } + if err := ioutil.WriteFile(path, formatted, 0644); err != nil { + return fmt.Errorf("writing %s: %w", path, err) + } + if debug { + fmt.Printf("Wrote %s\n", path) + } + return nil +} + +// pruneK8sReplaces removes any replace lines with Old.Path prefix "k8s.io/" +func pruneK8sReplaces(mf *modfile.File) { + var keep []*modfile.Replace + for _, rep := range mf.Replace { + if strings.HasPrefix(rep.Old.Path, "k8s.io/") { + fmt.Printf("Dropping old replace for %s => %s %s\n", + rep.Old.Path, rep.New.Path, rep.New.Version) + } else { + keep = append(keep, rep) + } + } + mf.Replace = keep +} + +// forceRequireStaging forcibly sets the require lines for all staging modules +// (k8s.io/*) to the desired patch version if a valid tag is found. We remove +// the old line first, then AddRequire so the final go.mod show them updated. +func forceRequireStaging(mf *modfile.File, newVersion string) { + var stagingPaths []string + + // gather all relevant require lines we want to unify + for _, req := range mf.Require { + p := req.Mod.Path + if strings.HasPrefix(p, "k8s.io/") && + p != "k8s.io/kubernetes" && + !hasMajorVersionSuffix(p) { + stagingPaths = append(stagingPaths, p) + } + } + // remove them + for _, p := range stagingPaths { + fmt.Printf("Removing require line for %s\n", p) + _ = mf.DropRequire(p) // returns an error if not found, ignore + } + // re-add them at the new version if we can download that version + for _, p := range stagingPaths { + if versionExists(p, newVersion) { + fmt.Printf("Adding require line for %s at %s\n", p, newVersion) + _ = mf.AddRequire(p, newVersion) + } else { + fmt.Printf("WARNING: no valid tag for %s at %s, skipping\n", p, newVersion) + } + } +} + +// discoverPinsAlways identifies k8s.io/* modules from the "go list -m -json all" +// output, and unifies them all to `published` if it’s downloadable. This does +// not skip forced downgrades. If it's a staging path, we pin it. +func discoverPinsAlways(listOut, published string) map[string]string { + pins := make(map[string]string) + dec := json.NewDecoder(strings.NewReader(listOut)) + for { + var mi moduleInfo + if err := dec.Decode(&mi); err != nil { + break + } + if !strings.HasPrefix(mi.Path, "k8s.io/") { + continue + } + if mi.Path == "k8s.io/kubernetes" { + continue + } + if hasMajorVersionSuffix(mi.Path) { + fmt.Printf("Skipping major-version module %s\n", mi.Path) + continue + } + // unify everything if a valid tag exists + if mi.Version != published { + if versionExists(mi.Path, published) { + fmt.Printf("Pinning %s from %s to %s\n", mi.Path, mi.Version, published) + pins[mi.Path] = published + } else { + fmt.Printf("WARNING: no valid tag for %s at %s, leaving as %s\n", + mi.Path, published, mi.Version) + } + } + } + return pins +} + +// applyReplacements adds replace lines for each pinned staging module +func applyReplacements(mf *modfile.File, pins map[string]string) { + if len(pins) == 0 { + return + } + var sorted []string + for p := range pins { + sorted = append(sorted, p) + } + sort.Strings(sorted) + for _, path := range sorted { + ver := pins[path] + fmt.Printf("Applying new replace: %s => %s\n", path, ver) + if err := mf.AddReplace(path, "", path, ver); err != nil { + die("Error adding replace for %s: %v", path, err) + } + } +} + +// ensureKubernetesReplace ensures there's a "k8s.io/kubernetes => k8s.io/kubernetes vX.Y.Z" line +// matching the require(...) version in case something references it directly. +func ensureKubernetesReplace(mf *modfile.File, k8sVer string) { + found := false + for _, rep := range mf.Replace { + if rep.Old.Path == "k8s.io/kubernetes" { + found = true + if rep.New.Version != k8sVer { + fmt.Printf("Updating k8s.io/kubernetes replace from %s to %s\n", + rep.New.Version, k8sVer) + rep.New.Version = k8sVer + } + break + } + } + if !found { + fmt.Printf("Inserting k8s.io/kubernetes => %s\n", k8sVer) + if err := mf.AddReplace("k8s.io/kubernetes", "", "k8s.io/kubernetes", k8sVer); err != nil { + die("Error adding replace for k8s.io/kubernetes: %v", err) + } + } +} + +// findKubernetesVersion returns the version in the require(...) block for k8s.io/kubernetes +func findKubernetesVersion(mf *modfile.File) string { + for _, req := range mf.Require { + if req.Mod.Path == "k8s.io/kubernetes" { + return req.Mod.Version + } + } + return "" +} + +// toPublishedVersion: e.g. "v1.32.2" => "v0.32.2" +func toPublishedVersion(k8sVersion string) string { + if !strings.HasPrefix(k8sVersion, "v") { + return "" + } + parts := strings.Split(strings.TrimPrefix(k8sVersion, "v"), ".") + if len(parts) < 3 { + return "" + } + return fmt.Sprintf("v0.%s.%s", parts[1], parts[2]) +} + +// runGoList runs "go list -m -json all" and returns stdout, stderr, error +func runGoList() (string, string, error) { + cmd := exec.Command("go", "list", "-m", "-json", "all") + var outBuf, errBuf bytes.Buffer + cmd.Stdout = &outBuf + cmd.Stderr = &errBuf + err := cmd.Run() + return outBuf.String(), errBuf.String(), err +} + +// runCmd runs a command with stdout/stderr displayed. Returns an error if it fails. +func runCmd(name string, args ...string) error { + cmd := exec.Command(name, args...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() +} + +// versionExists quietly tries `go mod download modPath@ver`. If 0 exit code => true. +func versionExists(modPath, ver string) bool { + cmd := exec.Command("go", "mod", "download", fmt.Sprintf("%s@%s", modPath, ver)) + cmd.Stdout = nil + cmd.Stderr = nil + return cmd.Run() == nil +} + +// hasMajorVersionSuffix checks for trailing /v2, /v3, etc. in the module path +func hasMajorVersionSuffix(path string) bool { + segs := strings.Split(path, "/") + last := segs[len(segs)-1] + return len(last) > 1 && last[0] == 'v' && last[1] >= '2' && last[1] <= '9' +} + +// die prints an error and exits. +func die(format string, args ...interface{}) { + fmt.Fprintf(os.Stderr, format+"\n", args...) + os.Exit(1) +} diff --git a/hack/tools/k8sMaintainer_doc.md b/hack/tools/k8sMaintainer_doc.md new file mode 100644 index 000000000..b40177be5 --- /dev/null +++ b/hack/tools/k8sMaintainer_doc.md @@ -0,0 +1,44 @@ +# Kubernetes Staging Module Version Synchronization Tool + +## Purpose +This tool ensures that if `k8s.io/kubernetes` changes version in your `go.mod`, all related staging modules (e.g., `k8s.io/api`, `k8s.io/apimachinery`) are automatically pinned to the corresponding published version. + +## How It Works + +1. **Parse and Filter:** + - Reads and parses your `go.mod`. + - Removes any existing `replace` directives referencing `k8s.io/`, ensuring no stale mappings persist. + +2. **Find Kubernetes Version & Compute Staging Version:** + - Identifies the pinned `k8s.io/kubernetes` version in the `require` block. + - Converts `"v1.xx.yy"` to `"v0.xx.yy"` to determine the correct version for staging modules. + +3. **List All Modules in the Graph:** + - Runs `go list -m -json all` to get the full dependency tree. + - Extracts all `k8s.io/*` modules, ensuring completeness. + +4. **Pin Staging Modules:** + - For each `k8s.io/*` module (except `k8s.io/kubernetes`): + - If it has a `v0.0.0` version (indicating it’s untagged) or its version doesn’t match the computed published version, the tool updates the `replace` directive accordingly. + - Ensures `k8s.io/kubernetes` itself has a `replace` entry for consistency. + +5. **Write & Finalize:** + - Writes the updated `go.mod`. + - Runs `go mod tidy` to clean up any dangling dependencies. + - Runs `go mod download k8s.io/kubernetes` to guarantee required entries in `go.sum`. + - Performs a final check that no modules remain at `v0.0.0`. + +## Behavior When Kubernetes Version Changes +- If you manually update `k8s.io/kubernetes` (e.g., from `v1.32.2` to `v1.32.1`) and rerun this tool: + - The tool detects the new version and calculates the corresponding staging version (`v0.32.1`). + - It updates all staging modules (`k8s.io/*`) to match the new version, ensuring consistency. + - Any outdated `replace` directives are removed and replaced with the correct version. + +## Additional Checks & Safeguards +- **Missing `go.sum` Entries:** If `go list -m -json all` fails due to missing entries, the tool runs `go mod download` to ensure completeness. +- **Conflicting Pinned Versions:** The tool enforces replace directives, but transitive dependencies may still cause conflicts that require manual resolution. +- **Modules Introduced/Removed in Certain Versions:** If a required module no longer exists in a given Kubernetes version, manual intervention may be needed. + +## Notes +- Ensures all `k8s.io/*` modules are treated consistently, even if they were not explicitly listed in `go.mod`. +- Warns if any module remains pinned at `v0.0.0`, which could indicate an issue with upstream tagging. From 58e39503ebf6b7a8b3fb1f26895f278b36277749 Mon Sep 17 00:00:00 2001 From: Brett Tofel Date: Fri, 7 Mar 2025 15:23:52 -0500 Subject: [PATCH 07/67] Make debug a flag Signed-off-by: Brett Tofel --- hack/tools/k8sMaintainer.go | 53 +++++++++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 14 deletions(-) diff --git a/hack/tools/k8sMaintainer.go b/hack/tools/k8sMaintainer.go index 4cd4a8aa9..a5c7b3887 100644 --- a/hack/tools/k8sMaintainer.go +++ b/hack/tools/k8sMaintainer.go @@ -3,6 +3,7 @@ package main import ( "bytes" "encoding/json" + "flag" "fmt" "io/ioutil" "os" @@ -14,7 +15,7 @@ import ( ) // debug controls whether we print extra statements. -var debug = true +var debug bool // moduleInfo is the partial output of `go list -m -json all`. type moduleInfo struct { @@ -23,6 +24,10 @@ type moduleInfo struct { } func main() { + // Define the command-line flag + flag.BoolVar(&debug, "debug", false, "Enable debug output") + flag.Parse() + if err := fixGoMod("go.mod"); err != nil { fmt.Fprintf(os.Stderr, "fixGoMod failed: %v\n", err) os.Exit(1) @@ -59,13 +64,17 @@ func fixGoMod(goModPath string) error { if k8sVer == "" { return fmt.Errorf("did not find k8s.io/kubernetes in require block") } - fmt.Printf("Found k8s.io/kubernetes version: %s\n", k8sVer) + if debug { + fmt.Printf("Found k8s.io/kubernetes version: %s\n", k8sVer) + } published := toPublishedVersion(k8sVer) if published == "" { return fmt.Errorf("cannot derive staging version from %s", k8sVer) } - fmt.Printf("Unifying staging modules to: %s (from %s)\n", published, k8sVer) + if debug { + fmt.Printf("Unifying staging modules to: %s (from %s)\n", published, k8sVer) + } // forcibly unify the REQUIRE items for all staging modules forceRequireStaging(mf2, published) @@ -100,7 +109,7 @@ func fixGoMod(goModPath string) error { return fmt.Errorf("running final go list: %w", err) } if bytes.Contains(finalOut, []byte("v0.0.0")) { - fmt.Println("Warning: Some modules remain at v0.0.0, possibly no valid tags.") + fmt.Println("WARNING: Some modules remain at v0.0.0, possibly no valid tags.") } else { fmt.Println("Success: staging modules pinned to", published) } @@ -140,8 +149,10 @@ func pruneK8sReplaces(mf *modfile.File) { var keep []*modfile.Replace for _, rep := range mf.Replace { if strings.HasPrefix(rep.Old.Path, "k8s.io/") { - fmt.Printf("Dropping old replace for %s => %s %s\n", - rep.Old.Path, rep.New.Path, rep.New.Version) + if debug { + fmt.Printf("Dropping old replace for %s => %s %s\n", + rep.Old.Path, rep.New.Path, rep.New.Version) + } } else { keep = append(keep, rep) } @@ -166,13 +177,17 @@ func forceRequireStaging(mf *modfile.File, newVersion string) { } // remove them for _, p := range stagingPaths { - fmt.Printf("Removing require line for %s\n", p) + if debug { + fmt.Printf("Removing require line for %s\n", p) + } _ = mf.DropRequire(p) // returns an error if not found, ignore } // re-add them at the new version if we can download that version for _, p := range stagingPaths { if versionExists(p, newVersion) { - fmt.Printf("Adding require line for %s at %s\n", p, newVersion) + if debug { + fmt.Printf("Adding require line for %s at %s\n", p, newVersion) + } _ = mf.AddRequire(p, newVersion) } else { fmt.Printf("WARNING: no valid tag for %s at %s, skipping\n", p, newVersion) @@ -198,13 +213,17 @@ func discoverPinsAlways(listOut, published string) map[string]string { continue } if hasMajorVersionSuffix(mi.Path) { - fmt.Printf("Skipping major-version module %s\n", mi.Path) + if debug { + fmt.Printf("Skipping major-version module %s\n", mi.Path) + } continue } // unify everything if a valid tag exists if mi.Version != published { if versionExists(mi.Path, published) { - fmt.Printf("Pinning %s from %s to %s\n", mi.Path, mi.Version, published) + if debug { + fmt.Printf("Pinning %s from %s to %s\n", mi.Path, mi.Version, published) + } pins[mi.Path] = published } else { fmt.Printf("WARNING: no valid tag for %s at %s, leaving as %s\n", @@ -227,7 +246,9 @@ func applyReplacements(mf *modfile.File, pins map[string]string) { sort.Strings(sorted) for _, path := range sorted { ver := pins[path] - fmt.Printf("Applying new replace: %s => %s\n", path, ver) + if debug { + fmt.Printf("Applying new replace: %s => %s\n", path, ver) + } if err := mf.AddReplace(path, "", path, ver); err != nil { die("Error adding replace for %s: %v", path, err) } @@ -242,15 +263,19 @@ func ensureKubernetesReplace(mf *modfile.File, k8sVer string) { if rep.Old.Path == "k8s.io/kubernetes" { found = true if rep.New.Version != k8sVer { - fmt.Printf("Updating k8s.io/kubernetes replace from %s to %s\n", - rep.New.Version, k8sVer) + if debug { + fmt.Printf("Updating k8s.io/kubernetes replace from %s to %s\n", + rep.New.Version, k8sVer) + } rep.New.Version = k8sVer } break } } if !found { - fmt.Printf("Inserting k8s.io/kubernetes => %s\n", k8sVer) + if debug { + fmt.Printf("Inserting k8s.io/kubernetes => %s\n", k8sVer) + } if err := mf.AddReplace("k8s.io/kubernetes", "", "k8s.io/kubernetes", k8sVer); err != nil { die("Error adding replace for k8s.io/kubernetes: %v", err) } From 291e161bb4e8ed1a336e08791b85ad1c82211e49 Mon Sep 17 00:00:00 2001 From: Brett Tofel Date: Mon, 10 Mar 2025 09:53:18 -0400 Subject: [PATCH 08/67] Small fix, fixes err on kubernetes replace itself Signed-off-by: Brett Tofel --- go.mod | 2 -- hack/tools/k8sMaintainer.go | 34 ++++++++++++++++------------------ 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/go.mod b/go.mod index c2e7ba6a2..6fa77a808 100644 --- a/go.mod +++ b/go.mod @@ -313,5 +313,3 @@ replace k8s.io/mount-utils => k8s.io/mount-utils v0.32.2 replace k8s.io/pod-security-admission => k8s.io/pod-security-admission v0.32.2 replace k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.32.2 - -replace k8s.io/kubernetes => k8s.io/kubernetes v1.32.2 diff --git a/hack/tools/k8sMaintainer.go b/hack/tools/k8sMaintainer.go index a5c7b3887..bb2308427 100644 --- a/hack/tools/k8sMaintainer.go +++ b/hack/tools/k8sMaintainer.go @@ -5,6 +5,7 @@ import ( "encoding/json" "flag" "fmt" + "golang.org/x/mod/module" "io/ioutil" "os" "os/exec" @@ -258,28 +259,25 @@ func applyReplacements(mf *modfile.File, pins map[string]string) { // ensureKubernetesReplace ensures there's a "k8s.io/kubernetes => k8s.io/kubernetes vX.Y.Z" line // matching the require(...) version in case something references it directly. func ensureKubernetesReplace(mf *modfile.File, k8sVer string) { - found := false + var newReplaces []*modfile.Replace + for _, rep := range mf.Replace { - if rep.Old.Path == "k8s.io/kubernetes" { - found = true - if rep.New.Version != k8sVer { - if debug { - fmt.Printf("Updating k8s.io/kubernetes replace from %s to %s\n", - rep.New.Version, k8sVer) - } - rep.New.Version = k8sVer + if rep.Old.Path == "k8s.io/kubernetes" && rep.New.Version != k8sVer { + if debug { + fmt.Printf("Updating k8s.io/kubernetes replace from %s to %s\n", rep.New.Version, k8sVer) } - break - } - } - if !found { - if debug { - fmt.Printf("Inserting k8s.io/kubernetes => %s\n", k8sVer) - } - if err := mf.AddReplace("k8s.io/kubernetes", "", "k8s.io/kubernetes", k8sVer); err != nil { - die("Error adding replace for k8s.io/kubernetes: %v", err) + continue // Skip adding this entry to newReplaces } + newReplaces = append(newReplaces, rep) } + + // Add the correct replace directive + newReplaces = append(newReplaces, &modfile.Replace{ + Old: module.Version{Path: "k8s.io/kubernetes"}, + New: module.Version{Path: "k8s.io/kubernetes", Version: k8sVer}, + }) + + mf.Replace = newReplaces } // findKubernetesVersion returns the version in the require(...) block for k8s.io/kubernetes From 29ea3e197f654065472ee11d26d2e9bc4f317c8a Mon Sep 17 00:00:00 2001 From: Brett Tofel Date: Mon, 10 Mar 2025 14:03:26 -0400 Subject: [PATCH 09/67] Changes to allow calling as make target Signed-off-by: Brett Tofel --- Makefile | 9 +++-- hack/tools/k8sMaintainer.go | 69 ++++++++++++++++--------------------- 2 files changed, 36 insertions(+), 42 deletions(-) diff --git a/Makefile b/Makefile index 0be7868f5..306ffb926 100644 --- a/Makefile +++ b/Makefile @@ -120,10 +120,13 @@ custom-linter-build: #EXHELP Build custom linter lint-custom: custom-linter-build #EXHELP Call custom linter for the project go vet -tags=$(GO_BUILD_TAGS) -vettool=./bin/custom-linter ./... +.PHONY: k8s-maintainer #EXHELP this tool also calls `go mod tidy` but also allows us maintain k8s.io/kubernetes` changes bumping related staging modules (e.g., `k8s.io/api`, `k8s.io/apimachinery) as needed +k8s-maintainer: + go run hack/tools/k8sMaintainer.go + .PHONY: tidy -tidy: #HELP Update dependencies. - # Force tidy to use the version already in go.mod - $(Q)go mod tidy -go=$(GOLANG_VERSION) +tidy: k8s-maintainer #HELP Update dependencies. + # k8s-maintainer calls go mod tidy .PHONY: manifests KUSTOMIZE_CATD_CRDS_DIR := config/base/catalogd/crd/bases diff --git a/hack/tools/k8sMaintainer.go b/hack/tools/k8sMaintainer.go index bb2308427..7c7db2c47 100644 --- a/hack/tools/k8sMaintainer.go +++ b/hack/tools/k8sMaintainer.go @@ -40,81 +40,72 @@ func main() { // Remove old k8s.io/* replace lines, rewrite + tidy so they’re really gone. // Parse again, unify staging modules in require + replace to the new patch version, rewrite + tidy. func fixGoMod(goModPath string) error { - // parse & remove old lines, write, go mod tidy - mf1, err := parseMod(goModPath) + mf, err := parseMod(goModPath) if err != nil { return err } - pruneK8sReplaces(mf1) - mf1.SortBlocks() - mf1.Cleanup() + pruneK8sReplaces(mf) + mf.SortBlocks() + mf.Cleanup() - if err := writeModFile(mf1, goModPath); err != nil { + if err := writeModFile(mf, goModPath); err != nil { return err } - if err := runCmd("go", "mod", "tidy"); err != nil { - return fmt.Errorf("go mod tidy failed: %w", err) - } - //parse again, unify everything to derived patch version in both require + replace blocks, write, go mod tidy - mf2, err := parseMod(goModPath) + + mf, err = parseMod(goModPath) if err != nil { return err } - k8sVer := findKubernetesVersion(mf2) + k8sVer := findKubernetesVersion(mf) if k8sVer == "" { return fmt.Errorf("did not find k8s.io/kubernetes in require block") } - if debug { - fmt.Printf("Found k8s.io/kubernetes version: %s\n", k8sVer) - } published := toPublishedVersion(k8sVer) if published == "" { return fmt.Errorf("cannot derive staging version from %s", k8sVer) } - if debug { - fmt.Printf("Unifying staging modules to: %s (from %s)\n", published, k8sVer) - } - // forcibly unify the REQUIRE items for all staging modules - forceRequireStaging(mf2, published) + forceRequireStaging(mf, published) - // discover all k8s.io/* modules in the graph and unify them with new replace lines listOut, errOut, err := runGoList() if err != nil { return fmt.Errorf("go list: %v\nStderr:\n%s", err, errOut) } stagingPins := discoverPinsAlways(listOut, published) - applyReplacements(mf2, stagingPins) + applyReplacements(mf, stagingPins) - // also ensure we have a replace for k8s.io/kubernetes => same version - ensureKubernetesReplace(mf2, k8sVer) + ensureKubernetesReplace(mf, k8sVer) - mf2.SortBlocks() - mf2.Cleanup() + mf.SortBlocks() + mf.Cleanup() - if err := writeModFile(mf2, goModPath); err != nil { + if err := writeModFile(mf, goModPath); err != nil { return err } - if err := runCmd("go", "mod", "tidy"); err != nil { - return fmt.Errorf("final tidy failed: %w", err) + + goVersion, err := getGoVersion(goModPath) + if err != nil { + return fmt.Errorf("failed to determine Go version: %w", err) } - if err := runCmd("go", "mod", "download", "k8s.io/kubernetes"); err != nil { - return fmt.Errorf("final: go mod download k8s.io/kubernetes failed: %w", err) + + if err := runCmd("go", "mod", "tidy", "-go="+goVersion); err != nil { + return fmt.Errorf("final tidy failed: %w", err) } - // final check - finalOut, err := exec.Command("go", "list", "-m", "all").Output() + return nil +} + +func getGoVersion(goModPath string) (string, error) { + mf, err := parseMod(goModPath) if err != nil { - return fmt.Errorf("running final go list: %w", err) + return "", err } - if bytes.Contains(finalOut, []byte("v0.0.0")) { - fmt.Println("WARNING: Some modules remain at v0.0.0, possibly no valid tags.") - } else { - fmt.Println("Success: staging modules pinned to", published) + if mf.Go == nil { + return "", fmt.Errorf("go version not found in go.mod") } - return nil + return mf.Go.Version, nil } // parseMod reads go.mod into memory as a modfile.File From be29c88e0f07747090f54a26f84e4961795c46a4 Mon Sep 17 00:00:00 2001 From: Brett Tofel Date: Mon, 10 Mar 2025 14:42:57 -0400 Subject: [PATCH 10/67] Run go mod tidy post rebase Signed-off-by: Brett Tofel --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 6fa77a808..a89e9a201 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( github.com/spf13/cobra v1.9.1 github.com/stretchr/testify v1.10.0 golang.org/x/exp v0.0.0-20250228200357-dead58393ab7 - golang.org/x/mod v0.23.0 + golang.org/x/mod v0.24.0 golang.org/x/sync v0.13.0 golang.org/x/tools v0.32.0 gopkg.in/yaml.v2 v2.4.0 From c8cf8053e78c12001c89f334e2056902231c6a41 Mon Sep 17 00:00:00 2001 From: Brett Tofel Date: Mon, 10 Mar 2025 14:44:11 -0400 Subject: [PATCH 11/67] From rebase - add PreAuthorizer to Helm struct Signed-off-by: Brett Tofel --- cmd/operator-controller/main.go | 2 +- internal/operator-controller/applier/helm.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/operator-controller/main.go b/cmd/operator-controller/main.go index 0176651a6..942fbdca3 100644 --- a/cmd/operator-controller/main.go +++ b/cmd/operator-controller/main.go @@ -420,7 +420,7 @@ func run() error { ActionClientGetter: acg, Preflights: preflights, BundleToHelmChartFn: convert.RegistryV1ToHelmChart, - PreAuthorizer: authorization.NewRBACPreAuthorizer(mgr.GetClient()), + PreAuthorizer: authorization.NewRBACPreAuthorizer(mgr.GetClient()), } cm := contentmanager.NewManager(clientRestConfigMapper, mgr.GetConfig(), mgr.GetRESTMapper()) diff --git a/internal/operator-controller/applier/helm.go b/internal/operator-controller/applier/helm.go index 0de66e038..fdf81105a 100644 --- a/internal/operator-controller/applier/helm.go +++ b/internal/operator-controller/applier/helm.go @@ -60,7 +60,7 @@ type BundleToHelmChartFn func(rv1 fs.FS, installNamespace string, watchNamespace type Helm struct { ActionClientGetter helmclient.ActionClientGetter Preflights []Preflight - PreAuthorizer authorization.PreAuthorizer + PreAuthorizer authorization.PreAuthorizer BundleToHelmChartFn BundleToHelmChartFn } From 124ed762fb97fa3e791a52c6784c0e57342ba75c Mon Sep 17 00:00:00 2001 From: Brett Tofel Date: Mon, 10 Mar 2025 16:03:05 -0400 Subject: [PATCH 12/67] Fixes to pass linter Signed-off-by: Brett Tofel --- hack/tools/k8sMaintainer.go | 14 +++++++------- internal/operator-controller/authorization/rbac.go | 3 +-- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/hack/tools/k8sMaintainer.go b/hack/tools/k8sMaintainer.go index 7c7db2c47..10c917394 100644 --- a/hack/tools/k8sMaintainer.go +++ b/hack/tools/k8sMaintainer.go @@ -5,14 +5,13 @@ import ( "encoding/json" "flag" "fmt" - "golang.org/x/mod/module" - "io/ioutil" "os" "os/exec" "sort" "strings" "golang.org/x/mod/modfile" + "golang.org/x/mod/module" ) // debug controls whether we print extra statements. @@ -110,7 +109,7 @@ func getGoVersion(goModPath string) (string, error) { // parseMod reads go.mod into memory as a modfile.File func parseMod(path string) (*modfile.File, error) { - data, err := ioutil.ReadFile(path) + data, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("reading %s: %w", path, err) } @@ -127,7 +126,7 @@ func writeModFile(mf *modfile.File, path string) error { if err != nil { return fmt.Errorf("formatting modfile: %w", err) } - if err := ioutil.WriteFile(path, formatted, 0644); err != nil { + if err := os.WriteFile(path, formatted, 0600); err != nil { return fmt.Errorf("writing %s: %w", path, err) } if debug { @@ -231,7 +230,7 @@ func applyReplacements(mf *modfile.File, pins map[string]string) { if len(pins) == 0 { return } - var sorted []string + sorted := make([]string, 0, len(pins)) for p := range pins { sorted = append(sorted, p) } @@ -250,7 +249,7 @@ func applyReplacements(mf *modfile.File, pins map[string]string) { // ensureKubernetesReplace ensures there's a "k8s.io/kubernetes => k8s.io/kubernetes vX.Y.Z" line // matching the require(...) version in case something references it directly. func ensureKubernetesReplace(mf *modfile.File, k8sVer string) { - var newReplaces []*modfile.Replace + newReplaces := make([]*modfile.Replace, 0, len(mf.Replace)+1) for _, rep := range mf.Replace { if rep.Old.Path == "k8s.io/kubernetes" && rep.New.Version != k8sVer { @@ -313,7 +312,8 @@ func runCmd(name string, args ...string) error { // versionExists quietly tries `go mod download modPath@ver`. If 0 exit code => true. func versionExists(modPath, ver string) bool { - cmd := exec.Command("go", "mod", "download", fmt.Sprintf("%s@%s", modPath, ver)) + safeArg := fmt.Sprintf("%s@%s", modPath, ver) + cmd := exec.Command("go", "mod", "download", safeArg) cmd.Stdout = nil cmd.Stderr = nil return cmd.Run() == nil diff --git a/internal/operator-controller/authorization/rbac.go b/internal/operator-controller/authorization/rbac.go index 3707d82a2..05ab9253a 100644 --- a/internal/operator-controller/authorization/rbac.go +++ b/internal/operator-controller/authorization/rbac.go @@ -21,13 +21,12 @@ import ( "k8s.io/apiserver/pkg/authentication/user" "k8s.io/apiserver/pkg/authorization/authorizer" "k8s.io/apiserver/pkg/endpoints/request" - "sigs.k8s.io/controller-runtime/pkg/client" - rbacinternal "k8s.io/kubernetes/pkg/apis/rbac" rbacv1helpers "k8s.io/kubernetes/pkg/apis/rbac/v1" rbacregistry "k8s.io/kubernetes/pkg/registry/rbac" "k8s.io/kubernetes/pkg/registry/rbac/validation" rbac "k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac" + "sigs.k8s.io/controller-runtime/pkg/client" ) type PreAuthorizer interface { From 2bacf292b969885364d78499337c21e8be3637b8 Mon Sep 17 00:00:00 2001 From: Brett Tofel Date: Tue, 11 Mar 2025 11:07:34 -0400 Subject: [PATCH 13/67] Add needed setups to preflightPerm unit tests Signed-off-by: Brett Tofel --- .../operator-controller/applier/helm_test.go | 49 +++++++++++++++++-- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/internal/operator-controller/applier/helm_test.go b/internal/operator-controller/applier/helm_test.go index 5b0451789..e62101174 100644 --- a/internal/operator-controller/applier/helm_test.go +++ b/internal/operator-controller/applier/helm_test.go @@ -3,6 +3,7 @@ package applier_test import ( "context" "errors" + "io" "io/fs" "os" "testing" @@ -15,7 +16,9 @@ import ( "helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/storage/driver" corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apiserver/pkg/authentication/user" featuregatetesting "k8s.io/component-base/featuregate/testing" "sigs.k8s.io/controller-runtime/pkg/client" @@ -32,6 +35,17 @@ type mockPreflight struct { upgradeErr error } +type noOpPreAuthorizer struct{} + +func (p *noOpPreAuthorizer) PreAuthorize( + ctx context.Context, + manifestManager user.Info, + manifestReader io.Reader, +) (map[string][]rbacv1.PolicyRule, error) { + // No-op: always return an empty map and no error + return nil, nil +} + func (mp *mockPreflight) Install(context.Context, *release.Release) error { return mp.installErr } @@ -280,11 +294,16 @@ func TestApply_InstallationWithPreflightPermissionsEnabled(t *testing.T) { mockAcg := &mockActionGetter{ getClientErr: driver.ErrReleaseNotFound, installErr: errors.New("failed installing chart"), + desiredRel: &release.Release{ + Info: &release.Info{Status: release.StatusDeployed}, + Manifest: validManifest, + }, } mockPf := &mockPreflight{installErr: errors.New("failed during install pre-flight check")} helmApplier := applier.Helm{ ActionClientGetter: mockAcg, Preflights: []applier.Preflight{mockPf}, + PreAuthorizer: &noOpPreAuthorizer{}, BundleToHelmChartFn: convert.RegistryV1ToHelmChart, } @@ -299,13 +318,26 @@ func TestApply_InstallationWithPreflightPermissionsEnabled(t *testing.T) { mockAcg := &mockActionGetter{ getClientErr: driver.ErrReleaseNotFound, installErr: errors.New("failed installing chart"), + desiredRel: &release.Release{ + Info: &release.Info{Status: release.StatusDeployed}, + Manifest: validManifest, + }, } helmApplier := applier.Helm{ ActionClientGetter: mockAcg, + PreAuthorizer: &noOpPreAuthorizer{}, BundleToHelmChartFn: convert.RegistryV1ToHelmChart, } - - objs, state, err := helmApplier.Apply(context.TODO(), validFS, testCE, testObjectLabels, testStorageLabels) + // Use a ClusterExtension with valid Spec fields. + validCE := &ocv1.ClusterExtension{ + Spec: ocv1.ClusterExtensionSpec{ + Namespace: "default", + ServiceAccount: ocv1.ServiceAccountReference{ + Name: "default", + }, + }, + } + objs, state, err := helmApplier.Apply(context.TODO(), validFS, validCE, testObjectLabels, testStorageLabels) require.Error(t, err) require.ErrorContains(t, err, "installing chart") require.Equal(t, applier.StateNeedsInstall, state) @@ -322,10 +354,21 @@ func TestApply_InstallationWithPreflightPermissionsEnabled(t *testing.T) { } helmApplier := applier.Helm{ ActionClientGetter: mockAcg, + PreAuthorizer: &noOpPreAuthorizer{}, BundleToHelmChartFn: convert.RegistryV1ToHelmChart, } - objs, state, err := helmApplier.Apply(context.TODO(), validFS, testCE, testObjectLabels, testStorageLabels) + // Use a ClusterExtension with valid Spec fields. + validCE := &ocv1.ClusterExtension{ + Spec: ocv1.ClusterExtensionSpec{ + Namespace: "default", + ServiceAccount: ocv1.ServiceAccountReference{ + Name: "default", + }, + }, + } + + objs, state, err := helmApplier.Apply(context.TODO(), validFS, validCE, testObjectLabels, testStorageLabels) require.NoError(t, err) require.Equal(t, applier.StateNeedsInstall, state) require.NotNil(t, objs) From c982cac5c42603384b255b1b9a106375b7125ae3 Mon Sep 17 00:00:00 2001 From: Brett Tofel Date: Fri, 14 Mar 2025 11:30:47 -0400 Subject: [PATCH 14/67] Address review comments on rbac.go rbac_test.go likely coming soon Signed-off-by: Brett Tofel --- .../operator-controller/authorization/rbac.go | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/internal/operator-controller/authorization/rbac.go b/internal/operator-controller/authorization/rbac.go index 05ab9253a..1080f9fb3 100644 --- a/internal/operator-controller/authorization/rbac.go +++ b/internal/operator-controller/authorization/rbac.go @@ -52,6 +52,14 @@ func NewRBACPreAuthorizer(cl client.Client) PreAuthorizer { } } +// PreAuthorize validates whether the current user/request satisfies the necessary permissions +// as defined by the RBAC policy. It examines the user’s roles, resource identifiers, and +// the intended action to determine if the operation is allowed. +// +// Return Value: +// - nil: indicates that the authorization check passed and the operation is permitted. +// - non-nil error: indicates that the authorization failed (either due to insufficient permissions +// or an error encountered during the check), the error provides a slice of several failures at once. func (a *rbacPreAuthorizer) PreAuthorize(ctx context.Context, manifestManager user.Info, manifestReader io.Reader) (map[string][]rbacv1.PolicyRule, error) { dm, err := a.decodeManifest(manifestReader) if err != nil { @@ -81,6 +89,10 @@ func (a *rbacPreAuthorizer) PreAuthorize(ctx context.Context, manifestManager us } for ns, nsMissingRules := range missingRules { + // NOTE: Although CompactRules is defined to return an error, its current implementation + // never produces a non-nil error. This is because all operations within the function are + // designed to succeed under current conditions. In the future, if more complex rule validations + // are introduced, this behavior may change and proper error handling will be required. if compactMissingRules, err := validation.CompactRules(nsMissingRules); err == nil { missingRules[ns] = compactMissingRules } @@ -120,7 +132,15 @@ func (a *rbacPreAuthorizer) decodeManifest(manifestReader io.Reader) (*decodedMa gvk := uObj.GroupVersionKind() restMapping, err := a.restMapper.RESTMapping(gvk.GroupKind(), gvk.Version) if err != nil { - errs = append(errs, fmt.Errorf("could not get REST mapping for object %d in manifest with GVK %s: %w", i, gvk, err)) + var objName string + if name := uObj.GetName(); name != "" { + objName = fmt.Sprintf(" (name: %s)", name) + } + + errs = append( + errs, + fmt.Errorf("could not get REST mapping for object %d in manifest with GVK %s%s: %w", i, gvk, objName, err), + ) continue } @@ -183,8 +203,11 @@ func (a *rbacPreAuthorizer) authorizeAttributesRecords(ctx context.Context, attr } func (a *rbacPreAuthorizer) attributesAllowed(ctx context.Context, attributesRecord authorizer.AttributesRecord) (bool, error) { - decision, _, err := a.authorizer.Authorize(ctx, attributesRecord) + decision, reason, err := a.authorizer.Authorize(ctx, attributesRecord) if err != nil { + if reason != "" { + return false, fmt.Errorf("%s: %w", reason, err) + } return false, err } return decision == authorizer.DecisionAllow, nil @@ -452,6 +475,9 @@ var fullAuthority = []rbacv1.PolicyRule{ } func hasAggregationRule(clusterRole *rbacv1.ClusterRole) bool { + // Currently, an aggregation rule is considered present only if it has one or more selectors. + // An empty slice of ClusterRoleSelectors means no selectors were provided, + // which does NOT imply "match all." return clusterRole.AggregationRule != nil && len(clusterRole.AggregationRule.ClusterRoleSelectors) > 0 } From cff38dec8d8b4306bc035ff16ec97936acd54cbd Mon Sep 17 00:00:00 2001 From: Tayler Geiger Date: Sat, 15 Mar 2025 14:22:12 -0500 Subject: [PATCH 15/67] Add tests for authorization/rbac.go Signed-off-by: Tayler Geiger --- .../authorization/rbac_test.go | 181 ++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 internal/operator-controller/authorization/rbac_test.go diff --git a/internal/operator-controller/authorization/rbac_test.go b/internal/operator-controller/authorization/rbac_test.go new file mode 100644 index 000000000..55c1ec30c --- /dev/null +++ b/internal/operator-controller/authorization/rbac_test.go @@ -0,0 +1,181 @@ +package authorization + +import ( + "context" + "fmt" + "strings" + "testing" + + "github.com/operator-framework/operator-controller/internal/operator-controller/features" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/api/meta/testrestmapper" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/authentication/user" + featuregatetesting "k8s.io/component-base/featuregate/testing" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +var ( + testManifest = `apiVersion: v1 +kind: Service +metadata: + name: test-service + namespace: test-namespace +spec: + clusterIP: None +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: test-extension-role + namespace: test-namespace +rules: +- apiGroups: ["*"] + resources: [serviceaccounts] + verbs: [watch] +- apiGroups: ["*"] + resources: [certificates] + verbs: [create] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: test-extension-binding + namespace: test-namespace +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: test-extension-role +subjects: +- kind: ServiceAccount + name: test-serviceaccount + namespace: test-namespace + ` + + saName = "test-serviceaccount" + ns = "test-namespace" + + objects = []client.Object{ + &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-namespace", + }, + }, + &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "admin-clusterrole-binding", + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: saName, + Namespace: ns, + }, + }, + RoleRef: rbacv1.RoleRef{ + Name: "admin-clusterrole", + Kind: "ClusterRole", + }, + }, + &corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-serviceaccount", + Namespace: "test-namespace", + }, + }, + } + + privilegedClusterRole = &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{ + Name: "admin-clusterrole", + }, + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{"*"}, + Resources: []string{"*"}, + Verbs: []string{"*"}, + }, + }, + } + + limitedClusterRole = &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{ + Name: "admin-clusterrole", + }, + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{""}, + Verbs: []string{""}, + }, + }, + } + + escalatingClusterRole = &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{ + Name: "admin-clusterrole", + }, + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{"*"}, + Resources: []string{"serviceaccounts", "services"}, + Verbs: []string{"*"}, + }, + { + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"roles", "clusterroles", "rolebindings", "clusterrolebindings"}, + Verbs: []string{"get", "patch", "watch", "list", "create", "update", "delete", "escalate", "bind"}, + }, + }, + } +) + +func setupFakeClient(role client.Object) client.Client { + s := runtime.NewScheme() + _ = corev1.AddToScheme(s) + _ = rbacv1.AddToScheme(s) + restMapper := testrestmapper.TestOnlyStaticRESTMapper(s) + // restMapper := meta.NewDefaultRESTMapper(nil) + fakeClientBuilder := fake.NewClientBuilder().WithObjects(append(objects, role)...).WithRESTMapper(restMapper) + return fakeClientBuilder.Build() +} + +func TestPreAuthorize_Success(t *testing.T) { + t.Run("preauthorize succeeds with no missing rbac rules", func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.PreflightPermissions, true) + fakeClient := setupFakeClient(privilegedClusterRole) + preAuth := NewRBACPreAuthorizer(fakeClient) + testServiceAccount := user.DefaultInfo{Name: fmt.Sprintf("system:serviceaccount:%s:%s", ns, saName)} + missingRules, err := preAuth.PreAuthorize(context.TODO(), &testServiceAccount, strings.NewReader(testManifest)) + require.NoError(t, err) + require.Equal(t, map[string][]rbacv1.PolicyRule{}, missingRules) + }) +} + +func TestPreAuthorize_Failure(t *testing.T) { + t.Run("preauthorize failes with missing rbac rules", func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.PreflightPermissions, true) + fakeClient := setupFakeClient(limitedClusterRole) + preAuth := NewRBACPreAuthorizer(fakeClient) + testServiceAccount := user.DefaultInfo{Name: fmt.Sprintf("system:serviceaccount:%s:%s", ns, saName)} + missingRules, err := preAuth.PreAuthorize(context.TODO(), &testServiceAccount, strings.NewReader(testManifest)) + require.Error(t, err) + require.NotEqual(t, map[string][]rbacv1.PolicyRule{}, missingRules) + }) +} + +func TestPreAuthorize_CheckEscalation(t *testing.T) { + t.Run("preauthorize succeeds with no missing rbac rules", func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.PreflightPermissions, true) + fakeClient := setupFakeClient(escalatingClusterRole) + preAuth := NewRBACPreAuthorizer(fakeClient) + testServiceAccount := user.DefaultInfo{Name: fmt.Sprintf("system:serviceaccount:%s:%s", ns, saName)} + missingRules, err := preAuth.PreAuthorize(context.TODO(), &testServiceAccount, strings.NewReader(testManifest)) + require.NoError(t, err) + require.Equal(t, map[string][]rbacv1.PolicyRule{}, missingRules) + }) +} From 923affbf9dcf8bd814ce6638000fc88a35cc4e8a Mon Sep 17 00:00:00 2001 From: Brett Tofel Date: Mon, 17 Mar 2025 15:31:55 -0400 Subject: [PATCH 16/67] Move k8sMaintainer code to its own dir Signed-off-by: Brett Tofel --- Makefile | 2 +- .../README.md} | 0 .../main.go} | 27 ++++++++++++++++++- 3 files changed, 27 insertions(+), 2 deletions(-) rename hack/tools/{k8sMaintainer_doc.md => k8smaintainer/README.md} (100%) rename hack/tools/{k8sMaintainer.go => k8smaintainer/main.go} (93%) diff --git a/Makefile b/Makefile index 306ffb926..7670fb8f2 100644 --- a/Makefile +++ b/Makefile @@ -122,7 +122,7 @@ lint-custom: custom-linter-build #EXHELP Call custom linter for the project .PHONY: k8s-maintainer #EXHELP this tool also calls `go mod tidy` but also allows us maintain k8s.io/kubernetes` changes bumping related staging modules (e.g., `k8s.io/api`, `k8s.io/apimachinery) as needed k8s-maintainer: - go run hack/tools/k8sMaintainer.go + go run hack/tools/k8smaintainer/main.go .PHONY: tidy tidy: k8s-maintainer #HELP Update dependencies. diff --git a/hack/tools/k8sMaintainer_doc.md b/hack/tools/k8smaintainer/README.md similarity index 100% rename from hack/tools/k8sMaintainer_doc.md rename to hack/tools/k8smaintainer/README.md diff --git a/hack/tools/k8sMaintainer.go b/hack/tools/k8smaintainer/main.go similarity index 93% rename from hack/tools/k8sMaintainer.go rename to hack/tools/k8smaintainer/main.go index 10c917394..2ed6d52f8 100644 --- a/hack/tools/k8sMaintainer.go +++ b/hack/tools/k8smaintainer/main.go @@ -28,12 +28,37 @@ func main() { flag.BoolVar(&debug, "debug", false, "Enable debug output") flag.Parse() - if err := fixGoMod("go.mod"); err != nil { + mainGoMod := getMainGoModPath() + + if err := fixGoMod(mainGoMod); err != nil { fmt.Fprintf(os.Stderr, "fixGoMod failed: %v\n", err) os.Exit(1) } } +func getMainGoModPath() string { + rootDir := findProjectRoot() + return fmt.Sprintf("%s/go.mod", rootDir) +} + +func findProjectRoot() string { + cwd, err := os.Getwd() + if err != nil { + fmt.Fprintf(os.Stderr, "failed to get working directory: %v\n", err) + os.Exit(1) + } + + for cwd != "/" { + if _, err := os.Stat(fmt.Sprintf("%s/go.mod", cwd)); err == nil { + return cwd + } + cwd = cwd[:strings.LastIndex(cwd, "/")] + } + fmt.Fprintln(os.Stderr, "Error: Could not find project root with go.mod") + os.Exit(1) + return "" +} + // fixGoMod is the main entrypoint. It does a 2‐phase approach: // // Remove old k8s.io/* replace lines, rewrite + tidy so they’re really gone. From c368e4ba68e214957aed2d470a7a7e32d17ca743 Mon Sep 17 00:00:00 2001 From: Brett Tofel Date: Tue, 18 Mar 2025 09:29:27 -0400 Subject: [PATCH 17/67] Run k8smaintainer code post rebase Signed-off-by: Brett Tofel --- go.mod | 67 +++++++++++++++++++++++++++++----------------------------- 1 file changed, 33 insertions(+), 34 deletions(-) diff --git a/go.mod b/go.mod index a89e9a201..2cefdbcb8 100644 --- a/go.mod +++ b/go.mod @@ -237,17 +237,16 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.32.2 - k8s.io/apiextensions-apiserver v0.32.2 - k8s.io/apimachinery v0.32.2 - k8s.io/apiserver v0.32.2 - k8s.io/cli-runtime v0.32.2 - k8s.io/client-go v0.32.2 - k8s.io/component-base v0.32.2 - k8s.io/component-helpers v0.32.2 // indirect - k8s.io/controller-manager v0.32.2 // indirect - k8s.io/kubectl v0.32.2 // indirect - k8s.io/kubernetes v1.32.2 + k8s.io/api v0.32.3 + k8s.io/apiextensions-apiserver v0.32.3 + k8s.io/apimachinery v0.32.3 + k8s.io/apiserver v0.32.3 + k8s.io/cli-runtime v0.32.3 + k8s.io/client-go v0.32.3 + k8s.io/component-base v0.32.3 + k8s.io/component-helpers v0.32.3 // indirect + k8s.io/controller-manager v0.32.3 // indirect + k8s.io/kubectl v0.32.3 // indirect oras.land/oras-go v1.2.5 // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0 // indirect sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect @@ -258,7 +257,7 @@ require ( replace k8s.io/api => k8s.io/api v0.32.2 -replace k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.32.2 +replace k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.32.3 replace k8s.io/apimachinery => k8s.io/apimachinery v0.32.2 @@ -268,48 +267,48 @@ replace k8s.io/cli-runtime => k8s.io/cli-runtime v0.32.2 replace k8s.io/client-go => k8s.io/client-go v0.32.2 -replace k8s.io/cloud-provider => k8s.io/cloud-provider v0.32.2 +replace k8s.io/cloud-provider => k8s.io/cloud-provider v0.32.3 -replace k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.32.2 +replace k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.32.3 -replace k8s.io/code-generator => k8s.io/code-generator v0.32.2 +replace k8s.io/code-generator => k8s.io/code-generator v0.32.3 replace k8s.io/component-base => k8s.io/component-base v0.32.2 -replace k8s.io/component-helpers => k8s.io/component-helpers v0.32.2 +replace k8s.io/component-helpers => k8s.io/component-helpers v0.32.3 -replace k8s.io/controller-manager => k8s.io/controller-manager v0.32.2 +replace k8s.io/controller-manager => k8s.io/controller-manager v0.32.3 -replace k8s.io/cri-api => k8s.io/cri-api v0.32.2 +replace k8s.io/cri-api => k8s.io/cri-api v0.32.3 -replace k8s.io/cri-client => k8s.io/cri-client v0.32.2 +replace k8s.io/cri-client => k8s.io/cri-client v0.32.3 -replace k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.32.2 +replace k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.32.3 -replace k8s.io/dynamic-resource-allocation => k8s.io/dynamic-resource-allocation v0.32.2 +replace k8s.io/dynamic-resource-allocation => k8s.io/dynamic-resource-allocation v0.32.3 -replace k8s.io/endpointslice => k8s.io/endpointslice v0.32.2 +replace k8s.io/endpointslice => k8s.io/endpointslice v0.32.3 -replace k8s.io/externaljwt => k8s.io/externaljwt v0.32.2 +replace k8s.io/externaljwt => k8s.io/externaljwt v0.32.3 -replace k8s.io/kms => k8s.io/kms v0.32.2 +replace k8s.io/kms => k8s.io/kms v0.32.3 -replace k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.32.2 +replace k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.32.3 -replace k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.32.2 +replace k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.32.3 -replace k8s.io/kube-proxy => k8s.io/kube-proxy v0.32.2 +replace k8s.io/kube-proxy => k8s.io/kube-proxy v0.32.3 -replace k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.32.2 +replace k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.32.3 -replace k8s.io/kubectl => k8s.io/kubectl v0.32.2 +replace k8s.io/kubectl => k8s.io/kubectl v0.32.3 -replace k8s.io/kubelet => k8s.io/kubelet v0.32.2 +replace k8s.io/kubelet => k8s.io/kubelet v0.32.3 -replace k8s.io/metrics => k8s.io/metrics v0.32.2 +replace k8s.io/metrics => k8s.io/metrics v0.32.3 -replace k8s.io/mount-utils => k8s.io/mount-utils v0.32.2 +replace k8s.io/mount-utils => k8s.io/mount-utils v0.32.3 -replace k8s.io/pod-security-admission => k8s.io/pod-security-admission v0.32.2 +replace k8s.io/pod-security-admission => k8s.io/pod-security-admission v0.32.3 -replace k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.32.2 +replace k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.32.3 From 956ed8646ee3de273c44c75452539eccd0ea6e84 Mon Sep 17 00:00:00 2001 From: Brett Tofel Date: Tue, 18 Mar 2025 09:31:18 -0400 Subject: [PATCH 18/67] Lint acceptable format for rbac_test.go Signed-off-by: Brett Tofel --- internal/operator-controller/authorization/rbac_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/operator-controller/authorization/rbac_test.go b/internal/operator-controller/authorization/rbac_test.go index 55c1ec30c..b6eb27dae 100644 --- a/internal/operator-controller/authorization/rbac_test.go +++ b/internal/operator-controller/authorization/rbac_test.go @@ -6,7 +6,6 @@ import ( "strings" "testing" - "github.com/operator-framework/operator-controller/internal/operator-controller/features" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" @@ -17,6 +16,8 @@ import ( featuregatetesting "k8s.io/component-base/featuregate/testing" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" + + "github.com/operator-framework/operator-controller/internal/operator-controller/features" ) var ( From fc34755d3d273232e9887abeda259e817b762604 Mon Sep 17 00:00:00 2001 From: Tayler Geiger Date: Sat, 15 Mar 2025 14:22:12 -0500 Subject: [PATCH 19/67] Add tests for authorization/rbac.go Signed-off-by: Tayler Geiger --- internal/operator-controller/authorization/rbac_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/operator-controller/authorization/rbac_test.go b/internal/operator-controller/authorization/rbac_test.go index b6eb27dae..55c1ec30c 100644 --- a/internal/operator-controller/authorization/rbac_test.go +++ b/internal/operator-controller/authorization/rbac_test.go @@ -6,6 +6,7 @@ import ( "strings" "testing" + "github.com/operator-framework/operator-controller/internal/operator-controller/features" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" @@ -16,8 +17,6 @@ import ( featuregatetesting "k8s.io/component-base/featuregate/testing" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" - - "github.com/operator-framework/operator-controller/internal/operator-controller/features" ) var ( From dfcd04bff8168187d80bf070fa136c374f82bb97 Mon Sep 17 00:00:00 2001 From: Brett Tofel Date: Tue, 18 Mar 2025 10:46:07 -0400 Subject: [PATCH 20/67] Refactor inline feature gate check Signed-off-by: Brett Tofel --- cmd/operator-controller/main.go | 9 +++++---- internal/operator-controller/applier/helm.go | 12 ++++++------ 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/cmd/operator-controller/main.go b/cmd/operator-controller/main.go index 942fbdca3..d07d1dad7 100644 --- a/cmd/operator-controller/main.go +++ b/cmd/operator-controller/main.go @@ -417,10 +417,11 @@ func run() error { } helmApplier := &applier.Helm{ - ActionClientGetter: acg, - Preflights: preflights, - BundleToHelmChartFn: convert.RegistryV1ToHelmChart, - PreAuthorizer: authorization.NewRBACPreAuthorizer(mgr.GetClient()), + ActionClientGetter: acg, + Preflights: preflights, + BundleToHelmChartFn: convert.RegistryV1ToHelmChart, + PreAuthorizer: authorization.NewRBACPreAuthorizer(mgr.GetClient()), + EnablePreflightPermissions: features.OperatorControllerFeatureGate.Enabled(features.PreflightPermissions), } cm := contentmanager.NewManager(clientRestConfigMapper, mgr.GetConfig(), mgr.GetRESTMapper()) diff --git a/internal/operator-controller/applier/helm.go b/internal/operator-controller/applier/helm.go index fdf81105a..be12f0766 100644 --- a/internal/operator-controller/applier/helm.go +++ b/internal/operator-controller/applier/helm.go @@ -27,7 +27,6 @@ import ( ocv1 "github.com/operator-framework/operator-controller/api/v1" "github.com/operator-framework/operator-controller/internal/operator-controller/authorization" - "github.com/operator-framework/operator-controller/internal/operator-controller/features" "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/preflights/crdupgradesafety" "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/util" ) @@ -58,10 +57,11 @@ type Preflight interface { type BundleToHelmChartFn func(rv1 fs.FS, installNamespace string, watchNamespace string) (*chart.Chart, error) type Helm struct { - ActionClientGetter helmclient.ActionClientGetter - Preflights []Preflight - PreAuthorizer authorization.PreAuthorizer - BundleToHelmChartFn BundleToHelmChartFn + ActionClientGetter helmclient.ActionClientGetter + Preflights []Preflight + PreAuthorizer authorization.PreAuthorizer + BundleToHelmChartFn BundleToHelmChartFn + EnablePreflightPermissions bool } // shouldSkipPreflight is a helper to determine if the preflight check is CRDUpgradeSafety AND @@ -95,7 +95,7 @@ func (h *Helm) Apply(ctx context.Context, contentFS fs.FS, ext *ocv1.ClusterExte labels: objectLabels, } - if features.OperatorControllerFeatureGate.Enabled(features.PreflightPermissions) { + if h.EnablePreflightPermissions { tmplRel, err := h.template(ctx, ext, chrt, values, post) if err != nil { return nil, "", fmt.Errorf("failed to get release state using client-only dry-run: %w", err) From 9461ba1d89b9bfcad9f2a578c93c7a8ed1aa1e21 Mon Sep 17 00:00:00 2001 From: Tayler Geiger Date: Tue, 18 Mar 2025 10:11:21 -0500 Subject: [PATCH 21/67] Change PreAuthorize() return value to []ScopedPolicyRules Use []ScopedPolicyRules struct for first return value in PreAuthorize() to avoid issues with random iteration order in previous map return value. Signed-off-by: Tayler Geiger --- internal/operator-controller/applier/helm.go | 6 +++--- internal/operator-controller/applier/helm_test.go | 4 ++-- .../operator-controller/authorization/rbac.go | 15 +++++++++++---- .../authorization/rbac_test.go | 6 +++--- 4 files changed, 19 insertions(+), 12 deletions(-) diff --git a/internal/operator-controller/applier/helm.go b/internal/operator-controller/applier/helm.go index be12f0766..1144ff789 100644 --- a/internal/operator-controller/applier/helm.go +++ b/internal/operator-controller/applier/helm.go @@ -107,9 +107,9 @@ func (h *Helm) Apply(ctx context.Context, contentFS fs.FS, ext *ocv1.ClusterExte var preAuthErrors []error if len(missingRules) > 0 { var missingRuleDescriptions []string - for ns, policyRules := range missingRules { - for _, rule := range policyRules { - missingRuleDescriptions = append(missingRuleDescriptions, ruleDescription(ns, rule)) + for _, policyRules := range missingRules { + for _, rule := range policyRules.MissingRules { + missingRuleDescriptions = append(missingRuleDescriptions, ruleDescription(policyRules.Namespace, rule)) } } slices.Sort(missingRuleDescriptions) diff --git a/internal/operator-controller/applier/helm_test.go b/internal/operator-controller/applier/helm_test.go index e62101174..bb48ce15f 100644 --- a/internal/operator-controller/applier/helm_test.go +++ b/internal/operator-controller/applier/helm_test.go @@ -16,7 +16,6 @@ import ( "helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/storage/driver" corev1 "k8s.io/api/core/v1" - rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apiserver/pkg/authentication/user" featuregatetesting "k8s.io/component-base/featuregate/testing" @@ -26,6 +25,7 @@ import ( ocv1 "github.com/operator-framework/operator-controller/api/v1" "github.com/operator-framework/operator-controller/internal/operator-controller/applier" + "github.com/operator-framework/operator-controller/internal/operator-controller/authorization" "github.com/operator-framework/operator-controller/internal/operator-controller/features" "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/convert" ) @@ -41,7 +41,7 @@ func (p *noOpPreAuthorizer) PreAuthorize( ctx context.Context, manifestManager user.Info, manifestReader io.Reader, -) (map[string][]rbacv1.PolicyRule, error) { +) ([]authorization.ScopedPolicyRules, error) { // No-op: always return an empty map and no error return nil, nil } diff --git a/internal/operator-controller/authorization/rbac.go b/internal/operator-controller/authorization/rbac.go index 1080f9fb3..2405a8333 100644 --- a/internal/operator-controller/authorization/rbac.go +++ b/internal/operator-controller/authorization/rbac.go @@ -30,7 +30,12 @@ import ( ) type PreAuthorizer interface { - PreAuthorize(ctx context.Context, manifestManager user.Info, manifestReader io.Reader) (map[string][]rbacv1.PolicyRule, error) + PreAuthorize(ctx context.Context, manifestManager user.Info, manifestReader io.Reader) ([]ScopedPolicyRules, error) +} + +type ScopedPolicyRules struct { + Namespace string + MissingRules []rbacv1.PolicyRule } var ( @@ -60,7 +65,8 @@ func NewRBACPreAuthorizer(cl client.Client) PreAuthorizer { // - nil: indicates that the authorization check passed and the operation is permitted. // - non-nil error: indicates that the authorization failed (either due to insufficient permissions // or an error encountered during the check), the error provides a slice of several failures at once. -func (a *rbacPreAuthorizer) PreAuthorize(ctx context.Context, manifestManager user.Info, manifestReader io.Reader) (map[string][]rbacv1.PolicyRule, error) { +func (a *rbacPreAuthorizer) PreAuthorize(ctx context.Context, manifestManager user.Info, manifestReader io.Reader) ([]ScopedPolicyRules, error) { + var allMissingPolicyRules = []ScopedPolicyRules{} dm, err := a.decodeManifest(manifestReader) if err != nil { return nil, err @@ -98,12 +104,13 @@ func (a *rbacPreAuthorizer) PreAuthorize(ctx context.Context, manifestManager us } sortableRules := rbacv1helpers.SortableRuleSlice(missingRules[ns]) sort.Sort(sortableRules) + allMissingPolicyRules = append(allMissingPolicyRules, ScopedPolicyRules{Namespace: ns, MissingRules: sortableRules}) } if len(preAuthEvaluationErrors) > 0 { - return missingRules, fmt.Errorf("authorization evaluation errors: %w", errors.Join(preAuthEvaluationErrors...)) + return allMissingPolicyRules, fmt.Errorf("authorization evaluation errors: %w", errors.Join(preAuthEvaluationErrors...)) } - return missingRules, nil + return allMissingPolicyRules, nil } func (a *rbacPreAuthorizer) decodeManifest(manifestReader io.Reader) (*decodedManifest, error) { diff --git a/internal/operator-controller/authorization/rbac_test.go b/internal/operator-controller/authorization/rbac_test.go index 55c1ec30c..5965186a0 100644 --- a/internal/operator-controller/authorization/rbac_test.go +++ b/internal/operator-controller/authorization/rbac_test.go @@ -152,7 +152,7 @@ func TestPreAuthorize_Success(t *testing.T) { testServiceAccount := user.DefaultInfo{Name: fmt.Sprintf("system:serviceaccount:%s:%s", ns, saName)} missingRules, err := preAuth.PreAuthorize(context.TODO(), &testServiceAccount, strings.NewReader(testManifest)) require.NoError(t, err) - require.Equal(t, map[string][]rbacv1.PolicyRule{}, missingRules) + require.Equal(t, []ScopedPolicyRules{}, missingRules) }) } @@ -164,7 +164,7 @@ func TestPreAuthorize_Failure(t *testing.T) { testServiceAccount := user.DefaultInfo{Name: fmt.Sprintf("system:serviceaccount:%s:%s", ns, saName)} missingRules, err := preAuth.PreAuthorize(context.TODO(), &testServiceAccount, strings.NewReader(testManifest)) require.Error(t, err) - require.NotEqual(t, map[string][]rbacv1.PolicyRule{}, missingRules) + require.NotEqual(t, []ScopedPolicyRules{}, missingRules) }) } @@ -176,6 +176,6 @@ func TestPreAuthorize_CheckEscalation(t *testing.T) { testServiceAccount := user.DefaultInfo{Name: fmt.Sprintf("system:serviceaccount:%s:%s", ns, saName)} missingRules, err := preAuth.PreAuthorize(context.TODO(), &testServiceAccount, strings.NewReader(testManifest)) require.NoError(t, err) - require.Equal(t, map[string][]rbacv1.PolicyRule{}, missingRules) + require.Equal(t, []ScopedPolicyRules{}, missingRules) }) } From a6e1203d4078a588bd2ee12a091572c5753e3ac8 Mon Sep 17 00:00:00 2001 From: Brett Tofel Date: Tue, 18 Mar 2025 11:26:10 -0400 Subject: [PATCH 22/67] Lint acceptable format for rbac_test.go (take 2) Signed-off-by: Brett Tofel --- internal/operator-controller/authorization/rbac_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/operator-controller/authorization/rbac_test.go b/internal/operator-controller/authorization/rbac_test.go index 5965186a0..b37faee7c 100644 --- a/internal/operator-controller/authorization/rbac_test.go +++ b/internal/operator-controller/authorization/rbac_test.go @@ -6,7 +6,6 @@ import ( "strings" "testing" - "github.com/operator-framework/operator-controller/internal/operator-controller/features" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" @@ -17,6 +16,8 @@ import ( featuregatetesting "k8s.io/component-base/featuregate/testing" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" + + "github.com/operator-framework/operator-controller/internal/operator-controller/features" ) var ( From a5a1c985c1b77c21a95dca77f86d1c86e2c392ca Mon Sep 17 00:00:00 2001 From: Brett Tofel Date: Wed, 19 Mar 2025 16:47:02 -0400 Subject: [PATCH 23/67] Add fakeStorage dry run for escalationCheck Signed-off-by: Brett Tofel --- .../operator-controller/authorization/rbac.go | 272 +++++++++--------- .../authorization/rbac_test.go | 129 ++++++++- 2 files changed, 256 insertions(+), 145 deletions(-) diff --git a/internal/operator-controller/authorization/rbac.go b/internal/operator-controller/authorization/rbac.go index 2405a8333..1bba5c4bf 100644 --- a/internal/operator-controller/authorization/rbac.go +++ b/internal/operator-controller/authorization/rbac.go @@ -9,8 +9,8 @@ import ( "sort" rbacv1 "k8s.io/api/rbac/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" + metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -18,14 +18,19 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/sets" apimachyaml "k8s.io/apimachinery/pkg/util/yaml" + "k8s.io/apimachinery/pkg/watch" "k8s.io/apiserver/pkg/authentication/user" "k8s.io/apiserver/pkg/authorization/authorizer" "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apiserver/pkg/registry/rest" rbacinternal "k8s.io/kubernetes/pkg/apis/rbac" rbacv1helpers "k8s.io/kubernetes/pkg/apis/rbac/v1" - rbacregistry "k8s.io/kubernetes/pkg/registry/rbac" + "k8s.io/kubernetes/pkg/registry/rbac/clusterrole/policybased" + policybasedClusterRoleBinding "k8s.io/kubernetes/pkg/registry/rbac/clusterrolebinding/policybased" + policybasedRole "k8s.io/kubernetes/pkg/registry/rbac/role/policybased" + policybasedRoleBinding "k8s.io/kubernetes/pkg/registry/rbac/rolebinding/policybased" "k8s.io/kubernetes/pkg/registry/rbac/validation" - rbac "k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac" + "k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -58,59 +63,61 @@ func NewRBACPreAuthorizer(cl client.Client) PreAuthorizer { } // PreAuthorize validates whether the current user/request satisfies the necessary permissions -// as defined by the RBAC policy. It examines the user’s roles, resource identifiers, and -// the intended action to determine if the operation is allowed. -// -// Return Value: -// - nil: indicates that the authorization check passed and the operation is permitted. -// - non-nil error: indicates that the authorization failed (either due to insufficient permissions -// or an error encountered during the check), the error provides a slice of several failures at once. +// as defined by the RBAC policy. It decodes the manifest, constructs authorization records, +// performs attribute checks, and then uses escalationChecker (which delegates to upstream logic) +// to verify that no privilege escalation is occurring. func (a *rbacPreAuthorizer) PreAuthorize(ctx context.Context, manifestManager user.Info, manifestReader io.Reader) ([]ScopedPolicyRules, error) { - var allMissingPolicyRules = []ScopedPolicyRules{} + var allMissingPolicyRules []ScopedPolicyRules dm, err := a.decodeManifest(manifestReader) if err != nil { return nil, err } attributesRecords := dm.asAuthorizationAttributesRecordsForUser(manifestManager) - var preAuthEvaluationErrors []error + // Compute missing rules using your original logic. missingRules, err := a.authorizeAttributesRecords(ctx, attributesRecords) if err != nil { - preAuthEvaluationErrors = append(preAuthEvaluationErrors, err) + // If there are errors here, you might choose to log them or collect them. + // We'll still try to run the upstream escalation check. } + var escalationErrors []error ec := escalationChecker{ authorizer: a.authorizer, ruleResolver: a.ruleResolver, extraClusterRoles: dm.clusterRoles, extraRoles: dm.roles, } - for _, obj := range dm.rbacObjects() { if err := ec.checkEscalation(ctx, manifestManager, obj); err != nil { - // In Kubernetes 1.32.2 the specialized PrivilegeEscalationError is gone. - // Instead, we simply collect the error. - preAuthEvaluationErrors = append(preAuthEvaluationErrors, err) + // If err is a composite error (errors.Join), unwrap it and add each underlying error. + var joinErr interface{ Unwrap() []error } + if errors.As(err, &joinErr) { + for _, singleErr := range joinErr.Unwrap() { + escalationErrors = append(escalationErrors, singleErr) + } + } else { + escalationErrors = append(escalationErrors, err) + } } } - for ns, nsMissingRules := range missingRules { - // NOTE: Although CompactRules is defined to return an error, its current implementation - // never produces a non-nil error. This is because all operations within the function are - // designed to succeed under current conditions. In the future, if more complex rule validations - // are introduced, this behavior may change and proper error handling will be required. - if compactMissingRules, err := validation.CompactRules(nsMissingRules); err == nil { - missingRules[ns] = compactMissingRules + // If escalation check fails, return the detailed missing rules along with the error. + if len(escalationErrors) > 0 { + // Process the missingRules map into a slice as before. + for ns, nsMissingRules := range missingRules { + if compactMissingRules, err := validation.CompactRules(nsMissingRules); err == nil { + missingRules[ns] = compactMissingRules + } + sortableRules := rbacv1helpers.SortableRuleSlice(missingRules[ns]) + sort.Sort(sortableRules) + allMissingPolicyRules = append(allMissingPolicyRules, ScopedPolicyRules{Namespace: ns, MissingRules: sortableRules}) } - sortableRules := rbacv1helpers.SortableRuleSlice(missingRules[ns]) - sort.Sort(sortableRules) - allMissingPolicyRules = append(allMissingPolicyRules, ScopedPolicyRules{Namespace: ns, MissingRules: sortableRules}) + return allMissingPolicyRules, fmt.Errorf("escalation check failed: %w", errors.Join(escalationErrors...)) } - if len(preAuthEvaluationErrors) > 0 { - return allMissingPolicyRules, fmt.Errorf("authorization evaluation errors: %w", errors.Join(preAuthEvaluationErrors...)) - } - return allMissingPolicyRules, nil + // If the escalation check passed, override any computed missing rules (since the final decision is allowed). + return []ScopedPolicyRules{}, nil } func (a *rbacPreAuthorizer) decodeManifest(manifestReader io.Reader) (*decodedManifest, error) { @@ -143,7 +150,6 @@ func (a *rbacPreAuthorizer) decodeManifest(manifestReader io.Reader) (*decodedMa if name := uObj.GetName(); name != "" { objName = fmt.Sprintf(" (name: %s)", name) } - errs = append( errs, fmt.Errorf("could not get REST mapping for object %d in manifest with GVK %s%s: %w", i, gvk, objName, err), @@ -184,6 +190,7 @@ func (a *rbacPreAuthorizer) decodeManifest(manifestReader io.Reader) (*decodedMa } dm.roleBindings[client.ObjectKeyFromObject(obj)] = *obj } + i++ } if len(errs) > 0 { return nil, errors.Join(errs...) @@ -192,10 +199,8 @@ func (a *rbacPreAuthorizer) decodeManifest(manifestReader io.Reader) (*decodedMa } func (a *rbacPreAuthorizer) authorizeAttributesRecords(ctx context.Context, attributesRecords []authorizer.AttributesRecord) (map[string][]rbacv1.PolicyRule, error) { - var ( - missingRules = map[string][]rbacv1.PolicyRule{} - errs []error - ) + missingRules := map[string][]rbacv1.PolicyRule{} + var errs []error for _, ar := range attributesRecords { allow, err := a.attributesAllowed(ctx, ar) if err != nil { @@ -229,18 +234,15 @@ func policyRuleFromAttributesRecord(attributesRecord authorizer.AttributesRecord pr.NonResourceURLs = []string{attributesRecord.Path} return pr } - pr.APIGroups = []string{attributesRecord.APIGroup} if attributesRecord.Name != "" { pr.ResourceNames = []string{attributesRecord.Name} } - r := attributesRecord.Resource if attributesRecord.Subresource != "" { r += "/" + attributesRecord.Subresource } pr.Resources = []string{r} - return pr } @@ -255,16 +257,20 @@ type decodedManifest struct { func (dm *decodedManifest) rbacObjects() []client.Object { objects := make([]client.Object, 0, len(dm.clusterRoles)+len(dm.roles)+len(dm.clusterRoleBindings)+len(dm.roleBindings)) for obj := range maps.Values(dm.clusterRoles) { - objects = append(objects, &obj) + o := obj // avoid aliasing + objects = append(objects, &o) } for obj := range maps.Values(dm.roles) { - objects = append(objects, &obj) + o := obj + objects = append(objects, &o) } for obj := range maps.Values(dm.clusterRoleBindings) { - objects = append(objects, &obj) + o := obj + objects = append(objects, &o) } for obj := range maps.Values(dm.roleBindings) { - objects = append(objects, &obj) + o := obj + objects = append(objects, &o) } return objects } @@ -358,134 +364,116 @@ type escalationChecker struct { extraClusterRoles map[types.NamespacedName]rbacv1.ClusterRole } +// checkEscalation delegates the escalation check to upstream storage by performing +// a dry-run Create call on the appropriate storage for the RBAC object type. func (ec *escalationChecker) checkEscalation(ctx context.Context, manifestManager user.Info, obj client.Object) error { + // Set up the context with user and namespace. ctx = request.WithUser(request.WithNamespace(ctx, obj.GetNamespace()), manifestManager) + noOpValidate := func(ctx context.Context, obj runtime.Object) error { return nil } + opts := &metav1.CreateOptions{DryRun: []string{metav1.DryRunAll}} + switch v := obj.(type) { - case *rbacv1.Role: - ctx = request.WithRequestInfo(ctx, &request.RequestInfo{APIGroup: rbacv1.GroupName, Resource: "roles", IsResourceRequest: true}) - return ec.checkRoleEscalation(ctx, v) - case *rbacv1.RoleBinding: - ctx = request.WithRequestInfo(ctx, &request.RequestInfo{APIGroup: rbacv1.GroupName, Resource: "rolebindings", IsResourceRequest: true}) - return ec.checkRoleBindingEscalation(ctx, v) case *rbacv1.ClusterRole: - ctx = request.WithRequestInfo(ctx, &request.RequestInfo{APIGroup: rbacv1.GroupName, Resource: "clusterroles", IsResourceRequest: true}) - return ec.checkClusterRoleEscalation(ctx, v) - case *rbacv1.ClusterRoleBinding: - ctx = request.WithRequestInfo(ctx, &request.RequestInfo{APIGroup: rbacv1.GroupName, Resource: "clusterrolebindings", IsResourceRequest: true}) - return ec.checkClusterRoleBindingEscalation(ctx, v) - default: - return fmt.Errorf("unknown object type %T", v) - } -} - -func (ec *escalationChecker) checkClusterRoleEscalation(ctx context.Context, clusterRole *rbacv1.ClusterRole) error { - if rbacregistry.EscalationAllowed(ctx) || rbacregistry.RoleEscalationAuthorized(ctx, ec.authorizer) { - return nil - } - - // to set the aggregation rule, since it can gather anything, requires * on *.* - if hasAggregationRule(clusterRole) { - if err := validation.ConfirmNoEscalation(ctx, ec.ruleResolver, fullAuthority); err != nil { - return fmt.Errorf("must have cluster-admin privileges to use an aggregationRule: %w", err) + // Merge extra ClusterRole rules if present. + key := types.NamespacedName{Name: v.Name} + if extra, ok := ec.extraClusterRoles[key]; ok { + v.Rules = append(v.Rules, extra.Rules...) } - } + // Convert external ClusterRole to internal representation. + var internalClusterRole rbacinternal.ClusterRole + if err := rbacv1helpers.Convert_v1_ClusterRole_To_rbac_ClusterRole(v, &internalClusterRole, nil); err != nil { + return err + } + storage := policybased.NewStorage(&FakeStandardStorage{}, ec.authorizer, ec.ruleResolver) + _, err := storage.Create(ctx, &internalClusterRole, noOpValidate, opts) + return err - if err := validation.ConfirmNoEscalation(ctx, ec.ruleResolver, clusterRole.Rules); err != nil { + case *rbacv1.ClusterRoleBinding: + // Convert external ClusterRoleBinding to internal representation. + var internalClusterRoleBinding rbacinternal.ClusterRoleBinding + if err := rbacv1helpers.Convert_v1_ClusterRoleBinding_To_rbac_ClusterRoleBinding(v, &internalClusterRoleBinding, nil); err != nil { + return err + } + storage := policybasedClusterRoleBinding.NewStorage(&FakeStandardStorage{}, ec.authorizer, ec.ruleResolver) + _, err := storage.Create(ctx, &internalClusterRoleBinding, noOpValidate, opts) return err - } - return nil -} -func (ec *escalationChecker) checkClusterRoleBindingEscalation(ctx context.Context, clusterRoleBinding *rbacv1.ClusterRoleBinding) error { - if rbacregistry.EscalationAllowed(ctx) { - return nil - } + case *rbacv1.Role: + // Merge extra Role rules if present. + key := types.NamespacedName{Namespace: v.Namespace, Name: v.Name} + if extra, ok := ec.extraRoles[key]; ok { + v.Rules = append(v.Rules, extra.Rules...) + } + // Convert external Role to internal representation. + var internalRole rbacinternal.Role + if err := rbacv1helpers.Convert_v1_Role_To_rbac_Role(v, &internalRole, nil); err != nil { + return err + } + storage := policybasedRole.NewStorage(&FakeStandardStorage{}, ec.authorizer, ec.ruleResolver) + _, err := storage.Create(ctx, &internalRole, noOpValidate, opts) + return err - roleRef := rbacinternal.RoleRef{} - err := rbacv1helpers.Convert_v1_RoleRef_To_rbac_RoleRef(&clusterRoleBinding.RoleRef, &roleRef, nil) - if err != nil { + case *rbacv1.RoleBinding: + // Convert external RoleBinding to internal representation. + var internalRoleBinding rbacinternal.RoleBinding + if err := rbacv1helpers.Convert_v1_RoleBinding_To_rbac_RoleBinding(v, &internalRoleBinding, nil); err != nil { + return err + } + storage := policybasedRoleBinding.NewStorage(&FakeStandardStorage{}, ec.authorizer, ec.ruleResolver) + _, err := storage.Create(ctx, &internalRoleBinding, noOpValidate, opts) return err - } - if rbacregistry.BindingAuthorized(ctx, roleRef, metav1.NamespaceNone, ec.authorizer) { - return nil + default: + return fmt.Errorf("unsupported object type %T", v) } +} - rules, err := ec.ruleResolver.GetRoleReferenceRules(ctx, clusterRoleBinding.RoleRef, metav1.NamespaceNone) - if err != nil && !apierrors.IsNotFound(err) { - return err - } +// FakeStandardStorage is a minimal fake implementation of rest.StandardStorage to satisfy required methods for dry-run operations. +type FakeStandardStorage struct{} - if clusterRoleBinding.RoleRef.Kind == "ClusterRole" { - if manifestClusterRole, ok := ec.extraClusterRoles[types.NamespacedName{Name: clusterRoleBinding.RoleRef.Name}]; ok { - rules = append(rules, manifestClusterRole.Rules...) - } - } +func (fs *FakeStandardStorage) New() runtime.Object { + return nil +} - if err := validation.ConfirmNoEscalation(ctx, ec.ruleResolver, rules); err != nil { - return err - } +func (fs *FakeStandardStorage) NewList() runtime.Object { return nil } -func (ec *escalationChecker) checkRoleEscalation(ctx context.Context, role *rbacv1.Role) error { - if rbacregistry.EscalationAllowed(ctx) || rbacregistry.RoleEscalationAuthorized(ctx, ec.authorizer) { - return nil - } +func (fs *FakeStandardStorage) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { + return nil, nil +} - rules := role.Rules - if err := validation.ConfirmNoEscalation(ctx, ec.ruleResolver, rules); err != nil { - return err - } - return nil +func (fs *FakeStandardStorage) List(ctx context.Context, options *metainternalversion.ListOptions) (runtime.Object, error) { + return nil, nil } -func (ec *escalationChecker) checkRoleBindingEscalation(ctx context.Context, roleBinding *rbacv1.RoleBinding) error { - if rbacregistry.EscalationAllowed(ctx) { - return nil - } +func (fs *FakeStandardStorage) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) { + // For dry-run escalation check, simply return the object. + return obj, nil +} - roleRef := rbacinternal.RoleRef{} - err := rbacv1helpers.Convert_v1_RoleRef_To_rbac_RoleRef(&roleBinding.RoleRef, &roleRef, nil) - if err != nil { - return err - } - if rbacregistry.BindingAuthorized(ctx, roleRef, roleBinding.Namespace, ec.authorizer) { - return nil - } +func (fs *FakeStandardStorage) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) { + return nil, false, nil +} - rules, err := ec.ruleResolver.GetRoleReferenceRules(ctx, roleBinding.RoleRef, roleBinding.Namespace) - if err != nil && !apierrors.IsNotFound(err) { - return err - } +func (fs *FakeStandardStorage) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) { + return nil, false, nil +} - switch roleRef.Kind { - case "ClusterRole": - if manifestClusterRole, ok := ec.extraClusterRoles[types.NamespacedName{Name: roleBinding.RoleRef.Name}]; ok { - rules = append(rules, manifestClusterRole.Rules...) - } - case "Role": - if manifestRole, ok := ec.extraRoles[types.NamespacedName{Namespace: roleBinding.Namespace, Name: roleBinding.RoleRef.Name}]; ok { - rules = append(rules, manifestRole.Rules...) - } - } +func (fs *FakeStandardStorage) Watch(ctx context.Context, options *metainternalversion.ListOptions) (watch.Interface, error) { + return nil, nil +} - if err := validation.ConfirmNoEscalation(ctx, ec.ruleResolver, rules); err != nil { - return err - } - return nil +func (fs *FakeStandardStorage) Destroy() { + return } -var fullAuthority = []rbacv1.PolicyRule{ - {Verbs: []string{"*"}, APIGroups: []string{"*"}, Resources: []string{"*"}}, - {Verbs: []string{"*"}, NonResourceURLs: []string{"*"}}, +func (fs *FakeStandardStorage) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { + return nil, nil } -func hasAggregationRule(clusterRole *rbacv1.ClusterRole) bool { - // Currently, an aggregation rule is considered present only if it has one or more selectors. - // An empty slice of ClusterRoleSelectors means no selectors were provided, - // which does NOT imply "match all." - return clusterRole.AggregationRule != nil && len(clusterRole.AggregationRule.ClusterRoleSelectors) > 0 +func (fs *FakeStandardStorage) DeleteCollection(ctx context.Context, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions, listOptions *metainternalversion.ListOptions) (runtime.Object, error) { + return nil, nil } func toPtrSlice[V any](in []V) []*V { diff --git a/internal/operator-controller/authorization/rbac_test.go b/internal/operator-controller/authorization/rbac_test.go index b37faee7c..9f50ef867 100644 --- a/internal/operator-controller/authorization/rbac_test.go +++ b/internal/operator-controller/authorization/rbac_test.go @@ -123,7 +123,7 @@ subjects: Rules: []rbacv1.PolicyRule{ { APIGroups: []string{"*"}, - Resources: []string{"serviceaccounts", "services"}, + Resources: []string{"serviceaccounts", "services", "certificates"}, Verbs: []string{"*"}, }, { @@ -174,9 +174,132 @@ func TestPreAuthorize_CheckEscalation(t *testing.T) { featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.PreflightPermissions, true) fakeClient := setupFakeClient(escalatingClusterRole) preAuth := NewRBACPreAuthorizer(fakeClient) - testServiceAccount := user.DefaultInfo{Name: fmt.Sprintf("system:serviceaccount:%s:%s", ns, saName)} - missingRules, err := preAuth.PreAuthorize(context.TODO(), &testServiceAccount, strings.NewReader(testManifest)) + testServiceAccount := user.DefaultInfo{ + Name: fmt.Sprintf("system:serviceaccount:%s:%s", ns, saName), + } + // Ensure the manifest contains only allowed rules so that the escalation check succeeds with no missing rules + modifiedManifest := strings.Replace(testManifest, `- apiGroups: ["*"] + resources: [certificates] + verbs: [create] +`, "", 1) + missingRules, err := preAuth.PreAuthorize(context.TODO(), &testServiceAccount, strings.NewReader(modifiedManifest)) require.NoError(t, err) require.Equal(t, []ScopedPolicyRules{}, missingRules) }) } + +func TestPreAuthorize_StorageLayerError(t *testing.T) { + t.Run("preauthorize fails with storage-layer error and computed missing rules", func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.PreflightPermissions, true) + // Use a client configured with a limited cluster role that should cause the escalation check to fail + fakeClient := setupFakeClient(limitedClusterRole) + preAuth := NewRBACPreAuthorizer(fakeClient) + testServiceAccount := user.DefaultInfo{ + Name: fmt.Sprintf("system:serviceaccount:%s:%s", ns, saName), + } + // Create a manifest that triggers escalation check failure. + manifest := `apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: test-escalation-role + namespace: test-namespace +rules: +- apiGroups: ["*"] + resources: ["*"] + verbs: ["*"] +` + missingRules, err := preAuth.PreAuthorize(context.TODO(), &testServiceAccount, strings.NewReader(manifest)) + require.Error(t, err) + // Instead of calling it opaque, we check that the error contains "forbidden:" to indicate it's from storage-layer checks + require.Contains(t, err.Error(), "forbidden:") + // Expect that our computed missing rules are non-empty (i.e. we have a detailed report). + require.NotEmpty(t, missingRules, "expected computed missing rules to be returned") + }) +} + +func TestPreAuthorize_MultipleEscalationErrors(t *testing.T) { + t.Run("preauthorize returns composite escalation error for multiple RBAC objects", func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.PreflightPermissions, true) + // Use a client configured with a limited cluster role that will fail escalation checks + fakeClient := setupFakeClient(limitedClusterRole) + preAuth := NewRBACPreAuthorizer(fakeClient) + testServiceAccount := user.DefaultInfo{ + Name: fmt.Sprintf("system:serviceaccount:%s:%s", ns, saName), + } + // Create a manifest with two RBAC objects that should both trigger escalation errors + manifest := `apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: test-escalation-role-1 + namespace: test-namespace +rules: +- apiGroups: ["*"] + resources: ["*"] + verbs: ["*"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: test-escalation-binding-1 + namespace: test-namespace +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: test-escalation-role-1 +subjects: +- kind: ServiceAccount + name: test-serviceaccount + namespace: test-namespace +` + missingRules, err := preAuth.PreAuthorize(context.TODO(), &testServiceAccount, strings.NewReader(manifest)) + require.Error(t, err, "expected escalation check to fail") + errMsg := err.Error() + // Instead of expecting two "forbidden:" substrings, check that both distinct error parts appear + require.Contains(t, errMsg, "forbidden:", "expected error message to contain 'forbidden:'") + require.Contains(t, errMsg, "not found", "expected error message to contain 'not found'") + // Also ensure that our computed missing rules are non-empty + require.NotEmpty(t, missingRules, "expected computed missing rules to be returned") + }) +} + +func TestPreAuthorize_MultipleRuleFailures(t *testing.T) { + t.Run("reports multiple missing rules when several rules fail", func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.PreflightPermissions, true) + // Use a client configured with a limited cluster role that lacks the necessary permissions. + fakeClient := setupFakeClient(limitedClusterRole) + preAuth := NewRBACPreAuthorizer(fakeClient) + testServiceAccount := user.DefaultInfo{ + Name: fmt.Sprintf("system:serviceaccount:%s:%s", ns, saName), + } + // This manifest defines a Role with two rules + // One rule requires "get" and "update" on "roles", + // and the other requires "list" and "watch" on "rolebindings" + // Both are expected to be missing + manifest := `apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: test-multiple-rule-failure + namespace: test-namespace +rules: +- apiGroups: ["rbac.authorization.k8s.io"] + resources: ["roles"] + verbs: ["get", "update"] +- apiGroups: ["rbac.authorization.k8s.io"] + resources: ["rolebindings"] + verbs: ["list", "watch"] +` + missingRules, err := preAuth.PreAuthorize(context.TODO(), &testServiceAccount, strings.NewReader(manifest)) + require.Error(t, err, "expected escalation check to fail due to missing rules") + + // Check that computed missing rules include multiple entries for the namespace + var nsMissingRules []rbacv1.PolicyRule + for _, scoped := range missingRules { + if scoped.Namespace == "test-namespace" { + nsMissingRules = scoped.MissingRules + break + } + } + require.NotEmpty(t, nsMissingRules, "expected missing rules for namespace test-namespace") + require.GreaterOrEqual(t, len(nsMissingRules), 2, "expected at least 2 missing rules to be reported") + }) +} From f75f5e021eac9edd70263dd774d04b7ac1b82ccf Mon Sep 17 00:00:00 2001 From: Brett Tofel Date: Thu, 20 Mar 2025 12:26:07 -0400 Subject: [PATCH 24/67] Revert "Add fakeStorage dry run for escalationCheck" This reverts commit 2681194ba221b3aeb7b95e1d6c2bedc895449bf8. --- .../operator-controller/authorization/rbac.go | 272 +++++++++--------- .../authorization/rbac_test.go | 129 +-------- 2 files changed, 145 insertions(+), 256 deletions(-) diff --git a/internal/operator-controller/authorization/rbac.go b/internal/operator-controller/authorization/rbac.go index 1bba5c4bf..2405a8333 100644 --- a/internal/operator-controller/authorization/rbac.go +++ b/internal/operator-controller/authorization/rbac.go @@ -9,8 +9,8 @@ import ( "sort" rbacv1 "k8s.io/api/rbac/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" - metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -18,19 +18,14 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/sets" apimachyaml "k8s.io/apimachinery/pkg/util/yaml" - "k8s.io/apimachinery/pkg/watch" "k8s.io/apiserver/pkg/authentication/user" "k8s.io/apiserver/pkg/authorization/authorizer" "k8s.io/apiserver/pkg/endpoints/request" - "k8s.io/apiserver/pkg/registry/rest" rbacinternal "k8s.io/kubernetes/pkg/apis/rbac" rbacv1helpers "k8s.io/kubernetes/pkg/apis/rbac/v1" - "k8s.io/kubernetes/pkg/registry/rbac/clusterrole/policybased" - policybasedClusterRoleBinding "k8s.io/kubernetes/pkg/registry/rbac/clusterrolebinding/policybased" - policybasedRole "k8s.io/kubernetes/pkg/registry/rbac/role/policybased" - policybasedRoleBinding "k8s.io/kubernetes/pkg/registry/rbac/rolebinding/policybased" + rbacregistry "k8s.io/kubernetes/pkg/registry/rbac" "k8s.io/kubernetes/pkg/registry/rbac/validation" - "k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac" + rbac "k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -63,61 +58,59 @@ func NewRBACPreAuthorizer(cl client.Client) PreAuthorizer { } // PreAuthorize validates whether the current user/request satisfies the necessary permissions -// as defined by the RBAC policy. It decodes the manifest, constructs authorization records, -// performs attribute checks, and then uses escalationChecker (which delegates to upstream logic) -// to verify that no privilege escalation is occurring. +// as defined by the RBAC policy. It examines the user’s roles, resource identifiers, and +// the intended action to determine if the operation is allowed. +// +// Return Value: +// - nil: indicates that the authorization check passed and the operation is permitted. +// - non-nil error: indicates that the authorization failed (either due to insufficient permissions +// or an error encountered during the check), the error provides a slice of several failures at once. func (a *rbacPreAuthorizer) PreAuthorize(ctx context.Context, manifestManager user.Info, manifestReader io.Reader) ([]ScopedPolicyRules, error) { - var allMissingPolicyRules []ScopedPolicyRules + var allMissingPolicyRules = []ScopedPolicyRules{} dm, err := a.decodeManifest(manifestReader) if err != nil { return nil, err } attributesRecords := dm.asAuthorizationAttributesRecordsForUser(manifestManager) - // Compute missing rules using your original logic. + var preAuthEvaluationErrors []error missingRules, err := a.authorizeAttributesRecords(ctx, attributesRecords) if err != nil { - // If there are errors here, you might choose to log them or collect them. - // We'll still try to run the upstream escalation check. + preAuthEvaluationErrors = append(preAuthEvaluationErrors, err) } - var escalationErrors []error ec := escalationChecker{ authorizer: a.authorizer, ruleResolver: a.ruleResolver, extraClusterRoles: dm.clusterRoles, extraRoles: dm.roles, } + for _, obj := range dm.rbacObjects() { if err := ec.checkEscalation(ctx, manifestManager, obj); err != nil { - // If err is a composite error (errors.Join), unwrap it and add each underlying error. - var joinErr interface{ Unwrap() []error } - if errors.As(err, &joinErr) { - for _, singleErr := range joinErr.Unwrap() { - escalationErrors = append(escalationErrors, singleErr) - } - } else { - escalationErrors = append(escalationErrors, err) - } + // In Kubernetes 1.32.2 the specialized PrivilegeEscalationError is gone. + // Instead, we simply collect the error. + preAuthEvaluationErrors = append(preAuthEvaluationErrors, err) } } - // If escalation check fails, return the detailed missing rules along with the error. - if len(escalationErrors) > 0 { - // Process the missingRules map into a slice as before. - for ns, nsMissingRules := range missingRules { - if compactMissingRules, err := validation.CompactRules(nsMissingRules); err == nil { - missingRules[ns] = compactMissingRules - } - sortableRules := rbacv1helpers.SortableRuleSlice(missingRules[ns]) - sort.Sort(sortableRules) - allMissingPolicyRules = append(allMissingPolicyRules, ScopedPolicyRules{Namespace: ns, MissingRules: sortableRules}) + for ns, nsMissingRules := range missingRules { + // NOTE: Although CompactRules is defined to return an error, its current implementation + // never produces a non-nil error. This is because all operations within the function are + // designed to succeed under current conditions. In the future, if more complex rule validations + // are introduced, this behavior may change and proper error handling will be required. + if compactMissingRules, err := validation.CompactRules(nsMissingRules); err == nil { + missingRules[ns] = compactMissingRules } - return allMissingPolicyRules, fmt.Errorf("escalation check failed: %w", errors.Join(escalationErrors...)) + sortableRules := rbacv1helpers.SortableRuleSlice(missingRules[ns]) + sort.Sort(sortableRules) + allMissingPolicyRules = append(allMissingPolicyRules, ScopedPolicyRules{Namespace: ns, MissingRules: sortableRules}) } - // If the escalation check passed, override any computed missing rules (since the final decision is allowed). - return []ScopedPolicyRules{}, nil + if len(preAuthEvaluationErrors) > 0 { + return allMissingPolicyRules, fmt.Errorf("authorization evaluation errors: %w", errors.Join(preAuthEvaluationErrors...)) + } + return allMissingPolicyRules, nil } func (a *rbacPreAuthorizer) decodeManifest(manifestReader io.Reader) (*decodedManifest, error) { @@ -150,6 +143,7 @@ func (a *rbacPreAuthorizer) decodeManifest(manifestReader io.Reader) (*decodedMa if name := uObj.GetName(); name != "" { objName = fmt.Sprintf(" (name: %s)", name) } + errs = append( errs, fmt.Errorf("could not get REST mapping for object %d in manifest with GVK %s%s: %w", i, gvk, objName, err), @@ -190,7 +184,6 @@ func (a *rbacPreAuthorizer) decodeManifest(manifestReader io.Reader) (*decodedMa } dm.roleBindings[client.ObjectKeyFromObject(obj)] = *obj } - i++ } if len(errs) > 0 { return nil, errors.Join(errs...) @@ -199,8 +192,10 @@ func (a *rbacPreAuthorizer) decodeManifest(manifestReader io.Reader) (*decodedMa } func (a *rbacPreAuthorizer) authorizeAttributesRecords(ctx context.Context, attributesRecords []authorizer.AttributesRecord) (map[string][]rbacv1.PolicyRule, error) { - missingRules := map[string][]rbacv1.PolicyRule{} - var errs []error + var ( + missingRules = map[string][]rbacv1.PolicyRule{} + errs []error + ) for _, ar := range attributesRecords { allow, err := a.attributesAllowed(ctx, ar) if err != nil { @@ -234,15 +229,18 @@ func policyRuleFromAttributesRecord(attributesRecord authorizer.AttributesRecord pr.NonResourceURLs = []string{attributesRecord.Path} return pr } + pr.APIGroups = []string{attributesRecord.APIGroup} if attributesRecord.Name != "" { pr.ResourceNames = []string{attributesRecord.Name} } + r := attributesRecord.Resource if attributesRecord.Subresource != "" { r += "/" + attributesRecord.Subresource } pr.Resources = []string{r} + return pr } @@ -257,20 +255,16 @@ type decodedManifest struct { func (dm *decodedManifest) rbacObjects() []client.Object { objects := make([]client.Object, 0, len(dm.clusterRoles)+len(dm.roles)+len(dm.clusterRoleBindings)+len(dm.roleBindings)) for obj := range maps.Values(dm.clusterRoles) { - o := obj // avoid aliasing - objects = append(objects, &o) + objects = append(objects, &obj) } for obj := range maps.Values(dm.roles) { - o := obj - objects = append(objects, &o) + objects = append(objects, &obj) } for obj := range maps.Values(dm.clusterRoleBindings) { - o := obj - objects = append(objects, &o) + objects = append(objects, &obj) } for obj := range maps.Values(dm.roleBindings) { - o := obj - objects = append(objects, &o) + objects = append(objects, &obj) } return objects } @@ -364,116 +358,134 @@ type escalationChecker struct { extraClusterRoles map[types.NamespacedName]rbacv1.ClusterRole } -// checkEscalation delegates the escalation check to upstream storage by performing -// a dry-run Create call on the appropriate storage for the RBAC object type. func (ec *escalationChecker) checkEscalation(ctx context.Context, manifestManager user.Info, obj client.Object) error { - // Set up the context with user and namespace. ctx = request.WithUser(request.WithNamespace(ctx, obj.GetNamespace()), manifestManager) - noOpValidate := func(ctx context.Context, obj runtime.Object) error { return nil } - opts := &metav1.CreateOptions{DryRun: []string{metav1.DryRunAll}} - switch v := obj.(type) { + case *rbacv1.Role: + ctx = request.WithRequestInfo(ctx, &request.RequestInfo{APIGroup: rbacv1.GroupName, Resource: "roles", IsResourceRequest: true}) + return ec.checkRoleEscalation(ctx, v) + case *rbacv1.RoleBinding: + ctx = request.WithRequestInfo(ctx, &request.RequestInfo{APIGroup: rbacv1.GroupName, Resource: "rolebindings", IsResourceRequest: true}) + return ec.checkRoleBindingEscalation(ctx, v) case *rbacv1.ClusterRole: - // Merge extra ClusterRole rules if present. - key := types.NamespacedName{Name: v.Name} - if extra, ok := ec.extraClusterRoles[key]; ok { - v.Rules = append(v.Rules, extra.Rules...) - } - // Convert external ClusterRole to internal representation. - var internalClusterRole rbacinternal.ClusterRole - if err := rbacv1helpers.Convert_v1_ClusterRole_To_rbac_ClusterRole(v, &internalClusterRole, nil); err != nil { - return err - } - storage := policybased.NewStorage(&FakeStandardStorage{}, ec.authorizer, ec.ruleResolver) - _, err := storage.Create(ctx, &internalClusterRole, noOpValidate, opts) - return err - + ctx = request.WithRequestInfo(ctx, &request.RequestInfo{APIGroup: rbacv1.GroupName, Resource: "clusterroles", IsResourceRequest: true}) + return ec.checkClusterRoleEscalation(ctx, v) case *rbacv1.ClusterRoleBinding: - // Convert external ClusterRoleBinding to internal representation. - var internalClusterRoleBinding rbacinternal.ClusterRoleBinding - if err := rbacv1helpers.Convert_v1_ClusterRoleBinding_To_rbac_ClusterRoleBinding(v, &internalClusterRoleBinding, nil); err != nil { - return err - } - storage := policybasedClusterRoleBinding.NewStorage(&FakeStandardStorage{}, ec.authorizer, ec.ruleResolver) - _, err := storage.Create(ctx, &internalClusterRoleBinding, noOpValidate, opts) - return err + ctx = request.WithRequestInfo(ctx, &request.RequestInfo{APIGroup: rbacv1.GroupName, Resource: "clusterrolebindings", IsResourceRequest: true}) + return ec.checkClusterRoleBindingEscalation(ctx, v) + default: + return fmt.Errorf("unknown object type %T", v) + } +} - case *rbacv1.Role: - // Merge extra Role rules if present. - key := types.NamespacedName{Namespace: v.Namespace, Name: v.Name} - if extra, ok := ec.extraRoles[key]; ok { - v.Rules = append(v.Rules, extra.Rules...) - } - // Convert external Role to internal representation. - var internalRole rbacinternal.Role - if err := rbacv1helpers.Convert_v1_Role_To_rbac_Role(v, &internalRole, nil); err != nil { - return err +func (ec *escalationChecker) checkClusterRoleEscalation(ctx context.Context, clusterRole *rbacv1.ClusterRole) error { + if rbacregistry.EscalationAllowed(ctx) || rbacregistry.RoleEscalationAuthorized(ctx, ec.authorizer) { + return nil + } + + // to set the aggregation rule, since it can gather anything, requires * on *.* + if hasAggregationRule(clusterRole) { + if err := validation.ConfirmNoEscalation(ctx, ec.ruleResolver, fullAuthority); err != nil { + return fmt.Errorf("must have cluster-admin privileges to use an aggregationRule: %w", err) } - storage := policybasedRole.NewStorage(&FakeStandardStorage{}, ec.authorizer, ec.ruleResolver) - _, err := storage.Create(ctx, &internalRole, noOpValidate, opts) + } + + if err := validation.ConfirmNoEscalation(ctx, ec.ruleResolver, clusterRole.Rules); err != nil { return err + } + return nil +} - case *rbacv1.RoleBinding: - // Convert external RoleBinding to internal representation. - var internalRoleBinding rbacinternal.RoleBinding - if err := rbacv1helpers.Convert_v1_RoleBinding_To_rbac_RoleBinding(v, &internalRoleBinding, nil); err != nil { - return err - } - storage := policybasedRoleBinding.NewStorage(&FakeStandardStorage{}, ec.authorizer, ec.ruleResolver) - _, err := storage.Create(ctx, &internalRoleBinding, noOpValidate, opts) +func (ec *escalationChecker) checkClusterRoleBindingEscalation(ctx context.Context, clusterRoleBinding *rbacv1.ClusterRoleBinding) error { + if rbacregistry.EscalationAllowed(ctx) { + return nil + } + + roleRef := rbacinternal.RoleRef{} + err := rbacv1helpers.Convert_v1_RoleRef_To_rbac_RoleRef(&clusterRoleBinding.RoleRef, &roleRef, nil) + if err != nil { return err + } - default: - return fmt.Errorf("unsupported object type %T", v) + if rbacregistry.BindingAuthorized(ctx, roleRef, metav1.NamespaceNone, ec.authorizer) { + return nil } -} -// FakeStandardStorage is a minimal fake implementation of rest.StandardStorage to satisfy required methods for dry-run operations. -type FakeStandardStorage struct{} + rules, err := ec.ruleResolver.GetRoleReferenceRules(ctx, clusterRoleBinding.RoleRef, metav1.NamespaceNone) + if err != nil && !apierrors.IsNotFound(err) { + return err + } -func (fs *FakeStandardStorage) New() runtime.Object { - return nil -} + if clusterRoleBinding.RoleRef.Kind == "ClusterRole" { + if manifestClusterRole, ok := ec.extraClusterRoles[types.NamespacedName{Name: clusterRoleBinding.RoleRef.Name}]; ok { + rules = append(rules, manifestClusterRole.Rules...) + } + } -func (fs *FakeStandardStorage) NewList() runtime.Object { + if err := validation.ConfirmNoEscalation(ctx, ec.ruleResolver, rules); err != nil { + return err + } return nil } -func (fs *FakeStandardStorage) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { - return nil, nil -} +func (ec *escalationChecker) checkRoleEscalation(ctx context.Context, role *rbacv1.Role) error { + if rbacregistry.EscalationAllowed(ctx) || rbacregistry.RoleEscalationAuthorized(ctx, ec.authorizer) { + return nil + } -func (fs *FakeStandardStorage) List(ctx context.Context, options *metainternalversion.ListOptions) (runtime.Object, error) { - return nil, nil + rules := role.Rules + if err := validation.ConfirmNoEscalation(ctx, ec.ruleResolver, rules); err != nil { + return err + } + return nil } -func (fs *FakeStandardStorage) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) { - // For dry-run escalation check, simply return the object. - return obj, nil -} +func (ec *escalationChecker) checkRoleBindingEscalation(ctx context.Context, roleBinding *rbacv1.RoleBinding) error { + if rbacregistry.EscalationAllowed(ctx) { + return nil + } -func (fs *FakeStandardStorage) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) { - return nil, false, nil -} + roleRef := rbacinternal.RoleRef{} + err := rbacv1helpers.Convert_v1_RoleRef_To_rbac_RoleRef(&roleBinding.RoleRef, &roleRef, nil) + if err != nil { + return err + } + if rbacregistry.BindingAuthorized(ctx, roleRef, roleBinding.Namespace, ec.authorizer) { + return nil + } -func (fs *FakeStandardStorage) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) { - return nil, false, nil -} + rules, err := ec.ruleResolver.GetRoleReferenceRules(ctx, roleBinding.RoleRef, roleBinding.Namespace) + if err != nil && !apierrors.IsNotFound(err) { + return err + } -func (fs *FakeStandardStorage) Watch(ctx context.Context, options *metainternalversion.ListOptions) (watch.Interface, error) { - return nil, nil -} + switch roleRef.Kind { + case "ClusterRole": + if manifestClusterRole, ok := ec.extraClusterRoles[types.NamespacedName{Name: roleBinding.RoleRef.Name}]; ok { + rules = append(rules, manifestClusterRole.Rules...) + } + case "Role": + if manifestRole, ok := ec.extraRoles[types.NamespacedName{Namespace: roleBinding.Namespace, Name: roleBinding.RoleRef.Name}]; ok { + rules = append(rules, manifestRole.Rules...) + } + } -func (fs *FakeStandardStorage) Destroy() { - return + if err := validation.ConfirmNoEscalation(ctx, ec.ruleResolver, rules); err != nil { + return err + } + return nil } -func (fs *FakeStandardStorage) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { - return nil, nil +var fullAuthority = []rbacv1.PolicyRule{ + {Verbs: []string{"*"}, APIGroups: []string{"*"}, Resources: []string{"*"}}, + {Verbs: []string{"*"}, NonResourceURLs: []string{"*"}}, } -func (fs *FakeStandardStorage) DeleteCollection(ctx context.Context, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions, listOptions *metainternalversion.ListOptions) (runtime.Object, error) { - return nil, nil +func hasAggregationRule(clusterRole *rbacv1.ClusterRole) bool { + // Currently, an aggregation rule is considered present only if it has one or more selectors. + // An empty slice of ClusterRoleSelectors means no selectors were provided, + // which does NOT imply "match all." + return clusterRole.AggregationRule != nil && len(clusterRole.AggregationRule.ClusterRoleSelectors) > 0 } func toPtrSlice[V any](in []V) []*V { diff --git a/internal/operator-controller/authorization/rbac_test.go b/internal/operator-controller/authorization/rbac_test.go index 9f50ef867..b37faee7c 100644 --- a/internal/operator-controller/authorization/rbac_test.go +++ b/internal/operator-controller/authorization/rbac_test.go @@ -123,7 +123,7 @@ subjects: Rules: []rbacv1.PolicyRule{ { APIGroups: []string{"*"}, - Resources: []string{"serviceaccounts", "services", "certificates"}, + Resources: []string{"serviceaccounts", "services"}, Verbs: []string{"*"}, }, { @@ -174,132 +174,9 @@ func TestPreAuthorize_CheckEscalation(t *testing.T) { featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.PreflightPermissions, true) fakeClient := setupFakeClient(escalatingClusterRole) preAuth := NewRBACPreAuthorizer(fakeClient) - testServiceAccount := user.DefaultInfo{ - Name: fmt.Sprintf("system:serviceaccount:%s:%s", ns, saName), - } - // Ensure the manifest contains only allowed rules so that the escalation check succeeds with no missing rules - modifiedManifest := strings.Replace(testManifest, `- apiGroups: ["*"] - resources: [certificates] - verbs: [create] -`, "", 1) - missingRules, err := preAuth.PreAuthorize(context.TODO(), &testServiceAccount, strings.NewReader(modifiedManifest)) + testServiceAccount := user.DefaultInfo{Name: fmt.Sprintf("system:serviceaccount:%s:%s", ns, saName)} + missingRules, err := preAuth.PreAuthorize(context.TODO(), &testServiceAccount, strings.NewReader(testManifest)) require.NoError(t, err) require.Equal(t, []ScopedPolicyRules{}, missingRules) }) } - -func TestPreAuthorize_StorageLayerError(t *testing.T) { - t.Run("preauthorize fails with storage-layer error and computed missing rules", func(t *testing.T) { - featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.PreflightPermissions, true) - // Use a client configured with a limited cluster role that should cause the escalation check to fail - fakeClient := setupFakeClient(limitedClusterRole) - preAuth := NewRBACPreAuthorizer(fakeClient) - testServiceAccount := user.DefaultInfo{ - Name: fmt.Sprintf("system:serviceaccount:%s:%s", ns, saName), - } - // Create a manifest that triggers escalation check failure. - manifest := `apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: test-escalation-role - namespace: test-namespace -rules: -- apiGroups: ["*"] - resources: ["*"] - verbs: ["*"] -` - missingRules, err := preAuth.PreAuthorize(context.TODO(), &testServiceAccount, strings.NewReader(manifest)) - require.Error(t, err) - // Instead of calling it opaque, we check that the error contains "forbidden:" to indicate it's from storage-layer checks - require.Contains(t, err.Error(), "forbidden:") - // Expect that our computed missing rules are non-empty (i.e. we have a detailed report). - require.NotEmpty(t, missingRules, "expected computed missing rules to be returned") - }) -} - -func TestPreAuthorize_MultipleEscalationErrors(t *testing.T) { - t.Run("preauthorize returns composite escalation error for multiple RBAC objects", func(t *testing.T) { - featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.PreflightPermissions, true) - // Use a client configured with a limited cluster role that will fail escalation checks - fakeClient := setupFakeClient(limitedClusterRole) - preAuth := NewRBACPreAuthorizer(fakeClient) - testServiceAccount := user.DefaultInfo{ - Name: fmt.Sprintf("system:serviceaccount:%s:%s", ns, saName), - } - // Create a manifest with two RBAC objects that should both trigger escalation errors - manifest := `apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: test-escalation-role-1 - namespace: test-namespace -rules: -- apiGroups: ["*"] - resources: ["*"] - verbs: ["*"] ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: test-escalation-binding-1 - namespace: test-namespace -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: test-escalation-role-1 -subjects: -- kind: ServiceAccount - name: test-serviceaccount - namespace: test-namespace -` - missingRules, err := preAuth.PreAuthorize(context.TODO(), &testServiceAccount, strings.NewReader(manifest)) - require.Error(t, err, "expected escalation check to fail") - errMsg := err.Error() - // Instead of expecting two "forbidden:" substrings, check that both distinct error parts appear - require.Contains(t, errMsg, "forbidden:", "expected error message to contain 'forbidden:'") - require.Contains(t, errMsg, "not found", "expected error message to contain 'not found'") - // Also ensure that our computed missing rules are non-empty - require.NotEmpty(t, missingRules, "expected computed missing rules to be returned") - }) -} - -func TestPreAuthorize_MultipleRuleFailures(t *testing.T) { - t.Run("reports multiple missing rules when several rules fail", func(t *testing.T) { - featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.PreflightPermissions, true) - // Use a client configured with a limited cluster role that lacks the necessary permissions. - fakeClient := setupFakeClient(limitedClusterRole) - preAuth := NewRBACPreAuthorizer(fakeClient) - testServiceAccount := user.DefaultInfo{ - Name: fmt.Sprintf("system:serviceaccount:%s:%s", ns, saName), - } - // This manifest defines a Role with two rules - // One rule requires "get" and "update" on "roles", - // and the other requires "list" and "watch" on "rolebindings" - // Both are expected to be missing - manifest := `apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: test-multiple-rule-failure - namespace: test-namespace -rules: -- apiGroups: ["rbac.authorization.k8s.io"] - resources: ["roles"] - verbs: ["get", "update"] -- apiGroups: ["rbac.authorization.k8s.io"] - resources: ["rolebindings"] - verbs: ["list", "watch"] -` - missingRules, err := preAuth.PreAuthorize(context.TODO(), &testServiceAccount, strings.NewReader(manifest)) - require.Error(t, err, "expected escalation check to fail due to missing rules") - - // Check that computed missing rules include multiple entries for the namespace - var nsMissingRules []rbacv1.PolicyRule - for _, scoped := range missingRules { - if scoped.Namespace == "test-namespace" { - nsMissingRules = scoped.MissingRules - break - } - } - require.NotEmpty(t, nsMissingRules, "expected missing rules for namespace test-namespace") - require.GreaterOrEqual(t, len(nsMissingRules), 2, "expected at least 2 missing rules to be reported") - }) -} From 836810b423d03e883e8b964dfdf1151aaaf10936 Mon Sep 17 00:00:00 2001 From: Brett Tofel Date: Tue, 1 Apr 2025 10:37:20 -0400 Subject: [PATCH 25/67] Rename template func to renderClientOnlyRelease Signed-off-by: Brett Tofel --- internal/operator-controller/applier/helm.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/operator-controller/applier/helm.go b/internal/operator-controller/applier/helm.go index 1144ff789..06a97b4c4 100644 --- a/internal/operator-controller/applier/helm.go +++ b/internal/operator-controller/applier/helm.go @@ -96,7 +96,7 @@ func (h *Helm) Apply(ctx context.Context, contentFS fs.FS, ext *ocv1.ClusterExte } if h.EnablePreflightPermissions { - tmplRel, err := h.template(ctx, ext, chrt, values, post) + tmplRel, err := h.renderClientOnlyRelease(ctx, ext, chrt, values, post) if err != nil { return nil, "", fmt.Errorf("failed to get release state using client-only dry-run: %w", err) } @@ -197,8 +197,8 @@ func (h *Helm) buildHelmChart(bundleFS fs.FS, ext *ocv1.ClusterExtension) (*char return h.BundleToHelmChartFn(bundleFS, ext.Spec.Namespace, watchNamespace) } -func (h *Helm) template(ctx context.Context, ext *ocv1.ClusterExtension, chrt *chart.Chart, values chartutil.Values, post postrender.PostRenderer) (*release.Release, error) { - // We need to get a separate action client because our template call below +func (h *Helm) renderClientOnlyRelease(ctx context.Context, ext *ocv1.ClusterExtension, chrt *chart.Chart, values chartutil.Values, post postrender.PostRenderer) (*release.Release, error) { + // We need to get a separate action client because our work below // permanently modifies the underlying action.Configuration for ClientOnly mode. ac, err := h.ActionClientGetter.ActionClientFor(ctx, ext) if err != nil { From 12b388418051d4f7947a13e7980fcbcfb99d2341 Mon Sep 17 00:00:00 2001 From: Brett Tofel Date: Tue, 1 Apr 2025 10:52:39 -0400 Subject: [PATCH 26/67] Updated comment on returns of PreAuthorize Signed-off-by: Brett Tofel --- internal/operator-controller/authorization/rbac.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/internal/operator-controller/authorization/rbac.go b/internal/operator-controller/authorization/rbac.go index 2405a8333..b8a2c9c88 100644 --- a/internal/operator-controller/authorization/rbac.go +++ b/internal/operator-controller/authorization/rbac.go @@ -63,8 +63,11 @@ func NewRBACPreAuthorizer(cl client.Client) PreAuthorizer { // // Return Value: // - nil: indicates that the authorization check passed and the operation is permitted. -// - non-nil error: indicates that the authorization failed (either due to insufficient permissions -// or an error encountered during the check), the error provides a slice of several failures at once. +// - non-nil error: indicates that an error occurred during the permission evaluation process +// (for example, a failure decoding the manifest or other internal issues). If the evaluation +// completes successfully but identifies missing rules, then a nil error is returned along with +// the list (or slice) of missing rules. Note that in some cases the error may encapsulate multiple +// evaluation failures func (a *rbacPreAuthorizer) PreAuthorize(ctx context.Context, manifestManager user.Info, manifestReader io.Reader) ([]ScopedPolicyRules, error) { var allMissingPolicyRules = []ScopedPolicyRules{} dm, err := a.decodeManifest(manifestReader) From c4e51b53dc723e3589a6302d034c40126398cd3d Mon Sep 17 00:00:00 2001 From: Tayler Geiger Date: Tue, 1 Apr 2025 11:01:32 -0500 Subject: [PATCH 27/67] Remove repetition in rbac_test.go Signed-off-by: Tayler Geiger --- internal/operator-controller/authorization/rbac_test.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/internal/operator-controller/authorization/rbac_test.go b/internal/operator-controller/authorization/rbac_test.go index b37faee7c..aa9fddc06 100644 --- a/internal/operator-controller/authorization/rbac_test.go +++ b/internal/operator-controller/authorization/rbac_test.go @@ -57,8 +57,9 @@ subjects: namespace: test-namespace ` - saName = "test-serviceaccount" - ns = "test-namespace" + saName = "test-serviceaccount" + ns = "test-namespace" + testServiceAccount = user.DefaultInfo{Name: fmt.Sprintf("system:serviceaccount:%s:%s", ns, saName)} objects = []client.Object{ &corev1.Namespace{ @@ -150,7 +151,6 @@ func TestPreAuthorize_Success(t *testing.T) { featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.PreflightPermissions, true) fakeClient := setupFakeClient(privilegedClusterRole) preAuth := NewRBACPreAuthorizer(fakeClient) - testServiceAccount := user.DefaultInfo{Name: fmt.Sprintf("system:serviceaccount:%s:%s", ns, saName)} missingRules, err := preAuth.PreAuthorize(context.TODO(), &testServiceAccount, strings.NewReader(testManifest)) require.NoError(t, err) require.Equal(t, []ScopedPolicyRules{}, missingRules) @@ -162,7 +162,6 @@ func TestPreAuthorize_Failure(t *testing.T) { featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.PreflightPermissions, true) fakeClient := setupFakeClient(limitedClusterRole) preAuth := NewRBACPreAuthorizer(fakeClient) - testServiceAccount := user.DefaultInfo{Name: fmt.Sprintf("system:serviceaccount:%s:%s", ns, saName)} missingRules, err := preAuth.PreAuthorize(context.TODO(), &testServiceAccount, strings.NewReader(testManifest)) require.Error(t, err) require.NotEqual(t, []ScopedPolicyRules{}, missingRules) @@ -174,7 +173,6 @@ func TestPreAuthorize_CheckEscalation(t *testing.T) { featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.PreflightPermissions, true) fakeClient := setupFakeClient(escalatingClusterRole) preAuth := NewRBACPreAuthorizer(fakeClient) - testServiceAccount := user.DefaultInfo{Name: fmt.Sprintf("system:serviceaccount:%s:%s", ns, saName)} missingRules, err := preAuth.PreAuthorize(context.TODO(), &testServiceAccount, strings.NewReader(testManifest)) require.NoError(t, err) require.Equal(t, []ScopedPolicyRules{}, missingRules) From dc2d31949165a810405713ec1eb89414729e7b67 Mon Sep 17 00:00:00 2001 From: Brett Tofel Date: Wed, 2 Apr 2025 12:37:58 -0400 Subject: [PATCH 28/67] k8smaintainer stage repo version pin logic upgrade Signed-off-by: Brett Tofel --- go.mod | 14 +- go.sum | 283 +++++++---------- hack/tools/k8smaintainer/main.go | 507 ++++++++++++++----------------- 3 files changed, 357 insertions(+), 447 deletions(-) diff --git a/go.mod b/go.mod index 2cefdbcb8..1f1fb7561 100644 --- a/go.mod +++ b/go.mod @@ -255,17 +255,17 @@ require ( sigs.k8s.io/structured-merge-diff/v4 v4.5.0 // indirect ) -replace k8s.io/api => k8s.io/api v0.32.2 +replace k8s.io/api => k8s.io/api v0.32.3 replace k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.32.3 -replace k8s.io/apimachinery => k8s.io/apimachinery v0.32.2 +replace k8s.io/apimachinery => k8s.io/apimachinery v0.32.3 -replace k8s.io/apiserver => k8s.io/apiserver v0.32.2 +replace k8s.io/apiserver => k8s.io/apiserver v0.32.3 -replace k8s.io/cli-runtime => k8s.io/cli-runtime v0.32.2 +replace k8s.io/cli-runtime => k8s.io/cli-runtime v0.32.3 -replace k8s.io/client-go => k8s.io/client-go v0.32.2 +replace k8s.io/client-go => k8s.io/client-go v0.32.3 replace k8s.io/cloud-provider => k8s.io/cloud-provider v0.32.3 @@ -273,7 +273,7 @@ replace k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.32.3 replace k8s.io/code-generator => k8s.io/code-generator v0.32.3 -replace k8s.io/component-base => k8s.io/component-base v0.32.2 +replace k8s.io/component-base => k8s.io/component-base v0.32.3 replace k8s.io/component-helpers => k8s.io/component-helpers v0.32.3 @@ -305,6 +305,8 @@ replace k8s.io/kubectl => k8s.io/kubectl v0.32.3 replace k8s.io/kubelet => k8s.io/kubelet v0.32.3 +replace k8s.io/kubernetes => k8s.io/kubernetes v1.32.3 + replace k8s.io/metrics => k8s.io/metrics v0.32.3 replace k8s.io/mount-utils => k8s.io/mount-utils v0.32.3 diff --git a/go.sum b/go.sum index 41c39ba01..4594f9569 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -cel.dev/expr v0.19.0 h1:lXuo+nDhpyJSpWxpPVi5cPUwzKb+dsdOiw6IreM5yt0= -cel.dev/expr v0.19.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= +cel.dev/expr v0.18.0 h1:CJ6drgk+Hf96lkLikr4rFf19WrU0BOWEihyZnI2TAzo= +cel.dev/expr v0.18.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= @@ -65,8 +65,8 @@ github.com/containerd/containerd/api v1.8.0 h1:hVTNJKR8fMc/2Tiw60ZRijntNMd1U+JVM github.com/containerd/containerd/api v1.8.0/go.mod h1:dFv4lt6S20wTu/hMcP4350RL87qPWLVa/OHOwmmdnYc= github.com/containerd/continuity v0.4.4 h1:/fNVfTJ7wIl/YPMHjf+5H32uFhl63JucB34PlCpMKII= github.com/containerd/continuity v0.4.4/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE= -github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= -github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/errdefs v0.3.0 h1:FSZgGOeK4yuT/+DnF07/Olde/q4KBoMsaamhXxIMDp4= +github.com/containerd/errdefs v0.3.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= @@ -77,18 +77,18 @@ github.com/containerd/stargz-snapshotter/estargz v0.16.3 h1:7evrXtoh1mSbGj/pfRcc github.com/containerd/stargz-snapshotter/estargz v0.16.3/go.mod h1:uyr4BfYfOj3G9WBVE8cOlQmXAbPN9VEQpBBeJIuOipU= github.com/containerd/ttrpc v1.2.7 h1:qIrroQvuOL9HQ1X6KHe2ohc7p+HP/0VE6XPU7elJRqQ= github.com/containerd/ttrpc v1.2.7/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o= -github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40= -github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk= -github.com/containers/common v0.62.0 h1:Sl9WE5h7Y/F3bejrMAA4teP1EcY9ygqJmW4iwSloZ10= -github.com/containers/common v0.62.0/go.mod h1:Yec+z8mrSq4rydHofrnDCBqAcNA/BGrSg1kfFUL6F6s= -github.com/containers/image/v5 v5.34.3 h1:/cMgfyA4Y7ILH7nzWP/kqpkE5Df35Ek4bp5ZPvJOVmI= -github.com/containers/image/v5 v5.34.3/go.mod h1:MG++slvQSZVq5ejAcLdu4APGsKGMb0YHHnAo7X28fdE= +github.com/containerd/typeurl/v2 v2.2.0 h1:6NBDbQzr7I5LHgp34xAXYF5DOTQDn05X58lsPEmzLso= +github.com/containerd/typeurl/v2 v2.2.0/go.mod h1:8XOOxnyatxSWuG8OfsZXVnAF4iZfedjS/8UHSPJnX4g= +github.com/containers/common v0.61.0 h1:j/84PTqZIKKYy42OEJsZmjZ4g4Kq2ERuC3tqp2yWdh4= +github.com/containers/common v0.61.0/go.mod h1:NGRISq2vTFPSbhNqj6MLwyes4tWSlCnqbJg7R77B8xc= +github.com/containers/image/v5 v5.33.1 h1:nTWKwxAlY0aJrilvvhssqssJVnley6VqxkLiLzTEYIs= +github.com/containers/image/v5 v5.33.1/go.mod h1:/FJiLlvVbeBxWNMPVPPIWJxHTAzwBoFvyN0a51zo1CE= github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 h1:Qzk5C6cYglewc+UyGf6lc8Mj2UaPTHy/iF2De0/77CA= github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01/go.mod h1:9rfv8iPl1ZP7aqh9YA68wnZv2NUDbXdcdPHVz0pFbPY= -github.com/containers/ocicrypt v1.2.1 h1:0qIOTT9DoYwcKmxSt8QJt+VzMY18onl9jUXsxpVhSmM= -github.com/containers/ocicrypt v1.2.1/go.mod h1:aD0AAqfMp0MtwqWgHM1bUwe1anx0VazI108CRrSKINQ= -github.com/containers/storage v1.57.2 h1:2roCtTyE9pzIaBDHibK72DTnYkPmwWaq5uXxZdaWK4U= -github.com/containers/storage v1.57.2/go.mod h1:i/Hb4lu7YgFr9G0K6BMjqW0BLJO1sFsnWQwj2UoWCUM= +github.com/containers/ocicrypt v1.2.0 h1:X14EgRK3xNFvJEfI5O4Qn4T3E25ANudSOZz/sirVuPM= +github.com/containers/ocicrypt v1.2.0/go.mod h1:ZNviigQajtdlxIZGibvblVuIFBKIuUI2M0QM12SD31U= +github.com/containers/storage v1.56.1 h1:gDZj/S6Zxus4Xx42X6iNB3ODXuh0qoOdH/BABfrvcKo= +github.com/containers/storage v1.56.1/go.mod h1:c6WKowcAlED/DkWGNuL9bvGYqIWCVy7isRMdCSKWNjk= github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= @@ -106,16 +106,16 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/distribution/distribution/v3 v3.0.0-rc.3 h1:JRJso9IVLoooKX76oWR+DWCCdZlK5m4nRtDWvzB1ITg= -github.com/distribution/distribution/v3 v3.0.0-rc.3/go.mod h1:offoOgrnYs+CFwis8nE0hyzYZqRCZj5EFc5kgfszwiE= +github.com/distribution/distribution/v3 v3.0.0-rc.1 h1:6M4ewmPBUhF7wtQ8URLOQ1W/PQuVKiD1u8ymwLDUGqQ= +github.com/distribution/distribution/v3 v3.0.0-rc.1/go.mod h1:tFjaPDeHCrLg28e4feBIy27cP+qmrc/mvkl6MFIfVi4= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/cli v28.0.0+incompatible h1:ido37VmLUqEp+5NFb9icd6BuBB+SNDgCn+5kPCr2buA= -github.com/docker/cli v28.0.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v27.5.0+incompatible h1:aMphQkcGtpHixwwhAXJT1rrK/detk2JIvDaFkLctbGM= +github.com/docker/cli v27.5.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v27.5.1+incompatible h1:4PYU5dnBYqRQi0294d1FBECqT9ECWeQAIfE8q4YnPY8= -github.com/docker/docker v27.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v27.5.0+incompatible h1:um++2NcQtGRTz5eEgO6aJimo6/JxrTXC941hd05JO6U= +github.com/docker/docker v27.5.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo= github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= @@ -148,8 +148,8 @@ github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7Dlme github.com/foxcpp/go-mockdns v1.1.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= -github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= +github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= @@ -206,8 +206,8 @@ github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJA github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-migrate/migrate/v4 v4.18.2 h1:2VSCMz7x7mjyTXx3m2zPokOY82LTRgxK1yQYKo6wWQ8= -github.com/golang-migrate/migrate/v4 v4.18.2/go.mod h1:2CM6tJvn2kqPXwnXO/d3rAQYiyoIm180VsO8PRX6Rpk= +github.com/golang-migrate/migrate/v4 v4.18.1 h1:JML/k+t4tpHCpQTCAD62Nu43NUFzHY4CV3uAuvHGC+Y= +github.com/golang-migrate/migrate/v4 v4.18.1/go.mod h1:HAX6m3sQgcdO81tdjn5exv20+3Kb13cmGli1hrD6hks= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= @@ -240,7 +240,6 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-containerregistry v0.20.3 h1:oNx7IdTI936V8CQRveCjaxOiegWwvM7kqkbXTpyiovI= @@ -267,8 +266,8 @@ github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJr github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20210315223345-82c243799c99 h1:JYghRBlGCZyCF2wNUJ8W0cwaQdtpcssJ4CgC406g+WU= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20210315223345-82c243799c99/go.mod h1:3bDW6wMZJB7tiONtC/1Xpicra6Wp5GgbTbQWCbI5fkc= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 h1:ad0vkEBuk23VJzZR9nkLVG0YAoN9coASF1GusYX6AlU= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0/go.mod h1:igFoXX2ELCW06bol23DWPB5BEWfZISOzSP5K2sbLea0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg= github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY= github.com/h2non/go-is-svg v0.0.0-20160927212452-35e8c4b0612c h1:fEE5/5VNnYUoBOj2I9TP8Jc+a7lge3QWn9DKE7NCwfc= @@ -357,8 +356,8 @@ github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= -github.com/moby/sys/capability v0.4.0 h1:4D4mI6KlNtWMCM1Z/K0i7RV1FkX+DBDHKVJpCndZoHk= -github.com/moby/sys/capability v0.4.0/go.mod h1:4g9IK291rVkms3LKCDOoYlnV8xKwoDTpIrNEE35Wq0I= +github.com/moby/sys/capability v0.3.0 h1:kEP+y6te0gEXIaeQhIi0s7vKs/w0RPoH1qPa6jROcVg= +github.com/moby/sys/capability v0.3.0/go.mod h1:4g9IK291rVkms3LKCDOoYlnV8xKwoDTpIrNEE35Wq0I= github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg= github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= @@ -403,12 +402,12 @@ github.com/operator-framework/helm-operator-plugins v0.8.0 h1:0f6HOQC5likkf0b/Ov github.com/operator-framework/helm-operator-plugins v0.8.0/go.mod h1:Sc+8bE38xTCgCChBUvtq/PxatEg9fAypr7S5iAw8nlA= github.com/operator-framework/operator-lib v0.17.0 h1:cbz51wZ9+GpWR1ZYP4CSKSSBxDlWxmmnseaHVZZjZt4= github.com/operator-framework/operator-lib v0.17.0/go.mod h1:TGopBxIE8L6E/Cojzo26R3NFp1eNlqhQNmzqhOblaLw= -github.com/operator-framework/operator-registry v1.51.0 h1:3T1H2W0wYvJx82x+Ue6nooFsn859ceJf1yH6MdRdjMQ= -github.com/operator-framework/operator-registry v1.51.0/go.mod h1:dJadFTSvsgpeiqhTMK7+zXrhU0LIlx4Y/aDz0efq5oQ= -github.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8= -github.com/otiai10/copy v1.14.1/go.mod h1:oQwrEDDOci3IM8dJF0d8+jnbfPDllW6vUjNc3DoZm9I= -github.com/otiai10/mint v1.6.3 h1:87qsV/aw1F5as1eH1zS/yqHY85ANKVMgkDrf9rcxbQs= -github.com/otiai10/mint v1.6.3/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM= +github.com/operator-framework/operator-registry v1.50.0 h1:kMAwsKAEDjuSx5dGchMX+CD3SMHWwOAC/xyK3LQweB4= +github.com/operator-framework/operator-registry v1.50.0/go.mod h1:713Z/XzA5jViFMGIsXmfAcpA6h5uUKqUl3fO1t4taa0= +github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= +github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= +github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks= +github.com/otiai10/mint v1.5.1/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= @@ -421,13 +420,13 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg= -github.com/proglottis/gpgme v0.1.4 h1:3nE7YNA70o2aLjcg63tXMOhPD7bplfE5CBdV+hLAm2M= -github.com/proglottis/gpgme v0.1.4/go.mod h1:5LoXMgpE4bttgwwdv9bLs/vwqv3qV7F4glEEZ7mRKrM= +github.com/proglottis/gpgme v0.1.3 h1:Crxx0oz4LKB3QXc5Ea0J19K/3ICfy3ftr5exgUK1AU0= +github.com/proglottis/gpgme v0.1.3/go.mod h1:fPbW/EZ0LvwQtH8Hy7eixhp1eF3G39dtx7GUN+0Gmy0= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= -github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= -github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= +github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk= +github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -446,8 +445,8 @@ github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 h1:EaDatTxkdHG+U3Bk4EUr+DZ7fO github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5/go.mod h1:fyalQWdtzDBECAQFBJuQe5bzQ02jGd5Qcbgb97Flm7U= github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 h1:EfpWLLCyXw8PSM2/XNJLjI3Pb27yVE+gIAfeqp8LUCc= github.com/redis/go-redis/extra/redisotel/v9 v9.0.5/go.mod h1:WZjPDy7VNzn77AAfnAfVjZNvfJTYfPetfZk5yoSTLaQ= -github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E= -github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw= +github.com/redis/go-redis/v9 v9.5.1 h1:H1X4D3yHPaYrkL5X06Wh6xNVM/pX0Ft4RV0vMGvLBh8= +github.com/redis/go-redis/v9 v9.5.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= @@ -457,23 +456,21 @@ github.com/rubenv/sql-migrate v1.7.1 h1:f/o0WgfO/GqNuVg+6801K/KW3WdDSupzSjDYODmi github.com/rubenv/sql-migrate v1.7.1/go.mod h1:Ob2Psprc0/3ggbM6wCzyYVFFuc6FyZrb2AS+ezLDFb4= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/secure-systems-lab/go-securesystemslib v0.9.0 h1:rf1HIbL64nUpEIZnjLZ3mcNEL9NBPB0iuVjyxvq3LZc= -github.com/secure-systems-lab/go-securesystemslib v0.9.0/go.mod h1:DVHKMcZ+V4/woA/peqr+L0joiRXbPpQ042GgJckkFgw= +github.com/secure-systems-lab/go-securesystemslib v0.8.0 h1:mr5An6X45Kb2nddcFlbmfHkLguCE9laoZCUzEEpIZXA= +github.com/secure-systems-lab/go-securesystemslib v0.8.0/go.mod h1:UH2VZVuJfCYR8WgMlCU1uFsOUU+KeyrTWcSS73NBOzU= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/sigstore/fulcio v1.6.4 h1:d86obfxUAG3Y6CYwOx1pdwCZwKmROB6w6927pKOVIRY= github.com/sigstore/fulcio v1.6.4/go.mod h1:Y6bn3i3KGhXpaHsAtYP3Z4Np0+VzCo1fLv8Ci6mbPDs= -github.com/sigstore/rekor v1.3.8 h1:B8kJI8mpSIXova4Jxa6vXdJyysRxFGsEsLKBDl0rRjA= -github.com/sigstore/rekor v1.3.8/go.mod h1:/dHFYKSuxEygfDRnEwyJ+ZD6qoVYNXQdi1mJrKvKWsI= -github.com/sigstore/sigstore v1.8.12 h1:S8xMVZbE2z9ZBuQUEG737pxdLjnbOIcFi5v9UFfkJFc= -github.com/sigstore/sigstore v1.8.12/go.mod h1:+PYQAa8rfw0QdPpBcT+Gl3egKD9c+TUgAlF12H3Nmjo= +github.com/sigstore/rekor v1.3.6 h1:QvpMMJVWAp69a3CHzdrLelqEqpTM3ByQRt5B5Kspbi8= +github.com/sigstore/rekor v1.3.6/go.mod h1:JDTSNNMdQ/PxdsS49DJkJ+pRJCO/83nbR5p3aZQteXc= +github.com/sigstore/sigstore v1.8.9 h1:NiUZIVWywgYuVTxXmRoTT4O4QAGiTEKup4N1wdxFadk= +github.com/sigstore/sigstore v1.8.9/go.mod h1:d9ZAbNDs8JJfxJrYmulaTazU3Pwr8uLL9+mii4BNR3w= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/smallstep/pkcs7 v0.1.1 h1:x+rPdt2W088V9Vkjho4KtoggyktZJlMduZAtRHm68LU= -github.com/smallstep/pkcs7 v0.1.1/go.mod h1:dL6j5AIz9GHjVEBTXtW+QliALcgM19RtXaTeyxI+AfA= github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= @@ -503,10 +500,10 @@ github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 h1:e/5i7d4oYZ+C github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399/go.mod h1:LdwHTNJT99C5fTAzDz0ud328OgXz+gierycbcIx2fRs= github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/vbatts/tar-split v0.11.7 h1:ixZ93pO/GmvaZw4Vq9OwmfZK/kc2zKdPfu0B+gYqs3U= -github.com/vbatts/tar-split v0.11.7/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA= -github.com/vbauerster/mpb/v8 v8.9.1 h1:LH5R3lXPfE2e3lIGxN7WNWv3Hl5nWO6LRi2B0L0ERHw= -github.com/vbauerster/mpb/v8 v8.9.1/go.mod h1:4XMvznPh8nfe2NpnDo1QTPvW9MVkUhbG90mPWvmOzcQ= +github.com/vbatts/tar-split v0.11.6 h1:4SjTW5+PU11n6fZenf2IPoV8/tz3AaYHMWjf23envGs= +github.com/vbatts/tar-split v0.11.6/go.mod h1:dqKNtesIOr2j2Qv3W/cHjnvk9I8+G7oAkFDFN6TCBEI= +github.com/vbauerster/mpb/v8 v8.8.3 h1:dTOByGoqwaTJYPubhVz3lO5O6MK553XVgUo33LdnNsQ= +github.com/vbauerster/mpb/v8 v8.8.3/go.mod h1:JfCCrtcMsJwP6ZwMn9e5LMnNyp3TVNpUWWkN+nd4EWk= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= @@ -520,9 +517,8 @@ github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.etcd.io/bbolt v1.4.0 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk= -go.etcd.io/bbolt v1.4.0/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk= +go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= +go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= go.etcd.io/etcd/api/v3 v3.5.16 h1:WvmyJVbjWqK4R1E+B12RRHz3bRGy9XVfh++MgbN+6n0= go.etcd.io/etcd/api/v3 v3.5.16/go.mod h1:1P4SlIP/VwkDmGo3OlOD7faPeP8KDIFhqvciH5EfN28= go.etcd.io/etcd/client/pkg/v3 v3.5.16 h1:ZgY48uH6UvB+/7R9Yf4x574uCO3jIx0TRDyetSfId3Q= @@ -531,52 +527,52 @@ go.etcd.io/etcd/client/v3 v3.5.16 h1:sSmVYOAHeC9doqi0gv7v86oY/BTld0SEFGaxsU9eRhE go.etcd.io/etcd/client/v3 v3.5.16/go.mod h1:X+rExSGkyqxvu276cr2OwPLBaeqFu1cIl4vmRjAD/50= go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80= go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= +go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 h1:CCriYyAfq1Br1aIYettdHZTy8mBTIPo7We18TuO/bak= +go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/bridges/prometheus v0.57.0 h1:UW0+QyeyBVhn+COBec3nGhfnFe5lwB0ic1JBVjzhk0w= -go.opentelemetry.io/contrib/bridges/prometheus v0.57.0/go.mod h1:ppciCHRLsyCio54qbzQv0E4Jyth/fLWDTJYfvWpcSVk= -go.opentelemetry.io/contrib/exporters/autoexport v0.57.0 h1:jmTVJ86dP60C01K3slFQa2NQ/Aoi7zA+wy7vMOKD9H4= -go.opentelemetry.io/contrib/exporters/autoexport v0.57.0/go.mod h1:EJBheUMttD/lABFyLXhce47Wr6DPWYReCzaZiXadH7g= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 h1:r6I7RJCN86bpD/FQwedZ0vSixDpwuWREjW9oRMsmqDc= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI= +go.opentelemetry.io/contrib/bridges/prometheus v0.54.0 h1:WWL67oxtknNVMb70lJXxXruf8UyK/a9hmIE1XO3Uedg= +go.opentelemetry.io/contrib/bridges/prometheus v0.54.0/go.mod h1:LqNcnXmyULp8ertk4hUTVtSUvKXj4h1Mx7gUCSSr/q0= +go.opentelemetry.io/contrib/exporters/autoexport v0.54.0 h1:dTmcmVm4J54IRPGm5oVjLci1uYat4UDea84E2tyBaAk= +go.opentelemetry.io/contrib/exporters/autoexport v0.54.0/go.mod h1:zPp5Fwpq2Hc7xMtVttg6GhZMcfTESjVbY9ONw2o/Dc4= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 h1:9G6E0TXzGFVfTnawRzrPl83iHOAV7L8NJiR8RSGYV1g= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0/go.mod h1:azvtTADFQJA8mX80jIH/akaE7h+dbm/sVuaHqN13w74= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q= go.opentelemetry.io/otel v1.33.0 h1:/FerN9bax5LoK51X/sI0SVYrjSE0/yUL7DpxW4K3FWw= go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRlNx4I8I= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 h1:WzNab7hOOLzdDF/EoWCt4glhrbMPVMOO5JYTmpz36Ls= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0/go.mod h1:hKvJwTzJdp90Vh7p6q/9PAOd55dI6WA6sWj62a/JvSs= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 h1:S+LdBGiQXtJdowoJoQPEtI52syEP/JYBUpjO49EQhV8= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0/go.mod h1:5KXybFvPGds3QinJWQT7pmXf+TN5YIa7CNYObWRkj50= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0 h1:j7ZSD+5yn+lo3sGV69nW04rRR0jhYnBwjuX3r0HvnK0= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0/go.mod h1:WXbYJTUaZXAbYd8lbgGuvih0yuCfOFC5RJoYnoLcGz8= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0 h1:t/Qur3vKSkUCcDVaSumWF2PKHt85pc7fRvFuoVT8qFU= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0/go.mod h1:Rl61tySSdcOJWoEgYZVtmnKdA0GeKrSqkHC1t+91CH8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 h1:IJFEoHiytixx8cMiVAO+GmHR6Frwu+u5Ur8njpFO6Ac= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0/go.mod h1:3rHrKNtLIoS0oZwkY2vxi+oJcwFRWdtUyRII+so45p8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0 h1:9kV11HXBHZAvuPUZxmMWrH8hZn/6UnHX4K0mu36vNsU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0/go.mod h1:JyA0FHXe22E1NeNiHmVp7kFHglnexDQ7uRWDiiJ1hKQ= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.5.0 h1:4d++HQ+Ihdl+53zSjtsCUFDmNMju2FC9qFkUlTxPLqo= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.5.0/go.mod h1:mQX5dTO3Mh5ZF7bPKDkt5c/7C41u/SiDr9XgTpzXXn8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 h1:k6fQVDQexDE+3jG2SfCQjnHS7OamcP73YMoxEVq5B6k= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0/go.mod h1:t4BrYLHU450Zo9fnydWlIuswB1bm7rM8havDpWOJeDo= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 h1:xvhQxJ/C9+RTnAj5DpTg7LSM1vbbMTiXt7e9hsfqHNw= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0/go.mod h1:Fcvs2Bz1jkDM+Wf5/ozBGmi3tQ/c9zPKLnsipnfhGAo= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 h1:dIIDULZJpgdiHz5tXrTgKIMLkus6jEFa7x5SOKcyR7E= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0/go.mod h1:jlRVBe7+Z1wyxFSUs48L6OBQZ5JwH2Hg/Vbl+t9rAgI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0 h1:nSiV3s7wiCam610XcLbYOmMfJxB9gO4uK3Xgv5gmTgg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0/go.mod h1:hKn/e/Nmd19/x1gvIHwtOwVWM+VhuITSWip3JUDghj0= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0 h1:wpMfgF8E1rkrT1Z6meFh1NDtownE9Ii3n3X2GJYjsaU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0/go.mod h1:wAy0T/dUbs468uOlkT31xjvqQgEVXv58BRFWEgn5v/0= -go.opentelemetry.io/otel/exporters/prometheus v0.54.0 h1:rFwzp68QMgtzu9PgP3jm9XaMICI6TsofWWPcBDKwlsU= -go.opentelemetry.io/otel/exporters/prometheus v0.54.0/go.mod h1:QyjcV9qDP6VeK5qPyKETvNjmaaEc7+gqjh4SS0ZYzDU= -go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.8.0 h1:CHXNXwfKWfzS65yrlB2PVds1IBZcdsX8Vepy9of0iRU= -go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.8.0/go.mod h1:zKU4zUgKiaRxrdovSS2amdM5gOc59slmo/zJwGX+YBg= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.32.0 h1:SZmDnHcgp3zwlPBS2JX2urGYe/jBKEIT6ZedHRUyCz8= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.32.0/go.mod h1:fdWW0HtZJ7+jNpTKUR0GpMEDP69nR8YBJQxNiVCE3jk= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0 h1:cC2yDI3IQd0Udsux7Qmq8ToKAx1XCilTQECZ0KDZyTw= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0/go.mod h1:2PD5Ex6z8CFzDbTdOlwyNIUywRr1DN0ospafJM1wJ+s= -go.opentelemetry.io/otel/log v0.8.0 h1:egZ8vV5atrUWUbnSsHn6vB8R21G2wrKqNiDt3iWertk= -go.opentelemetry.io/otel/log v0.8.0/go.mod h1:M9qvDdUTRCopJcGRKg57+JSQ9LgLBrwwfC32epk5NX8= +go.opentelemetry.io/otel/exporters/prometheus v0.51.0 h1:G7uexXb/K3T+T9fNLCCKncweEtNEBMTO+46hKX5EdKw= +go.opentelemetry.io/otel/exporters/prometheus v0.51.0/go.mod h1:v0mFe5Kk7woIh938mrZBJBmENYquyA0IICrlYm4Y0t4= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.5.0 h1:ThVXnEsdwNcxdBO+r96ci1xbF+PgNjwlk457VNuJODo= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.5.0/go.mod h1:rHWcSmC4q2h3gje/yOq6sAOaq8+UHxN/Ru3BbmDXOfY= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0 h1:WDdP9acbMYjbKIyJUhTvtzj601sVJOqgWdUxSdR/Ysc= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0/go.mod h1:BLbf7zbNIONBLPwvFnwNHGj4zge8uTCM/UPIVW1Mq2I= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.29.0 h1:X3ZjNp36/WlkSYx0ul2jw4PtbNEDDeLskw3VPsrpYM0= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.29.0/go.mod h1:2uL/xnOXh0CHOBFCWXz5u1A4GXLiW+0IQIzVbeOEQ0U= +go.opentelemetry.io/otel/log v0.5.0 h1:x1Pr6Y3gnXgl1iFBwtGy1W/mnzENoK0w0ZoaeOI3i30= +go.opentelemetry.io/otel/log v0.5.0/go.mod h1:NU/ozXeGuOR5/mjCRXYbTC00NFJ3NYuraV/7O78F0rE= go.opentelemetry.io/otel/metric v1.33.0 h1:r+JOocAyeRVXD8lZpjdQjzMadVZp2M4WmQ+5WtEnklQ= go.opentelemetry.io/otel/metric v1.33.0/go.mod h1:L9+Fyctbp6HFTddIxClbQkjtubW6O9QS3Ann/M82u6M= go.opentelemetry.io/otel/sdk v1.33.0 h1:iax7M131HuAm9QkZotNHEfstof92xM+N8sr3uHXc2IM= go.opentelemetry.io/otel/sdk v1.33.0/go.mod h1:A1Q5oi7/9XaMlIWzPSxLRWOI8nG3FnzHJNbiENQuihM= -go.opentelemetry.io/otel/sdk/log v0.8.0 h1:zg7GUYXqxk1jnGF/dTdLPrK06xJdrXgqgFLnI4Crxvs= -go.opentelemetry.io/otel/sdk/log v0.8.0/go.mod h1:50iXr0UVwQrYS45KbruFrEt4LvAdCaWWgIrsN3ZQggo= -go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU= -go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= +go.opentelemetry.io/otel/sdk/log v0.5.0 h1:A+9lSjlZGxkQOr7QSBJcuyyYBw79CufQ69saiJLey7o= +go.opentelemetry.io/otel/sdk/log v0.5.0/go.mod h1:zjxIW7sw1IHolZL2KlSAtrUi8JHttoeiQy43Yl3WuVQ= +go.opentelemetry.io/otel/sdk/metric v1.29.0 h1:K2CfmJohnRgvZ9UAj2/FhIf/okdWcNdBwe1m8xFXiSY= +go.opentelemetry.io/otel/sdk/metric v1.29.0/go.mod h1:6zZLdCl2fkauYoZIOn/soQIDSWFmNSRcICarHfuhNJQ= go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s= go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= @@ -591,13 +587,8 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf 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.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= -golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= -golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20250228200357-dead58393ab7 h1:aWwlzYV971S4BXRS9AmqwDLAD85ouC6X+pocatKY58c= golang.org/x/exp v0.0.0-20250228200357-dead58393ab7/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk= @@ -606,11 +597,6 @@ golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvx golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -624,15 +610,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-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= -golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= -golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= +golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= +golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= @@ -642,14 +621,8 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= -golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -657,44 +630,18 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= -golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= -golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= -golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= -golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= -golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= +golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= 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.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= -golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -705,12 +652,8 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU= -golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s= +golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= +golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -722,8 +665,8 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk= -google.golang.org/genproto v0.0.0-20241118233622-e639e219e697/go.mod h1:JJrvXBWRZaFMxBufik1a4RpFw4HhgVtBBWQeQgUj2cc= +google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1 h1:BulPr26Jqjnd4eYDVe+YvyR7Yc2vJGkO5/0UxD0/jZU= +google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:hL97c3SYopEHblzpxRL4lSs523++l8DYxGM1FQiYmb4= google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950= google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg= google.golang.org/genproto/googleapis/rpc v0.0.0-20250227231956-55c901821b1e h1:YA5lmSs3zc/5w+xsRcHqpETkaYyK63ivEPzNTcUUlSA= @@ -733,8 +676,8 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= -google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= +google.golang.org/grpc v1.68.1 h1:oI5oTa11+ng8r8XMMN7jAOmWfPZWbYpCFaMUTACxkM0= +google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -765,14 +708,14 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= -helm.sh/helm/v3 v3.17.3 h1:3n5rW3D0ArjFl0p4/oWO8IbY/HKaNNwJtOQFdH2AZHg= -helm.sh/helm/v3 v3.17.3/go.mod h1:+uJKMH/UiMzZQOALR3XUf3BLIoczI2RKKD6bMhPh4G8= +helm.sh/helm/v3 v3.17.2 h1:agYQ5ew2jq5vdx2K7q5W44KyKQrnSubUMCQsjkiv3/o= +helm.sh/helm/v3 v3.17.2/go.mod h1:+uJKMH/UiMzZQOALR3XUf3BLIoczI2RKKD6bMhPh4G8= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8s.io/api v0.32.3 h1:Hw7KqxRusq+6QSplE3NYG4MBxZw1BZnq4aP4cJVINls= k8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k= -k8s.io/apiextensions-apiserver v0.32.2 h1:2YMk285jWMk2188V2AERy5yDwBYrjgWYggscghPCvV4= -k8s.io/apiextensions-apiserver v0.32.2/go.mod h1:GPwf8sph7YlJT3H6aKUWtd0E+oyShk/YHWQHf/OOgCA= +k8s.io/apiextensions-apiserver v0.32.3 h1:4D8vy+9GWerlErCwVIbcQjsWunF9SUGNu7O7hiQTyPY= +k8s.io/apiextensions-apiserver v0.32.3/go.mod h1:8YwcvVRMVzw0r1Stc7XfGAzB/SIVLunqApySV5V7Dss= k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U= k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= k8s.io/apiserver v0.32.3 h1:kOw2KBuHOA+wetX1MkmrxgBr648ksz653j26ESuWNY8= @@ -783,12 +726,18 @@ k8s.io/client-go v0.32.3 h1:RKPVltzopkSgHS7aS98QdscAgtgah/+zmpAogooIqVU= k8s.io/client-go v0.32.3/go.mod h1:3v0+3k4IcT9bXTc4V2rt+d2ZPPG700Xy6Oi0Gdl2PaY= k8s.io/component-base v0.32.3 h1:98WJvvMs3QZ2LYHBzvltFSeJjEx7t5+8s71P7M74u8k= k8s.io/component-base v0.32.3/go.mod h1:LWi9cR+yPAv7cu2X9rZanTiFKB2kHA+JjmhkKjCZRpI= +k8s.io/component-helpers v0.32.3 h1:9veHpOGTPLluqU4hAu5IPOwkOIZiGAJUhHndfVc5FT4= +k8s.io/component-helpers v0.32.3/go.mod h1:utTBXk8lhkJewBKNuNf32Xl3KT/0VV19DmiXU/SV4Ao= +k8s.io/controller-manager v0.32.3 h1:jBxZnQ24k6IMeWLyxWZmpa3QVS7ww+osAIzaUY/jqyc= +k8s.io/controller-manager v0.32.3/go.mod h1:out1L3DZjE/p7JG0MoMMIaQGWIkt3c+pKaswqSHgKsI= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7 h1:hcha5B1kVACrLujCKLbr8XWMxCxzQx42DY8QKYJrDLg= -k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7/go.mod h1:GewRfANuJ70iYzvn+i4lezLDAFzvjxZYK1gn1lWcfas= -k8s.io/kubectl v0.32.2 h1:TAkag6+XfSBgkqK9I7ZvwtF0WVtUAvK8ZqTt+5zi1Us= -k8s.io/kubectl v0.32.2/go.mod h1:+h/NQFSPxiDZYX/WZaWw9fwYezGLISP0ud8nQKg+3g8= +k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y= +k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4= +k8s.io/kubectl v0.32.3 h1:VMi584rbboso+yjfv0d8uBHwwxbC438LKq+dXd5tOAI= +k8s.io/kubectl v0.32.3/go.mod h1:6Euv2aso5GKzo/UVMacV6C7miuyevpfI91SvBvV9Zdg= +k8s.io/kubernetes v1.32.3 h1:2A58BlNME8NwsMawmnM6InYo3Jf35Nw5G79q46kXwoA= +k8s.io/kubernetes v1.32.3/go.mod h1:GvhiBeolvSRzBpFlgM0z/Bbu3Oxs9w3P6XfEgYaMi8k= k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0= k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= oras.land/oras-go v1.2.5 h1:XpYuAwAb0DfQsunIyMfeET92emK8km3W4yEzZvUbsTo= diff --git a/hack/tools/k8smaintainer/main.go b/hack/tools/k8smaintainer/main.go index 2ed6d52f8..a7461b4d1 100644 --- a/hack/tools/k8smaintainer/main.go +++ b/hack/tools/k8smaintainer/main.go @@ -3,356 +3,315 @@ package main import ( "bytes" "encoding/json" - "flag" "fmt" + "log" "os" "os/exec" + "path/filepath" "sort" "strings" "golang.org/x/mod/modfile" "golang.org/x/mod/module" + "golang.org/x/mod/semver" ) -// debug controls whether we print extra statements. -var debug bool +const k8sRepo = "k8s.io/kubernetes" -// moduleInfo is the partial output of `go list -m -json all`. -type moduleInfo struct { - Path string `json:"Path"` - Version string `json:"Version"` -} +//nolint:gochecknoglobals +var goExe = "go" func main() { - // Define the command-line flag - flag.BoolVar(&debug, "debug", false, "Enable debug output") - flag.Parse() - - mainGoMod := getMainGoModPath() - - if err := fixGoMod(mainGoMod); err != nil { - fmt.Fprintf(os.Stderr, "fixGoMod failed: %v\n", err) - os.Exit(1) + log.SetFlags(0) + if os.Getenv("GOEXE") != "" { + goExe = os.Getenv("GOEXE") } -} -func getMainGoModPath() string { - rootDir := findProjectRoot() - return fmt.Sprintf("%s/go.mod", rootDir) -} - -func findProjectRoot() string { - cwd, err := os.Getwd() + wd, err := os.Getwd() if err != nil { - fmt.Fprintf(os.Stderr, "failed to get working directory: %v\n", err) - os.Exit(1) + log.Fatalf("Error getting working directory: %v", err) } - - for cwd != "/" { - if _, err := os.Stat(fmt.Sprintf("%s/go.mod", cwd)); err == nil { - return cwd - } - cwd = cwd[:strings.LastIndex(cwd, "/")] + modRoot := findModRoot(wd) + if modRoot == "" { + log.Fatalf("Failed to find go.mod in %s or parent directories", wd) } - fmt.Fprintln(os.Stderr, "Error: Could not find project root with go.mod") - os.Exit(1) - return "" -} - -// fixGoMod is the main entrypoint. It does a 2‐phase approach: -// -// Remove old k8s.io/* replace lines, rewrite + tidy so they’re really gone. -// Parse again, unify staging modules in require + replace to the new patch version, rewrite + tidy. -func fixGoMod(goModPath string) error { - mf, err := parseMod(goModPath) - if err != nil { - return err + if err := os.Chdir(modRoot); err != nil { + log.Fatalf("Error changing directory to %s: %v", modRoot, err) } - pruneK8sReplaces(mf) - mf.SortBlocks() - mf.Cleanup() + log.Printf("Running in module root: %s", modRoot) - if err := writeModFile(mf, goModPath); err != nil { - return err + modBytes, err := os.ReadFile("go.mod") + if err != nil { + log.Fatalf("Error reading go.mod: %v", err) } - mf, err = parseMod(goModPath) + modF, err := modfile.Parse("go.mod", modBytes, nil) if err != nil { - return err + log.Fatalf("Error parsing go.mod: %v", err) } - k8sVer := findKubernetesVersion(mf) + // Find k8s.io/kubernetes version + k8sVer := "" + for _, req := range modF.Require { + if req.Mod.Path == k8sRepo { + k8sVer = req.Mod.Version + break + } + } if k8sVer == "" { - return fmt.Errorf("did not find k8s.io/kubernetes in require block") + log.Fatalf("Could not find %s in go.mod require block", k8sRepo) } + log.Printf("Found %s version: %s", k8sRepo, k8sVer) - published := toPublishedVersion(k8sVer) - if published == "" { - return fmt.Errorf("cannot derive staging version from %s", k8sVer) + // Calculate target staging version + if !semver.IsValid(k8sVer) { + log.Fatalf("Invalid semver for %s: %s", k8sRepo, k8sVer) } - - forceRequireStaging(mf, published) - - listOut, errOut, err := runGoList() + majorMinor := semver.MajorMinor(k8sVer) // e.g., "v1.32" + patch := strings.TrimPrefix(k8sVer, majorMinor+".") // e.g., "3" + if len(strings.Split(majorMinor, ".")) != 2 { + log.Fatalf("Unexpected format for MajorMinor: %s", majorMinor) + } + targetStagingVer := "v0" + strings.TrimPrefix(majorMinor, "v1") + "." + patch // e.g., "v0.32.3" + if !semver.IsValid(targetStagingVer) { + log.Fatalf("Calculated invalid staging semver: %s", targetStagingVer) + } + log.Printf("Target staging version calculated: %s", targetStagingVer) + + // Run `go list -m -json all` + type Module struct { + Path string + Version string + Replace *Module + Main bool + } + log.Println("Running 'go list -m -json all'...") + output, err := runGoCommand("list", "-m", "-json", "all") if err != nil { - return fmt.Errorf("go list: %v\nStderr:\n%s", err, errOut) + // Try downloading first if list fails + log.Println("go list failed, trying go mod download...") + if _, downloadErr := runGoCommand("mod", "download"); downloadErr != nil { + log.Fatalf("Error running 'go mod download' after list failed: %v", downloadErr) + } + output, err = runGoCommand("list", "-m", "-json", "all") + if err != nil { + log.Fatalf("Error running 'go list -m -json all' even after download: %v", err) + } } - stagingPins := discoverPinsAlways(listOut, published) - applyReplacements(mf, stagingPins) - ensureKubernetesReplace(mf, k8sVer) + // Iterate, identify k8s.io/* staging modules, and determine version to pin + pins := make(map[string]string) // Module path -> version to pin + decoder := json.NewDecoder(bytes.NewReader(output)) + for decoder.More() { + var mod Module + if err := decoder.Decode(&mod); err != nil { + log.Fatalf("Error decoding go list output: %v", err) + } - mf.SortBlocks() - mf.Cleanup() + // Skip main module, non-k8s modules, k8s.io/kubernetes itself, and versioned modules like k8s.io/client-go/v2 + _, pathSuffix, _ := module.SplitPathVersion(mod.Path) // Check if path has a version suffix like /v2, /v3 etc. + if mod.Main || !strings.HasPrefix(mod.Path, "k8s.io/") || mod.Path == k8sRepo || pathSuffix != "" { + continue + } - if err := writeModFile(mf, goModPath); err != nil { - return err - } + // Use replacement path if it exists + effectivePath := mod.Path + if mod.Replace != nil { + effectivePath = mod.Replace.Path + // Skip local file replacements + if !strings.Contains(effectivePath, ".") { // Basic check if it looks like a module path vs local path + log.Printf("Skipping local replace: %s => %s", mod.Path, effectivePath) + continue + } + } - goVersion, err := getGoVersion(goModPath) - if err != nil { - return fmt.Errorf("failed to determine Go version: %w", err) - } + // Check existence of target version, fallback to previous patch if needed + determinedVer, err := getLatestExistingVersion(effectivePath, targetStagingVer) + if err != nil { + log.Printf("WARNING: Error checking versions for %s: %v. Skipping pinning.", effectivePath, err) + continue + } - if err := runCmd("go", "mod", "tidy", "-go="+goVersion); err != nil { - return fmt.Errorf("final tidy failed: %w", err) - } + if determinedVer == "" { + log.Printf("WARNING: Neither target version %s nor its predecessor found for %s. Skipping pinning.", targetStagingVer, effectivePath) + continue + } - return nil -} + if determinedVer != targetStagingVer { + log.Printf("WARNING: Target version %s not found for %s. Using existing version %s.", targetStagingVer, effectivePath, determinedVer) + } -func getGoVersion(goModPath string) (string, error) { - mf, err := parseMod(goModPath) - if err != nil { - return "", err - } - if mf.Go == nil { - return "", fmt.Errorf("go version not found in go.mod") + // map the original module path (as seen in the dependency graph) to the desired version for the 'replace' directive + pins[mod.Path] = determinedVer } - return mf.Go.Version, nil -} -// parseMod reads go.mod into memory as a modfile.File -func parseMod(path string) (*modfile.File, error) { - data, err := os.ReadFile(path) + // Add k8s.io/kubernetes itself to the pins map + pins[k8sRepo] = k8sVer + log.Printf("Identified %d k8s.io/* modules to manage.", len(pins)) + + // 7. Parse go.mod again (to have a fresh modfile object) + modBytes, err = os.ReadFile("go.mod") if err != nil { - return nil, fmt.Errorf("reading %s: %w", path, err) + log.Fatalf("Error reading go.mod again: %v", err) } - mf, err := modfile.Parse(path, data, nil) + modF, err = modfile.Parse("go.mod", modBytes, nil) if err != nil { - return nil, fmt.Errorf("parsing %s: %w", path, err) + log.Fatalf("Error parsing go.mod again: %v", err) } - return mf, nil -} -// writeModFile formats and writes the modfile back to disk -func writeModFile(mf *modfile.File, path string) error { - formatted, err := mf.Format() - if err != nil { - return fmt.Errorf("formatting modfile: %w", err) - } - if err := os.WriteFile(path, formatted, 0600); err != nil { - return fmt.Errorf("writing %s: %w", path, err) + // Remove all existing k8s.io/* replaces + log.Println("Removing existing k8s.io/* replace directives...") + var replacesToRemove []string + for _, rep := range modF.Replace { + if strings.HasPrefix(rep.Old.Path, "k8s.io/") { + replacesToRemove = append(replacesToRemove, rep.Old.Path) + } } - if debug { - fmt.Printf("Wrote %s\n", path) + for _, path := range replacesToRemove { + if err := modF.DropReplace(path, ""); err != nil { + // Tolerate errors if the replace was already somehow removed + log.Printf("Note: Error dropping replace for %s (might be benign): %v", path, err) + } } - return nil -} -// pruneK8sReplaces removes any replace lines with Old.Path prefix "k8s.io/" -func pruneK8sReplaces(mf *modfile.File) { - var keep []*modfile.Replace - for _, rep := range mf.Replace { - if strings.HasPrefix(rep.Old.Path, "k8s.io/") { - if debug { - fmt.Printf("Dropping old replace for %s => %s %s\n", - rep.Old.Path, rep.New.Path, rep.New.Version) - } - } else { - keep = append(keep, rep) + // Add new replace directives + log.Println("Adding determined replace directives...") + // Sort for deterministic output + sortedPaths := make([]string, 0, len(pins)) + for path := range pins { + sortedPaths = append(sortedPaths, path) + } + sort.Strings(sortedPaths) + + for _, path := range sortedPaths { + version := pins[path] + // Add replace for the module path itself (e.g., k8s.io/api => k8s.io/api v0.32.3) + // This handles cases where the effective path from `go list` might differ due to other replaces + if err := modF.AddReplace(path, "", path, version); err != nil { + log.Fatalf("Error adding replace for %s => %s %s: %v", path, path, version, err) } + log.Printf("Adding replace: %s => %s %s", path, path, version) } - mf.Replace = keep -} -// forceRequireStaging forcibly sets the require lines for all staging modules -// (k8s.io/*) to the desired patch version if a valid tag is found. We remove -// the old line first, then AddRequire so the final go.mod show them updated. -func forceRequireStaging(mf *modfile.File, newVersion string) { - var stagingPaths []string - - // gather all relevant require lines we want to unify - for _, req := range mf.Require { - p := req.Mod.Path - if strings.HasPrefix(p, "k8s.io/") && - p != "k8s.io/kubernetes" && - !hasMajorVersionSuffix(p) { - stagingPaths = append(stagingPaths, p) - } + // Write go.mod + log.Println("Writing updated go.mod...") + modF.Cleanup() // Sort blocks, etc. + newModBytes, err := modF.Format() + if err != nil { + log.Fatalf("Error formatting go.mod: %v", err) } - // remove them - for _, p := range stagingPaths { - if debug { - fmt.Printf("Removing require line for %s\n", p) - } - _ = mf.DropRequire(p) // returns an error if not found, ignore + if err := os.WriteFile("go.mod", newModBytes, 0600); err != nil { + log.Fatalf("Error writing go.mod: %v", err) } - // re-add them at the new version if we can download that version - for _, p := range stagingPaths { - if versionExists(p, newVersion) { - if debug { - fmt.Printf("Adding require line for %s at %s\n", p, newVersion) - } - _ = mf.AddRequire(p, newVersion) - } else { - fmt.Printf("WARNING: no valid tag for %s at %s, skipping\n", p, newVersion) - } + + // Run `go mod tidy` + goVer := modF.Go.Version + tidyArgs := []string{"mod", "tidy"} + if goVer != "" { + tidyArgs = append(tidyArgs, fmt.Sprintf("-go=%s", goVer)) + log.Printf("Running 'go mod tidy -go=%s'...", goVer) + } else { + log.Println("Running 'go mod tidy'...") + } + if _, err := runGoCommand(tidyArgs...); err != nil { + log.Fatalf("Error running 'go mod tidy': %v", err) + } + + // Run `go mod download k8s.io/kubernetes` + log.Printf("Running 'go mod download %s'...", k8sRepo) + if _, err := runGoCommand("mod", "download", k8sRepo); err != nil { + // This might not be fatal, could be network issues, but log it prominently + log.Printf("WARNING: Error running 'go mod download %s': %v", k8sRepo, err) } + + log.Println("Successfully updated k8s dependencies.") } -// discoverPinsAlways identifies k8s.io/* modules from the "go list -m -json all" -// output, and unifies them all to `published` if it’s downloadable. This does -// not skip forced downgrades. If it's a staging path, we pin it. -func discoverPinsAlways(listOut, published string) map[string]string { - pins := make(map[string]string) - dec := json.NewDecoder(strings.NewReader(listOut)) +// findModRoot searches for go.mod in dir and parent directories +func findModRoot(dir string) string { for { - var mi moduleInfo - if err := dec.Decode(&mi); err != nil { - break - } - if !strings.HasPrefix(mi.Path, "k8s.io/") { - continue - } - if mi.Path == "k8s.io/kubernetes" { - continue + if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil { + return dir } - if hasMajorVersionSuffix(mi.Path) { - if debug { - fmt.Printf("Skipping major-version module %s\n", mi.Path) - } - continue - } - // unify everything if a valid tag exists - if mi.Version != published { - if versionExists(mi.Path, published) { - if debug { - fmt.Printf("Pinning %s from %s to %s\n", mi.Path, mi.Version, published) - } - pins[mi.Path] = published - } else { - fmt.Printf("WARNING: no valid tag for %s at %s, leaving as %s\n", - mi.Path, published, mi.Version) - } + parent := filepath.Dir(dir) + if parent == dir { + return "" // Reached root } + dir = parent } - return pins } -// applyReplacements adds replace lines for each pinned staging module -func applyReplacements(mf *modfile.File, pins map[string]string) { - if len(pins) == 0 { - return - } - sorted := make([]string, 0, len(pins)) - for p := range pins { - sorted = append(sorted, p) +// runGoCommand executes a go command and returns its stdout or an error +func runGoCommand(args ...string) ([]byte, error) { + cmd := exec.Command(goExe, args...) + cmd.Env = append(os.Environ(), "GO111MODULE=on") // Ensure module mode + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + if err := cmd.Run(); err != nil { + return nil, fmt.Errorf("command '%s %s' failed: %v\nStderr:\n%s", goExe, strings.Join(args, " "), err, stderr.String()) } - sort.Strings(sorted) - for _, path := range sorted { - ver := pins[path] - if debug { - fmt.Printf("Applying new replace: %s => %s\n", path, ver) - } - if err := mf.AddReplace(path, "", path, ver); err != nil { - die("Error adding replace for %s: %v", path, err) + return stdout.Bytes(), nil +} + +// getModuleVersions retrieves the list of available versions for a module +func getModuleVersions(modulePath string) ([]string, error) { + output, err := runGoCommand("list", "-m", "-versions", modulePath) + if err != nil { + // Check if the error is "no matching versions" - this is not a fatal error for our logic + if strings.Contains(string(output)+err.Error(), "no matching versions") { + return []string{}, nil // Return empty list, not an error } + return nil, fmt.Errorf("error listing versions for %s: %w", modulePath, err) } + fields := strings.Fields(string(output)) + if len(fields) < 2 { + return []string{}, nil // No versions listed + } + return fields[1:], nil // First field is the module path } -// ensureKubernetesReplace ensures there's a "k8s.io/kubernetes => k8s.io/kubernetes vX.Y.Z" line -// matching the require(...) version in case something references it directly. -func ensureKubernetesReplace(mf *modfile.File, k8sVer string) { - newReplaces := make([]*modfile.Replace, 0, len(mf.Replace)+1) +// getLatestExistingVersion checks for targetVer and its predecessor, returning the latest one that exists +func getLatestExistingVersion(modulePath, targetVer string) (string, error) { + availableVersions, err := getModuleVersions(modulePath) + if err != nil { + return "", err + } - for _, rep := range mf.Replace { - if rep.Old.Path == "k8s.io/kubernetes" && rep.New.Version != k8sVer { - if debug { - fmt.Printf("Updating k8s.io/kubernetes replace from %s to %s\n", rep.New.Version, k8sVer) - } - continue // Skip adding this entry to newReplaces + foundTarget := false + for _, v := range availableVersions { + if v == targetVer { + foundTarget = true + break } - newReplaces = append(newReplaces, rep) } - // Add the correct replace directive - newReplaces = append(newReplaces, &modfile.Replace{ - Old: module.Version{Path: "k8s.io/kubernetes"}, - New: module.Version{Path: "k8s.io/kubernetes", Version: k8sVer}, - }) + if foundTarget { + return targetVer, nil // Target version exists + } - mf.Replace = newReplaces -} + // Target not found, try previous patch version + majorMinor := semver.MajorMinor(targetVer) // e.g., v0.32 + patchStr := strings.TrimPrefix(targetVer, majorMinor+".") // e.g., 3 + var patch int + if _, err := fmt.Sscan(patchStr, &patch); err != nil || patch < 1 { + log.Printf("Could not parse patch version or patch <= 0 for %s, cannot determine predecessor.", targetVer) + return "", nil // Cannot determine predecessor + } + prevPatchVer := fmt.Sprintf("%s.%d", majorMinor, patch-1) // e.g., v0.32.2 -// findKubernetesVersion returns the version in the require(...) block for k8s.io/kubernetes -func findKubernetesVersion(mf *modfile.File) string { - for _, req := range mf.Require { - if req.Mod.Path == "k8s.io/kubernetes" { - return req.Mod.Version + foundPrev := false + for _, v := range availableVersions { + if v == prevPatchVer { + foundPrev = true + break } } - return "" -} -// toPublishedVersion: e.g. "v1.32.2" => "v0.32.2" -func toPublishedVersion(k8sVersion string) string { - if !strings.HasPrefix(k8sVersion, "v") { - return "" - } - parts := strings.Split(strings.TrimPrefix(k8sVersion, "v"), ".") - if len(parts) < 3 { - return "" + if foundPrev { + return prevPatchVer, nil // Predecessor version exists } - return fmt.Sprintf("v0.%s.%s", parts[1], parts[2]) -} - -// runGoList runs "go list -m -json all" and returns stdout, stderr, error -func runGoList() (string, string, error) { - cmd := exec.Command("go", "list", "-m", "-json", "all") - var outBuf, errBuf bytes.Buffer - cmd.Stdout = &outBuf - cmd.Stderr = &errBuf - err := cmd.Run() - return outBuf.String(), errBuf.String(), err -} - -// runCmd runs a command with stdout/stderr displayed. Returns an error if it fails. -func runCmd(name string, args ...string) error { - cmd := exec.Command(name, args...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - return cmd.Run() -} - -// versionExists quietly tries `go mod download modPath@ver`. If 0 exit code => true. -func versionExists(modPath, ver string) bool { - safeArg := fmt.Sprintf("%s@%s", modPath, ver) - cmd := exec.Command("go", "mod", "download", safeArg) - cmd.Stdout = nil - cmd.Stderr = nil - return cmd.Run() == nil -} - -// hasMajorVersionSuffix checks for trailing /v2, /v3, etc. in the module path -func hasMajorVersionSuffix(path string) bool { - segs := strings.Split(path, "/") - last := segs[len(segs)-1] - return len(last) > 1 && last[0] == 'v' && last[1] >= '2' && last[1] <= '9' -} -// die prints an error and exits. -func die(format string, args ...interface{}) { - fmt.Fprintf(os.Stderr, format+"\n", args...) - os.Exit(1) + // Neither found + return "", nil } From ead6f0925518f21556c68dac82946f3510b95e6c Mon Sep 17 00:00:00 2001 From: Brett Tofel Date: Wed, 2 Apr 2025 14:46:52 -0400 Subject: [PATCH 29/67] Simplify PreAuthorizer handling via feature gate Signed-off-by: Brett Tofel --- cmd/operator-controller/main.go | 19 ++++++++++++++----- internal/operator-controller/applier/helm.go | 11 +++++------ 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/cmd/operator-controller/main.go b/cmd/operator-controller/main.go index d07d1dad7..c3d8c4fde 100644 --- a/cmd/operator-controller/main.go +++ b/cmd/operator-controller/main.go @@ -416,12 +416,21 @@ func run() error { crdupgradesafety.NewPreflight(aeClient.CustomResourceDefinitions()), } + // determine if PreAuthorizer should be enabled based on feature gate + var preAuth authorization.PreAuthorizer + if features.OperatorControllerFeatureGate.Enabled(features.PreflightPermissions) { + setupLog.Info("preflight permissions check enabled via feature gate") + preAuth = authorization.NewRBACPreAuthorizer(mgr.GetClient()) + } else { + setupLog.Info("preflight permissions check disabled via feature gate") + } + + // now initialize the helmApplier, assigning the potentially nil preAuth helmApplier := &applier.Helm{ - ActionClientGetter: acg, - Preflights: preflights, - BundleToHelmChartFn: convert.RegistryV1ToHelmChart, - PreAuthorizer: authorization.NewRBACPreAuthorizer(mgr.GetClient()), - EnablePreflightPermissions: features.OperatorControllerFeatureGate.Enabled(features.PreflightPermissions), + ActionClientGetter: acg, + Preflights: preflights, + BundleToHelmChartFn: convert.RegistryV1ToHelmChart, + PreAuthorizer: preAuth, } cm := contentmanager.NewManager(clientRestConfigMapper, mgr.GetConfig(), mgr.GetRESTMapper()) diff --git a/internal/operator-controller/applier/helm.go b/internal/operator-controller/applier/helm.go index 06a97b4c4..c5cefce32 100644 --- a/internal/operator-controller/applier/helm.go +++ b/internal/operator-controller/applier/helm.go @@ -57,11 +57,10 @@ type Preflight interface { type BundleToHelmChartFn func(rv1 fs.FS, installNamespace string, watchNamespace string) (*chart.Chart, error) type Helm struct { - ActionClientGetter helmclient.ActionClientGetter - Preflights []Preflight - PreAuthorizer authorization.PreAuthorizer - BundleToHelmChartFn BundleToHelmChartFn - EnablePreflightPermissions bool + ActionClientGetter helmclient.ActionClientGetter + Preflights []Preflight + PreAuthorizer authorization.PreAuthorizer + BundleToHelmChartFn BundleToHelmChartFn } // shouldSkipPreflight is a helper to determine if the preflight check is CRDUpgradeSafety AND @@ -95,7 +94,7 @@ func (h *Helm) Apply(ctx context.Context, contentFS fs.FS, ext *ocv1.ClusterExte labels: objectLabels, } - if h.EnablePreflightPermissions { + if h.PreAuthorizer != nil { tmplRel, err := h.renderClientOnlyRelease(ctx, ext, chrt, values, post) if err != nil { return nil, "", fmt.Errorf("failed to get release state using client-only dry-run: %w", err) From 2adb8280b51c7f54fa99cc3bbb9f42d7a37ff258 Mon Sep 17 00:00:00 2001 From: Brett Tofel Date: Wed, 2 Apr 2025 15:31:34 -0400 Subject: [PATCH 30/67] Split pre-auth checks cluster-scoped & ns-scoped Signed-off-by: Brett Tofel --- .../operator-controller/authorization/rbac.go | 30 +++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/internal/operator-controller/authorization/rbac.go b/internal/operator-controller/authorization/rbac.go index b8a2c9c88..0631ea401 100644 --- a/internal/operator-controller/authorization/rbac.go +++ b/internal/operator-controller/authorization/rbac.go @@ -8,6 +8,7 @@ import ( "maps" "sort" + corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" @@ -38,10 +39,7 @@ type ScopedPolicyRules struct { MissingRules []rbacv1.PolicyRule } -var ( - collectionVerbs = []string{"list", "watch", "create"} - objectVerbs = []string{"get", "patch", "update", "delete"} -) +var objectVerbs = []string{"get", "patch", "update", "delete"} type rbacPreAuthorizer struct { authorizer authorizer.Authorizer @@ -274,10 +272,19 @@ func (dm *decodedManifest) rbacObjects() []client.Object { func (dm *decodedManifest) asAuthorizationAttributesRecordsForUser(manifestManager user.Info) []authorizer.AttributesRecord { var attributeRecords []authorizer.AttributesRecord + + // Here we are splitting collection verbs based on required scope + // NB: this split is tightly coupled to the requirements of the contentmanager, specifically + // its need for cluster-scoped list/watch permissions. + // TODO: We are accepting this coupling for now, but plan to decouple + namespacedCollectionVerbs := []string{"create"} + clusterCollectionVerbs := []string{"list", "watch"} + for gvr, keys := range dm.gvrs { namespaces := sets.New[string]() for _, k := range keys { namespaces.Insert(k.Namespace) + // generate records for object-specific verbs (get, update, patch, delete) in their respective namespaces for _, v := range objectVerbs { attributeRecords = append(attributeRecords, authorizer.AttributesRecord{ User: manifestManager, @@ -291,8 +298,9 @@ func (dm *decodedManifest) asAuthorizationAttributesRecordsForUser(manifestManag }) } } + // generate records for namespaced collection verbs (create) for each relevant namespace for _, ns := range sets.List(namespaces) { - for _, v := range collectionVerbs { + for _, v := range namespacedCollectionVerbs { attributeRecords = append(attributeRecords, authorizer.AttributesRecord{ User: manifestManager, Namespace: ns, @@ -304,6 +312,18 @@ func (dm *decodedManifest) asAuthorizationAttributesRecordsForUser(manifestManag }) } } + // generate records for cluster-scoped collection verbs (list, watch) required by contentmanager + for _, v := range clusterCollectionVerbs { + attributeRecords = append(attributeRecords, authorizer.AttributesRecord{ + User: manifestManager, + Namespace: corev1.NamespaceAll, // check cluster scope + APIGroup: gvr.Group, + APIVersion: gvr.Version, + Resource: gvr.Resource, + ResourceRequest: true, + Verb: v, + }) + } } return attributeRecords } From 4d615a77ac1e81c55227dcfd2b5fefa000e045aa Mon Sep 17 00:00:00 2001 From: Tayler Geiger Date: Wed, 2 Apr 2025 14:25:59 -0500 Subject: [PATCH 31/67] Handle missing rules from escalation errors Also sort final missing rules by namespace Signed-off-by: Tayler Geiger --- .../operator-controller/authorization/rbac.go | 54 +++++++++++- .../authorization/rbac_test.go | 83 ++++++++++++++++++- 2 files changed, 134 insertions(+), 3 deletions(-) diff --git a/internal/operator-controller/authorization/rbac.go b/internal/operator-controller/authorization/rbac.go index 0631ea401..c281e55cd 100644 --- a/internal/operator-controller/authorization/rbac.go +++ b/internal/operator-controller/authorization/rbac.go @@ -6,7 +6,10 @@ import ( "fmt" "io" "maps" + "reflect" + "regexp" "sort" + "strings" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" @@ -89,8 +92,14 @@ func (a *rbacPreAuthorizer) PreAuthorize(ctx context.Context, manifestManager us for _, obj := range dm.rbacObjects() { if err := ec.checkEscalation(ctx, manifestManager, obj); err != nil { - // In Kubernetes 1.32.2 the specialized PrivilegeEscalationError is gone. - // Instead, we simply collect the error. + missingEscalationRules, namespace := parseEscalationErrorForMissingRules(err) + // Check if we already have these escalation PolicyRules, if so don't append + for i, rule := range missingEscalationRules { + previousRule := missingRules[namespace][len(missingRules[namespace])-len(missingEscalationRules)+i] + if !arePolicyRulesEqual(previousRule, rule) { + missingRules[namespace] = append(missingRules[namespace], missingEscalationRules...) + } + } preAuthEvaluationErrors = append(preAuthEvaluationErrors, err) } } @@ -108,6 +117,11 @@ func (a *rbacPreAuthorizer) PreAuthorize(ctx context.Context, manifestManager us allMissingPolicyRules = append(allMissingPolicyRules, ScopedPolicyRules{Namespace: ns, MissingRules: sortableRules}) } + // sort allMissingPolicyRules alphabetically by namespace + sort.Slice(allMissingPolicyRules, func(i, j int) bool { + return allMissingPolicyRules[i].Namespace < allMissingPolicyRules[j].Namespace + }) + if len(preAuthEvaluationErrors) > 0 { return allMissingPolicyRules, fmt.Errorf("authorization evaluation errors: %w", errors.Join(preAuthEvaluationErrors...)) } @@ -504,6 +518,42 @@ var fullAuthority = []rbacv1.PolicyRule{ {Verbs: []string{"*"}, NonResourceURLs: []string{"*"}}, } +func parseEscalationErrorForMissingRules(ecError error) ([]rbacv1.PolicyRule, string) { + // Regex to capture namespace and serviceaccount + userRegex := regexp.MustCompile(`system:serviceaccount:(?P[^:]+):(?P[^"]+)`) + // Regex to capture missing permissions + permRegex := regexp.MustCompile(`{APIGroups:\[("[^"]*")\], Resources:\[("[^"]*")\], Verbs:\[("[^"]*")\]}`) + + userMatches := userRegex.FindStringSubmatch(ecError.Error()) + namespace := userMatches[1] + + // Extract permissions + var permissions []rbacv1.PolicyRule + permMatches := permRegex.FindAllStringSubmatch(ecError.Error(), -1) + for _, match := range permMatches { + permissions = append(permissions, rbacv1.PolicyRule{ + APIGroups: []string{strings.Trim(match[1], `"`)}, + Resources: []string{strings.Trim(match[2], `"`)}, + Verbs: []string{strings.Trim(match[3], `"`)}, + }) + } + + return permissions, namespace +} + +func arePolicyRulesEqual(rule1 rbacv1.PolicyRule, rule2 rbacv1.PolicyRule) bool { + sort.Strings(rule1.Verbs) + sort.Strings(rule2.Verbs) + sort.Strings(rule1.APIGroups) + sort.Strings(rule2.APIGroups) + sort.Strings(rule1.Resources) + sort.Strings(rule2.Resources) + sort.Strings(rule1.ResourceNames) + sort.Strings(rule2.ResourceNames) + + return reflect.DeepEqual(rule1, rule2) +} + func hasAggregationRule(clusterRole *rbacv1.ClusterRole) bool { // Currently, an aggregation rule is considered present only if it has one or more selectors. // An empty slice of ClusterRoleSelectors means no selectors were provided, diff --git a/internal/operator-controller/authorization/rbac_test.go b/internal/operator-controller/authorization/rbac_test.go index aa9fddc06..90f780b3c 100644 --- a/internal/operator-controller/authorization/rbac_test.go +++ b/internal/operator-controller/authorization/rbac_test.go @@ -55,6 +55,76 @@ subjects: - kind: ServiceAccount name: test-serviceaccount namespace: test-namespace + ` + + testManifestMultiNamespace = `apiVersion: v1 +kind: Service +metadata: + name: test-service + namespace: test-namespace +spec: + clusterIP: None +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: test-extension-role + namespace: test-namespace +rules: +- apiGroups: ["*"] + resources: [serviceaccounts] + verbs: [watch] +- apiGroups: ["*"] + resources: [certificates] + verbs: [create] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: test-extension-binding + namespace: test-namespace +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: test-extension-role +subjects: +- kind: ServiceAccount + name: test-serviceaccount + namespace: test-namespace +--- +kind: Service +metadata: + name: test-service + namespace: a-test-namespace +spec: + clusterIP: None +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: test-extension-role + namespace: a-test-namespace +rules: +- apiGroups: ["*"] + resources: [serviceaccounts] + verbs: [watch] +- apiGroups: ["*"] + resources: [certificates] + verbs: [create] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: test-extension-binding + namespace: a-test-namespace +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: test-extension-role +subjects: +- kind: ServiceAccount + name: test-serviceaccount + namespace: a-test-namespace ` saName = "test-serviceaccount" @@ -158,7 +228,7 @@ func TestPreAuthorize_Success(t *testing.T) { } func TestPreAuthorize_Failure(t *testing.T) { - t.Run("preauthorize failes with missing rbac rules", func(t *testing.T) { + t.Run("preauthorize fails with missing rbac rules", func(t *testing.T) { featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.PreflightPermissions, true) fakeClient := setupFakeClient(limitedClusterRole) preAuth := NewRBACPreAuthorizer(fakeClient) @@ -168,6 +238,17 @@ func TestPreAuthorize_Failure(t *testing.T) { }) } +func TestPreAuthorizeMultiNamespace_Failure(t *testing.T) { + t.Run("preauthorize fails with missing rbac rules in multiple namespaces", func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.PreflightPermissions, true) + fakeClient := setupFakeClient(limitedClusterRole) + preAuth := NewRBACPreAuthorizer(fakeClient) + missingRules, err := preAuth.PreAuthorize(context.TODO(), &testServiceAccount, strings.NewReader(testManifestMultiNamespace)) + require.Error(t, err) + require.NotEqual(t, []ScopedPolicyRules{}, missingRules) + }) +} + func TestPreAuthorize_CheckEscalation(t *testing.T) { t.Run("preauthorize succeeds with no missing rbac rules", func(t *testing.T) { featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.PreflightPermissions, true) From 99617301ae60e00308b8680d25d7e7720e16730f Mon Sep 17 00:00:00 2001 From: Tayler Geiger Date: Wed, 2 Apr 2025 16:43:46 -0500 Subject: [PATCH 32/67] Clean up escalation error parsing and fix tests Pass in the clusterextension to PreAuthorize instead of the user.Info since we need the extension to create the clusterextension/finalizer Signed-off-by: Tayler Geiger --- internal/operator-controller/applier/helm.go | 4 +- .../operator-controller/applier/helm_test.go | 3 +- .../operator-controller/authorization/rbac.go | 74 ++++++++++--------- .../authorization/rbac_test.go | 27 ++++--- 4 files changed, 58 insertions(+), 50 deletions(-) diff --git a/internal/operator-controller/applier/helm.go b/internal/operator-controller/applier/helm.go index c5cefce32..544e4fb4c 100644 --- a/internal/operator-controller/applier/helm.go +++ b/internal/operator-controller/applier/helm.go @@ -19,7 +19,6 @@ import ( rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" apimachyaml "k8s.io/apimachinery/pkg/util/yaml" - "k8s.io/apiserver/pkg/authentication/user" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" @@ -100,8 +99,7 @@ func (h *Helm) Apply(ctx context.Context, contentFS fs.FS, ext *ocv1.ClusterExte return nil, "", fmt.Errorf("failed to get release state using client-only dry-run: %w", err) } - ceServiceAccount := user.DefaultInfo{Name: fmt.Sprintf("system:serviceaccount:%s:%s", ext.Spec.Namespace, ext.Spec.ServiceAccount.Name)} - missingRules, err := h.PreAuthorizer.PreAuthorize(ctx, &ceServiceAccount, strings.NewReader(tmplRel.Manifest)) + missingRules, err := h.PreAuthorizer.PreAuthorize(ctx, ext, strings.NewReader(tmplRel.Manifest)) var preAuthErrors []error if len(missingRules) > 0 { diff --git a/internal/operator-controller/applier/helm_test.go b/internal/operator-controller/applier/helm_test.go index bb48ce15f..bbc20955f 100644 --- a/internal/operator-controller/applier/helm_test.go +++ b/internal/operator-controller/applier/helm_test.go @@ -17,7 +17,6 @@ import ( "helm.sh/helm/v3/pkg/storage/driver" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apiserver/pkg/authentication/user" featuregatetesting "k8s.io/component-base/featuregate/testing" "sigs.k8s.io/controller-runtime/pkg/client" @@ -39,7 +38,7 @@ type noOpPreAuthorizer struct{} func (p *noOpPreAuthorizer) PreAuthorize( ctx context.Context, - manifestManager user.Info, + ext *ocv1.ClusterExtension, manifestReader io.Reader, ) ([]authorization.ScopedPolicyRules, error) { // No-op: always return an empty map and no error diff --git a/internal/operator-controller/authorization/rbac.go b/internal/operator-controller/authorization/rbac.go index c281e55cd..1140819b5 100644 --- a/internal/operator-controller/authorization/rbac.go +++ b/internal/operator-controller/authorization/rbac.go @@ -6,11 +6,11 @@ import ( "fmt" "io" "maps" - "reflect" "regexp" "sort" "strings" + ocv1 "github.com/operator-framework/operator-controller/api/v1" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -34,7 +34,7 @@ import ( ) type PreAuthorizer interface { - PreAuthorize(ctx context.Context, manifestManager user.Info, manifestReader io.Reader) ([]ScopedPolicyRules, error) + PreAuthorize(ctx context.Context, ext *ocv1.ClusterExtension, manifestReader io.Reader) ([]ScopedPolicyRules, error) } type ScopedPolicyRules struct { @@ -69,13 +69,14 @@ func NewRBACPreAuthorizer(cl client.Client) PreAuthorizer { // completes successfully but identifies missing rules, then a nil error is returned along with // the list (or slice) of missing rules. Note that in some cases the error may encapsulate multiple // evaluation failures -func (a *rbacPreAuthorizer) PreAuthorize(ctx context.Context, manifestManager user.Info, manifestReader io.Reader) ([]ScopedPolicyRules, error) { +func (a *rbacPreAuthorizer) PreAuthorize(ctx context.Context, ext *ocv1.ClusterExtension, manifestReader io.Reader) ([]ScopedPolicyRules, error) { var allMissingPolicyRules = []ScopedPolicyRules{} dm, err := a.decodeManifest(manifestReader) if err != nil { return nil, err } - attributesRecords := dm.asAuthorizationAttributesRecordsForUser(manifestManager) + manifestManager := &user.DefaultInfo{Name: fmt.Sprintf("system:serviceaccount:%s:%s", ext.Spec.Namespace, ext.Spec.ServiceAccount.Name)} + attributesRecords := dm.asAuthorizationAttributesRecordsForUser(manifestManager, ext) var preAuthEvaluationErrors []error missingRules, err := a.authorizeAttributesRecords(ctx, attributesRecords) @@ -92,14 +93,8 @@ func (a *rbacPreAuthorizer) PreAuthorize(ctx context.Context, manifestManager us for _, obj := range dm.rbacObjects() { if err := ec.checkEscalation(ctx, manifestManager, obj); err != nil { - missingEscalationRules, namespace := parseEscalationErrorForMissingRules(err) - // Check if we already have these escalation PolicyRules, if so don't append - for i, rule := range missingEscalationRules { - previousRule := missingRules[namespace][len(missingRules[namespace])-len(missingEscalationRules)+i] - if !arePolicyRulesEqual(previousRule, rule) { - missingRules[namespace] = append(missingRules[namespace], missingEscalationRules...) - } - } + missingEscalationRules, err := parseEscalationErrorForMissingRules(err) + missingRules[obj.GetNamespace()] = append(missingRules[obj.GetNamespace()], missingEscalationRules...) preAuthEvaluationErrors = append(preAuthEvaluationErrors, err) } } @@ -112,7 +107,20 @@ func (a *rbacPreAuthorizer) PreAuthorize(ctx context.Context, manifestManager us if compactMissingRules, err := validation.CompactRules(nsMissingRules); err == nil { missingRules[ns] = compactMissingRules } - sortableRules := rbacv1helpers.SortableRuleSlice(missingRules[ns]) + + missingRulesWithDeduplicatedVerbs := []rbacv1.PolicyRule{} + for _, rule := range missingRules[ns] { + verbSet := sets.New[string](rule.Verbs...) + if verbSet.Has("*") { + rule.Verbs = []string{"*"} + } else { + rule.Verbs = sets.List(verbSet) + } + missingRulesWithDeduplicatedVerbs = append(missingRulesWithDeduplicatedVerbs, rule) + } + + sortableRules := rbacv1helpers.SortableRuleSlice(missingRulesWithDeduplicatedVerbs) + sort.Sort(sortableRules) allMissingPolicyRules = append(allMissingPolicyRules, ScopedPolicyRules{Namespace: ns, MissingRules: sortableRules}) } @@ -284,7 +292,7 @@ func (dm *decodedManifest) rbacObjects() []client.Object { return objects } -func (dm *decodedManifest) asAuthorizationAttributesRecordsForUser(manifestManager user.Info) []authorizer.AttributesRecord { +func (dm *decodedManifest) asAuthorizationAttributesRecordsForUser(manifestManager user.Info, ext *ocv1.ClusterExtension) []authorizer.AttributesRecord { var attributeRecords []authorizer.AttributesRecord // Here we are splitting collection verbs based on required scope @@ -338,6 +346,18 @@ func (dm *decodedManifest) asAuthorizationAttributesRecordsForUser(manifestManag Verb: v, }) } + + for _, verb := range []string{"update", "patch"} { + attributeRecords = append(attributeRecords, authorizer.AttributesRecord{ + User: manifestManager, + Name: ext.Name, + APIGroup: ext.GroupVersionKind().Group, + APIVersion: ext.GroupVersionKind().Version, + Resource: "clusterextensions/finalizers", + ResourceRequest: true, + Verb: verb, + }) + } } return attributeRecords } @@ -518,17 +538,14 @@ var fullAuthority = []rbacv1.PolicyRule{ {Verbs: []string{"*"}, NonResourceURLs: []string{"*"}}, } -func parseEscalationErrorForMissingRules(ecError error) ([]rbacv1.PolicyRule, string) { - // Regex to capture namespace and serviceaccount - userRegex := regexp.MustCompile(`system:serviceaccount:(?P[^:]+):(?P[^"]+)`) - // Regex to capture missing permissions +func parseEscalationErrorForMissingRules(ecError error) ([]rbacv1.PolicyRule, error) { + errRegex := regexp.MustCompile(`(?s)^(user \".*\" \(groups=.*\) is attempting to grant RBAC permissions not currently held):.*?$`) permRegex := regexp.MustCompile(`{APIGroups:\[("[^"]*")\], Resources:\[("[^"]*")\], Verbs:\[("[^"]*")\]}`) - userMatches := userRegex.FindStringSubmatch(ecError.Error()) - namespace := userMatches[1] + errMatches := errRegex.FindAllStringSubmatch(ecError.Error(), -1) // Extract permissions - var permissions []rbacv1.PolicyRule + permissions := []rbacv1.PolicyRule{} permMatches := permRegex.FindAllStringSubmatch(ecError.Error(), -1) for _, match := range permMatches { permissions = append(permissions, rbacv1.PolicyRule{ @@ -538,20 +555,7 @@ func parseEscalationErrorForMissingRules(ecError error) ([]rbacv1.PolicyRule, st }) } - return permissions, namespace -} - -func arePolicyRulesEqual(rule1 rbacv1.PolicyRule, rule2 rbacv1.PolicyRule) bool { - sort.Strings(rule1.Verbs) - sort.Strings(rule2.Verbs) - sort.Strings(rule1.APIGroups) - sort.Strings(rule2.APIGroups) - sort.Strings(rule1.Resources) - sort.Strings(rule2.Resources) - sort.Strings(rule1.ResourceNames) - sort.Strings(rule2.ResourceNames) - - return reflect.DeepEqual(rule1, rule2) + return permissions, errors.New(errMatches[0][1]) } func hasAggregationRule(clusterRole *rbacv1.ClusterRole) bool { diff --git a/internal/operator-controller/authorization/rbac_test.go b/internal/operator-controller/authorization/rbac_test.go index 90f780b3c..38cd0af10 100644 --- a/internal/operator-controller/authorization/rbac_test.go +++ b/internal/operator-controller/authorization/rbac_test.go @@ -2,17 +2,16 @@ package authorization import ( "context" - "fmt" "strings" "testing" + ocv1 "github.com/operator-framework/operator-controller/api/v1" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/api/meta/testrestmapper" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apiserver/pkg/authentication/user" featuregatetesting "k8s.io/component-base/featuregate/testing" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" @@ -127,9 +126,17 @@ subjects: namespace: a-test-namespace ` - saName = "test-serviceaccount" - ns = "test-namespace" - testServiceAccount = user.DefaultInfo{Name: fmt.Sprintf("system:serviceaccount:%s:%s", ns, saName)} + saName = "test-serviceaccount" + ns = "test-namespace" + exampleClusterExtension = ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{Name: "test-cluster-extension"}, + Spec: ocv1.ClusterExtensionSpec{ + Namespace: ns, + ServiceAccount: ocv1.ServiceAccountReference{ + Name: saName, + }, + }, + } objects = []client.Object{ &corev1.Namespace{ @@ -194,7 +201,7 @@ subjects: Rules: []rbacv1.PolicyRule{ { APIGroups: []string{"*"}, - Resources: []string{"serviceaccounts", "services"}, + Resources: []string{"serviceaccounts", "services", "clusterextensions/finalizers"}, Verbs: []string{"*"}, }, { @@ -221,7 +228,7 @@ func TestPreAuthorize_Success(t *testing.T) { featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.PreflightPermissions, true) fakeClient := setupFakeClient(privilegedClusterRole) preAuth := NewRBACPreAuthorizer(fakeClient) - missingRules, err := preAuth.PreAuthorize(context.TODO(), &testServiceAccount, strings.NewReader(testManifest)) + missingRules, err := preAuth.PreAuthorize(context.TODO(), &exampleClusterExtension, strings.NewReader(testManifest)) require.NoError(t, err) require.Equal(t, []ScopedPolicyRules{}, missingRules) }) @@ -232,7 +239,7 @@ func TestPreAuthorize_Failure(t *testing.T) { featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.PreflightPermissions, true) fakeClient := setupFakeClient(limitedClusterRole) preAuth := NewRBACPreAuthorizer(fakeClient) - missingRules, err := preAuth.PreAuthorize(context.TODO(), &testServiceAccount, strings.NewReader(testManifest)) + missingRules, err := preAuth.PreAuthorize(context.TODO(), &exampleClusterExtension, strings.NewReader(testManifest)) require.Error(t, err) require.NotEqual(t, []ScopedPolicyRules{}, missingRules) }) @@ -243,7 +250,7 @@ func TestPreAuthorizeMultiNamespace_Failure(t *testing.T) { featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.PreflightPermissions, true) fakeClient := setupFakeClient(limitedClusterRole) preAuth := NewRBACPreAuthorizer(fakeClient) - missingRules, err := preAuth.PreAuthorize(context.TODO(), &testServiceAccount, strings.NewReader(testManifestMultiNamespace)) + missingRules, err := preAuth.PreAuthorize(context.TODO(), &exampleClusterExtension, strings.NewReader(testManifestMultiNamespace)) require.Error(t, err) require.NotEqual(t, []ScopedPolicyRules{}, missingRules) }) @@ -254,7 +261,7 @@ func TestPreAuthorize_CheckEscalation(t *testing.T) { featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.PreflightPermissions, true) fakeClient := setupFakeClient(escalatingClusterRole) preAuth := NewRBACPreAuthorizer(fakeClient) - missingRules, err := preAuth.PreAuthorize(context.TODO(), &testServiceAccount, strings.NewReader(testManifest)) + missingRules, err := preAuth.PreAuthorize(context.TODO(), &exampleClusterExtension, strings.NewReader(testManifest)) require.NoError(t, err) require.Equal(t, []ScopedPolicyRules{}, missingRules) }) From 41b15ad952f53cde4a792d4d5272c54c27de1e2a Mon Sep 17 00:00:00 2001 From: Brett Tofel Date: Thu, 3 Apr 2025 14:17:32 -0400 Subject: [PATCH 33/67] Make tidy after rebase Signed-off-by: Brett Tofel --- go.sum | 131 ++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 93 insertions(+), 38 deletions(-) diff --git a/go.sum b/go.sum index 4594f9569..6f26e8db3 100644 --- a/go.sum +++ b/go.sum @@ -77,18 +77,18 @@ github.com/containerd/stargz-snapshotter/estargz v0.16.3 h1:7evrXtoh1mSbGj/pfRcc github.com/containerd/stargz-snapshotter/estargz v0.16.3/go.mod h1:uyr4BfYfOj3G9WBVE8cOlQmXAbPN9VEQpBBeJIuOipU= github.com/containerd/ttrpc v1.2.7 h1:qIrroQvuOL9HQ1X6KHe2ohc7p+HP/0VE6XPU7elJRqQ= github.com/containerd/ttrpc v1.2.7/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o= -github.com/containerd/typeurl/v2 v2.2.0 h1:6NBDbQzr7I5LHgp34xAXYF5DOTQDn05X58lsPEmzLso= -github.com/containerd/typeurl/v2 v2.2.0/go.mod h1:8XOOxnyatxSWuG8OfsZXVnAF4iZfedjS/8UHSPJnX4g= +github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40= +github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk= github.com/containers/common v0.61.0 h1:j/84PTqZIKKYy42OEJsZmjZ4g4Kq2ERuC3tqp2yWdh4= github.com/containers/common v0.61.0/go.mod h1:NGRISq2vTFPSbhNqj6MLwyes4tWSlCnqbJg7R77B8xc= -github.com/containers/image/v5 v5.33.1 h1:nTWKwxAlY0aJrilvvhssqssJVnley6VqxkLiLzTEYIs= -github.com/containers/image/v5 v5.33.1/go.mod h1:/FJiLlvVbeBxWNMPVPPIWJxHTAzwBoFvyN0a51zo1CE= +github.com/containers/image/v5 v5.34.3 h1:/cMgfyA4Y7ILH7nzWP/kqpkE5Df35Ek4bp5ZPvJOVmI= +github.com/containers/image/v5 v5.34.3/go.mod h1:MG++slvQSZVq5ejAcLdu4APGsKGMb0YHHnAo7X28fdE= github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 h1:Qzk5C6cYglewc+UyGf6lc8Mj2UaPTHy/iF2De0/77CA= github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01/go.mod h1:9rfv8iPl1ZP7aqh9YA68wnZv2NUDbXdcdPHVz0pFbPY= -github.com/containers/ocicrypt v1.2.0 h1:X14EgRK3xNFvJEfI5O4Qn4T3E25ANudSOZz/sirVuPM= -github.com/containers/ocicrypt v1.2.0/go.mod h1:ZNviigQajtdlxIZGibvblVuIFBKIuUI2M0QM12SD31U= -github.com/containers/storage v1.56.1 h1:gDZj/S6Zxus4Xx42X6iNB3ODXuh0qoOdH/BABfrvcKo= -github.com/containers/storage v1.56.1/go.mod h1:c6WKowcAlED/DkWGNuL9bvGYqIWCVy7isRMdCSKWNjk= +github.com/containers/ocicrypt v1.2.1 h1:0qIOTT9DoYwcKmxSt8QJt+VzMY18onl9jUXsxpVhSmM= +github.com/containers/ocicrypt v1.2.1/go.mod h1:aD0AAqfMp0MtwqWgHM1bUwe1anx0VazI108CRrSKINQ= +github.com/containers/storage v1.57.2 h1:2roCtTyE9pzIaBDHibK72DTnYkPmwWaq5uXxZdaWK4U= +github.com/containers/storage v1.57.2/go.mod h1:i/Hb4lu7YgFr9G0K6BMjqW0BLJO1sFsnWQwj2UoWCUM= github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= @@ -110,12 +110,12 @@ github.com/distribution/distribution/v3 v3.0.0-rc.1 h1:6M4ewmPBUhF7wtQ8URLOQ1W/P github.com/distribution/distribution/v3 v3.0.0-rc.1/go.mod h1:tFjaPDeHCrLg28e4feBIy27cP+qmrc/mvkl6MFIfVi4= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/cli v27.5.0+incompatible h1:aMphQkcGtpHixwwhAXJT1rrK/detk2JIvDaFkLctbGM= -github.com/docker/cli v27.5.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v27.5.1+incompatible h1:JB9cieUT9YNiMITtIsguaN55PLOHhBSz3LKVc6cqWaY= +github.com/docker/cli v27.5.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v27.5.0+incompatible h1:um++2NcQtGRTz5eEgO6aJimo6/JxrTXC941hd05JO6U= -github.com/docker/docker v27.5.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v27.5.1+incompatible h1:4PYU5dnBYqRQi0294d1FBECqT9ECWeQAIfE8q4YnPY8= +github.com/docker/docker v27.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo= github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= @@ -240,6 +240,7 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-containerregistry v0.20.3 h1:oNx7IdTI936V8CQRveCjaxOiegWwvM7kqkbXTpyiovI= @@ -356,8 +357,8 @@ github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= -github.com/moby/sys/capability v0.3.0 h1:kEP+y6te0gEXIaeQhIi0s7vKs/w0RPoH1qPa6jROcVg= -github.com/moby/sys/capability v0.3.0/go.mod h1:4g9IK291rVkms3LKCDOoYlnV8xKwoDTpIrNEE35Wq0I= +github.com/moby/sys/capability v0.4.0 h1:4D4mI6KlNtWMCM1Z/K0i7RV1FkX+DBDHKVJpCndZoHk= +github.com/moby/sys/capability v0.4.0/go.mod h1:4g9IK291rVkms3LKCDOoYlnV8xKwoDTpIrNEE35Wq0I= github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg= github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= @@ -420,8 +421,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg= -github.com/proglottis/gpgme v0.1.3 h1:Crxx0oz4LKB3QXc5Ea0J19K/3ICfy3ftr5exgUK1AU0= -github.com/proglottis/gpgme v0.1.3/go.mod h1:fPbW/EZ0LvwQtH8Hy7eixhp1eF3G39dtx7GUN+0Gmy0= +github.com/proglottis/gpgme v0.1.4 h1:3nE7YNA70o2aLjcg63tXMOhPD7bplfE5CBdV+hLAm2M= +github.com/proglottis/gpgme v0.1.4/go.mod h1:5LoXMgpE4bttgwwdv9bLs/vwqv3qV7F4glEEZ7mRKrM= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= @@ -445,8 +446,8 @@ github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 h1:EaDatTxkdHG+U3Bk4EUr+DZ7fO github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5/go.mod h1:fyalQWdtzDBECAQFBJuQe5bzQ02jGd5Qcbgb97Flm7U= github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 h1:EfpWLLCyXw8PSM2/XNJLjI3Pb27yVE+gIAfeqp8LUCc= github.com/redis/go-redis/extra/redisotel/v9 v9.0.5/go.mod h1:WZjPDy7VNzn77AAfnAfVjZNvfJTYfPetfZk5yoSTLaQ= -github.com/redis/go-redis/v9 v9.5.1 h1:H1X4D3yHPaYrkL5X06Wh6xNVM/pX0Ft4RV0vMGvLBh8= -github.com/redis/go-redis/v9 v9.5.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= +github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E= +github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= @@ -456,21 +457,23 @@ github.com/rubenv/sql-migrate v1.7.1 h1:f/o0WgfO/GqNuVg+6801K/KW3WdDSupzSjDYODmi github.com/rubenv/sql-migrate v1.7.1/go.mod h1:Ob2Psprc0/3ggbM6wCzyYVFFuc6FyZrb2AS+ezLDFb4= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/secure-systems-lab/go-securesystemslib v0.8.0 h1:mr5An6X45Kb2nddcFlbmfHkLguCE9laoZCUzEEpIZXA= -github.com/secure-systems-lab/go-securesystemslib v0.8.0/go.mod h1:UH2VZVuJfCYR8WgMlCU1uFsOUU+KeyrTWcSS73NBOzU= +github.com/secure-systems-lab/go-securesystemslib v0.9.0 h1:rf1HIbL64nUpEIZnjLZ3mcNEL9NBPB0iuVjyxvq3LZc= +github.com/secure-systems-lab/go-securesystemslib v0.9.0/go.mod h1:DVHKMcZ+V4/woA/peqr+L0joiRXbPpQ042GgJckkFgw= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/sigstore/fulcio v1.6.4 h1:d86obfxUAG3Y6CYwOx1pdwCZwKmROB6w6927pKOVIRY= github.com/sigstore/fulcio v1.6.4/go.mod h1:Y6bn3i3KGhXpaHsAtYP3Z4Np0+VzCo1fLv8Ci6mbPDs= -github.com/sigstore/rekor v1.3.6 h1:QvpMMJVWAp69a3CHzdrLelqEqpTM3ByQRt5B5Kspbi8= -github.com/sigstore/rekor v1.3.6/go.mod h1:JDTSNNMdQ/PxdsS49DJkJ+pRJCO/83nbR5p3aZQteXc= -github.com/sigstore/sigstore v1.8.9 h1:NiUZIVWywgYuVTxXmRoTT4O4QAGiTEKup4N1wdxFadk= -github.com/sigstore/sigstore v1.8.9/go.mod h1:d9ZAbNDs8JJfxJrYmulaTazU3Pwr8uLL9+mii4BNR3w= +github.com/sigstore/rekor v1.3.8 h1:B8kJI8mpSIXova4Jxa6vXdJyysRxFGsEsLKBDl0rRjA= +github.com/sigstore/rekor v1.3.8/go.mod h1:/dHFYKSuxEygfDRnEwyJ+ZD6qoVYNXQdi1mJrKvKWsI= +github.com/sigstore/sigstore v1.8.12 h1:S8xMVZbE2z9ZBuQUEG737pxdLjnbOIcFi5v9UFfkJFc= +github.com/sigstore/sigstore v1.8.12/go.mod h1:+PYQAa8rfw0QdPpBcT+Gl3egKD9c+TUgAlF12H3Nmjo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/smallstep/pkcs7 v0.1.1 h1:x+rPdt2W088V9Vkjho4KtoggyktZJlMduZAtRHm68LU= +github.com/smallstep/pkcs7 v0.1.1/go.mod h1:dL6j5AIz9GHjVEBTXtW+QliALcgM19RtXaTeyxI+AfA= github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= @@ -500,10 +503,10 @@ github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 h1:e/5i7d4oYZ+C github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399/go.mod h1:LdwHTNJT99C5fTAzDz0ud328OgXz+gierycbcIx2fRs= github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/vbatts/tar-split v0.11.6 h1:4SjTW5+PU11n6fZenf2IPoV8/tz3AaYHMWjf23envGs= -github.com/vbatts/tar-split v0.11.6/go.mod h1:dqKNtesIOr2j2Qv3W/cHjnvk9I8+G7oAkFDFN6TCBEI= -github.com/vbauerster/mpb/v8 v8.8.3 h1:dTOByGoqwaTJYPubhVz3lO5O6MK553XVgUo33LdnNsQ= -github.com/vbauerster/mpb/v8 v8.8.3/go.mod h1:JfCCrtcMsJwP6ZwMn9e5LMnNyp3TVNpUWWkN+nd4EWk= +github.com/vbatts/tar-split v0.11.7 h1:ixZ93pO/GmvaZw4Vq9OwmfZK/kc2zKdPfu0B+gYqs3U= +github.com/vbatts/tar-split v0.11.7/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA= +github.com/vbauerster/mpb/v8 v8.9.1 h1:LH5R3lXPfE2e3lIGxN7WNWv3Hl5nWO6LRi2B0L0ERHw= +github.com/vbauerster/mpb/v8 v8.9.1/go.mod h1:4XMvznPh8nfe2NpnDo1QTPvW9MVkUhbG90mPWvmOzcQ= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= @@ -517,6 +520,7 @@ github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= go.etcd.io/etcd/api/v3 v3.5.16 h1:WvmyJVbjWqK4R1E+B12RRHz3bRGy9XVfh++MgbN+6n0= @@ -527,8 +531,6 @@ go.etcd.io/etcd/client/v3 v3.5.16 h1:sSmVYOAHeC9doqi0gv7v86oY/BTld0SEFGaxsU9eRhE go.etcd.io/etcd/client/v3 v3.5.16/go.mod h1:X+rExSGkyqxvu276cr2OwPLBaeqFu1cIl4vmRjAD/50= go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80= go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= -go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 h1:CCriYyAfq1Br1aIYettdHZTy8mBTIPo7We18TuO/bak= -go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= @@ -537,8 +539,8 @@ go.opentelemetry.io/contrib/bridges/prometheus v0.54.0 h1:WWL67oxtknNVMb70lJXxXr go.opentelemetry.io/contrib/bridges/prometheus v0.54.0/go.mod h1:LqNcnXmyULp8ertk4hUTVtSUvKXj4h1Mx7gUCSSr/q0= go.opentelemetry.io/contrib/exporters/autoexport v0.54.0 h1:dTmcmVm4J54IRPGm5oVjLci1uYat4UDea84E2tyBaAk= go.opentelemetry.io/contrib/exporters/autoexport v0.54.0/go.mod h1:zPp5Fwpq2Hc7xMtVttg6GhZMcfTESjVbY9ONw2o/Dc4= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 h1:9G6E0TXzGFVfTnawRzrPl83iHOAV7L8NJiR8RSGYV1g= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0/go.mod h1:azvtTADFQJA8mX80jIH/akaE7h+dbm/sVuaHqN13w74= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 h1:r6I7RJCN86bpD/FQwedZ0vSixDpwuWREjW9oRMsmqDc= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q= go.opentelemetry.io/otel v1.33.0 h1:/FerN9bax5LoK51X/sI0SVYrjSE0/yUL7DpxW4K3FWw= @@ -571,8 +573,8 @@ go.opentelemetry.io/otel/sdk v1.33.0 h1:iax7M131HuAm9QkZotNHEfstof92xM+N8sr3uHXc go.opentelemetry.io/otel/sdk v1.33.0/go.mod h1:A1Q5oi7/9XaMlIWzPSxLRWOI8nG3FnzHJNbiENQuihM= go.opentelemetry.io/otel/sdk/log v0.5.0 h1:A+9lSjlZGxkQOr7QSBJcuyyYBw79CufQ69saiJLey7o= go.opentelemetry.io/otel/sdk/log v0.5.0/go.mod h1:zjxIW7sw1IHolZL2KlSAtrUi8JHttoeiQy43Yl3WuVQ= -go.opentelemetry.io/otel/sdk/metric v1.29.0 h1:K2CfmJohnRgvZ9UAj2/FhIf/okdWcNdBwe1m8xFXiSY= -go.opentelemetry.io/otel/sdk/metric v1.29.0/go.mod h1:6zZLdCl2fkauYoZIOn/soQIDSWFmNSRcICarHfuhNJQ= +go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc= +go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8= go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s= go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= @@ -587,6 +589,11 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf 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.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -597,6 +604,11 @@ golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvx golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -610,6 +622,13 @@ 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-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -621,6 +640,12 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -630,16 +655,42 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= 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.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= @@ -652,6 +703,10 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -665,8 +720,8 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1 h1:BulPr26Jqjnd4eYDVe+YvyR7Yc2vJGkO5/0UxD0/jZU= -google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:hL97c3SYopEHblzpxRL4lSs523++l8DYxGM1FQiYmb4= +google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk= +google.golang.org/genproto v0.0.0-20241118233622-e639e219e697/go.mod h1:JJrvXBWRZaFMxBufik1a4RpFw4HhgVtBBWQeQgUj2cc= google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950= google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg= google.golang.org/genproto/googleapis/rpc v0.0.0-20250227231956-55c901821b1e h1:YA5lmSs3zc/5w+xsRcHqpETkaYyK63ivEPzNTcUUlSA= @@ -676,8 +731,8 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.68.1 h1:oI5oTa11+ng8r8XMMN7jAOmWfPZWbYpCFaMUTACxkM0= -google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw= +google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A= +google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= From 5848d1ba000e03971012f9bb8979d2b4d08900d3 Mon Sep 17 00:00:00 2001 From: Brett Tofel Date: Thu, 3 Apr 2025 14:17:51 -0400 Subject: [PATCH 34/67] GCI the files so lint passes Signed-off-by: Brett Tofel --- internal/operator-controller/authorization/rbac.go | 3 ++- internal/operator-controller/authorization/rbac_test.go | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/operator-controller/authorization/rbac.go b/internal/operator-controller/authorization/rbac.go index 1140819b5..443d231d5 100644 --- a/internal/operator-controller/authorization/rbac.go +++ b/internal/operator-controller/authorization/rbac.go @@ -10,7 +10,6 @@ import ( "sort" "strings" - ocv1 "github.com/operator-framework/operator-controller/api/v1" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -31,6 +30,8 @@ import ( "k8s.io/kubernetes/pkg/registry/rbac/validation" rbac "k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac" "sigs.k8s.io/controller-runtime/pkg/client" + + ocv1 "github.com/operator-framework/operator-controller/api/v1" ) type PreAuthorizer interface { diff --git a/internal/operator-controller/authorization/rbac_test.go b/internal/operator-controller/authorization/rbac_test.go index 38cd0af10..e6b2062dc 100644 --- a/internal/operator-controller/authorization/rbac_test.go +++ b/internal/operator-controller/authorization/rbac_test.go @@ -5,7 +5,6 @@ import ( "strings" "testing" - ocv1 "github.com/operator-framework/operator-controller/api/v1" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" @@ -16,6 +15,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" + ocv1 "github.com/operator-framework/operator-controller/api/v1" "github.com/operator-framework/operator-controller/internal/operator-controller/features" ) From 71889970ac3ee69f2cb97ba7034adf548bbe89d4 Mon Sep 17 00:00:00 2001 From: Tayler Geiger Date: Thu, 3 Apr 2025 15:27:33 -0500 Subject: [PATCH 35/67] Use slices.SortFunc instead of sort.Slice Signed-off-by: Tayler Geiger --- internal/operator-controller/authorization/rbac.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/operator-controller/authorization/rbac.go b/internal/operator-controller/authorization/rbac.go index 443d231d5..dd194ac0f 100644 --- a/internal/operator-controller/authorization/rbac.go +++ b/internal/operator-controller/authorization/rbac.go @@ -7,6 +7,7 @@ import ( "io" "maps" "regexp" + "slices" "sort" "strings" @@ -127,8 +128,8 @@ func (a *rbacPreAuthorizer) PreAuthorize(ctx context.Context, ext *ocv1.ClusterE } // sort allMissingPolicyRules alphabetically by namespace - sort.Slice(allMissingPolicyRules, func(i, j int) bool { - return allMissingPolicyRules[i].Namespace < allMissingPolicyRules[j].Namespace + slices.SortFunc(allMissingPolicyRules, func(a, b ScopedPolicyRules) int { + return strings.Compare(a.Namespace, b.Namespace) }) if len(preAuthEvaluationErrors) > 0 { From d7bf186efbdc4c628fd5e7f9f8c4f0fed5f4d393 Mon Sep 17 00:00:00 2001 From: Brett Tofel Date: Fri, 4 Apr 2025 09:53:06 -0400 Subject: [PATCH 36/67] Lift running pre-auth checks out of Helm Apply Signed-off-by: Brett Tofel --- internal/operator-controller/applier/helm.go | 57 ++++++++++++-------- 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/internal/operator-controller/applier/helm.go b/internal/operator-controller/applier/helm.go index 544e4fb4c..764f70824 100644 --- a/internal/operator-controller/applier/helm.go +++ b/internal/operator-controller/applier/helm.go @@ -82,6 +82,38 @@ func shouldSkipPreflight(ctx context.Context, preflight Preflight, ext *ocv1.Clu return false } +// runPreAuthorizationChecks performs pre-authorization checks for a Helm release +// it renders a client-only release, checks permissions using the PreAuthorizer +// and returns an error if authorization fails or required permissions are missing +func (h *Helm) runPreAuthorizationChecks(ctx context.Context, ext *ocv1.ClusterExtension, chart *chart.Chart, values chartutil.Values, post postrender.PostRenderer) error { + tmplRel, err := h.renderClientOnlyRelease(ctx, ext, chart, values, post) + if err != nil { + return fmt.Errorf("failed to get release state using client-only dry-run: %w", err) + } + + missingRules, authErr := h.PreAuthorizer.PreAuthorize(ctx, ext, strings.NewReader(tmplRel.Manifest)) + + var preAuthErrors []error + + if len(missingRules) > 0 { + var missingRuleDescriptions []string + for _, policyRules := range missingRules { + for _, rule := range policyRules.MissingRules { + missingRuleDescriptions = append(missingRuleDescriptions, ruleDescription(policyRules.Namespace, rule)) + } + } + slices.Sort(missingRuleDescriptions) + preAuthErrors = append(preAuthErrors, fmt.Errorf("service account lacks permission to manage cluster extension:\n %s", strings.Join(missingRuleDescriptions, "\n "))) + } + if authErr != nil { + preAuthErrors = append(preAuthErrors, fmt.Errorf("authorization evaluation error: %w", authErr)) + } + if len(preAuthErrors) > 0 { + return fmt.Errorf("pre-authorization failed: %v", preAuthErrors) + } + return nil +} + func (h *Helm) Apply(ctx context.Context, contentFS fs.FS, ext *ocv1.ClusterExtension, objectLabels map[string]string, storageLabels map[string]string) ([]client.Object, string, error) { chrt, err := h.buildHelmChart(contentFS, ext) if err != nil { @@ -94,29 +126,10 @@ func (h *Helm) Apply(ctx context.Context, contentFS fs.FS, ext *ocv1.ClusterExte } if h.PreAuthorizer != nil { - tmplRel, err := h.renderClientOnlyRelease(ctx, ext, chrt, values, post) + err := h.runPreAuthorizationChecks(ctx, ext, chrt, values, post) if err != nil { - return nil, "", fmt.Errorf("failed to get release state using client-only dry-run: %w", err) - } - - missingRules, err := h.PreAuthorizer.PreAuthorize(ctx, ext, strings.NewReader(tmplRel.Manifest)) - - var preAuthErrors []error - if len(missingRules) > 0 { - var missingRuleDescriptions []string - for _, policyRules := range missingRules { - for _, rule := range policyRules.MissingRules { - missingRuleDescriptions = append(missingRuleDescriptions, ruleDescription(policyRules.Namespace, rule)) - } - } - slices.Sort(missingRuleDescriptions) - preAuthErrors = append(preAuthErrors, fmt.Errorf("service account lacks permission to manage cluster extension:\n %s", strings.Join(missingRuleDescriptions, "\n "))) - } - if err != nil { - preAuthErrors = append(preAuthErrors, fmt.Errorf("authorization evaluation error: %w", err)) - } - if len(preAuthErrors) > 0 { - return nil, "", fmt.Errorf("pre-authorization failed: %v", preAuthErrors) + // Return the pre-authorization error directly + return nil, "", err } } From 48734453eda4be2ae6a6954c1d36b816b8a62c46 Mon Sep 17 00:00:00 2001 From: Brett Tofel Date: Fri, 4 Apr 2025 15:03:27 -0400 Subject: [PATCH 37/67] Add centralized logging for feature gate status Signed-off-by: Brett Tofel --- cmd/operator-controller/main.go | 6 +++--- .../operator-controller/features/features.go | 20 +++++++++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/cmd/operator-controller/main.go b/cmd/operator-controller/main.go index c3d8c4fde..12322a610 100644 --- a/cmd/operator-controller/main.go +++ b/cmd/operator-controller/main.go @@ -187,6 +187,9 @@ func run() error { setupLog.Info("starting up the controller", "version info", version.String()) + // log feature gate status after parsing flags and setting up logger + features.LogFeatureGateStates(setupLog, features.OperatorControllerFeatureGate) + authFilePath := filepath.Join(os.TempDir(), fmt.Sprintf("%s-%s.json", authFilePrefix, apimachineryrand.String(8))) var globalPullSecretKey *k8stypes.NamespacedName if cfg.globalPullSecret != "" { @@ -419,10 +422,7 @@ func run() error { // determine if PreAuthorizer should be enabled based on feature gate var preAuth authorization.PreAuthorizer if features.OperatorControllerFeatureGate.Enabled(features.PreflightPermissions) { - setupLog.Info("preflight permissions check enabled via feature gate") preAuth = authorization.NewRBACPreAuthorizer(mgr.GetClient()) - } else { - setupLog.Info("preflight permissions check disabled via feature gate") } // now initialize the helmApplier, assigning the potentially nil preAuth diff --git a/internal/operator-controller/features/features.go b/internal/operator-controller/features/features.go index 885f3b4db..311f73e7b 100644 --- a/internal/operator-controller/features/features.go +++ b/internal/operator-controller/features/features.go @@ -1,6 +1,9 @@ package features import ( + "sort" + + "github.com/go-logr/logr" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/component-base/featuregate" ) @@ -36,3 +39,20 @@ var OperatorControllerFeatureGate featuregate.MutableFeatureGate = featuregate.N func init() { utilruntime.Must(OperatorControllerFeatureGate.Add(operatorControllerFeatureGates)) } + +// LogFeatureGateStates logs the state of all known feature gates. +func LogFeatureGateStates(log logr.Logger, fg featuregate.FeatureGate) { + // Sort the keys for consistent logging order + featureKeys := make([]featuregate.Feature, 0, len(operatorControllerFeatureGates)) + for k := range operatorControllerFeatureGates { + featureKeys = append(featureKeys, k) + } + sort.Slice(featureKeys, func(i, j int) bool { + return string(featureKeys[i]) < string(featureKeys[j]) // Sort by string representation + }) + + log.Info("Feature Gates Status:") + for _, feature := range featureKeys { + log.Info(" ", "feature", string(feature), "enabled", fg.Enabled(feature)) + } +} From aeee3ef70303e6dcab9a1d22962a3e69d265fb69 Mon Sep 17 00:00:00 2001 From: Brett Tofel Date: Mon, 7 Apr 2025 13:29:54 -0400 Subject: [PATCH 38/67] Err msg reads better Co-authored-by: Per Goncalves da Silva --- internal/operator-controller/applier/helm.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/operator-controller/applier/helm.go b/internal/operator-controller/applier/helm.go index 764f70824..dbddc62d9 100644 --- a/internal/operator-controller/applier/helm.go +++ b/internal/operator-controller/applier/helm.go @@ -103,7 +103,7 @@ func (h *Helm) runPreAuthorizationChecks(ctx context.Context, ext *ocv1.ClusterE } } slices.Sort(missingRuleDescriptions) - preAuthErrors = append(preAuthErrors, fmt.Errorf("service account lacks permission to manage cluster extension:\n %s", strings.Join(missingRuleDescriptions, "\n "))) + preAuthErrors = append(preAuthErrors, fmt.Errorf("service account requires the following permissions to manage cluster extension:\n %s", strings.Join(missingRuleDescriptions, "\n "))) } if authErr != nil { preAuthErrors = append(preAuthErrors, fmt.Errorf("authorization evaluation error: %w", authErr)) From d45b66603ea9e3c4f40ae5163e0dfcd3eeb010ef Mon Sep 17 00:00:00 2001 From: Brett Tofel Date: Mon, 7 Apr 2025 13:40:52 -0400 Subject: [PATCH 39/67] Run make tidy after rebase Signed-off-by: Brett Tofel --- go.sum | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/go.sum b/go.sum index 6f26e8db3..3558f7467 100644 --- a/go.sum +++ b/go.sum @@ -148,8 +148,8 @@ github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7Dlme github.com/foxcpp/go-mockdns v1.1.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= -github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= From c0b1ddad95d3771e56ceb1e77a2358b4988ae0e5 Mon Sep 17 00:00:00 2001 From: Brett Tofel Date: Mon, 7 Apr 2025 16:32:57 -0400 Subject: [PATCH 40/67] No more magic numbers Signed-off-by: Brett Tofel --- hack/tools/k8smaintainer/main.go | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/hack/tools/k8smaintainer/main.go b/hack/tools/k8smaintainer/main.go index a7461b4d1..f74973837 100644 --- a/hack/tools/k8smaintainer/main.go +++ b/hack/tools/k8smaintainer/main.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/json" "fmt" + "io/fs" // Imported for fs.FileMode "log" "os" "os/exec" @@ -16,7 +17,13 @@ import ( "golang.org/x/mod/semver" ) -const k8sRepo = "k8s.io/kubernetes" +const ( + k8sRepo = "k8s.io/kubernetes" + expectedMajorMinorParts = 2 + goModFilePerms = fs.FileMode(0600) + minGoListVersionFields = 2 + minValidPatchNumber = 1 +) //nolint:gochecknoglobals var goExe = "go" @@ -69,7 +76,7 @@ func main() { } majorMinor := semver.MajorMinor(k8sVer) // e.g., "v1.32" patch := strings.TrimPrefix(k8sVer, majorMinor+".") // e.g., "3" - if len(strings.Split(majorMinor, ".")) != 2 { + if len(strings.Split(majorMinor, ".")) != expectedMajorMinorParts { log.Fatalf("Unexpected format for MajorMinor: %s", majorMinor) } targetStagingVer := "v0" + strings.TrimPrefix(majorMinor, "v1") + "." + patch // e.g., "v0.32.3" @@ -200,7 +207,7 @@ func main() { if err != nil { log.Fatalf("Error formatting go.mod: %v", err) } - if err := os.WriteFile("go.mod", newModBytes, 0600); err != nil { + if err := os.WriteFile("go.mod", newModBytes, goModFilePerms); err != nil { log.Fatalf("Error writing go.mod: %v", err) } @@ -265,7 +272,7 @@ func getModuleVersions(modulePath string) ([]string, error) { return nil, fmt.Errorf("error listing versions for %s: %w", modulePath, err) } fields := strings.Fields(string(output)) - if len(fields) < 2 { + if len(fields) < minGoListVersionFields { return []string{}, nil // No versions listed } return fields[1:], nil // First field is the module path @@ -294,8 +301,8 @@ func getLatestExistingVersion(modulePath, targetVer string) (string, error) { majorMinor := semver.MajorMinor(targetVer) // e.g., v0.32 patchStr := strings.TrimPrefix(targetVer, majorMinor+".") // e.g., 3 var patch int - if _, err := fmt.Sscan(patchStr, &patch); err != nil || patch < 1 { - log.Printf("Could not parse patch version or patch <= 0 for %s, cannot determine predecessor.", targetVer) + if _, err := fmt.Sscan(patchStr, &patch); err != nil || patch < minValidPatchNumber { + log.Printf("Could not parse patch version or patch < %d for %s, cannot determine predecessor.", minValidPatchNumber, targetVer) return "", nil // Cannot determine predecessor } prevPatchVer := fmt.Sprintf("%s.%d", majorMinor, patch-1) // e.g., v0.32.2 From ab8cfbced3743a7ea0bbe5bcb2473e8076c65732 Mon Sep 17 00:00:00 2001 From: Brett Tofel Date: Tue, 8 Apr 2025 10:55:30 -0400 Subject: [PATCH 41/67] Sort components of missing rules lists Signed-off-by: Brett Tofel --- internal/operator-controller/applier/helm.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/operator-controller/applier/helm.go b/internal/operator-controller/applier/helm.go index dbddc62d9..9b47ba37e 100644 --- a/internal/operator-controller/applier/helm.go +++ b/internal/operator-controller/applier/helm.go @@ -305,19 +305,19 @@ func ruleDescription(ns string, rule rbacv1.PolicyRule) string { sb.WriteString(fmt.Sprintf("Namespace:%q", ns)) if len(rule.APIGroups) > 0 { - sb.WriteString(fmt.Sprintf(" APIGroups:[%s]", strings.Join(rule.APIGroups, ","))) + sb.WriteString(fmt.Sprintf(" APIGroups:[%s]", strings.Join(slices.Sorted(slices.Values(rule.APIGroups)), ","))) } if len(rule.Resources) > 0 { - sb.WriteString(fmt.Sprintf(" Resources:[%s]", strings.Join(rule.Resources, ","))) + sb.WriteString(fmt.Sprintf(" Resources:[%s]", strings.Join(slices.Sorted(slices.Values(rule.Resources)), ","))) } if len(rule.ResourceNames) > 0 { - sb.WriteString(fmt.Sprintf(" ResourceNames:[%s]", strings.Join(rule.ResourceNames, ","))) + sb.WriteString(fmt.Sprintf(" ResourceNames:[%s]", strings.Join(slices.Sorted(slices.Values(rule.ResourceNames)), ","))) } if len(rule.Verbs) > 0 { - sb.WriteString(fmt.Sprintf(" Verbs:[%s]", strings.Join(rule.Verbs, ","))) + sb.WriteString(fmt.Sprintf(" Verbs:[%s]", strings.Join(slices.Sorted(slices.Values(rule.Verbs)), ","))) } if len(rule.NonResourceURLs) > 0 { - sb.WriteString(fmt.Sprintf(" NonResourceURLs:[%s]", strings.Join(rule.NonResourceURLs, ","))) + sb.WriteString(fmt.Sprintf(" NonResourceURLs:[%s]", strings.Join(slices.Sorted(slices.Values(rule.NonResourceURLs)), ","))) } return sb.String() } From dc6691ddd869b6f8ffb308cf918e7ace5db764cb Mon Sep 17 00:00:00 2001 From: Brett Tofel Date: Tue, 8 Apr 2025 11:08:36 -0400 Subject: [PATCH 42/67] Streamline var usage --- internal/operator-controller/authorization/rbac.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/operator-controller/authorization/rbac.go b/internal/operator-controller/authorization/rbac.go index dd194ac0f..72a3ae3ff 100644 --- a/internal/operator-controller/authorization/rbac.go +++ b/internal/operator-controller/authorization/rbac.go @@ -72,7 +72,7 @@ func NewRBACPreAuthorizer(cl client.Client) PreAuthorizer { // the list (or slice) of missing rules. Note that in some cases the error may encapsulate multiple // evaluation failures func (a *rbacPreAuthorizer) PreAuthorize(ctx context.Context, ext *ocv1.ClusterExtension, manifestReader io.Reader) ([]ScopedPolicyRules, error) { - var allMissingPolicyRules = []ScopedPolicyRules{} + var allMissingPolicyRules []ScopedPolicyRules dm, err := a.decodeManifest(manifestReader) if err != nil { return nil, err From 0cf3e7564696e449f0d63e74f9e918b38ca414ad Mon Sep 17 00:00:00 2001 From: Brett Tofel Date: Tue, 8 Apr 2025 11:18:34 -0400 Subject: [PATCH 43/67] Lift to escalationCheckerFor method Signed-off-by: Brett Tofel --- .../operator-controller/authorization/rbac.go | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/internal/operator-controller/authorization/rbac.go b/internal/operator-controller/authorization/rbac.go index 72a3ae3ff..f47140868 100644 --- a/internal/operator-controller/authorization/rbac.go +++ b/internal/operator-controller/authorization/rbac.go @@ -86,12 +86,7 @@ func (a *rbacPreAuthorizer) PreAuthorize(ctx context.Context, ext *ocv1.ClusterE preAuthEvaluationErrors = append(preAuthEvaluationErrors, err) } - ec := escalationChecker{ - authorizer: a.authorizer, - ruleResolver: a.ruleResolver, - extraClusterRoles: dm.clusterRoles, - extraRoles: dm.roles, - } + ec := a.escalationCheckerFor(dm) for _, obj := range dm.rbacObjects() { if err := ec.checkEscalation(ctx, manifestManager, obj); err != nil { @@ -138,6 +133,16 @@ func (a *rbacPreAuthorizer) PreAuthorize(ctx context.Context, ext *ocv1.ClusterE return allMissingPolicyRules, nil } +func (a *rbacPreAuthorizer) escalationCheckerFor(dm *decodedManifest) escalationChecker { + ec := escalationChecker{ + authorizer: a.authorizer, + ruleResolver: a.ruleResolver, + extraClusterRoles: dm.clusterRoles, + extraRoles: dm.roles, + } + return ec +} + func (a *rbacPreAuthorizer) decodeManifest(manifestReader io.Reader) (*decodedManifest, error) { dm := &decodedManifest{ gvrs: map[schema.GroupVersionResource][]types.NamespacedName{}, From db7e7409855a55017c91cd3d5c2a9f6b527387ac Mon Sep 17 00:00:00 2001 From: Brett Tofel Date: Tue, 8 Apr 2025 11:27:25 -0400 Subject: [PATCH 44/67] Fix lint prealloc err on allMissingPolicyRules Signed-off-by: Brett Tofel --- internal/operator-controller/authorization/rbac.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/operator-controller/authorization/rbac.go b/internal/operator-controller/authorization/rbac.go index f47140868..536e10262 100644 --- a/internal/operator-controller/authorization/rbac.go +++ b/internal/operator-controller/authorization/rbac.go @@ -72,7 +72,6 @@ func NewRBACPreAuthorizer(cl client.Client) PreAuthorizer { // the list (or slice) of missing rules. Note that in some cases the error may encapsulate multiple // evaluation failures func (a *rbacPreAuthorizer) PreAuthorize(ctx context.Context, ext *ocv1.ClusterExtension, manifestReader io.Reader) ([]ScopedPolicyRules, error) { - var allMissingPolicyRules []ScopedPolicyRules dm, err := a.decodeManifest(manifestReader) if err != nil { return nil, err @@ -95,6 +94,7 @@ func (a *rbacPreAuthorizer) PreAuthorize(ctx context.Context, ext *ocv1.ClusterE preAuthEvaluationErrors = append(preAuthEvaluationErrors, err) } } + allMissingPolicyRules := make([]ScopedPolicyRules, 0, len(missingRules)) for ns, nsMissingRules := range missingRules { // NOTE: Although CompactRules is defined to return an error, its current implementation From bd57a658ed71d5423f5e65ea77af329ceaa6a224 Mon Sep 17 00:00:00 2001 From: Brett Tofel Date: Tue, 8 Apr 2025 13:10:55 -0400 Subject: [PATCH 45/67] Prealloc missingRulesWithDeduplicatedVerbs --- internal/operator-controller/authorization/rbac.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/operator-controller/authorization/rbac.go b/internal/operator-controller/authorization/rbac.go index 536e10262..fb0a9029b 100644 --- a/internal/operator-controller/authorization/rbac.go +++ b/internal/operator-controller/authorization/rbac.go @@ -105,7 +105,7 @@ func (a *rbacPreAuthorizer) PreAuthorize(ctx context.Context, ext *ocv1.ClusterE missingRules[ns] = compactMissingRules } - missingRulesWithDeduplicatedVerbs := []rbacv1.PolicyRule{} + missingRulesWithDeduplicatedVerbs := make([]rbacv1.PolicyRule, 0, len(missingRules[ns])) for _, rule := range missingRules[ns] { verbSet := sets.New[string](rule.Verbs...) if verbSet.Has("*") { From fb5fa52b3de404b1f17f1667d36afbc921d0ffbb Mon Sep 17 00:00:00 2001 From: Brett Tofel Date: Tue, 8 Apr 2025 13:41:11 -0400 Subject: [PATCH 46/67] Tidy verb vars together with comment & issue link Signed-off-by: Brett Tofel --- .../operator-controller/authorization/rbac.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/internal/operator-controller/authorization/rbac.go b/internal/operator-controller/authorization/rbac.go index fb0a9029b..1def4dafd 100644 --- a/internal/operator-controller/authorization/rbac.go +++ b/internal/operator-controller/authorization/rbac.go @@ -46,6 +46,14 @@ type ScopedPolicyRules struct { var objectVerbs = []string{"get", "patch", "update", "delete"} +// Here we are splitting collection verbs based on required scope +// NB: this split is tightly coupled to the requirements of the contentmanager, specifically +// its need for cluster-scoped list/watch permissions. +// TODO: We are accepting this coupling for now, but plan to decouple +// TODO: link for above https://github.com/operator-framework/operator-controller/issues/1911 +var namespacedCollectionVerbs = []string{"create"} +var clusterCollectionVerbs = []string{"list", "watch"} + type rbacPreAuthorizer struct { authorizer authorizer.Authorizer ruleResolver validation.AuthorizationRuleResolver @@ -302,13 +310,6 @@ func (dm *decodedManifest) rbacObjects() []client.Object { func (dm *decodedManifest) asAuthorizationAttributesRecordsForUser(manifestManager user.Info, ext *ocv1.ClusterExtension) []authorizer.AttributesRecord { var attributeRecords []authorizer.AttributesRecord - // Here we are splitting collection verbs based on required scope - // NB: this split is tightly coupled to the requirements of the contentmanager, specifically - // its need for cluster-scoped list/watch permissions. - // TODO: We are accepting this coupling for now, but plan to decouple - namespacedCollectionVerbs := []string{"create"} - clusterCollectionVerbs := []string{"list", "watch"} - for gvr, keys := range dm.gvrs { namespaces := sets.New[string]() for _, k := range keys { From a1904d8e26a8f391c703e3ca78b15b0b1b346b56 Mon Sep 17 00:00:00 2001 From: Brett Tofel Date: Tue, 8 Apr 2025 14:03:22 -0400 Subject: [PATCH 47/67] Add comments and protections on parsing err msg Signed-off-by: Brett Tofel --- .../operator-controller/authorization/rbac.go | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/internal/operator-controller/authorization/rbac.go b/internal/operator-controller/authorization/rbac.go index 1def4dafd..b9aa2a710 100644 --- a/internal/operator-controller/authorization/rbac.go +++ b/internal/operator-controller/authorization/rbac.go @@ -546,16 +546,38 @@ var fullAuthority = []rbacv1.PolicyRule{ {Verbs: []string{"*"}, NonResourceURLs: []string{"*"}}, } +// TODO: Investigate replacing this regex parsing with structured error handling once there are +// +// structured RBAC errors introduced by https://github.com/kubernetes/kubernetes/pull/130955. +// +// parseEscalationErrorForMissingRules attempts to extract specific RBAC permissions +// that were denied due to escalation prevention from a given error's text. +// It returns the list of extracted PolicyRules and an error. +// Note: If parsing is successful, the returned error is derived from the *input* error's +// message (specifically the part indicating the escalation attempt), not an error +// encountered during the parsing process itself. If parsing fails due to an unexpected +// error format, a distinct parsing error is returned. func parseEscalationErrorForMissingRules(ecError error) ([]rbacv1.PolicyRule, error) { - errRegex := regexp.MustCompile(`(?s)^(user \".*\" \(groups=.*\) is attempting to grant RBAC permissions not currently held):.*?$`) - permRegex := regexp.MustCompile(`{APIGroups:\[("[^"]*")\], Resources:\[("[^"]*")\], Verbs:\[("[^"]*")\]}`) + // errRegex captures the standard prefix of an escalation error message + errRegex := regexp.MustCompile(`(?s)^(user ".*" \(groups=.*\) is attempting to grant RBAC permissions not currently held):.*?$`) + // permRegex extracts the details (APIGroups, Resources, Verbs) of individual permissions listed within the error message + permRegex := regexp.MustCompile(`{APIGroups:\[("[^"]*")], Resources:\[("[^"]*")], Verbs:\[("[^"]*")]}`) errMatches := errRegex.FindAllStringSubmatch(ecError.Error(), -1) + // Check if the main error message prefix was matched and captured + if len(errMatches) == 0 || len(errMatches[0]) < 2 { + // The error format doesn't match the expected pattern for escalation errors + return nil, fmt.Errorf("failed to parse escalation error: unexpected format: %w", ecError) + } - // Extract permissions + // Extract permissions using permRegex permissions := []rbacv1.PolicyRule{} permMatches := permRegex.FindAllStringSubmatch(ecError.Error(), -1) for _, match := range permMatches { + // Ensure the match has the expected number of capture groups + if len(match) < 4 { + continue // Skip malformed permission strings + } permissions = append(permissions, rbacv1.PolicyRule{ APIGroups: []string{strings.Trim(match[1], `"`)}, Resources: []string{strings.Trim(match[2], `"`)}, @@ -563,6 +585,7 @@ func parseEscalationErrorForMissingRules(ecError error) ([]rbacv1.PolicyRule, er }) } + // Return the extracted permissions and the captured escalation message prefix as the error context return permissions, errors.New(errMatches[0][1]) } From b5094f5c946063865ae591ea56bb5650eee99593 Mon Sep 17 00:00:00 2001 From: Brett Tofel Date: Wed, 9 Apr 2025 11:33:39 -0400 Subject: [PATCH 48/67] Improvements to k8smaintainer code Signed-off-by: Brett Tofel --- hack/tools/k8smaintainer/main.go | 137 +++++++++++++++++++------------ 1 file changed, 86 insertions(+), 51 deletions(-) diff --git a/hack/tools/k8smaintainer/main.go b/hack/tools/k8smaintainer/main.go index f74973837..46f0900e4 100644 --- a/hack/tools/k8smaintainer/main.go +++ b/hack/tools/k8smaintainer/main.go @@ -18,16 +18,30 @@ import ( ) const ( - k8sRepo = "k8s.io/kubernetes" - expectedMajorMinorParts = 2 - goModFilePerms = fs.FileMode(0600) - minGoListVersionFields = 2 - minValidPatchNumber = 1 + k8sRepo = "k8s.io/kubernetes" + expectedMajorMinorParts = 2 + goModFilename = "go.mod" + goModFilePerms = fs.FileMode(0600) + minGoListVersionFields = 2 + minPatchNumberToDecrementFrom = 1 // We can only decrement patch if it's 1 or greater (to get 0 or greater) ) //nolint:gochecknoglobals var goExe = "go" +// readAndParseGoMod reads and parses the go.mod file. +func readAndParseGoMod(filename string) ([]byte, *modfile.File, error) { + modBytes, err := os.ReadFile(filename) + if err != nil { + return nil, nil, fmt.Errorf("error reading %s: %w", filename, err) + } + modF, err := modfile.Parse(filename, modBytes, nil) + if err != nil { + return nil, nil, fmt.Errorf("error parsing %s: %w", filename, err) + } + return modBytes, modF, nil +} + func main() { log.SetFlags(0) if os.Getenv("GOEXE") != "" { @@ -40,21 +54,16 @@ func main() { } modRoot := findModRoot(wd) if modRoot == "" { - log.Fatalf("Failed to find go.mod in %s or parent directories", wd) + log.Fatalf("Failed to find %s in %s or parent directories", goModFilename, wd) } if err := os.Chdir(modRoot); err != nil { log.Fatalf("Error changing directory to %s: %v", modRoot, err) } log.Printf("Running in module root: %s", modRoot) - modBytes, err := os.ReadFile("go.mod") + _, modF, err := readAndParseGoMod(goModFilename) if err != nil { - log.Fatalf("Error reading go.mod: %v", err) - } - - modF, err := modfile.Parse("go.mod", modBytes, nil) - if err != nil { - log.Fatalf("Error parsing go.mod: %v", err) + log.Fatal(err) // Error already formatted by helper function } // Find k8s.io/kubernetes version @@ -66,7 +75,7 @@ func main() { } } if k8sVer == "" { - log.Fatalf("Could not find %s in go.mod require block", k8sRepo) + log.Fatalf("Could not find %s in %s require block", k8sRepo, goModFilename) } log.Printf("Found %s version: %s", k8sRepo, k8sVer) @@ -74,12 +83,14 @@ func main() { if !semver.IsValid(k8sVer) { log.Fatalf("Invalid semver for %s: %s", k8sRepo, k8sVer) } + // Example: k8sVer = v1.32.3 majorMinor := semver.MajorMinor(k8sVer) // e.g., "v1.32" patch := strings.TrimPrefix(k8sVer, majorMinor+".") // e.g., "3" if len(strings.Split(majorMinor, ".")) != expectedMajorMinorParts { log.Fatalf("Unexpected format for MajorMinor: %s", majorMinor) } - targetStagingVer := "v0" + strings.TrimPrefix(majorMinor, "v1") + "." + patch // e.g., "v0.32.3" + // targetStagingVer becomes "v0" + ".32" + "." + "3" => "v0.32.3" + targetStagingVer := "v0" + strings.TrimPrefix(majorMinor, "v1") + "." + patch if !semver.IsValid(targetStagingVer) { log.Fatalf("Calculated invalid staging semver: %s", targetStagingVer) } @@ -96,7 +107,7 @@ func main() { output, err := runGoCommand("list", "-m", "-json", "all") if err != nil { // Try downloading first if list fails - log.Println("go list failed, trying go mod download...") + log.Println("'go list' failed, trying 'go mod download'...") if _, downloadErr := runGoCommand("mod", "download"); downloadErr != nil { log.Fatalf("Error running 'go mod download' after list failed: %v", downloadErr) } @@ -121,15 +132,17 @@ func main() { continue } - // Use replacement path if it exists + // Use replacement path if it exists, but skip local file replacements effectivePath := mod.Path if mod.Replace != nil { - effectivePath = mod.Replace.Path - // Skip local file replacements - if !strings.Contains(effectivePath, ".") { // Basic check if it looks like a module path vs local path - log.Printf("Skipping local replace: %s => %s", mod.Path, effectivePath) + // Heuristic: Assume module paths have a domain-like structure (e.g., 'xxx.yyy/zzz') in the first segment. + // Local paths usually don't (e.g., '../othermod', './local'). + parts := strings.SplitN(mod.Replace.Path, "/", 2) + if len(parts) > 0 && !strings.Contains(parts[0], ".") { + log.Printf("Skipping local replace: %s => %s", mod.Path, mod.Replace.Path) continue } + effectivePath = mod.Replace.Path } // Check existence of target version, fallback to previous patch if needed @@ -152,33 +165,39 @@ func main() { pins[mod.Path] = determinedVer } - // Add k8s.io/kubernetes itself to the pins map + // Add k8s.io/kubernetes itself to the pins map (ensures it's covered by the replace logic) pins[k8sRepo] = k8sVer log.Printf("Identified %d k8s.io/* modules to manage.", len(pins)) - // 7. Parse go.mod again (to have a fresh modfile object) - modBytes, err = os.ReadFile("go.mod") - if err != nil { - log.Fatalf("Error reading go.mod again: %v", err) - } - modF, err = modfile.Parse("go.mod", modBytes, nil) + // Parse go.mod again (needed in case `go list` modified it) + _, modF, err = readAndParseGoMod(goModFilename) if err != nil { - log.Fatalf("Error parsing go.mod again: %v", err) + log.Fatal(err) // Error already formatted by helper function } // Remove all existing k8s.io/* replaces log.Println("Removing existing k8s.io/* replace directives...") var replacesToRemove []string for _, rep := range modF.Replace { - if strings.HasPrefix(rep.Old.Path, "k8s.io/") { + // Only remove replaces targeting k8s.io/* modules (not local replacements like ../staging) + // assumes standard module paths contain '.' + if strings.HasPrefix(rep.Old.Path, "k8s.io/") && strings.Contains(rep.New.Path, ".") { replacesToRemove = append(replacesToRemove, rep.Old.Path) + } else if strings.HasPrefix(rep.Old.Path, "k8s.io/") { + log.Printf("Note: Found existing non-module replace for %s, leaving untouched: %s => %s %s", rep.Old.Path, rep.Old.Path, rep.New.Path, rep.New.Version) } } - for _, path := range replacesToRemove { - if err := modF.DropReplace(path, ""); err != nil { - // Tolerate errors if the replace was already somehow removed - log.Printf("Note: Error dropping replace for %s (might be benign): %v", path, err) + if len(replacesToRemove) > 0 { + for _, path := range replacesToRemove { + log.Printf("Removing replace for: %s", path) + // Drop replace expects oldPath and oldVersion. Version is empty for path-only replaces. + if err := modF.DropReplace(path, ""); err != nil { + // Tolerate errors if the replace was already somehow removed or structure changed + log.Printf("Note: Error dropping replace for %s (might be benign): %v", path, err) + } } + } else { + log.Println("No existing k8s.io/* module replaces found to remove.") } // Add new replace directives @@ -193,7 +212,6 @@ func main() { for _, path := range sortedPaths { version := pins[path] // Add replace for the module path itself (e.g., k8s.io/api => k8s.io/api v0.32.3) - // This handles cases where the effective path from `go list` might differ due to other replaces if err := modF.AddReplace(path, "", path, version); err != nil { log.Fatalf("Error adding replace for %s => %s %s: %v", path, path, version, err) } @@ -202,30 +220,31 @@ func main() { // Write go.mod log.Println("Writing updated go.mod...") - modF.Cleanup() // Sort blocks, etc. + modF.Cleanup() // Sort blocks, remove redundant directives etc. newModBytes, err := modF.Format() if err != nil { log.Fatalf("Error formatting go.mod: %v", err) } - if err := os.WriteFile("go.mod", newModBytes, goModFilePerms); err != nil { - log.Fatalf("Error writing go.mod: %v", err) + if err := os.WriteFile(goModFilename, newModBytes, goModFilePerms); err != nil { + log.Fatalf("Error writing %s: %v", goModFilename, err) } // Run `go mod tidy` - goVer := modF.Go.Version + goVer := "" + if modF.Go != nil { // Ensure Go directive exists before accessing Version + goVer = modF.Go.Version + } tidyArgs := []string{"mod", "tidy"} if goVer != "" { tidyArgs = append(tidyArgs, fmt.Sprintf("-go=%s", goVer)) - log.Printf("Running 'go mod tidy -go=%s'...", goVer) - } else { - log.Println("Running 'go mod tidy'...") } + log.Printf("Running '%s %s'...", goExe, strings.Join(tidyArgs, " ")) if _, err := runGoCommand(tidyArgs...); err != nil { log.Fatalf("Error running 'go mod tidy': %v", err) } // Run `go mod download k8s.io/kubernetes` - log.Printf("Running 'go mod download %s'...", k8sRepo) + log.Printf("Running '%s mod download %s'...", goExe, k8sRepo) if _, err := runGoCommand("mod", "download", k8sRepo); err != nil { // This might not be fatal, could be network issues, but log it prominently log.Printf("WARNING: Error running 'go mod download %s': %v", k8sRepo, err) @@ -237,7 +256,7 @@ func main() { // findModRoot searches for go.mod in dir and parent directories func findModRoot(dir string) string { for { - if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil { + if _, err := os.Stat(filepath.Join(dir, goModFilename)); err == nil { return dir } parent := filepath.Dir(dir) @@ -264,16 +283,24 @@ func runGoCommand(args ...string) ([]byte, error) { // getModuleVersions retrieves the list of available versions for a module func getModuleVersions(modulePath string) ([]string, error) { output, err := runGoCommand("list", "-m", "-versions", modulePath) + // Combine output and error message for checking because 'go list' sometimes writes errors to stdout + combinedOutput := string(output) + if err != nil { + combinedOutput += err.Error() + } + + // Check if the error/output indicates "no matching versions" - this is not a fatal error for our logic + if strings.Contains(combinedOutput, "no matching versions") { + return []string{}, nil // Return empty list, not an error + } + // If there was an actual error beyond "no matching versions" if err != nil { - // Check if the error is "no matching versions" - this is not a fatal error for our logic - if strings.Contains(string(output)+err.Error(), "no matching versions") { - return []string{}, nil // Return empty list, not an error - } return nil, fmt.Errorf("error listing versions for %s: %w", modulePath, err) } + fields := strings.Fields(string(output)) if len(fields) < minGoListVersionFields { - return []string{}, nil // No versions listed + return []string{}, nil // No versions listed (e.g., just the module path) } return fields[1:], nil // First field is the module path } @@ -301,10 +328,18 @@ func getLatestExistingVersion(modulePath, targetVer string) (string, error) { majorMinor := semver.MajorMinor(targetVer) // e.g., v0.32 patchStr := strings.TrimPrefix(targetVer, majorMinor+".") // e.g., 3 var patch int - if _, err := fmt.Sscan(patchStr, &patch); err != nil || patch < minValidPatchNumber { - log.Printf("Could not parse patch version or patch < %d for %s, cannot determine predecessor.", minValidPatchNumber, targetVer) + // Use Sscan to parse the integer patch number + if _, err := fmt.Sscan(patchStr, &patch); err != nil { + log.Printf("Could not parse patch version from %s for module %s: %v. Cannot determine predecessor.", targetVer, modulePath, err) return "", nil // Cannot determine predecessor } + + // Only try to decrement if the patch number is >= the minimum required to do so + if patch < minPatchNumberToDecrementFrom { + log.Printf("Patch version %d is less than %d for %s, cannot determine predecessor.", patch, minPatchNumberToDecrementFrom, targetVer) + return "", nil // Cannot determine predecessor (e.g., target was v0.32.0) + } + prevPatchVer := fmt.Sprintf("%s.%d", majorMinor, patch-1) // e.g., v0.32.2 foundPrev := false From 6348b6037764ca9fe9c8f4cda853fbbcb05a5521 Mon Sep 17 00:00:00 2001 From: Brett Tofel Date: Wed, 9 Apr 2025 11:47:47 -0400 Subject: [PATCH 49/67] Linter fix for unused byte slice Signed-off-by: Brett Tofel --- hack/tools/k8smaintainer/main.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/hack/tools/k8smaintainer/main.go b/hack/tools/k8smaintainer/main.go index 46f0900e4..1eb0ab2b1 100644 --- a/hack/tools/k8smaintainer/main.go +++ b/hack/tools/k8smaintainer/main.go @@ -30,16 +30,16 @@ const ( var goExe = "go" // readAndParseGoMod reads and parses the go.mod file. -func readAndParseGoMod(filename string) ([]byte, *modfile.File, error) { +func readAndParseGoMod(filename string) (*modfile.File, error) { modBytes, err := os.ReadFile(filename) if err != nil { - return nil, nil, fmt.Errorf("error reading %s: %w", filename, err) + return nil, fmt.Errorf("error reading %s: %w", filename, err) } modF, err := modfile.Parse(filename, modBytes, nil) if err != nil { - return nil, nil, fmt.Errorf("error parsing %s: %w", filename, err) + return nil, fmt.Errorf("error parsing %s: %w", filename, err) } - return modBytes, modF, nil + return modF, nil } func main() { @@ -61,7 +61,7 @@ func main() { } log.Printf("Running in module root: %s", modRoot) - _, modF, err := readAndParseGoMod(goModFilename) + modF, err := readAndParseGoMod(goModFilename) if err != nil { log.Fatal(err) // Error already formatted by helper function } @@ -170,7 +170,7 @@ func main() { log.Printf("Identified %d k8s.io/* modules to manage.", len(pins)) // Parse go.mod again (needed in case `go list` modified it) - _, modF, err = readAndParseGoMod(goModFilename) + modF, err = readAndParseGoMod(goModFilename) if err != nil { log.Fatal(err) // Error already formatted by helper function } From b6d6b07424f88cb1133aa2519e13ae911f9d2c2a Mon Sep 17 00:00:00 2001 From: Brett Tofel Date: Wed, 9 Apr 2025 14:39:05 -0400 Subject: [PATCH 50/67] New target now 'k8s-pin', take ENVVAR for k8s ver Also separate the target from make tiday and some code cleanup. Signed-off-by: Brett Tofel --- Makefile | 14 ++--- hack/tools/k8smaintainer/main.go | 97 ++++++++++++++++++++++++-------- 2 files changed, 80 insertions(+), 31 deletions(-) diff --git a/Makefile b/Makefile index 7670fb8f2..65186d269 100644 --- a/Makefile +++ b/Makefile @@ -120,13 +120,13 @@ custom-linter-build: #EXHELP Build custom linter lint-custom: custom-linter-build #EXHELP Call custom linter for the project go vet -tags=$(GO_BUILD_TAGS) -vettool=./bin/custom-linter ./... -.PHONY: k8s-maintainer #EXHELP this tool also calls `go mod tidy` but also allows us maintain k8s.io/kubernetes` changes bumping related staging modules (e.g., `k8s.io/api`, `k8s.io/apimachinery) as needed -k8s-maintainer: - go run hack/tools/k8smaintainer/main.go +.PHONY: k8s-pin #EXHELP Pin k8s staging modules based on k8s.io/kubernetes version (in go.mod or from K8S_IO_K8S_VERSION env var) and run go mod tidy. +k8s-pin: + K8S_IO_K8S_VERSION='$(K8S_IO_K8S_VERSION)' go run hack/tools/k8smaintainer/main.go -.PHONY: tidy -tidy: k8s-maintainer #HELP Update dependencies. - # k8s-maintainer calls go mod tidy +.PHONY: tidy #HELP Run go mod tidy. +tidy: + go mod tidy .PHONY: manifests KUSTOMIZE_CATD_CRDS_DIR := config/base/catalogd/crd/bases @@ -154,7 +154,7 @@ generate: $(CONTROLLER_GEN) #EXHELP Generate code containing DeepCopy, DeepCopyI $(CONTROLLER_GEN) --load-build-tags=$(GO_BUILD_TAGS) object:headerFile="hack/boilerplate.go.txt" paths="./..." .PHONY: verify -verify: tidy fmt generate manifests crd-ref-docs generate-test-data #HELP Verify all generated code is up-to-date. +verify: k8s-pin fmt generate manifests crd-ref-docs generate-test-data #HELP Verify all generated code is up-to-date. Runs k8s-pin instead of just tidy. git diff --exit-code # Renders registry+v1 bundles in test/convert diff --git a/hack/tools/k8smaintainer/main.go b/hack/tools/k8smaintainer/main.go index 1eb0ab2b1..1623e228e 100644 --- a/hack/tools/k8smaintainer/main.go +++ b/hack/tools/k8smaintainer/main.go @@ -24,6 +24,7 @@ const ( goModFilePerms = fs.FileMode(0600) minGoListVersionFields = 2 minPatchNumberToDecrementFrom = 1 // We can only decrement patch if it's 1 or greater (to get 0 or greater) + k8sVersionEnvVar = "K8S_IO_K8S_VERSION" ) //nolint:gochecknoglobals @@ -42,6 +43,42 @@ func readAndParseGoMod(filename string) (*modfile.File, error) { return modF, nil } +// getK8sVersionFromEnv processes the version specified via environment variable. +// It validates the version and runs `go get` to update the dependency. +func getK8sVersionFromEnv(targetK8sVer string) (string, error) { + log.Printf("Found target %s version override from env var %s: %s", k8sRepo, k8sVersionEnvVar, targetK8sVer) + if !semver.IsValid(targetK8sVer) { + return "", fmt.Errorf("invalid semver specified in %s: %s", k8sVersionEnvVar, targetK8sVer) + } + // Update the go.mod file first + log.Printf("Running 'go get %s@%s' to update the main dependency...", k8sRepo, targetK8sVer) + getArgs := fmt.Sprintf("%s@%s", k8sRepo, targetK8sVer) + if _, err := runGoCommand("get", getArgs); err != nil { + return "", fmt.Errorf("error running 'go get %s': %w", getArgs, err) + } + return targetK8sVer, nil // Return the validated version +} + +// getK8sVersionFromMod reads the go.mod file to find the current version of k8s.io/kubernetes. +// It returns the version string if found, or an empty string (and nil error) if not found. +func getK8sVersionFromMod() (string, error) { + modF, err := readAndParseGoMod(goModFilename) + if err != nil { + return "", err // Propagate error from reading/parsing + } + + // Find k8s.io/kubernetes version + for _, req := range modF.Require { + if req.Mod.Path == k8sRepo { + log.Printf("Found existing %s version in %s: %s", k8sRepo, goModFilename, req.Mod.Version) + return req.Mod.Version, nil // Return found version + } + } + // Not found case - return empty string, no error (as per original logic) + log.Printf("INFO: %s not found in %s require block. Nothing to do.", k8sRepo, goModFilename) + return "", nil +} + func main() { log.SetFlags(0) if os.Getenv("GOEXE") != "" { @@ -61,23 +98,27 @@ func main() { } log.Printf("Running in module root: %s", modRoot) - modF, err := readAndParseGoMod(goModFilename) - if err != nil { - log.Fatal(err) // Error already formatted by helper function - } + var k8sVer string - // Find k8s.io/kubernetes version - k8sVer := "" - for _, req := range modF.Require { - if req.Mod.Path == k8sRepo { - k8sVer = req.Mod.Version - break + // Determine the target k8s version using helper functions + targetK8sVerEnv := os.Getenv(k8sVersionEnvVar) + if targetK8sVerEnv != "" { + // Process version from environment variable + k8sVer, err = getK8sVersionFromEnv(targetK8sVerEnv) + if err != nil { + log.Fatalf("Failed to process k8s version from environment variable %s: %v", k8sVersionEnvVar, err) + } + } else { + // Process version from go.mod file + k8sVer, err = getK8sVersionFromMod() + if err != nil { + log.Fatalf("Failed to get k8s version from %s: %v", goModFilename, err) + } + // Handle the "not found" case where getK8sVersionFromMod returns "", nil + if k8sVer == "" { + os.Exit(0) // Exit gracefully as requested } } - if k8sVer == "" { - log.Fatalf("Could not find %s in %s require block", k8sRepo, goModFilename) - } - log.Printf("Found %s version: %s", k8sRepo, k8sVer) // Calculate target staging version if !semver.IsValid(k8sVer) { @@ -92,7 +133,7 @@ func main() { // targetStagingVer becomes "v0" + ".32" + "." + "3" => "v0.32.3" targetStagingVer := "v0" + strings.TrimPrefix(majorMinor, "v1") + "." + patch if !semver.IsValid(targetStagingVer) { - log.Fatalf("Calculated invalid staging semver: %s", targetStagingVer) + log.Fatalf("Calculated invalid staging semver: %s from k8s version %s", targetStagingVer, k8sVer) } log.Printf("Target staging version calculated: %s", targetStagingVer) @@ -158,7 +199,7 @@ func main() { } if determinedVer != targetStagingVer { - log.Printf("WARNING: Target version %s not found for %s. Using existing version %s.", targetStagingVer, effectivePath, determinedVer) + log.Printf("INFO: Target version %s not found for %s. Using existing predecessor version %s.", targetStagingVer, effectivePath, determinedVer) } // map the original module path (as seen in the dependency graph) to the desired version for the 'replace' directive @@ -169,18 +210,18 @@ func main() { pins[k8sRepo] = k8sVer log.Printf("Identified %d k8s.io/* modules to manage.", len(pins)) - // Parse go.mod again (needed in case `go list` modified it) - modF, err = readAndParseGoMod(goModFilename) + // Parse go.mod again (needed in case `go list` or `go get` modified it) + modF, err := readAndParseGoMod(goModFilename) if err != nil { log.Fatal(err) // Error already formatted by helper function } - // Remove all existing k8s.io/* replaces - log.Println("Removing existing k8s.io/* replace directives...") + // Remove all existing k8s.io/* replaces that target other modules (not local paths) + log.Println("Removing existing k8s.io/* module replace directives...") var replacesToRemove []string for _, rep := range modF.Replace { // Only remove replaces targeting k8s.io/* modules (not local replacements like ../staging) - // assumes standard module paths contain '.' + // Check that the old path starts with k8s.io/ and the new path looks like a module path (contains '.') if strings.HasPrefix(rep.Old.Path, "k8s.io/") && strings.Contains(rep.New.Path, ".") { replacesToRemove = append(replacesToRemove, rep.Old.Path) } else if strings.HasPrefix(rep.Old.Path, "k8s.io/") { @@ -274,8 +315,12 @@ func runGoCommand(args ...string) ([]byte, error) { var stdout, stderr bytes.Buffer cmd.Stdout = &stdout cmd.Stderr = &stderr + log.Printf("Executing: %s %s", goExe, strings.Join(args, " ")) if err := cmd.Run(); err != nil { - return nil, fmt.Errorf("command '%s %s' failed: %v\nStderr:\n%s", goExe, strings.Join(args, " "), err, stderr.String()) + if stderr.Len() > 0 { + log.Printf("Stderr:\n%s", stderr.String()) + } + return nil, fmt.Errorf("command '%s %s' failed: %w", goExe, strings.Join(args, " "), err) } return stdout.Bytes(), nil } @@ -286,11 +331,14 @@ func getModuleVersions(modulePath string) ([]string, error) { // Combine output and error message for checking because 'go list' sometimes writes errors to stdout combinedOutput := string(output) if err != nil { - combinedOutput += err.Error() + if !strings.Contains(combinedOutput, err.Error()) { + combinedOutput += err.Error() + } } // Check if the error/output indicates "no matching versions" - this is not a fatal error for our logic - if strings.Contains(combinedOutput, "no matching versions") { + if strings.Contains(combinedOutput, "no matching versions") || strings.Contains(combinedOutput, "no required module provides package") { + log.Printf("INFO: No versions found for module %s via 'go list'.", modulePath) return []string{}, nil // Return empty list, not an error } // If there was an actual error beyond "no matching versions" @@ -300,6 +348,7 @@ func getModuleVersions(modulePath string) ([]string, error) { fields := strings.Fields(string(output)) if len(fields) < minGoListVersionFields { + log.Printf("INFO: No versions listed for module %s (output: '%s')", modulePath, string(output)) return []string{}, nil // No versions listed (e.g., just the module path) } return fields[1:], nil // First field is the module path From 51b6b89d69f2bc6f29ade4b273a3804e5a0ea3c6 Mon Sep 17 00:00:00 2001 From: Brett Tofel Date: Wed, 9 Apr 2025 14:50:16 -0400 Subject: [PATCH 51/67] Replace x/mod/semver w/ blang - more legible parse Signed-off-by: Brett Tofel --- hack/tools/k8smaintainer/main.go | 50 +++++++++++++++++--------------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/hack/tools/k8smaintainer/main.go b/hack/tools/k8smaintainer/main.go index 1623e228e..978c884a6 100644 --- a/hack/tools/k8smaintainer/main.go +++ b/hack/tools/k8smaintainer/main.go @@ -4,7 +4,7 @@ import ( "bytes" "encoding/json" "fmt" - "io/fs" // Imported for fs.FileMode + "io/fs" "log" "os" "os/exec" @@ -12,9 +12,9 @@ import ( "sort" "strings" + "github.com/blang/semver/v4" "golang.org/x/mod/modfile" "golang.org/x/mod/module" - "golang.org/x/mod/semver" ) const ( @@ -47,8 +47,8 @@ func readAndParseGoMod(filename string) (*modfile.File, error) { // It validates the version and runs `go get` to update the dependency. func getK8sVersionFromEnv(targetK8sVer string) (string, error) { log.Printf("Found target %s version override from env var %s: %s", k8sRepo, k8sVersionEnvVar, targetK8sVer) - if !semver.IsValid(targetK8sVer) { - return "", fmt.Errorf("invalid semver specified in %s: %s", k8sVersionEnvVar, targetK8sVer) + if _, err := semver.ParseTolerant(targetK8sVer); err != nil { + return "", fmt.Errorf("invalid semver specified in %s: %s (%w)", k8sVersionEnvVar, targetK8sVer, err) } // Update the go.mod file first log.Printf("Running 'go get %s@%s' to update the main dependency...", k8sRepo, targetK8sVer) @@ -121,19 +121,22 @@ func main() { } // Calculate target staging version - if !semver.IsValid(k8sVer) { - log.Fatalf("Invalid semver for %s: %s", k8sRepo, k8sVer) + k8sSemVer, err := semver.ParseTolerant(k8sVer) + if err != nil { + // This should ideally not happen if validation passed earlier, but check anyway. + log.Fatalf("Invalid semver for %s: %s (%v)", k8sRepo, k8sVer, err) // Adjusted log format slightly } - // Example: k8sVer = v1.32.3 - majorMinor := semver.MajorMinor(k8sVer) // e.g., "v1.32" - patch := strings.TrimPrefix(k8sVer, majorMinor+".") // e.g., "3" - if len(strings.Split(majorMinor, ".")) != expectedMajorMinorParts { - log.Fatalf("Unexpected format for MajorMinor: %s", majorMinor) + + if k8sSemVer.Major != 1 { + log.Fatalf("Expected k8s version %s to have major version 1", k8sVer) } - // targetStagingVer becomes "v0" + ".32" + "." + "3" => "v0.32.3" - targetStagingVer := "v0" + strings.TrimPrefix(majorMinor, "v1") + "." + patch - if !semver.IsValid(targetStagingVer) { - log.Fatalf("Calculated invalid staging semver: %s from k8s version %s", targetStagingVer, k8sVer) + targetSemVer := semver.Version{Major: 0, Minor: k8sSemVer.Minor, Patch: k8sSemVer.Patch} + // Prepend 'v' as expected by Go modules and the rest of the script logic + targetStagingVer := "v" + targetSemVer.String() + + // Validate the constructed staging version string + if _, err := semver.ParseTolerant(targetStagingVer); err != nil { + log.Fatalf("Calculated invalid staging semver: %s from k8s version %s (%v)", targetStagingVer, k8sVer, err) // Adjusted log format slightly } log.Printf("Target staging version calculated: %s", targetStagingVer) @@ -374,22 +377,21 @@ func getLatestExistingVersion(modulePath, targetVer string) (string, error) { } // Target not found, try previous patch version - majorMinor := semver.MajorMinor(targetVer) // e.g., v0.32 - patchStr := strings.TrimPrefix(targetVer, majorMinor+".") // e.g., 3 - var patch int - // Use Sscan to parse the integer patch number - if _, err := fmt.Sscan(patchStr, &patch); err != nil { - log.Printf("Could not parse patch version from %s for module %s: %v. Cannot determine predecessor.", targetVer, modulePath, err) + targetSemVer, err := semver.ParseTolerant(targetVer) + if err != nil { + log.Printf("Could not parse target version %s for module %s: %v. Cannot determine predecessor.", targetVer, modulePath, err) return "", nil // Cannot determine predecessor } // Only try to decrement if the patch number is >= the minimum required to do so - if patch < minPatchNumberToDecrementFrom { - log.Printf("Patch version %d is less than %d for %s, cannot determine predecessor.", patch, minPatchNumberToDecrementFrom, targetVer) + if targetSemVer.Patch < uint64(minPatchNumberToDecrementFrom) { + log.Printf("Patch version %d is less than %d for %s, cannot determine predecessor.", targetSemVer.Patch, minPatchNumberToDecrementFrom, targetVer) return "", nil // Cannot determine predecessor (e.g., target was v0.32.0) } - prevPatchVer := fmt.Sprintf("%s.%d", majorMinor, patch-1) // e.g., v0.32.2 + prevSemVer := targetSemVer + prevSemVer.Patch-- + prevPatchVer := "v" + prevSemVer.String() foundPrev := false for _, v := range availableVersions { From 0195c72b458dde98450f459947262e75976dddd5 Mon Sep 17 00:00:00 2001 From: Brett Tofel Date: Wed, 9 Apr 2025 16:40:03 -0400 Subject: [PATCH 52/67] Move EXHELP for k8s-pin target Signed-off-by: Brett Tofel --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 65186d269..6245c6ccb 100644 --- a/Makefile +++ b/Makefile @@ -120,8 +120,8 @@ custom-linter-build: #EXHELP Build custom linter lint-custom: custom-linter-build #EXHELP Call custom linter for the project go vet -tags=$(GO_BUILD_TAGS) -vettool=./bin/custom-linter ./... -.PHONY: k8s-pin #EXHELP Pin k8s staging modules based on k8s.io/kubernetes version (in go.mod or from K8S_IO_K8S_VERSION env var) and run go mod tidy. -k8s-pin: +.PHONY: k8s-pin +k8s-pin: #EXHELP Pin k8s staging modules based on k8s.io/kubernetes version (in go.mod or from K8S_IO_K8S_VERSION env var) and run go mod tidy. K8S_IO_K8S_VERSION='$(K8S_IO_K8S_VERSION)' go run hack/tools/k8smaintainer/main.go .PHONY: tidy #HELP Run go mod tidy. From b19add3e1151e5a4b712dffa8a8bad29cc42e149 Mon Sep 17 00:00:00 2001 From: Brett Tofel Date: Wed, 9 Apr 2025 17:10:42 -0400 Subject: [PATCH 53/67] Update README.md to account for changes Signed-off-by: Brett Tofel --- hack/tools/k8smaintainer/README.md | 75 +++++++++++++++--------------- 1 file changed, 37 insertions(+), 38 deletions(-) diff --git a/hack/tools/k8smaintainer/README.md b/hack/tools/k8smaintainer/README.md index b40177be5..a8162704d 100644 --- a/hack/tools/k8smaintainer/README.md +++ b/hack/tools/k8smaintainer/README.md @@ -1,44 +1,43 @@ # Kubernetes Staging Module Version Synchronization Tool ## Purpose -This tool ensures that if `k8s.io/kubernetes` changes version in your `go.mod`, all related staging modules (e.g., `k8s.io/api`, `k8s.io/apimachinery`) are automatically pinned to the corresponding published version. +This tool ensures that if `k8s.io/kubernetes` changes version in your `go.mod`, all related staging modules (e.g., `k8s.io/api`, `k8s.io/apimachinery`) are automatically pinned to the corresponding published version. Recent improvements include an environment variable override and refined logic for version resolution. ## How It Works -1. **Parse and Filter:** - - Reads and parses your `go.mod`. - - Removes any existing `replace` directives referencing `k8s.io/`, ensuring no stale mappings persist. - -2. **Find Kubernetes Version & Compute Staging Version:** - - Identifies the pinned `k8s.io/kubernetes` version in the `require` block. - - Converts `"v1.xx.yy"` to `"v0.xx.yy"` to determine the correct version for staging modules. - -3. **List All Modules in the Graph:** - - Runs `go list -m -json all` to get the full dependency tree. - - Extracts all `k8s.io/*` modules, ensuring completeness. - -4. **Pin Staging Modules:** - - For each `k8s.io/*` module (except `k8s.io/kubernetes`): - - If it has a `v0.0.0` version (indicating it’s untagged) or its version doesn’t match the computed published version, the tool updates the `replace` directive accordingly. - - Ensures `k8s.io/kubernetes` itself has a `replace` entry for consistency. - -5. **Write & Finalize:** - - Writes the updated `go.mod`. - - Runs `go mod tidy` to clean up any dangling dependencies. - - Runs `go mod download k8s.io/kubernetes` to guarantee required entries in `go.sum`. - - Performs a final check that no modules remain at `v0.0.0`. - -## Behavior When Kubernetes Version Changes -- If you manually update `k8s.io/kubernetes` (e.g., from `v1.32.2` to `v1.32.1`) and rerun this tool: - - The tool detects the new version and calculates the corresponding staging version (`v0.32.1`). - - It updates all staging modules (`k8s.io/*`) to match the new version, ensuring consistency. - - Any outdated `replace` directives are removed and replaced with the correct version. - -## Additional Checks & Safeguards -- **Missing `go.sum` Entries:** If `go list -m -json all` fails due to missing entries, the tool runs `go mod download` to ensure completeness. -- **Conflicting Pinned Versions:** The tool enforces replace directives, but transitive dependencies may still cause conflicts that require manual resolution. -- **Modules Introduced/Removed in Certain Versions:** If a required module no longer exists in a given Kubernetes version, manual intervention may be needed. - -## Notes -- Ensures all `k8s.io/*` modules are treated consistently, even if they were not explicitly listed in `go.mod`. -- Warns if any module remains pinned at `v0.0.0`, which could indicate an issue with upstream tagging. +1. **Parsing and Filtering:** + - Reads and parses your `go.mod` file. + - Removes existing `replace` directives for `k8s.io/` modules to avoid stale mappings. + +2. **Determine Kubernetes Version:** + - **Environment Variable Override:** + If the environment variable `K8S_IO_K8S_VERSION` is set, its value is validated (using semver standards) and used as the target version for `k8s.io/kubernetes`. The tool then runs `go get k8s.io/kubernetes@` to update the dependency. + - **Default Behavior:** + If `K8S_IO_K8S_VERSION` is not set, the tool reads the version of `k8s.io/kubernetes` from the `go.mod` file. + +3. **Compute the Target Staging Version:** + - Converts a Kubernetes version in the form `v1.xx.yy` into the staging version format `v0.xx.yy`. + - If the target staging version is unavailable, the tool attempts to fall back to the previous patch version. + +4. **Updating Module Replace Directives:** + - Retrieves the full dependency graph using `go list -m -json all`. + - Identifies relevant `k8s.io/*` modules (skipping the main module and version-suffixed modules). + - Removes outdated `replace` directives (ignoring local path replacements). + - Adds new `replace` directives to pin modules—including `k8s.io/kubernetes`—to the computed staging version. + +5. **Finalizing Changes:** + - Writes the updated `go.mod` file. + - Runs `go mod tidy` to clean up dependencies. + - Executes `go mod download k8s.io/kubernetes` to update `go.sum`. + - Logs any issues, such as modules remaining at an untagged version (`v0.0.0`), which may indicate upstream tagging problems. + +## Environment Variables + +- **K8S_IO_K8S_VERSION (optional):** + When set, this environment variable overrides the Kubernetes version found in `go.mod`. The tool validates this semver string, updates the dependency using `go get`, and processes modules accordingly. + +## Additional Notes + +- The tool ensures consistency across all `k8s.io/*` modules, even if they are not explicitly listed in `go.mod`. +- If a suitable staging version is not found, a warning is logged and the closest valid version is used. +- All operations are logged, which helps in troubleshooting and verifying the process. \ No newline at end of file From 364bfb90088b46ca9fd864bd569307e0ccb9b1fc Mon Sep 17 00:00:00 2001 From: Brett Tofel Date: Thu, 10 Apr 2025 15:03:57 -0400 Subject: [PATCH 54/67] Split permission & resolution error captures Signed-off-by: Brett Tofel --- .../operator-controller/authorization/rbac.go | 40 +++++++++++++------ 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/internal/operator-controller/authorization/rbac.go b/internal/operator-controller/authorization/rbac.go index b9aa2a710..818076b8b 100644 --- a/internal/operator-controller/authorization/rbac.go +++ b/internal/operator-controller/authorization/rbac.go @@ -552,29 +552,37 @@ var fullAuthority = []rbacv1.PolicyRule{ // // parseEscalationErrorForMissingRules attempts to extract specific RBAC permissions // that were denied due to escalation prevention from a given error's text. -// It returns the list of extracted PolicyRules and an error. +// It returns the list of extracted PolicyRules and an error detailing the escalation attempt +// and any resolution errors found. // Note: If parsing is successful, the returned error is derived from the *input* error's -// message (specifically the part indicating the escalation attempt), not an error -// encountered during the parsing process itself. If parsing fails due to an unexpected +// message, not an error encountered during the parsing process itself. If parsing fails due to an unexpected // error format, a distinct parsing error is returned. func parseEscalationErrorForMissingRules(ecError error) ([]rbacv1.PolicyRule, error) { - // errRegex captures the standard prefix of an escalation error message - errRegex := regexp.MustCompile(`(?s)^(user ".*" \(groups=.*\) is attempting to grant RBAC permissions not currently held):.*?$`) + // errRegex captures the missing permissions and optionally resolution errors from an escalation error message + // Group 1: The list of missing permissions + // Group 2: Optional resolution errors + errRegex := regexp.MustCompile(`(?s)^user ".*" \(groups=.*\) is attempting to grant RBAC permissions not currently held: (.*)(?:; resolution errors: (.*))?$`) // permRegex extracts the details (APIGroups, Resources, Verbs) of individual permissions listed within the error message permRegex := regexp.MustCompile(`{APIGroups:\[("[^"]*")], Resources:\[("[^"]*")], Verbs:\[("[^"]*")]}`) - errMatches := errRegex.FindAllStringSubmatch(ecError.Error(), -1) - // Check if the main error message prefix was matched and captured - if len(errMatches) == 0 || len(errMatches[0]) < 2 { + errString := ecError.Error() + errMatches := errRegex.FindStringSubmatch(errString) // Use FindStringSubmatch for single match expected + + // Check if the main error message pattern was matched and captured the required groups + // We expect at least 3 elements: full match, missing permissions, resolution errors (can be empty) + if len(errMatches) < 3 { // The error format doesn't match the expected pattern for escalation errors return nil, fmt.Errorf("failed to parse escalation error: unexpected format: %w", ecError) } - // Extract permissions using permRegex + missingPermissionsStr := errMatches[1] + resolutionErrorsStr := errMatches[2] + + // Extract permissions using permRegex from the captured permissions string (Group 1) permissions := []rbacv1.PolicyRule{} - permMatches := permRegex.FindAllStringSubmatch(ecError.Error(), -1) + permMatches := permRegex.FindAllStringSubmatch(missingPermissionsStr, -1) for _, match := range permMatches { - // Ensure the match has the expected number of capture groups + // Ensure the match has the expected number of capture groups (full match + 3 groups) if len(match) < 4 { continue // Skip malformed permission strings } @@ -585,8 +593,14 @@ func parseEscalationErrorForMissingRules(ecError error) ([]rbacv1.PolicyRule, er }) } - // Return the extracted permissions and the captured escalation message prefix as the error context - return permissions, errors.New(errMatches[0][1]) + // Construct the error message to return. Include resolution errors if present + errMsg := "escalation check failed" + if resolutionErrorsStr != "" { + errMsg = fmt.Sprintf("%s; resolution errors: %s", errMsg, resolutionErrorsStr) + } + + // Return the extracted permissions and the constructed error message + return permissions, errors.New(errMsg) } func hasAggregationRule(clusterRole *rbacv1.ClusterRole) bool { From 47fcb60a19ddfe87338a9b13ef5d75a883b1c1fc Mon Sep 17 00:00:00 2001 From: Tayler Geiger Date: Wed, 9 Apr 2025 16:34:39 -0500 Subject: [PATCH 55/67] Improve permission regexp matching Now handles multiple values in any of APIGroups, Resources, or Verbs. Adds small utility function for trimming and splitting those values into a string slice. Signed-off-by: Tayler Geiger --- .../operator-controller/authorization/rbac.go | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/internal/operator-controller/authorization/rbac.go b/internal/operator-controller/authorization/rbac.go index 818076b8b..9faa3d0b6 100644 --- a/internal/operator-controller/authorization/rbac.go +++ b/internal/operator-controller/authorization/rbac.go @@ -563,7 +563,7 @@ func parseEscalationErrorForMissingRules(ecError error) ([]rbacv1.PolicyRule, er // Group 2: Optional resolution errors errRegex := regexp.MustCompile(`(?s)^user ".*" \(groups=.*\) is attempting to grant RBAC permissions not currently held: (.*)(?:; resolution errors: (.*))?$`) // permRegex extracts the details (APIGroups, Resources, Verbs) of individual permissions listed within the error message - permRegex := regexp.MustCompile(`{APIGroups:\[("[^"]*")], Resources:\[("[^"]*")], Verbs:\[("[^"]*")]}`) + permRegex := regexp.MustCompile(`{APIGroups:\[([^\]]*)\], Resources:\[([^\]]*)\], Verbs:\[([^\]]*)\]}`) errString := ecError.Error() errMatches := errRegex.FindStringSubmatch(errString) // Use FindStringSubmatch for single match expected @@ -587,9 +587,9 @@ func parseEscalationErrorForMissingRules(ecError error) ([]rbacv1.PolicyRule, er continue // Skip malformed permission strings } permissions = append(permissions, rbacv1.PolicyRule{ - APIGroups: []string{strings.Trim(match[1], `"`)}, - Resources: []string{strings.Trim(match[2], `"`)}, - Verbs: []string{strings.Trim(match[3], `"`)}, + APIGroups: splitAndTrim(match[1]), + Resources: splitAndTrim(match[2]), + Verbs: splitAndTrim(match[3]), }) } @@ -603,6 +603,18 @@ func parseEscalationErrorForMissingRules(ecError error) ([]rbacv1.PolicyRule, er return permissions, errors.New(errMsg) } +func splitAndTrim(input string) []string { + parts := strings.Split(input, ",") + + output := make([]string, 0, len(parts)) + for _, part := range parts { + trimmed := strings.TrimSpace(part) + trimmed = strings.Trim(trimmed, `"`) + output = append(output, trimmed) + } + return output +} + func hasAggregationRule(clusterRole *rbacv1.ClusterRole) bool { // Currently, an aggregation rule is considered present only if it has one or more selectors. // An empty slice of ClusterRoleSelectors means no selectors were provided, From 465645688ce66cc2fed59c9897b554ba325e42f9 Mon Sep 17 00:00:00 2001 From: Brett Tofel Date: Fri, 11 Apr 2025 09:36:52 -0400 Subject: [PATCH 56/67] Run make k8s-pin post-rebase Signed-off-by: Brett Tofel --- go.mod | 17 ++----- go.sum | 142 +++++++++++++++++++++++++++++---------------------------- 2 files changed, 77 insertions(+), 82 deletions(-) diff --git a/go.mod b/go.mod index 1f1fb7561..fa3c783c0 100644 --- a/go.mod +++ b/go.mod @@ -31,13 +31,12 @@ require ( gopkg.in/yaml.v2 v2.4.0 helm.sh/helm/v3 v3.17.3 k8s.io/api v0.32.3 - k8s.io/apiextensions-apiserver v0.32.2 + k8s.io/apiextensions-apiserver v0.32.3 k8s.io/apimachinery v0.32.3 k8s.io/apiserver v0.32.3 k8s.io/cli-runtime v0.32.3 k8s.io/client-go v0.32.3 k8s.io/component-base v0.32.3 - k8s.io/component-helpers v0.32.1 k8s.io/klog/v2 v2.130.1 k8s.io/kubernetes v1.32.3 k8s.io/utils v0.0.0-20241210054802-24370beab758 @@ -45,7 +44,10 @@ require ( sigs.k8s.io/yaml v1.4.0 ) -require k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect +require ( + k8s.io/component-helpers v0.32.3 // indirect + k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect +) require ( cel.dev/expr v0.19.0 // indirect @@ -220,7 +222,6 @@ require ( go.opentelemetry.io/otel/trace v1.33.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect golang.org/x/crypto v0.37.0 // indirect - golang.org/x/mod v0.24.0 // indirect golang.org/x/net v0.39.0 // indirect golang.org/x/oauth2 v0.27.0 // indirect golang.org/x/sys v0.32.0 // indirect @@ -237,14 +238,6 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.32.3 - k8s.io/apiextensions-apiserver v0.32.3 - k8s.io/apimachinery v0.32.3 - k8s.io/apiserver v0.32.3 - k8s.io/cli-runtime v0.32.3 - k8s.io/client-go v0.32.3 - k8s.io/component-base v0.32.3 - k8s.io/component-helpers v0.32.3 // indirect k8s.io/controller-manager v0.32.3 // indirect k8s.io/kubectl v0.32.3 // indirect oras.land/oras-go v1.2.5 // indirect diff --git a/go.sum b/go.sum index 3558f7467..f788a5f2b 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -cel.dev/expr v0.18.0 h1:CJ6drgk+Hf96lkLikr4rFf19WrU0BOWEihyZnI2TAzo= -cel.dev/expr v0.18.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= +cel.dev/expr v0.19.0 h1:lXuo+nDhpyJSpWxpPVi5cPUwzKb+dsdOiw6IreM5yt0= +cel.dev/expr v0.19.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= @@ -65,8 +65,8 @@ github.com/containerd/containerd/api v1.8.0 h1:hVTNJKR8fMc/2Tiw60ZRijntNMd1U+JVM github.com/containerd/containerd/api v1.8.0/go.mod h1:dFv4lt6S20wTu/hMcP4350RL87qPWLVa/OHOwmmdnYc= github.com/containerd/continuity v0.4.4 h1:/fNVfTJ7wIl/YPMHjf+5H32uFhl63JucB34PlCpMKII= github.com/containerd/continuity v0.4.4/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE= -github.com/containerd/errdefs v0.3.0 h1:FSZgGOeK4yuT/+DnF07/Olde/q4KBoMsaamhXxIMDp4= -github.com/containerd/errdefs v0.3.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= +github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= @@ -79,8 +79,8 @@ github.com/containerd/ttrpc v1.2.7 h1:qIrroQvuOL9HQ1X6KHe2ohc7p+HP/0VE6XPU7elJRq github.com/containerd/ttrpc v1.2.7/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o= github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40= github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk= -github.com/containers/common v0.61.0 h1:j/84PTqZIKKYy42OEJsZmjZ4g4Kq2ERuC3tqp2yWdh4= -github.com/containers/common v0.61.0/go.mod h1:NGRISq2vTFPSbhNqj6MLwyes4tWSlCnqbJg7R77B8xc= +github.com/containers/common v0.62.0 h1:Sl9WE5h7Y/F3bejrMAA4teP1EcY9ygqJmW4iwSloZ10= +github.com/containers/common v0.62.0/go.mod h1:Yec+z8mrSq4rydHofrnDCBqAcNA/BGrSg1kfFUL6F6s= github.com/containers/image/v5 v5.34.3 h1:/cMgfyA4Y7ILH7nzWP/kqpkE5Df35Ek4bp5ZPvJOVmI= github.com/containers/image/v5 v5.34.3/go.mod h1:MG++slvQSZVq5ejAcLdu4APGsKGMb0YHHnAo7X28fdE= github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 h1:Qzk5C6cYglewc+UyGf6lc8Mj2UaPTHy/iF2De0/77CA= @@ -106,12 +106,12 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/distribution/distribution/v3 v3.0.0-rc.1 h1:6M4ewmPBUhF7wtQ8URLOQ1W/PQuVKiD1u8ymwLDUGqQ= -github.com/distribution/distribution/v3 v3.0.0-rc.1/go.mod h1:tFjaPDeHCrLg28e4feBIy27cP+qmrc/mvkl6MFIfVi4= +github.com/distribution/distribution/v3 v3.0.0-rc.3 h1:JRJso9IVLoooKX76oWR+DWCCdZlK5m4nRtDWvzB1ITg= +github.com/distribution/distribution/v3 v3.0.0-rc.3/go.mod h1:offoOgrnYs+CFwis8nE0hyzYZqRCZj5EFc5kgfszwiE= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/cli v27.5.1+incompatible h1:JB9cieUT9YNiMITtIsguaN55PLOHhBSz3LKVc6cqWaY= -github.com/docker/cli v27.5.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v28.0.0+incompatible h1:ido37VmLUqEp+5NFb9icd6BuBB+SNDgCn+5kPCr2buA= +github.com/docker/cli v28.0.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v27.5.1+incompatible h1:4PYU5dnBYqRQi0294d1FBECqT9ECWeQAIfE8q4YnPY8= @@ -206,8 +206,8 @@ github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJA github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-migrate/migrate/v4 v4.18.1 h1:JML/k+t4tpHCpQTCAD62Nu43NUFzHY4CV3uAuvHGC+Y= -github.com/golang-migrate/migrate/v4 v4.18.1/go.mod h1:HAX6m3sQgcdO81tdjn5exv20+3Kb13cmGli1hrD6hks= +github.com/golang-migrate/migrate/v4 v4.18.2 h1:2VSCMz7x7mjyTXx3m2zPokOY82LTRgxK1yQYKo6wWQ8= +github.com/golang-migrate/migrate/v4 v4.18.2/go.mod h1:2CM6tJvn2kqPXwnXO/d3rAQYiyoIm180VsO8PRX6Rpk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= @@ -267,8 +267,8 @@ github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJr github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20210315223345-82c243799c99 h1:JYghRBlGCZyCF2wNUJ8W0cwaQdtpcssJ4CgC406g+WU= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20210315223345-82c243799c99/go.mod h1:3bDW6wMZJB7tiONtC/1Xpicra6Wp5GgbTbQWCbI5fkc= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 h1:ad0vkEBuk23VJzZR9nkLVG0YAoN9coASF1GusYX6AlU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0/go.mod h1:igFoXX2ELCW06bol23DWPB5BEWfZISOzSP5K2sbLea0= github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg= github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY= github.com/h2non/go-is-svg v0.0.0-20160927212452-35e8c4b0612c h1:fEE5/5VNnYUoBOj2I9TP8Jc+a7lge3QWn9DKE7NCwfc= @@ -403,12 +403,12 @@ github.com/operator-framework/helm-operator-plugins v0.8.0 h1:0f6HOQC5likkf0b/Ov github.com/operator-framework/helm-operator-plugins v0.8.0/go.mod h1:Sc+8bE38xTCgCChBUvtq/PxatEg9fAypr7S5iAw8nlA= github.com/operator-framework/operator-lib v0.17.0 h1:cbz51wZ9+GpWR1ZYP4CSKSSBxDlWxmmnseaHVZZjZt4= github.com/operator-framework/operator-lib v0.17.0/go.mod h1:TGopBxIE8L6E/Cojzo26R3NFp1eNlqhQNmzqhOblaLw= -github.com/operator-framework/operator-registry v1.50.0 h1:kMAwsKAEDjuSx5dGchMX+CD3SMHWwOAC/xyK3LQweB4= -github.com/operator-framework/operator-registry v1.50.0/go.mod h1:713Z/XzA5jViFMGIsXmfAcpA6h5uUKqUl3fO1t4taa0= -github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= -github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= -github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks= -github.com/otiai10/mint v1.5.1/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM= +github.com/operator-framework/operator-registry v1.51.0 h1:3T1H2W0wYvJx82x+Ue6nooFsn859ceJf1yH6MdRdjMQ= +github.com/operator-framework/operator-registry v1.51.0/go.mod h1:dJadFTSvsgpeiqhTMK7+zXrhU0LIlx4Y/aDz0efq5oQ= +github.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8= +github.com/otiai10/copy v1.14.1/go.mod h1:oQwrEDDOci3IM8dJF0d8+jnbfPDllW6vUjNc3DoZm9I= +github.com/otiai10/mint v1.6.3 h1:87qsV/aw1F5as1eH1zS/yqHY85ANKVMgkDrf9rcxbQs= +github.com/otiai10/mint v1.6.3/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= @@ -426,8 +426,8 @@ github.com/proglottis/gpgme v0.1.4/go.mod h1:5LoXMgpE4bttgwwdv9bLs/vwqv3qV7F4glE github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= -github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk= -github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg= +github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= +github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -521,8 +521,8 @@ github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= -go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= +go.etcd.io/bbolt v1.4.0 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk= +go.etcd.io/bbolt v1.4.0/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk= go.etcd.io/etcd/api/v3 v3.5.16 h1:WvmyJVbjWqK4R1E+B12RRHz3bRGy9XVfh++MgbN+6n0= go.etcd.io/etcd/api/v3 v3.5.16/go.mod h1:1P4SlIP/VwkDmGo3OlOD7faPeP8KDIFhqvciH5EfN28= go.etcd.io/etcd/client/pkg/v3 v3.5.16 h1:ZgY48uH6UvB+/7R9Yf4x574uCO3jIx0TRDyetSfId3Q= @@ -535,46 +535,48 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/bridges/prometheus v0.54.0 h1:WWL67oxtknNVMb70lJXxXruf8UyK/a9hmIE1XO3Uedg= -go.opentelemetry.io/contrib/bridges/prometheus v0.54.0/go.mod h1:LqNcnXmyULp8ertk4hUTVtSUvKXj4h1Mx7gUCSSr/q0= -go.opentelemetry.io/contrib/exporters/autoexport v0.54.0 h1:dTmcmVm4J54IRPGm5oVjLci1uYat4UDea84E2tyBaAk= -go.opentelemetry.io/contrib/exporters/autoexport v0.54.0/go.mod h1:zPp5Fwpq2Hc7xMtVttg6GhZMcfTESjVbY9ONw2o/Dc4= +go.opentelemetry.io/contrib/bridges/prometheus v0.57.0 h1:UW0+QyeyBVhn+COBec3nGhfnFe5lwB0ic1JBVjzhk0w= +go.opentelemetry.io/contrib/bridges/prometheus v0.57.0/go.mod h1:ppciCHRLsyCio54qbzQv0E4Jyth/fLWDTJYfvWpcSVk= +go.opentelemetry.io/contrib/exporters/autoexport v0.57.0 h1:jmTVJ86dP60C01K3slFQa2NQ/Aoi7zA+wy7vMOKD9H4= +go.opentelemetry.io/contrib/exporters/autoexport v0.57.0/go.mod h1:EJBheUMttD/lABFyLXhce47Wr6DPWYReCzaZiXadH7g= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 h1:r6I7RJCN86bpD/FQwedZ0vSixDpwuWREjW9oRMsmqDc= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q= go.opentelemetry.io/otel v1.33.0 h1:/FerN9bax5LoK51X/sI0SVYrjSE0/yUL7DpxW4K3FWw= go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRlNx4I8I= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.5.0 h1:4d++HQ+Ihdl+53zSjtsCUFDmNMju2FC9qFkUlTxPLqo= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.5.0/go.mod h1:mQX5dTO3Mh5ZF7bPKDkt5c/7C41u/SiDr9XgTpzXXn8= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 h1:k6fQVDQexDE+3jG2SfCQjnHS7OamcP73YMoxEVq5B6k= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0/go.mod h1:t4BrYLHU450Zo9fnydWlIuswB1bm7rM8havDpWOJeDo= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 h1:xvhQxJ/C9+RTnAj5DpTg7LSM1vbbMTiXt7e9hsfqHNw= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0/go.mod h1:Fcvs2Bz1jkDM+Wf5/ozBGmi3tQ/c9zPKLnsipnfhGAo= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 h1:dIIDULZJpgdiHz5tXrTgKIMLkus6jEFa7x5SOKcyR7E= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0/go.mod h1:jlRVBe7+Z1wyxFSUs48L6OBQZ5JwH2Hg/Vbl+t9rAgI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0 h1:nSiV3s7wiCam610XcLbYOmMfJxB9gO4uK3Xgv5gmTgg= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0/go.mod h1:hKn/e/Nmd19/x1gvIHwtOwVWM+VhuITSWip3JUDghj0= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 h1:WzNab7hOOLzdDF/EoWCt4glhrbMPVMOO5JYTmpz36Ls= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0/go.mod h1:hKvJwTzJdp90Vh7p6q/9PAOd55dI6WA6sWj62a/JvSs= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 h1:S+LdBGiQXtJdowoJoQPEtI52syEP/JYBUpjO49EQhV8= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0/go.mod h1:5KXybFvPGds3QinJWQT7pmXf+TN5YIa7CNYObWRkj50= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0 h1:j7ZSD+5yn+lo3sGV69nW04rRR0jhYnBwjuX3r0HvnK0= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0/go.mod h1:WXbYJTUaZXAbYd8lbgGuvih0yuCfOFC5RJoYnoLcGz8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0 h1:t/Qur3vKSkUCcDVaSumWF2PKHt85pc7fRvFuoVT8qFU= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0/go.mod h1:Rl61tySSdcOJWoEgYZVtmnKdA0GeKrSqkHC1t+91CH8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 h1:IJFEoHiytixx8cMiVAO+GmHR6Frwu+u5Ur8njpFO6Ac= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0/go.mod h1:3rHrKNtLIoS0oZwkY2vxi+oJcwFRWdtUyRII+so45p8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0 h1:9kV11HXBHZAvuPUZxmMWrH8hZn/6UnHX4K0mu36vNsU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0/go.mod h1:JyA0FHXe22E1NeNiHmVp7kFHglnexDQ7uRWDiiJ1hKQ= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0 h1:wpMfgF8E1rkrT1Z6meFh1NDtownE9Ii3n3X2GJYjsaU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0/go.mod h1:wAy0T/dUbs468uOlkT31xjvqQgEVXv58BRFWEgn5v/0= -go.opentelemetry.io/otel/exporters/prometheus v0.51.0 h1:G7uexXb/K3T+T9fNLCCKncweEtNEBMTO+46hKX5EdKw= -go.opentelemetry.io/otel/exporters/prometheus v0.51.0/go.mod h1:v0mFe5Kk7woIh938mrZBJBmENYquyA0IICrlYm4Y0t4= -go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.5.0 h1:ThVXnEsdwNcxdBO+r96ci1xbF+PgNjwlk457VNuJODo= -go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.5.0/go.mod h1:rHWcSmC4q2h3gje/yOq6sAOaq8+UHxN/Ru3BbmDXOfY= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0 h1:WDdP9acbMYjbKIyJUhTvtzj601sVJOqgWdUxSdR/Ysc= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0/go.mod h1:BLbf7zbNIONBLPwvFnwNHGj4zge8uTCM/UPIVW1Mq2I= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.29.0 h1:X3ZjNp36/WlkSYx0ul2jw4PtbNEDDeLskw3VPsrpYM0= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.29.0/go.mod h1:2uL/xnOXh0CHOBFCWXz5u1A4GXLiW+0IQIzVbeOEQ0U= -go.opentelemetry.io/otel/log v0.5.0 h1:x1Pr6Y3gnXgl1iFBwtGy1W/mnzENoK0w0ZoaeOI3i30= -go.opentelemetry.io/otel/log v0.5.0/go.mod h1:NU/ozXeGuOR5/mjCRXYbTC00NFJ3NYuraV/7O78F0rE= +go.opentelemetry.io/otel/exporters/prometheus v0.54.0 h1:rFwzp68QMgtzu9PgP3jm9XaMICI6TsofWWPcBDKwlsU= +go.opentelemetry.io/otel/exporters/prometheus v0.54.0/go.mod h1:QyjcV9qDP6VeK5qPyKETvNjmaaEc7+gqjh4SS0ZYzDU= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.8.0 h1:CHXNXwfKWfzS65yrlB2PVds1IBZcdsX8Vepy9of0iRU= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.8.0/go.mod h1:zKU4zUgKiaRxrdovSS2amdM5gOc59slmo/zJwGX+YBg= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.32.0 h1:SZmDnHcgp3zwlPBS2JX2urGYe/jBKEIT6ZedHRUyCz8= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.32.0/go.mod h1:fdWW0HtZJ7+jNpTKUR0GpMEDP69nR8YBJQxNiVCE3jk= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0 h1:cC2yDI3IQd0Udsux7Qmq8ToKAx1XCilTQECZ0KDZyTw= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0/go.mod h1:2PD5Ex6z8CFzDbTdOlwyNIUywRr1DN0ospafJM1wJ+s= +go.opentelemetry.io/otel/log v0.8.0 h1:egZ8vV5atrUWUbnSsHn6vB8R21G2wrKqNiDt3iWertk= +go.opentelemetry.io/otel/log v0.8.0/go.mod h1:M9qvDdUTRCopJcGRKg57+JSQ9LgLBrwwfC32epk5NX8= go.opentelemetry.io/otel/metric v1.33.0 h1:r+JOocAyeRVXD8lZpjdQjzMadVZp2M4WmQ+5WtEnklQ= go.opentelemetry.io/otel/metric v1.33.0/go.mod h1:L9+Fyctbp6HFTddIxClbQkjtubW6O9QS3Ann/M82u6M= go.opentelemetry.io/otel/sdk v1.33.0 h1:iax7M131HuAm9QkZotNHEfstof92xM+N8sr3uHXc2IM= go.opentelemetry.io/otel/sdk v1.33.0/go.mod h1:A1Q5oi7/9XaMlIWzPSxLRWOI8nG3FnzHJNbiENQuihM= -go.opentelemetry.io/otel/sdk/log v0.5.0 h1:A+9lSjlZGxkQOr7QSBJcuyyYBw79CufQ69saiJLey7o= -go.opentelemetry.io/otel/sdk/log v0.5.0/go.mod h1:zjxIW7sw1IHolZL2KlSAtrUi8JHttoeiQy43Yl3WuVQ= -go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc= -go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8= +go.opentelemetry.io/otel/sdk/log v0.8.0 h1:zg7GUYXqxk1jnGF/dTdLPrK06xJdrXgqgFLnI4Crxvs= +go.opentelemetry.io/otel/sdk/log v0.8.0/go.mod h1:50iXr0UVwQrYS45KbruFrEt4LvAdCaWWgIrsN3ZQggo= +go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU= +go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s= go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= @@ -594,8 +596,8 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= -golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20250228200357-dead58393ab7 h1:aWwlzYV971S4BXRS9AmqwDLAD85ouC6X+pocatKY58c= golang.org/x/exp v0.0.0-20250228200357-dead58393ab7/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk= @@ -629,8 +631,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= -golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= -golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= +golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= @@ -646,8 +648,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= -golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= +golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -669,8 +671,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -680,8 +682,8 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= -golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= -golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= +golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= 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.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= @@ -691,8 +693,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -707,8 +709,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= -golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= +golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU= +golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -731,8 +733,8 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A= -google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= +google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= +google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -763,8 +765,8 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= -helm.sh/helm/v3 v3.17.2 h1:agYQ5ew2jq5vdx2K7q5W44KyKQrnSubUMCQsjkiv3/o= -helm.sh/helm/v3 v3.17.2/go.mod h1:+uJKMH/UiMzZQOALR3XUf3BLIoczI2RKKD6bMhPh4G8= +helm.sh/helm/v3 v3.17.3 h1:3n5rW3D0ArjFl0p4/oWO8IbY/HKaNNwJtOQFdH2AZHg= +helm.sh/helm/v3 v3.17.3/go.mod h1:+uJKMH/UiMzZQOALR3XUf3BLIoczI2RKKD6bMhPh4G8= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8s.io/api v0.32.3 h1:Hw7KqxRusq+6QSplE3NYG4MBxZw1BZnq4aP4cJVINls= From bab5b215f902f1ec639c584c9cd0da3cf3e2e4a0 Mon Sep 17 00:00:00 2001 From: Brett Tofel Date: Fri, 11 Apr 2025 09:57:39 -0400 Subject: [PATCH 57/67] Add tests to verify kubernetes API errors vs regex Signed-off-by: Brett Tofel --- .../authorization/rbac_test.go | 144 ++++++++++++++++++ 1 file changed, 144 insertions(+) diff --git a/internal/operator-controller/authorization/rbac_test.go b/internal/operator-controller/authorization/rbac_test.go index e6b2062dc..8ee232d2a 100644 --- a/internal/operator-controller/authorization/rbac_test.go +++ b/internal/operator-controller/authorization/rbac_test.go @@ -2,9 +2,11 @@ package authorization import ( "context" + "errors" "strings" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" @@ -266,3 +268,145 @@ func TestPreAuthorize_CheckEscalation(t *testing.T) { require.Equal(t, []ScopedPolicyRules{}, missingRules) }) } + +// TestParseEscalationErrorForMissingRules Are tests with respect to https://github.com/kubernetes/api/blob/e8d4d542f6a9a16a694bfc8e3b8cd1557eecfc9d/rbac/v1/types.go#L49-L74 +// Goal is: prove the regex works as planned AND that if the error messages ever change we'll learn about it with these tests +func TestParseEscalationErrorForMissingRules(t *testing.T) { + testCases := []struct { + name string + inputError error + expectParseError bool // Whether the parser itself should fail (due to unexpected format) + expectedRules []rbacv1.PolicyRule + expectedErrorString string // The string of the error returned by the function + }{ + { + name: "One Missing Resource Rule", + inputError: errors.New(`user "test-user" (groups=test) is attempting to grant RBAC permissions not currently held: {APIGroups:["apps"], Resources:["deployments"], Verbs:["get"]}`), + expectedRules: []rbacv1.PolicyRule{ + {APIGroups: []string{"apps"}, Resources: []string{"deployments"}, Verbs: []string{"get"}}, + }, + expectedErrorString: "escalation check failed", + }, + { + name: "Multiple Missing Rules (Resource + NonResource)", + inputError: errors.New(`user "sa" (groups=["system:authenticated"]) is attempting to grant RBAC permissions not currently held: ` + + `{APIGroups:[""], Resources:["pods"], Verbs:["list", "watch"]} ` + // APIGroups:[""] becomes empty slice {} + `{NonResourceURLs:["/healthz"], Verbs:["get"]}`), + expectedRules: []rbacv1.PolicyRule{ + {APIGroups: []string{}, Resources: []string{"pods"}, Verbs: []string{"list", "watch"}}, + {NonResourceURLs: []string{"/healthz"}, Verbs: []string{"get"}}, + }, + expectedErrorString: "escalation check failed", + }, + { + name: "One Missing Rule with Resolution Errors", + inputError: errors.New(`user "test-admin" (groups=["system:masters"]) is attempting to grant RBAC permissions not currently held: ` + + `{APIGroups:["batch"], Resources:["jobs"], Verbs:["create"]} ; resolution errors: role "missing-role" not found`), + expectedRules: []rbacv1.PolicyRule{ + {APIGroups: []string{"batch"}, Resources: []string{"jobs"}, Verbs: []string{"create"}}, + }, + expectedErrorString: `escalation check failed; resolution errors: role "missing-role" not found`, + }, + { + name: "Multiple Missing Rules with Resolution Errors", + inputError: errors.New(`user "another-user" (groups=[]) is attempting to grant RBAC permissions not currently held: ` + + ` {APIGroups:[""], Resources:["secrets"], Verbs:["get"]} ` + // APIGroups:[""] becomes {} + ` {APIGroups:[""], Resources:["configmaps"], Verbs:["list"]} ; ` + // APIGroups:[""] becomes {} + ` resolution errors: clusterrole "missing-clusterrole" not found, role "other-missing" not found `), // Added spaces + expectedRules: []rbacv1.PolicyRule{ + {APIGroups: []string{}, Resources: []string{"secrets"}, Verbs: []string{"get"}}, + {APIGroups: []string{}, Resources: []string{"configmaps"}, Verbs: []string{"list"}}, + }, + expectedErrorString: `escalation check failed; resolution errors: clusterrole "missing-clusterrole" not found, role "other-missing" not found`, + }, + { + name: "Missing Rule (All Resource Fields)", + inputError: errors.New(`user "resource-name-user" (groups=test) is attempting to grant RBAC permissions not currently held: ` + + `{APIGroups:["extensions"], Resources:["ingresses"], ResourceNames:["my-ingress"], Verbs:["update","patch"]}`), + expectedRules: []rbacv1.PolicyRule{ + {APIGroups: []string{"extensions"}, Resources: []string{"ingresses"}, ResourceNames: []string{"my-ingress"}, Verbs: []string{"update", "patch"}}, + }, + expectedErrorString: "escalation check failed", + }, + { + name: "Missing Rule (No ResourceNames)", + inputError: errors.New(`user "no-res-name-user" (groups=test) is attempting to grant RBAC permissions not currently held: ` + + `{APIGroups:["networking.k8s.io"], Resources:["networkpolicies"], Verbs:["watch"]}`), + expectedRules: []rbacv1.PolicyRule{ + {APIGroups: []string{"networking.k8s.io"}, Resources: []string{"networkpolicies"}, Verbs: []string{"watch"}}, + }, + expectedErrorString: "escalation check failed", + }, + { + name: "Missing Rule (NonResourceURLs only)", + inputError: errors.New(`user "url-user" (groups=test) is attempting to grant RBAC permissions not currently held: ` + + `{NonResourceURLs:["/version", "/apis"], Verbs:["get"]}`), + expectedRules: []rbacv1.PolicyRule{ + {NonResourceURLs: []string{"/version", "/apis"}, Verbs: []string{"get"}}, + }, + expectedErrorString: "escalation check failed", + }, + { + name: "Unexpected Format", + inputError: errors.New("some completely different error message that doesn't match"), + expectParseError: true, // Expecting the parser itself to fail + }, + { + name: "Empty Permissions String", + inputError: errors.New(`user "empty-perms" (groups=test) is attempting to grant RBAC permissions not currently held: `), + expectedRules: []rbacv1.PolicyRule{}, // Should parse successfully but find no rules + expectedErrorString: "escalation check failed", + }, + { + name: "Malformed Permissions Block (No Verbs)", + inputError: errors.New(`user "malformed" (groups=test) is attempting to grant RBAC permissions not currently held: ` + + `{APIGroups:[""], Resources:["pods"]} {NonResourceURLs:["/ok"], Verbs:["get"]}`), + expectedRules: []rbacv1.PolicyRule{ // Only the valid block should be parsed + {NonResourceURLs: []string{"/ok"}, Verbs: []string{"get"}}, + }, + expectedErrorString: "escalation check failed", + }, + { + name: "Rule with Empty Strings in lists", + inputError: errors.New(`user "empty-strings" (groups=test) is attempting to grant RBAC permissions not currently held: ` + + `{APIGroups:["","apps"], Resources:["", "deployments"], Verbs:["get", ""]}`), + expectedRules: []rbacv1.PolicyRule{ + {APIGroups: []string{"apps"}, Resources: []string{"deployments"}, Verbs: []string{"get"}}, + }, + expectedErrorString: "escalation check failed", + }, + { + name: "Rule with Only Empty Verb", // Add test case for Verbs:[""] + inputError: errors.New(`user "empty-verb" (groups=test) is attempting to grant RBAC permissions not currently held: {APIGroups:[""], Resources:["pods"], Verbs:[""]}`), + expectedRules: []rbacv1.PolicyRule{}, // This rule should be skipped because verbs become empty + expectedErrorString: "escalation check failed", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + rules, err := parseEscalationErrorForMissingRules(tc.inputError) + + if tc.expectParseError { + // We expect a parsing error because the input format is wrong + require.Error(t, err, "Expected a parsing error, but got nil") + require.Contains(t, err.Error(), "failed to parse escalation error structure", "Expected error to contain parsing failure message: got %v", err) + // Check that the original error is wrapped + require.ErrorIs(t, err, tc.inputError, "Expected original error to be wrapped in parsing error") + require.Nil(t, rules, "Expected nil rules on parsing error") + } else { + // We expect parsing to succeed + // The function *should* return a non-nil error representing the escalation failure + // This error should NOT be the specific parsing error + require.Error(t, err, "Expected a non-nil error representing the escalation failure, but got nil") + require.NotContains(t, err.Error(), "failed to parse escalation error structure", "Got an unexpected parsing error message when success was expected: %v", err) + + // Check the returned error *message* matches the expected generic or resolution error message string + require.EqualError(t, err, tc.expectedErrorString, "Returned error message string does not match expected") + + // Check the parsed rules match + assert.ElementsMatch(t, tc.expectedRules, rules, "Parsed rules do not match expected rules") + } + }) + } +} From a167ef494634aace8dfa3cbde63949eb88867ced Mon Sep 17 00:00:00 2001 From: Joe Lanford Date: Fri, 11 Apr 2025 16:25:42 -0400 Subject: [PATCH 58/67] permissions preflight: refactoring escalation error parser Signed-off-by: Joe Lanford --- .../operator-controller/authorization/rbac.go | 112 +++++++++++------- .../authorization/rbac_test.go | 8 +- 2 files changed, 74 insertions(+), 46 deletions(-) diff --git a/internal/operator-controller/authorization/rbac.go b/internal/operator-controller/authorization/rbac.go index 9faa3d0b6..8ba60b183 100644 --- a/internal/operator-controller/authorization/rbac.go +++ b/internal/operator-controller/authorization/rbac.go @@ -95,11 +95,13 @@ func (a *rbacPreAuthorizer) PreAuthorize(ctx context.Context, ext *ocv1.ClusterE ec := a.escalationCheckerFor(dm) + var parseErrors []error for _, obj := range dm.rbacObjects() { if err := ec.checkEscalation(ctx, manifestManager, obj); err != nil { - missingEscalationRules, err := parseEscalationErrorForMissingRules(err) - missingRules[obj.GetNamespace()] = append(missingRules[obj.GetNamespace()], missingEscalationRules...) - preAuthEvaluationErrors = append(preAuthEvaluationErrors, err) + result, err := parseEscalationErrorForMissingRules(err) + missingRules[obj.GetNamespace()] = append(missingRules[obj.GetNamespace()], result.MissingRules...) + preAuthEvaluationErrors = append(preAuthEvaluationErrors, result.ResolutionErrors) + parseErrors = append(parseErrors, err) } } allMissingPolicyRules := make([]ScopedPolicyRules, 0, len(missingRules)) @@ -135,8 +137,15 @@ func (a *rbacPreAuthorizer) PreAuthorize(ctx context.Context, ext *ocv1.ClusterE return strings.Compare(a.Namespace, b.Namespace) }) + var errs []error + if parseErr := errors.Join(parseErrors...); parseErr != nil { + errs = append(errs, fmt.Errorf("failed to parse escalation check error strings: %v", parseErr)) + } if len(preAuthEvaluationErrors) > 0 { - return allMissingPolicyRules, fmt.Errorf("authorization evaluation errors: %w", errors.Join(preAuthEvaluationErrors...)) + errs = append(errs, fmt.Errorf("failed to resolve or evaluate permissions: %v", errors.Join(preAuthEvaluationErrors...))) + } + if len(errs) > 0 { + return allMissingPolicyRules, fmt.Errorf("missing rules may be incomplete: %w", errors.Join(errs...)) } return allMissingPolicyRules, nil } @@ -546,6 +555,17 @@ var fullAuthority = []rbacv1.PolicyRule{ {Verbs: []string{"*"}, NonResourceURLs: []string{"*"}}, } +var ( + errRegex = regexp.MustCompile(`(?s)^user ".*" \(groups=.*\) is attempting to grant RBAC permissions not currently held: (.*)(?:; resolution errors: (.*))?$`) + ruleRegex = regexp.MustCompile(`{([^}]+)}`) + itemRegex = regexp.MustCompile(`"[^"]*"`) +) + +type parseResult struct { + MissingRules []rbacv1.PolicyRule + ResolutionErrors error +} + // TODO: Investigate replacing this regex parsing with structured error handling once there are // // structured RBAC errors introduced by https://github.com/kubernetes/kubernetes/pull/130955. @@ -557,62 +577,62 @@ var fullAuthority = []rbacv1.PolicyRule{ // Note: If parsing is successful, the returned error is derived from the *input* error's // message, not an error encountered during the parsing process itself. If parsing fails due to an unexpected // error format, a distinct parsing error is returned. -func parseEscalationErrorForMissingRules(ecError error) ([]rbacv1.PolicyRule, error) { +func parseEscalationErrorForMissingRules(ecError error) (*parseResult, error) { // errRegex captures the missing permissions and optionally resolution errors from an escalation error message // Group 1: The list of missing permissions // Group 2: Optional resolution errors - errRegex := regexp.MustCompile(`(?s)^user ".*" \(groups=.*\) is attempting to grant RBAC permissions not currently held: (.*)(?:; resolution errors: (.*))?$`) - // permRegex extracts the details (APIGroups, Resources, Verbs) of individual permissions listed within the error message - permRegex := regexp.MustCompile(`{APIGroups:\[([^\]]*)\], Resources:\[([^\]]*)\], Verbs:\[([^\]]*)\]}`) - errString := ecError.Error() errMatches := errRegex.FindStringSubmatch(errString) // Use FindStringSubmatch for single match expected // Check if the main error message pattern was matched and captured the required groups // We expect at least 3 elements: full match, missing permissions, resolution errors (can be empty) - if len(errMatches) < 3 { + if len(errMatches) != 3 { // The error format doesn't match the expected pattern for escalation errors - return nil, fmt.Errorf("failed to parse escalation error: unexpected format: %w", ecError) + return &parseResult{}, fmt.Errorf("unexpected format of escalation check error string: %q", errString) } - missingPermissionsStr := errMatches[1] resolutionErrorsStr := errMatches[2] // Extract permissions using permRegex from the captured permissions string (Group 1) - permissions := []rbacv1.PolicyRule{} - permMatches := permRegex.FindAllStringSubmatch(missingPermissionsStr, -1) - for _, match := range permMatches { - // Ensure the match has the expected number of capture groups (full match + 3 groups) - if len(match) < 4 { - continue // Skip malformed permission strings - } - permissions = append(permissions, rbacv1.PolicyRule{ - APIGroups: splitAndTrim(match[1]), - Resources: splitAndTrim(match[2]), - Verbs: splitAndTrim(match[3]), + var ( + permissions []rbacv1.PolicyRule + parseErrors []error + ) + for _, rule := range ruleRegex.FindAllString(missingPermissionsStr, -1) { + items := mapSlice(strings.Split(rule[1:len(rule)-1], ","), func(in string) string { + return strings.TrimSpace(in) }) - } - - // Construct the error message to return. Include resolution errors if present - errMsg := "escalation check failed" - if resolutionErrorsStr != "" { - errMsg = fmt.Sprintf("%s; resolution errors: %s", errMsg, resolutionErrorsStr) + var pr rbacv1.PolicyRule + for _, item := range items { + field, valuesStr, ok := strings.Cut(item, ":") + if !ok { + parseErrors = append(parseErrors, fmt.Errorf("unexpected item %q: expected :[...]", item)) + continue + } + values := mapSlice(itemRegex.FindAllString(valuesStr, -1), func(in string) string { + return strings.Trim(in, `"`) + }) + switch field { + case "APIGroups": + pr.APIGroups = values + case "Resources": + pr.Resources = values + case "ResourceNames": + pr.ResourceNames = values + case "NonResourceURLs": + pr.NonResourceURLs = values + case "Verbs": + pr.Verbs = values + } + } + permissions = append(permissions, pr) } // Return the extracted permissions and the constructed error message - return permissions, errors.New(errMsg) -} - -func splitAndTrim(input string) []string { - parts := strings.Split(input, ",") - - output := make([]string, 0, len(parts)) - for _, part := range parts { - trimmed := strings.TrimSpace(part) - trimmed = strings.Trim(trimmed, `"`) - output = append(output, trimmed) - } - return output + return &parseResult{ + MissingRules: permissions, + ResolutionErrors: errors.New(resolutionErrorsStr), + }, errors.Join(parseErrors...) } func hasAggregationRule(clusterRole *rbacv1.ClusterRole) bool { @@ -622,6 +642,14 @@ func hasAggregationRule(clusterRole *rbacv1.ClusterRole) bool { return clusterRole.AggregationRule != nil && len(clusterRole.AggregationRule.ClusterRoleSelectors) > 0 } +func mapSlice[I, O any](in []I, f func(I) O) []O { + out := make([]O, len(in)) + for i := range in { + out[i] = f(in[i]) + } + return out +} + func toPtrSlice[V any](in []V) []*V { out := make([]*V, len(in)) for i := range in { diff --git a/internal/operator-controller/authorization/rbac_test.go b/internal/operator-controller/authorization/rbac_test.go index 8ee232d2a..3d08cb293 100644 --- a/internal/operator-controller/authorization/rbac_test.go +++ b/internal/operator-controller/authorization/rbac_test.go @@ -290,7 +290,7 @@ func TestParseEscalationErrorForMissingRules(t *testing.T) { { name: "Multiple Missing Rules (Resource + NonResource)", inputError: errors.New(`user "sa" (groups=["system:authenticated"]) is attempting to grant RBAC permissions not currently held: ` + - `{APIGroups:[""], Resources:["pods"], Verbs:["list", "watch"]} ` + // APIGroups:[""] becomes empty slice {} + `{APIGroups:[""], Resources:["pods"], Verbs:["list" "watch"]} ` + // APIGroups:[""] becomes empty slice {} `{NonResourceURLs:["/healthz"], Verbs:["get"]}`), expectedRules: []rbacv1.PolicyRule{ {APIGroups: []string{}, Resources: []string{"pods"}, Verbs: []string{"list", "watch"}}, @@ -322,7 +322,7 @@ func TestParseEscalationErrorForMissingRules(t *testing.T) { { name: "Missing Rule (All Resource Fields)", inputError: errors.New(`user "resource-name-user" (groups=test) is attempting to grant RBAC permissions not currently held: ` + - `{APIGroups:["extensions"], Resources:["ingresses"], ResourceNames:["my-ingress"], Verbs:["update","patch"]}`), + `{APIGroups:["extensions"], Resources:["ingresses"], ResourceNames:["my-ingress"], Verbs:["update" "patch"]}`), expectedRules: []rbacv1.PolicyRule{ {APIGroups: []string{"extensions"}, Resources: []string{"ingresses"}, ResourceNames: []string{"my-ingress"}, Verbs: []string{"update", "patch"}}, }, @@ -340,7 +340,7 @@ func TestParseEscalationErrorForMissingRules(t *testing.T) { { name: "Missing Rule (NonResourceURLs only)", inputError: errors.New(`user "url-user" (groups=test) is attempting to grant RBAC permissions not currently held: ` + - `{NonResourceURLs:["/version", "/apis"], Verbs:["get"]}`), + `{NonResourceURLs:["/version" "/apis"], Verbs:["get"]}`), expectedRules: []rbacv1.PolicyRule{ {NonResourceURLs: []string{"/version", "/apis"}, Verbs: []string{"get"}}, }, @@ -369,7 +369,7 @@ func TestParseEscalationErrorForMissingRules(t *testing.T) { { name: "Rule with Empty Strings in lists", inputError: errors.New(`user "empty-strings" (groups=test) is attempting to grant RBAC permissions not currently held: ` + - `{APIGroups:["","apps"], Resources:["", "deployments"], Verbs:["get", ""]}`), + `{APIGroups:["" "apps"], Resources:["" "deployments"], Verbs:["get" ""]}`), expectedRules: []rbacv1.PolicyRule{ {APIGroups: []string{"apps"}, Resources: []string{"deployments"}, Verbs: []string{"get"}}, }, From 014d7a4e805919b48f29c2411d85cc57bd753822 Mon Sep 17 00:00:00 2001 From: Joe Lanford Date: Fri, 11 Apr 2025 18:06:33 -0400 Subject: [PATCH 59/67] permission preflight: emit error when encountering unknown policy rule field Signed-off-by: Joe Lanford --- internal/operator-controller/authorization/rbac.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/operator-controller/authorization/rbac.go b/internal/operator-controller/authorization/rbac.go index 8ba60b183..dd3df6ff9 100644 --- a/internal/operator-controller/authorization/rbac.go +++ b/internal/operator-controller/authorization/rbac.go @@ -623,6 +623,9 @@ func parseEscalationErrorForMissingRules(ecError error) (*parseResult, error) { pr.NonResourceURLs = values case "Verbs": pr.Verbs = values + default: + parseErrors = append(parseErrors, fmt.Errorf("unexpected item %q: unknown field: %q", item, field)) + continue } } permissions = append(permissions, pr) From c06d4f260a46f2f2fed8c04b41ae7e64848487e2 Mon Sep 17 00:00:00 2001 From: Joe Lanford Date: Fri, 11 Apr 2025 19:33:25 -0400 Subject: [PATCH 60/67] permissions preflight: fixup escalation error parser and tests Signed-off-by: Joe Lanford --- .../operator-controller/authorization/rbac.go | 89 ++++---- .../authorization/rbac_test.go | 202 ++++++++++-------- 2 files changed, 162 insertions(+), 129 deletions(-) diff --git a/internal/operator-controller/authorization/rbac.go b/internal/operator-controller/authorization/rbac.go index dd3df6ff9..226b7b4f1 100644 --- a/internal/operator-controller/authorization/rbac.go +++ b/internal/operator-controller/authorization/rbac.go @@ -556,8 +556,8 @@ var fullAuthority = []rbacv1.PolicyRule{ } var ( - errRegex = regexp.MustCompile(`(?s)^user ".*" \(groups=.*\) is attempting to grant RBAC permissions not currently held: (.*)(?:; resolution errors: (.*))?$`) - ruleRegex = regexp.MustCompile(`{([^}]+)}`) + errRegex = regexp.MustCompile(`(?s)^user ".*" \(groups=.*\) is attempting to grant RBAC permissions not currently held:\n([^;]+)(?:; resolution errors: (.*))?$`) + ruleRegex = regexp.MustCompile(`{([^}]*)}`) itemRegex = regexp.MustCompile(`"[^"]*"`) ) @@ -578,6 +578,11 @@ type parseResult struct { // message, not an error encountered during the parsing process itself. If parsing fails due to an unexpected // error format, a distinct parsing error is returned. func parseEscalationErrorForMissingRules(ecError error) (*parseResult, error) { + var ( + result = &parseResult{} + parseErrors []error + ) + // errRegex captures the missing permissions and optionally resolution errors from an escalation error message // Group 1: The list of missing permissions // Group 2: Optional resolution errors @@ -591,51 +596,55 @@ func parseEscalationErrorForMissingRules(ecError error) (*parseResult, error) { return &parseResult{}, fmt.Errorf("unexpected format of escalation check error string: %q", errString) } missingPermissionsStr := errMatches[1] - resolutionErrorsStr := errMatches[2] + if resolutionErrorsStr := errMatches[2]; resolutionErrorsStr != "" { + result.ResolutionErrors = errors.New(resolutionErrorsStr) + } // Extract permissions using permRegex from the captured permissions string (Group 1) - var ( - permissions []rbacv1.PolicyRule - parseErrors []error - ) for _, rule := range ruleRegex.FindAllString(missingPermissionsStr, -1) { - items := mapSlice(strings.Split(rule[1:len(rule)-1], ","), func(in string) string { + pr, err := parseCompactRuleString(rule) + if err != nil { + parseErrors = append(parseErrors, err) + continue + } + result.MissingRules = append(result.MissingRules, *pr) + } + // Return the extracted permissions and the constructed error message + return result, errors.Join(parseErrors...) +} + +func parseCompactRuleString(rule string) (*rbacv1.PolicyRule, error) { + var fields []string + if ruleText := rule[1 : len(rule)-1]; ruleText != "" { + fields = mapSlice(strings.Split(rule[1:len(rule)-1], ","), func(in string) string { return strings.TrimSpace(in) }) - var pr rbacv1.PolicyRule - for _, item := range items { - field, valuesStr, ok := strings.Cut(item, ":") - if !ok { - parseErrors = append(parseErrors, fmt.Errorf("unexpected item %q: expected :[...]", item)) - continue - } - values := mapSlice(itemRegex.FindAllString(valuesStr, -1), func(in string) string { - return strings.Trim(in, `"`) - }) - switch field { - case "APIGroups": - pr.APIGroups = values - case "Resources": - pr.Resources = values - case "ResourceNames": - pr.ResourceNames = values - case "NonResourceURLs": - pr.NonResourceURLs = values - case "Verbs": - pr.Verbs = values - default: - parseErrors = append(parseErrors, fmt.Errorf("unexpected item %q: unknown field: %q", item, field)) - continue - } + } + var pr rbacv1.PolicyRule + for _, item := range fields { + field, valuesStr, ok := strings.Cut(item, ":") + if !ok { + return nil, fmt.Errorf("unexpected item %q: expected :[...]", item) + } + values := mapSlice(itemRegex.FindAllString(valuesStr, -1), func(in string) string { + return strings.Trim(in, `"`) + }) + switch field { + case "APIGroups": + pr.APIGroups = values + case "Resources": + pr.Resources = values + case "ResourceNames": + pr.ResourceNames = values + case "NonResourceURLs": + pr.NonResourceURLs = values + case "Verbs": + pr.Verbs = values + default: + return nil, fmt.Errorf("unexpected item %q: unknown field: %q", item, field) } - permissions = append(permissions, pr) } - - // Return the extracted permissions and the constructed error message - return &parseResult{ - MissingRules: permissions, - ResolutionErrors: errors.New(resolutionErrorsStr), - }, errors.Join(parseErrors...) + return &pr, nil } func hasAggregationRule(clusterRole *rbacv1.ClusterRole) bool { diff --git a/internal/operator-controller/authorization/rbac_test.go b/internal/operator-controller/authorization/rbac_test.go index 3d08cb293..9bbd5c9ce 100644 --- a/internal/operator-controller/authorization/rbac_test.go +++ b/internal/operator-controller/authorization/rbac_test.go @@ -6,7 +6,6 @@ import ( "strings" "testing" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" @@ -273,113 +272,156 @@ func TestPreAuthorize_CheckEscalation(t *testing.T) { // Goal is: prove the regex works as planned AND that if the error messages ever change we'll learn about it with these tests func TestParseEscalationErrorForMissingRules(t *testing.T) { testCases := []struct { - name string - inputError error - expectParseError bool // Whether the parser itself should fail (due to unexpected format) - expectedRules []rbacv1.PolicyRule - expectedErrorString string // The string of the error returned by the function + name string + inputError error + expectedResult *parseResult + expectError require.ErrorAssertionFunc }{ { - name: "One Missing Resource Rule", - inputError: errors.New(`user "test-user" (groups=test) is attempting to grant RBAC permissions not currently held: {APIGroups:["apps"], Resources:["deployments"], Verbs:["get"]}`), - expectedRules: []rbacv1.PolicyRule{ - {APIGroups: []string{"apps"}, Resources: []string{"deployments"}, Verbs: []string{"get"}}, + name: "One Missing Resource Rule", + inputError: errors.New(`user "test-user" (groups=test) is attempting to grant RBAC permissions not currently held: +{APIGroups:["apps"], Resources:["deployments"], Verbs:["get"]}`), + expectedResult: &parseResult{ + MissingRules: []rbacv1.PolicyRule{ + {APIGroups: []string{"apps"}, Resources: []string{"deployments"}, Verbs: []string{"get"}}, + }, }, - expectedErrorString: "escalation check failed", + expectError: require.NoError, }, { name: "Multiple Missing Rules (Resource + NonResource)", - inputError: errors.New(`user "sa" (groups=["system:authenticated"]) is attempting to grant RBAC permissions not currently held: ` + - `{APIGroups:[""], Resources:["pods"], Verbs:["list" "watch"]} ` + // APIGroups:[""] becomes empty slice {} - `{NonResourceURLs:["/healthz"], Verbs:["get"]}`), - expectedRules: []rbacv1.PolicyRule{ - {APIGroups: []string{}, Resources: []string{"pods"}, Verbs: []string{"list", "watch"}}, - {NonResourceURLs: []string{"/healthz"}, Verbs: []string{"get"}}, + inputError: errors.New(`user "sa" (groups=["system:authenticated"]) is attempting to grant RBAC permissions not currently held: +{APIGroups:[""], Resources:["pods"], Verbs:["list" "watch"]} +{NonResourceURLs:["/healthz"], Verbs:["get"]}`), + expectedResult: &parseResult{ + MissingRules: []rbacv1.PolicyRule{ + {APIGroups: []string{""}, Resources: []string{"pods"}, Verbs: []string{"list", "watch"}}, + {NonResourceURLs: []string{"/healthz"}, Verbs: []string{"get"}}, + }, }, - expectedErrorString: "escalation check failed", + expectError: require.NoError, }, { name: "One Missing Rule with Resolution Errors", - inputError: errors.New(`user "test-admin" (groups=["system:masters"]) is attempting to grant RBAC permissions not currently held: ` + - `{APIGroups:["batch"], Resources:["jobs"], Verbs:["create"]} ; resolution errors: role "missing-role" not found`), - expectedRules: []rbacv1.PolicyRule{ - {APIGroups: []string{"batch"}, Resources: []string{"jobs"}, Verbs: []string{"create"}}, + inputError: errors.New(`user "test-admin" (groups=["system:masters"]) is attempting to grant RBAC permissions not currently held: +{APIGroups:["batch"], Resources:["jobs"], Verbs:["create"]}; resolution errors: role "missing-role" not found`), + expectedResult: &parseResult{ + MissingRules: []rbacv1.PolicyRule{ + {APIGroups: []string{"batch"}, Resources: []string{"jobs"}, Verbs: []string{"create"}}, + }, + ResolutionErrors: errors.New(`role "missing-role" not found`), }, - expectedErrorString: `escalation check failed; resolution errors: role "missing-role" not found`, + expectError: require.NoError, }, { name: "Multiple Missing Rules with Resolution Errors", - inputError: errors.New(`user "another-user" (groups=[]) is attempting to grant RBAC permissions not currently held: ` + - ` {APIGroups:[""], Resources:["secrets"], Verbs:["get"]} ` + // APIGroups:[""] becomes {} - ` {APIGroups:[""], Resources:["configmaps"], Verbs:["list"]} ; ` + // APIGroups:[""] becomes {} - ` resolution errors: clusterrole "missing-clusterrole" not found, role "other-missing" not found `), // Added spaces - expectedRules: []rbacv1.PolicyRule{ - {APIGroups: []string{}, Resources: []string{"secrets"}, Verbs: []string{"get"}}, - {APIGroups: []string{}, Resources: []string{"configmaps"}, Verbs: []string{"list"}}, + inputError: errors.New(`user "another-user" (groups=[]) is attempting to grant RBAC permissions not currently held: +{APIGroups:[""], Resources:["secrets"], Verbs:["get"]} +{APIGroups:[""], Resources:["configmaps"], Verbs:["list"]}; resolution errors: clusterrole "missing-clusterrole" not found, role "other-missing" not found`), + expectedResult: &parseResult{ + MissingRules: []rbacv1.PolicyRule{ + {APIGroups: []string{""}, Resources: []string{"secrets"}, Verbs: []string{"get"}}, + {APIGroups: []string{""}, Resources: []string{"configmaps"}, Verbs: []string{"list"}}, + }, + ResolutionErrors: errors.New(`clusterrole "missing-clusterrole" not found, role "other-missing" not found`), }, - expectedErrorString: `escalation check failed; resolution errors: clusterrole "missing-clusterrole" not found, role "other-missing" not found`, + expectError: require.NoError, }, { name: "Missing Rule (All Resource Fields)", - inputError: errors.New(`user "resource-name-user" (groups=test) is attempting to grant RBAC permissions not currently held: ` + - `{APIGroups:["extensions"], Resources:["ingresses"], ResourceNames:["my-ingress"], Verbs:["update" "patch"]}`), - expectedRules: []rbacv1.PolicyRule{ - {APIGroups: []string{"extensions"}, Resources: []string{"ingresses"}, ResourceNames: []string{"my-ingress"}, Verbs: []string{"update", "patch"}}, + inputError: errors.New(`user "resource-name-user" (groups=test) is attempting to grant RBAC permissions not currently held: +{APIGroups:["extensions"], Resources:["ingresses"], ResourceNames:["my-ingress"], Verbs:["update" "patch"]}`), + expectedResult: &parseResult{ + MissingRules: []rbacv1.PolicyRule{ + {APIGroups: []string{"extensions"}, Resources: []string{"ingresses"}, ResourceNames: []string{"my-ingress"}, Verbs: []string{"update", "patch"}}, + }, }, - expectedErrorString: "escalation check failed", + expectError: require.NoError, }, { name: "Missing Rule (No ResourceNames)", - inputError: errors.New(`user "no-res-name-user" (groups=test) is attempting to grant RBAC permissions not currently held: ` + - `{APIGroups:["networking.k8s.io"], Resources:["networkpolicies"], Verbs:["watch"]}`), - expectedRules: []rbacv1.PolicyRule{ - {APIGroups: []string{"networking.k8s.io"}, Resources: []string{"networkpolicies"}, Verbs: []string{"watch"}}, + inputError: errors.New(`user "no-res-name-user" (groups=test) is attempting to grant RBAC permissions not currently held: +{APIGroups:["networking.k8s.io"], Resources:["networkpolicies"], Verbs:["watch"]}`), + expectedResult: &parseResult{ + MissingRules: []rbacv1.PolicyRule{ + {APIGroups: []string{"networking.k8s.io"}, Resources: []string{"networkpolicies"}, Verbs: []string{"watch"}}, + }, }, - expectedErrorString: "escalation check failed", + expectError: require.NoError, }, { name: "Missing Rule (NonResourceURLs only)", - inputError: errors.New(`user "url-user" (groups=test) is attempting to grant RBAC permissions not currently held: ` + - `{NonResourceURLs:["/version" "/apis"], Verbs:["get"]}`), - expectedRules: []rbacv1.PolicyRule{ - {NonResourceURLs: []string{"/version", "/apis"}, Verbs: []string{"get"}}, + inputError: errors.New(`user "url-user" (groups=test) is attempting to grant RBAC permissions not currently held: +{NonResourceURLs:["/version" "/apis"], Verbs:["get"]}`), + expectedResult: &parseResult{ + MissingRules: []rbacv1.PolicyRule{ + {NonResourceURLs: []string{"/version", "/apis"}, Verbs: []string{"get"}}, + }, }, - expectedErrorString: "escalation check failed", + expectError: require.NoError, }, { - name: "Unexpected Format", - inputError: errors.New("some completely different error message that doesn't match"), - expectParseError: true, // Expecting the parser itself to fail + name: "Unexpected Format", + inputError: errors.New("some completely different error message that doesn't match"), + expectedResult: &parseResult{}, + expectError: func(t require.TestingT, err error, i ...interface{}) { + require.ErrorContains(t, err, "unexpected format of escalation check error string") + }, }, { - name: "Empty Permissions String", - inputError: errors.New(`user "empty-perms" (groups=test) is attempting to grant RBAC permissions not currently held: `), - expectedRules: []rbacv1.PolicyRule{}, // Should parse successfully but find no rules - expectedErrorString: "escalation check failed", + name: "Empty Permissions String", + inputError: errors.New(`user "empty-perms" (groups=test) is attempting to grant RBAC permissions not currently held: +`), + expectedResult: &parseResult{}, + expectError: func(t require.TestingT, err error, i ...interface{}) { + require.ErrorContains(t, err, "unexpected format of escalation check error string") + }, }, { - name: "Malformed Permissions Block (No Verbs)", - inputError: errors.New(`user "malformed" (groups=test) is attempting to grant RBAC permissions not currently held: ` + - `{APIGroups:[""], Resources:["pods"]} {NonResourceURLs:["/ok"], Verbs:["get"]}`), - expectedRules: []rbacv1.PolicyRule{ // Only the valid block should be parsed - {NonResourceURLs: []string{"/ok"}, Verbs: []string{"get"}}, + name: "Rule with Empty Strings in lists", + inputError: errors.New(`user "empty-strings" (groups=test) is attempting to grant RBAC permissions not currently held: +{APIGroups:["" "apps"], Resources:["" "deployments"], Verbs:["get" ""]}`), + expectedResult: &parseResult{ + MissingRules: []rbacv1.PolicyRule{ + {APIGroups: []string{"", "apps"}, Resources: []string{"", "deployments"}, Verbs: []string{"get", ""}}, + }, }, - expectedErrorString: "escalation check failed", + expectError: require.NoError, }, { - name: "Rule with Empty Strings in lists", - inputError: errors.New(`user "empty-strings" (groups=test) is attempting to grant RBAC permissions not currently held: ` + - `{APIGroups:["" "apps"], Resources:["" "deployments"], Verbs:["get" ""]}`), - expectedRules: []rbacv1.PolicyRule{ - {APIGroups: []string{"apps"}, Resources: []string{"deployments"}, Verbs: []string{"get"}}, + name: "Rule with Only Empty Verb", + inputError: errors.New(`user "empty-verb" (groups=test) is attempting to grant RBAC permissions not currently held: +{APIGroups:[""], Resources:["pods"], Verbs:[""]}`), + expectedResult: &parseResult{ + MissingRules: []rbacv1.PolicyRule{ + {APIGroups: []string{""}, Resources: []string{"pods"}, Verbs: []string{""}}, + }, + }, + expectError: require.NoError, + }, + { + name: "Rule with no fields", + inputError: errors.New(`user "empty-verb" (groups=test) is attempting to grant RBAC permissions not currently held: +{}`), + expectedResult: &parseResult{ + MissingRules: []rbacv1.PolicyRule{{}}, }, - expectedErrorString: "escalation check failed", + expectError: require.NoError, }, { - name: "Rule with Only Empty Verb", // Add test case for Verbs:[""] - inputError: errors.New(`user "empty-verb" (groups=test) is attempting to grant RBAC permissions not currently held: {APIGroups:[""], Resources:["pods"], Verbs:[""]}`), - expectedRules: []rbacv1.PolicyRule{}, // This rule should be skipped because verbs become empty - expectedErrorString: "escalation check failed", + name: "Rule with unknown field", + inputError: errors.New(`user "empty-verb" (groups=test) is attempting to grant RBAC permissions not currently held: +{FooBar:["baz"]} +{APIGroups:[""], Resources:["secrets"], Verbs:["get"]} +`), + expectedResult: &parseResult{ + MissingRules: []rbacv1.PolicyRule{ + {APIGroups: []string{""}, Resources: []string{"secrets"}, Verbs: []string{"get"}}, + }, + }, + expectError: func(t require.TestingT, err error, i ...interface{}) { + require.ErrorContains(t, err, `unknown field: "FooBar"`) + }, }, } @@ -387,26 +429,8 @@ func TestParseEscalationErrorForMissingRules(t *testing.T) { t.Run(tc.name, func(t *testing.T) { rules, err := parseEscalationErrorForMissingRules(tc.inputError) - if tc.expectParseError { - // We expect a parsing error because the input format is wrong - require.Error(t, err, "Expected a parsing error, but got nil") - require.Contains(t, err.Error(), "failed to parse escalation error structure", "Expected error to contain parsing failure message: got %v", err) - // Check that the original error is wrapped - require.ErrorIs(t, err, tc.inputError, "Expected original error to be wrapped in parsing error") - require.Nil(t, rules, "Expected nil rules on parsing error") - } else { - // We expect parsing to succeed - // The function *should* return a non-nil error representing the escalation failure - // This error should NOT be the specific parsing error - require.Error(t, err, "Expected a non-nil error representing the escalation failure, but got nil") - require.NotContains(t, err.Error(), "failed to parse escalation error structure", "Got an unexpected parsing error message when success was expected: %v", err) - - // Check the returned error *message* matches the expected generic or resolution error message string - require.EqualError(t, err, tc.expectedErrorString, "Returned error message string does not match expected") - - // Check the parsed rules match - assert.ElementsMatch(t, tc.expectedRules, rules, "Parsed rules do not match expected rules") - } + tc.expectError(t, err) + require.Equal(t, tc.expectedResult, rules) }) } } From 2de215e6b67723385c11335cd4487262a7dcf0ed Mon Sep 17 00:00:00 2001 From: Joe Lanford Date: Fri, 11 Apr 2025 20:40:12 -0400 Subject: [PATCH 61/67] permissions preflight: add kubernetes compatibility tests, other small fixups Signed-off-by: Joe Lanford --- .../operator-controller/authorization/rbac.go | 2 +- .../authorization/rbac_test.go | 166 +++++++++++++++--- 2 files changed, 146 insertions(+), 22 deletions(-) diff --git a/internal/operator-controller/authorization/rbac.go b/internal/operator-controller/authorization/rbac.go index 226b7b4f1..779256942 100644 --- a/internal/operator-controller/authorization/rbac.go +++ b/internal/operator-controller/authorization/rbac.go @@ -616,7 +616,7 @@ func parseEscalationErrorForMissingRules(ecError error) (*parseResult, error) { func parseCompactRuleString(rule string) (*rbacv1.PolicyRule, error) { var fields []string if ruleText := rule[1 : len(rule)-1]; ruleText != "" { - fields = mapSlice(strings.Split(rule[1:len(rule)-1], ","), func(in string) string { + fields = mapSlice(strings.Split(ruleText, ","), func(in string) string { return strings.TrimSpace(in) }) } diff --git a/internal/operator-controller/authorization/rbac_test.go b/internal/operator-controller/authorization/rbac_test.go index 9bbd5c9ce..c081377ac 100644 --- a/internal/operator-controller/authorization/rbac_test.go +++ b/internal/operator-controller/authorization/rbac_test.go @@ -3,6 +3,7 @@ package authorization import ( "context" "errors" + "fmt" "strings" "testing" @@ -12,12 +13,13 @@ import ( "k8s.io/apimachinery/pkg/api/meta/testrestmapper" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - featuregatetesting "k8s.io/component-base/featuregate/testing" + "k8s.io/apiserver/pkg/authentication/user" + "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/kubernetes/pkg/registry/rbac/validation" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" ocv1 "github.com/operator-framework/operator-controller/api/v1" - "github.com/operator-framework/operator-controller/internal/operator-controller/features" ) var ( @@ -226,7 +228,6 @@ func setupFakeClient(role client.Object) client.Client { func TestPreAuthorize_Success(t *testing.T) { t.Run("preauthorize succeeds with no missing rbac rules", func(t *testing.T) { - featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.PreflightPermissions, true) fakeClient := setupFakeClient(privilegedClusterRole) preAuth := NewRBACPreAuthorizer(fakeClient) missingRules, err := preAuth.PreAuthorize(context.TODO(), &exampleClusterExtension, strings.NewReader(testManifest)) @@ -237,7 +238,6 @@ func TestPreAuthorize_Success(t *testing.T) { func TestPreAuthorize_Failure(t *testing.T) { t.Run("preauthorize fails with missing rbac rules", func(t *testing.T) { - featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.PreflightPermissions, true) fakeClient := setupFakeClient(limitedClusterRole) preAuth := NewRBACPreAuthorizer(fakeClient) missingRules, err := preAuth.PreAuthorize(context.TODO(), &exampleClusterExtension, strings.NewReader(testManifest)) @@ -248,7 +248,6 @@ func TestPreAuthorize_Failure(t *testing.T) { func TestPreAuthorizeMultiNamespace_Failure(t *testing.T) { t.Run("preauthorize fails with missing rbac rules in multiple namespaces", func(t *testing.T) { - featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.PreflightPermissions, true) fakeClient := setupFakeClient(limitedClusterRole) preAuth := NewRBACPreAuthorizer(fakeClient) missingRules, err := preAuth.PreAuthorize(context.TODO(), &exampleClusterExtension, strings.NewReader(testManifestMultiNamespace)) @@ -259,7 +258,6 @@ func TestPreAuthorizeMultiNamespace_Failure(t *testing.T) { func TestPreAuthorize_CheckEscalation(t *testing.T) { t.Run("preauthorize succeeds with no missing rbac rules", func(t *testing.T) { - featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.PreflightPermissions, true) fakeClient := setupFakeClient(escalatingClusterRole) preAuth := NewRBACPreAuthorizer(fakeClient) missingRules, err := preAuth.PreAuthorize(context.TODO(), &exampleClusterExtension, strings.NewReader(testManifest)) @@ -270,7 +268,7 @@ func TestPreAuthorize_CheckEscalation(t *testing.T) { // TestParseEscalationErrorForMissingRules Are tests with respect to https://github.com/kubernetes/api/blob/e8d4d542f6a9a16a694bfc8e3b8cd1557eecfc9d/rbac/v1/types.go#L49-L74 // Goal is: prove the regex works as planned AND that if the error messages ever change we'll learn about it with these tests -func TestParseEscalationErrorForMissingRules(t *testing.T) { +func TestParseEscalationErrorForMissingRules_ParsingLogic(t *testing.T) { testCases := []struct { name string inputError error @@ -279,7 +277,7 @@ func TestParseEscalationErrorForMissingRules(t *testing.T) { }{ { name: "One Missing Resource Rule", - inputError: errors.New(`user "test-user" (groups=test) is attempting to grant RBAC permissions not currently held: + inputError: errors.New(`user "test-user" (groups=["test"]) is attempting to grant RBAC permissions not currently held: {APIGroups:["apps"], Resources:["deployments"], Verbs:["get"]}`), expectedResult: &parseResult{ MissingRules: []rbacv1.PolicyRule{ @@ -304,12 +302,12 @@ func TestParseEscalationErrorForMissingRules(t *testing.T) { { name: "One Missing Rule with Resolution Errors", inputError: errors.New(`user "test-admin" (groups=["system:masters"]) is attempting to grant RBAC permissions not currently held: -{APIGroups:["batch"], Resources:["jobs"], Verbs:["create"]}; resolution errors: role "missing-role" not found`), +{APIGroups:["batch"], Resources:["jobs"], Verbs:["create"]}; resolution errors: [role "missing-role" not found]`), expectedResult: &parseResult{ MissingRules: []rbacv1.PolicyRule{ {APIGroups: []string{"batch"}, Resources: []string{"jobs"}, Verbs: []string{"create"}}, }, - ResolutionErrors: errors.New(`role "missing-role" not found`), + ResolutionErrors: errors.New(`[role "missing-role" not found]`), }, expectError: require.NoError, }, @@ -317,19 +315,19 @@ func TestParseEscalationErrorForMissingRules(t *testing.T) { name: "Multiple Missing Rules with Resolution Errors", inputError: errors.New(`user "another-user" (groups=[]) is attempting to grant RBAC permissions not currently held: {APIGroups:[""], Resources:["secrets"], Verbs:["get"]} -{APIGroups:[""], Resources:["configmaps"], Verbs:["list"]}; resolution errors: clusterrole "missing-clusterrole" not found, role "other-missing" not found`), +{APIGroups:[""], Resources:["configmaps"], Verbs:["list"]}; resolution errors: [clusterrole "missing-clusterrole" not found, role "other-missing" not found]`), expectedResult: &parseResult{ MissingRules: []rbacv1.PolicyRule{ {APIGroups: []string{""}, Resources: []string{"secrets"}, Verbs: []string{"get"}}, {APIGroups: []string{""}, Resources: []string{"configmaps"}, Verbs: []string{"list"}}, }, - ResolutionErrors: errors.New(`clusterrole "missing-clusterrole" not found, role "other-missing" not found`), + ResolutionErrors: errors.New(`[clusterrole "missing-clusterrole" not found, role "other-missing" not found]`), }, expectError: require.NoError, }, { name: "Missing Rule (All Resource Fields)", - inputError: errors.New(`user "resource-name-user" (groups=test) is attempting to grant RBAC permissions not currently held: + inputError: errors.New(`user "resource-name-user" (groups=["test"]) is attempting to grant RBAC permissions not currently held: {APIGroups:["extensions"], Resources:["ingresses"], ResourceNames:["my-ingress"], Verbs:["update" "patch"]}`), expectedResult: &parseResult{ MissingRules: []rbacv1.PolicyRule{ @@ -340,7 +338,7 @@ func TestParseEscalationErrorForMissingRules(t *testing.T) { }, { name: "Missing Rule (No ResourceNames)", - inputError: errors.New(`user "no-res-name-user" (groups=test) is attempting to grant RBAC permissions not currently held: + inputError: errors.New(`user "no-res-name-user" (groups=["test"]) is attempting to grant RBAC permissions not currently held: {APIGroups:["networking.k8s.io"], Resources:["networkpolicies"], Verbs:["watch"]}`), expectedResult: &parseResult{ MissingRules: []rbacv1.PolicyRule{ @@ -351,7 +349,7 @@ func TestParseEscalationErrorForMissingRules(t *testing.T) { }, { name: "Missing Rule (NonResourceURLs only)", - inputError: errors.New(`user "url-user" (groups=test) is attempting to grant RBAC permissions not currently held: + inputError: errors.New(`user "url-user" (groups=["test"]) is attempting to grant RBAC permissions not currently held: {NonResourceURLs:["/version" "/apis"], Verbs:["get"]}`), expectedResult: &parseResult{ MissingRules: []rbacv1.PolicyRule{ @@ -370,7 +368,7 @@ func TestParseEscalationErrorForMissingRules(t *testing.T) { }, { name: "Empty Permissions String", - inputError: errors.New(`user "empty-perms" (groups=test) is attempting to grant RBAC permissions not currently held: + inputError: errors.New(`user "empty-perms" (groups=["test"]) is attempting to grant RBAC permissions not currently held: `), expectedResult: &parseResult{}, expectError: func(t require.TestingT, err error, i ...interface{}) { @@ -379,7 +377,7 @@ func TestParseEscalationErrorForMissingRules(t *testing.T) { }, { name: "Rule with Empty Strings in lists", - inputError: errors.New(`user "empty-strings" (groups=test) is attempting to grant RBAC permissions not currently held: + inputError: errors.New(`user "empty-strings" (groups=["test"]) is attempting to grant RBAC permissions not currently held: {APIGroups:["" "apps"], Resources:["" "deployments"], Verbs:["get" ""]}`), expectedResult: &parseResult{ MissingRules: []rbacv1.PolicyRule{ @@ -390,7 +388,7 @@ func TestParseEscalationErrorForMissingRules(t *testing.T) { }, { name: "Rule with Only Empty Verb", - inputError: errors.New(`user "empty-verb" (groups=test) is attempting to grant RBAC permissions not currently held: + inputError: errors.New(`user "empty-verb" (groups=["test"]) is attempting to grant RBAC permissions not currently held: {APIGroups:[""], Resources:["pods"], Verbs:[""]}`), expectedResult: &parseResult{ MissingRules: []rbacv1.PolicyRule{ @@ -401,16 +399,26 @@ func TestParseEscalationErrorForMissingRules(t *testing.T) { }, { name: "Rule with no fields", - inputError: errors.New(`user "empty-verb" (groups=test) is attempting to grant RBAC permissions not currently held: + inputError: errors.New(`user "empty-verb" (groups=["test"]) is attempting to grant RBAC permissions not currently held: {}`), expectedResult: &parseResult{ MissingRules: []rbacv1.PolicyRule{{}}, }, expectError: require.NoError, }, + { + name: "Rule with no colon separator", + inputError: errors.New(`user "empty-verb" (groups=["test"]) is attempting to grant RBAC permissions not currently held: +{APIGroups:[""], Resources, Verbs:["get"]} +`), + expectedResult: &parseResult{}, + expectError: func(t require.TestingT, err error, i ...interface{}) { + require.ErrorContains(t, err, `unexpected item "Resources": expected :[...]`) + }, + }, { name: "Rule with unknown field", - inputError: errors.New(`user "empty-verb" (groups=test) is attempting to grant RBAC permissions not currently held: + inputError: errors.New(`user "empty-verb" (groups=["test"]) is attempting to grant RBAC permissions not currently held: {FooBar:["baz"]} {APIGroups:[""], Resources:["secrets"], Verbs:["get"]} `), @@ -428,9 +436,125 @@ func TestParseEscalationErrorForMissingRules(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { rules, err := parseEscalationErrorForMissingRules(tc.inputError) - tc.expectError(t, err) require.Equal(t, tc.expectedResult, rules) }) } } + +func TestParseEscalationErrorForMissingRules_KubernetesCompatibility(t *testing.T) { + testCases := []struct { + name string + ruleResolver validation.AuthorizationRuleResolver + wantRules []rbacv1.PolicyRule + expectedErrorString string + expectedResult *parseResult + }{ + { + name: "missing rules", + ruleResolver: mockRulesResolver{ + rules: []rbacv1.PolicyRule{}, + err: nil, + }, + wantRules: []rbacv1.PolicyRule{ + {APIGroups: []string{""}, Resources: []string{"secrets"}, Verbs: []string{"get"}, ResourceNames: []string{"test-secret"}}, + {APIGroups: []string{""}, Resources: []string{"configmaps"}, Verbs: []string{"get", "list", "watch"}}, + {APIGroups: []string{"apps"}, Resources: []string{"deployments", "replicasets"}, Verbs: []string{"create", "update", "patch", "delete"}}, + {NonResourceURLs: []string{"/healthz", "/livez"}, Verbs: []string{"get", "post"}}, + }, + expectedErrorString: `user "user" (groups=["a" "b"]) is attempting to grant RBAC permissions not currently held: +{APIGroups:[""], Resources:["configmaps"], Verbs:["get" "list" "watch"]} +{APIGroups:[""], Resources:["secrets"], ResourceNames:["test-secret"], Verbs:["get"]} +{APIGroups:["apps"], Resources:["deployments"], Verbs:["create" "update" "patch" "delete"]} +{APIGroups:["apps"], Resources:["replicasets"], Verbs:["create" "update" "patch" "delete"]} +{NonResourceURLs:["/healthz"], Verbs:["get"]} +{NonResourceURLs:["/healthz"], Verbs:["post"]} +{NonResourceURLs:["/livez"], Verbs:["get"]} +{NonResourceURLs:["/livez"], Verbs:["post"]}`, + expectedResult: &parseResult{ + MissingRules: []rbacv1.PolicyRule{ + {APIGroups: []string{""}, Resources: []string{"configmaps"}, Verbs: []string{"get", "list", "watch"}}, + {APIGroups: []string{""}, Resources: []string{"secrets"}, Verbs: []string{"get"}, ResourceNames: []string{"test-secret"}}, + {APIGroups: []string{"apps"}, Resources: []string{"deployments"}, Verbs: []string{"create", "update", "patch", "delete"}}, + {APIGroups: []string{"apps"}, Resources: []string{"replicasets"}, Verbs: []string{"create", "update", "patch", "delete"}}, + {NonResourceURLs: []string{"/healthz"}, Verbs: []string{"get"}}, + {NonResourceURLs: []string{"/healthz"}, Verbs: []string{"post"}}, + {NonResourceURLs: []string{"/livez"}, Verbs: []string{"get"}}, + {NonResourceURLs: []string{"/livez"}, Verbs: []string{"post"}}, + }, + }, + }, + { + name: "resolution failure", + ruleResolver: mockRulesResolver{ + rules: []rbacv1.PolicyRule{}, + err: errors.New("resolution error"), + }, + wantRules: []rbacv1.PolicyRule{ + {APIGroups: []string{""}, Resources: []string{"secrets"}, Verbs: []string{"get"}, ResourceNames: []string{"test-secret"}}, + {APIGroups: []string{""}, Resources: []string{"configmaps"}, Verbs: []string{"get", "list", "watch"}}, + {APIGroups: []string{"apps"}, Resources: []string{"deployments", "replicasets"}, Verbs: []string{"create", "update", "patch", "delete"}}, + {NonResourceURLs: []string{"/healthz", "/livez"}, Verbs: []string{"get", "post"}}, + }, + expectedErrorString: `user "user" (groups=["a" "b"]) is attempting to grant RBAC permissions not currently held: +{APIGroups:[""], Resources:["configmaps"], Verbs:["get" "list" "watch"]} +{APIGroups:[""], Resources:["secrets"], ResourceNames:["test-secret"], Verbs:["get"]} +{APIGroups:["apps"], Resources:["deployments"], Verbs:["create" "update" "patch" "delete"]} +{APIGroups:["apps"], Resources:["replicasets"], Verbs:["create" "update" "patch" "delete"]} +{NonResourceURLs:["/healthz"], Verbs:["get"]} +{NonResourceURLs:["/healthz"], Verbs:["post"]} +{NonResourceURLs:["/livez"], Verbs:["get"]} +{NonResourceURLs:["/livez"], Verbs:["post"]}; resolution errors: [resolution error]`, + expectedResult: &parseResult{ + MissingRules: []rbacv1.PolicyRule{ + {APIGroups: []string{""}, Resources: []string{"configmaps"}, Verbs: []string{"get", "list", "watch"}}, + {APIGroups: []string{""}, Resources: []string{"secrets"}, Verbs: []string{"get"}, ResourceNames: []string{"test-secret"}}, + {APIGroups: []string{"apps"}, Resources: []string{"deployments"}, Verbs: []string{"create", "update", "patch", "delete"}}, + {APIGroups: []string{"apps"}, Resources: []string{"replicasets"}, Verbs: []string{"create", "update", "patch", "delete"}}, + {NonResourceURLs: []string{"/healthz"}, Verbs: []string{"get"}}, + {NonResourceURLs: []string{"/healthz"}, Verbs: []string{"post"}}, + {NonResourceURLs: []string{"/livez"}, Verbs: []string{"get"}}, + {NonResourceURLs: []string{"/livez"}, Verbs: []string{"post"}}, + }, + ResolutionErrors: errors.New("[resolution error]"), + }, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ctx := request.WithUser(request.WithNamespace(context.Background(), "namespace"), &user.DefaultInfo{ + Name: "user", + Groups: []string{"a", "b"}, + }) + + // Let's actually call the upstream function that generates and returns the + // error message that we are attempting to parse correctly. The hope is that + // these tests will start failing if we bump to a new version of kubernetes + // that causes our parsing logic to be incorrect. + err := validation.ConfirmNoEscalation(ctx, tc.ruleResolver, tc.wantRules) + require.Error(t, err) + require.Equal(t, tc.expectedErrorString, err.Error()) + + res, err := parseEscalationErrorForMissingRules(err) + require.NoError(t, err) + require.Equal(t, tc.expectedResult, res) + }) + } +} + +type mockRulesResolver struct { + rules []rbacv1.PolicyRule + err error +} + +func (m mockRulesResolver) GetRoleReferenceRules(ctx context.Context, roleRef rbacv1.RoleRef, namespace string) ([]rbacv1.PolicyRule, error) { + panic("unimplemented") +} + +func (m mockRulesResolver) RulesFor(ctx context.Context, user user.Info, namespace string) ([]rbacv1.PolicyRule, error) { + return m.rules, m.err +} + +func (m mockRulesResolver) VisitRulesFor(ctx context.Context, user user.Info, namespace string, visitor func(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool) { + panic("unimplemented") +} From 7369824d2a8d8c43f5476973488b3f46e6357e55 Mon Sep 17 00:00:00 2001 From: Joe Lanford Date: Fri, 11 Apr 2025 21:06:07 -0400 Subject: [PATCH 62/67] preflight permissions: removing clusterextensions/finalizer patch requirement The clusterextensions/finalizer requirement comes from the desire to support clusters where OwnerReferencesPermissionEnforcement plugin is enabled. This plugin requires "update", but not "patch" for the clusterextensions/finalizers permission. See: https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#ownerreferencespermissionenforcement Signed-off-by: Joe Lanford --- internal/operator-controller/authorization/rbac.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/operator-controller/authorization/rbac.go b/internal/operator-controller/authorization/rbac.go index 779256942..f841c6561 100644 --- a/internal/operator-controller/authorization/rbac.go +++ b/internal/operator-controller/authorization/rbac.go @@ -364,7 +364,7 @@ func (dm *decodedManifest) asAuthorizationAttributesRecordsForUser(manifestManag }) } - for _, verb := range []string{"update", "patch"} { + for _, verb := range []string{"update"} { attributeRecords = append(attributeRecords, authorizer.AttributesRecord{ User: manifestManager, Name: ext.Name, From ac29e098ce2ba34b4aaad8fbf55b4e3831a19dee Mon Sep 17 00:00:00 2001 From: Tayler Geiger Date: Mon, 14 Apr 2025 17:02:06 -0500 Subject: [PATCH 63/67] Addressing latest round of PR feedback Signed-off-by: Tayler Geiger --- .golangci.yaml | 4 -- codecov.yml | 2 - .../operator-controller/applier/helm_test.go | 53 +++++++++++++++---- .../operator-controller/features/features.go | 6 ++- 4 files changed, 48 insertions(+), 17 deletions(-) diff --git a/.golangci.yaml b/.golangci.yaml index cf46e72fd..7f64bc040 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -16,10 +16,6 @@ run: # Default timeout is 1m, up to give more room timeout: 4m -issues: - exclude-dirs: - - internal/operator-controller/authorization/internal/kubernetes - linters: enable: - asciicheck diff --git a/codecov.yml b/codecov.yml index 5370b84e1..7ea9929a5 100644 --- a/codecov.yml +++ b/codecov.yml @@ -10,5 +10,3 @@ coverage: - "cmd/" - "internal/" -ignore: - - "internal/operator-controller/authorization/internal/kubernetes" diff --git a/internal/operator-controller/applier/helm_test.go b/internal/operator-controller/applier/helm_test.go index bbc20955f..1fa1a0ea6 100644 --- a/internal/operator-controller/applier/helm_test.go +++ b/internal/operator-controller/applier/helm_test.go @@ -17,7 +17,6 @@ import ( "helm.sh/helm/v3/pkg/storage/driver" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - featuregatetesting "k8s.io/component-base/featuregate/testing" "sigs.k8s.io/controller-runtime/pkg/client" helmclient "github.com/operator-framework/helm-operator-plugins/pkg/client" @@ -25,7 +24,6 @@ import ( ocv1 "github.com/operator-framework/operator-controller/api/v1" "github.com/operator-framework/operator-controller/internal/operator-controller/applier" "github.com/operator-framework/operator-controller/internal/operator-controller/authorization" - "github.com/operator-framework/operator-controller/internal/operator-controller/features" "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/convert" ) @@ -45,6 +43,17 @@ func (p *noOpPreAuthorizer) PreAuthorize( return nil, nil } +type errorPreAuthorizer struct{} + +func (p *errorPreAuthorizer) PreAuthorize( + ctx context.Context, + ext *ocv1.ClusterExtension, + manifestReader io.Reader, +) ([]authorization.ScopedPolicyRules, error) { + // Always returns no missing rules and an error + return nil, errors.New("problem running preauthorization") +} + func (mp *mockPreflight) Install(context.Context, *release.Release) error { return mp.installErr } @@ -270,7 +279,6 @@ func TestApply_Installation(t *testing.T) { } func TestApply_InstallationWithPreflightPermissionsEnabled(t *testing.T) { - featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.PreflightPermissions, true) t.Run("fails during dry-run installation", func(t *testing.T) { mockAcg := &mockActionGetter{ @@ -313,10 +321,9 @@ func TestApply_InstallationWithPreflightPermissionsEnabled(t *testing.T) { require.Nil(t, objs) }) - t.Run("fails during installation", func(t *testing.T) { + t.Run("fails during installation because of missing RBAC rules", func(t *testing.T) { mockAcg := &mockActionGetter{ getClientErr: driver.ErrReleaseNotFound, - installErr: errors.New("failed installing chart"), desiredRel: &release.Release{ Info: &release.Info{Status: release.StatusDeployed}, Manifest: validManifest, @@ -324,7 +331,7 @@ func TestApply_InstallationWithPreflightPermissionsEnabled(t *testing.T) { } helmApplier := applier.Helm{ ActionClientGetter: mockAcg, - PreAuthorizer: &noOpPreAuthorizer{}, + PreAuthorizer: &errorPreAuthorizer{}, BundleToHelmChartFn: convert.RegistryV1ToHelmChart, } // Use a ClusterExtension with valid Spec fields. @@ -338,8 +345,37 @@ func TestApply_InstallationWithPreflightPermissionsEnabled(t *testing.T) { } objs, state, err := helmApplier.Apply(context.TODO(), validFS, validCE, testObjectLabels, testStorageLabels) require.Error(t, err) - require.ErrorContains(t, err, "installing chart") - require.Equal(t, applier.StateNeedsInstall, state) + require.ErrorContains(t, err, "problem running preauthorization") + require.Equal(t, "", state) + require.Nil(t, objs) + }) + + t.Run("fails during installation because of pre-authorization failure", func(t *testing.T) { + mockAcg := &mockActionGetter{ + getClientErr: driver.ErrReleaseNotFound, + desiredRel: &release.Release{ + Info: &release.Info{Status: release.StatusDeployed}, + Manifest: validManifest, + }, + } + helmApplier := applier.Helm{ + ActionClientGetter: mockAcg, + PreAuthorizer: &errorPreAuthorizer{}, + BundleToHelmChartFn: convert.RegistryV1ToHelmChart, + } + // Use a ClusterExtension with valid Spec fields. + validCE := &ocv1.ClusterExtension{ + Spec: ocv1.ClusterExtensionSpec{ + Namespace: "default", + ServiceAccount: ocv1.ServiceAccountReference{ + Name: "default", + }, + }, + } + objs, state, err := helmApplier.Apply(context.TODO(), validFS, validCE, testObjectLabels, testStorageLabels) + require.Error(t, err) + require.ErrorContains(t, err, "problem running preauthorization") + require.Equal(t, "", state) require.Nil(t, objs) }) @@ -488,7 +524,6 @@ func TestApply_Upgrade(t *testing.T) { } func TestApply_InstallationWithSingleOwnNamespaceInstallSupportEnabled(t *testing.T) { - featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.SingleOwnNamespaceInstallSupport, true) t.Run("generates bundle resources using the configured watch namespace", func(t *testing.T) { var expectedWatchNamespace = "watch-namespace" diff --git a/internal/operator-controller/features/features.go b/internal/operator-controller/features/features.go index 311f73e7b..37d8a49ce 100644 --- a/internal/operator-controller/features/features.go +++ b/internal/operator-controller/features/features.go @@ -1,6 +1,7 @@ package features import ( + "fmt" "sort" "github.com/go-logr/logr" @@ -51,8 +52,9 @@ func LogFeatureGateStates(log logr.Logger, fg featuregate.FeatureGate) { return string(featureKeys[i]) < string(featureKeys[j]) // Sort by string representation }) - log.Info("Feature Gates Status:") + featurePairs := make([]string, 0, len(featureKeys)) for _, feature := range featureKeys { - log.Info(" ", "feature", string(feature), "enabled", fg.Enabled(feature)) + featurePairs = append(featurePairs, string(feature), fmt.Sprintf("%v", fg.Enabled(feature))) } + log.Info("feature gate status", featurePairs) } From 9ab708e0d4a8b3ee2430e80aff44252cac3abd1b Mon Sep 17 00:00:00 2001 From: Brett Tofel Date: Tue, 15 Apr 2025 09:08:27 -0400 Subject: [PATCH 64/67] Fix linting errors Signed-off-by: Brett Tofel --- internal/operator-controller/applier/helm_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/internal/operator-controller/applier/helm_test.go b/internal/operator-controller/applier/helm_test.go index 1fa1a0ea6..3329e27dc 100644 --- a/internal/operator-controller/applier/helm_test.go +++ b/internal/operator-controller/applier/helm_test.go @@ -279,7 +279,6 @@ func TestApply_Installation(t *testing.T) { } func TestApply_InstallationWithPreflightPermissionsEnabled(t *testing.T) { - t.Run("fails during dry-run installation", func(t *testing.T) { mockAcg := &mockActionGetter{ getClientErr: driver.ErrReleaseNotFound, @@ -524,7 +523,6 @@ func TestApply_Upgrade(t *testing.T) { } func TestApply_InstallationWithSingleOwnNamespaceInstallSupportEnabled(t *testing.T) { - t.Run("generates bundle resources using the configured watch namespace", func(t *testing.T) { var expectedWatchNamespace = "watch-namespace" From 9414dce522f3f637a18f4206b76ca74c467ab022 Mon Sep 17 00:00:00 2001 From: Brett Tofel Date: Tue, 15 Apr 2025 10:06:30 -0400 Subject: [PATCH 65/67] SingleOwnNSInstallSupport feature gate reset Signed-off-by: Brett Tofel --- internal/operator-controller/applier/helm_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/operator-controller/applier/helm_test.go b/internal/operator-controller/applier/helm_test.go index 3329e27dc..1a607f47a 100644 --- a/internal/operator-controller/applier/helm_test.go +++ b/internal/operator-controller/applier/helm_test.go @@ -17,6 +17,7 @@ import ( "helm.sh/helm/v3/pkg/storage/driver" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + featuregatetesting "k8s.io/component-base/featuregate/testing" "sigs.k8s.io/controller-runtime/pkg/client" helmclient "github.com/operator-framework/helm-operator-plugins/pkg/client" @@ -24,6 +25,7 @@ import ( ocv1 "github.com/operator-framework/operator-controller/api/v1" "github.com/operator-framework/operator-controller/internal/operator-controller/applier" "github.com/operator-framework/operator-controller/internal/operator-controller/authorization" + "github.com/operator-framework/operator-controller/internal/operator-controller/features" "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/convert" ) @@ -523,6 +525,7 @@ func TestApply_Upgrade(t *testing.T) { } func TestApply_InstallationWithSingleOwnNamespaceInstallSupportEnabled(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.SingleOwnNamespaceInstallSupport, true) t.Run("generates bundle resources using the configured watch namespace", func(t *testing.T) { var expectedWatchNamespace = "watch-namespace" From c2d1a7e823eaf92de5762c0453d40211c47019e1 Mon Sep 17 00:00:00 2001 From: Tayler Geiger Date: Tue, 15 Apr 2025 10:29:26 -0500 Subject: [PATCH 66/67] Fix feature gate logging unhashable hash problem Signed-off-by: Tayler Geiger --- internal/operator-controller/features/features.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/internal/operator-controller/features/features.go b/internal/operator-controller/features/features.go index 37d8a49ce..41645f62c 100644 --- a/internal/operator-controller/features/features.go +++ b/internal/operator-controller/features/features.go @@ -1,7 +1,6 @@ package features import ( - "fmt" "sort" "github.com/go-logr/logr" @@ -52,9 +51,9 @@ func LogFeatureGateStates(log logr.Logger, fg featuregate.FeatureGate) { return string(featureKeys[i]) < string(featureKeys[j]) // Sort by string representation }) - featurePairs := make([]string, 0, len(featureKeys)) + featurePairs := make([]interface{}, 0, len(featureKeys)) for _, feature := range featureKeys { - featurePairs = append(featurePairs, string(feature), fmt.Sprintf("%v", fg.Enabled(feature))) + featurePairs = append(featurePairs, feature, fg.Enabled(feature)) } - log.Info("feature gate status", featurePairs) + log.Info("feature gate status", featurePairs...) } From 8200b976e9d1c7e6a1fdb86d1b81c4190999b3bf Mon Sep 17 00:00:00 2001 From: Tayler Geiger Date: Tue, 15 Apr 2025 12:19:05 -0500 Subject: [PATCH 67/67] Remove duplicate test case Signed-off-by: Tayler Geiger --- .../operator-controller/applier/helm_test.go | 29 ------------------- 1 file changed, 29 deletions(-) diff --git a/internal/operator-controller/applier/helm_test.go b/internal/operator-controller/applier/helm_test.go index 1a607f47a..b46991206 100644 --- a/internal/operator-controller/applier/helm_test.go +++ b/internal/operator-controller/applier/helm_test.go @@ -322,35 +322,6 @@ func TestApply_InstallationWithPreflightPermissionsEnabled(t *testing.T) { require.Nil(t, objs) }) - t.Run("fails during installation because of missing RBAC rules", func(t *testing.T) { - mockAcg := &mockActionGetter{ - getClientErr: driver.ErrReleaseNotFound, - desiredRel: &release.Release{ - Info: &release.Info{Status: release.StatusDeployed}, - Manifest: validManifest, - }, - } - helmApplier := applier.Helm{ - ActionClientGetter: mockAcg, - PreAuthorizer: &errorPreAuthorizer{}, - BundleToHelmChartFn: convert.RegistryV1ToHelmChart, - } - // Use a ClusterExtension with valid Spec fields. - validCE := &ocv1.ClusterExtension{ - Spec: ocv1.ClusterExtensionSpec{ - Namespace: "default", - ServiceAccount: ocv1.ServiceAccountReference{ - Name: "default", - }, - }, - } - objs, state, err := helmApplier.Apply(context.TODO(), validFS, validCE, testObjectLabels, testStorageLabels) - require.Error(t, err) - require.ErrorContains(t, err, "problem running preauthorization") - require.Equal(t, "", state) - require.Nil(t, objs) - }) - t.Run("fails during installation because of pre-authorization failure", func(t *testing.T) { mockAcg := &mockActionGetter{ getClientErr: driver.ErrReleaseNotFound,