55 "context"
66 "errors"
77 "fmt"
8+ "io"
89 "io/fs"
910 "maps"
1011 "slices"
@@ -16,6 +17,8 @@ import (
1617 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1718 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
1819 "k8s.io/apimachinery/pkg/runtime"
20+ "k8s.io/apiserver/pkg/authentication/user"
21+ "k8s.io/apiserver/pkg/authorization/authorizer"
1922 "sigs.k8s.io/controller-runtime/pkg/client"
2023 "sigs.k8s.io/controller-runtime/pkg/client/apiutil"
2124 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
@@ -25,6 +28,7 @@ import (
2528 helmclient "github.com/operator-framework/helm-operator-plugins/pkg/client"
2629
2730 ocv1 "github.com/operator-framework/operator-controller/api/v1"
31+ "github.com/operator-framework/operator-controller/internal/operator-controller/authorization"
2832 "github.com/operator-framework/operator-controller/internal/operator-controller/labels"
2933 "github.com/operator-framework/operator-controller/internal/shared/util/cache"
3034)
@@ -279,28 +283,27 @@ type Boxcutter struct {
279283 Scheme * runtime.Scheme
280284 RevisionGenerator ClusterExtensionRevisionGenerator
281285 Preflights []Preflight
286+ PreAuthorizer authorization.PreAuthorizer
282287 FieldOwner string
283288}
284289
285- func (bc * Boxcutter ) getObjects (rev * ocv1.ClusterExtensionRevision ) []client.Object {
286- var objs []client.Object
287- for _ , phase := range rev .Spec .Phases {
288- for _ , phaseObject := range phase .Objects {
289- objs = append (objs , & phaseObject .Object )
290- }
291- }
292- return objs
293- }
294-
295- func (bc * Boxcutter ) createOrUpdate (ctx context.Context , obj client.Object ) error {
296- if obj .GetObjectKind ().GroupVersionKind ().Empty () {
297- gvk , err := apiutil .GVKForObject (obj , bc .Scheme )
290+ // createOrUpdate creates or updates the revision object. PreAuthorization checks are performed to ensure the
291+ // manifestManager has sufficient permissions to manage the revision and its resources
292+ func (bc * Boxcutter ) createOrUpdate (ctx context.Context , manifestManager user.Info , rev * ocv1.ClusterExtensionRevision ) error {
293+ if rev .GetObjectKind ().GroupVersionKind ().Empty () {
294+ gvk , err := apiutil .GVKForObject (rev , bc .Scheme )
298295 if err != nil {
299296 return err
300297 }
301- obj .GetObjectKind ().SetGroupVersionKind (gvk )
298+ rev .GetObjectKind ().SetGroupVersionKind (gvk )
302299 }
303- return bc .Client .Patch (ctx , obj , client .Apply , client .FieldOwner (bc .FieldOwner ), client .ForceOwnership )
300+
301+ // Run auth preflight checks
302+ if err := bc .runPreAuthorizationChecks (ctx , manifestManager , rev ); err != nil {
303+ return err
304+ }
305+
306+ return bc .Client .Patch (ctx , rev , client .Apply , client .FieldOwner (bc .FieldOwner ), client .ForceOwnership )
304307}
305308
306309func (bc * Boxcutter ) Apply (ctx context.Context , contentFS fs.FS , ext * ocv1.ClusterExtension , objectLabels , revisionAnnotations map [string ]string ) error {
@@ -329,7 +332,7 @@ func (bc *Boxcutter) Apply(ctx context.Context, contentFS fs.FS, ext *ocv1.Clust
329332 desiredRevision .Spec .Revision = currentRevision .Spec .Revision
330333 desiredRevision .Name = currentRevision .Name
331334
332- err := bc .createOrUpdate (ctx , desiredRevision )
335+ err := bc .createOrUpdate (ctx , getUserInfo ( ext ), desiredRevision )
333336 switch {
334337 case apierrors .IsInvalid (err ):
335338 // We could not update the current revision due to trying to update an immutable field.
@@ -344,7 +347,7 @@ func (bc *Boxcutter) Apply(ctx context.Context, contentFS fs.FS, ext *ocv1.Clust
344347 }
345348
346349 // Preflights
347- plainObjs := bc . getObjects (desiredRevision )
350+ plainObjs := getObjects (desiredRevision )
348351 for _ , preflight := range bc .Preflights {
349352 if shouldSkipPreflight (ctx , preflight , ext , state ) {
350353 continue
@@ -379,14 +382,31 @@ func (bc *Boxcutter) Apply(ctx context.Context, contentFS fs.FS, ext *ocv1.Clust
379382 return fmt .Errorf ("garbage collecting old revisions: %w" , err )
380383 }
381384
382- if err := bc .createOrUpdate (ctx , desiredRevision ); err != nil {
385+ if err := bc .createOrUpdate (ctx , getUserInfo ( ext ), desiredRevision ); err != nil {
383386 return fmt .Errorf ("creating new Revision: %w" , err )
384387 }
385388 }
386389
387390 return nil
388391}
389392
393+ // runPreAuthorizationChecks runs PreAuthorization checks if the PreAuthorizer is set. An error will be returned if
394+ // the ClusterExtension service account does not have the necessary permissions to manage the revision's resources
395+ func (bc * Boxcutter ) runPreAuthorizationChecks (ctx context.Context , manifestManager user.Info , rev * ocv1.ClusterExtensionRevision ) error {
396+ if bc .PreAuthorizer == nil {
397+ return nil
398+ }
399+
400+ // collect the revision manifests
401+ manifestReader , err := revisionManifestReader (rev )
402+ if err != nil {
403+ return err
404+ }
405+
406+ // run preauthorization check
407+ return formatPreAuthorizerOutput (bc .PreAuthorizer .PreAuthorize (ctx , manifestManager , manifestReader , revisionManagementPerms (rev )))
408+ }
409+
390410// garbageCollectOldRevisions deletes archived revisions beyond ClusterExtensionRevisionRetentionLimit.
391411// Active revisions are never deleted. revisionList must be sorted oldest to newest.
392412func (bc * Boxcutter ) garbageCollectOldRevisions (ctx context.Context , revisionList []ocv1.ClusterExtensionRevision ) error {
@@ -445,3 +465,45 @@ func splitManifestDocuments(file string) []string {
445465 }
446466 return docs
447467}
468+
469+ // getObjects returns a slice of all objects in the revision
470+ func getObjects (rev * ocv1.ClusterExtensionRevision ) []client.Object {
471+ var objs []client.Object
472+ for _ , phase := range rev .Spec .Phases {
473+ for _ , phaseObject := range phase .Objects {
474+ objs = append (objs , & phaseObject .Object )
475+ }
476+ }
477+ return objs
478+ }
479+
480+ // revisionManifestReader returns an io.Reader containing all manifests in the revision
481+ func revisionManifestReader (rev * ocv1.ClusterExtensionRevision ) (io.Reader , error ) {
482+ var manifestBuilder strings.Builder
483+ for _ , obj := range getObjects (rev ) {
484+ objBytes , err := yaml .Marshal (obj )
485+ if err != nil {
486+ return nil , fmt .Errorf ("error generating revision manifest: %w" , err )
487+ }
488+ manifestBuilder .WriteString ("---\n " )
489+ manifestBuilder .WriteString (string (objBytes ))
490+ manifestBuilder .WriteString ("\n " )
491+ }
492+ return strings .NewReader (manifestBuilder .String ()), nil
493+ }
494+
495+ func revisionManagementPerms (rev * ocv1.ClusterExtensionRevision ) func (user.Info ) []authorizer.AttributesRecord {
496+ return func (user user.Info ) []authorizer.AttributesRecord {
497+ return []authorizer.AttributesRecord {
498+ {
499+ User : user ,
500+ Name : rev .Name ,
501+ APIGroup : ocv1 .GroupVersion .Group ,
502+ APIVersion : ocv1 .GroupVersion .Version ,
503+ Resource : "clusterextensionrevisions/finalizers" ,
504+ ResourceRequest : true ,
505+ Verb : "update" ,
506+ },
507+ }
508+ }
509+ }
0 commit comments