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)
@@ -283,28 +289,27 @@ type Boxcutter struct {
283289 Scheme * runtime.Scheme
284290 RevisionGenerator ClusterExtensionRevisionGenerator
285291 Preflights []Preflight
292+ PreAuthorizer authorization.PreAuthorizer
286293 FieldOwner string
287294}
288295
289- func (bc * Boxcutter ) getObjects (rev * ocv1.ClusterExtensionRevision ) []client.Object {
290- var objs []client.Object
291- for _ , phase := range rev .Spec .Phases {
292- for _ , phaseObject := range phase .Objects {
293- objs = append (objs , & phaseObject .Object )
294- }
295- }
296- return objs
297- }
298-
299- func (bc * Boxcutter ) createOrUpdate (ctx context.Context , obj client.Object ) error {
300- if obj .GetObjectKind ().GroupVersionKind ().Empty () {
301- gvk , err := apiutil .GVKForObject (obj , bc .Scheme )
296+ // createOrUpdate creates or updates the revision object. PreAuthorization checks are performed to ensure the
297+ // user has sufficient permissions to manage the revision and its resources
298+ func (bc * Boxcutter ) createOrUpdate (ctx context.Context , user user.Info , rev * ocv1.ClusterExtensionRevision ) error {
299+ if rev .GetObjectKind ().GroupVersionKind ().Empty () {
300+ gvk , err := apiutil .GVKForObject (rev , bc .Scheme )
302301 if err != nil {
303302 return err
304303 }
305- obj .GetObjectKind ().SetGroupVersionKind (gvk )
304+ rev .GetObjectKind ().SetGroupVersionKind (gvk )
306305 }
307- return bc .Client .Patch (ctx , obj , client .Apply , client .FieldOwner (bc .FieldOwner ), client .ForceOwnership )
306+
307+ // Run auth preflight checks
308+ if err := bc .runPreAuthorizationChecks (ctx , user , rev ); err != nil {
309+ return err
310+ }
311+
312+ return bc .Client .Patch (ctx , rev , client .Apply , client .FieldOwner (bc .FieldOwner ), client .ForceOwnership )
308313}
309314
310315func (bc * Boxcutter ) Apply (ctx context.Context , contentFS fs.FS , ext * ocv1.ClusterExtension , objectLabels , revisionAnnotations map [string ]string ) error {
@@ -333,7 +338,7 @@ func (bc *Boxcutter) Apply(ctx context.Context, contentFS fs.FS, ext *ocv1.Clust
333338 desiredRevision .Spec .Revision = currentRevision .Spec .Revision
334339 desiredRevision .Name = currentRevision .Name
335340
336- err := bc .createOrUpdate (ctx , desiredRevision )
341+ err := bc .createOrUpdate (ctx , getUserInfo ( ext ), desiredRevision )
337342 switch {
338343 case apierrors .IsInvalid (err ):
339344 // We could not update the current revision due to trying to update an immutable field.
@@ -348,7 +353,7 @@ func (bc *Boxcutter) Apply(ctx context.Context, contentFS fs.FS, ext *ocv1.Clust
348353 }
349354
350355 // Preflights
351- plainObjs := bc . getObjects (desiredRevision )
356+ plainObjs := getObjects (desiredRevision )
352357 for _ , preflight := range bc .Preflights {
353358 if shouldSkipPreflight (ctx , preflight , ext , state ) {
354359 continue
@@ -383,14 +388,31 @@ func (bc *Boxcutter) Apply(ctx context.Context, contentFS fs.FS, ext *ocv1.Clust
383388 return fmt .Errorf ("garbage collecting old revisions: %w" , err )
384389 }
385390
386- if err := bc .createOrUpdate (ctx , desiredRevision ); err != nil {
391+ if err := bc .createOrUpdate (ctx , getUserInfo ( ext ), desiredRevision ); err != nil {
387392 return fmt .Errorf ("creating new Revision: %w" , err )
388393 }
389394 }
390395
391396 return nil
392397}
393398
399+ // runPreAuthorizationChecks runs PreAuthorization checks if the PreAuthorizer is set. An error will be returned if
400+ // the ClusterExtension service account does not have the necessary permissions to manage the revision's resources
401+ func (bc * Boxcutter ) runPreAuthorizationChecks (ctx context.Context , user user.Info , rev * ocv1.ClusterExtensionRevision ) error {
402+ if bc .PreAuthorizer == nil {
403+ return nil
404+ }
405+
406+ // collect the revision manifests
407+ manifestReader , err := revisionManifestReader (rev )
408+ if err != nil {
409+ return err
410+ }
411+
412+ // run preauthorization check
413+ return formatPreAuthorizerOutput (bc .PreAuthorizer .PreAuthorize (ctx , user , manifestReader , revisionManagementPerms (rev )))
414+ }
415+
394416// garbageCollectOldRevisions deletes archived revisions beyond ClusterExtensionRevisionRetentionLimit.
395417// Active revisions are never deleted. revisionList must be sorted oldest to newest.
396418func (bc * Boxcutter ) garbageCollectOldRevisions (ctx context.Context , revisionList []ocv1.ClusterExtensionRevision ) error {
@@ -449,3 +471,43 @@ func splitManifestDocuments(file string) []string {
449471 }
450472 return docs
451473}
474+
475+ // getObjects returns a slice of all objects in the revision
476+ func getObjects (rev * ocv1.ClusterExtensionRevision ) []client.Object {
477+ var objs []client.Object
478+ for _ , phase := range rev .Spec .Phases {
479+ for _ , phaseObject := range phase .Objects {
480+ objs = append (objs , & phaseObject .Object )
481+ }
482+ }
483+ return objs
484+ }
485+
486+ // revisionManifestReader returns an io.Reader containing all manifests in the revision
487+ func revisionManifestReader (rev * ocv1.ClusterExtensionRevision ) (io.Reader , error ) {
488+ printer := printers.YAMLPrinter {}
489+ buf := new (bytes.Buffer )
490+ for _ , obj := range getObjects (rev ) {
491+ buf .WriteString ("---\n " )
492+ if err := printer .PrintObj (obj , buf ); err != nil {
493+ return nil , err
494+ }
495+ }
496+ return buf , nil
497+ }
498+
499+ func revisionManagementPerms (rev * ocv1.ClusterExtensionRevision ) func (user.Info ) []authorizer.AttributesRecord {
500+ return func (user user.Info ) []authorizer.AttributesRecord {
501+ return []authorizer.AttributesRecord {
502+ {
503+ User : user ,
504+ Name : rev .Name ,
505+ APIGroup : ocv1 .GroupVersion .Group ,
506+ APIVersion : ocv1 .GroupVersion .Version ,
507+ Resource : "clusterextensionrevisions/finalizers" ,
508+ ResourceRequest : true ,
509+ Verb : "update" ,
510+ },
511+ }
512+ }
513+ }
0 commit comments