Skip to content

Commit 45a7311

Browse files
committed
permissions preflight: add clusterextension.status.missingRules
Signed-off-by: Joe Lanford <[email protected]>
1 parent 2b306b9 commit 45a7311

File tree

5 files changed

+195
-6
lines changed

5 files changed

+195
-6
lines changed

api/v1/clusterextension_types.go

+42
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package v1
1818

1919
import (
20+
rbacv1 "k8s.io/api/rbac/v1"
2021
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2122
)
2223

@@ -454,6 +455,47 @@ type ClusterExtensionStatus struct {
454455
//
455456
// +optional
456457
Install *ClusterExtensionInstallStatus `json:"install,omitempty"`
458+
459+
// missing rules is a list of rules (by namespace) that the cluster extension's service account is likely lacking,
460+
// but which are needed to be able to fully manage all resources in the resolved cluster extension manifest.
461+
//
462+
// +listType=atomic
463+
// +optional
464+
// +kubebuilder:validation:MaxItems:=64
465+
MissingRules []ClusterExtensionMissingRulesItem `json:"missingRules,omitempty"`
466+
}
467+
468+
// ClusterExtensionMissingRulesItem represents the set of rules that are missing in a given scope.
469+
type ClusterExtensionMissingRulesItem struct {
470+
// namespace is a reference to a Kubernetes namespace.
471+
// This is the namespace where the ClusterExtension's service account is lacking
472+
// the rules provided in missingRules.
473+
//
474+
// When namespace is non-empty, one or more RoleBindings should be created to reference
475+
// Roles and/or ClusterRoles that provided the missingRules in the namespace.
476+
//
477+
// When namespace is the empty string, one or more ClusterRoleBindings should be created
478+
// to reference ClusterRoles that provide the missingRules cluster-wide.
479+
//
480+
// namespace is required and follows the DNS label standard
481+
// as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters or hyphens (-),
482+
// start and end with an alphanumeric character, and be no longer than 63 characters
483+
//
484+
// [RFC 1123]: https://tools.ietf.org/html/rfc1123
485+
//
486+
// +kubebuilder:validation:MaxLength:=63
487+
// +kubebuilder:validation:XValidation:rule="self.matches(\"^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\")",message="namespace must be a valid DNS1123 label"
488+
// +kubebuilder:validation:Required
489+
Namespace string `json:"namespace"`
490+
491+
// missingRules is a list of RBAC policy rules that the ClusterExtension's service account lacks in the
492+
// namespace defined in the namespace field.
493+
//
494+
// missingRules is required.
495+
//
496+
// +listType=atomic
497+
// +kubebuilder:validation:MaxItems:=1024
498+
MissingRules []rbacv1.PolicyRule `json:"missingRules"`
457499
}
458500

459501
// ClusterExtensionInstallStatus is a representation of the status of the identified bundle.

api/v1/zz_generated.deepcopy.go

+30
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/base/operator-controller/crd/bases/olm.operatorframework.io_clusterextensions.yaml

