Secrets Management
The Internal Scanning chart needs two Kubernetes Secrets to run:
- A config Secret holding the Detectify license key, connector API key, and connector URL.
- A registry Secret holding Docker credentials for pulling scanner images.
How you provide each is up to you. The chart supports two independent modes — pick the combination that matches your security posture.
Deploying on AWS via the Terraform module? This page covers the chart-level mechanics, which apply equally to module-provisioned clusters. For Terraform-specific patterns (KMS-encrypted blobs, aws_secretsmanager_secret_version data sources, how the approaches compose with Terraform state), see the AWS Secrets Management guide.
Two modes
| Mode | How | When to use |
|---|---|---|
| Chart-managed (default) | Put the credentials in your Helm values under secrets.*; the chart renders the Secret for you. | Development, quick evaluations, or when keeping credentials in Helm values / Terraform state is acceptable to your security team. |
| Bring-your-own | Create the Secret yourself (via any tool), then set secrets.existingConfigSecret / secrets.existingRegistrySecret to its name. | Production setups where secrets must live outside Helm — Vault, external-secrets-operator, sealed-secrets, AWS Secrets Manager, CSI Secret Store driver, hand-managed kubernetes_secret Terraform resource, etc. |
The two existing*Secret fields are independent. Common setups:
- Both chart-managed — simplest; equivalent to the 1.x behaviour.
- Both BYO — everything flows through your secrets platform.
- Chart-managed config + BYO registry — for example, when registry creds live in Vault but the license key is fine in Terraform.
- BYO config + chart-managed registry — the mirror case.
Secret schemas
If you’re supplying Secrets yourself, they must match these shapes exactly. The chart crashloops if keys are missing.
| Secret (values reference) | Kubernetes type | Keys |
|---|---|---|
secrets.existingConfigSecret | Opaque | license-key, connector-api-key, connector-url |
secrets.existingRegistrySecret | kubernetes.io/dockerconfigjson | .dockerconfigjson |
connector-url should match config.connectorServerUrl (default https://connector.detectify.com).
Approach comparison
A quick decision guide — in practice most EKS users land on ESO, most non-EKS users land on sealed-secrets or raw kubectl. This table compares secret-management mechanisms at the chart level; for how each composes with the AWS Terraform module (Terraform state implications, rotation flow with terraform apply), see AWS Secrets Management → Comparison.
| Approach | Secrets live in | Best for | Notes |
|---|---|---|---|
| Chart values (chart-managed) | Helm release / your CI values file | Evaluations, small teams | Credentials visible to anyone with helm get values. |
Raw kubectl | Cluster only | On-premises, air-gapped | You manage rotation; fine for rarely-changing creds. |
| Sealed-secrets | Git (encrypted) + cluster | GitOps where cluster holds the decryption key | Encrypted blobs are commit-safe; decryption tied to one cluster. |
| external-secrets-operator (ESO) | External store (Vault, AWS Secrets Manager, GCP SM, Azure KV, …) | Multi-tenant / multi-cluster, central rotation | First-class AWS Secrets Manager support via ClusterSecretStore. |
| AWS Secrets Store CSI Driver | AWS Secrets Manager / Parameter Store | AWS-only, CSI-friendly workloads | Sync-on-mount caveat (see below). We recommend ESO instead for this workload. |
| AWS KMS + Terraform | Git (encrypted) + Terraform state | AWS shops that already encrypt at rest with KMS | See the AWS Terraform Secrets Management guide. |
Option 1: kubectl
The quickest BYO path. Create both Secrets in the release namespace, then install the chart pointed at them.
kubectl create namespace scanner
# Config Secret
kubectl create secret generic my-team-scanner-config \
-n scanner \
--from-literal=license-key='your-license-key' \
--from-literal=connector-api-key='your-connector-api-key' \
--from-literal=connector-url='https://connector.detectify.com'
# Registry Secret
kubectl create secret docker-registry my-team-detectify-registry \
-n scanner \
--docker-server=registry.detectify.com \
--docker-username='your-registry-username' \
--docker-password='your-registry-password'Then install the chart:
helm install detectify-scanner detectify/internal-scanning-agent \
--version '~> 2.0' \
-n scanner \
--set secrets.existingConfigSecret=my-team-scanner-config \
--set secrets.existingRegistrySecret=my-team-detectify-registryOption 2: external-secrets-operator
external-secrets-operator (ESO) syncs secrets from an external store (Vault, AWS Secrets Manager, GCP Secret Manager, Azure Key Vault, …) into native Kubernetes Secrets. Point the chart at those synced Secrets and the operator handles the rest.
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: my-team-scanner-config
namespace: scanner
spec:
refreshInterval: 1h
secretStoreRef:
name: my-store
kind: ClusterSecretStore
target:
name: my-team-scanner-config # matches secrets.existingConfigSecret
creationPolicy: Owner
data:
- secretKey: license-key
remoteRef: { key: detectify/scanner, property: licenseKey }
- secretKey: connector-api-key
remoteRef: { key: detectify/scanner, property: connectorApiKey }
- secretKey: connector-url
remoteRef: { key: detectify/scanner, property: connectorUrl }
---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: my-team-detectify-registry
namespace: scanner
spec:
refreshInterval: 1h
secretStoreRef:
name: my-store
kind: ClusterSecretStore
target:
name: my-team-detectify-registry # matches secrets.existingRegistrySecret
creationPolicy: Owner
template:
type: kubernetes.io/dockerconfigjson
data:
.dockerconfigjson: |
{"auths":{"registry.detectify.com":{"username":"{{ .username }}","password":"{{ .password }}","auth":"{{ printf "%s:%s" .username .password | b64enc }}"}}}
data:
- secretKey: username
remoteRef: { key: detectify/registry, property: username }
- secretKey: password
remoteRef: { key: detectify/registry, property: password }Install the chart referencing the synced Secrets:
helm install detectify-scanner detectify/internal-scanning-agent \
--version '~> 2.0' \
-n scanner \
--set secrets.existingConfigSecret=my-team-scanner-config \
--set secrets.existingRegistrySecret=my-team-detectify-registryOption 3: sealed-secrets
sealed-secrets encrypts Secret manifests with a cluster-specific key so they can be safely committed to Git. The controller in the cluster decrypts them on apply.
# Seal the config Secret
kubectl create secret generic my-team-scanner-config \
-n scanner \
--from-literal=license-key='...' \
--from-literal=connector-api-key='...' \
--from-literal=connector-url='https://connector.detectify.com' \
--dry-run=client -o yaml \
| kubeseal -o yaml > sealed-scanner-config.yaml
kubectl apply -f sealed-scanner-config.yamlRepeat for the registry Secret, then install the chart with secrets.existingConfigSecret / secrets.existingRegistrySecret set to the names above.
AWS Secrets Manager + external-secrets-operator (EKS)
On EKS, the most-requested combination is: credentials stored in AWS Secrets Manager, synced into the cluster by ESO. This avoids keeping secrets in Helm values, Terraform state, or Git.
This recipe follows the official external-secrets-operator patterns — ClusterSecretStore, ExternalSecret, and the AWS Secrets Manager provider with IRSA auth . Refer to the ESO docs for the authoritative reference on each resource.
Using the Detectify Terraform module on AWS? This recipe is the recommended path when secrets rotate continuously (outside terraform apply). If rotation happens via Terraform instead, the AWS-side secrets guide covers pure-Terraform alternatives (KMS-encrypted blobs, Secrets Manager via data sources).
1. Store the credentials in AWS Secrets Manager
Store each as a JSON blob so ESO can pluck named properties:
aws secretsmanager create-secret \
--name detectify/scanner \
--description 'Detectify scanner license + connector' \
--secret-string '{
"licenseKey": "your-license-key",
"connectorApiKey": "your-connector-api-key",
"connectorUrl": "https://connector.detectify.com"
}'
aws secretsmanager create-secret \
--name detectify/registry \
--description 'Detectify docker registry credentials' \
--secret-string '{
"username": "your-registry-username",
"password": "your-registry-password"
}'2. Install external-secrets-operator
helm repo add external-secrets https://charts.external-secrets.io
helm install external-secrets external-secrets/external-secrets \
-n external-secrets \
--create-namespace3. Grant ESO read access via IRSA
Create an IAM role that the ESO service account can assume (IAM Roles for Service Accounts). The trust policy should reference your EKS cluster’s OIDC provider; attach a policy like:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["secretsmanager:GetSecretValue", "secretsmanager:DescribeSecret"],
"Resource": [
"arn:aws:secretsmanager:*:*:secret:detectify/scanner-*",
"arn:aws:secretsmanager:*:*:secret:detectify/registry-*"
]
}
]
}Annotate the ESO service account with the role ARN:
kubectl annotate serviceaccount external-secrets \
-n external-secrets \
eks.amazonaws.com/role-arn=arn:aws:iam::123456789012:role/eso-secrets-reader4. Define a ClusterSecretStore
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
name: aws-secrets-manager
spec:
provider:
aws:
service: SecretsManager
region: eu-west-1
auth:
jwt:
serviceAccountRef:
name: external-secrets
namespace: external-secrets5. Create two ExternalSecret CRs
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: scanner-config
namespace: scanner
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-manager
kind: ClusterSecretStore
target:
name: scanner-config # matches secrets.existingConfigSecret
creationPolicy: Owner
data:
- secretKey: license-key
remoteRef: { key: detectify/scanner, property: licenseKey }
- secretKey: connector-api-key
remoteRef: { key: detectify/scanner, property: connectorApiKey }
- secretKey: connector-url
remoteRef: { key: detectify/scanner, property: connectorUrl }
---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: detectify-registry
namespace: scanner
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-manager
kind: ClusterSecretStore
target:
name: detectify-registry # matches secrets.existingRegistrySecret
creationPolicy: Owner
template:
type: kubernetes.io/dockerconfigjson
data:
.dockerconfigjson: |
{"auths":{"registry.detectify.com":{"username":"{{ .username }}","password":"{{ .password }}","auth":"{{ printf "%s:%s" .username .password | b64enc }}"}}}
data:
- secretKey: username
remoteRef: { key: detectify/registry, property: username }
- secretKey: password
remoteRef: { key: detectify/registry, property: password }6. Install the chart pointed at the synced Secrets
helm install detectify-scanner detectify/internal-scanning-agent \
--version '~> 2.0' \
-n scanner \
--create-namespace \
--set secrets.existingConfigSecret=scanner-config \
--set secrets.existingRegistrySecret=detectify-registryRotation
ESO re-syncs at the refreshInterval above, so new secret values appear in the cluster without manual intervention. However, the scanner pods read credentials via envFrom: secretKeyRef at start-up — updating the Secret does not restart existing pods.
Options:
- stakater/reloader — annotate the scanner Deployments and let Reloader roll them when the Secret changes.
- Manual rollout — after a rotation, run
kubectl rollout restart deployment -n scanner(or target justscan-scheduler/scan-manager/chrome-controller).
AWS Secrets Store CSI Driver (alternative)
The AWS Secrets Store CSI Driver mounts secrets from AWS Secrets Manager into pods as files or syncs them into Kubernetes Secrets on pod start.
Caveat: secrets are only synced when a pod mounting the SecretProviderClass starts. The scanner doesn’t mount secrets as files — it reads them via envFrom — so you still need the CSI driver’s optional “sync as K8s Secret” behaviour, and Secret updates still require a pod restart.
For this workload we recommend external-secrets-operator over the CSI driver. ESO’s ExternalSecret custom resource is more ergonomic for sync-and-forget secrets that the scanner reads once at start-up.
Switching modes later
You can move from chart-managed to BYO (or vice versa) with a helm upgrade. Helm removes the previously chart-managed scanner-config / detectify-registry Secrets automatically on the next release when you set secrets.existingConfigSecret / secrets.existingRegistrySecret. No manual cleanup required.
Multi-tenant deployments
Running one scanner per team or tenant? Put each in its own namespace (helm -n tenant-a, helm -n tenant-b, …) and give each its own Secret names. BYO mode makes this clean — each tenant owns its own ExternalSecret / sealed-secret referencing its own upstream secret, and the scanners never see each other’s credentials.
See Use Cases for the broader multi-tenancy story.
Next Steps
- Getting Started — Install the chart
- Configuration — Full values reference
- Upgrade from Chart 1.x — Migrate an existing install
- Troubleshooting — Common issues