Skip to main content

IP Whitelist Manager

Lambda function for managing IP whitelist stored in DynamoDB with automatic security group synchronization.

Why This Exists

Accessing protected infrastructure (Temporal Web UI, gRPC endpoints) requires IP whitelisting. Without automation:

  • Engineers manually edit security groups (error-prone)
  • IPs are added but never removed (security risk)
  • No audit trail of who added what
  • No notifications when access expires

The IP whitelist manager solves this with:

  • Self-service: Add IPs via Slack with TTL
  • Auto-expiration: DynamoDB TTL removes stale IPs
  • Real-time sync: DynamoDB Streams update security groups instantly
  • Audit trail: Full user attribution and logging

Architecture

┌─────────────┐      ┌──────────────┐      ┌─────────────┐
│ Slack Bot │─────▶│ Lambda │─────▶│ DynamoDB │
│ /infra │ │ Handler │ │ Table │
│ whitelist │ └──────────────┘ └──────┬──────┘
└─────────────┘ │ │
│ DynamoDB Stream
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ Security │◀─────│ Lambda │
│ Groups │ │ (Stream) │
│ ALB + NLB │ └──────┬───────┘
└──────────────┘ │

┌──────────────┐
│ Slack │
│ Notification │
└──────────────┘

Actions

Add IP

/infra whitelist add 1.2.3.4 --ttl=7d --description='Home office'

Event:

{
"action": "add",
"ip_address": "1.2.3.4/32",
"environment": "dev",
"user_info": {
"user_id": "U01234567",
"username": "john.doe",
"display_name": "John Doe"
},
"ttl_minutes": 10080,
"description": "Home office"
}

Remove IP

/infra whitelist remove 1.2.3.4

List IPs

/infra whitelist list dev

Refresh Security Groups

/infra whitelist refresh dev

Manual sync (usually not needed due to DynamoDB Streams).

TTL Format

FormatRangeExampleDescription
Minutes1m - 1440m60m1 minute to 24 hours
Days1d - 365d30d1 day to 1 year

Default: 30 days

Examples:

/infra whitelist add 1.2.3.4 --ttl=5m      # Quick test (5 minutes)
/infra whitelist add 1.2.3.4 --ttl=60m # 1 hour
/infra whitelist add 1.2.3.4 --ttl=7d # 1 week
/infra whitelist add 1.2.3.4 # Default 30 days

Managed Ports

PortProtocolPurposeSecurity Group Type
80TCPHTTP trafficALB
443TCPHTTPS trafficALB
7233TCPgRPC (Temporal workers)NLB

Security Group Discovery

Tag security groups with WhitelistManaged=true to include them:

aws ec2 create-tags \
--resources sg-xxxxxxxxx \
--tags Key=WhitelistManaged,Value=true

The Lambda automatically discovers and manages any security group with this tag.

Explicit Mode

Specify security group IDs via MANAGED_SECURITY_GROUP_IDS environment variable.

DynamoDB Schema

Table: ip-whitelist-{environment}

AttributeTypeDescription
ip_address (PK)StringIP in CIDR notation
environment (SK)StringEnvironment name
added_atStringISO 8601 timestamp
expires_atStringISO 8601 timestamp
ttlNumberUnix timestamp for auto-deletion
user_idStringSlack user ID
usernameStringSlack username
descriptionStringOptional description

Slack Notifications

EventEmojiDescription
IP addedNew IP added to whitelist
IP removed🗑IP removed manually
IP expiredIP expired via TTL

Example notification:

:hourglass: IP Whitelist Update

IP `1.2.3.4/32` expired from `dev` whitelist.

Added by: @john.doe
Description: Home office
Expired at: 2025-01-17 12:00:00 UTC

Environment Variables

VariableRequiredDescription
DYNAMODB_TABLE_NAMEYesDynamoDB table name
ENVIRONMENTYesCurrent environment
ENABLE_SG_DISCOVERYNoEnable tag-based discovery (default: false)
MANAGED_SECURITY_GROUP_IDSNo*JSON array of security group IDs
SG_DISCOVERY_TAG_KEYNoTag key for discovery (default: WhitelistManaged)
SG_DISCOVERY_TAG_VALUENoTag value for discovery (default: true)
SLACK_WEBHOOK_SECRET_ARNNoSecrets Manager ARN for Slack webhook

*Required if ENABLE_SG_DISCOVERY is false

Deployment

module "ip_whitelist" {
source = "../../modules/ip-whitelist"

environment = "dev"
vpc_id = module.vpc.vpc_id
private_subnet_ids = module.vpc.private_subnet_ids

enable_sg_discovery = true
sg_discovery_tag_key = "WhitelistManaged"
sg_discovery_tag_value = "true"

slack_webhook_secret_arn = aws_secretsmanager_secret.slack_webhook.arn
}

Then tag your security groups:

aws ec2 create-tags \
--resources sg-xxx sg-yyy \
--tags Key=WhitelistManaged,Value=true

Development Workflow

Local Testing

cd docustack-mono/services/lambdas/ip-whitelist-manager

# Install dependencies
pip install boto3

# Set environment variables
export DYNAMODB_TABLE_NAME=ip-whitelist-dev
export ENVIRONMENT=dev
export ENABLE_SG_DISCOVERY=true
export LOG_LEVEL=DEBUG

# Test locally
python -c "
from handler import lambda_handler
result = lambda_handler({
'action': 'list',
'environment': 'dev'
}, None)
print(result)
"

Unit Tests

pytest tests/ -v

Integration Tests

# Add IP with 1-hour TTL
aws lambda invoke \
--function-name ip-whitelist-manager-dev \
--payload '{"action":"add","ip_address":"1.2.3.4/32","environment":"dev","user_info":{"user_id":"U123"},"ttl_minutes":60}' \
response.json

# List IPs
aws lambda invoke \
--function-name ip-whitelist-manager-dev \
--payload '{"action":"list","environment":"dev"}' \
response.json

Monitoring

CloudWatch Logs

  • Log Group: /aws/lambda/ip-whitelist-manager-{environment}
  • Includes: Action, IP address, user, timestamps, errors
MetricThresholdDescription
Lambda errors> 5 in 5 minFunction failures
Lambda duration> 10 secSlow execution
DynamoDB throttles> 0Capacity issues
Stream IteratorAge> 60000msProcessing lag

Troubleshooting

IP not appearing in security group

  1. Check DynamoDB table for the IP
  2. Verify IP is not expired
  3. Check DynamoDB Stream Lambda logs
  4. Run refresh action manually
  5. Discovery mode: Verify security group has WhitelistManaged=true tag

Security group not being discovered

  1. Verify ENABLE_SG_DISCOVERY=true
  2. Check security group has correct tag
  3. Check CloudWatch logs for discovery output

DynamoDB TTL not deleting items

  • TTL deletion can take up to 48 hours
  • Items are filtered out by list_ips() even if not yet deleted
  • Use remove action for immediate removal

Slack notifications not sending

  1. Verify SLACK_WEBHOOK_SECRET_ARN is set
  2. Check Secrets Manager secret exists
  3. Verify Lambda has secretsmanager:GetSecretValue permission

Code Location

docustack-mono/services/lambdas/ip-whitelist-manager/
├── handler.py # Main Lambda handler
├── stream_handler.py # DynamoDB Stream processor
└── README.md