Skip to content

Commit dfc5ccb

Browse files
committed
auth: use synthetic user/group when service account is not defined
Signed-off-by: Joe Lanford <[email protected]>
1 parent 1573846 commit dfc5ccb

14 files changed

+165
-222
lines changed

api/v1/clusterextension_types.go

+12-3
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,19 @@ type ClusterExtensionSpec struct {
6666
// with the cluster that are required to manage the extension.
6767
// The ServiceAccount must be configured with the necessary permissions to perform these interactions.
6868
// The ServiceAccount must exist in the namespace referenced in the spec.
69-
// serviceAccount is required.
7069
//
71-
// +kubebuilder:validation:Required
72-
ServiceAccount ServiceAccountReference `json:"serviceAccount"`
70+
// serviceAccount is optional. If a service account is not defined, requests to the apiserver will instead use
71+
// username "olmv1:clusterextensions:<clusterExtension.metadata.name>:admin" and groups
72+
// "olmv1:clusterextensions:admin" and "system:authenticated"
73+
//
74+
// Deprecated: Use of serviceAccount is not recommended. Instead, administrators are encouraged
75+
// to use the synthetic user/groups described above. All of the same RBAC setup is still required with these
76+
// synthetic user/groups. However, this mode is preferred because it requires administrators to specifically
77+
// configure RBAC for extension management, rather than enabling piggybacking on existing highly privileged
78+
// service accounts that already exist on the cluster.
79+
//
80+
// +optional
81+
ServiceAccount *ServiceAccountReference `json:"serviceAccount"`
7382

7483
// source is a required field which selects the installation source of content
7584
// for this ClusterExtension. Selection is performed by setting the sourceType.

api/v1/zz_generated.deepcopy.go

+5-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmd/operator-controller/main.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ func run() error {
298298
return err
299299
}
300300
tokenGetter := authentication.NewTokenGetter(coreClient, authentication.WithExpirationDuration(1*time.Hour))
301-
clientRestConfigMapper := action.ServiceAccountRestConfigMapper(tokenGetter)
301+
clientRestConfigMapper := action.ClusterExtensionUserRestConfigMapper(tokenGetter)
302302

303303
cfgGetter, err := helmclient.NewActionConfigGetter(mgr.GetConfig(), mgr.GetRESTMapper(),
304304
helmclient.StorageDriverMapper(action.ChunkedStorageDriverMapper(coreClient, mgr.GetAPIReader(), cfg.systemNamespace)),

config/base/operator-controller/crd/bases/olm.operatorframework.io_clusterextensions.yaml

+10-2
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,16 @@ spec:
135135
with the cluster that are required to manage the extension.
136136
The ServiceAccount must be configured with the necessary permissions to perform these interactions.
137137
The ServiceAccount must exist in the namespace referenced in the spec.
138-
serviceAccount is required.
138+
139+
serviceAccount is optional. If a service account is not defined, requests to the apiserver will instead use
140+
username "olmv1:clusterextensions:<clusterExtension.metadata.name>:admin" and groups
141+
"olmv1:clusterextensions:admin" and "system:authenticated"
142+
143+
Deprecated: Use of serviceAccount is not recommended. Instead, administrators are encouraged
144+
to use the synthetic user/groups described above. All of the same RBAC setup is still required with these
145+
synthetic user/groups. However, this mode is preferred because it requires administrators to specifically
146+
configure RBAC for extension management, rather than enabling piggybacking on existing highly privileged
147+
service accounts that already exist on the cluster.
139148
properties:
140149
name:
141150
description: |-
@@ -458,7 +467,6 @@ spec:
458467
has(self.catalog) : !has(self.catalog)'
459468
required:
460469
- namespace
461-
- serviceAccount
462470
- source
463471
type: object
464472
status:

config/base/operator-controller/rbac/role.yaml

+7
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@ kind: ClusterRole
44
metadata:
55
name: manager-role
66
rules:
7+
- apiGroups:
8+
- ""
9+
resources:
10+
- groups
11+
- users
12+
verbs:
13+
- impersonate
714
- apiGroups:
815
- ""
916
resources:

config/samples/olm_v1_clusterextension.yaml

+11-22
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,7 @@
22
apiVersion: v1
33
kind: Namespace
44
metadata:
5-
name: argocd
6-
---
7-
apiVersion: v1
8-
kind: ServiceAccount
9-
metadata:
10-
name: argocd-installer
11-
namespace: argocd
5+
name: argocd-system
126
---
137
apiVersion: rbac.authorization.k8s.io/v1
148
kind: ClusterRoleBinding
@@ -19,9 +13,8 @@ roleRef:
1913
kind: ClusterRole
2014
name: argocd-installer-clusterrole
2115
subjects:
22-
- kind: ServiceAccount
23-
name: argocd-installer
24-
namespace: argocd
16+
- kind: User
17+
name: "olmv1:clusterextensions:argocd-operator:admin"
2518
---
2619
apiVersion: rbac.authorization.k8s.io/v1
2720
kind: ClusterRole
@@ -76,9 +69,8 @@ roleRef:
7669
kind: ClusterRole
7770
name: argocd-installer-rbac-clusterrole
7871
subjects:
79-
- kind: ServiceAccount
80-
name: argocd-installer
81-
namespace: argocd
72+
- kind: User
73+
name: "olmv1:clusterextensions:argocd-operator:admin"
8274
---
8375
apiVersion: rbac.authorization.k8s.io/v1
8476
kind: ClusterRole
@@ -222,7 +214,7 @@ apiVersion: rbac.authorization.k8s.io/v1
222214
kind: Role
223215
metadata:
224216
name: argocd-installer-role
225-
namespace: argocd
217+
namespace: argocd-system
226218
rules:
227219
- apiGroups: [""]
228220
resources: [serviceaccounts]
@@ -260,24 +252,21 @@ apiVersion: rbac.authorization.k8s.io/v1
260252
kind: RoleBinding
261253
metadata:
262254
name: argocd-installer-binding
263-
namespace: argocd
255+
namespace: argocd-system
264256
roleRef:
265257
apiGroup: rbac.authorization.k8s.io
266258
kind: Role
267259
name: argocd-installer-role
268260
subjects:
269-
- kind: ServiceAccount
270-
name: argocd-installer
271-
namespace: argocd
261+
- kind: User
262+
name: "olmv1:clusterextensions:argocd-operator:admin"
272263
---
273264
apiVersion: olm.operatorframework.io/v1
274265
kind: ClusterExtension
275266
metadata:
276-
name: argocd
267+
name: argocd-operator
277268
spec:
278-
namespace: argocd
279-
serviceAccount:
280-
name: argocd-installer
269+
namespace: argocd-system
281270
source:
282271
sourceType: Catalog
283272
catalog:

docs/api-reference/operator-controller-api-reference.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ _Appears in:_
306306
| Field | Description | Default | Validation |
307307
| --- | --- | --- | --- |
308308
| `namespace` _string_ | namespace is a reference to a Kubernetes namespace.<br />This is the namespace in which the provided ServiceAccount must exist.<br />It also designates the default namespace where namespace-scoped resources<br />for the extension are applied to the cluster.<br />Some extensions may contain namespace-scoped resources to be applied in other namespaces.<br />This namespace must exist.<br /><br />namespace is required, immutable, and follows the DNS label standard<br />as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters or hyphens (-),<br />start and end with an alphanumeric character, and be no longer than 63 characters<br /><br />[RFC 1123]: https://tools.ietf.org/html/rfc1123 | | MaxLength: 63 <br />Required: \{\} <br /> |
309-
| `serviceAccount` _[ServiceAccountReference](#serviceaccountreference)_ | serviceAccount is a reference to a ServiceAccount used to perform all interactions<br />with the cluster that are required to manage the extension.<br />The ServiceAccount must be configured with the necessary permissions to perform these interactions.<br />The ServiceAccount must exist in the namespace referenced in the spec.<br />serviceAccount is required. | | Required: \{\} <br /> |
309+
| `serviceAccount` _[ServiceAccountReference](#serviceaccountreference)_ | serviceAccount is a reference to a ServiceAccount used to perform all interactions<br />with the cluster that are required to manage the extension.<br />The ServiceAccount must be configured with the necessary permissions to perform these interactions.<br />The ServiceAccount must exist in the namespace referenced in the spec.<br /><br />serviceAccount is optional. If a service account is not defined, a synthetic user (with synthetic groups)<br />will be used to manage the extension. This synthetic user will be configured based on the name and namespace<br />of the cluster extension as follows:<br /> username:<br /> olmv1:clusterextension:<clusterExtension.metadata.name>:admin<br /> groups:<br /> olmv1:clusterextensions:admin | | |
310310
| `source` _[SourceConfig](#sourceconfig)_ | source is a required field which selects the installation source of content<br />for this ClusterExtension. Selection is performed by setting the sourceType.<br /><br />Catalog is currently the only implemented sourceType, and setting the<br />sourcetype to "Catalog" requires the catalog field to also be defined.<br /><br />Below is a minimal example of a source definition (in yaml):<br /><br />source:<br /> sourceType: Catalog<br /> catalog:<br /> packageName: example-package | | Required: \{\} <br /> |
311311
| `install` _[ClusterExtensionInstallConfig](#clusterextensioninstallconfig)_ | install is an optional field used to configure the installation options<br />for the ClusterExtension such as the pre-flight check configuration. | | |
312312

internal/operator-controller/action/restconfig.go

+35-2
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,54 @@ package action
22

33
import (
44
"context"
5+
"fmt"
56
"net/http"
67

78
"k8s.io/apimachinery/pkg/types"
89
"k8s.io/client-go/rest"
10+
"k8s.io/client-go/transport"
911
"sigs.k8s.io/controller-runtime/pkg/client"
1012

1113
ocv1 "github.com/operator-framework/operator-controller/api/v1"
1214
"github.com/operator-framework/operator-controller/internal/operator-controller/authentication"
1315
)
1416

15-
func ServiceAccountRestConfigMapper(tokenGetter *authentication.TokenGetter) func(ctx context.Context, o client.Object, c *rest.Config) (*rest.Config, error) {
17+
func ClusterExtensionUserRestConfigMapper(tokenGetter *authentication.TokenGetter) func(ctx context.Context, o client.Object, c *rest.Config) (*rest.Config, error) {
18+
saRestConfigMapper := serviceAccountRestConfigMapper(tokenGetter)
19+
synthRestConfigMapper := sythenticUserRestConfigMapper()
20+
21+
return func(ctx context.Context, o client.Object, c *rest.Config) (*rest.Config, error) {
22+
cExt := o.(*ocv1.ClusterExtension)
23+
if cExt.Spec.ServiceAccount != nil { //nolint:staticcheck
24+
return saRestConfigMapper(ctx, o, c)
25+
}
26+
return synthRestConfigMapper(ctx, o, c)
27+
}
28+
}
29+
30+
func sythenticUserRestConfigMapper() func(ctx context.Context, o client.Object, c *rest.Config) (*rest.Config, error) {
31+
return func(ctx context.Context, o client.Object, c *rest.Config) (*rest.Config, error) {
32+
cExt := o.(*ocv1.ClusterExtension)
33+
cc := rest.CopyConfig(c)
34+
cc.Wrap(func(rt http.RoundTripper) http.RoundTripper {
35+
impersonateConfig := transport.ImpersonationConfig{
36+
UserName: fmt.Sprintf("olmv1:clusterextensions:%s:admin", cExt.Name),
37+
Groups: []string{
38+
"system:authenticated",
39+
"olmv1:clusterextensions:admin",
40+
},
41+
}
42+
return transport.NewImpersonatingRoundTripper(impersonateConfig, rt)
43+
})
44+
return cc, nil
45+
}
46+
}
47+
48+
func serviceAccountRestConfigMapper(tokenGetter *authentication.TokenGetter) func(ctx context.Context, o client.Object, c *rest.Config) (*rest.Config, error) {
1649
return func(ctx context.Context, o client.Object, c *rest.Config) (*rest.Config, error) {
1750
cExt := o.(*ocv1.ClusterExtension)
1851
saKey := types.NamespacedName{
19-
Name: cExt.Spec.ServiceAccount.Name,
52+
Name: cExt.Spec.ServiceAccount.Name, //nolint:staticcheck
2053
Namespace: cExt.Spec.Namespace,
2154
}
2255
saConfig := rest.AnonymousClientConfig(c)

internal/operator-controller/controllers/clusterextension_admission_test.go

+2-23
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,6 @@ func TestClusterExtensionSourceConfig(t *testing.T) {
4444
},
4545
},
4646
Namespace: "default",
47-
ServiceAccount: ocv1.ServiceAccountReference{
48-
Name: "default",
49-
},
5047
}))
5148
}
5249
if tc.unionField == "" {
@@ -55,9 +52,6 @@ func TestClusterExtensionSourceConfig(t *testing.T) {
5552
SourceType: tc.sourceType,
5653
},
5754
Namespace: "default",
58-
ServiceAccount: ocv1.ServiceAccountReference{
59-
Name: "default",
60-
},
6155
}))
6256
}
6357

@@ -114,9 +108,6 @@ func TestClusterExtensionAdmissionPackageName(t *testing.T) {
114108
},
115109
},
116110
Namespace: "default",
117-
ServiceAccount: ocv1.ServiceAccountReference{
118-
Name: "default",
119-
},
120111
}))
121112
if tc.errMsg == "" {
122113
require.NoError(t, err, "unexpected error for package name %q: %w", tc.pkgName, err)
@@ -212,9 +203,6 @@ func TestClusterExtensionAdmissionVersion(t *testing.T) {
212203
},
213204
},
214205
Namespace: "default",
215-
ServiceAccount: ocv1.ServiceAccountReference{
216-
Name: "default",
217-
},
218206
}))
219207
if tc.errMsg == "" {
220208
require.NoError(t, err, "unexpected error for version %q: %w", tc.version, err)
@@ -267,9 +255,6 @@ func TestClusterExtensionAdmissionChannel(t *testing.T) {
267255
},
268256
},
269257
Namespace: "default",
270-
ServiceAccount: ocv1.ServiceAccountReference{
271-
Name: "default",
272-
},
273258
}))
274259
if tc.errMsg == "" {
275260
require.NoError(t, err, "unexpected error for channel %q: %w", tc.channels, err)
@@ -320,9 +305,6 @@ func TestClusterExtensionAdmissionInstallNamespace(t *testing.T) {
320305
},
321306
},
322307
Namespace: tc.namespace,
323-
ServiceAccount: ocv1.ServiceAccountReference{
324-
Name: "default",
325-
},
326308
}))
327309
if tc.errMsg == "" {
328310
require.NoError(t, err, "unexpected error for namespace %q: %w", tc.namespace, err)
@@ -374,7 +356,7 @@ func TestClusterExtensionAdmissionServiceAccount(t *testing.T) {
374356
},
375357
},
376358
Namespace: "default",
377-
ServiceAccount: ocv1.ServiceAccountReference{
359+
ServiceAccount: &ocv1.ServiceAccountReference{
378360
Name: tc.serviceAccount,
379361
},
380362
}))
@@ -433,10 +415,7 @@ func TestClusterExtensionAdmissionInstall(t *testing.T) {
433415
},
434416
},
435417
Namespace: "default",
436-
ServiceAccount: ocv1.ServiceAccountReference{
437-
Name: "default",
438-
},
439-
Install: tc.installConfig,
418+
Install: tc.installConfig,
440419
}))
441420
if tc.errMsg == "" {
442421
require.NoError(t, err, "unexpected error for install configuration %v: %w", tc.installConfig, err)

internal/operator-controller/controllers/clusterextension_controller.go

+1
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ type InstalledBundleGetter interface {
9595
//+kubebuilder:rbac:groups=olm.operatorframework.io,resources=clusterextensions/finalizers,verbs=update
9696
//+kubebuilder:rbac:namespace=system,groups=core,resources=secrets,verbs=create;update;patch;delete;deletecollection;get;list;watch
9797
//+kubebuilder:rbac:groups=core,resources=serviceaccounts/token,verbs=create
98+
//+kubebuilder:rbac:groups=core,resources=users;groups,verbs=impersonate
9899
//+kubebuilder:rbac:groups=apiextensions.k8s.io,resources=customresourcedefinitions,verbs=get
99100

100101
//+kubebuilder:rbac:groups=olm.operatorframework.io,resources=clustercatalogs,verbs=list;watch

0 commit comments

Comments
 (0)