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
| Format | Range | Example | Description |
|---|---|---|---|
| Minutes | 1m - 1440m | 60m | 1 minute to 24 hours |
| Days | 1d - 365d | 30d | 1 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
| Port | Protocol | Purpose | Security Group Type |
|---|---|---|---|
| 80 | TCP | HTTP traffic | ALB |
| 443 | TCP | HTTPS traffic | ALB |
| 7233 | TCP | gRPC (Temporal workers) | NLB |
Security Group Discovery
Discovery Mode (Recommended)
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}
| Attribute | Type | Description |
|---|---|---|
ip_address (PK) | String | IP in CIDR notation |
environment (SK) | String | Environment name |
added_at | String | ISO 8601 timestamp |
expires_at | String | ISO 8601 timestamp |
ttl | Number | Unix timestamp for auto-deletion |
user_id | String | Slack user ID |
username | String | Slack username |
description | String | Optional description |
Slack Notifications
| Event | Emoji | Description |
|---|---|---|
| IP added | ✅ | New IP added to whitelist |
| IP removed | 🗑 | IP removed manually |
| IP expired | ⌛ | IP 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
| Variable | Required | Description |
|---|---|---|
DYNAMODB_TABLE_NAME | Yes | DynamoDB table name |
ENVIRONMENT | Yes | Current environment |
ENABLE_SG_DISCOVERY | No | Enable tag-based discovery (default: false) |
MANAGED_SECURITY_GROUP_IDS | No* | JSON array of security group IDs |
SG_DISCOVERY_TAG_KEY | No | Tag key for discovery (default: WhitelistManaged) |
SG_DISCOVERY_TAG_VALUE | No | Tag value for discovery (default: true) |
SLACK_WEBHOOK_SECRET_ARN | No | Secrets Manager ARN for Slack webhook |
*Required if ENABLE_SG_DISCOVERY is false
Deployment
Discovery Mode (Recommended)
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
Recommended Alarms
| Metric | Threshold | Description |
|---|---|---|
| Lambda errors | > 5 in 5 min | Function failures |
| Lambda duration | > 10 sec | Slow execution |
| DynamoDB throttles | > 0 | Capacity issues |
| Stream IteratorAge | > 60000ms | Processing lag |
Troubleshooting
IP not appearing in security group
- Check DynamoDB table for the IP
- Verify IP is not expired
- Check DynamoDB Stream Lambda logs
- Run refresh action manually
- Discovery mode: Verify security group has
WhitelistManaged=truetag
Security group not being discovered
- Verify
ENABLE_SG_DISCOVERY=true - Check security group has correct tag
- 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
removeaction for immediate removal
Slack notifications not sending
- Verify
SLACK_WEBHOOK_SECRET_ARNis set - Check Secrets Manager secret exists
- Verify Lambda has
secretsmanager:GetSecretValuepermission
Code Location
docustack-mono/services/lambdas/ip-whitelist-manager/
├── handler.py # Main Lambda handler
├── stream_handler.py # DynamoDB Stream processor
└── README.md