Secure Access Modules
These modules provide zero-trust access to private resources like RDS databases without exposing them to the internet.
Overview
DocuStack uses ephemeral bastion hosts managed via Slack commands:
Developer → Slack Bot → Bastion Orchestrator → EC2 Bastion → RDS
↓
Auto-terminate (3 hours)
Key Principles:
- Zero standing access - Bastions only exist when needed
- No SSH keys - SSM Session Manager provides secure access
- No public IPs - Everything runs in private subnets
- Automatic cleanup - Bastions auto-terminate after 3 hours
- Full audit trail - All sessions logged to CloudWatch
Bastion
Terraform module for deploying on-demand bastion hosts for secure database access via AWS Systems Manager Session Manager.
Why This Module?
Traditional bastion hosts have security issues:
- Long-lived instances increase attack surface
- SSH keys can be compromised
- Public IPs expose the bastion to the internet
- No automatic cleanup
This module solves these problems with ephemeral, SSM-based bastions.
Architecture
┌─────────────────────────────────────────────────────────────┐
│ Bastion Architecture │
├─────────────────────────────────────────────────────────────┤
│ Developer │
│ │ │
│ │ aws ssm start-session │
│ v │
│ ┌──────────────────┐ │
│ │ SSM Session │ │
│ │ Manager │ │
│ └────────┬─────────┘ │
│ │ │
│ │ Secure tunnel (no SSH) │
│ v │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ Bastion EC2 │────────►│ RDS PostgreSQL │ │
│ │ (Private subnet)│ 5432 │ (Private subnet)│ │
│ │ │ │ │ │
│ │ - No public IP │ │ - No public IP │ │
│ │ - IMDSv2 only │ │ - Encrypted │ │
│ │ - Encrypted EBS │ │ - HIPAA ready │ │
│ └──────────────────┘ └──────────────────┘ │
│ │ │
│ │ Session logs │
│ v │
│ ┌──────────────────┐ │
│ │ CloudWatch Logs │ │
│ │ (Encrypted) │ │
│ └──────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Security Features
| Feature | Implementation |
|---|---|
| No public IP | Bastion runs in private subnet |
| No SSH | SSM Session Manager only |
| No open ports | Security group allows only outbound to RDS |
| IMDSv2 required | Prevents SSRF attacks |
| Encrypted EBS | Root volume encrypted at rest |
| Session logging | All sessions logged to CloudWatch |
| Auto-termination | Expires after 3 hours |
Usage
module "bastion" {
source = "git::git@github.com:docustackapp/docustack-infrastructure-modules.git//modules/bastion?ref=v1.0.0"
project_name = "docustack"
environment = "dev"
deploy_bastion = 1 # 0 = no bastion, 1 = deploy
instance_type = "t4g.nano"
ami_id = "ami-0c55b159cbfafe1f0" # Amazon Linux 2023 ARM64
vpc_id = module.vpc.vpc_id
subnet_id = module.vpc.private_subnets[0]
rds_security_group_id = module.rds.security_group_id
rds_endpoint = module.rds.endpoint
# Audit trail
requested_by_user_id = "U12345678"
requested_by_username = "john.doe"
created_at = "2024-01-15T10:30:00Z"
expires_at = "2024-01-15T13:30:00Z"
auto_terminate = true
kms_key_arn = aws_kms_key.cloudwatch.arn
}
Connecting to Bastion
# Get instance ID
INSTANCE_ID=$(terragrunt output -raw instance_id)
# Start interactive session
aws ssm start-session --target $INSTANCE_ID
# Port forward to RDS
aws ssm start-session \
--target $INSTANCE_ID \
--document-name AWS-StartPortForwardingSessionToRemoteHost \
--parameters '{"host":["your-rds-endpoint.rds.amazonaws.com"],"portNumber":["5432"],"localPortNumber":["5432"]}'
# In another terminal, connect to database
psql -h localhost -p 5432 -U postgres -d temporal
Cost
| Instance Type | Cost/Hour | Cost/3 Hours |
|---|---|---|
| t4g.nano (ARM) | $0.0042 | $0.0126 |
| t3.nano (x86) | $0.0052 | $0.0156 |
Monthly estimate (20 sessions × 3 hours): ~$0.25/month
Bastion Orchestrator
Lambda-based orchestration system for on-demand bastion instance lifecycle management with automatic termination.
Why This Module?
The bastion module creates individual bastions, but you need orchestration to:
- Create bastions on-demand via Slack or API
- Track who requested each bastion
- Auto-terminate expired bastions
- Send notifications about lifecycle events
Architecture
┌─────────────────────────────────────────────────────────────┐
│ Bastion Orchestrator Architecture │
├─────────────────────────────────────────────────────────────┤
│ Slack Bot / API │
│ │ │
│ │ /bastion create dev │
│ v │
│ ┌──────────────────────┐ │
│ │ Orchestrator Lambda │ │
│ │ │ │
│ │ Actions: │ │
│ │ - create │──────► EC2 Instance (Bastion) │
│ │ - destroy │ - t4g.nano │
│ │ - status │ - Private subnet │
│ │ - extend │ - ExpiresAt tag │
│ └──────────────────────┘ │
│ │
│ ┌──────────────────────┐ │
│ │ EventBridge Schedule │ │
│ │ (every 15 minutes) │ │
│ └──────────┬───────────┘ │
│ │ trigger │
│ v │
│ ┌──────────────────────┐ │
│ │ Auto-Terminate │ │
│ │ Lambda │ │
│ │ │ │
│ │ - Scan for expired │ │
│ │ - Terminate instances│ │
│ │ - Send notifications │ │
│ └──────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Usage
module "bastion_orchestrator" {
source = "git::git@github.com:docustackapp/docustack-infrastructure-modules.git//modules/bastion-orchestrator?ref=v1.0.0"
name = "docustack-dev"
environment = "dev"
vpc_id = module.vpc.vpc_id
subnet_id = module.vpc.private_subnet_ids[0]
ami_id = "ami-0c55b159cbfafe1f0"
instance_type = "t4g.nano"
bastion_lifetime_hours = 3
lambda_source_dir = "${path.root}/../../../docustack-mono/services/lambdas/bastion-orchestrator"
cloudwatch_kms_key_arn = aws_kms_key.cloudwatch.arn
slack_notification_channel = "C01234567"
}
Slack Commands
# Create bastion (auto-terminates after 3 hours)
/bastion create dev
# Check bastion status
/bastion status dev
# Extend bastion lifetime by 3 hours
/bastion extend dev
# Terminate bastion early
/bastion destroy dev
Auto-Termination Flow
- Bastion Created - Tagged with
ExpiresAttimestamp - EventBridge Schedule - Triggers auto-terminate Lambda every 15 minutes
- Lambda Scans - Finds bastions with
ExpiresAtin the past - Termination - Terminates expired instances
- Notification - Sends Slack notification to requester
Lambda Code Location
Source: docustack-mono/services/lambdas/bastion-orchestrator/
Cost
| Component | Monthly Cost |
|---|---|
| Orchestrator Lambda | ~$0.01 |
| Auto-Terminate Lambda | ~$0.05 |
| Bastion instances (20 × 3 hrs) | ~$0.25 |
| Total | ~$0.31/month |
Prerequisites
Before using bastion modules:
- VPC with private subnets - Bastion runs in private subnet
- SSM VPC endpoints - Required for Session Manager:
ssmssmmessagesec2messages
- KMS key - For CloudWatch Logs encryption
- Session Manager plugin - Install on developer machines:
# macOS
brew install --cask session-manager-plugin
# Linux
curl "https://s3.amazonaws.com/session-manager-downloads/plugin/latest/ubuntu_64bit/session-manager-plugin.deb" -o "session-manager-plugin.deb"
sudo dpkg -i session-manager-plugin.deb
# Verify
session-manager-plugin --version
Troubleshooting
Cannot Connect via SSM
An error occurred (TargetNotConnected) when calling the StartSession operation
Diagnosis:
# Check instance status
aws ec2 describe-instance-status --instance-ids $INSTANCE_ID
# Check SSM agent status
aws ssm describe-instance-information --filters "Key=InstanceIds,Values=$INSTANCE_ID"
Common Causes:
- SSM VPC endpoints not configured
- Instance in subnet without NAT/endpoints
- IAM role missing SSM permissions
- SSM agent not running
Cannot Connect to RDS
psql: could not connect to server: Connection refused
Diagnosis:
# Check security group rules
aws ec2 describe-security-groups \
--group-ids $RDS_SECURITY_GROUP_ID \
--query 'SecurityGroups[0].IpPermissions'
Solution: The bastion module automatically adds ingress rule to RDS security group. Verify it exists.
Bastion Not Auto-Terminating
Diagnosis:
# Check auto-terminate Lambda logs
aws logs tail /aws/lambda/docustack-dev-bastion-auto-terminate --since 1h
# Check EventBridge schedule
aws scheduler get-schedule --name bastion-auto-terminate --group-name docustack-dev
# Check bastion tags
aws ec2 describe-instances --instance-ids $INSTANCE_ID --query 'Reservations[0].Instances[0].Tags'
Common Causes:
- EventBridge schedule disabled
- Lambda missing EC2 permissions
ExpiresAttag not set correctly
HIPAA Compliance
| Control | Implementation |
|---|---|
| §164.312(a)(2)(i) Unique user identification | Tracked via tags (requested_by) |
| §164.312(b) Audit controls | CloudWatch session logs |
| §164.312(d) Authentication | SSM IAM authentication |
| §164.312(e)(1) Transmission security | Encrypted SSM tunnel |
| §164.312(a)(2)(iv) Encryption at rest | Encrypted EBS volumes |