This module automates the installation and configuration of the External Secrets Operator in a cluster.
External Secrets Operator synchronizes secrets in the Kubernetes cluster with secrets that are mapped in Secrets Manager.
The module provides the following features:
- Install and configure External Secrets Operator (ESO).
- Customise External Secret Operator deployment on specific cluster workers by configuration approriate NodeSelector and Tolerations in the ESO helm release More details below
The submodules automate the configuration of an operator, providing the following features:
- Deploy and configure ClusterSecretStore resources for cluster scope secrets store eso-clusterstore
- Deploy and configure SecretStore resources for namespace scope secrets store eso-secretstore
- Leverage on two authentication methods to be configured on the single stores instances:
- IAM apikey standard authentication
- IAM Trusted profile: in conjunction with
eso-trusted-profile
submodule, which allows to create one or more trusted profiles to use with the ESO module for trusted profile authentication.
- Configure the ExternalSecret resources to be bound to the expected secrets store (according to the visibility you need) and to configure the target secret details
- The following secret types of Kubernetes Secrets are currently supported:
Opaque
(opaque
in this module)kubernetes.io/dockerconfigjson
(dockerconfigjson
in this module)
- The following secret types of Kubernetes Secrets are currently supported:
The current version of the module supports multitenants configuration by setting up "ESO as a service" (ref. https://cloud.redhat.com/blog/how-to-setup-external-secrets-operator-eso-as-a-service) for both authentication methods More details below
The following combinations of Kubernetes Secrets and Secrets Manager secrets are used with given External-Secret type.
es_kubernetes_secret_type1 | sm_secret_type2 | external_secret_type3 |
---|---|---|
dockerconfigjson | arbitrary | arbitrary |
dockerconfigjson | iam_credentials | iam_credentials |
dockerconfigjson | username_password | username_password |
opaque | arbitrary | arbitrary |
opaque | iam_credentials | iam_credentials |
opaque | username_password | username_password |
opaque | kv | kv |
tls | imported_cert | imported_cert |
tls | public_cert | public_cert |
tls | private_cert | private_cert |
In order to customise the NodeSelector and the tolerations to make the External Secret Operator deployed on specific cluster nodes it is possible to configure the following input variable with the appropriate values:
variable "eso_cluster_nodes_configuration" {
description = "Configuration to use to customise ESO deployment on specific cluster nodes. Setting appropriate values will result in customising ESO helm release. Default value is null to keep ESO standard deployment."
type = object({
nodeSelector = object({
label = string
value = string
})
tolerations = object({
key = string
operator = string
value = string
effect = string
})
})
default = null
}
For example:
module "external_secrets_operator" {
(...)
eso_cluster_nodes_configuration = {
nodeSelector = {
label = "dedicated"
value = "edge"
}
tolerations = {
key = "dedicated"
operator = "Equal"
value = "edge"
effect = "NoExecute"
}
}
(...)
will make the External Secret Operator to run on clusters nodes labeled with dedicated: edge
.
The resulting helm release configuration, according to the terraform plan
output would be like
(...)
# module.external_secrets_operator.helm_release.external_secrets_operator[0] will be created
+ resource "helm_release" "external_secrets_operator" {
+ atomic = false
+ chart = "external-secrets"
+ cleanup_on_fail = false
+ create_namespace = false
+ dependency_update = false
+ disable_crd_hooks = false
+ disable_openapi_validation = false
+ disable_webhooks = false
+ force_update = false
+ id = (known after apply)
+ lint = false
+ manifest = (known after apply)
+ max_history = 0
+ metadata = (known after apply)
+ name = "external-secrets"
+ namespace = "es-operator"
+ pass_credentials = false
+ recreate_pods = false
+ render_subchart_notes = true
+ replace = false
+ repository = "https://charts.external-secrets.io"
+ reset_values = false
+ reuse_values = false
+ skip_crds = false
+ status = "deployed"
+ timeout = 300
+ values = [
+ <<-EOT
installCRDs: true
extraVolumes:
- name: sa-token
projected:
defaultMode: 0644
sources:
- serviceAccountToken:
path: sa-token
expirationSeconds: 3600
audience: iam
extraVolumeMounts:
- mountPath: /var/run/secrets/tokens
name: sa-token
webhook:
extraVolumes:
- name: sa-token
projected:
defaultMode: 0644
sources:
- serviceAccountToken:
path: sa-token
expirationSeconds: 3600
audience: iam
extraVolumeMounts:
- mountPath: /var/run/secrets/tokens
name: sa-token
EOT,
+ <<-EOT
nodeSelector: { dedicated: edge }
tolerations:
- key: dedicated
operator: Equal
value: edge
effect: NoExecute
webhook:
nodeSelector: { dedicated: edge }
tolerations:
- key: dedicated
operator: Equal
value: edge
effect: NoExecute
certController:
nodeSelector: { dedicated: edge }
tolerations:
- key: dedicated
operator: Equal
value: edge
effect: NoExecute
EOT,
]
+ verify = false
+ version = "0.9.7"
+ wait = true
+ wait_for_jobs = false
}
(...)
Similarly exporting this environment variable
export TF_VAR_eso_cluster_nodes_configuration="{\"nodeSelector\": {\"label\": \"dedicated\", \"value\": \"transit\"}, \"tolerations\": {\"key\": \"dedicated\", \"operator\": \"Equal\", \"value\": \"transit\", \"effect\": \"NoExecute\"}}"
will make the External Secret Operator to run on clusters nodes labeled with dedicated: transit
.
The default null
value keeps the default ESO behaviour.
To configure a set of tenants to be configured in their proper namespace (to achieve tenant isolation) you need simply to follow these steps:
- deploy ESO in the cluster
module "external_secrets_operator" {
source = "terraform-ibm-modules/external-secrets-operator/ibm"
version = "1.0.0"
eso_namespace = var.eso_namespace # namespace to deploy ESO
service_endpoints = var.service_endpoints # use public or private endpoints for IAM and Secrets Manager
eso_cluster_nodes_configuration = <<the eso configuration for specific cluster nodes selection if needed - read above>>
}
- create multiple
SecretStore
(s) in the proper namespaces
With api_key
authentication mode
module "eso_namespace_secretstore_1" {
depends_on = [
module.external_secrets_operator
]
source = "../modules/eso-secretstore"
eso_authentication = "api_key"
region = local.sm_region # SM region
sstore_namespace = var.es_kubernetes_namespaces[2] # namespace to create the secret store
sstore_secrets_manager_guid = local.sm_guid # the guid of the secrets manager instance to use
sstore_store_name = "${var.es_kubernetes_namespaces[2]}-store" # store name
# to pull the secrets from SM
sstore_secret_apikey = data.ibm_sm_iam_credentials_secret.secret_puller_secret.api_key # pragma: allowlist secret
service_endpoints = var.service_endpoints
sstore_helm_rls_name = "es-store" # helm release name suffix to use for the store
sstore_secret_name = "generic-cluster-api-key" #checkov:skip=CKV_SECRET_6
}
With trusted_profile
authentication mode
module "eso_namespace_secretstores" {
depends_on = [
module.external_secrets_operator
]
source = "../modules/eso-secretstore"
eso_authentication = "trusted_profile"
region = local.sm_region # SM region
sstore_namespace = kubernetes_namespace.examples[count.index].metadata[0].name # namespace to create the secret store
sstore_secrets_manager_guid = local.sm_guid # the guid of the secrets manager instance to use
sstore_store_name = "${kubernetes_namespace.examples[count.index].metadata[0].name}-store" # store name
sstore_trusted_profile_name = module.external_secrets_trusted_profiles[count.index].trusted_profile_name # trusted profile name to use into this secret store
service_endpoints = var.service_endpoints
sstore_helm_rls_name = "es-store-${count.index}" # helm release name suffix to use for the store
sstore_secret_name = "secretstore-api-key" #checkov:skip=CKV_SECRET_6
}
More details can be found in the examples linked below.
For more information about IAM Trusted profiles and ESO Multitenancy configuration please refer to
- IBM IAM Trusted profiles article
- Setup of ESO as a Service from RedHat
- ESO Multitenancy configuration from ESO Docs
The current ESO version doesn't allow to customise the default IAM endpoint (https://iam.cloud.ibm.com) it uses when authenticating through apikey (api_key
authentication) for both ClusterSecretStore and SecretStore APIs.
As a direct effect of this limitation, for a standard OCP cluster topology as defined by GoldenEye design (3 workers zones edge
private
and transit
), an ESO deployment with api_key
authentication configuration needs to be performed on the workers pool with access to the public network (dedicated: edge
label in GE usual topology) to work fine. If the ESO deployment is performed on a workers pool without access to public network (i.e. to https://iam.cloud.ibm.com) the apikey authentication is expected to fail.
When secrets are updated, depending on you configuration pods may need to be restarted to pick up the new secrets. To do this you can use the Stakater Reloader.
By default, the module deploys this to watch for changes in secrets and configmaps and trigger a rolling update of the related pods.
To have Reloader watch a secret or configMap add the annotation reloader.stakater.com/auto: "true"
to the secret or configMap, the same annotation can be added to deployments to have them restarted when the secret or configMap changes.
When using the eso-external-secret submodule, use the reloader_watching
variable to have the annotation added to the secret.
This can be further configured as needed, for more details see https://github.com/stakater/Reloader By default is watches all namespaces.
If you do not need it please set reloader_deployed = false
in the module call.
In the case of problems with secrets synchronization a good start point to the investigation is to list the externalsecrets resources in the cluster:
oc get externalsecrets -A
NAMESPACE NAME AGE STATUS CAPABILITIES READY
apikeynspace3 secretstore.external-secrets.io/apikeynspace3-store 32m Valid ReadOnly True
apikeynspace4 secretstore.external-secrets.io/apikeynspace4-store 32m Valid ReadOnly True
tpnspace1 secretstore.external-secrets.io/tpnspace1-store 32m Valid ReadOnly True
tpnspace2 secretstore.external-secrets.io/tpnspace2-store 32m Valid ReadOnly True
NAMESPACE NAME AGE STATUS CAPABILITIES READY
clustersecretstore.external-secrets.io/cluster-store 32m Valid ReadOnly True
NAMESPACE NAME STORE REFRESH INTERVAL STATUS READY
apikeynspace1 externalsecret.external-secrets.io/dockerconfigjson-uc cluster-store 1m SecretSyncedError False
apikeynspace2 externalsecret.external-secrets.io/cloudant-opaque-arb cluster-store 5m SecretSyncedError False
apikeynspace3 externalsecret.external-secrets.io/dockerconfigjson-arb apikeynspace3-store 1h SecretSyncedError False
apikeynspace4 externalsecret.external-secrets.io/dockerconfigjson-iam apikeynspace4-store 1h SecretSyncedError False
tpnspace1 externalsecret.external-secrets.io/geretain-tesoall-arbitrary-arb-tp-0 tpnspace1-store 5m SecretSynced True
tpnspace2 externalsecret.external-secrets.io/geretain-tesoall-arbitrary-arb-tp-1 tpnspace2-store 5m SecretSynced True
In the example above some of the externalsecrets are experiencing secrets synchronization errors. By describing them you should be able to identify the error:
oc describe externalsecret dockerconfigjson-uc -n apikeynspace1
Name: dockerconfigjson-uc
Namespace: apikeynspace1
Labels: app=raw
app.kubernetes.io/managed-by=Helm
chart=raw-v0.2.5
heritage=Helm
release=apikeynspace1-es-docker-uc
Annotations: meta.helm.sh/release-name: apikeynspace1-es-docker-uc
meta.helm.sh/release-namespace: apikeynspace1
API Version: external-secrets.io/v1beta1
Kind: ExternalSecret
Metadata:
(...)
Status:
Conditions:
Last Transition Time: 2023-06-27T15:18:31Z
Message: could not get secret data from provider
Reason: SecretSyncedError
Status: False
Type: Ready
Refresh Time: <nil>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
(...)
Warning UpdateFailed 119s (x13 over 28m) external-secrets An error occurred while performing the 'authenticate' step: Post "https://iam.cloud.ibm.com/identity/token": context deadline exceeded (Client.Timeout exceeded while awaiting headers)
In the output above there is a problem with reaching IAM endpoint (verify that the pods where ESO is running are able to reach that endpoint)
If you taint or destroy or simply make a change that needs the helm_release resource of the ESO operator to be deleted and recreated, this would make terraform to destroy the operator itself, including all the CRDs, which would destroy all the secrets synched through ESO, even if the helm_release resource of these CRDs aren't directly touched and terraform wouldn't be able to identify such a change.
So in the case you plan to make changes to the operator helm_release once deployed, run preliminary a terraform plan
to be sure that the release isn't destroyed and recreated.
dockerconfigjson from arbitrary or iam_credentials
Secret Body >
apiVersion: v1
kind: Secret
metadata:
name: dockerconfigjson-iam
namespace: test-ns
data:
.dockerconfigjson: [BASE64ENCODED]
type: kubernetes.io/dockerconfigjson
Base64 Decoded .dockerconfigson
>
{
"auths": {
"us.icr.io": {
"username": "iamapikey",
"password": "APIKEYVALUE", # pragma: allowlist secret
"email": "[email protected]"
}
}
}
dockerconfigjson from username_password
Secret Body >
apiVersion: v1
kind: Secret
metadata:
name: dockerconfigjson-uc
namespace: test-ns
data:
.dockerconfigjson: [BASE64ENCODED]
type: kubernetes.io/dockerconfigjson
Base64 Decoded .dockerconfigson
>
{
"auths": {
"xx.artifactory.xyz-devops.com": {
"username": "[email protected]",
"password": "APIKEYVALUE" # pragma: allowlist secret
}
}
}
opaque from arbitrary or iam_credentials
Secret Body >
apiVersion: v1
kind: Secret
metadata:
name: opaque-arb
namespace: test-ns
type: Opaque
data:
apikey: APIKEYVALUE # pragma: allowlist secret
opaque from username_password
Secret Body >
apiVersion: v1
kind: Secret
metadata:
name: opaque-uc
namespace: test-ns
type: Opaque
data:
username: test-user
password: PASSWORDVALUE # pragma: allowlist secret
module "es_kubernetes_secret" {
source = "../modules/eso-external-secret"
es_kubernetes_secret_type = "dockerconfigjson"
sm_secret_type = "iam_credentials"
sm_secret_id = module.docker_config.serviceid_apikey_secret_id
eso_setup = true
es_kubernetes_namespaces = var.es_kubernetes_namespaces
es_docker_email = "[email protected]"
eso_generic_secret_apikey = data.ibm_secrets_manager_secret.secret_puller_secret.api_key # pragma: allowlist secret
secrets_manager_guid = module.secrets_manager_iam_configuration.secrets_manager_guid
region = "us-south"
es_kubernetes_secret_name = "dockerconfigjson-iam"
depends_on = [
kubernetes_namespace.cluster_namespaces
]
es_kubernetes_secret_data_key = "apiKey"
es_helm_rls_name = "es-docker-iam"
}
Name | Version |
---|---|
terraform | >= 1.0.0 |
helm | >= 2.11.0, < 3.0.0 |
kubernetes | >= 2.16.1, < 3.0.0 |
Name | Source | Version |
---|---|---|
eso_namespace | terraform-ibm-modules/namespace/ibm | 1.0.2 |
Name | Type |
---|---|
helm_release.external_secrets_operator | resource |
helm_release.pod_reloader | resource |
kubernetes_namespace.existing_eso_namespace | data source |
Name | Description | Type | Default | Required |
---|---|---|---|---|
eso_chart_location | The location of the External Secrets Operator Helm chart. | string |
"https://charts.external-secrets.io" |
no |
eso_chart_version | The version of the External Secrets Operator Helm chart. Ensure that the chart version is compatible with the image version specified in eso_image_version. | string |
"0.12.1" |
no |
eso_cluster_nodes_configuration | Configuration to use to customise ESO deployment on specific cluster nodes. Setting appropriate values will result in customising ESO helm release. Default value is null to keep ESO standard deployment. | object({ |
null |
no |
eso_enroll_in_servicemesh | Flag to enroll ESO into istio servicemesh | bool |
false |
no |
eso_image | The External Secrets Operator image in the format of [registry-url]/[namespace]/[image] . |
string |
"ghcr.io/external-secrets/external-secrets" |
no |
eso_image_version | The version or digest for the external secrets image to deploy. If changing the value, ensure it is compatible with the chart version set in eso_chart_version. | string |
"v0.12.1-ubi@sha256:d38834043de0a4e4feeac8a08d0bc96b71ddd7fe1d4c8583ee3751badeaeb01d" |
no |
eso_namespace | Namespace to create and be used to install ESO components including helm releases. If eso_store_scope == cluster, this will also be used to deploy ClusterSecretStore/cluster_store in it | string |
null |
no |
eso_pod_configuration | Configuration to use to customise ESO deployment on specific pods. Setting appropriate values will result in customising ESO helm release. Default value is {} to keep ESO standard deployment. Ignore the key if not required. | object({ |
{} |
no |
existing_eso_namespace | Existing Namespace to be used to install ESO components including helm releases. If eso_store_scope == cluster, this will also be used to deploy ClusterSecretStore/cluster_store in it | string |
null |
no |
reloader_chart_location | The location of the Reloader Helm chart. | string |
"https://stakater.github.io/stakater-charts" |
no |
reloader_chart_version | The version of the Reloader Helm chart. Ensure that the chart version is compatible with the image version specified in reloader_image_version. | string |
"1.2.1" |
no |
reloader_custom_values | String containing custom values to be used for reloader helm chart. See https://github.com/stakater/Reloader/blob/master/deployments/kubernetes/chart/reloader/values.yaml | string |
null |
no |
reloader_deployed | Whether to deploy reloader or not https://github.com/stakater/Reloader | bool |
true |
no |
reloader_ignore_configmaps | Whether to ignore configmap changes or not | bool |
false |
no |
reloader_ignore_secrets | Whether to ignore secret changes or not | bool |
false |
no |
reloader_image | The reloader image in the format of [registry-url]/[namespace]/[image] . |
string |
"ghcr.io/stakater/reloader" |
no |
reloader_image_version | The version or digest for the reloader image to deploy. If changing the value, ensure it is compatible with the chart version set in reloader_chart_version. | string |
"v1.2.1-ubi@sha256:80a557100c6835c7e3c9842194250c9c4ca78f43200bc3a93a32e5b105ad11bb" |
no |
reloader_is_argo_rollouts | Enable Argo Rollouts | bool |
false |
no |
reloader_is_openshift | Enable OpenShift DeploymentConfigs | bool |
true |
no |
reloader_log_format | The log format to use for reloader. Possible values are json or text . Default value is json |
string |
"text" |
no |
reloader_namespaces_selector | List of comma separated label selectors, if multiple are provided they are combined with the AND operator | string |
null |
no |
reloader_namespaces_to_ignore | List of comma separated namespaces to ignore for reloader. If multiple are provided they are combined with the AND operator | string |
null |
no |
reloader_pod_monitor_metrics | Enable to scrape Reloader's Prometheus metrics | bool |
false |
no |
reloader_reload_on_create | Enable reload on create events | bool |
true |
no |
reloader_reload_strategy | The reload strategy to use for reloader. Possible values are env-vars or annotations . Default value is annotations |
string |
"annotations" |
no |
reloader_resource_label_selector | List of comma separated label selectors, if multiple are provided they are combined with the AND operator | string |
null |
no |
reloader_resources_to_ignore | List of comma separated resources to ignore for reloader. If multiple are provided they are combined with the AND operator | string |
null |
no |
reloader_sync_after_restart | Enable sync after Reloader restarts for Add events, works only when reloadOnCreate is true | bool |
true |
no |
No outputs.
You can report issues and request features for this module in GitHub issues in the module repo. See Report an issue or request a feature.
To set up your local development environment, see Local development setup in the project documentation.
Footnotes
-
es_kubernetes_secret_type: The Kubernetes Secrets type or format that ESO installs in the cluster. ↩
-
sm_secret_type: IBM Cloud Secrets Manager secret type that is used as source data by ESO. ↩
-
external_secret_type: The secret type that is used by ESO. ↩