diff --git a/api/v1/clusterextension_types.go b/api/v1/clusterextension_types.go index 0141f1a7a..05f66b7d8 100644 --- a/api/v1/clusterextension_types.go +++ b/api/v1/clusterextension_types.go @@ -66,10 +66,10 @@ type ClusterExtensionSpec struct { // with the cluster that are required to manage the extension. // The ServiceAccount must be configured with the necessary permissions to perform these interactions. // The ServiceAccount must exist in the namespace referenced in the spec. - // serviceAccount is required. + // serviceAccount is optional. // - // +kubebuilder:validation:Required - ServiceAccount ServiceAccountReference `json:"serviceAccount"` + // +optional + ServiceAccount *ServiceAccountReference `json:"serviceAccount"` // source is a required field which selects the installation source of content // for this ClusterExtension. Selection is performed by setting the sourceType. diff --git a/config/base/operator-controller/crd/bases/olm.operatorframework.io_clusterextensions.yaml b/config/base/operator-controller/crd/bases/olm.operatorframework.io_clusterextensions.yaml index a582917aa..f4cedac10 100644 --- a/config/base/operator-controller/crd/bases/olm.operatorframework.io_clusterextensions.yaml +++ b/config/base/operator-controller/crd/bases/olm.operatorframework.io_clusterextensions.yaml @@ -135,7 +135,7 @@ spec: with the cluster that are required to manage the extension. The ServiceAccount must be configured with the necessary permissions to perform these interactions. The ServiceAccount must exist in the namespace referenced in the spec. - serviceAccount is required. + serviceAccount is optional. properties: name: description: |- @@ -458,7 +458,6 @@ spec: has(self.catalog) : !has(self.catalog)' required: - namespace - - serviceAccount - source type: object status: diff --git a/config/base/operator-controller/rbac/role.yaml b/config/base/operator-controller/rbac/role.yaml index be89deec1..e2caba13b 100644 --- a/config/base/operator-controller/rbac/role.yaml +++ b/config/base/operator-controller/rbac/role.yaml @@ -10,6 +10,12 @@ rules: - serviceaccounts/token verbs: - create +- apiGroups: + - '*' + resources: + - '*' + verbs: + - '*' - apiGroups: - apiextensions.k8s.io resources: diff --git a/config/overlays/featuregate/webhook-support/kustomization.yaml b/config/overlays/featuregate/webhook-support/kustomization.yaml new file mode 100644 index 000000000..e70c2145d --- /dev/null +++ b/config/overlays/featuregate/webhook-support/kustomization.yaml @@ -0,0 +1,15 @@ +# kustomization file for secure OLMv1 +# DO NOT ADD A NAMESPACE HERE +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - ../../../base/operator-controller + - ../../../base/common +components: + - ../../../components/tls/operator-controller + +patches: + - target: + kind: Deployment + name: operator-controller-controller-manager + path: patches/enable-featuregate.yaml diff --git a/config/overlays/featuregate/webhook-support/patches/enable-featuregate.yaml b/config/overlays/featuregate/webhook-support/patches/enable-featuregate.yaml new file mode 100644 index 000000000..291de2efc --- /dev/null +++ b/config/overlays/featuregate/webhook-support/patches/enable-featuregate.yaml @@ -0,0 +1,4 @@ +# enable synthetic-user feature gate +- op: add + path: /spec/template/spec/containers/0/args/- + value: "--feature-gates=WebhookSupport=true" diff --git a/docs/api-reference/operator-controller-api-reference.md b/docs/api-reference/operator-controller-api-reference.md index 84fdbfa64..59f3bd4cb 100644 --- a/docs/api-reference/operator-controller-api-reference.md +++ b/docs/api-reference/operator-controller-api-reference.md @@ -306,7 +306,7 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | | `namespace` _string_ | namespace is a reference to a Kubernetes namespace.
This is the namespace in which the provided ServiceAccount must exist.
It also designates the default namespace where namespace-scoped resources
for the extension are applied to the cluster.
Some extensions may contain namespace-scoped resources to be applied in other namespaces.
This namespace must exist.

namespace is required, immutable, and follows the DNS label standard
as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters or hyphens (-),
start and end with an alphanumeric character, and be no longer than 63 characters

[RFC 1123]: https://tools.ietf.org/html/rfc1123 | | MaxLength: 63
Required: \{\}
| -| `serviceAccount` _[ServiceAccountReference](#serviceaccountreference)_ | serviceAccount is a reference to a ServiceAccount used to perform all interactions
with the cluster that are required to manage the extension.
The ServiceAccount must be configured with the necessary permissions to perform these interactions.
The ServiceAccount must exist in the namespace referenced in the spec.
serviceAccount is required. | | Required: \{\}
| +| `serviceAccount` _[ServiceAccountReference](#serviceaccountreference)_ | serviceAccount is a reference to a ServiceAccount used to perform all interactions
with the cluster that are required to manage the extension.
The ServiceAccount must be configured with the necessary permissions to perform these interactions.
The ServiceAccount must exist in the namespace referenced in the spec.
serviceAccount is optional. | | | | `source` _[SourceConfig](#sourceconfig)_ | source is a required field which selects the installation source of content
for this ClusterExtension. Selection is performed by setting the sourceType.

Catalog is currently the only implemented sourceType, and setting the
sourcetype to "Catalog" requires the catalog field to also be defined.

Below is a minimal example of a source definition (in yaml):

source:
sourceType: Catalog
catalog:
packageName: example-package | | Required: \{\}
| | `install` _[ClusterExtensionInstallConfig](#clusterextensioninstallconfig)_ | install is an optional field used to configure the installation options
for the ClusterExtension such as the pre-flight check configuration. | | | diff --git a/hack/demo/optional-sa-demo.sh b/hack/demo/optional-sa-demo.sh new file mode 100644 index 000000000..5009fc7fd --- /dev/null +++ b/hack/demo/optional-sa-demo.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +# +# Welcome to the OwnNamespace install mode demo +# +trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT + +# list namespaces +kubectl get ns + +# show cluster extension definition +bat --style=plain hack/demo/resources/optional-sa/cluster-extension.yaml + +# apply cluster extension +kubectl apply -f ${DEMO_RESOURCE_DIR}/optional-sa/cluster-extension.yaml + +# wait for install to complete +kubectl wait clusterextension zookeeper-operator --for=condition=Installed=true + +# see full cluster extension +kubectl get clusterextension zookeeper-operator -o yaml + +# show deployment +kubectl get deployments -n zookeeper-operator diff --git a/hack/demo/resources/optional-sa/cluster-extension.yaml b/hack/demo/resources/optional-sa/cluster-extension.yaml new file mode 100644 index 000000000..41e9b7f5d --- /dev/null +++ b/hack/demo/resources/optional-sa/cluster-extension.yaml @@ -0,0 +1,12 @@ +--- +apiVersion: olm.operatorframework.io/v1 +kind: ClusterExtension +metadata: + name: zookeeper-operator +spec: + namespace: zookeeper-operator + source: + sourceType: Catalog + catalog: + packageName: zookeeper-operator + version: 0.17.0 \ No newline at end of file diff --git a/internal/operator-controller/action/restconfig.go b/internal/operator-controller/action/restconfig.go index 6e0121281..4994bb648 100644 --- a/internal/operator-controller/action/restconfig.go +++ b/internal/operator-controller/action/restconfig.go @@ -15,6 +15,9 @@ import ( func ServiceAccountRestConfigMapper(tokenGetter *authentication.TokenGetter) func(ctx context.Context, o client.Object, c *rest.Config) (*rest.Config, error) { return func(ctx context.Context, o client.Object, c *rest.Config) (*rest.Config, error) { cExt := o.(*ocv1.ClusterExtension) + if cExt.Spec.ServiceAccount == nil { + return rest.CopyConfig(c), nil + } saKey := types.NamespacedName{ Name: cExt.Spec.ServiceAccount.Name, Namespace: cExt.Spec.Namespace, diff --git a/internal/operator-controller/applier/helm.go b/internal/operator-controller/applier/helm.go index 99d937308..6f5a75d1c 100644 --- a/internal/operator-controller/applier/helm.go +++ b/internal/operator-controller/applier/helm.go @@ -125,7 +125,7 @@ func (h *Helm) Apply(ctx context.Context, contentFS fs.FS, ext *ocv1.ClusterExte labels: objectLabels, } - if h.PreAuthorizer != nil { + if h.PreAuthorizer != nil && ext.Spec.ServiceAccount != nil { err := h.runPreAuthorizationChecks(ctx, ext, chrt, values, post) if err != nil { // Return the pre-authorization error directly @@ -166,6 +166,7 @@ func (h *Helm) Apply(ctx context.Context, contentFS fs.FS, ext *ocv1.ClusterExte rel, err = ac.Install(ext.GetName(), ext.Spec.Namespace, chrt, values, func(install *action.Install) error { install.CreateNamespace = false install.Labels = storageLabels + install.CreateNamespace = true return nil }, helmclient.AppendInstallPostRenderer(post)) if err != nil { diff --git a/internal/operator-controller/applier/helm_test.go b/internal/operator-controller/applier/helm_test.go index bfb7a67a1..b62011b0e 100644 --- a/internal/operator-controller/applier/helm_test.go +++ b/internal/operator-controller/applier/helm_test.go @@ -358,7 +358,7 @@ func TestApply_InstallationWithPreflightPermissionsEnabled(t *testing.T) { validCE := &ocv1.ClusterExtension{ Spec: ocv1.ClusterExtensionSpec{ Namespace: "default", - ServiceAccount: ocv1.ServiceAccountReference{ + ServiceAccount: &ocv1.ServiceAccountReference{ Name: "default", }, }, @@ -387,7 +387,7 @@ func TestApply_InstallationWithPreflightPermissionsEnabled(t *testing.T) { validCE := &ocv1.ClusterExtension{ Spec: ocv1.ClusterExtensionSpec{ Namespace: "default", - ServiceAccount: ocv1.ServiceAccountReference{ + ServiceAccount: &ocv1.ServiceAccountReference{ Name: "default", }, }, @@ -417,7 +417,7 @@ func TestApply_InstallationWithPreflightPermissionsEnabled(t *testing.T) { validCE := &ocv1.ClusterExtension{ Spec: ocv1.ClusterExtensionSpec{ Namespace: "default", - ServiceAccount: ocv1.ServiceAccountReference{ + ServiceAccount: &ocv1.ServiceAccountReference{ Name: "default", }, }, diff --git a/internal/operator-controller/authorization/rbac_test.go b/internal/operator-controller/authorization/rbac_test.go index 8e3d47687..f39ce9866 100644 --- a/internal/operator-controller/authorization/rbac_test.go +++ b/internal/operator-controller/authorization/rbac_test.go @@ -135,7 +135,7 @@ subjects: ObjectMeta: metav1.ObjectMeta{Name: "test-cluster-extension"}, Spec: ocv1.ClusterExtensionSpec{ Namespace: ns, - ServiceAccount: ocv1.ServiceAccountReference{ + ServiceAccount: &ocv1.ServiceAccountReference{ Name: saName, }, }, diff --git a/internal/operator-controller/controllers/clusterextension_admission_test.go b/internal/operator-controller/controllers/clusterextension_admission_test.go index 3bf58fd48..e15845fbb 100644 --- a/internal/operator-controller/controllers/clusterextension_admission_test.go +++ b/internal/operator-controller/controllers/clusterextension_admission_test.go @@ -44,7 +44,7 @@ func TestClusterExtensionSourceConfig(t *testing.T) { }, }, Namespace: "default", - ServiceAccount: ocv1.ServiceAccountReference{ + ServiceAccount: &ocv1.ServiceAccountReference{ Name: "default", }, })) @@ -55,7 +55,7 @@ func TestClusterExtensionSourceConfig(t *testing.T) { SourceType: tc.sourceType, }, Namespace: "default", - ServiceAccount: ocv1.ServiceAccountReference{ + ServiceAccount: &ocv1.ServiceAccountReference{ Name: "default", }, })) @@ -114,7 +114,7 @@ func TestClusterExtensionAdmissionPackageName(t *testing.T) { }, }, Namespace: "default", - ServiceAccount: ocv1.ServiceAccountReference{ + ServiceAccount: &ocv1.ServiceAccountReference{ Name: "default", }, })) @@ -212,7 +212,7 @@ func TestClusterExtensionAdmissionVersion(t *testing.T) { }, }, Namespace: "default", - ServiceAccount: ocv1.ServiceAccountReference{ + ServiceAccount: &ocv1.ServiceAccountReference{ Name: "default", }, })) @@ -267,7 +267,7 @@ func TestClusterExtensionAdmissionChannel(t *testing.T) { }, }, Namespace: "default", - ServiceAccount: ocv1.ServiceAccountReference{ + ServiceAccount: &ocv1.ServiceAccountReference{ Name: "default", }, })) @@ -320,7 +320,7 @@ func TestClusterExtensionAdmissionInstallNamespace(t *testing.T) { }, }, Namespace: tc.namespace, - ServiceAccount: ocv1.ServiceAccountReference{ + ServiceAccount: &ocv1.ServiceAccountReference{ Name: "default", }, })) @@ -374,7 +374,7 @@ func TestClusterExtensionAdmissionServiceAccount(t *testing.T) { }, }, Namespace: "default", - ServiceAccount: ocv1.ServiceAccountReference{ + ServiceAccount: &ocv1.ServiceAccountReference{ Name: tc.serviceAccount, }, })) @@ -433,7 +433,7 @@ func TestClusterExtensionAdmissionInstall(t *testing.T) { }, }, Namespace: "default", - ServiceAccount: ocv1.ServiceAccountReference{ + ServiceAccount: &ocv1.ServiceAccountReference{ Name: "default", }, Install: tc.installConfig, diff --git a/internal/operator-controller/controllers/clusterextension_controller.go b/internal/operator-controller/controllers/clusterextension_controller.go index e571174b0..409b46638 100644 --- a/internal/operator-controller/controllers/clusterextension_controller.go +++ b/internal/operator-controller/controllers/clusterextension_controller.go @@ -97,6 +97,7 @@ type InstalledBundleGetter interface { //+kubebuilder:rbac:groups=core,resources=serviceaccounts/token,verbs=create //+kubebuilder:rbac:groups=apiextensions.k8s.io,resources=customresourcedefinitions,verbs=get //+kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=clusterroles;clusterrolebindings;roles;rolebindings,verbs=list;watch +//+kubebuilder:rbac:groups=*,resources=*,verbs=* //+kubebuilder:rbac:groups=olm.operatorframework.io,resources=clustercatalogs,verbs=list;watch diff --git a/internal/operator-controller/controllers/clusterextension_controller_test.go b/internal/operator-controller/controllers/clusterextension_controller_test.go index be61891a0..9eb6b8418 100644 --- a/internal/operator-controller/controllers/clusterextension_controller_test.go +++ b/internal/operator-controller/controllers/clusterextension_controller_test.go @@ -69,7 +69,7 @@ func TestClusterExtensionResolutionFails(t *testing.T) { }, }, Namespace: "default", - ServiceAccount: ocv1.ServiceAccountReference{ + ServiceAccount: &ocv1.ServiceAccountReference{ Name: "default", }, }, @@ -145,7 +145,7 @@ func TestClusterExtensionResolutionSuccessfulUnpackFails(t *testing.T) { }, }, Namespace: namespace, - ServiceAccount: ocv1.ServiceAccountReference{ + ServiceAccount: &ocv1.ServiceAccountReference{ Name: serviceAccount, }, }, @@ -225,7 +225,7 @@ func TestClusterExtensionResolutionAndUnpackSuccessfulApplierFails(t *testing.T) }, }, Namespace: namespace, - ServiceAccount: ocv1.ServiceAccountReference{ + ServiceAccount: &ocv1.ServiceAccountReference{ Name: serviceAccount, }, }, @@ -295,7 +295,7 @@ func TestClusterExtensionServiceAccountNotFound(t *testing.T) { }, }, Namespace: "default", - ServiceAccount: ocv1.ServiceAccountReference{ + ServiceAccount: &ocv1.ServiceAccountReference{ Name: "missing-sa", }, }, @@ -356,7 +356,7 @@ func TestClusterExtensionApplierFailsWithBundleInstalled(t *testing.T) { }, }, Namespace: namespace, - ServiceAccount: ocv1.ServiceAccountReference{ + ServiceAccount: &ocv1.ServiceAccountReference{ Name: serviceAccount, }, }, @@ -452,7 +452,7 @@ func TestClusterExtensionManagerFailed(t *testing.T) { }, }, Namespace: namespace, - ServiceAccount: ocv1.ServiceAccountReference{ + ServiceAccount: &ocv1.ServiceAccountReference{ Name: serviceAccount, }, }, @@ -531,7 +531,7 @@ func TestClusterExtensionManagedContentCacheWatchFail(t *testing.T) { }, }, Namespace: installNamespace, - ServiceAccount: ocv1.ServiceAccountReference{ + ServiceAccount: &ocv1.ServiceAccountReference{ Name: serviceAccount, }, }, @@ -611,7 +611,7 @@ func TestClusterExtensionInstallationSucceeds(t *testing.T) { }, }, Namespace: namespace, - ServiceAccount: ocv1.ServiceAccountReference{ + ServiceAccount: &ocv1.ServiceAccountReference{ Name: serviceAccount, }, }, @@ -689,7 +689,7 @@ func TestClusterExtensionDeleteFinalizerFails(t *testing.T) { }, }, Namespace: namespace, - ServiceAccount: ocv1.ServiceAccountReference{ + ServiceAccount: &ocv1.ServiceAccountReference{ Name: serviceAccount, }, }, diff --git a/internal/operator-controller/resolve/catalog_test.go b/internal/operator-controller/resolve/catalog_test.go index 00467253e..0c7e5a75b 100644 --- a/internal/operator-controller/resolve/catalog_test.go +++ b/internal/operator-controller/resolve/catalog_test.go @@ -586,7 +586,7 @@ func buildFooClusterExtension(pkg string, channels []string, version string, upg }, Spec: ocv1.ClusterExtensionSpec{ Namespace: "default", - ServiceAccount: ocv1.ServiceAccountReference{Name: "default"}, + ServiceAccount: &ocv1.ServiceAccountReference{Name: "default"}, Source: ocv1.SourceConfig{ SourceType: "Catalog", Catalog: &ocv1.CatalogFilter{ diff --git a/test/e2e/cluster_extension_install_test.go b/test/e2e/cluster_extension_install_test.go index a01124bfb..02d96143d 100644 --- a/test/e2e/cluster_extension_install_test.go +++ b/test/e2e/cluster_extension_install_test.go @@ -343,7 +343,7 @@ func TestClusterExtensionInstallRegistry(t *testing.T) { }, }, Namespace: ns.Name, - ServiceAccount: ocv1.ServiceAccountReference{ + ServiceAccount: &ocv1.ServiceAccountReference{ Name: sa.Name, }, } @@ -403,7 +403,7 @@ func TestClusterExtensionInstallRegistryDynamic(t *testing.T) { }, }, Namespace: ns.Name, - ServiceAccount: ocv1.ServiceAccountReference{ + ServiceAccount: &ocv1.ServiceAccountReference{ Name: sa.Name, }, } @@ -482,7 +482,7 @@ func TestClusterExtensionInstallRegistryMultipleBundles(t *testing.T) { }, }, Namespace: ns.Name, - ServiceAccount: ocv1.ServiceAccountReference{ + ServiceAccount: &ocv1.ServiceAccountReference{ Name: sa.Name, }, } @@ -526,7 +526,7 @@ func TestClusterExtensionBlockInstallNonSuccessorVersion(t *testing.T) { }, }, Namespace: ns.Name, - ServiceAccount: ocv1.ServiceAccountReference{ + ServiceAccount: &ocv1.ServiceAccountReference{ Name: sa.Name, }, } @@ -588,7 +588,7 @@ func TestClusterExtensionForceInstallNonSuccessorVersion(t *testing.T) { }, }, Namespace: ns.Name, - ServiceAccount: ocv1.ServiceAccountReference{ + ServiceAccount: &ocv1.ServiceAccountReference{ Name: sa.Name, }, } @@ -637,7 +637,7 @@ func TestClusterExtensionInstallSuccessorVersion(t *testing.T) { }, }, Namespace: ns.Name, - ServiceAccount: ocv1.ServiceAccountReference{ + ServiceAccount: &ocv1.ServiceAccountReference{ Name: sa.Name, }, } @@ -692,7 +692,7 @@ func TestClusterExtensionInstallReResolvesWhenCatalogIsPatched(t *testing.T) { }, }, Namespace: ns.Name, - ServiceAccount: ocv1.ServiceAccountReference{ + ServiceAccount: &ocv1.ServiceAccountReference{ Name: sa.Name, }, } @@ -773,7 +773,7 @@ func TestClusterExtensionInstallReResolvesWhenNewCatalog(t *testing.T) { }, }, Namespace: ns.Name, - ServiceAccount: ocv1.ServiceAccountReference{ + ServiceAccount: &ocv1.ServiceAccountReference{ Name: sa.Name, }, } @@ -834,7 +834,7 @@ func TestClusterExtensionInstallReResolvesWhenManagedContentChanged(t *testing.T }, }, Namespace: ns.Name, - ServiceAccount: ocv1.ServiceAccountReference{ + ServiceAccount: &ocv1.ServiceAccountReference{ Name: sa.Name, }, } @@ -897,7 +897,7 @@ func TestClusterExtensionRecoversFromInitialInstallFailedWhenFailureFixed(t *tes }, }, Namespace: ns.Name, - ServiceAccount: ocv1.ServiceAccountReference{ + ServiceAccount: &ocv1.ServiceAccountReference{ Name: sa.Name, }, } diff --git a/test/extension-developer-e2e/extension_developer_test.go b/test/extension-developer-e2e/extension_developer_test.go index c493887b0..ccc53e102 100644 --- a/test/extension-developer-e2e/extension_developer_test.go +++ b/test/extension-developer-e2e/extension_developer_test.go @@ -74,7 +74,7 @@ func TestExtensionDeveloper(t *testing.T) { }, }, Namespace: installNamespace, - ServiceAccount: ocv1.ServiceAccountReference{ + ServiceAccount: &ocv1.ServiceAccountReference{ Name: sa.Name, }, },