Skip to content

Commit 221976e

Browse files
committed
permissions preflight: add clusterextension.status.rules
Signed-off-by: Joe Lanford <[email protected]>
1 parent 794d5d4 commit 221976e

File tree

5 files changed

+253
-28
lines changed

5 files changed

+253
-28
lines changed

api/v1/clusterextension_types.go

+50
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,55 @@ type ClusterExtensionStatus struct {
454455
//
455456
// +optional
456457
Install *ClusterExtensionInstallStatus `json:"install,omitempty"`
458+
459+
// rules is a representation of an assessment of permissions related
460+
// to the cluster extension and its service account.
461+
//
462+
// +optional
463+
Rules *ClusterExtensionRulesStatus `json:"rules,omitempty"`
464+
}
465+
466+
type ClusterExtensionRulesStatus struct {
467+
// missing is the group of rules that the cluster extension's service account is likely lacking,
468+
// but which are needed to be able to fully manage all resources in the resolved cluster extension manifest.
469+
//
470+
// +listType=atomic
471+
// +optional
472+
// +kubebuilder:validation:MaxItems:=64
473+
Missing []ClusterExtensionRulesStatusItem `json:"missing,omitempty"`
474+
}
475+
476+
type ClusterExtensionRulesStatusItem struct {
477+
// namespace is a reference to a Kubernetes namespace.
478+
// This is the namespace where the ClusterExtension's service account is lacking
479+
// the rules provided in missingRules.
480+
//
481+
// When namespace is non-empty, one or more RoleBindings should be created to reference
482+
// Roles and/or ClusterRoles that provided the missingRules in the namespace.
483+
//
484+
// When namespace is the empty string, one or more ClusterRoleBindings should be created
485+
// to reference ClusterRoles that provide the missingRules cluster-wide.
486+
//
487+
// namespace is required and follows the DNS label standard
488+
// as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters or hyphens (-),
489+
// start and end with an alphanumeric character, and be no longer than 63 characters
490+
//
491+
// [RFC 1123]: https://tools.ietf.org/html/rfc1123
492+
//
493+
// +kubebuilder:validation:MaxLength:=63
494+
// +kubebuilder:validation:XValidation:rule="self == \"\" || self.matches(\"^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\")",message="namespace must be empty string or a valid DNS1123 label"
495+
// +kubebuilder:validation:Required
496+
Namespace string `json:"namespace"`
497+
498+
// rules is a list of RBAC policy rules that are related to the management of the rendered manifest of the
499+
// ClusterExtension's bundle.
500+
//
501+
// rules is optional. If it is empty or nil, the semantic meaning is that there are no rules in this grouping
502+
// for the named namespace.
503+
//
504+
// +listType=atomic
505+
// +kubebuilder:validation:MaxItems:=1024
506+
Rules []rbacv1.PolicyRule `json:"rules,omitempty"`
457507
}
458508

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

api/v1/zz_generated.deepcopy.go

+50
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

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

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

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

294294

