Kaizen

SSM Bastion

A jump host with no SSH, reached only through AWS Systems Manager Session Manager.

GovCloudFIPSCMKView source
Module
bastion
Layer
Network
Interface
11 inputs, 4 outputs
Used by
2 Kaizen apps

Why it matters

Engineers sometimes need to reach a private database to debug or run a migration. This host gives them that access with no SSH keys and no open port 22: every session goes through AWS Systems Manager, which checks IAM permissions and records the session. It switches the operating system to its FIPS crypto policy at boot and requires a customer managed key for its disk.

Use it when

  • Operators need psql access to rds-postgres from a laptop.
  • You route Cloudflare WARP traffic into the VPC. Set cloudflared_token_ssm_parameter, and tls_passthrough_target for the load balancer.

Reach for something else when

  • You only need a shell in a running app container. Turn on ECS Exec for the incident instead. Use ecs-service.

What it creates

  • aws_iam_role (AmazonSSMManagedInstanceCore and CloudWatchAgentServerPolicy, partition aware ARNs)
  • aws_iam_role_policy (SSM parameter reads and kms:Decrypt, optional)
  • aws_iam_instance_profile
  • aws_security_group (egress only, plus 443 and 80 when TLS passthrough is on)
  • aws_instance (Amazon Linux 2023, private subnet, no public IP, IMDSv2 required, encrypted root volume)

Secure by default

  • No SSH key and no port 22 ingress rule.
  • No public IP (associate_public_ip_address = false).
  • IMDSv2 is required with a hop limit of 1.
  • The root volume is encrypted with the required kms_key_arn.
  • User data runs update-crypto-policies --set FIPS at first boot.

Commercial and GovCloud

main.tf
module "bastion" {
  source = "git::ssh://git@github.com/the-kaizen-labs/terraform-modules.git//bastion?ref=v1.7.0"

  name              = local.app_name
  vpc_id            = module.networking.vpc_id
  private_subnet_id = module.networking.private_subnet_ids[0]
  ami_id            = data.aws_ami.al2023.id
  kms_key_arn       = aws_kms_key.this.arn

  ssm_parameter_arns = [data.aws_ssm_parameter.db_password.arn]

  tags = local.tags
}
SettingCommercial defaultFederal settingNote
ami_idAmazon Linux 2023 AMIthe FIPS enabled Amazon Linux 2023 AMIThe user data also sets the FIPS crypto policy in both cases.
kms_key_arnrequired, no defaultrequired, customer managed KMS key ARN

How it connects

  • From networking: vpc_id, private_subnet_ids[0] (as private_subnet_id)
  • From alb: alb_dns_name (as tls_passthrough_target)
  • To rds-postgres: security_group_id (as allowed_security_group_ids)

Inputs

NameTypeDefaultRequiredDescription
ami_idstringyesAL2023 AMI ID. Use the FIPS-enabled variant for federal posture; the user-data also runs update-crypto-policies --set FIPS as a belt-and-suspenders.
kms_key_arnstringyesCustomer-managed KMS key ARN for the EBS root volume + SSM SecureString decryption
namestringyesResource name prefix
private_subnet_idstringyesPrivate subnet ID. The bastion has no public IP - operators reach it via SSM Session Manager.
vpc_idstringyesVPC ID
cloudflared_token_ssm_parameterstringnullnoName of a SecureString SSM parameter holding a Cloudflare tunnel token. When set, cloudflared is installed at boot and registered as a systemd service acting as a connector for that tunnel. The parameter must exist before the instance boots.
instance_typestring"t4g.nano"noEC2 instance type
ssm_parameter_arnslist(string)[]noSSM parameter ARNs the bastion can read (e.g. RDS password). Empty list = no SSM read.
tagsmap(string){}noTags
tls_passthrough_allowed_cidrslist(string)[]noCIDRs allowed to reach the TLS passthrough ports (443/80). Typically the VPC CIDR so peer tunnel connectors can relay too.
tls_passthrough_targetstringnullnoDNS name to TCP-passthrough ports 443/80 to (typically the env ALB). When set, an nginx stream proxy is installed at boot and the SG allows 443/80 from tls_passthrough_allowed_cidrs. TLS is not terminated on the bastion.

Outputs

NameDescription
iam_role_arnValue: aws_iam_role.bastion.arn
instance_idValue: aws_instance.bastion.id
private_ipValue: aws_instance.bastion.private_ip
security_group_idBastion SG - grant ingress from this on port 5432 to allow psql to RDS

Used by

AppPinned ref
ginkgov1.3.0
magnoliav1.2.0

On this page