This directory contains the configuration for deploying the External Secrets Operator (ESO) with Bitwarden Secrets Manager integration. The operator is deployed in the external-secrets namespace and uses Sealed Secrets for encrypting credentials at rest in Git.
- Bitwarden organization with Secrets Manager enabled
- Bitwarden machine account access token
kubesealCLI tool installed- Sealed Secrets controller running in the cluster (
kube-systemnamespace) - cert-manager installed and running (required for TLS certificate chain)
- ArgoCD installed and configured to manage this repository
The setup uses a self-signed certificate chain managed by cert-manager:
- Bootstrap ClusterIssuer (self-signed) creates a CA certificate
- CA ClusterIssuer uses that CA to issue leaf certificates
- Leaf certificate (
bitwarden-tls-certs) is mounted by the Bitwarden SDK server for TLS - Client certificate (
bitwarden-css-certs) is used for client authentication - ClusterSecretStore connects to the SDK server over HTTPS and trusts it via the CA
ArgoCD sync-wave annotations enforce the correct deployment order:
| Wave | Resource |
|---|---|
-3 |
Bootstrap ClusterIssuer (self-signed) |
-2 |
Bootstrap CA Certificate |
-1 |
CA ClusterIssuer |
0 |
Leaf TLS + client certificates, SDK server |
1 |
ClusterSecretStore |
Generate a machine account access token in Bitwarden Secrets Manager, then create a Kubernetes secret:
kubectl create secret generic bitwarden-access-token \
--from-literal=token=YOUR_ACCESS_TOKEN \
-n external-secrets \
--dry-run=client \
-o yaml > bitwarden-access-token.yamlFetch the Sealed Secrets public key and seal the secret:
kubeseal --fetch-cert > public-key.pem
kubeseal --format=yaml \
--cert=public-key.pem \
--scope=namespace-wide \
< bitwarden-access-token.yaml \
> bitwarden-access-token-sealed.yamlReplace the contents of templates/bitwarden-access-token-sealed.yaml with the output.
The templates/bitwarden-secret-store.yaml file contains the ClusterSecretStore configuration. Update these fields with your Bitwarden organization details:
organizationID— your Bitwarden organization UUIDprojectID— your Bitwarden project UUID
Commit and push your changes. ArgoCD will automatically detect and deploy the configuration via the ApplicationSet.
To sync a secret from Bitwarden into Kubernetes, create a YAML file in the target application directory:
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: my-app-secret
namespace: my-namespace
spec:
refreshInterval: "1h"
secretStoreRef:
name: bitwarden-secretsmanager
kind: ClusterSecretStore
data:
- secretKey: API_KEY
remoteRef:
key: my-bitwarden-secret-name
conversionStrategy: Default
decodingStrategy: None
metadataPolicy: NoneCommit and push. ArgoCD will apply it automatically.
-
Certificate errors / TLS handshake failures:
- Verify cert-manager is running and certificates are issued:
kubectl get certificates -n external-secrets kubectl get certificates -n cert-manager
- Check the
ca.crtkey exists in the TLS secret:kubectl get secret bitwarden-tls-certs -n external-secrets -o jsonpath='{.data.ca\.crt}' | base64 -d | openssl x509 -text -noout
- Check what certificate the SDK server is presenting:
kubectl exec -n external-secrets deploy/bitwarden-sdk-server -- \ openssl s_client -connect localhost:9998 -showcerts
- Verify cert-manager is running and certificates are issued:
-
Secret not found:
- Verify the secret name in Bitwarden matches the
remoteRef.key - Check that the access token has permissions for the project
- Ensure the secret belongs to the correct organization and project
- Verify the secret name in Bitwarden matches the
-
Authentication errors:
- Verify the sealed secret was created correctly:
kubectl get secret bitwarden-access-token -n external-secrets
- Check if the access token has expired or been revoked
- Verify the sealed secret was created correctly:
-
Sync issues:
# Check operator logs kubectl logs -n external-secrets -l app.kubernetes.io/name=external-secrets # Check SDK server logs kubectl logs -n external-secrets -l app.kubernetes.io/name=bitwarden-sdk-server # Check the status of your ExternalSecret kubectl get externalsecret -n <namespace> <secret-name> -o yaml
- Never commit unsealed secrets to the repository
- Rotate your Bitwarden access tokens regularly (every 90 days recommended)
- Use the principle of least privilege for access token permissions
- Monitor the External Secrets Operator logs for synchronization issues
- TLS certificates auto-rotate every 90 days (renewal starts 3 days before expiry)