Skip to Content
Internal ScanningDeployHelmSecrets Management (Chart)

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

ModeHowWhen 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-ownCreate 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 typeKeys
secrets.existingConfigSecretOpaquelicense-key, connector-api-key, connector-url
secrets.existingRegistrySecretkubernetes.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.

ApproachSecrets live inBest forNotes
Chart values (chart-managed)Helm release / your CI values fileEvaluations, small teamsCredentials visible to anyone with helm get values.
Raw kubectlCluster onlyOn-premises, air-gappedYou manage rotation; fine for rarely-changing creds.
Sealed-secretsGit (encrypted) + clusterGitOps where cluster holds the decryption keyEncrypted 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 rotationFirst-class AWS Secrets Manager support via ClusterSecretStore.
AWS Secrets Store CSI DriverAWS Secrets Manager / Parameter StoreAWS-only, CSI-friendly workloadsSync-on-mount caveat (see below). We recommend ESO instead for this workload.
AWS KMS + TerraformGit (encrypted) + Terraform stateAWS shops that already encrypt at rest with KMSSee 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-registry

Option 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-registry

Option 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.yaml

Repeat 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-namespace

3. 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-reader

4. 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-secrets

5. 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-registry

Rotation

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 just scan-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

Last updated on