@@ -16,17 +16,24 @@ import (
16
16
"helm.sh/helm/v3/pkg/release"
17
17
"helm.sh/helm/v3/pkg/storage/driver"
18
18
corev1 "k8s.io/api/core/v1"
19
+ v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
19
20
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
20
21
apimachyaml "k8s.io/apimachinery/pkg/util/yaml"
22
+ "k8s.io/apiserver/pkg/authorization/authorizer"
23
+ "k8s.io/client-go/rest"
21
24
"sigs.k8s.io/controller-runtime/pkg/client"
22
25
"sigs.k8s.io/controller-runtime/pkg/log"
23
26
24
27
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"
25
31
26
32
ocv1 "github.com/operator-framework/operator-controller/api/v1"
27
33
"github.com/operator-framework/operator-controller/internal/rukpak/convert"
28
34
"github.com/operator-framework/operator-controller/internal/rukpak/preflights/crdupgradesafety"
29
35
"github.com/operator-framework/operator-controller/internal/rukpak/util"
36
+ rbacauthorizer "k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac"
30
37
)
31
38
32
39
const (
@@ -52,9 +59,24 @@ type Preflight interface {
52
59
Upgrade (context.Context , * release.Release ) error
53
60
}
54
61
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
+
55
69
type Helm struct {
56
70
ActionClientGetter helmclient.ActionClientGetter
57
71
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
+ }
58
80
}
59
81
60
82
// 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
93
115
labels : objectLabels ,
94
116
}
95
117
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
+
96
133
rel , desiredRel , state , err := h .getReleaseState (ac , ext , chrt , values , post )
97
134
if err != nil {
98
135
return nil , "" , err
@@ -151,8 +188,103 @@ func (h *Helm) Apply(ctx context.Context, contentFS fs.FS, ext *ocv1.ClusterExte
151
188
return relObjects , state , nil
152
189
}
153
190
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
+
154
285
func (h * Helm ) getReleaseState (cl helmclient.ActionInterface , ext * ocv1.ClusterExtension , chrt * chart.Chart , values chartutil.Values , post postrender.PostRenderer ) (* release.Release , * release.Release , string , error ) {
155
286
currentRelease , err := cl .Get (ext .GetName ())
287
+
156
288
if errors .Is (err , driver .ErrReleaseNotFound ) {
157
289
desiredRelease , err := cl .Install (ext .GetName (), ext .Spec .Namespace , chrt , values , func (i * action.Install ) error {
158
290
i .DryRun = true
0 commit comments