11package applier
22
33import (
4+ "bytes"
45 "cmp"
56 "context"
67 "errors"
78 "fmt"
9+ "io"
810 "io/fs"
911 "maps"
1012 "slices"
@@ -16,6 +18,9 @@ import (
1618 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1719 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
1820 "k8s.io/apimachinery/pkg/runtime"
21+ "k8s.io/apiserver/pkg/authentication/user"
22+ "k8s.io/apiserver/pkg/authorization/authorizer"
23+ "k8s.io/cli-runtime/pkg/printers"
1924 "sigs.k8s.io/controller-runtime/pkg/client"
2025 "sigs.k8s.io/controller-runtime/pkg/client/apiutil"
2126 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
@@ -25,6 +30,7 @@ import (
2530 helmclient "github.com/operator-framework/helm-operator-plugins/pkg/client"
2631
2732 ocv1 "github.com/operator-framework/operator-controller/api/v1"
33+ "github.com/operator-framework/operator-controller/internal/operator-controller/authorization"
2834 "github.com/operator-framework/operator-controller/internal/operator-controller/labels"
2935 "github.com/operator-framework/operator-controller/internal/shared/util/cache"
3036)
@@ -279,28 +285,27 @@ type Boxcutter struct {
279285 Scheme * runtime.Scheme
280286 RevisionGenerator ClusterExtensionRevisionGenerator
281287 Preflights []Preflight
288+ PreAuthorizer authorization.PreAuthorizer
282289 FieldOwner string
283290}
284291
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 )
292+ // createOrUpdate creates or updates the revision object. PreAuthorization checks are performed to ensure the
293+ // user has sufficient permissions to manage the revision and its resources
294+ func (bc * Boxcutter ) createOrUpdate (ctx context.Context , user user.Info , rev * ocv1.ClusterExtensionRevision ) error {
295+ if rev .GetObjectKind ().GroupVersionKind ().Empty () {
296+ gvk , err := apiutil .GVKForObject (rev , bc .Scheme )
298297 if err != nil {
299298 return err
300299 }
301- obj .GetObjectKind ().SetGroupVersionKind (gvk )
300+ rev .GetObjectKind ().SetGroupVersionKind (gvk )
302301 }
303- return bc .Client .Patch (ctx , obj , client .Apply , client .FieldOwner (bc .FieldOwner ), client .ForceOwnership )
302+
303+ // Run auth preflight checks
304+ if err := bc .runPreAuthorizationChecks (ctx , user , rev ); err != nil {
305+ return err
306+ }
307+
308+ return bc .Client .Patch (ctx , rev , client .Apply , client .FieldOwner (bc .FieldOwner ), client .ForceOwnership )
304309}
305310
306311func (bc * Boxcutter ) Apply (ctx context.Context , contentFS fs.FS , ext * ocv1.ClusterExtension , objectLabels , revisionAnnotations map [string ]string ) error {
@@ -329,7 +334,7 @@ func (bc *Boxcutter) Apply(ctx context.Context, contentFS fs.FS, ext *ocv1.Clust
329334 desiredRevision .Spec .Revision = currentRevision .Spec .Revision
330335 desiredRevision .Name = currentRevision .Name
331336
332- err := bc .createOrUpdate (ctx , desiredRevision )
337+ err := bc .createOrUpdate (ctx , getUserInfo ( ext ), desiredRevision )
333338 switch {
334339 case apierrors .IsInvalid (err ):
335340 // We could not update the current revision due to trying to update an immutable field.
@@ -344,7 +349,7 @@ func (bc *Boxcutter) Apply(ctx context.Context, contentFS fs.FS, ext *ocv1.Clust
344349 }
345350
346351 // Preflights
347- plainObjs := bc . getObjects (desiredRevision )
352+ plainObjs := getObjects (desiredRevision )
348353 for _ , preflight := range bc .Preflights {
349354 if shouldSkipPreflight (ctx , preflight , ext , state ) {
350355 continue
@@ -379,14 +384,31 @@ func (bc *Boxcutter) Apply(ctx context.Context, contentFS fs.FS, ext *ocv1.Clust
379384 return fmt .Errorf ("garbage collecting old revisions: %w" , err )
380385 }
381386
382- if err := bc .createOrUpdate (ctx , desiredRevision ); err != nil {
387+ if err := bc .createOrUpdate (ctx , getUserInfo ( ext ), desiredRevision ); err != nil {
383388 return fmt .Errorf ("creating new Revision: %w" , err )
384389 }
385390 }
386391
387392 return nil
388393}
389394
395+ // runPreAuthorizationChecks runs PreAuthorization checks if the PreAuthorizer is set. An error will be returned if
396+ // the ClusterExtension service account does not have the necessary permissions to manage the revision's resources
397+ func (bc * Boxcutter ) runPreAuthorizationChecks (ctx context.Context , user user.Info , rev * ocv1.ClusterExtensionRevision ) error {
398+ if bc .PreAuthorizer == nil {
399+ return nil
400+ }
401+
402+ // collect the revision manifests
403+ manifestReader , err := revisionManifestReader (rev )
404+ if err != nil {
405+ return err
406+ }
407+
408+ // run preauthorization check
409+ return formatPreAuthorizerOutput (bc .PreAuthorizer .PreAuthorize (ctx , user , manifestReader , revisionManagementPerms (rev )))
410+ }
411+
390412// garbageCollectOldRevisions deletes archived revisions beyond ClusterExtensionRevisionRetentionLimit.
391413// Active revisions are never deleted. revisionList must be sorted oldest to newest.
392414func (bc * Boxcutter ) garbageCollectOldRevisions (ctx context.Context , revisionList []ocv1.ClusterExtensionRevision ) error {
@@ -445,3 +467,43 @@ func splitManifestDocuments(file string) []string {
445467 }
446468 return docs
447469}
470+
471+ // getObjects returns a slice of all objects in the revision
472+ func getObjects (rev * ocv1.ClusterExtensionRevision ) []client.Object {
473+ var objs []client.Object
474+ for _ , phase := range rev .Spec .Phases {
475+ for _ , phaseObject := range phase .Objects {
476+ objs = append (objs , & phaseObject .Object )
477+ }
478+ }
479+ return objs
480+ }
481+
482+ // revisionManifestReader returns an io.Reader containing all manifests in the revision
483+ func revisionManifestReader (rev * ocv1.ClusterExtensionRevision ) (io.Reader , error ) {
484+ printer := printers.YAMLPrinter {}
485+ buf := new (bytes.Buffer )
486+ for _ , obj := range getObjects (rev ) {
487+ buf .WriteString ("---\n " )
488+ if err := printer .PrintObj (obj , buf ); err != nil {
489+ return nil , err
490+ }
491+ }
492+ return buf , 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