I’ve created primary_sg, secondary_sg_tcpci, secondary_sg_tcpqa, secondary_sq_tcpprod security groups.
primary_sg has three outbound rules to it and each one is associated with secondary_sg securitygroup as destination.
For secondary, I created three security groups secondary_sg_tcpci, secondary_sg_tcpqa and secondary_sg_tcpprod. Each of these security group inbound rules are associated again with same primary_sg securitygroup as source in it and referring to some local variables.
main.tf
data "aws_vpc" "default" {
id = "vpc-1234"
}
data "aws_security_groups" "server_sg"{
tags = {
name = "server_sg"
}
}
locals {
vpc_id = data.aws_vpc.default.id
server_jumpbox_security_groups = data.aws_security_groups.server_sg.id
}
####### primary_sg###########
resource "aws_security_group" "primary_sg" {
name = "primary_security_group"
description = "Allows outbound rules"
vpc_id = local.vpc_id
}
resource "aws_security_group_rule" "rule_01" {
type = "egress"
from_port = 22
to_port = 22
protocol = "tcp"
source_security_group_id = aws_security_group.secondary_sg_tcpci.id ## Associating secondary_sg_tcpci as destination security group ####
description = "primary_sg rule01"
security_group_id = aws_security_group.primary_sg.id
}
resource "aws_security_group_rule" "rule_02" {
type = "egress"
from_port = 22
to_port = 22
protocol = "tcp"
source_security_group_id = aws_security_group.secondary_sg_tcpqa.id ## Associating secondary_sg_tcpqa as destination security group ####
description = "primary_sg rule02"
security_group_id = aws_security_group.primary_sg.id
}
resource "aws_security_group_rule" "rule_03" {
type = "egress"
from_port = 22
to_port = 22
protocol = "tcp"
source_security_group_id = aws_security_group.secondary_sg_tcpprod.id ## Associating secondary_sg_tcpprod as destination security group ####
description = "primary_sg rule02"
security_group_id = aws_security_group.primary_sg.id
}
########secondary_sg#########
resource "aws_security_group" "secondary_sg_tcpci" {
name = "secondary_sg_tcpci"
description = "RDS SG for TCPCI env"
vpc_id = data.aws_vpc.default.id
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
security_groups = [local.server_jumpbox_security_groups]
description = "Server SG"
}
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
security_groups = ["${aws_security_group.primary_sg.id}"]
description = "Secondary SG"
}
lifecycle {
create_before_destroy = true
}
}
resource "aws_security_group" "secondary_sg_tcpqa" {
name = "secondary_sg_tcpqa"
description = "RDS SG for TCPQA env"
vpc_id = data.aws_vpc.default.id
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
security_groups = [local.server_jumpbox_security_groups]
description = "Server SG"
}
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
security_groups = ["${aws_security_group.primary_sg.id}"]
description = "Secondary SG"
}
lifecycle {
create_before_destroy = true
}
}
resource "aws_security_group" "secondary_sg_tcpprod" {
name = "secondary_sg_tcpprod"
description = "RDS SG for TCPQA env"
vpc_id = data.aws_vpc.default.id
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
security_groups = [local.server_jumpbox_security_groups]
description = "Server SG"
}
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
security_groups = ["${aws_security_group.primary_sg.id}"]
description = "Secondary SG"
}
lifecycle {
create_before_destroy = true
}
}
I tried using for_each loop.
main.tf
resource "aws_security_group" "secondary_sg_tcpci" {
for_each = var.config
name = "${each.key}-rds"
description = "RDS SG for TCPCI env"
vpc_id = data.aws_vpc.default.id
dynamic "ingress" {
for_each = var.ingress_rules
iterator = port
content {
from_port = ingress.value.port
to_port = ingress.value.port
protocol = ingress.value.protocol
security_groups = ["${aws_security_group.primary_sg.id}"]
}
}
}
resource "aws_security_group" "primary_sg" {
name = "primary_security_group"
description = "Allows outbound rules"
vpc_id = local.vpc_id
}
resource "aws_security_group_rule" "rule_01" {
type = "egress"
from_port = 22
to_port = 22
protocol = "tcp"
source_security_group_id = aws_security_group.secondary_sg_tcpci.id ## Associating secondary_sg_tcpci as destination security group ####
description = "primary_sg rule01"
security_group_id = aws_security_group.primary_sg.id
}
resource "aws_security_group_rule" "rule_02" {
type = "egress"
from_port = 22
to_port = 22
protocol = "tcp"
source_security_group_id = aws_security_group.secondary_sg_tcpqa.id ## Associating secondary_sg_tcpqa as destination security group ####
description = "primary_sg rule02"
security_group_id = aws_security_group.primary_sg.id
}
vars.tf
variable "config" {
description = "Security groups configuration"
type = map(object({
name = string
}))
default = {
"sg-1" = {
name = "sg-1"
}
"sg-2" = {
name = "sg-2"
}
}
}
variable "ingress_rules" {
type = map(object({
port = number
protocol = string
}))
default = {
"22" = {
cidr_block = ["0.0.0.0/0"]
port = 22
protocol = "tcp"
}
"22" = {
cidr_block = ["0.0.0.0/0"]
port = 22
protocol = "tcp"
}
}
}
Since you are creating the SG with for_each, you can only access the SG attributes but using proper keys. There is also a small error in other parts of code as in the dynamic block you are setting the iterator and then later on in the content you are not using it, so you can drop it (as it will default to the dynamic block name which is ingress):
resource "aws_security_group" "secondary_sg_tcpci" {
for_each = var.config
name = "${each.key}-rds"
description = "RDS SG for TCPCI env"
vpc_id = data.aws_vpc.default.id
dynamic "ingress" {
for_each = var.ingress_rules
content {
from_port = ingress.value.port
to_port = ingress.value.port
protocol = ingress.value.protocol
security_groups = [aws_security_group.primary_sg.id]
}
}
}
resource "aws_security_group" "primary_sg" {
name = "primary_security_group"
description = "Allows outbound rules"
vpc_id = local.vpc_id
}
resource "aws_security_group_rule" "rule_01" {
for_each = aws_security_group.secondary_sg_tcpci
type = "egress"
from_port = 22
to_port = 22
protocol = "tcp"
source_security_group_id = each.value.id # <--- this is how you would reference the security IDs for all the SGs created with `for_each`
description = "primary_sg rule01"
security_group_id = aws_security_group.primary_sg.id
}
This is only for the one SG created with for_each. This technique is called resource chaining with for_each [1]. If you need to repeat the same for other SGs (tcpqa, tcpprod etc.) you would have to adjust the rest of the code.
[1] https://developer.hashicorp.com/terraform/language/meta-arguments/for_each#chaining-for_each-between-resources
Related
I'm new to terraform.. I'm getting the below error when I run terraform plan,
Error: Unsupported argument
> │
on 4_data_vpc.tf line 6, in data "aws_subnets" "subnets":
6: vpc_id = data.aws_vpc.default_vpc.id
> │
An argument named "vpc_id" is not expected here.
This is the data_vpc.tf file
data "aws_vpc" "default_vpc" {
default = true
}
# subnet list in the "default" VPC
# The "default" VPC has all "public subnets"
data "aws_subnet_ids" "default_public" {
vpc_id = "${data.aws_vpc.default_vpc.id}"
}
I've updated aws_subnet_ids to aws_subnets as aws_subnet_ids data source has been deprecated
provider.tf file
provider "aws" {
region = "us-east-1"
shared_credentials_file = "~/.aws/credentials"
profile = "dev"
}
securityGroups.tf
# Security Group:
resource "aws_security_group" "jenkins_server" {
name = "jenkins_server"
description = "Jenkins Server: created by Terraform for [dev]"
# legacy name of VPC ID
vpc_id = "${data.aws_vpc.default_vpc.id}"
tags {
Name = "jenkins_server"
env = "dev"
}
}
###############################################################################
# ALL INBOUND
###############################################################################
# ssh
resource "aws_security_group_rule" "jenkins_server_from_source_ingress_ssh" {
type = "ingress"
from_port = 22
to_port = 22
protocol = "tcp"
security_group_id = "${aws_security_group.jenkins_server.id}"
cidr_blocks = ["<Your Public IP>/32", "172.0.0.0/8"]
description = "ssh to jenkins_server"
}
# web
resource "aws_security_group_rule" "jenkins_server_from_source_ingress_webui" {
type = "ingress"
from_port = 8080
to_port = 8080
protocol = "tcp"
security_group_id = "${aws_security_group.jenkins_server.id}"
cidr_blocks = ["0.0.0.0/0"]
description = "jenkins server web"
}
# JNLP
resource "aws_security_group_rule" "jenkins_server_from_source_ingress_jnlp" {
type = "ingress"
from_port = 33453
to_port = 33453
protocol = "tcp"
security_group_id = "${aws_security_group.jenkins_server.id}"
cidr_blocks = ["172.31.0.0/16"]
description = "jenkins server JNLP Connection"
}
###############################################################################
# ALL OUTBOUND
###############################################################################
resource "aws_security_group_rule" "jenkins_server_to_other_machines_ssh" {
type = "egress"
from_port = 22
to_port = 22
protocol = "tcp"
security_group_id = "${aws_security_group.jenkins_server.id}"
cidr_blocks = ["0.0.0.0/0"]
description = "allow jenkins servers to ssh to other machines"
}
resource "aws_security_group_rule" "jenkins_server_outbound_all_80" {
type = "egress"
from_port = 80
to_port = 80
protocol = "tcp"
security_group_id = "${aws_security_group.jenkins_server.id}"
cidr_blocks = ["0.0.0.0/0"]
description = "allow jenkins servers for outbound yum"
}
resource "aws_security_group_rule" "jenkins_server_outbound_all_443" {
type = "egress"
from_port = 443
to_port = 443
protocol = "tcp"
security_group_id = "${aws_security_group.jenkins_server.id}"
cidr_blocks = ["0.0.0.0/0"]
description = "allow jenkins servers for outbound yum"
}
When I declare a variable vpc_id in the data_vpc.tf file and run terraform plan then the terminal is asking me to enter a value.
var.vpc_id
Enter a value:
When you are using aws_subnets, to add vpc_id, you have to use filter block:
data "aws_subnets" "subnets" {
filter {
name = "vpc-id"
values = [data.aws_vpc.default_vpc.id]
}
}
I have a security group resource in the module called "networking":
resource "aws_security_group" "dev_sg" {
for_each = var.security_groups
name = each.value.name
description = each.value.description
vpc_id = aws_vpc.dev_vpc.id
dynamic "ingress" {
for_each = each.value.ingress
#iterator = port
content {
from_port = ingress.value.from
to_port = ingress.value.to
protocol = ingress.value.protocol
cidr_blocks = ingress.value.cidr_blocks
}
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
Also, outside of the module, in the root module i have locals.tf file which is this:
locals {
security_groups = {
public = {
name = "public_sg"
description = "Security Group for Public Access"
ingress = {
ssh = {
from = 22
to = 22
protocol = "tcp"
cidr_blocks = [var.access_ip]
}
http = {
from = 80
to = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
}
}
And here is the module definition:
module "networking" {
source = "./networking"
vpc_cidr = local.vpc_cidr
security_groups = local.security_groups
public_sn_count = 2
private_sn_count = 3
}
Now, my question is, how can I reference a security group ID instead of cidr_block inside locals.tf file? I have no clue how to implement this?
For example:
cidr_blocks = ["192.168.8.0/21", "${var.security_group_id}"]
You need to use security_groups in aws_security_group ingress or egress
resource "aws_security_group" "dev_sg" {
for_each = var.security_groups
name = each.value.name
description = each.value.description
vpc_id = aws_vpc.dev_vpc.id
dynamic "ingress" {
for_each = each.value.ingress
#iterator = port
content {
from_port = ingress.value.from
to_port = ingress.value.to
protocol = ingress.value.protocol
cidr_blocks = ingress.value.cidr_blocks
security_groups = lookup(ingress.value, "security_groups", null)
}
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
locals {
security_groups = {
public = {
name = "public_sg"
description = "Security Group for Public Access"
ingress = {
ssh = {
from = 22
to = 22
protocol = "tcp"
cidr_blocks = [var.access_ip]
security_groups = [var.security_group_id]
}
http = {
from = 80
to = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"],
security_groups = [var.security_group_id]
}
}
}
}
module "networking" {
source = "./networking"
vpc_cidr = local.vpc_cidr
security_groups = local.security_groups
public_sn_count = 2
private_sn_count = 3
}
My code will create security groups as well as ingress/egress as we give the list of security groups and rules in the dev.tfvars file
The code ran successfully but created security groups takes ingress/egress rules from all given security groups.
./security.tf
resource "aws_security_group" "sg" {
count = length(var.vpc_config.security_groups)
name = var.vpc_config.security_groups[count.index].name
description = var.vpc_config.security_groups[count.index].description
vpc_id = var.vpc_id
dynamic "ingress" {
for_each = var.vpc_config.security_groups
content {
from_port = ingress.value.ingress.from_port
to_port = ingress.value.ingress.to_port
protocol = ingress.value.ingress.protocol
cidr_blocks = ingress.value.ingress.cidr_block
}
}
dynamic "egress" {
for_each = var.vpc_config.security_groups
content {
from_port = egress.value.egress.from_port
to_port = egress.value.egress.to_port
protocol = egress.value.egress.protocol
cidr_blocks = egress.value.egress.cidr_block
}
}
tags = {
Name = var.vpc_config.security_groups[count.index].name
Environment = var.vpc_config.environment
}
}
./dev.tfvars
vpc_config = {
security_groups = [ {
name = "sg_1"
description = "security group 1"
ingress = {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_block = ["0.0.0.0/0"]
}
egress = {
from_port = 0
to_port = 0
protocol = "-1"
cidr_block = ["0.0.0.0/0"]
}
},
{
name = "sg_2"
description = "security group 2"
ingress = {
from_port = 21
to_port = 21
protocol = "tcp"
cidr_block = ["0.0.0.0/0"]
}
egress = {
from_port = 443
to_port = 443
protocol = "http"
cidr_block = ["0.0.0.0/0"]
}
}
]
}
It will create two security groups with one ingress and one egress each but it creates two security groups with two ingress and two egress each.
If your goal is to create a 2 security groups, each having a certain ingress and egress rules explicitly defined, you do not want to have dynamic blocks. With dynamic blocks, you will create an inner loop, which is not what you would want.
I recommend using only one for_each at the resource level and no dynamic blocks:
resource "aws_security_group" "sg" {
for_each = {
for sg in var.vpc_config.security_groups : sg.name => sg
}
name = each.value.name
description = each.value.description
vpc_id = var.vpc_id
ingress {
from_port = each.value.ingress.from_port
to_port = each.value.ingress.to_port
protocol = each.value.ingress.protocol
cidr_blocks = each.value.ingress.cidr_block
}
egress {
from_port = each.value.egress.from_port
to_port = each.value.egress.to_port
protocol = each.value.egress.protocol
cidr_blocks = each.value.egress.cidr_block
}
tags = {
Name = each.value.name
Environment = var.vpc_config.environment
}
}
If you want to use count, you can do it as follows:
resource "aws_security_group" "sg" {
count = length(var.vpc_config.security_groups)
name = var.vpc_config.security_groups[count.index].name
description = var.vpc_config.security_groups[count.index].description
vpc_id = var.vpc_id
ingress {
from_port = var.vpc_config.security_groups[count.index].ingress.from_port
to_port = var.vpc_config.security_groups[count.index].ingress.to_port
protocol = var.vpc_config.security_groups[count.index].ingress.protocol
cidr_blocks = var.vpc_config.security_groups[count.index].ingress.cidr_block
}
egress {
from_port = var.vpc_config.security_groups[count.index].egress.from_port
to_port = var.vpc_config.security_groups[count.index].egress.to_port
protocol = var.vpc_config.security_groups[count.index].egress.protocol
cidr_blocks = var.vpc_config.security_groups[count.index].egress.cidr_block
}
tags = {
Name = var.vpc_config.security_groups[count.index].name
Environment = var.vpc_config.environment
}
}
I am currently learning Terraform and I need help with regard to the below code. I want to create a simple architecture of an autoscaling group of EC2 instances behind an Application load balancer. The setup gets completed but when I try to access the application endpoint, it gets timed out. When I tried to access the EC2 instances, I was unable to (because EC2 instances were in a security group allowing access from the ALB security group only). I changed the instance security group ingress values and ran the user_data script manually following which I reverted the changes to the instance security group to complete my setup.
My question is why is my setup not working via the below code? Is it because the access is being restricted by the load balancer security group or is my launch configuration block incorrect?
data "aws_ami" "amazon-linux" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["amzn2-ami-kernel-5.10-hvm-2.0.20220426.0-x86_64-gp2"]
}
}
data "aws_availability_zones" "available" {
state = "available"
}
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "3.14.0"
name = "main-vpc"
cidr = "10.0.0.0/16"
azs = data.aws_availability_zones.available.names
public_subnets = ["10.0.4.0/24","10.0.5.0/24","10.0.6.0/24"]
enable_dns_hostnames = true
enable_dns_support = true
}
resource "aws_launch_configuration" "TestLC" {
name_prefix = "Lab-Instance-"
image_id = data.aws_ami.amazon-linux.id
instance_type = "t2.nano"
key_name = "CloudformationKeyPair"
user_data = file("./user_data.sh")
security_groups = [aws_security_group.TestInstanceSG.id]
lifecycle {
create_before_destroy = true
}
}
resource "aws_autoscaling_group" "TestASG" {
min_size = 1
max_size = 3
desired_capacity = 2
launch_configuration = aws_launch_configuration.TestLC.name
vpc_zone_identifier = module.vpc.public_subnets
}
resource "aws_lb_listener" "TestListener"{
load_balancer_arn = aws_lb.TestLB.arn
port = "80"
protocol = "HTTP"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.TestTG.arn
}
}
resource "aws_lb" "TestLB" {
name = "Lab-App-Load-Balancer"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.TestLoadBalanceSG.id]
subnets = module.vpc.public_subnets
}
resource "aws_lb_target_group" "TestTG" {
name = "LabTargetGroup"
port = "80"
protocol = "HTTP"
vpc_id = module.vpc.vpc_id
}
resource "aws_autoscaling_attachment" "TestAutoScalingAttachment" {
autoscaling_group_name = aws_autoscaling_group.TestASG.id
lb_target_group_arn = aws_lb_target_group.TestTG.arn
}
resource "aws_security_group" "TestInstanceSG" {
name = "LAB-Instance-SecurityGroup"
ingress{
from_port = 80
to_port = 80
protocol = "tcp"
security_groups = [aws_security_group.TestLoadBalanceSG.id]
}
ingress{
from_port = 22
to_port = 22
protocol = "tcp"
security_groups = [aws_security_group.TestLoadBalanceSG.id]
}
egress{
from_port = 0
to_port = 0
protocol = "-1"
security_groups = [aws_security_group.TestLoadBalanceSG.id]
}
vpc_id = module.vpc.vpc_id
}
resource "aws_security_group" "TestLoadBalanceSG" {
name = "LAB-LoadBalancer-SecurityGroup"
ingress{
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress{
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress{
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
vpc_id = module.vpc.vpc_id
}
I'm trying to get the output of VPC instance IP and the IP not correct here's my configuration
resource "aws_vpc" "default" {
cidr_block = "${var.vpc_cidr}"
enable_dns_hostnames = true
tags {
Name = "terraform-aws-vpc"
}
}
resource "aws_internet_gateway" "default" {
vpc_id = "${aws_vpc.default.id}"
}
/*
NAT Instance
*/
resource "aws_security_group" "nat" {
name = "vpc_nat"
description = "Allow traffic to pass from the private subnet to the internet"
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["${var.private_subnet_cidr}"]
}
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["${var.private_subnet_cidr}"]
}
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = -1
to_port = -1
protocol = "icmp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["${var.vpc_cidr}"]
}
egress {
from_port = -1
to_port = -1
protocol = "icmp"
cidr_blocks = ["0.0.0.0/0"]
}
vpc_id = "${aws_vpc.default.id}"
tags {
Name = "NAT"
}
}
resource "aws_instance" "nat" {
ami = "ami-30913f47" # this is a special ami preconfigured to do NAT
availability_zone = "eu-west-1a"
instance_type = "m1.small"
key_name = "admin_key"
vpc_security_group_ids = ["${aws_security_group.nat.id}"]
subnet_id = "${aws_subnet.eu-west-1a-public.id}"
associate_public_ip_address = true
source_dest_check = false
tags {
Name = "VPC NAT"
}
}
resource "aws_eip" "nat" {
instance = "${aws_instance.nat.id}"
vpc = true
}
/*
Public Subnet
*/
resource "aws_subnet" "eu-west-1a-public" {
vpc_id = "${aws_vpc.default.id}"
cidr_block = "${var.public_subnet_cidr}"
availability_zone = "eu-west-1a"
tags {
Name = "Public Subnet"
}
}
resource "aws_route_table" "eu-west-1a-public" {
vpc_id = "${aws_vpc.default.id}"
route {
cidr_block = "0.0.0.0/0"
gateway_id = "${aws_internet_gateway.default.id}"
}
tags {
Name = "Public Subnet"
}
}
resource "aws_route_table_association" "eu-west-1a-public" {
subnet_id = "${aws_subnet.eu-west-1a-public.id}"
route_table_id = "${aws_route_table.eu-west-1a-public.id}"
}
/*
Private Subnet
*/
resource "aws_subnet" "eu-west-1a-private" {
vpc_id = "${aws_vpc.default.id}"
cidr_block = "${var.private_subnet_cidr}"
availability_zone = "eu-west-1a"
tags {
Name = "Private Subnet"
}
}
resource "aws_route_table" "eu-west-1a-private" {
vpc_id = "${aws_vpc.default.id}"
route {
cidr_block = "0.0.0.0/0"
instance_id = "${aws_instance.nat.id}"
}
tags {
Name = "Private Subnet"
}
}
resource "aws_route_table_association" "eu-west-1a-private" {
subnet_id = "${aws_subnet.eu-west-1a-private.id}"
route_table_id = "${aws_route_table.eu-west-1a-private.id}"
}
output "NAT_Private_IP" {
value = "${aws_instance.nat.private_ip}"
}
I have tested the following
aws_instance.nat.public_ip
and
aws_eip.nat.public_ip
but no chance about this when using aws_instance.nat.public_ip it's gives not correct ip, this code based on terraform AWS and I'm trying to make VPC bastion host
Looks like you are trying modify the default VPC, see the following:
https://aws.amazon.com/premiumsupport/knowledge-center/vpc-ip-address-range/
https://github.com/terraform-providers/terraform-provider-aws/issues/3403