Skip to content

Commit a6027b8

Browse files
committed
Use ServiceAccount impersonation instead of token-based auth
Replace token-based ServiceAccount authentication with Kubernetes impersonation. This eliminates the need for ServiceAccount resources to exist in the cluster and improves security posture. Changes: - Replace TokenGetter/TokenInjectingRoundTripper with impersonation - Remove SyntheticPermissions feature gate and synthetic auth code - Update operator-controller to always use cluster-admin permissions (previously conditional on BoxcutterRuntime feature gate) - Update ClusterRole to grant full cluster-admin permissions instead of limited subset Security benefits: - ServiceAccount resources no longer need to exist in the cluster - Eliminates risk of highly-privileged ServiceAccounts being mounted by unintended or malicious pods in the same namespace - OLM impersonates the ServiceAccount name and is subject to RBAC permissions without requiring actual ServiceAccount credentials Documentation updates: - Update derive-service-account.md to explain impersonation and recommend NOT creating ServiceAccount resources - Update install-extension.md tutorial with security warnings - Remove ServiceAccount resource from sample YAML - Update API type definitions to remove "must exist" requirements - Regenerate CRDs and API reference docs Existing ClusterExtensions continue to work unchanged - the API is the same, and RBAC that was previously configured will continue to work exactly as before. Users can safely delete installer ServiceAccount resources after upgrading to this version of operator-controller.
1 parent c06f27f commit a6027b8

26 files changed

+140
-933
lines changed

