Mapping multiple Security Groups into ELB - amazon-web-services

I'm trying to attach multiple security groups containing Cloudfront CIDRs to my AWS ALB.
locals {
chunks = chunklist(data.aws_ip_ranges.cloudfront.cidr_blocks, 60)
chunks_map = { for i in range(length(local.chunks)): i => local.chunks[i] }
}
resource "aws_security_group" "sg" {
for_each = local.chunks_map
name = "{each.key}"
egress {
....
}
}
resource "aws_elb" "load" {
name = "test"
security_groups = aws_security_group.sg.id // This is wrong
My error that I'm receiving is
Because aws_security_group.sg has for_each se, its attributes must be access on specific instances
Using for_each again doesn't make sense because i don't want to create multiple resources, I just want to ensure that all security groups created are attached to the load balancer. Any ideas?

Since you've used for_each there will be more than instance of aws_security_group.sg. To get id from all of them you can use splat operator:
security_groups = values(aws_security_group.sg)[*].id

Related

Is it possible to attach a security group to a load balancer ONLY IF the security group count is set to 1?

I'm working on a terraform module which creates a load balancer amongst other resources. I would like to attach a security group to the load balancer, but only if a a certain variable is equal to "all" OR "gw".
I currently have a count argument in place for the security group itself:
resource "aws_security_group" "akamai_sg" {
count = var.domain_name_suffix == "all" || var.domain_name_suffix == "gw" ? 1 : 0
name = "akamai-pl-sg"
description = "Manage access from Akamai to ${var.environment} alb"
vpc_id = var.vpc_id
tags = merge(var.common_tags, tomap({ "Name" = "akamai-pl-sg" }))
revoke_rules_on_delete = true
}
This means that the security group will only be created if the domain_name_suffix is set to "all" or "gw".
I would like this same functionality for attaching this security group to the ALB. However, I still want to create the ALB regardless of this variable, it is just the security group attachment which I want to be dependent.
Currently I have this configuration for my ALB:
resource "aws_lb" "internal" {
name = "${var.environment}-${var.domain_name_suffix}"
internal = true
load_balancer_type = "application"
security_groups = [aws_security_group.alb_sg.id, aws_security_group.akamai_sg.id]
subnets = var.alb_subnets
idle_timeout = var.alb_idle_timeout
tags = var.common_tags
drop_invalid_header_fields = true
dynamic "access_logs" {
for_each = var.alb_access_logging_enabled == true ? [var.alb_access_logging_enabled] : []
content {
bucket = var.alb_access_logging_bucket
enabled = true
prefix = "internal"
}
}
}
However, this results in an error:
"Error: Missing resource instance key
on .terraform/modules/comm_common/alb.tf line 5, in resource "aws_lb" "internal":
5: security_groups = [aws_security_group.alb_sg.id, aws_security_group.akamai_sg.id]
Because aws_security_group.alb_sg has "count" set, its attributes must be
accessed on specific instances.
For example, to correlate with indices of a referring resource, use:
aws_security_group.alb_sg[count.index]"
When I add in this [count.index] to the ALB security group reference, I still get the same error.
Could you try the same condition in security_groups field as well? but the value would be a list with two items if condition is true and with one item if condition is false?
resource "aws_lb" "internal" {
name = "${var.environment}-${var.domain_name_suffix}"
internal = true
load_balancer_type = "application"
security_groups = var.domain_name_suffix == "all" || var.domain_name_suffix == "gw" ? [aws_security_group.alb_sg.id, aws_security_group.akamai_sg.id] : [aws_security_group.alb_sg.id]
subnets = var.alb_subnets
idle_timeout = var.alb_idle_timeout
...
}

How do I retrieve multiple vpc endpoints?

ERROR: no matching VPC Endpoint found
(error referring to data code block)
I am trying to retrieve multiple endpoints from data "aws_vpc_endpoint" resource. I created locals to retrieve service name for multiple endpoints that share the first few characters. Afterwards, the endpoints have unique characters to identify them individually.
I am wanting the data resource to loop through the data and retrieve each endpoint that shares those few characters. Then grab each endpoint id for "aws_route". FYI: The endpoints are being created from resource "aws_networkfirewall_firewall" The main thing to look at in this code snippet is locals, data, and the last line for resource "aws_route" How can I express in locals that the service_name does not end there and the rest of the string is unique to the endpoint without hard coding each service_name?
locals {
endpoints = {
service_name = "com.amazonaws.vpce.us-east-1.vpce-svc-"
}
}
data "aws_vpc_endpoint" "firewall-endpoints" {
for_each = local.endpoints
vpc_id = aws_vpc.vpc.id
service_name = each.value
#filter {
# name = "tag:AWSNetworkFirewallManaged"
# values = [true]
#}
}
resource "aws_route" "tgw_route" {
count = var.number_azs
route_table_id = aws_route_table.tgw_rt[count.index].id
destination_cidr_block = var.tgw_aws_route[0]
vpc_endpoint_id = data.aws_vpc_endpoint.firewall-endpoints["service_name"].id
}
I can't test this, but I think what you want to do is something like this:
resource "aws_route" "tgw_route" {
for_each = aws_networkfirewall_firewall.firewall_status.sync_states
route_table_id = aws_route_table.tgw_rt[???].id
destination_cidr_block = var.tgw_aws_route[0]
vpc_endpoint_id = each.value.attachment.endpoint_id
}
I'm not clear on the structure of the firewall_status output, so that may need to change slightly. The main question is how to get the appropriate route table ID per subnet. Can you access the outputs of the tgw_rt module in some way other than by index? Unfortunately, I have no experience with setting up an AWS firewall, just with Terraform, so I don't know how to solve this part of the puzzle.

Filter out Subnet IDs based on sufficient capacity in availability zones in Terraform

I'm trying to deploy an EKS cluster and everything seems to be fine except for one!
The facade module looks like this:
module "eks" {
source = "../../../../infrastructure_modules/eks"
## EKS ##
create_eks = var.create_eks
cluster_version = var.cluster_version
cluster_name = local.cluster_name
vpc_id = data.aws_vpc.this.id
subnets = data.aws_subnet_ids.this.ids
# note: either pass worker_groups or node_groups
# this is for (EKSCTL API) unmanaged node group
worker_groups = var.worker_groups
# this is for (EKS API) managed node group
node_groups = var.node_groups
## Common tag metadata ##
env = var.env
app_name = var.app_name
tags = local.eks_tags
region = var.region
}
The VPC id is retrieved through the following block :
data "aws_vpc" "this" {
tags = {
Name = "tagName"
}
}
Which then is used to retrieve the subnet_IDs as following:
data "aws_subnet_ids" "this" {
vpc_id = data.aws_vpc.this.id
}
Nevertheless, deploying this results in error stating:
Error: error creating EKS Cluster (data-layer-eks):
UnsupportedAvailabilityZoneException: Cannot create cluster
'data-layer-eks' because us-east-1e, the targeted availability zone,
does not currently have sufficient capacity to support the cluster.
Which is a well known error, and anybody can come across this for even EC2s.
I could solve this by simply hardcoding the subnet value, but that's really undesirable and hardly maintainable.
So the question is, how can I filter out subnet_IDs based on availability zones that have sufficient capacity?
First you need to collect the subnets with all of their attributes:
data "aws_subnets" "this" {
filter {
name = "vpc-id"
values = [data.aws_vpc.this.id]
}
}
data "aws_subnet" "this" {
for_each = toset(data.aws_subnets.this.ids)
id = each.value
}
data.aws_subnet.this is now a map(object) with all of the subnets and their attributes. You can now filter by availability zone accordingly:
subnets = [for subnet in data.aws_subnet.this : subnet.id if subnet.availability_zone != "us-east-1e"]
You can also filter by truthy conditionals if that condition is easier for you:
subnets = [for subnet in data.aws_subnet.this : subnet.id if contains(["us-east-1a", "us-east-1b", "us-east-1c", "us-east-1d"], subnet.availability_zone)]
It depends on your personal use case.

Security groups should be able to communicate to other security groups

My company requires that I we expressly specify all allowed ports and protocols in security group ingress rules. I would like to have a long list of ports protocols and security groups to allow ingress/egress for
from_port, to_port, protocol, security_group_that_port_protocol_restriction_applies_to
The below example has the problem that the "master-sg-ingress-security-groups" variable needs to have the security groups to be defined.
resource "aws_security_group" "master_lb_sg" {
....
}
resource "aws_security_group" "worker_sg" {
......
}
########
####### list of port protocols and security groups to create ingress blocks for. Problem is that security groups to not exist at variable creation time.
########
variable "master-sg-ingress-security-groups" {
depends_on = [aws_security_group.master_lb_sg, aws_security_group.worker_sg]
description = "List of port numbers for specific security group. company bans allowing all ports and protocols. "
type = map(any)
default = {
"ingress1" = [80, 80, "TCP", aws_security_group.master_lb_sg],
"ingress2" = [443, 443, "TCP", aws_security_group.master_lb_sg],
"ingress3" = [3398,3398, "RDP", aws_security_group.bastion_host_sg],
....
"ingress4" = [1024, 1024, "UDP", aws_security_group.worker_sg]
}
}
#####
#### I want to iterate over the above list of security groups and create dynamic ingress rules but other security groups do not exist
####
resource "aws_security_group" "test" {
depends_on = [aws_security_group.master_lb_sg, aws_security_group.worker_sg]
provider = aws.region_master
name = "master-sg"
description = "security group for Jenkins master"
vpc_id = aws_vpc.vpc_master.id
dynamic "ingress" {
# this for_each is not identical to for_each in line 21
for_each = var.master-sg-ingress-security-groups
content {
from_port = ingress.value[0]
to_port = ingress.value[1]
protocol = ingress.value[2]
security_group = ingress[3]
}
}
}
I am think ing I have to just copy paste blocks of text for each ingress
Is there a way to get around the problem of aws_security_group.worker_sg in a variable
Sadly not from TF itself. Variables must be fully defined when you run your script. But you could maybe modify master-sg-ingress-security-groups into a local variable. This way you could construct your map which includes other variables.
So depending exactly on your use-case, you could maybe have a base variable called base-master-sg-ingress-security-groups, and then in locals construct a final map which would containe references to other existing SGs.
Alternatively, you could split your TF script into two parts. The first one would deploy core SGs and output their IDs. Then these IDs would be used as input variables for the second part which would deploy SGs that reference the core ones.

Terrafrom datasource aws_vpcs - count.index error

I am trying to use data source aws_vpcs to get the vpc id having specific tag.
For reference:
https://www.terraform.io/docs/providers/aws/d/vpcs.html
Below is my terraform yaml file.
Terrafrom version used is: 0.12.3
data "aws_vpcs" "foo" {
tags = {
Name = "test1-VPC"
}
}
resource "aws_security_group" "cluster" {
count = "${length(data.aws_vpcs.foo.ids)}"
vpc_id = "${tolist(data.aws_vpcs.foo.ids)[count.index]}"
}
resource "aws_security_group_rule" "cluster-ingress-node-https" {
description = "Rule to do xyz"
from_port = 443
protocol = "tcp"
security_group_id = "${aws_security_group.cluster.id}"
to_port = 443
type = "ingress"
}
I am getting below error. Request for help to fix this
terraform plan
Error: Missing resource instance key
on modules/eks/eks-cluster.tf line 40, in resource "aws_security_group_rule" "cluster-ingress-node-https":
40: security_group_id = "${aws_security_group.cluster.id}"
Because aws_security_group.cluster has "count" set, its attributes must be
accessed on specific instances.
For example, to correlate with indices of a referring resource, use:
aws_security_group.cluster[count.index]
You are creating a list of aws_security_group as you are using count on the aws_security_group resource. The error even mentions it:
Because aws_security_group.cluster has "count" set, its attributes
must be accessed on specific instances.
So either you need to include count on the aws_security_group_rule resource and create one aws_security_group_rule for each aws_security_group created, or in the case you expect only one VPC to be returned, create only one aws_security_group by accessing the returned aws_vpcs.foo.ids with index 0.
You will need to convert the list of security group.
Terraform provides flatten function to do that https://nedinthecloud.com/2018/07/16/terraform-fotd-flatten/
You should not get this error afterwards
I know this was posted a while ago. Stumbled upon this issue.
${aws_security_group.cluster.*.id} should do it.
Since the resource aws_security_group is creating multiple security groups with count, resource block aws_security_group_rule needs to reference the correct index in the list.