如何用Terraform实现EC2堡垒机SSH主机密钥在实例重部署时持久化?
解决EC2堡垒机重建时SSH主机密钥持久化的方案
针对你遇到的实例重建后SSH主机密钥变化导致验证失败的问题,有两种优雅的Terraform实现方式:
方案一:预生成固定SSH主机密钥并注入实例
直接通过Terraform生成并管理固定的SSH主机密钥,每次实例重建时都注入相同的密钥,从根源上避免密钥变化。
实现步骤
- 用Terraform的
tls_private_key资源生成RSA和ECDSA类型的主机密钥 - 在
user_data脚本中把密钥写入EC2实例的SSH配置目录,设置正确权限后重启sshd - 输出主机公钥,方便用户一次性配置到
known_hosts
Terraform代码示例
# 生成固定的SSH主机密钥 resource "tls_private_key" "ssh_host_rsa" { algorithm = "RSA" rsa_bits = 4096 } resource "tls_private_key" "ssh_host_ecdsa" { algorithm = "ECDSA" ecdsa_curve = "P384" } # 创建堡垒机EC2实例 resource "aws_instance" "bastion" { ami = "ami-xxxxxx" # 替换为你使用的AMI instance_type = "t2.micro" associate_public_ip_address = false # 绑定弹性IP,不使用临时公IP user_data_replace_on_change = true user_data = <<-EOF #!/bin/bash # 部署用户SSH密钥(原有逻辑保留) mkdir -p /home/ec2-user/.ssh echo "${var.ssh_public_keys}" >> /home/ec2-user/.ssh/authorized_keys chmod 600 /home/ec2-user/.ssh/authorized_keys chown ec2-user:ec2-user /home/ec2-user/.ssh/authorized_keys # 写入固定的SSH主机密钥 cat > /etc/ssh/ssh_host_rsa_key << 'KEY' ${tls_private_key.ssh_host_rsa.private_key_pem} KEY cat > /etc/ssh/ssh_host_rsa_key.pub << 'KEY' ${tls_private_key.ssh_host_rsa.public_key_openssh} KEY cat > /etc/ssh/ssh_host_ecdsa_key << 'KEY' ${tls_private_key.ssh_host_ecdsa.private_key_pem} KEY cat > /etc/ssh/ssh_host_ecdsa_key.pub << 'KEY' ${tls_private_key.ssh_host_ecdsa.public_key_openssh} KEY # 设置密钥文件权限(sshd要求严格权限) chmod 600 /etc/ssh/ssh_host_*_key chmod 644 /etc/ssh/ssh_host_*_key.pub # 重启sshd服务加载新密钥 systemctl restart sshd EOF } # 创建并关联弹性IP resource "aws_eip" "bastion_eip" { vpc = true } resource "aws_eip_association" "bastion_eip" { instance_id = aws_instance.bastion.id allocation_id = aws_eip.bastion_eip.id } # 输出主机公钥,方便用户配置known_hosts output "ssh_host_rsa_public_key" { value = "${tls_private_key.ssh_host_rsa.public_key_openssh} ${aws_eip.bastion_eip.public_ip}" } output "ssh_host_ecdsa_public_key" { value = "${tls_private_key.ssh_host_ecdsa.public_key_openssh} ${aws_eip.bastion_eip.public_ip}" }
使用时,用户只需把输出的公钥内容追加到本地~/.ssh/known_hosts文件,后续实例重建无需再修改。
方案二:用独立EBS卷持久化SSH主机密钥
将SSH主机密钥存储在单独的EBS卷中,实例重建时重新挂载该卷,保留原有密钥。
实现步骤
- 创建一个小型EBS卷用于存储密钥
- 首次启动实例时格式化卷并复制初始密钥到卷中
- 后续重建时直接挂载卷,确保sshd使用原有密钥
Terraform代码示例
# 创建存储SSH密钥的EBS卷 resource "aws_ebs_volume" "ssh_host_keys" { availability_zone = aws_instance.bastion.availability_zone size = 1 # 1GB足够存储密钥文件 } # 创建堡垒机EC2实例 resource "aws_instance" "bastion" { ami = "ami-xxxxxx" instance_type = "t2.micro" associate_public_ip_address = false user_data_replace_on_change = true user_data = <<-EOF #!/bin/bash # 部署用户SSH密钥 mkdir -p /home/ec2-user/.ssh echo "${var.ssh_public_keys}" >> /home/ec2-user/.ssh/authorized_keys chmod 600 /home/ec2-user/.ssh/authorized_keys chown ec2-user:ec2-user /home/ec2-user/.ssh/authorized_keys # 挂载EBS卷到/etc/ssh MOUNT_POINT="/etc/ssh" DEVICE="/dev/xvdf" # 检查卷是否已格式化,未格式化则初始化 if ! blkid $DEVICE; then mkfs.ext4 $DEVICE mkdir -p $MOUNT_POINT mount $DEVICE $MOUNT_POINT # 复制初始生成的主机密钥到卷中 cp -a /etc/ssh/ssh_host_* $MOUNT_POINT/ else mkdir -p $MOUNT_POINT mount $DEVICE $MOUNT_POINT fi # 写入fstab,确保开机自动挂载 grep -q "$DEVICE" /etc/fstab || echo "$DEVICE $MOUNT_POINT ext4 defaults 0 0" >> /etc/fstab # 重启sshd systemctl restart sshd EOF } # 附加EBS卷到实例 resource "aws_volume_attachment" "ssh_keys_attach" { device_name = "/dev/xvdf" volume_id = aws_ebs_volume.ssh_host_keys.id instance_id = aws_instance.bastion.id # 确保实例创建后再挂载卷 depends_on = [aws_instance.bastion] } # 弹性IP配置(同方案一) resource "aws_eip" "bastion_eip" { vpc = true } resource "aws_eip_association" "bastion_eip" { instance_id = aws_instance.bastion.id allocation_id = aws_eip.bastion_eip.id }
这个方案的优势是密钥不存储在Terraform状态中,安全性更高,但需要额外管理EBS卷。
内容的提问来源于stack exchange,提问作者RadioDevMan
相关产品推荐
相关产品推荐

