@@ -26,6 +26,7 @@ import (
2626 helmclient "github.com/operator-framework/helm-operator-plugins/pkg/client"
2727
2828 ocv1 "github.com/operator-framework/operator-controller/api/v1"
29+ "github.com/operator-framework/operator-controller/internal/operator-controller/authorization"
2930 "github.com/operator-framework/operator-controller/internal/operator-controller/labels"
3031 "github.com/operator-framework/operator-controller/internal/shared/util/cache"
3132)
@@ -274,6 +275,7 @@ type Boxcutter struct {
274275 Scheme * runtime.Scheme
275276 RevisionGenerator ClusterExtensionRevisionGenerator
276277 Preflights []Preflight
278+ PreAuthorizer authorization.PreAuthorizer
277279 FieldOwner string
278280}
279281
@@ -309,6 +311,11 @@ func (bc *Boxcutter) apply(ctx context.Context, contentFS fs.FS, ext *ocv1.Clust
309311 return false , "" , err
310312 }
311313
314+ // Run auth preflight checks
315+ if err := bc .runPreAuthorizationChecks (ctx , ext , desiredRevision ); err != nil {
316+ return false , "" , err
317+ }
318+
312319 if err := controllerutil .SetControllerReference (ext , desiredRevision , bc .Scheme ); err != nil {
313320 return false , "" , fmt .Errorf ("set ownerref: %w" , err )
314321 }
@@ -405,6 +412,48 @@ func (bc *Boxcutter) apply(ctx context.Context, contentFS fs.FS, ext *ocv1.Clust
405412 return true , "" , nil
406413}
407414
415+ func (bc * Boxcutter ) runPreAuthorizationChecks (ctx context.Context , ext * ocv1.ClusterExtension , rev * ocv1.ClusterExtensionRevision ) error {
416+ if bc .PreAuthorizer == nil {
417+ return nil
418+ }
419+
420+ var manifestBuilder strings.Builder
421+ for _ , phase := range rev .Spec .Phases {
422+ for _ , phaseObject := range phase .Objects {
423+ objBytes , err := yaml .Marshal (phaseObject .Object .Object )
424+ if err != nil {
425+ return fmt .Errorf ("error generating revision manifest: %w" , err )
426+ }
427+ manifestBuilder .WriteString ("---\n " )
428+ manifestBuilder .WriteString (string (objBytes ))
429+ manifestBuilder .WriteString ("\n " )
430+ }
431+ }
432+ missingRules , authErr := bc .PreAuthorizer .PreAuthorize (ctx , ext , strings .NewReader (manifestBuilder .String ()))
433+
434+ var preAuthErrors []error
435+
436+ if len (missingRules ) > 0 {
437+ var missingRuleDescriptions []string
438+ for _ , policyRules := range missingRules {
439+ for _ , rule := range policyRules .MissingRules {
440+ missingRuleDescriptions = append (missingRuleDescriptions , ruleDescription (policyRules .Namespace , rule ))
441+ }
442+ }
443+ slices .Sort (missingRuleDescriptions )
444+ // This phrase is explicitly checked by external testing
445+ preAuthErrors = append (preAuthErrors , fmt .Errorf ("service account requires the following permissions to manage cluster extension:\n %s" , strings .Join (missingRuleDescriptions , "\n " )))
446+ }
447+ if authErr != nil {
448+ preAuthErrors = append (preAuthErrors , fmt .Errorf ("authorization evaluation error: %w" , authErr ))
449+ }
450+ if len (preAuthErrors ) > 0 {
451+ // This phrase is explicitly checked by external testing
452+ return fmt .Errorf ("pre-authorization failed: %v" , errors .Join (preAuthErrors ... ))
453+ }
454+ return nil
455+ }
456+
408457// garbageCollectOldRevisions deletes archived revisions beyond ClusterExtensionRevisionRetentionLimit.
409458// Active revisions are never deleted. revisionList must be sorted oldest to newest.
410459func (bc * Boxcutter ) garbageCollectOldRevisions (ctx context.Context , revisionList []ocv1.ClusterExtensionRevision ) error {
0 commit comments