Secrets Management
This guide explains how to securely handle sensitive credentials when deploying the Internal Scanner.
What Are Secrets?
The Internal Scanner requires credentials provided by Detectify:
| Secret | Description | Where to Find |
|---|---|---|
| License Key | Activates your scanner instance | Detectify UI → Internal Scanning Agents |
| Connector API Key | Authenticates with Detectify platform | Detectify UI → Internal Scanning Agents |
| Registry Credentials | Pulls scanner container images | Detectify UI → Internal Scanning Agents |
These credentials are generated by Detectify and have a validity period managed on Detectify’s side. They are static and do not rotate automatically.
Security Principle: Secrets should never exist in plaintext in version control, logs, or anywhere they could be accidentally exposed.
Recommended Approaches
Choose the approach that fits your infrastructure:
┌─────────────────────────────────────────────────────────────────────────────┐
│ SECURE SECRETS MANAGEMENT │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ KMS Encryption (Recommended) AWS Secrets Manager │
│ ┌─────────────────────────┐ ┌─────────────────────────┐ │
│ │ Encrypted file in repo │ │ Centralized storage │ │
│ │ │ │ │ │
│ │ ✓ Version controlled │ │ ✓ Cross-account access │ │
│ │ ✓ Audit via CloudTrail │ │ ✓ Fine-grained IAM │ │
│ │ ✓ Team collaboration │ │ ✓ Central audit logs │ │
│ │ ✓ CI/CD friendly │ │ ✓ No files in repo │ │
│ └─────────────────────────┘ └─────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘| Approach | Best For |
|---|---|
| KMS Encryption | Most deployments - encrypted secrets stored in version control |
| AWS Secrets Manager | Organizations with centralized secrets management policies |
Note: For initial local testing only, you can use a gitignored
terraform.tfvarsfile as shown in the Terraform guide. However, you should set up proper secrets management before deploying to any shared environment.
KMS Encryption (Recommended)
Encrypt secrets with AWS KMS and store the encrypted file in version control. Terraform decrypts them automatically during deployment.
How It Works
┌──────────────────────────────────────────────────────────────────────────────┐
│ ENCRYPTION WORKFLOW │
│ │
│ Developer Workstation │
│ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 1. Create secrets.json 2. Run encrypt.sh │ │
│ │ ┌─────────────────┐ ┌─────────────────┐ │ │
│ │ │ { │ │ aws kms encrypt │ │ │
│ │ │ "license_key":│ ──────► │ --key-id alias/ │ │ │
│ │ │ "abc123", │ │ scanner-secrets │ │ │
│ │ │ ... │ └────────┬────────┘ │ │
│ │ │ } │ │ │ │
│ │ └─────────────────┘ ▼ │ │
│ │ │ ┌─────────────────┐ │ │
│ │ │ │ secrets. │ │ │
│ │ ▼ │ encrypted │ │ │
│ │ ┌─────────────────┐ │ (binary blob) │ │ │
│ │ │ DELETE THIS │ └────────┬────────┘ │ │
│ │ │ FILE! │ │ │ │
│ │ └─────────────────┘ │ │ │
│ │ ▼ │ │
│ │ ┌─────────────────┐ │ │
│ │ │ git commit │ │ │
│ │ │ (safe to commit)│ │ │
│ │ └─────────────────┘ │ │
│ │ │ │
│ └────────────────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ DECRYPTION WORKFLOW │
│ │
│ CI/CD Pipeline │
│ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 1. Checkout repo 2. Terraform apply │ │
│ │ ┌─────────────────┐ ┌─────────────────┐ │ │
│ │ │ git clone │ │ aws_kms_secrets │ │ │
│ │ │ ... │ ──────► │ data source │ │ │
│ │ │ secrets/ │ │ (auto-decrypt) │ │ │
│ │ │ secrets. │ └────────┬────────┘ │ │
│ │ │ encrypted │ │ │ │
│ │ └─────────────────┘ ▼ │ │
│ │ ┌─────────────────┐ │ │
│ │ │ Plaintext in │ │ │
│ │ │ memory only │ │ │
│ │ │ (never on disk) │ │ │
│ │ └────────┬────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌─────────────────┐ │ │
│ │ │ Pass to module │ │ │
│ │ │ as variables │ │ │
│ │ └─────────────────┘ │ │
│ │ │ │
│ └────────────────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘Prerequisites
- AWS CLI configured with appropriate permissions
- Terraform >= 1.5.0 installed
- Detectify credentials from UI (Internal Scanning Agents):
- License key
- Connector API key
- Docker registry credentials (username, password)
Step 1: Create a KMS Key
Create a dedicated KMS key for encrypting scanner secrets:
# kms.tf
resource "aws_kms_key" "scanner_secrets" {
description = "KMS key for Internal Scanner secrets"
deletion_window_in_days = 30
enable_key_rotation = true
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "Enable IAM User Permissions"
Effect = "Allow"
Principal = {
AWS = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"
}
Action = "kms:*"
Resource = "*"
},
{
Sid = "Allow Pipeline Role to Decrypt"
Effect = "Allow"
Principal = {
AWS = var.pipeline_role_arn # Your CI/CD role
}
Action = [
"kms:Decrypt",
"kms:DescribeKey"
]
Resource = "*"
}
]
})
tags = {
Name = "internal-scanner-secrets"
Purpose = "Encrypt deployment secrets"
}
}
resource "aws_kms_alias" "scanner_secrets" {
name = "alias/internal-scanner-secrets"
target_key_id = aws_kms_key.scanner_secrets.key_id
}
data "aws_caller_identity" "current" {}
variable "pipeline_role_arn" {
description = "ARN of the IAM role used by your CI/CD pipeline"
type = string
}
output "kms_key_alias" {
description = "Use this alias when encrypting secrets"
value = aws_kms_alias.scanner_secrets.name
}Apply to create the key:
terraform init
terraform apply -target=aws_kms_key.scanner_secrets -target=aws_kms_alias.scanner_secretsStep 2: Create Secrets Directory
mkdir -p secrets/productionStep 3: Create Plaintext Secrets File
Create secrets/production/secrets.json:
{
"registry_username": "your-registry-username",
"registry_password": "your-registry-password",
"license_key": "your-license-key",
"connector_api_key": "your-connector-api-key"
}Security: This file will be deleted after encryption. Never commit it.
Step 4: Create Encryption Script
Create secrets/production/encrypt.sh:
#!/bin/bash
set -e
# ============================================================
# CONFIGURATION - Update these values for your environment
# ============================================================
KEY_ALIAS="alias/internal-scanner-secrets"
REGION="eu-west-1"
# AWS_PROFILE="your-profile" # Uncomment if using named profile
# ============================================================
# File paths (no changes needed)
# ============================================================
SECRETS_FILE="secrets.json"
ENCRYPTED_FILE="secrets.encrypted"
# Validate secrets file exists
if [ ! -f "$SECRETS_FILE" ]; then
echo "Error: $SECRETS_FILE not found"
echo ""
echo "Create it with this structure:"
echo '{'
echo ' "registry_username": "...", '
echo ' "registry_password": "...",'
echo ' "license_key": "...",'
echo ' "connector_api_key": "..."'
echo '}'
exit 1
fi
# Validate JSON syntax
if ! jq empty "$SECRETS_FILE" 2>/dev/null; then
echo "Error: $SECRETS_FILE contains invalid JSON"
exit 1
fi
echo "Encrypting secrets with KMS..."
echo " Key: $KEY_ALIAS"
echo " Region: $REGION"
# Encrypt
aws kms encrypt \
--region "$REGION" \
${AWS_PROFILE:+--profile "$AWS_PROFILE"} \
--key-id "$KEY_ALIAS" \
--plaintext "fileb://$SECRETS_FILE" \
--output text \
--query CiphertextBlob | base64 --decode > "$ENCRYPTED_FILE"
echo ""
echo "Encryption successful!"
echo ""
echo "Next steps:"
echo " 1. Delete plaintext: rm $SECRETS_FILE"
echo " 2. Commit encrypted: git add $ENCRYPTED_FILE"Make executable and run:
chmod +x secrets/production/encrypt.sh
cd secrets/production
./encrypt.shStep 5: Delete Plaintext and Commit
# Delete plaintext (required!)
rm secrets.json
# Commit encrypted file
git add secrets.encrypted encrypt.sh
git commit -m "Add encrypted scanner secrets"Step 6: Configure Terraform Decryption
Create secrets.tf in your main Terraform directory:
#---------------------------------------------------------------
# Secrets Decryption
#---------------------------------------------------------------
# Decrypts KMS-encrypted secrets at runtime.
# The encrypted file is safe to store in version control.
data "aws_kms_secrets" "scanner" {
secret {
name = "secrets_json"
payload = filebase64("${path.module}/secrets/${var.environment}/secrets.encrypted")
}
}
locals {
# Parse decrypted JSON
secrets = jsondecode(data.aws_kms_secrets.scanner.plaintext["secrets_json"])
# Extract individual values for use in module
registry_username = local.secrets["registry_username"]
registry_password = local.secrets["registry_password"]
license_key = local.secrets["license_key"]
connector_api_key = local.secrets["connector_api_key"]
}Step 7: Use Decrypted Secrets in Module
Update main.tf:
module "internal_scanner" {
source = "detectify/detectify-internal-scanning/aws"
version = "1.0.0"
# ... network configuration ...
# Secrets (decrypted at runtime)
license_key = local.license_key
connector_api_key = local.connector_api_key
registry_username = local.registry_username
registry_password = local.registry_password
}Step 8: Configure .gitignore
# Terraform state
.terraform/
*.tfstate
*.tfstate.*
# Plaintext secrets - NEVER commit
secrets/*/secrets.json
*.auto.tfvars
secrets.tfvars
# Encrypted secrets - safe to commit
!secrets/*/secrets.encryptedUpdating Secrets
When you need to change a secret value:
┌────────────────────────────────────────────────────────────────────┐
│ SECRET UPDATE WORKFLOW │
│ │
│ 1. Decrypt ──► 2. Edit ──► 3. Re-encrypt ──► 4. Commit |
│ │
│ ./decrypt.sh vi secrets.json ./encrypt.sh git add. |
│ rm secrets.json git commit |
│ │
└────────────────────────────────────────────────────────────────────┘Create secrets/production/decrypt.sh for this workflow:
#!/bin/bash
set -e
REGION="eu-west-1"
# AWS_PROFILE="your-profile" # Uncomment if using named profile
ENCRYPTED_FILE="secrets.encrypted"
DECRYPTED_FILE="secrets.json"
if [ ! -f "$ENCRYPTED_FILE" ]; then
echo "Error: $ENCRYPTED_FILE not found"
exit 1
fi
echo "Decrypting secrets..."
aws kms decrypt \
--region "$REGION" \
${AWS_PROFILE:+--profile "$AWS_PROFILE"} \
--ciphertext-blob "fileb://$ENCRYPTED_FILE" \
--output text \
--query Plaintext | base64 --decode > "$DECRYPTED_FILE"
echo "Decrypted to: $DECRYPTED_FILE"
echo ""
echo "WARNING: Delete this file after editing!"
echo " Run: rm $DECRYPTED_FILE"AWS Secrets Manager
For organizations with centralized secrets management policies or teams that prefer not to store encrypted files in repositories.
Architecture
┌──────────────────────────────────────────────────────────────────────────────┐
│ SECRETS MANAGER ARCHITECTURE │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌───────────────┐ │
│ │ AWS Secrets │ │ Terraform │ │ Scanner │ │
│ │ Manager │◄────────│ (runtime) │────────►│ Module │ │
│ │ │ fetch │ │ pass │ │ │
│ │ /internal- │ │ data source │ │ variables │ │
│ │ scanner/prod │ │ │ │ │ │
│ └─────────────────┘ └─────────────────┘ └───────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘Store Secrets
aws secretsmanager create-secret \
--name "internal-scanner/production" \
--description "Internal Scanner credentials" \
--secret-string '{
"registry_username": "your-registry-username",
"registry_password": "your-registry-password",
"license_key": "your-license-key",
"connector_api_key": "your-connector-api-key"
}'Terraform Configuration
# secrets.tf
data "aws_secretsmanager_secret_version" "scanner" {
secret_id = "internal-scanner/${var.environment}"
}
locals {
secrets = jsondecode(data.aws_secretsmanager_secret_version.scanner.secret_string)
registry_username = local.secrets["registry_username"]
registry_password = local.secrets["registry_password"]
license_key = local.secrets["license_key"]
connector_api_key = local.secrets["connector_api_key"]
}IAM Permissions
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["secretsmanager:GetSecretValue"],
"Resource": "arn:aws:secretsmanager:*:*:secret:internal-scanner/*"
}
]
}Security Best Practices Checklist
Follow these practices regardless of which level you choose:
Repository Security
- Add
secrets.json,*.tfvarsto.gitignore - Enable branch protection on main branch
- Require pull request reviews for changes to secrets directory
- Use git-secrets or similar to prevent accidental commits
Access Control
- Use least-privilege IAM policies
- Separate encrypt (developers) and decrypt (pipeline) permissions
- Rotate KMS keys annually (enabled by default with
enable_key_rotation = true) - Audit KMS key usage via CloudTrail
Operational Security
- Never log secret values (Terraform marks them sensitive automatically)
- Use separate secrets per environment (production, staging)
- Document procedures for updating secrets when Detectify issues new credentials
- Test secret recovery procedures
IAM Policy Reference
KMS Encryption (for developers)
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowEncrypt",
"Effect": "Allow",
"Action": ["kms:Encrypt", "kms:DescribeKey"],
"Resource": "arn:aws:kms:REGION:ACCOUNT:key/KEY-ID"
}
]
}KMS Decryption (for CI/CD pipeline)
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowDecrypt",
"Effect": "Allow",
"Action": ["kms:Decrypt", "kms:DescribeKey"],
"Resource": "arn:aws:kms:REGION:ACCOUNT:key/KEY-ID"
}
]
}Troubleshooting
”AccessDeniedException” during encryption
Cause: Your IAM user/role lacks kms:Encrypt permission.
Solution: Add the encrypt policy above to your IAM identity or ensure you’re in the KMS key policy.
”AccessDeniedException” during terraform apply
Cause: Pipeline role lacks kms:Decrypt permission.
Solution:
- Verify the role ARN in your KMS key policy
- Check the IAM role has the decrypt policy attached
- Confirm you’re using the correct AWS region
”InvalidCiphertextException”
Cause: Encrypted file corrupted or encrypted with a different key.
Solution:
- Re-encrypt from the original plaintext
- Verify you’re using the same KMS key alias
- Check the file wasn’t modified after encryption
Terraform shows secrets in plan output
Cause: Variables not marked as sensitive = true.
Solution: Ensure all secret variables have sensitive = true in their definition:
variable "license_key" {
type = string
sensitive = true # This prevents display in logs
}Next Steps
- Terraform Deployment - Complete deployment guide
- Scaling - Configure for your workload
- Troubleshooting - Common issues