Skip to content

AWS Lambda

The dashboard runs as a Lambda container image. deploy/Dockerfile.lambda builds it on the AWS Lambda Python base image and sets the handler vpcatlas.dashboard.lambda_handler.handler: the FastAPI application behind Mangum. deploy/lambda/README.md has the short version of this page.

How it behaves in Lambda

Lambda freezes the container between invocations, so there is no background refresh loop. The dashboard runs in lazy mode, chosen automatically when AWS_LAMBDA_FUNCTION_NAME is set (override with VPCATLAS_REFRESH_MODE).

The handler accepts two kinds of events:

  • HTTP events from a Function URL, API Gateway or an Application Load Balancer are served by the dashboard.
  • A scan event: {"atlas_action": "scan"}, or an EventBridge scheduled event (source aws.events, or detail-type Scheduled Event). It runs a discovery and stores the snapshot in VPCATLAS_SNAPSHOT_URI. If that variable is not set the event is skipped (the result would be lost), and the invocation returns {"status": "skipped", ...}. On success it returns the snapshot id, the time, the number of pages and the number of collection issues.

Recommended setup: a scheduled scan plus an S3 snapshot. A schedule invokes the function with the scan event, the snapshot is stored in S3, and page views only read it, so nobody waits for a scan. Set VPCATLAS_SCAN_ON_REQUEST=false so a request never starts a scan.

Without S3, the first request after the snapshot expires scans inline (VPCATLAS_REFRESH_SECONDS is the snapshot's lifetime; 0 keeps it for the life of the container). A Function URL can wait for that; API Gateway cannot, because its integration timeout is far shorter than a scan.

A scan is a full read of the account, roughly a thousand list calls for 17 Regions. Keep it inside the function timeout (15 minutes at most) by listing the Regions you need in VPCATLAS_REGIONS. Responses are limited to 6 MB, so a very large .drawio or .zip export may not fit; use the CLI for those.

1. Build and push the image

Lambda pulls images from Amazon ECR, in the Region of the function. Build for the architecture of the function:

REGION=eu-west-1
ACCOUNT=123456789012
REPO=vpcatlas-lambda
REGISTRY="$ACCOUNT.dkr.ecr.$REGION.amazonaws.com"

aws ecr create-repository --repository-name "$REPO" --region "$REGION"
aws ecr get-login-password --region "$REGION" | docker login --username AWS --password-stdin "$REGISTRY"

docker build --platform linux/arm64 -f deploy/Dockerfile.lambda -t "$REGISTRY/$REPO:0.1.0" .
docker push "$REGISTRY/$REPO:0.1.0"

Tagged releases also publish vpcatlas/vpcatlas-lambda:<version>-amd64 and -arm64 on Docker Hub. Lambda cannot pull from Docker Hub, so copy the one you need into your ECR repository instead of building it.

2. Roles

The execution role of the function needs:

  • the basic Lambda logging policy (AWSLambdaBasicExecutionRole);
  • the auditor policy deploy/iam/auditor-policy.json (also printed by vpcatlas iam-policy), or ReadOnlyAccess; or sts:AssumeRole on the auditor roles of other accounts when you set VPCATLAS_ROLE_ARN (and VPCATLAS_EXTERNAL_ID when their trust policies have one; see iam.md);
  • when you use VPCATLAS_SNAPSHOT_URI: s3:GetObject, s3:PutObject and s3:DeleteObject on arn:aws:s3:::my-bucket/atlas/* (the history lives under that prefix and the oldest snapshots are deleted), plus s3:ListBucket on arn:aws:s3:::my-bucket with the condition s3:prefix = atlas/*. Without s3:ListBucket, S3 answers AccessDenied instead of NoSuchKey for an object that is not there yet, and the store reports it as a read error.

The function reads AWS with the credentials of its execution role: no access keys are configured. The keys written in the bucket are listed in the dashboard page.

3. Create the function

aws lambda create-function \
  --function-name vpcatlas \
  --package-type Image \
  --code ImageUri="$REGISTRY/$REPO:0.1.0" \
  --architectures arm64 \
  --role "arn:aws:iam::$ACCOUNT:role/vpcatlas-lambda" \
  --memory-size 2048 \
  --timeout 900 \
  --environment '{"Variables":{"VPCATLAS_REGIONS":"eu-west-1,eu-central-1","VPCATLAS_SNAPSHOT_URI":"s3://my-bucket/atlas","VPCATLAS_REFRESH_SECONDS":"1800","VPCATLAS_SCAN_ON_REQUEST":"false"}}' \
  --region "$REGION"

The architecture of the function must match the image (--platform linux/arm64 above and --architectures arm64). The image sets no port: Lambda does not use one. The settings are the same VPCATLAS_* variables as everywhere else (the dashboard page).

4. Schedule the scan

aws scheduler create-schedule \
  --name vpcatlas-scan \
  --schedule-expression "rate(30 minutes)" \
  --flexible-time-window Mode=OFF \
  --target '{"Arn":"arn:aws:lambda:eu-west-1:123456789012:function:vpcatlas","RoleArn":"arn:aws:iam::123456789012:role/vpcatlas-scheduler","Input":"{\"atlas_action\":\"scan\"}"}' \
  --region "$REGION"

The scheduler role needs lambda:InvokeFunction on the function. Invoke the same event once by hand to create the first snapshot:

aws lambda invoke --function-name vpcatlas --cli-binary-format raw-in-base64-out \
  --payload '{"atlas_action":"scan"}' out.json

Until a snapshot exists, the data routes answer 503. Keep the schedule interval at or below VPCATLAS_REFRESH_SECONDS so the page never shows a snapshot that the dashboard already considers stale.

5. Put something in front of it

The dashboard shows your network topology, so decide who may open it:

Option When
Function URL with AWS_IAM auth Automation that signs its requests (for example curl --aws-sigv4). Browsers cannot sign.
Function URL with NONE auth and VPCATLAS_AUTH_PASSWORD A small team over HTTPS. The URL is public and only HTTP Basic protects it: use a long password.
API Gateway HTTP API with a JWT authorizer (Cognito or any OIDC provider) Single sign-on. Set VPCATLAS_BASE_PATH when the API has a stage prefix.
CloudFront in front of either Custom domain, WAF, caching of /static/.

/healthz is not covered by the password, and neither are /static/ and /api/docs; see authentication. Binary downloads (.lucid, .zip) are returned base64-encoded; Function URLs and HTTP APIs decode them. For a REST API, add application/zip and application/vnd.jgraph.mxfile to its binary media types.

Alternative: AWS Lambda Web Adapter

To run the unmodified web image instead, add the Lambda Web Adapter extension to deploy/Dockerfile (COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:<version> /lambda-adapter /opt/extensions/lambda-adapter, pinning a released version) and set AWS_LWA_PORT=8080. The Mangum handler needs no extension and also supports the scheduled scan event, so it is the default.