@@ -16,17 +16,24 @@ import (
1616 "helm.sh/helm/v3/pkg/release"
1717 "helm.sh/helm/v3/pkg/storage/driver"
1818 corev1 "k8s.io/api/core/v1"
19+ v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1920 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2021 apimachyaml "k8s.io/apimachinery/pkg/util/yaml"
22+ "k8s.io/apiserver/pkg/authorization/authorizer"
23+ "k8s.io/client-go/rest"
2124 "sigs.k8s.io/controller-runtime/pkg/client"
2225 "sigs.k8s.io/controller-runtime/pkg/log"
2326
2427 helmclient "github.com/operator-framework/helm-operator-plugins/pkg/client"
28+ authv1 "k8s.io/api/authorization/v1"
29+ rbacv1 "k8s.io/api/rbac/v1"
30+ authorizationv1client "k8s.io/client-go/kubernetes/typed/authorization/v1"
2531
2632 ocv1 "github.com/operator-framework/operator-controller/api/v1"
2733 "github.com/operator-framework/operator-controller/internal/rukpak/convert"
2834 "github.com/operator-framework/operator-controller/internal/rukpak/preflights/crdupgradesafety"
2935 "github.com/operator-framework/operator-controller/internal/rukpak/util"
36+ rbacauthorizer "k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac"
3037)
3138
3239const (
@@ -52,9 +59,24 @@ type Preflight interface {
5259 Upgrade (context.Context , * release.Release ) error
5360}
5461
62+ type RestConfigMapper func (context.Context , client.Object , * rest.Config ) (* rest.Config , error )
63+
64+ type AuthClientMapper struct {
65+ rcm RestConfigMapper
66+ baseCfg * rest.Config
67+ }
68+
5569type Helm struct {
5670 ActionClientGetter helmclient.ActionClientGetter
5771 Preflights []Preflight
72+ AuthClientMapper AuthClientMapper
73+ }
74+
75+ func NewAuthClientMapper (rcm RestConfigMapper , baseCfg * rest.Config ) AuthClientMapper {
76+ return AuthClientMapper {
77+ rcm : rcm ,
78+ baseCfg : baseCfg ,
79+ }
5880}
5981
6082// shouldSkipPreflight is a helper to determine if the preflight check is CRDUpgradeSafety AND
@@ -93,6 +115,21 @@ func (h *Helm) Apply(ctx context.Context, contentFS fs.FS, ext *ocv1.ClusterExte
93115 labels : objectLabels ,
94116 }
95117
118+ authcfg , err := h .AuthClientMapper .rcm (ctx , ext , h .AuthClientMapper .baseCfg )
119+ if err != nil {
120+ return nil , "" , err
121+ }
122+
123+ authclient , err := authorizationv1client .NewForConfig (authcfg )
124+ if err != nil {
125+ return nil , "" , err
126+ }
127+
128+ err = h .checkGetPermissions (ctx , authclient , ac , ext , chrt , values , post )
129+ if err != nil {
130+ return nil , "" , err
131+ }
132+
96133 rel , desiredRel , state , err := h .getReleaseState (ac , ext , chrt , values , post )
97134 if err != nil {
98135 return nil , "" , err
@@ -151,8 +188,103 @@ func (h *Helm) Apply(ctx context.Context, contentFS fs.FS, ext *ocv1.ClusterExte
151188 return relObjects , state , nil
152189}
153190
191+ func (h * Helm ) checkGetPermissions (ctx context.Context , authcl * authorizationv1client.AuthorizationV1Client , cl helmclient.ActionInterface , ext * ocv1.ClusterExtension , chrt * chart.Chart , values chartutil.Values , post postrender.PostRenderer ) error {
192+ // client-only dry run
193+ clientDryRunRelease , err := cl .Install (ext .GetName (), ext .Spec .Namespace , chrt , values , func (i * action.Install ) error {
194+ i .DryRun = true
195+ i .DryRunOption = "client"
196+ return nil
197+ }, helmclient .AppendInstallPostRenderer (post ))
198+ if err != nil {
199+ return err
200+ }
201+ objects , err := util .ManifestObjects (strings .NewReader (clientDryRunRelease .Manifest ), fmt .Sprintf ("%s-release-manifest" , clientDryRunRelease .Name ))
202+
203+ if err != nil {
204+ return err
205+ }
206+
207+ ssrr := & authv1.SelfSubjectRulesReview {
208+ Spec : authv1.SelfSubjectRulesReviewSpec {
209+ Namespace : ext .Spec .Namespace ,
210+ },
211+ }
212+
213+ ssrr , err = authcl .SelfSubjectRulesReviews ().Create (ctx , ssrr , v1.CreateOptions {})
214+ if err != nil {
215+ return err
216+ }
217+
218+ rules := []rbacv1.PolicyRule {}
219+ for _ , rule := range ssrr .Status .ResourceRules {
220+ rules = append (rules , rbacv1.PolicyRule {
221+ Verbs : rule .Verbs ,
222+ APIGroups : rule .APIGroups ,
223+ Resources : rule .Resources ,
224+ ResourceNames : rule .ResourceNames ,
225+ })
226+ }
227+
228+ for _ , rule := range ssrr .Status .NonResourceRules {
229+ rules = append (rules , rbacv1.PolicyRule {
230+ Verbs : rule .Verbs ,
231+ NonResourceURLs : rule .NonResourceURLs ,
232+ })
233+ }
234+
235+ resAttrs := []authorizer.AttributesRecord {}
236+ errs := []error {}
237+
238+ checked := make (map [string ]bool )
239+ for _ , o := range objects {
240+ if ! checked [o .GetObjectKind ().GroupVersionKind ().String ()] {
241+ checked [o .GetObjectKind ().GroupVersionKind ().String ()] = true
242+ resAttrs = append (resAttrs , authorizer.AttributesRecord {
243+ Namespace : o .GetNamespace (),
244+ Verb : "get" ,
245+ APIGroup : o .GetObjectKind ().GroupVersionKind ().Group ,
246+ APIVersion : o .GetObjectKind ().GroupVersionKind ().Version ,
247+ Resource : o .GetObjectKind ().GroupVersionKind ().Kind ,
248+ ResourceRequest : true ,
249+ })
250+ }
251+ }
252+
253+ for _ , resAttr := range resAttrs {
254+ if ! rbacauthorizer .RulesAllow (resAttr , rules ... ) {
255+ errs = append (errs , fmt .Errorf ("%s is not permitted to get %ss" ,
256+ ext .Spec .ServiceAccount .Name ,
257+ resAttr .Resource ))
258+ }
259+ }
260+ if len (errs ) > 0 {
261+ errs = append ([]error {fmt .Errorf ("installer service account %s is missing required get permissions" , ext .Spec .ServiceAccount .Name )}, errs ... )
262+ }
263+
264+ return errors .Join (errs ... )
265+
266+ // for _, o := range objects {
267+ // ssar := &authv1.SelfSubjectAccessReview{
268+ // Spec: authv1.SelfSubjectAccessReviewSpec{
269+ // ResourceAttributes: &authv1.ResourceAttributes{
270+ // Namespace: ext.Spec.Namespace,
271+ // Verb: "get",
272+ // Resource: o.GetObjectKind().GroupVersionKind().Kind,
273+ // Group: o.GetObjectKind().GroupVersionKind().Group,
274+ // },
275+ // },
276+ // }
277+ // ssar, err = authcl.SelfSubjectAccessReviews().Create(ctx, ssar, v1.CreateOptions{})
278+ // if err != nil {
279+ // return err
280+ // }
281+ // }
282+
283+ }
284+
154285func (h * Helm ) getReleaseState (cl helmclient.ActionInterface , ext * ocv1.ClusterExtension , chrt * chart.Chart , values chartutil.Values , post postrender.PostRenderer ) (* release.Release , * release.Release , string , error ) {
155286 currentRelease , err := cl .Get (ext .GetName ())
287+
156288 if errors .Is (err , driver .ErrReleaseNotFound ) {
157289 desiredRelease , err := cl .Install (ext .GetName (), ext .Spec .Namespace , chrt , values , func (i * action.Install ) error {
158290 i .DryRun = true
0 commit comments