Skip to main content

Extra

¿Qué pasa si borramos un secret en AWS que está mapeado?

❯ k get secrets
NAME TYPE DATA AGE
app-auth kubernetes.io/basic-auth 2 104m
mi-secret Opaque 1 37m
mi-secret-k8s Opaque 4 129m

❯ k describe es external-secret-auth
Name: external-secret-auth
Namespace: default
Labels: <none>
Annotations: <none>
API Version: external-secrets.io/v1
Kind: ExternalSecret
Metadata:
Creation Timestamp: 2025-07-01T01:13:27Z
Generation: 1
Resource Version: 3407507
UID: acf4b93d-b1ee-4c36-b0fa-0a1ed172ae10
Spec:
Data From:
Extract:
Conversion Strategy: Default
Decoding Strategy: None
Key: test/app
Metadata Policy: None
Refresh Interval: 1m
Secret Store Ref:
Kind: ClusterSecretStore
Name: aws-secrets-manager
Target:
Creation Policy: Owner
Deletion Policy: Retain
Name: app-auth
Template:
Engine Version: v2
Merge Policy: Replace
Metadata:
Labels:
App: my-app
Type: kubernetes.io/basic-auth
Status:
Binding:
Name: app-auth
Conditions:
Last Transition Time: 2025-07-01T01:13:27Z
Message: secret synced
Reason: SecretSynced
Status: True # ¡Atención aquí!
Type: Ready
Refresh Time: 2025-07-01T02:58:27Z
Synced Resource Version: 1-dd100cb940ef5edeb926b19bde61ff16c82209bcb92fb4574e67bbcf
Events: <none>

Borré test/app.

Si borras un secret en AWS que está siendo usado por un ExternalSecret, lo que pasa dependerá de la configuración y del comportamiento por defecto del ESO:

  • El ESO intenta sincronizar a cada refreshInterval.
  • Si no encuentra el secret en el provider, no borra el Secret de Kubernetes por defecto.
  • Pero el .status.conditions del ExternalSecret entra en error con algo tipo:
❯ k describe es external-secret-auth
Name: external-secret-auth
Namespace: default
Labels: <none>
Annotations: <none>
API Version: external-secrets.io/v1
Kind: ExternalSecret
Metadata:
Creation Timestamp: 2025-07-01T01:13:27Z
Generation: 1
Resource Version: 3408307
UID: acf4b93d-b1ee-4c36-b0fa-0a1ed172ae10
Spec:
Data From:
Extract:
Conversion Strategy: Default
Decoding Strategy: None
Key: test/app
Metadata Policy: None
Refresh Interval: 1m
Secret Store Ref:
Kind: ClusterSecretStore
Name: aws-secrets-manager
Target:
Creation Policy: Owner
Deletion Policy: Retain
Name: app-auth
Template:
Engine Version: v2
Merge Policy: Replace
Metadata:
Labels:
App: my-app
Type: kubernetes.io/basic-auth
Status:
Binding:
Name: app-auth
Conditions:
Last Transition Time: 2025-07-01T03:00:27Z
Message: could not get secret data from provider # Mensaje
Reason: SecretSyncedError # Error
Status: False # Ya está como status false
Type: Ready
Refresh Time: 2025-07-01T02:59:27Z
Synced Resource Version: 1-dd100cb940ef5edeb926b19bde61ff16c82209bcb92fb4574e67bbcf
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning UpdateFailed 2m18s external-secrets error processing spec.dataFrom[0].extract, err: operation error Secrets Manager: GetSecretValue, https response error StatusCode: 400, RequestID: 4dee00d1-6413-4f21-adad-5e7b5bebf05d, InvalidRequestException: You can't perform this operation on the secret because it was marked for deletion.

El ESO no borra el Secret de K8s solo porque el secret del provider desapareció. Esto es proposital, para no causar quiebre de app por error externo. Si retornas el secret el status quedará true y el mensaje de error desaparecerá.

Consejo de buenas prácticas: Siempre ten alertas en los .status.conditions de los ExternalSecrets con Prometheus u otro sistema. Valida si el .status.conditions[].status == False y reason == SecretSyncedError.

ClusterExternalSecret

Si tienes 20 namespaces y quieres replicar un mismo ExternalSecret, sin copiar el YAML 20 veces... existe el ClusterExternalSecret.

apiVersion: external-secrets.io/v1beta1
kind: ClusterExternalSecret
metadata:
name: sync-para-todos
spec:
namespaceSelector:
matchLabels:
sync-db: "true"
template:
metadata:
name: db-secret
spec:
refreshInterval: 1m
secretStoreRef:
name: aws-secrets-manager
kind: ClusterSecretStore
target:
name: mi-secret-k8s
creationPolicy: Owner
data:
- secretKey: DB_USER
remoteRef:
key: test/app
property: username
- secretKey: DB_PASS
remoteRef:
key: test/app
property: password

  • El ESO va a crear un ExternalSecret llamado db-secret en cada namespace con sync-db: "true".
  • Ese ExternalSecret va a tener exactamente el contenido del template.
  • Cada namespace recibirá su propio Secret llamado mi-secret-k8s.

Si quieres algo más dinámico aún puedes usar el Go Template para cambiar el nombre del secret por namespace.

###...
spec:
refreshInterval: 1m
secretStoreRef:
name: aws-secrets-manager
kind: ClusterSecretStore
target:
name: app-secret-{{ .metadata.namespace }}
creationPolicy: Owner
###...

Secret de Múltiples Providers (Multi-Backend)

Tener un SecretStore para AWS, otro para GCP, otro para Vault y combinar todo en el mismo ExternalSecret, tomando datos de cada uno vía data o dataFrom.

apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: multi-provider-secret
namespace: default
spec:
refreshInterval: 1h
target:
name: mi-secret-final
creationPolicy: Owner
# El secretStoreRef en el nivel del spec (global) es opcional si todos los campos usan un store específico en data.
data:
- secretKey: DB_USER
remoteRef:
key: my-app/credentials
property: username

secretStoreRef:
name: aws-secretstore
kind: ClusterSecretStore

- secretKey: GCP_API_KEY
remoteRef:
key: gcp-secret-id
secretStoreRef:
name: gcp-secretstore
kind: ClusterSecretStore

- secretKey: VAULT_TOKEN
remoteRef:
key: secret/data/my-app
property: token
secretStoreRef:
name: vault-secretstore
kind: ClusterSecretStore

Y tener un único secret juntando varias partes esperando un resultado tipo este en el secret.

data:
DB_USER: <viene de AWS>
GCP_API_KEY: <viene de GCP Secrets Manager>
VAULT_TOKEN: <viene de HashiCorp Vault>