Integrate External Secrets Operator
External Secrets Operator (ESO) runs in a cluster, creates Kubernetes Secrets from values retrieved from Secrets Manager, and refreshes them.
This guide covers integration configuration, supported scope, and secret usage for the KakaoCloud ESO distribution used with Kubernetes Engine.
- If this is your first integration, complete Install ESO and verify basic synchronization first.
- To practice secret synchronization and refresh after installation, see Synchronize and refresh a secret.
Scope and limitations
The supported features and limitations of the KakaoCloud ESO distribution are as follows.
Supported features
- Retrieve values by secret ID
- Retrieve the default or a specified version
- Store a selected JSON field under a specified key
- Store all top-level keys and values in a JSON object
- Create and refresh Kubernetes Secrets according to a synchronization policy
Limitations
- The KakaoCloud distribution supports only the KakaoCloud provider.
- Creating, modifying, or deleting Secrets Manager secrets through ESO is not supported.
- Searching for and synchronizing multiple Secrets Manager secrets by name or tag (
dataFrom.find) is not supported.
Prerequisites
- See the installation tutorial prerequisites for cluster and network requirements.
- The IAM principal used for ESO authentication needs permission to read Secrets Manager secret values and the KMS User role or higher. See Secrets Manager and KMS roles.
- If per-secret access control is enabled, include the IAM principal in the allowed targets.
- Manage source values and versions in Secrets Manager. See Create a secret and Create a version.
Distribution information
This section describes the installation tools, image, and version of the KakaoCloud ESO distribution used with Kubernetes Engine.
Deployment information
| Item | Value |
|---|---|
| Installation tools | Helm 3, kubectl |
| Helm chart | oci://ghcr.io/kakaoenterprise/charts/external-secrets |
| Helm chart version | 2.10.0-kc.1 |
| Container image | ghcr.io/kakaoenterprise/external-secrets-kakaocloud:v2.10.0-kc.1 |
| Base ESO version | External Secrets Operator 2.10.0 |
| Provider | KakaoCloud Secrets Manager |
| Provider mode | Read-only |
Version information
This distribution is based on External Secrets Operator (ESO) 2.10.0 and adds integration with KakaoCloud Secrets Manager. Helm chart versions and image tags use the kc identifier to distinguish this distribution from the ESO project distribution.
The current version in the table is 2.10.0-kc.1. 2.10.0-kc.2 and 2.11.0-kc.1 are examples for explaining version numbering and do not indicate actual availability or a release schedule.
| Item | Helm chart version | Image tag |
|---|---|---|
First KakaoCloud distribution based on ESO 2.10.0 | 2.10.0-kc.1 | v2.10.0-kc.1 |
| Example: KakaoCloud revision based on the same ESO version | 2.10.0-kc.2 | v2.10.0-kc.2 |
Example: First KakaoCloud distribution based on ESO 2.11.0 | 2.11.0-kc.1 | v2.11.0-kc.1 |
The KakaoCloud distribution is configured for the KakaoCloud environment based on External Secrets Operator. Check the complete image address and deployment version when installing, troubleshooting, upgrading, or rolling back.
Changelog
Major changes to the KakaoCloud ESO distribution are listed below. Entries are shown from newest to oldest; release dates are based on GHCR publication.
| Version | Release date | Major changes |
|---|---|---|
2.10.0-kc.1 | 2026-09-15 | KakaoCloud distribution based on ESO 2.10.0 |
Integration architecture
ESO uses the SecretStore specified by an ExternalSecret to obtain connection settings and credentials. IAM access keys stored in a Kubernetes Secret are used for authentication.
| Resource | Role |
|---|---|
| Kubernetes Secret — IAM credentials | Created by the user to store the IAM access key ID and secret access key used by ESO |
SecretStore | Defines the KakaoCloud endpoints and references the Secret containing IAM credentials |
ExternalSecret | Defines the Secrets Manager secret ID to retrieve and the synchronization target |
| Secrets Manager secret | Manages source values and versions delivered to applications |
| Kubernetes Secret — synchronized value | Stores values retrieved by ESO for use as environment variables or files |
Basic integration configuration
Use the installed ESO to synchronize Secrets Manager values to Kubernetes Secrets. For the complete procedure and commands, see the basic integration steps in the installation tutorial.
- Prepare a namespace.
- Create an IAM credentials Secret.
- Configure and apply the endpoints and credential reference in a SecretStore.
- Specify the Secrets Manager secret ID and target Kubernetes Secret in an ExternalSecret, then apply it.
- Verify synchronization.
The examples below use the eso-demo namespace and kakaocloud-secret-store created by this procedure. Follow each example for instructions on writing and applying its YAML.
The main settings are as follows.
| Resource | Setting | Description |
|---|---|---|
| SecretStore | spec.provider.kakaocloud.secretsManagerEndpoint | HTTPS base URL for Secrets Manager |
| SecretStore | spec.provider.kakaocloud.auth.iamEndpoint | HTTPS base URL for IAM |
| SecretStore | spec.provider.kakaocloud.auth.secretRef | Reference to the Kubernetes Secret containing IAM access keys |
| ExternalSecret | spec.secretStoreRef | Name and kind of the SecretStore |
| ExternalSecret | spec.target.name | Name of the synchronized Kubernetes Secret |
| ExternalSecret | spec.data[].remoteRef.key | Secrets Manager secret ID, not its name |
| ExternalSecret | spec.data[].remoteRef.version | Version number string; omitted means the default version |
| ExternalSecret | spec.data[].secretKey | Key under which to store the retrieved value |
| ExternalSecret | spec.data[].remoteRef.property | JSON field to retrieve; omitted means the entire value |
| ExternalSecret | spec.dataFrom[].extract.key | ID of the JSON secret whose top-level fields are extracted |
| ExternalSecret | spec.dataFrom[].extract.version | Version number string; omitted means the default version |
- Enter only HTTPS base URLs for endpoints. Do not include API paths, query strings, or credentials.
- The Secrets Manager value must be a valid JSON object when using
remoteRef.propertyordataFrom.extract. - Applying field extraction to a plain string or specifying a missing field causes synchronization to fail.
Use private endpoints
To use private endpoints, set spec.provider.kakaocloud.secretsManagerEndpoint and spec.provider.kakaocloud.auth.iamEndpoint in the SecretStore to the HTTPS base URLs provided for each service. Use the actual endpoint addresses, such as those under *.kakaocloud-in.com, and do not append /identity/v3/auth/tokens to the IAM URL.
Verify that the ESO controller pod can resolve the addresses to private IPs, connect over HTTPS, and validate the TLS certificates. Services that do not use private endpoints, as well as GHCR, may still require an external communication path.
Pin specific secret version
To keep using a specific version for reproducible deployments or staged rotation, set remoteRef.version in spec.data and reapply the manifest. Enter the secret ID and version in the fields below. When a version is pinned, changing the default version does not make the target Kubernetes Secret follow that new default.
data:
- secretKey: password
remoteRef:
key: ${SECRET_ID}
version: "${SECRET_VERSION}"
| Variable | Description |
|---|---|
| SECRET_ID🖌︎ | Secrets Manager secret ID |
| SECRET_VERSION🖌︎ | Version number |
To pin a specific version while synchronizing all top-level keys and values of a JSON object, set extract.version in spec.dataFrom. If omitted, the default version is retrieved. Place the following configuration under spec.
dataFrom:
- extract:
key: ${SECRET_ID}
version: "${SECRET_VERSION}"
| Variable | Description |
|---|---|
| SECRET_ID🖌︎ | Secrets Manager secret ID |
| SECRET_VERSION🖌︎ | Version number |
remoteRef.version and extract.version refer to Secrets Manager versions. Both fields are strings, so enclose version numbers in quotation marks.
Synchronize JSON secret
Prepare a Secrets Manager secret containing the JSON object below and enter its ID in each YAML example.
Both examples use refreshInterval: 1h0m0s. The Kubernetes Secret is first created after the manifest is applied; subsequent changes to the remote value are reflected at the configured one-hour interval.
{
"username": "app-user",
"password": "app-password",
"endpoint": "db.example.internal"
}
Select specific properties
Use remoteRef.property to select fields and secretKey to set their Kubernetes Secret keys. The following example stores username and password under keys with the same names.
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: app-credentials
namespace: eso-demo
spec:
refreshPolicy: Periodic
refreshInterval: 1h0m0s
secretStoreRef:
name: kakaocloud-secret-store
kind: SecretStore
target:
name: app-credentials
creationPolicy: Owner
data:
- secretKey: username
remoteRef:
key: ${SECRET_ID}
property: username
- secretKey: password
remoteRef:
key: ${SECRET_ID}
property: password
| Variable | Description |
|---|---|
| SECRET_ID🖌︎ | Secrets Manager secret ID |
kubectl apply -f app-credentials.yaml
kubectl wait --for=condition=Ready \
externalsecret/app-credentials -n eso-demo --timeout=60s
kubectl get secret app-credentials -n eso-demo \
-o go-template='{{range $key, $_ := .data}}{{$key}}{{"\n"}}{{end}}'
This example succeeds when the ExternalSecret reports Ready=True and the output contains the username and password keys.
Extract all top-level JSON fields
Use dataFrom.extract to store every top-level key and value in the Kubernetes Secret. The following example creates the username, password, and endpoint keys and stores each field value under its corresponding key.
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: database-credentials
namespace: eso-demo
spec:
refreshPolicy: Periodic
refreshInterval: 1h0m0s
secretStoreRef:
name: kakaocloud-secret-store
kind: SecretStore
target:
name: database-credentials
creationPolicy: Owner
dataFrom:
- extract:
key: ${SECRET_ID}
| Variable | Description |
|---|---|
| SECRET_ID🖌︎ | Secrets Manager secret ID |
kubectl apply -f database-credentials.yaml
kubectl wait --for=condition=Ready \
externalsecret/database-credentials -n eso-demo --timeout=60s
kubectl get secret database-credentials -n eso-demo \
-o go-template='{{range $key, $_ := .data}}{{$key}}{{"\n"}}{{end}}'
This example succeeds when the ExternalSecret reports Ready=True and the output contains the username, password, and endpoint keys.
To pass only the fields required by the application, select them with remoteRef.property as shown above.
Pass application settings
Pass the app-credentials Secret created in the JSON synchronization example to an application as environment variables or mounted files. Choose one of the following methods to match how the application uses the values.
Inject environment variables
First create app-credentials using the JSON synchronization example above. Add the following partial configuration to the relevant container under spec.template.spec.containers[] in a Deployment in the eso-demo namespace.
env:
- name: APP_USERNAME
valueFrom:
secretKeyRef:
name: app-credentials
key: username
- name: APP_PASSWORD
valueFrom:
secretKeyRef:
name: app-credentials
key: password
Refreshing a Kubernetes Secret does not automatically change environment variables in running containers. Recreate the pod or configure a workload restart policy to apply new values.
Mount Secret volume
Mount each key of app-credentials from the JSON synchronization example as a file. Enter the image address of an application configured to read these files in the input field below the code, then create the following file.
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-credentials-volume
namespace: eso-demo
spec:
replicas: 1
selector:
matchLabels:
app: app-credentials-volume
template:
metadata:
labels:
app: app-credentials-volume
spec:
containers:
- name: app
image: ${APPLICATION_IMAGE}
volumeMounts:
- name: app-credentials
mountPath: /etc/app-credentials
readOnly: true
volumes:
- name: app-credentials
secret:
secretName: app-credentials
items:
- key: username
path: username
- key: password
path: password
| Variable | Description |
|---|---|
| APPLICATION_IMAGE🖌︎ | Application image address |
kubectl apply -f app-credentials-volume.yaml
kubectl rollout status deployment/app-credentials-volume -n eso-demo --timeout=120s
The container receives /etc/app-credentials/username and /etc/app-credentials/password, each containing the value of the corresponding Secret key. In this example, they contain app-user and app-password. In addition to checking Deployment readiness, verify that the application reads these paths.
When ESO updates the Kubernetes Secret, the mounted files are automatically updated after a delay that depends on cluster settings and state. If the application reads its configuration only at startup, reload the configuration or restart the pod to use the new values.
Pass TLS certificate to Ingress
Synchronize a TLS certificate and private key to a Kubernetes Secret and reference it from an existing Ingress.
Prepare the following resources:
- An Ingress and backend service in
eso-demo - An ingress controller that handles that Ingress
- A domain for the service
Create a separate JSON secret in KakaoCloud Secrets Manager with the following fields.
| Field | Value |
|---|---|
certificate | A PEM certificate including any required intermediate certificate chain |
privateKey | The PEM private key paired with certificate |
Enter the certificate and private key as PEM strings without additional Base64 encoding.
Preserve line breaks in the PEM certificate and private key. In JSON, represent line breaks within strings as \n. Do not replace line breaks with spaces.
The following example shows the JSON input format. Replace each complete string, including certificate content and private key content, with the actual PEM certificate and private key. Preserve the original private key's BEGIN and END markers.
{
"certificate": "-----BEGIN CERTIFICATE-----\ncertificate content\n-----END CERTIFICATE-----\n",
"privateKey": "-----BEGIN PRIVATE KEY-----\nprivate key content\n-----END PRIVATE KEY-----\n"
}
The example uses refreshInterval: 1h0m0s. The TLS Secret is first created after applying the manifest, and subsequent remote changes are reflected at the configured one-hour interval.
Enter the TLS Secrets Manager secret ID in the field below the code. The certificate and private key are mapped to tls.crt and tls.key, and the target Kubernetes Secret type is set to kubernetes.io/tls.
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: ingress-tls
namespace: eso-demo
spec:
refreshPolicy: Periodic
refreshInterval: 1h0m0s
secretStoreRef:
name: kakaocloud-secret-store
kind: SecretStore
target:
name: ingress-tls
creationPolicy: Owner
template:
type: kubernetes.io/tls
data:
- secretKey: tls.crt
remoteRef:
key: ${SECRET_ID}
property: certificate
- secretKey: tls.key
remoteRef:
key: ${SECRET_ID}
property: privateKey
| Variable | Description |
|---|---|
| SECRET_ID🖌︎ | TLS Secrets Manager secret ID |
Apply the manifest and check the Kubernetes Secret type and data keys.
kubectl apply -f ingress-tls-secret.yaml
kubectl wait --for=condition=Ready \
externalsecret/ingress-tls -n eso-demo --timeout=60s
kubectl get secret ingress-tls -n eso-demo \
-o go-template='{{.type}}{{"\n"}}{{range $key, $_ := .data}}{{$key}}{{"\n"}}{{end}}'
Verify that the output includes type kubernetes.io/tls and keys tls.crt and tls.key. This check alone does not validate the certificate or confirm that it matches the private key.
The following is a partial configuration for an existing Ingress in eso-demo. Enter the actual service domain in the field below the code and use it with the existing host and backend settings.
spec:
tls:
- hosts:
- ${DOMAIN}
secretName: ingress-tls
| Variable | Description |
|---|---|
| DOMAIN🖌︎ | Service domain |
After configuring the Ingress, verify HTTPS access using the actual domain. If the connection fails, check the ingress controller, DNS, and Load Balancer settings. Also verify that the certificate matches the domain and that no intermediate certificates are missing.
After running all service guide examples, see Clean up service guide example resources.
Operations and verification
This section covers synchronization policies, configuration checks, security, and use across multiple namespaces.
- Grant IAM principals only the project and Secrets Manager read permissions they need.
- Use the secret ID, not its name, in
remoteRef.key. Never store actual secret values or IAM access keys in YAML files. - Do not print secret values during operational checks. Check
Ready=True,SecretSynced, and the latest synchronization time; verify data changes separately. - Restrict access to
kakaocloud-credentialsand synchronized Secrets to required users and workloads.
Synchronization policies
refreshPolicy determines when a Kubernetes Secret is refreshed. If omitted, it behaves as Periodic.
| Policy | Behavior | Example |
|---|---|---|
Periodic | Retrieves remote values at refreshInterval; 0s disables periodic retrieval after initial synchronization | Automatically reflect secret rotation |
OnChange | Synchronizes when ExternalSecret metadata or spec changes; refreshInterval does not apply | Manually apply changes after approval |
CreatedOnce | Does not periodically refresh after initial synchronization; refreshInterval does not apply | No periodic remote updates needed |
With Periodic and refreshInterval: 0s, changing the force-sync annotation does not trigger a refresh after initial synchronization while the target Kubernetes Secret remains valid.
With creationPolicy: Owner in these examples, even CreatedOnce resynchronizes the target Kubernetes Secret if it is deleted or its data is modified.
Verification criteria
| Check | Success criteria | What to check first if it fails |
|---|---|---|
| SecretStore configuration and authentication | SecretStore reports Ready=True | IAM credentials, endpoints, and network connectivity |
| Initial secret retrieval and synchronization | ExternalSecret reports Ready=True; the target Kubernetes Secret and expected data keys exist | Secret ID, read permissions, Secrets Manager connectivity, SecretStore reference, and controller events |
| Kubernetes Secret refresh | The output matches the value of the Secrets Manager secret version being retrieved | Whether the new version exists, refreshPolicy, and version pinning |
| Environment variable injection | Recreated pods use the new value | Pod restart and secretKeyRef typos |
| Secret volume mount | Mounted files are updated and the application reloads them | Whether subPath is used and whether the application rereads changed files |
| Ingress TLS connection | Correct TLS Secret type and successful HTTPS access | Ingress controller, DNS, certificate matching the domain, and missing intermediate certificates |
SecretStore Ready=True alone does not verify read permission or a successful secret read. Verify the ExternalSecret status and target data.
Use across multiple namespaces
Use ClusterSecretStore to share connection and authentication settings across namespaces, and ClusterExternalSecret to create and manage the same ExternalSecret configuration in batches. See the ESO documentation for ClusterSecretStore and ClusterExternalSecret.
Troubleshooting
For installation errors, webhook timeouts, or synchronization failures, see ESO integration troubleshooting.