+94
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,100 @@ spec:
581581
required:
582582
- bundle
583583
type: object
584+
missingRules:
585+
description: |-
586+
missing rules is a list of rules (by namespace) that the cluster extension's service account is likely lacking,
587+
but which are needed to be able to fully manage all resources in the resolved cluster extension manifest.
588+
items:
589+
description: ClusterExtensionMissingRulesItem represents the set
590+
of rules that are missing in a given scope.
591+
properties:
592+
missingRules:
593+
description: |-
594+
missingRules is a list of RBAC policy rules that the ClusterExtension's service account lacks in the
595+
namespace defined in the namespace field.
596+
597+
missingRules is required.
598+
items:
599+
description: |-
600+
PolicyRule holds information that describes a policy rule, but does not contain information
601+
about who the rule applies to or which namespace the rule applies to.
602+
properties:
603+
apiGroups:
604+
description: |-
605+
APIGroups is the name of the APIGroup that contains the resources. If multiple API groups are specified, any action requested against one of
606+
the enumerated resources in any API group will be allowed. "" represents the core API group and "*" represents all API groups.
607+
items:
608+
type: string
609+
type: array
610+
x-kubernetes-list-type: atomic
611+
nonResourceURLs:
612+
description: |-
613+
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
614+
Since non-resource URLs are not namespaced, this field is only applicable for ClusterRoles referenced from a ClusterRoleBinding.
615+
Rules can either apply to API resources (such as "pods" or "secrets") or non-resource URL paths (such as "/api"), but not both.
616+
items:
617+
type: string
618+
type: array
619+
x-kubernetes-list-type: atomic
620+
resourceNames:
621+
description: ResourceNames is an optional white list of
622+
names that the rule applies to. An empty set means
623+
that everything is allowed.
624+
items:
625+
type: string
626+
type: array
627+
x-kubernetes-list-type: atomic
628+
resources:
629+
description: Resources is a list of resources this rule
630+
applies to. '*' represents all resources.
631+
items:
632+
type: string
633+
type: array
634+
x-kubernetes-list-type: atomic
635+
verbs:
636+
description: Verbs is a list of Verbs that apply to ALL
637+
the ResourceKinds contained in this rule. '*' represents
638+
all verbs.
639+
items:
640+
type: string
641+
type: array
642+
x-kubernetes-list-type: atomic
643+
required:
644+
- verbs
645+
type: object
646+
maxItems: 1024
647+
type: array
648+
x-kubernetes-list-type: atomic
649+
namespace:
650+
description: |-
651+
namespace is a reference to a Kubernetes namespace.
652+
This is the namespace where the ClusterExtension's service account is lacking
653+
the rules provided in missingRules.
654+
655+
When namespace is non-empty, one or more RoleBindings should be created to reference
656+
Roles and/or ClusterRoles that provided the missingRules in the namespace.
657+
658+
When namespace is the empty string, one or more ClusterRoleBindings should be created
659+
to reference ClusterRoles that provide the missingRules cluster-wide.
660+
661+
namespace is required and follows the DNS label standard
662+
as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters or hyphens (-),
663+
start and end with an alphanumeric character, and be no longer than 63 characters
664+
665+
[RFC 1123]: https://tools.ietf.org/html/rfc1123
666+
maxLength: 63
667+
type: string
668+
x-kubernetes-validations:
669+
- message: namespace must be a valid DNS1123 label
670+
rule: self.matches("^[a-z0-9]([-a-z0-9]*[a-z0-9])?$")
671+
required:
672+
- missingRules
673+
- namespace
674+
type: object
675+
maxItems: 64
676+
type: array
677+
x-kubernetes-list-type: atomic
584678
type: object
585679
type: object
586680
served: true

docs/api-reference/operator-controller-api-reference.md

