Automated vulnerability scanning for AWS Lambda functions using Qualys QScanner.
- AWS CLI configured with appropriate permissions
- Qualys subscription with TotalCloud
- Qualys Access Token
- QScanner binary (
scanner-lambda/qscanner.gz)
Deploy scanner to one AWS account:
export QUALYS_ACCESS_TOKEN="your-token"
make deploy QUALYS_POD=US2 AWS_REGION=us-east-1Disable Lambda tagging:
make deploy QUALYS_POD=US2 TAG=falseDeploy to multiple regions in same account:
make deploy-multi-region QUALYS_POD=US2Deploy standalone scanner to each account via AWS Organizations:
export QUALYS_ACCESS_TOKEN="your-token"
make deploy-stackset QUALYS_POD=US2 ORG_UNIT_IDS="ou-xxxx-xxxxxxxx"Deploy central scanner in security account, forward events from member accounts:
- Deploy hub in security account:
export QUALYS_ACCESS_TOKEN="your-token"
make deploy-hub QUALYS_POD=US2- Deploy spokes to member accounts:
make deploy-spoke-stackset ORG_UNIT_IDS="ou-xxxx-xxxxxxxx"Lambda Create/Update Event
│
▼
CloudTrail → EventBridge → Scanner Lambda
│
├── QScanner (Zip packages)
│ └── qscanner lambda <arn>
│
└── QScanner (Container images)
└── qscanner image <uri>
│
▼
Results
├── S3 (scan artifacts)
├── SNS (notifications)
├── DynamoDB (cache)
└── Lambda tags
| Resource | Description |
|---|---|
| Scanner Lambda | Executes QScanner against Lambda functions |
| Bulk Scan Lambda | Scans all existing functions on-demand |
| Lambda Layer | QScanner binary |
| DynamoDB Table | Scan cache (prevents duplicate scans) |
| S3 Bucket | Scan result artifacts |
| SNS Topic | Scan notifications |
| EventBridge Rules | Triggers on Lambda create/update |
| KMS Key | Encryption at rest |
| Secrets Manager | Qualys credentials |
| Tag | Description |
|---|---|
QualysScanTimestamp |
ISO timestamp of scan |
QualysScanStatus |
success, partial, or failed |
Scan all existing Lambda functions:
aws lambda invoke \
--function-name qualys-lambda-scanner-bulk-scan \
--payload '{}' \
output.jsonOptions:
{
"regions": ["us-east-1", "us-west-2"],
"dry_run": true,
"exclude_patterns": ["test-", "dev-"]
}Update function code only:
make update-functionView logs:
aws logs tail /aws/lambda/qualys-lambda-scanner-scanner --since 1hForce re-scan (clear cache):
aws dynamodb delete-item \
--table-name qualys-lambda-scanner-scan-cache \
--key '{"function_arn":{"S":"arn:aws:lambda:us-east-1:123456789012:function:my-function"}}'Preview resources to be deleted:
make clean-dry-runFull cleanup (includes pre-stack resources like secrets and layers):
make clean-all # Single account
make clean-all-hub ORG_UNIT_IDS=ou-xxx # Hub-spoke
make clean-all-stackset ORG_UNIT_IDS=ou-xxx # StackSetDelete individual resources:
make delete # CloudFormation stack only
make delete-secret # Secrets Manager secret
make delete-layers # Lambda layers
make delete-buckets # S3 buckets
make delete-log-groups # CloudWatch logsUS1, US2, US3, US4, GOV1, EU1, EU2, EU3, IN1, CA1, AE1, UK1, AU1, KSA1
qualys-lambda/
├── scanner-lambda/
│ ├── lambda_function.py # Scanner
│ ├── bulk_scan.py # Bulk scanner
│ └── qscanner.gz # QScanner binary
├── cloudformation/
│ ├── single-account-native.yaml
│ ├── stackset.yaml
│ ├── centralized-hub.yaml
│ └── centralized-spoke.yaml
├── Makefile
└── README.md