@@ -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,38 @@ 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
+
69
+ func (acm * AuthClientMapper ) GetAuthenticationClient (ctx context.Context , ext * ocv1.ClusterExtension ) (* authorizationv1client.AuthorizationV1Client , error ) {
70
+ authcfg , err := acm .rcm (ctx , ext , acm .baseCfg )
71
+ if err != nil {
72
+ return nil , err
73
+ }
74
+
75
+ authclient , err := authorizationv1client .NewForConfig (authcfg )
76
+ if err != nil {
77
+ return nil , err
78
+ }
79
+
80
+ return authclient , nil
81
+ }
82
+
55
83
type Helm struct {
56
84
ActionClientGetter helmclient.ActionClientGetter
57
85
Preflights []Preflight
86
+ AuthClientMapper AuthClientMapper
87
+ }
88
+
89
+ func NewAuthClientMapper (rcm RestConfigMapper , baseCfg * rest.Config ) AuthClientMapper {
90
+ return AuthClientMapper {
91
+ rcm : rcm ,
92
+ baseCfg : baseCfg ,
93
+ }
58
94
}
59
95
60
96
// shouldSkipPreflight is a helper to determine if the preflight check is CRDUpgradeSafety AND
@@ -93,6 +129,16 @@ func (h *Helm) Apply(ctx context.Context, contentFS fs.FS, ext *ocv1.ClusterExte
93
129
labels : objectLabels ,
94
130
}
95
131
132
+ authclient , err := h .AuthClientMapper .GetAuthenticationClient (ctx , ext )
133
+ if err != nil {
134
+ return nil , "" , err
135
+ }
136
+
137
+ err = h .checkGetPermissions (ctx , authclient , ac , ext , chrt , values , post )
138
+ if err != nil {
139
+ return nil , "" , err
140
+ }
141
+
96
142
rel , desiredRel , state , err := h .getReleaseState (ac , ext , chrt , values , post )
97
143
if err != nil {
98
144
return nil , "" , err
@@ -151,8 +197,101 @@ func (h *Helm) Apply(ctx context.Context, contentFS fs.FS, ext *ocv1.ClusterExte
151
197
return relObjects , state , nil
152
198
}
153
199
200
+ // re-org this to take in just the resulting manifest so I can use it for checking both the client-only and server-connnected dry-runs
201
+ 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 {
202
+ // client-only dry run
203
+ // plainObjs, err := convert.Convert(chrt, "", []string)
204
+ clientDryRunRelease , err := cl .Install (ext .GetName (), ext .Spec .Namespace , chrt , values , func (i * action.Install ) error {
205
+ i .DryRun = true
206
+ i .DryRunOption = "client"
207
+ return nil
208
+ }, helmclient .AppendInstallPostRenderer (post ))
209
+ if err != nil {
210
+ return err
211
+ }
212
+ objects , err := util .ManifestObjects (strings .NewReader (clientDryRunRelease .Manifest ), fmt .Sprintf ("%s-release-manifest" , clientDryRunRelease .Name ))
213
+
214
+ if err != nil {
215
+ return err
216
+ }
217
+
218
+ ssrr := & authv1.SelfSubjectRulesReview {
219
+ Spec : authv1.SelfSubjectRulesReviewSpec {
220
+ Namespace : ext .Spec .Namespace ,
221
+ },
222
+ }
223
+
224
+ ssrr , err = authcl .SelfSubjectRulesReviews ().Create (ctx , ssrr , v1.CreateOptions {})
225
+ if err != nil {
226
+ return err
227
+ }
228
+
229
+ rules := []rbacv1.PolicyRule {}
230
+ for _ , rule := range ssrr .Status .ResourceRules {
231
+ rules = append (rules , rbacv1.PolicyRule {
232
+ Verbs : rule .Verbs ,
233
+ APIGroups : rule .APIGroups ,
234
+ Resources : rule .Resources ,
235
+ ResourceNames : rule .ResourceNames ,
236
+ })
237
+ }
238
+
239
+ for _ , rule := range ssrr .Status .NonResourceRules {
240
+ rules = append (rules , rbacv1.PolicyRule {
241
+ Verbs : rule .Verbs ,
242
+ NonResourceURLs : rule .NonResourceURLs ,
243
+ })
244
+ }
245
+
246
+ resAttrs := []authorizer.AttributesRecord {}
247
+ errs := []error {}
248
+
249
+ for _ , o := range objects {
250
+ resAttrs = append (resAttrs , authorizer.AttributesRecord {
251
+ Namespace : o .GetNamespace (),
252
+ Verb : "get" ,
253
+ APIGroup : o .GetObjectKind ().GroupVersionKind ().Group ,
254
+ Resource : sanitizeResourceName (o .GetObjectKind ().GroupVersionKind ().Kind ),
255
+ Name : o .GetName (),
256
+ ResourceRequest : true ,
257
+ })
258
+ }
259
+
260
+ for _ , resAttr := range resAttrs {
261
+ if ! rbacauthorizer .RulesAllow (resAttr , rules ... ) {
262
+ errs = append (errs , fmt .Errorf ("serviceaccount %s cannot get %s in namespace %s" ,
263
+ ext .Spec .ServiceAccount .Name ,
264
+ resAttr .Resource ,
265
+ resAttr .Namespace ))
266
+ }
267
+ }
268
+ if len (errs ) > 0 {
269
+ errs = append ([]error {fmt .Errorf ("installer service account %s is missing required get permissions" , ext .Spec .ServiceAccount .Name )}, errs ... )
270
+ }
271
+
272
+ return errors .Join (errs ... )
273
+
274
+ // for _, o := range objects {
275
+ // ssar := &authv1.SelfSubjectAccessReview{
276
+ // Spec: authv1.SelfSubjectAccessReviewSpec{
277
+ // ResourceAttributes: &authv1.ResourceAttributes{
278
+ // Namespace: ext.Spec.Namespace,
279
+ // Verb: "get",
280
+ // Resource: o.GetObjectKind().GroupVersionKind().Kind,
281
+ // Group: o.GetObjectKind().GroupVersionKind().Group,
282
+ // },
283
+ // },
284
+ // }
285
+ // ssar, err = authcl.SelfSubjectAccessReviews().Create(ctx, ssar, v1.CreateOptions{})
286
+ // if err != nil {
287
+ // return err
288
+ // }
289
+ // }
290
+ }
291
+
154
292
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
293
currentRelease , err := cl .Get (ext .GetName ())
294
+
156
295
if errors .Is (err , driver .ErrReleaseNotFound ) {
157
296
desiredRelease , err := cl .Install (ext .GetName (), ext .Spec .Namespace , chrt , values , func (i * action.Install ) error {
158
297
i .DryRun = true
@@ -186,6 +325,11 @@ func (h *Helm) getReleaseState(cl helmclient.ActionInterface, ext *ocv1.ClusterE
186
325
return currentRelease , desiredRelease , relState , nil
187
326
}
188
327
328
+ // RulesAllow() checks expects resource names to be lowercase and plural, there's probably a better way to do this
329
+ func sanitizeResourceName (resourceName string ) string {
330
+ return strings .ToLower (resourceName ) + "s"
331
+ }
332
+
189
333
type postrenderer struct {
190
334
labels map [string ]string
191
335
cascade postrender.PostRenderer
0 commit comments