Kubernetes operator for generating secrets with templates and storing them in multiple destinations.
It plays well with Kubernetes External Secrets too.
- Multiple Storage: Kubernetes secrets, AWS Secrets Manager, AWS Parameter Store, Azure Key Vault, GCP Secret Manager, HashiCorp Vault
- Template Engine: Go templates with crypto, random, and TLS generators
- Create-Once: Secrets generated once and never modified
- Cloud Integration: AWS, Azure, GCP, and HashiCorp Vault authentication support
- Dry-Run Mode: Validate templates and preview masked output without creating secrets
- Metadata: Automatic metadata for traceability and observability
helm repo add logiciq https://charts.logiciq.ca
helm install secret-santa logiciq/secret-santaFor AWS Secrets Manager or Parameter Store:
# With service account annotations (EKS)
helm install secret-santa logiciq/secret-santa \
--set aws.enabled=true \
--set aws.region=us-west-2 \
--set serviceAccount.annotations."eks\.amazonaws\.com/role-arn"=arn:aws:iam::123456789012:role/secret-santa
# With environment variables
helm install secret-santa logiciq/secret-santa \
--set aws.enabled=true \
--set aws.region=us-west-2 \
--set aws.credentials.useServiceAccount=false \
--set aws.credentials.accessKeyId=AKIA... \
--set aws.credentials.secretAccessKey=...For Azure Key Vault:
# With service principal
# amazonq-ignore-next-line
helm install secret-santa logiciq/secret-santa \
--set azure.enabled=true \
--set azure.tenantId=00000000-0000-0000-0000-000000000000 \
--set azure.credentials.useManagedIdentity=false \
--set azure.credentials.clientId=00000000-0000-0000-0000-000000000000 \
--set azure.credentials.clientSecret=your-client-secret
# With managed identity (AKS)
helm install secret-santa logiciq/secret-santa \
--set azure.enabled=true \
--set azure.tenantId=00000000-0000-0000-0000-000000000000 \
--set azure.credentials.useManagedIdentity=true \
--set serviceAccount.annotations."azure\.workload\.identity/client-id"=00000000-0000-0000-0000-000000000000For HashiCorp Vault:
# With Kubernetes auth (recommended in-cluster)
helm install secret-santa logiciq/secret-santa \
--set vault.enabled=true \
--set vault.address=https://vault.example.com \
--set vault.authMethod=kubernetes \
--set vault.role=secret-santa
# With token auth
helm install secret-santa logiciq/secret-santa \
--set vault.enabled=true \
--set vault.address=https://vault.example.com \
--set vault.token=<vault-token>For GCP Secret Manager:
# With service account key file
# amazonq-ignore-next-line
helm install secret-santa logiciq/secret-santa \
--set gcp.enabled=true \
--set gcp.projectId=my-project-id \
--set gcp.credentials.useWorkloadIdentity=false \
--set gcp.credentials.keyFile=/etc/gcp/key.json \
--set gcp.credentials.existingSecret=gcp-service-account-key
# With workload identity (GKE)
helm install secret-santa logiciq/secret-santa \
--set gcp.enabled=true \
--set gcp.projectId=my-project-id \
--set gcp.credentials.useWorkloadIdentity=true \
--set serviceAccount.annotations."iam\.gke\.io/gcp-service-account"=secret-santa@my-project.iam.gserviceaccount.comValidate templates and preview output without creating secrets:
apiVersion: secrets.secret-santa.io/v1alpha1
kind: SecretSanta
metadata:
name: dry-run-example
spec:
dryRun: true
template: |
{
"password": "{{ .pass.value }}",
"api_key": "{{ .key.value }}"
}
generators:
- name: pass
type: random_password
config:
length: 32
- name: key
type: random_string
config:
length: 64Check the status for masked output:
kubectl get secretsanta dry-run-example -o yaml
# status.dryRunResult.maskedOutput shows structure with <MASKED> valuesapiVersion: secrets.secret-santa.io/v1alpha1
kind: SecretSanta
metadata:
name: app-password
spec:
template: |
password: {{ .pass.password }}
generators:
- name: pass
type: random_password
config:
length: 32
media:
type: k8s # Default - can be omittedapiVersion: secrets.secret-santa.io/v1alpha1
kind: SecretSanta
metadata:
name: tls-cert
spec:
template: |
tls.crt: {{ .cert.certificate }}
tls.key: {{ .key.private_key_pem }}
generators:
- name: key
type: tls_private_key
- name: cert
type: tls_self_signed_cert
config:
key_pem: "{{ .key.private_key_pem }}"
subject:
common_name: example.com
secretType: kubernetes.io/tls
media:
type: k8s # Default - can be omittedapiVersion: secrets.secret-santa.io/v1alpha1
kind: SecretSanta
metadata:
name: aws-secret
spec:
template: |
{
"username": "admin",
"password": "{{ .pass.password }}"
}
generators:
- name: pass
type: random_password
config:
length: 24
media:
type: aws-secrets-manager
config:
region: us-west-2
kms_key_id: alias/secrets-keyapiVersion: secrets.secret-santa.io/v1alpha1
kind: SecretSanta
metadata:
name: db-url
spec:
template: |
postgresql://user:{{ .pass.password }}@db.example.com/app
generators:
- name: pass
type: random_password
config:
length: 32
media:
type: aws-parameter-store
config:
region: us-east-1
parameter_name: /app/database-urlapiVersion: secrets.secret-santa.io/v1alpha1
kind: SecretSanta
metadata:
name: azure-secret
spec:
template: |
{
"username": "admin",
"password": "{{ .pass.password }}"
}
generators:
- name: pass
type: random_password
config:
length: 24
media:
type: azure-key-vault
config:
vault_url: https://my-vault.vault.azure.net
secret_name: app-credentialsapiVersion: secrets.secret-santa.io/v1alpha1
kind: SecretSanta
metadata:
name: gcp-secret
spec:
template: |
{
"username": "admin",
"password": "{{ .pass.password }}"
}
generators:
- name: pass
type: random_password
config:
length: 24
media:
type: gcp-secret-manager
config:
project_id: my-gcp-project
secret_name: app-credentialsapiVersion: secrets.secret-santa.io/v1alpha1
kind: SecretSanta
metadata:
name: vault-secret
spec:
template: |
{
"username": "admin",
"password": "{{ .pass.password }}"
}
generators:
- name: pass
type: random_password
config:
length: 24
media:
type: hashicorp-vault
config:
address: https://vault.example.com
mount_path: secret
path: myapp/credentials
auth_method: kubernetes
role: secret-santa# Default media - can be omitted entirely
media:
type: k8s
# Or with custom secret name
media:
type: k8s
config:
secret_name: my-custom-secret-namemedia:
type: aws-secrets-manager
config:
region: us-west-2 # Optional
secret_name: my-custom-secret # Optional
kms_key_id: alias/my-kms-key # Optionalmedia:
type: aws-parameter-store
config:
region: us-east-1 # Optional
parameter_name: /my/custom/param # Optional
kms_key_id: alias/my-kms-key # Optionalmedia:
type: azure-key-vault
config:
vault_url: https://my-vault.vault.azure.net # Required
secret_name: my-custom-secret # Optional
tenant_id: 00000000-0000-0000-0000-000000000000 # Optional - uses default if emptymedia:
type: gcp-secret-manager
config:
project_id: my-gcp-project # Required
secret_name: my-custom-secret # Optional
credentials_file: /path/to/key.json # Optional - uses workload identity if emptymedia:
type: hashicorp-vault
config:
address: https://vault.example.com # Optional - uses VAULT_ADDR env if empty
mount_path: secret # Optional - defaults to "secret"
path: myapp/credentials # Optional - defaults to namespace/name
auth_method: kubernetes # Optional - "kubernetes" or omit for token
role: secret-santa # Optional - Vault role for kubernetes auth
token: <vault-token> # Optional - use for token authrandom_password- Secure passwordsrandom_string- Random stringsrandom_uuid- UUIDsrandom_bytes- Byte arrays
tls_private_key- Private keystls_self_signed_cert- Self-signed certificatestls_cert_request- Certificate requests
crypto_aes_key- AES keyscrypto_rsa_key- RSA keyscrypto_ed25519_key- Ed25519 keys
controller:
replicas: 1
args:
maxConcurrentReconciles: 5
watchNamespaces: ["default", "production"]
logLevel: "info"
aws:
enabled: true
region: us-west-2
credentials:
useServiceAccount: true
azure:
enabled: true
tenantId: 00000000-0000-0000-0000-000000000000
credentials:
useManagedIdentity: true
# OR for service principal:
# useManagedIdentity: false
# clientId: 00000000-0000-0000-0000-000000000000
# existingSecret: azure-credentials
# existingSecretKey: client-secret
gcp:
enabled: true
projectId: my-gcp-project
credentials:
useWorkloadIdentity: true
# OR for service account key:
# useWorkloadIdentity: false
# existingSecret: gcp-service-account-key
# existingSecretKey: key.json
vault:
enabled: true
address: https://vault.example.com
authMethod: kubernetes
role: secret-santa
# OR for token auth:
# authMethod: ""
# token: <vault-token>
serviceAccount:
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/secret-santa
azure.workload.identity/client-id: 00000000-0000-0000-0000-000000000000
iam.gke.io/gcp-service-account: secret-santa@my-project.iam.gserviceaccount.comSECRET_SANTA_MAX_CONCURRENT_RECONCILES=5
SECRET_SANTA_WATCH_NAMESPACES=default,production
SECRET_SANTA_LOG_LEVEL=debug
SECRET_SANTA_DRY_RUN=true
SECRET_SANTA_ENABLE_METADATA=false
AWS_REGION=us-west-2
AZURE_TENANT_ID=00000000-0000-0000-0000-000000000000
AZURE_CLIENT_ID=00000000-0000-0000-0000-000000000000
GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json
GCP_PROJECT_ID=my-gcp-project
VAULT_ADDR=https://vault.example.com
VAULT_TOKEN=<vault-token>Use dryRun: true to validate templates and generator configurations:
- Validates template syntax
- Checks generator types and configurations
- Executes generators and templates
- Masks sensitive output in status
- No secrets are created
status:
dryRunResult:
maskedOutput: |
{
"password": "<MASKED>",
"api_key": "<MASKED>"
}
generatorsUsed:
- "pass (random_password)"
- "key (random_string)"
executionTime: "2024-01-15T10:30:00Z"
conditions:
- type: DryRunComplete
status: "True"
message: "Dry-run completed successfully with masked output"Enable dry-run for all resources via controller flag:
helm install secret-santa logiciq/secret-santa \
--set controller.args.dryRun=trueAutomatic metadata is added to all generated secrets for traceability:
secrets.secret-santa.io/created-at: Creation timestampsecrets.secret-santa.io/generator-types: Generator types usedsecrets.secret-santa.io/template-checksum: Template checksumsecrets.secret-santa.io/source-cr: Source SecretSanta reference
Same metadata keys with platform-specific formatting.
helm install secret-santa logiciq/secret-santa \
--set controller.args.enableMetadata=false