The requirements for this are to find all VPCs, create a Security Group in each one, find all running & stopped EC2 instances then attach this new security group to the primary interface to each instance. The TF file:
provider "aws" {
region = "us-east-1"
}
# Data block to store all VPCS
data "aws_vpcs" "all" {}
resource "aws_security_group" "vpc_sg" {
for_each = toset(data.aws_vpcs.all.ids)
name = "Example"
description = "Allows SSH access"
vpc_id = each.value
ingress {
from_port = 0
to_port = 22
protocol = "tcp"
cidr_blocks = ["1.1.1.0/27"] #Update for each environment
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
# Fetch all EC2 instances
data "aws_instances" "all" {
filter {
name = "instance-state-name"
values = ["running", "stopped"]
}
}
# Fetch instance details
data "aws_instance" "instance" {
for_each = toset(data.aws_instances.all.ids)
instance_id = each.key
}
# Fetch details of the network interfaces
# data "aws_network_interface" "interface" {
# #for_each = toset(flatten([for id in data.aws_instances.all.ids : data.aws_instance.instance[id].network_interface_id]))
# id = each.value
# }
# data "aws_network_interface" "interface" {
# for_each = toset([for id in data.aws_instances.all.ids : data.aws_instance.instance[id].network_interface_id])
# id = each.value
# }
# tolist(data.aws_instance.instance[id].network_interface_id)
# Attach Security Group only to instances NOT in an ASG
resource "aws_network_interface_sg_attachment" "attach_sg" {
for_each = {
for id in data.aws_instances.all.ids : id => (data.aws_instance.instance[id].network_interface_id)
if lookup(data.aws_instance.instance[id].tags, "aws:autoscaling:groupName", null) == null
}
security_group_id = aws_security_group.vpc_sg[data.aws_instance.instance[each.key].network_interface_id]
network_interface_id = data.aws_instance.instance[each.key].network_interface_id
}
# security_group_id = aws_security_group.vpc_sg[data.aws_network_interface.interface[data.aws_instance.instance[each.value].network_interface_id].vpc_id].id
# network_interface_id = each.value
# security_group_id = aws_security_group.vpc_sg[data.aws_instance.instance[each.key].network_interface_id]
# network_interface_id = data.aws_instance.instance[each.key].network_interface_id
I’ve also tried finding all network interfaces to select the primary one. It’s in a comment block above. Below the resource block commented out are other attempts to solve this.
---------------- terraform plan output ------------------------------------
data.aws_vpcs.all: Reading...
data.aws_instances.all: Reading...
data.aws_vpcs.all: Read complete after 0s [id=us-east-1]
data.aws_instances.all: Read complete after 1s [id=us-east-1]
data.aws_instance.instance["i-xxxxxxxxxxxxxxxxx"]: Reading...
data.aws_instance.instance["i-xxxxxxxxxxxxxxxxx"]: Reading...
<repeated output snipped>
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform planned the following actions, but then encountered a problem:
# aws_security_group.vpc_sg["vpc-xxxxxxxxxxxxxxxxx"] will be created
+ resource "aws_security_group" "vpc_sg" {
+ arn = (known after apply)
+ description = "Allows SSH access"
+ egress = [
+ {
+ cidr_blocks = [
+ "0.0.0.0/0",
]
+ from_port = 0
+ ipv6_cidr_blocks = []
+ prefix_list_ids = []
+ protocol = "-1"
+ security_groups = []
+ self = false
+ to_port = 0
# (1 unchanged attribute hidden)
},
]
+ id = (known after apply)
+ ingress = [
+ {
+ cidr_blocks = [
+ "10.0.0.0/8",
]
+ from_port = 0
+ ipv6_cidr_blocks = []
+ prefix_list_ids = []
+ protocol = "tcp"
+ security_groups = []
+ self = false
+ to_port = 22
# (1 unchanged attribute hidden)
},
]
+ name = "Example"
+ name_prefix = (known after apply)
+ owner_id = (known after apply)
+ revoke_rules_on_delete = false
+ tags_all = (known after apply)
+ vpc_id = "vpc-xxxxxxxxxxxxxxxxx"
}
# aws_security_group.vpc_sg["vpc-xxxxxxxxxxxxxxxxx"] will be created
+ resource "aws_security_group" "vpc_sg" {
+ arn = (known after apply)
+ description = "Allows SSH access"
+ egress = [
+ {
+ cidr_blocks = [
+ "0.0.0.0/0",
]
+ from_port = 0
+ ipv6_cidr_blocks = []
+ prefix_list_ids = []
+ protocol = "-1"
+ security_groups = []
+ self = false
+ to_port = 0
# (1 unchanged attribute hidden)
},
]
+ id = (known after apply)
+ ingress = [
+ {
+ cidr_blocks = [
+ "10.0.0.0/8",
]
+ from_port = 0
+ ipv6_cidr_blocks = []
+ prefix_list_ids = []
+ protocol = "tcp"
+ security_groups = []
+ self = false
+ to_port = 22
# (1 unchanged attribute hidden)
},
]
+ name = "Example"
+ name_prefix = (known after apply)
+ owner_id = (known after apply)
+ revoke_rules_on_delete = false
+ tags_all = (known after apply)
+ vpc_id = "vpc-xxxxxxxxxxxxxxxxx"
}
# aws_security_group.vpc_sg["vpc-xxxxxxxxxxxxxxxxx"] will be created
+ resource "aws_security_group" "vpc_sg" {
+ arn = (known after apply)
+ description = "Allows SSH access"
+ egress = [
+ {
+ cidr_blocks = [
+ "0.0.0.0/0",
]
+ from_port = 0
+ ipv6_cidr_blocks = []
+ prefix_list_ids = []
+ protocol = "-1"
+ security_groups = []
+ self = false
+ to_port = 0
# (1 unchanged attribute hidden)
},
]
+ id = (known after apply)
+ ingress = [
+ {
+ cidr_blocks = [
+ "10.0.0.0/8",
]
+ from_port = 0
+ ipv6_cidr_blocks = []
+ prefix_list_ids = []
+ protocol = "tcp"
+ security_groups = []
+ self = false
+ to_port = 22
# (1 unchanged attribute hidden)
},
]
+ name = "Example"
+ name_prefix = (known after apply)
+ owner_id = (known after apply)
+ revoke_rules_on_delete = false
+ tags_all = (known after apply)
+ vpc_id = "vpc-xxxxxxxxxxxxxxxxx"
}
Plan: 3 to add, 0 to change, 0 to destroy.
<repeated error snipped, it's the same for each instance>
│ Error: Invalid index
│
│ on main.tf line 70, in resource "aws_network_interface_sg_attachment" "attach_sg":
│ 70: security_group_id = aws_security_group.vpc_sg[data.aws_instance.instance[each.key].network_interface_id]
│ ├────────────────
│ │ aws_security_group.vpc_sg is object with 3 attributes
│ │ data.aws_instance.instance is object with 33 attributes
│ │ each.key is "i-xxxxxxxxxxxxxxxxx"
│
│ The given key does not identify an element in this collection value.
I've tried finding the network interface ID through aws_instance - it's there. I've also tried finding all network interface IDs for an instance and selecting the first one. That attempt is in nte comment block. I've also tried different ways in the resource block too. Additional attempts are in the comments below the resource.
For some reason I can’t figure out is why each.key has the instance ID and not the network interface ID.
If we make this into a minimal reproduceable example you can see whats happening.
data "aws_vpcs" "all" {}
data "aws_instances" "all" {
filter {
name = "instance-state-name"
values = ["running", "stopped"]
}
}
# Fetch instance details
data "aws_instance" "instance" {
for_each = toset(data.aws_instances.all.ids)
instance_id = each.key
}
output "out" {
value = {
for id in data.aws_instances.all.ids : id => (data.aws_instance.instance[id].network_interface_id)
if lookup(data.aws_instance.instance[id].tags, "aws:autoscaling:groupName", null) == null
}
}
Your for_each
expression in your aws_network_interface_sg_attachment
resource is producing a map of instance-id = eni-id
OUTPUT
Outputs:
out = {
"i-0ed20be25b8fc853c" = "eni-05181571ed2dcc41a"
}
It sounds like what you acutally need is a map of the ENI-ID and the VPC-ID. We can do this building on your work so far
locals {
eni_vpc_mapping = {for eni in data.aws_network_interface.eni: eni.id => eni.vpc_id }
}
data "aws_vpcs" "all" {}
data "aws_instances" "all" {
filter {
name = "instance-state-name"
values = ["running", "stopped"]
}
}
data "aws_instance" "instance" {
for_each = toset(data.aws_instances.all.ids)
instance_id = each.key
}
data "aws_network_interface" "eni" {
for_each = toset([for instance in data.aws_instance.instance:
instance.network_interface_id if lookup(instance.tags, "aws:autoscaling:groupName", null) == null
])
id = each.key
}
output "out" {
value = local.eni_vpc_mapping
}
this then gives us a map of ENI to VPC
out = {
"eni-05181571ed2dcc41a" = "vpc-0cd0a2ae255e97441"
}
which we can then use in the resource like
resource "aws_network_interface_sg_attachment" "attach_sg" {
for_each = local.eni_vpc_mapping
security_group_id = aws_security_group.vpc_sg[each.value]
network_interface_id = each.key
}