Building a Serverless Security Scanning Pipeline with Terraform and ECS Fargate
Hook
Most security scanning infrastructure runs 24/7 but actually scans for maybe 30 minutes a day. You're paying for 23.5 hours of idle compute that could cost you nothing.
Context
Security scanning has traditionally required dedicated infrastructure: virtual machines running Nmap, vulnerability scanners polling web applications, or Jenkins agents executing security pipelines. This means paying for always-on compute, managing OS patches, and babysitting cron jobs that may or may not execute successfully. For teams running periodic scans—daily reconnaissance, weekly vulnerability assessments, or monthly compliance checks—this is wildly inefficient. You're maintaining infrastructure that's idle 95% of the time.
The opendevsecops/terraform-aws-scanner module tackles this waste by treating security scans as ephemeral workloads. Instead of persistent VMs, it orchestrates containerized security tools through ECS Fargate, spinning up compute only when CloudWatch Events trigger a scan. The infrastructure exists for minutes, not months. This serverless approach mirrors how modern applications handle bursty workloads, applying the same cost optimization principles to security operations. The module wraps popular open-source scanners like Nmap, WPScan, GitLeaks, and Amass in a Terraform-deployable package, making scheduled security scanning as simple as defining a target and a cron expression.
Technical Insight
The architecture revolves around three AWS primitives working in concert: CloudWatch Events for scheduling, ECS Fargate for execution, and CloudWatch Logs for output. The base module creates a VPC and ECS cluster, then individual scanner modules define task definitions pointing to pre-built Docker images. Here's how you'd deploy a weekly Nmap scan of your infrastructure:
module "scanner" {
source = "opendevsecops/scanner/aws"
version = "1.0.0"
}
module "nmap_scan" {
source = "opendevsecops/scanner-nmap/aws"
version = "1.0.0"
cluster_id = module.scanner.cluster_id
vpc_id = module.scanner.vpc_id
subnets = module.scanner.subnets
schedule_expression = "cron(0 2 ? * SUN *)"
command = ["nmap", "-sV", "-p-", "192.168.1.0/24"]
log_group_name = "/aws/scanner/nmap"
}
When Sunday at 2 AM UTC rolls around, CloudWatch Events triggers the ECS task. Fargate provisions a container from the opendevsecops/scanner-nmap image, executes the Nmap command against your target network, streams output to CloudWatch Logs, and terminates. You're billed only for the container's runtime—typically under 10 minutes for most scans.
The modular design shines when combining multiple scanners. Want to add weekly WPScan runs against WordPress sites and daily GitLeaks checks for exposed secrets? Just add more scanner modules with different schedules:
module "wpscan" {
source = "opendevsecops/scanner-wpscan/aws"
version = "1.0.0"
cluster_id = module.scanner.cluster_id
vpc_id = module.scanner.vpc_id
subnets = module.scanner.subnets
schedule_expression = "cron(0 3 ? * SUN *)"
command = ["wpscan", "--url", "https://example.com", "--enumerate", "vp"]
log_group_name = "/aws/scanner/wpscan"
}
module "gitleaks" {
source = "opendevsecops/scanner-gitleaks/aws"
version = "1.0.0"
cluster_id = module.scanner.cluster_id
vpc_id = module.scanner.vpc_id
subnets = module.scanner.subnets
schedule_expression = "rate(1 day)"
command = ["gitleaks", "detect", "--source", "https://github.com/yourorg/yourrepo"]
log_group_name = "/aws/scanner/gitleaks"
}
Each scanner runs independently with its own schedule, logs, and resource allocation. The ECS task definitions use the awslogs driver by default, meaning stdout and stderr automatically flow to CloudWatch Logs without additional configuration. This is crucial because many security tools write findings to stdout—you get structured logging without custom parsing.
Under the hood, the module leverages ECS task networking modes to give containers internet access through NAT gateways, essential for scanners that need to reach external targets. The VPC module creates public and private subnets, with Fargate tasks launching in private subnets for security. If you're scanning internal resources, you can modify the security groups to allow outbound traffic to your private network ranges:
resource "aws_security_group_rule" "scanner_internal" {
type = "egress"
from_port = 0
to_port = 65535
protocol = "tcp"
cidr_blocks = ["10.0.0.0/8"]
security_group_id = module.scanner.security_group_id
}
The real elegance is in the cost model. A typical Fargate task with 0.5 vCPU and 1GB memory costs about $0.03 per hour. If your Nmap scan runs for 15 minutes weekly, that's $0.0075 per week or $0.39 annually. Compare that to a t3.small instance running 24/7 at roughly $182 per year. For teams running a dozen different scanners, the savings compound quickly—potentially thousands of dollars annually that would otherwise go to idle infrastructure.
The module also supports passing environment variables for sensitive data like API keys, which many scanners require. You can integrate with AWS Secrets Manager or Parameter Store:
module "authenticated_scan" {
source = "opendevsecops/scanner-nikto/aws"
version = "1.0.0"
cluster_id = module.scanner.cluster_id
vpc_id = module.scanner.vpc_id
subnets = module.scanner.subnets
schedule_expression = "cron(0 4 * * ? *)"
command = ["nikto", "-h", "https://example.com"]
environment = [
{
name = "API_KEY"
value = data.aws_ssm_parameter.scanner_api_key.value
}
]
log_group_name = "/aws/scanner/nikto"
}
Gotcha
The biggest limitation isn't technical architecture—it's what happens after scans complete. Results land in CloudWatch Logs as raw text. There's no built-in parsing, no vulnerability database integration, no alerting when critical findings appear. You're responsible for extracting insights from logs, which means building custom CloudWatch Logs Insights queries or shipping logs to a SIEM. For one-off scans where you manually review results, this is fine. For production security monitoring, it's a gap. You'll need Lambda functions parsing scan output, SNS topics for alerts, or a full vulnerability management platform consuming the logs. The module gives you data collection but leaves analysis as an exercise for the operator.
Maintenance concerns also loom. The repository shows limited recent activity, uses older Terraform syntax (notice the string interpolation patterns in the source code), and may require updates for current AWS provider versions. The pre-built Docker images are convenient but opaque—you're trusting images without clear update schedules or security patching processes. For compliance-heavy environments, building your own scanner images from verified base images and maintaining them internally would be prudent. Additionally, scanners requiring state persistence between runs (like progressive web crawlers or authenticated session-based scans) don't fit this ephemeral execution model. Each Fargate task starts fresh with no memory of previous executions.
Verdict
Use if: you need cost-effective scheduled security scanning in AWS, already have log aggregation and alerting infrastructure, and want to avoid maintaining dedicated scanning servers. This is perfect for development teams running weekly reconnaissance, security teams doing periodic external vulnerability assessments, or compliance teams scheduling monthly configuration audits. The Terraform approach makes it easy to version-control your scanning strategy and integrate into infrastructure-as-code workflows. Skip if: you need real-time scanning triggered by code commits or deployments, require sophisticated vulnerability management with deduplication and remediation tracking, or need production-grade alerting without custom development. In those cases, dedicated platforms like AWS Security Hub for cloud-native scanning, DefectDojo for vulnerability management, or commercial solutions like Qualys provide better out-of-box functionality. Also skip if you're uncomfortable with potentially outdated dependencies—vet the Docker images and Terraform code carefully before production use.