From d97b3db48534f18049bdb61ffdf091a9b7ac6f2b Mon Sep 17 00:00:00 2001 From: Willem-Barkhuizen Date: Mon, 9 Feb 2026 17:53:39 +0200 Subject: [PATCH] chore: Update and improve jump host Terraform definition \#### Summary While deploying this module to our environment, I noticed that when logged in via Session Manager, I could not use the shortcut to reach the internal jump host printed in the output. On digging around, I realised that the SSH configuration to enable the `ssh internal-jump` shortcut was being added to the `ec2-user` home directory but I was logged in as `ssm-user`, which is the default for Session Manager. I also noticed that the instances were still using AL2, which is slated for EOL in less than 6 months (2026/06/30), and that `cloud-init` ended up overwriting the custom `/etc/motd` file that was being created by the user data script. This commit addresses the above by: - Changing the default AMI from AL2 to AL2023 - Adding a new module variable specifying which username to use for SSM access, defaulting to the SSM default of 'ssm-user' - Modifying the user data template to get the jump host username from a template parameter (the new variable for the external or 'ec2-user' for the internal) - Fixing up the script in the user data template to: - Create the templated SSM username if it doesn't exist yet, along with its group and home directory, and setting up its `sudo` permissions the same as SSM Agent would have - Add the SSH configuration for the 'ssh internal-jump' shortcut to the home directory of the templated user instead, along with some minor fixes and improvements to the related script logic - Changes how the custom MOTD message is added to avoid it being overwritten It also makes some minor formatting improvements to the instructions printed out as part of the module output. \#### Testing Planned and applied against our environment, confirming the instructions formatting, that the shortcut now works out the box, `sudo` still works and that the MOTD behavior is as expected. --- .../aws/modules/composition/jump-host/data.tf | 9 ++++- .../modules/composition/jump-host/locals.tf | 5 ++- .../aws/modules/composition/jump-host/main.tf | 1 + .../modules/composition/jump-host/outputs.tf | 9 +++-- .../jump-host/templates/userdata.sh | 39 +++++++++++++------ .../composition/jump-host/variables.tf | 6 +++ 6 files changed, 50 insertions(+), 19 deletions(-) diff --git a/terraform/aws/modules/composition/jump-host/data.tf b/terraform/aws/modules/composition/jump-host/data.tf index 69b6f7b..7fb2cd1 100644 --- a/terraform/aws/modules/composition/jump-host/data.tf +++ b/terraform/aws/modules/composition/jump-host/data.tf @@ -2,14 +2,14 @@ data "aws_region" "current" {} data "aws_caller_identity" "current" {} -data "aws_ami" "amazon_linux_2" { +data "aws_ami" "amazon_linux_2023" { count = var.external_jump_ami_id == null || var.internal_jump_ami_id == null ? 1 : 0 most_recent = true owners = ["amazon"] filter { name = "name" - values = ["amzn2-ami-hvm-*-x86_64-gp2"] + values = ["al2023-ami-2023.*-x86_64"] } filter { @@ -17,6 +17,11 @@ data "aws_ami" "amazon_linux_2" { values = ["hvm"] } + filter { + name = "block-device-mapping.volume-type" + values = ["gp3"] + } + filter { name = "state" values = ["available"] diff --git a/terraform/aws/modules/composition/jump-host/locals.tf b/terraform/aws/modules/composition/jump-host/locals.tf index 4b437e6..77e951d 100644 --- a/terraform/aws/modules/composition/jump-host/locals.tf +++ b/terraform/aws/modules/composition/jump-host/locals.tf @@ -12,13 +12,14 @@ locals { } ) - external_ami_id = var.external_jump_ami_id != null ? var.external_jump_ami_id : data.aws_ami.amazon_linux_2[0].id - internal_ami_id = var.internal_jump_ami_id != null ? var.internal_jump_ami_id : data.aws_ami.amazon_linux_2[0].id + external_ami_id = var.external_jump_ami_id != null ? var.external_jump_ami_id : data.aws_ami.amazon_linux_2023[0].id + internal_ami_id = var.internal_jump_ami_id != null ? var.internal_jump_ami_id : data.aws_ami.amazon_linux_2023[0].id userdata_internal = templatefile("${path.module}/templates/userdata.sh", { jump_type = "internal" environment = var.environment cloudwatch_region = data.aws_region.current.id internal_jump_ip = "" # Not used for internal jump + os_username = "ec2-user" }) } diff --git a/terraform/aws/modules/composition/jump-host/main.tf b/terraform/aws/modules/composition/jump-host/main.tf index 3b7fdb1..6ee43e8 100644 --- a/terraform/aws/modules/composition/jump-host/main.tf +++ b/terraform/aws/modules/composition/jump-host/main.tf @@ -393,6 +393,7 @@ module "external_jump_instance" { environment = var.environment cloudwatch_region = data.aws_region.current.id internal_jump_ip = module.internal_jump_instance.private_ip + os_username = var.ssm_os_username })) # Root volume configuration diff --git a/terraform/aws/modules/composition/jump-host/outputs.tf b/terraform/aws/modules/composition/jump-host/outputs.tf index a35de1a..28dec1a 100644 --- a/terraform/aws/modules/composition/jump-host/outputs.tf +++ b/terraform/aws/modules/composition/jump-host/outputs.tf @@ -91,21 +91,22 @@ output "connection_guide" { 1. Connect to External Jump Host (via Session Manager): aws ssm start-session --target ${module.external_jump_instance.id} - ${var.enable_internal_jump_ssm ? "2. Connect to Internal Jump Host (via Session Manager):\n aws ssm start-session --target ${module.internal_jump_instance.id}\n\n 3. From External Jump, SSH to Internal Jump (alternative method):\n ssh internal-jump\n (SSH key is automatically configured in ec2-user's home directory)\n\n 4. Manual SSH to Internal Jump (if needed):\n ssh -i /home/ec2-user/.ssh/internal_jump_key ec2-user@${module.internal_jump_instance.private_ip}" : "2. From External Jump, SSH to Internal Jump:\n ssh internal-jump\n (SSH key is automatically configured in ec2-user's home directory)\n\n 3. Manual SSH to Internal Jump (if needed):\n ssh -i /home/ec2-user/.ssh/internal_jump_key ec2-user@${module.internal_jump_instance.private_ip}"} + ${var.enable_internal_jump_ssm ? "2. Connect to Internal Jump Host (via Session Manager):\n aws ssm start-session --target ${module.internal_jump_instance.id}\n\n a. From External Jump, SSH to Internal Jump (alternative method):\n ssh internal-jump\n (SSH key is automatically configured in ${var.ssm_os_username}'s home directory)\n\n b. Manual SSH to Internal Jump (if needed):\n ssh -i /home/${var.ssm_os_username}/.ssh/internal_jump_key ec2-user@${module.internal_jump_instance.private_ip}" : "2. From External Jump, SSH to Internal Jump:\n ssh internal-jump\n (SSH key is automatically configured in ${var.ssm_os_username}'s home directory)\n\n a. Manual SSH to Internal Jump (if needed):\n ssh -i /home/${var.ssm_os_username}/.ssh/internal_jump_key ec2-user@${module.internal_jump_instance.private_ip}"} - ${var.enable_internal_jump_ssm ? "5." : "4."} Retrieve Internal Jump SSH Key (from your local machine): + 3. Retrieve Internal Jump SSH Key (from your local machine): ${module.internal_jump_ssh_key_parameter.ssm_parameter_name} aws ssm get-parameter --name ${module.internal_jump_ssh_key_parameter.ssm_parameter_name} --with-decryption --query 'Parameter.Value' --output text > internal_jump_key.pem chmod 400 internal_jump_key.pem - ${var.enable_internal_jump_ssm ? "6." : "5."} View Logs: + 4. View Logs: External: aws logs tail ${aws_cloudwatch_log_group.jump_host["external"].name} --follow Internal: aws logs tail ${aws_cloudwatch_log_group.jump_host["internal"].name} --follow IMPORTANT NOTES: - External Jump: Accessible via Session Manager (IAM-based auth) + - Default user: ${var.ssm_os_username} (Amazon Linux 2023 via SSM) - Internal Jump: ${var.enable_internal_jump_ssm ? "Accessible via Session Manager AND SSH from external jump" : "NO Session Manager access (must SSH from external jump)"} - - Default user: ec2-user (Amazon Linux 2) + - Default user: ec2-user (Amazon Linux 2023) - SSH key stored in SSM Parameter Store: ${module.internal_jump_ssh_key_parameter.ssm_parameter_name} Prerequisites: diff --git a/terraform/aws/modules/composition/jump-host/templates/userdata.sh b/terraform/aws/modules/composition/jump-host/templates/userdata.sh index 60ab007..f54670a 100644 --- a/terraform/aws/modules/composition/jump-host/templates/userdata.sh +++ b/terraform/aws/modules/composition/jump-host/templates/userdata.sh @@ -5,6 +5,7 @@ set -e JUMP_TYPE="${jump_type}" ENVIRONMENT="${environment}" CLOUDWATCH_REGION="${cloudwatch_region}" +OS_USERNAME="${os_username}" %{ if jump_type == "external" ~} INTERNAL_JUMP_IP="${internal_jump_ip}" %{ endif ~} @@ -25,6 +26,17 @@ systemctl restart sshd # Type-specific configuration if [ "$JUMP_TYPE" = "external" ]; then + # Check to see whether SSM access username exists, creating it and its similarly named group, and home directory if not. + if ! id -u $OS_USERNAME >/dev/null 2>&1; then + useradd --create-home --user-group --shell /bin/bash $OS_USERNAME + + # Create sudoers file to allow SSM access username to execute commands as root without password, same as SSM Agent would have. + cat > /etc/sudoers.d/ssm-agent-users < /home/ec2-user/.ssh/internal_jump_key - chmod 600 /home/ec2-user/.ssh/internal_jump_key - chown ec2-user:ec2-user /home/ec2-user/.ssh/internal_jump_key + # Store SSH key for OS user + mkdir -p /home/$OS_USERNAME/.ssh + chmod 700 /home/$OS_USERNAME/.ssh + echo "$INTERNAL_SSH_KEY" > /home/$OS_USERNAME/.ssh/internal_jump_key + chmod 600 /home/$OS_USERNAME/.ssh/internal_jump_key # Create SSH config for easy connection - cat > /home/ec2-user/.ssh/config < /home/$OS_USERNAME/.ssh/config < /etc/motd < /etc/motd.d/40-hyperswitch <