295+
#### ClusterExtensionRulesStatus
296+
297+
298+
299+
300+
301+
302+
303+
_Appears in:_
304+
- [ClusterExtensionStatus](#clusterextensionstatus)
305+
306+
| Field | Description | Default | Validation |
307+
| --- | --- | --- | --- |
308+
| `missing` _[ClusterExtensionRulesStatusItem](#clusterextensionrulesstatusitem) array_ | missing is the group of rules 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 /> |
309+
310+
311+
#### ClusterExtensionRulesStatusItem
312+
313+
314+
315+
316+
317+
318+
319+
_Appears in:_
320+
- [ClusterExtensionRulesStatus](#clusterextensionrulesstatus)
321+
322+
| Field | Description | Default | Validation |
323+
| --- | --- | --- | --- |
324+
| `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 /> |
325+
| `rules` _[PolicyRule](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#policyrule-v1-rbac) array_ | rules is a list of RBAC policy rules that are related to the management of the rendered manifest of the<br />ClusterExtension's bundle.<br /><br />rules is optional. If it is empty or nil, the semantic meaning is that there are no rules in this grouping<br />for the named namespace. | | MaxItems: 1024 <br /> |
326+
327+
295328
#### ClusterExtensionSpec
296329

297330

@@ -326,6 +359,7 @@ _Appears in:_
326359
| --- | --- | --- | --- |
327360
| `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. | | |
328361
| `install` _[ClusterExtensionInstallStatus](#clusterextensioninstallstatus)_ | install is a representation of the current installation status for this ClusterExtension. | | |
362+
| `rules` _[ClusterExtensionRulesStatus](#clusterextensionrulesstatus)_ | rules is a representation of an assessment of permissions related<br />to the cluster extension and its service account. | | |
329363

330364

331365
#### ImageSource

internal/operator-controller/applier/helm.go

+20-28
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"
@@ -17,7 +18,6 @@ import (
1718
"helm.sh/helm/v3/pkg/release"
1819
"helm.sh/helm/v3/pkg/storage/driver"
1920
corev1 "k8s.io/api/core/v1"
20-
rbacv1 "k8s.io/api/rbac/v1"
2121
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2222
apimachyaml "k8s.io/apimachinery/pkg/util/yaml"
2323
"k8s.io/apiserver/pkg/authentication/user"
@@ -104,15 +104,29 @@ func (h *Helm) Apply(ctx context.Context, contentFS fs.FS, ext *ocv1.ClusterExte
104104
missingRules, err := h.PreAuthorizer.PreAuthorize(ctx, &ceServiceAccount, strings.NewReader(tmplRel.Manifest))
105105

106106
var preAuthErrors []error
107+
ext.Status.Rules = nil
107108
if len(missingRules) > 0 {
108-
var missingRuleDescriptions []string
109+
var missingRulesItems []ocv1.ClusterExtensionRulesStatusItem
109110
for ns, policyRules := range missingRules {
110-
for _, rule := range policyRules {
111-
missingRuleDescriptions = append(missingRuleDescriptions, ruleDescription(ns, rule))
111+
item := ocv1.ClusterExtensionRulesStatusItem{
112+
Namespace: ns,
113+
Rules: policyRules,
112114
}
115+
if len(item.Rules) > 1024 {
116+
item.Rules = item.Rules[:1024]
117+
}
118+
missingRulesItems = append(missingRulesItems, item)
119+
}
120+
slices.SortFunc(missingRulesItems, func(a, b ocv1.ClusterExtensionRulesStatusItem) int {
121+
return cmp.Compare(a.Namespace, b.Namespace)
122+
})
123+
124+
if len(missingRulesItems) > 64 {
125+
missingRulesItems = missingRulesItems[:64]
113126
}
114-
slices.Sort(missingRuleDescriptions)
115-
preAuthErrors = append(preAuthErrors, fmt.Errorf("service account lacks permission to manage cluster extension:\n %s", strings.Join(missingRuleDescriptions, "\n ")))
127+
128+
ext.Status.Rules = &ocv1.ClusterExtensionRulesStatus{Missing: missingRulesItems}
129+
preAuthErrors = append(preAuthErrors, fmt.Errorf("service account lacks permission to manage cluster extension"))
116130
}
117131
if err != nil {
118132
preAuthErrors = append(preAuthErrors, fmt.Errorf("authorization evaluation error: %w", err))
@@ -277,25 +291,3 @@ func (p *postrenderer) Run(renderedManifests *bytes.Buffer) (*bytes.Buffer, erro
277291
}
278292
return &buf, nil
279293
}
280-
281-
func ruleDescription(ns string, rule rbacv1.PolicyRule) string {
282-
var sb strings.Builder
283-
sb.WriteString(fmt.Sprintf("Namespace:%q", ns))
284-
285-
if len(rule.APIGroups) > 0 {
286-
sb.WriteString(fmt.Sprintf(" APIGroups:[%s]", strings.Join(rule.APIGroups, ",")))
287-
}
288-
if len(rule.Resources) > 0 {
289-
sb.WriteString(fmt.Sprintf(" Resources:[%s]", strings.Join(rule.Resources, ",")))
290-
}
291-
if len(rule.ResourceNames) > 0 {
292-
sb.WriteString(fmt.Sprintf(" ResourceNames:[%s]", strings.Join(rule.ResourceNames, ",")))
293-
}
294-
if len(rule.Verbs) > 0 {
295-
sb.WriteString(fmt.Sprintf(" Verbs:[%s]", strings.Join(rule.Verbs, ",")))
296-
}
297-
if len(rule.NonResourceURLs) > 0 {
298-
sb.WriteString(fmt.Sprintf(" NonResourceURLs:[%s]", strings.Join(rule.NonResourceURLs, ",")))
299-
}
300-
return sb.String()
301-
}

0 commit comments

Comments
 (0)