api/v1/clusterextension_types.go

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ const (
4949
// ClusterExtensionSpec defines the desired state of ClusterExtension
5050
type ClusterExtensionSpec struct {
5151
// namespace is a reference to a Kubernetes namespace.
52-
// This is the namespace in which the provided ServiceAccount must exist.
53-
// It also designates the default namespace where namespace-scoped resources
52+
//
53+
// This designates the default namespace where namespace-scoped resources
5454
// for the extension are applied to the cluster.
5555
// Some extensions may contain namespace-scoped resources to be applied in other namespaces.
5656
// This namespace must exist.
@@ -69,8 +69,7 @@ type ClusterExtensionSpec struct {
6969

7070
// serviceAccount is a reference to a ServiceAccount used to perform all interactions
7171
// with the cluster that are required to manage the extension.
72-
// The ServiceAccount must be configured with the necessary permissions to perform these interactions.
73-
// The ServiceAccount must exist in the namespace referenced in the spec.
72+
// Appropriate RBAC must be configured to grant the necessary permissions to the ServiceAccount.
7473
// serviceAccount is required.
7574
//
7675
// +kubebuilder:validation:Required
@@ -374,14 +373,12 @@ type CatalogFilter struct {
374373
UpgradeConstraintPolicy UpgradeConstraintPolicy `json:"upgradeConstraintPolicy,omitempty"`
375374
}
376375

377-
// ServiceAccountReference identifies the serviceAccount used fo install a ClusterExtension.
376+
// ServiceAccountReference identifies the serviceAccount name used for managing a ClusterExtension.
378377
type ServiceAccountReference struct {
379378
// name is a required, immutable reference to the name of the ServiceAccount
380379
// to be used for installation and management of the content for the package
381380
// specified in the packageName field.
382381
//
383-
// This ServiceAccount must exist in the installNamespace.
384-
//
385382
// name follows the DNS subdomain standard as defined in [RFC 1123].
386383
// It must contain only lowercase alphanumeric characters,
387384
// hyphens (-) or periods (.), start and end with an alphanumeric character,

cmd/operator-controller/main.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ import (
6363
ocv1 "github.com/operator-framework/operator-controller/api/v1"
6464
"github.com/operator-framework/operator-controller/internal/operator-controller/action"
6565
"github.com/operator-framework/operator-controller/internal/operator-controller/applier"
66-
"github.com/operator-framework/operator-controller/internal/operator-controller/authentication"
6766
"github.com/operator-framework/operator-controller/internal/operator-controller/authorization"
6867
"github.com/operator-framework/operator-controller/internal/operator-controller/catalogmetadata/cache"
6968
catalogclient "github.com/operator-framework/operator-controller/internal/operator-controller/catalogmetadata/client"
@@ -645,11 +644,7 @@ func setupHelm(
645644
if err != nil {
646645
return fmt.Errorf("unable to create core client: %w", err)
647646
}
648-
tokenGetter := authentication.NewTokenGetter(coreClient, authentication.WithExpirationDuration(1*time.Hour))
649-
clientRestConfigMapper := action.ServiceAccountRestConfigMapper(tokenGetter)
650-
if features.OperatorControllerFeatureGate.Enabled(features.SyntheticPermissions) {
651-
clientRestConfigMapper = action.SyntheticUserRestConfigMapper(clientRestConfigMapper)
652-
}
647+
clientRestConfigMapper := action.ServiceAccountRestConfigMapper()
653648

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

config/samples/olm_v1_clusterextension.yaml

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ kind: Namespace
44
metadata:
55
name: argocd
66
---
7-
apiVersion: v1
8-
kind: ServiceAccount
9-
metadata:
10-
name: argocd-installer
11-
namespace: argocd
7+
# NOTE: The ServiceAccount resource is intentionally NOT created here.
8+
# OLM v1 uses Kubernetes impersonation and does not require the ServiceAccount to exist.
9+
# For security reasons, you should NOT create a ServiceAccount resource for the installer,
10+
# as it would be highly privileged and could be mounted by other pods in the namespace.
11+
# Instead, only the RBAC resources (ClusterRole, ClusterRoleBinding, Role, RoleBinding)
12+
# are created, which reference the ServiceAccount name "argocd-installer".
13+
# OLM will impersonate that ServiceAccount and be subject to these RBAC permissions.
1214
---
1315
apiVersion: rbac.authorization.k8s.io/v1
1416
kind: ClusterRoleBinding

docs/api-reference/olmv1-api-reference.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -339,8 +339,8 @@ _Appears in:_
339339

340340
| Field | Description | Default | Validation |
341341
| --- | --- | --- | --- |
342-
| `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 />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 />[RFC 1123]: https://tools.ietf.org/html/rfc1123 | | MaxLength: 63 <br />Required: \{\} <br /> |
343-
| `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 /> |
342+
| `namespace` _string_ | namespace is a reference to a Kubernetes namespace.<br />This 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 />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 />[RFC 1123]: https://tools.ietf.org/html/rfc1123 | | MaxLength: 63 <br />Required: \{\} <br /> |
343+
| `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 />Appropriate RBAC must be configured to grant the necessary permissions to the ServiceAccount.<br />serviceAccount is required. | | Required: \{\} <br /> |
344344
| `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 />Catalog is currently the only implemented sourceType, and setting the<br />sourcetype to "Catalog" requires the catalog field to also be defined.<br />Below is a minimal example of a source definition (in yaml):<br />source:<br /> sourceType: Catalog<br /> catalog:<br /> packageName: example-package | | Required: \{\} <br /> |
345345
| `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. | | |
346346
| `config` _[ClusterExtensionConfig](#clusterextensionconfig)_ | config is an optional field used to specify bundle specific configuration<br />used to configure the bundle. Configuration is bundle specific and a bundle may provide<br />a configuration schema. When not specified, the default configuration of the resolved bundle will be used.<br />config is validated against a configuration schema provided by the resolved bundle. If the bundle does not provide<br />a configuration schema the final manifests will be derived on a best-effort basis. More information on how<br />to configure the bundle should be found in its end-user documentation.<br /><opcon:experimental> | | |
@@ -439,7 +439,7 @@ _Appears in:_
439439

440440

441441

442-
ServiceAccountReference identifies the serviceAccount used fo install a ClusterExtension.
442+
ServiceAccountReference identifies the serviceAccount name used for managing a ClusterExtension.
443443

444444

445445

@@ -448,7 +448,7 @@ _Appears in:_
448448

449449
| Field | Description | Default | Validation |
450450
| --- | --- | --- | --- |
451-
| `name` _string_ | name is a required, immutable reference to the name of the ServiceAccount<br />to be used for installation and management of the content for the package<br />specified in the packageName field.<br />This ServiceAccount must exist in the installNamespace.<br />name follows the DNS subdomain standard as defined in [RFC 1123].<br />It must contain only lowercase alphanumeric characters,<br />hyphens (-) or periods (.), start and end with an alphanumeric character,<br />and be no longer than 253 characters.<br />Some examples of valid values are:<br /> - some-serviceaccount<br /> - 123-serviceaccount<br /> - 1-serviceaccount-2<br /> - someserviceaccount<br /> - some.serviceaccount<br />Some examples of invalid values are:<br /> - -some-serviceaccount<br /> - some-serviceaccount-<br />[RFC 1123]: https://tools.ietf.org/html/rfc1123 | | MaxLength: 253 <br />Required: \{\} <br /> |
451+
| `name` _string_ | name is a required, immutable reference to the name of the ServiceAccount<br />to be used for installation and management of the content for the package<br />specified in the packageName field.<br />name follows the DNS subdomain standard as defined in [RFC 1123].<br />It must contain only lowercase alphanumeric characters,<br />hyphens (-) or periods (.), start and end with an alphanumeric character,<br />and be no longer than 253 characters.<br />Some examples of valid values are:<br /> - some-serviceaccount<br /> - 123-serviceaccount<br /> - 1-serviceaccount-2<br /> - someserviceaccount<br /> - some.serviceaccount<br />Some examples of invalid values are:<br /> - -some-serviceaccount<br /> - some-serviceaccount-<br />[RFC 1123]: https://tools.ietf.org/html/rfc1123 | | MaxLength: 253 <br />Required: \{\} <br /> |
452452

453453

454454
#### SourceConfig

docs/draft/howto/use-synthetic-permissions.md

Lines changed: 0 additions & 133 deletions
This file was deleted.

docs/howto/derive-service-account.md

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
# Derive minimal ServiceAccount required for ClusterExtension Installation and Management
1+
# Derive minimal RBAC required for ClusterExtension Installation and Management
22

33
OLM v1 does not have permission to install extensions on a cluster by default. In order to install a [supported bundle](../project/olmv1_limitations.md),
4-
OLM must be provided a ServiceAccount configured with the appropriate permissions.
4+
OLM must be configured with the appropriate RBAC permissions via a ServiceAccount reference in the ClusterExtension.
5+
6+
!!! note "ServiceAccount Impersonation"
7+
OLM v1 uses Kubernetes impersonation to act as the specified ServiceAccount. This means **the ServiceAccount does not need to physically exist** as a resource in the cluster. However, you must still create the RBAC resources (ClusterRole, Role, ClusterRoleBinding, RoleBinding) that grant permissions to that ServiceAccount, as OLM will be subject to those same permissions when impersonating it.
58

69
This document serves as a guide for how to derive the RBAC necessary to install a bundle.
710

@@ -252,9 +255,12 @@ This example's `ClusterServiceVersion` can be found [here](https://github.com/ar
252255
#### Step 2: `ServiceAccount` permissions
253256

254257
The installer service account must be able to create and manage the `ServiceAccount`(s) for the extension controller(s).
255-
The `ServiceAccount` name(s) can be found in deployment template in the `ClusterServiceVersion` resource packed in the bundle under `.spec.install.deployments`.
258+
The `ServiceAccount` name(s) can be found in the deployment template in the `ClusterServiceVersion` resource packed in the bundle under `.spec.install.deployments`.
256259
This example's `ClusterServiceVersion` can be found [here](https://github.com/argoproj-labs/argocd-operator/blob/da6b8a7e68f71920de9545152714b9066990fc4b/deploy/olm-catalog/argocd-operator/0.6.0/argocd-operator.v0.6.0.clusterserviceversion.yaml).
257260

261+
!!! note
262+
These permissions are required for the installer to manage the extension controller's ServiceAccount, which is separate from the installer ServiceAccount referenced in the ClusterExtension spec.
263+
258264
```yaml
259265
- apiGroups: [""]
260266
resources: [serviceaccounts]
@@ -300,12 +306,14 @@ Therefore, the following permissions must be given to the installer service acco
300306
Once the installer service account required cluster-scoped and namespace-scoped permissions have been collected:
301307

302308
1. Create the installation namespace
303-
2. Create the installer `ServiceAccount`
304-
3. Create the installer `ClusterRole`
305-
4. Create the `ClusterRoleBinding` between the installer service account and its cluster role
306-
5. Create the installer `Role`
307-
6. Create the `RoleBinding` between the installer service account and its role
308-
7. Create the `ClusterExtension`
309+
2. Create the installer `ClusterRole`
310+
3. Create the `ClusterRoleBinding` referencing the installer service account name
311+
4. Create the installer `Role`
312+
5. Create the `RoleBinding` referencing the installer service account name
313+
6. Create the `ClusterExtension`
314+
315+
!!! warning "Do NOT Create the ServiceAccount Resource"
316+
**For security reasons, you should NOT create an actual ServiceAccount resource.** OLM v1 uses Kubernetes impersonation and does not require the ServiceAccount to exist as a cluster resource. Since the installer ServiceAccount is typically highly privileged, creating it as an actual resource would allow any pod in the same namespace to potentially mount and use that ServiceAccount's credentials. By relying on impersonation only, you eliminate this security risk while still maintaining the same permission boundaries through RBAC.
309317

310318
A manifest with the full set of resources can be found [here](https://github.com/operator-framework/operator-controller/blob/main/config/samples/olm_v1_clusterextension.yaml).
311319

@@ -324,7 +332,7 @@ that this alternative only be used in test clusters. Never in production.
324332
Below is an example ClusterRoleBinding using the cluster-admin ClusterRole:
325333

326334
```terminal
327-
# Create ClusterRole
335+
# Create ClusterRoleBinding
328336
kubectl apply -f - <<EOF
329337
apiVersion: rbac.authorization.k8s.io/v1
330338
kind: ClusterRoleBinding
@@ -349,6 +357,9 @@ kubectl create clusterrolebinding my-cluster-extension-installer-role-binding \
349357
--serviceaccount=my-cluster-extension-namespace:my-cluster-installer-service-account
350358
```
351359

360+
!!! note
361+
Remember: Do **not** create an actual ServiceAccount resource. The ClusterRoleBinding references the ServiceAccount by name only, and OLM will impersonate it.
362+
352363
### hack/tools/catalog
353364

354365
In the spirit of making this process more tenable until the proper tools are in place, the scripts

0 commit comments

Comments
 (0)