+18
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,23 @@ ClusterExtensionList contains a list of ClusterExtension
292292
| `items` _[ClusterExtension](#clusterextension) array_ | items is a required list of ClusterExtension objects. | | Required: \{\} <br /> |
293293

294294

295+
#### ClusterExtensionMissingRulesItem
296+
297+
298+
299+
ClusterExtensionMissingRulesItem represents the set of rules that are missing in a given scope.
300+
301+
302+
303+
_Appears in:_
304+
- [ClusterExtensionStatus](#clusterextensionstatus)
305+
306+
| Field | Description | Default | Validation |
307+
| --- | --- | --- | --- |
308+
| `namespace` _string_ | namespace is a reference to a Kubernetes namespace.<br />This is the namespace where the ClusterExtension's service account is lacking<br />the rules provided in missingRules.<br /><br />When namespace is non-empty, one or more RoleBindings should be created to reference<br />Roles and/or ClusterRoles that provided the missingRules in the namespace.<br /><br />When namespace is the empty string, one or more ClusterRoleBindings should be created<br />to reference ClusterRoles that provide the missingRules cluster-wide.<br /><br />namespace is required and follows the DNS label standard<br />as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters or hyphens (-),<br />start and end with an alphanumeric character, and be no longer than 63 characters<br /><br />[RFC 1123]: https://tools.ietf.org/html/rfc1123 | | MaxLength: 63 <br />Required: \{\} <br /> |
309+
| `missingRules` _[PolicyRule](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#policyrule-v1-rbac) array_ | missingRules is a list of RBAC policy rules that the ClusterExtension's service account lacks in the<br />namespace defined in the namespace field.<br /><br />missingRules is required. | | MaxItems: 1024 <br /> |
310+
311+
295312
#### ClusterExtensionSpec
296313

297314

@@ -326,6 +343,7 @@ _Appears in:_
326343
| --- | --- | --- | --- |
327344
| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#condition-v1-meta) array_ | The set of condition types which apply to all spec.source variations are Installed and Progressing.<br /><br />The Installed condition represents whether or not the bundle has been installed for this ClusterExtension.<br />When Installed is True and the Reason is Succeeded, the bundle has been successfully installed.<br />When Installed is False and the Reason is Failed, the bundle has failed to install.<br /><br />The Progressing condition represents whether or not the ClusterExtension is advancing towards a new state.<br />When Progressing is True and the Reason is Succeeded, the ClusterExtension is making progress towards a new state.<br />When Progressing is True and the Reason is Retrying, the ClusterExtension has encountered an error that could be resolved on subsequent reconciliation attempts.<br />When Progressing is False and the Reason is Blocked, the ClusterExtension has encountered an error that requires manual intervention for recovery.<br /><br />When the ClusterExtension is sourced from a catalog, if may also communicate a deprecation condition.<br />These are indications from a package owner to guide users away from a particular package, channel, or bundle.<br />BundleDeprecated is set if the requested bundle version is marked deprecated in the catalog.<br />ChannelDeprecated is set if the requested channel is marked deprecated in the catalog.<br />PackageDeprecated is set if the requested package is marked deprecated in the catalog.<br />Deprecated is a rollup condition that is present when any of the deprecated conditions are present. | | |
328345
| `install` _[ClusterExtensionInstallStatus](#clusterextensioninstallstatus)_ | install is a representation of the current installation status for this ClusterExtension. | | |
346+
| `missingRules` _[ClusterExtensionMissingRulesItem](#clusterextensionmissingrulesitem) array_ | missing rules is a list of rules (by namespace) that the cluster extension's service account is likely lacking,<br />but which are needed to be able to fully manage all resources in the resolved cluster extension manifest. | | MaxItems: 64 <br /> |
329347

330348

331349
#### ImageSource

internal/operator-controller/applier/helm.go

+11-6
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package applier
22

33
import (
44
"bytes"
5+
"cmp"
56
"context"
67
"errors"
78
"fmt"
@@ -105,14 +106,18 @@ func (h *Helm) Apply(ctx context.Context, contentFS fs.FS, ext *ocv1.ClusterExte
105106

106107
var preAuthErrors []error
107108
if len(missingRules) > 0 {
108-
var missingRuleDescriptions []string
109+
var missingRulesItems []ocv1.ClusterExtensionMissingRulesItem
109110
for ns, policyRules := range missingRules {
110-
for _, rule := range policyRules {
111-
missingRuleDescriptions = append(missingRuleDescriptions, ruleDescription(ns, rule))
112-
}
111+
missingRulesItems = append(missingRulesItems, ocv1.ClusterExtensionMissingRulesItem{
112+
Namespace: ns,
113+
MissingRules: policyRules,
114+
})
113115
}
114-
slices.Sort(missingRuleDescriptions)
115-
preAuthErrors = append(preAuthErrors, fmt.Errorf("service account lacks permission to manage cluster extension:\n %s", strings.Join(missingRuleDescriptions, "\n ")))
116+
slices.SortFunc(missingRulesItems, func(a, b ocv1.ClusterExtensionMissingRulesItem) int {
117+
return cmp.Compare(a.Namespace, b.Namespace)
118+
})
119+
ext.Status.MissingRules = missingRulesItems
120+
preAuthErrors = append(preAuthErrors, fmt.Errorf("service account lacks permission to manage cluster extension"))
116121
}
117122
if err != nil {
118123
preAuthErrors = append(preAuthErrors, fmt.Errorf("authorization evaluation error: %w", err))

0 commit comments

Comments
 (0)