Documentation

Quick Start

git clone https://github.com/durrello/cloudsentry.git
cd cloudsentry/terraform
cp terraform.tfvars.example terraform.tfvars
# Edit: set notification_emails and account_name
terraform init
terraform apply
# Check email inbox and click SES verification link

How It Works

CloudSentry runs as a Lambda function triggered weekly by EventBridge (Sunday 7am UTC). It scans your AWS account across all active regions, checks 10+ services for security misconfigurations, and generates a report with fix commands.

Flow

EventBridge (weekly cron) + API Gateway (on-demand)
  -> Lambda (Python 3.12, 5min timeout)
      |-- Scans all active regions
      |-- Checks: IAM, VPC, SGs, EC2, Lambda, S3, RDS, DynamoDB,
      |   KMS, CloudTrail, GuardDuty, Route 53, ACM, ELBs
      |-- Calculates security score (0-100)
      |-- Stores history in DynamoDB
      |-- Sends email via SES
      |-- Uploads HTML dashboard to S3 + CloudFront

On-Demand Scan

Trigger a scan anytime without waiting for the weekly schedule:

# Full scan
aws lambda invoke --function-name cloudsentry-scanner \
  --payload '{}' /dev/stdout --cli-read-timeout 300

# Via API Gateway
curl https://YOUR_API_ID.execute-api.REGION.amazonaws.com/scan

# Specific modules only
curl https://YOUR_API_ID.execute-api.REGION.amazonaws.com/scan?modules=iam,network,cost

Configuration

All configuration lives in terraform.tfvars:

# Notification emails (SES verified)
notification_emails = ["you@example.com"]

# Account display name
account_name = "My Account"

# Tag policy (resources without these tags are flagged)
required_tags = ["Environment", "Project", "Owner", "CostCenter", "ManagedBy"]
environment_values = ["production", "staging", "development", "sandbox"]

# Approved regions (resources outside these are flagged)
approved_regions = ["us-east-1", "eu-west-1"]

# Cost thresholds
cost_alert_threshold = 50
excluded_services_from_cost_alert = ["AmazonBedrock", "AmazonSageMaker"]

# Budget caps for known expensive services
budget_caps = [
  { service = "AmazonBedrock", max_monthly = 200, reason = "AI development" },
]

Multi-Account Setup

  1. Deploy the audit role in each target account:
    cd terraform/cross-account-role
    terraform apply -var="hub_account_id=YOUR_HUB_ACCOUNT_ID"
  2. Add accounts to your terraform.tfvars:
    accounts = [
      {
        name       = "Production"
        account_id = "111111111111"
        role_arn   = "arn:aws:iam::111111111111:role/CloudSentryAuditRole"
      },
    ]
  3. Apply: terraform apply

Custom Domain Setup

  1. Deploy CloudSentry first (creates CloudFront)
  2. Request ACM certificate:
    aws acm request-certificate \
      --domain-name cloudsentry.yourdomain.com \
      --validation-method DNS --region us-east-1
  3. Get validation CNAME:
    aws acm describe-certificate --certificate-arn ARN \
      --region us-east-1 \
      --query 'Certificate.DomainValidationOptions[0].ResourceRecord'
  4. Add validation CNAME to DNS (Proxy: OFF)
  5. Wait for cert to validate
  6. Update terraform.tfvars:
    dashboard_domain = "cloudsentry.yourdomain.com"
    dashboard_acm_cert_arn = "arn:aws:acm:us-east-1:..."
  7. Apply: terraform apply
  8. Add CNAME: cloudsentry -> CloudFront domain (Proxy: OFF)

Troubleshooting

Lambda times out

If scanning many regions/accounts, increase timeout:

# In terraform.tfvars:
lambda_timeout = 600  # 10 minutes (max is 900)

Cost Explorer shows $0

Cost Explorer data has a 24-48 hour delay. If you just created the account or it's early in the month, data may not be available yet. The billing dashboard updates faster than the API.

Email not received

Emails are sent via SES. Each recipient must verify their email:

aws ses verify-email-identity --email-address you@example.com --region us-east-1
# Click the verification link in your inbox

SES in sandbox mode only sends to verified addresses. If SES fails, CloudSentry falls back to SNS (plain text with dashboard link).

Dashboard shows old data

CloudFront caches pages. Invalidate after a scan:

aws cloudfront create-invalidation --distribution-id YOUR_DIST_ID --paths "/*"

Permission errors in Lambda logs

The Lambda role needs read-only access to all services it scans. Check CloudWatch logs:

aws logs get-log-events --log-group-name /aws/lambda/cloudsentry-scanner \
  --log-stream-name LATEST_STREAM --region us-east-1

If a specific service is denied, add the permission to the Lambda role in terraform/lambda.tf.

Tag enforcement blocking legitimate deploys

The RequireTagsOnCreate IAM policy blocks resource creation without tags. If Terraform or CI/CD is failing, ensure your provider includes required tags:

# Terraform example:
resource "aws_instance" "example" {
  # ...
  tags = {
    Environment = "production"
    Project     = "myapp"
    Owner       = "durrell"
  }
}

DynamoDB "Float types not supported"

DynamoDB doesn't accept Python floats. If you see this error, cost values need to be converted to Decimal before storing. This is handled in utils/dynamo.py.

Scan finds false positives

Some findings are "by design" (e.g., production instance needs public IP). These can't be suppressed yet. Future: add an allowlist in config to exclude specific resources.

Destroy Everything

cd terraform
terraform destroy
# This removes ALL CloudSentry resources. No leftovers.

Cost

ServiceMonthly
Lambda$0.00 (always free)
EventBridge$0.00 (always free)
DynamoDB$0.00 (always free)
SNS$0.00 (always free)
SES$0.00 (free from Lambda)
CloudWatch Logs$0.00 (always free)
S3~$0.01
CloudFront~$0.00
Cost Explorer API~$0.20
Total~$0.21/month