Skip to main content

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

FeatureImplementation
No public IPBastion runs in private subnet
No SSHSSM Session Manager only
No open portsSecurity group allows only outbound to RDS
IMDSv2 requiredPrevents SSRF attacks
Encrypted EBSRoot volume encrypted at rest
Session loggingAll sessions logged to CloudWatch
Auto-terminationExpires 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 TypeCost/HourCost/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

  1. Bastion Created - Tagged with ExpiresAt timestamp
  2. EventBridge Schedule - Triggers auto-terminate Lambda every 15 minutes
  3. Lambda Scans - Finds bastions with ExpiresAt in the past
  4. Termination - Terminates expired instances
  5. Notification - Sends Slack notification to requester

Lambda Code Location

Source: docustack-mono/services/lambdas/bastion-orchestrator/

Cost

ComponentMonthly 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:

  1. VPC with private subnets - Bastion runs in private subnet
  2. SSM VPC endpoints - Required for Session Manager:
    • ssm
    • ssmmessages
    • ec2messages
  3. KMS key - For CloudWatch Logs encryption
  4. 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
  • ExpiresAt tag not set correctly

HIPAA Compliance

ControlImplementation
§164.312(a)(2)(i) Unique user identificationTracked via tags (requested_by)
§164.312(b) Audit controlsCloudWatch session logs
§164.312(d) AuthenticationSSM IAM authentication
§164.312(e)(1) Transmission securityEncrypted SSM tunnel
§164.312(a)(2)(iv) Encryption at restEncrypted EBS volumes