You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Terraform创建EC2后修改安全组规则:关闭出站流量求助

问题描述

使用Terraform创建EC2实例,通过cloud-init完成Docker、Vim、Git等软件安装后,需要停止实例的出站流量,但当前无法覆盖原有安全组,添加新安全组也未生效,相关代码如下:

主配置文件

module "airgap_instance" {
  source        = "../../../tf-modules/ec2"
  instance_type = "t3.medium"
  instance_name = "test_instance"
  sg            = module.airgap_secuirtygroups.sg_id
  user_data     = data.cloudinit_config.cloudconfig.rendered
}
data "cloudinit_config" "cloudconfig" {
  gzip          = false
  base64_encode = true
  part {
    filename     = "install.sh"
    content_type = "text/x-shellscript"
    content      = file("${path.module}/scripts/install.sh")
  }
}
resource "null_resource" "wait_for_instance" {
  depends_on = [
    module.airgap_instance.ec2_id
  ]
  triggers = {
    always_run = "${timestamp()}"
  }
  provisioner "local-exec" {
    command = "${path.module}/scripts/dep_wait.sh ${module.airgap_instance.ec2_id}"
  }
}

resource "aws_network_interface_sg_attachment" "sg_init_attachment" {
  security_group_id    = module.airgap_secuirtygroups.sg_id
  network_interface_id = module.airgap_instance.interface_id
}

resource "aws_network_interface_sg_attachment" "sg_final_attachment" {
  security_group_id    = module.airgap_secuirtygroups_update.sg_id
  network_interface_id = module.airgap_instance.interface_id
  depends_on = [
    null_resource.wait_for_instance
  ]
}

security_groups.tf

module "airgap_secuirtygroups" {
  source         = "../../../tf-modules/secuirtygroups"
  sg_name        = "terraform-airgap-init-sg"
  sg_description = "terraform-airgap-init-sg-description"
  ingress_ports  = [80, 443]
  egress_ports   = [0]
}

module "airgap_secuirtygroups_update" {
  source         = "../../../tf-modules/secuirtygroups"
  sg_name        = "terraform-airgap-sg"
  sg_description = "terraform-airgap-sg-description"
  ingress_ports  = [80, 443]
  egress_ports   = []
}

EC2模块(ec2.tf)

resource "aws_instance" "ec2_instance" {
  ami                  = var.ami_id
  instance_type        = var.instance_type
  key_name             = var.key_pair_name
  iam_instance_profile = var.instance_profile
  availability_zone    = var.availability_zone
  subnet_id            = var.subnet_id
  security_groups      = ["${var.sg}"]
  user_data            = var.user_data
  tags = {
    Name = "${var.instance_name}"
  }

  metadata_options {
    http_endpoint = "enabled"
    http_tokens   = "required"
  }
}

安全组模块(secuirty_groups.tf)

resource "aws_security_group" "allow_traffic" {
  name        = var.sg_name
  description = var.sg_description
  vpc_id      = var.sg_vpc_id
  lifecycle {
    create_before_destroy = true
  }

  dynamic "ingress" {
    iterator = port
    for_each = var.ingress_ports
    content {
      from_port   = port.value
      to_port     = port.value
      protocol    = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
    }
  }
  dynamic "egress" {
    iterator = port
    for_each = var.egress_ports
    content {
      from_port   = port.value
      to_port     = port.value
      protocol    = "-1"
      cidr_blocks = ["0.0.0.0/0"]
    }
  }

  tags = {
    Name = "${var.sg_name}"
  }
}

解决方案

1. 修复安全组替换逻辑

当前使用aws_network_interface_sg_attachment是添加安全组而非替换,最终实例会同时挂载两个安全组,出站流量仍被允许。同时EC2模块中已指定security_groups,会和后续的attachment操作产生状态冲突。

替换方案:移除两个aws_network_interface_sg_attachment资源,改用local-exec调用AWS CLI直接替换实例的安全组:

resource "null_resource" "replace_sg" {
  depends_on = [null_resource.wait_for_instance]

  provisioner "local-exec" {
    command = <<EOT
      aws ec2 modify-instance-attribute --instance-id ${module.airgap_instance.ec2_id} --groups ${module.airgap_secuirtygroups_update.sg_id}
    EOT
  }
}

同时在EC2模块的aws_instance资源中添加生命周期规则,忽略安全组的变更,避免Terraform后续计划中恢复原有安全组:

resource "aws_instance" "ec2_instance" {
  # ... 原有配置 ...
  # VPC实例优先使用vpc_security_group_ids而非security_groups
  vpc_security_group_ids = [var.sg]

  lifecycle {
    ignore_changes = [vpc_security_group_ids]
  }
}

2. 修正无出站规则的安全组配置

AWS安全组默认包含一条允许所有出站流量的规则,当egress_ports = []时,动态块不会生成任何规则,但默认规则仍会生效,导致出站流量未被禁止。

修改安全组模块,使用aws_security_group_rule单独管理出站规则,确保默认规则被覆盖:

# 安全组模块中添加
resource "aws_security_group_rule" "deny_all_egress" {
  count             = length(var.egress_ports) == 0 ? 1 : 0
  type              = "egress"
  from_port         = 0
  to_port           = 0
  protocol          = "-1"
  cidr_blocks       = ["0.0.0.0/0"]
  security_group_id = aws_security_group.allow_traffic.id
}

# 同时在aws_security_group资源中添加生命周期规则忽略默认出站规则
resource "aws_security_group" "allow_traffic" {
  # ... 原有配置 ...
  lifecycle {
    create_before_destroy = true
    ignore_changes = [egress]
  }
}

3. 确保cloud-init真正完成

当前dep_wait.sh仅等待实例启动,未验证cloud-init是否完成软件安装。修改脚本,通过SSM检查cloud-init状态(需实例拥有SSM权限):

# dep_wait.sh内容
INSTANCE_ID=$1
aws ssm send-command --instance-ids $INSTANCE_ID --document-name "AWS-RunShellScript" --parameters commands="cloud-init status --wait" --output text > /dev/null

内容的提问来源于stack exchange,提问作者user3398900

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 10:49:55