List of public IPs of NAT Gateways in VPC - terraform

Is it possible to get a list of the public IPs of the NAT gateways in a VPC, using a Terraform data source?
An example of getting a list of subnet ids is shown here, but it is predicated on the aws_subnet_ids data source, which returns a list to start with.
We've got NAT gateways per private subnet. I'm not finding a way to get the list of NAT gateways in a vpc and then get the public IPs from that list.
Has anyone needed and/or solved this issue?

This workaround worked for me
https://github.com/hashicorp/terraform-provider-aws/issues/7575
My code sample
data "aws_nat_gateway" "nat_gw" {
for_each = toset(module.vpc.public_subnets)
subnet_id = each.value
}
Get public IP of NAT to add as source for an Security group
resource "aws_security_group_rule" "allow_https"{
type = "ingress"
security_group_id = module.sg.id
from_port = "443"
to_port = "443"
protocol = "tcp"
cidr_blocks = [ for v in data.aws_nat_gateway.nat_gw : format("${v.public_ip}%s", "/32") ]
}

Try this code.
terraform {
required_providers {
shell = {
source = "scottwinkler/shell"
version = "1.7.10"
}
}
}
provider "shell" {
# Configuration options
}
data "shell_script" "nat_gateways" {
lifecycle_commands {
read = <<-EOF
aws ec2 describe-nat-gateways --region ${var.region}
EOF
}
}
locals {
nat_gw_ips = flatten([
for elem in jsondecode(data.shell_script.nat_gateways.output.NatGateways):
format("${elem.NatGatewayAddresses[0].PublicIp}%s", "/32")
])
}
output "natgwips" {
value = local.nat_gw_ips
}

Related

Terraform retrieve inbound NAT rules ports

I'm deploying infrastructure on Azure using Terraform,
I'm using modules for a linux scale set an a load balancer and using azurerm_lb_nat_pool in order to have SSH access to the VMs,
I have a need now to retrieve the ports of the NAT rules for other purposes.
For the life of me I cannot find a way to retrieve them, went through all the terraform documentation and cannot find it under any data source or attribute reference.
Here is my LB code:
resource "azurerm_lb" "front-load-balancer" {
name = "front-load-balancer"
location = var.def-location
resource_group_name = var.rg-name
sku = "Standard"
frontend_ip_configuration {
name = "frontend-IP-configuration"
public_ip_address_id = var.public-ip-id
}
}
resource "azurerm_lb_nat_pool" "lb-nat-pool" {
resource_group_name = var.rg-name
loadbalancer_id = azurerm_lb.front-load-balancer.id
name = "lb-nat-pool"
protocol = "Tcp"
frontend_port_start = var.frontend-port-start
frontend_port_end = var.frontend-port-end
backend_port = 22
frontend_ip_configuration_name = "frontend-IP-configuration"
}
Any assistance would be very appreciated.
EDIT:
I tried exporting the inbound_nat_rules export on the azurerm_lb frontend IP configuration, it gives a list of the resources which I do not currently know how to extract the ports from::
output "frontend-ip-confguration-inbound-nat-rules" {
value = azurerm_lb.front-load-balancer.frontend_ip_configuration[*].inbound_nat_rules
}
Which results in this:
Changes to Outputs:
+ LB-frontend-IP-confguration-Inbound-nat-rules = [
+ [
+ "/subscriptions/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/resourceGroups/weight-tracker-stage-rg/providers/Microsoft.Network/loadBalancers/front-load-balancer/inboundNatRules/lb-nat-pool.3",
+ "/subscriptions/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/resourceGroups/weight-tracker-stage-rg/providers/Microsoft.Network/loadBalancers/front-load-balancer/inboundNatRules/lb-nat-pool.4",
+ "/subscriptions/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/resourceGroups/weight-tracker-stage-rg/providers/Microsoft.Network/loadBalancers/front-load-balancer/inboundNatRules/lb-nat-pool.6",
],
]

Can Terraform add individual Digital Ocean firewall rules?

I'm building a mix of EC2 instance and DO droplets which must be able to communicate in a fully meshed manner, which requires firewall configurations for AWS and DO to have knowledge of the WAN and LAN addresses for all created resources. The number of resources per platform is variable.
When using Terraform with the AWS provider, I can create a security group and later add individual rules to it:
resource "aws_security_group" "sg-1" {
ingress {
...
}
egress {
...
}
}
resource "aws_security_group_rule" "sg1-rule-1" {
security_group_id = aws_security_group.sg-1.id
...
}
The DigitalOcean provider provides the digitalocean_firewall resource, but nothing similar to the aws_security_group_rule resource, which means the entire firewall ruleset must be known in advance and applied within the digitalocean_firewall resource block.
My only solution so far is to dynamically create the digitalocean_firewall.tf file from a template once the resources have been created, but this then requires a second terraform apply to apply the dynamic rules.
In the absence of a digitalocean_firewall_rule resource, is there a better approach?
Answering my own question!
The solution is to use Terraform's dynamic block resource:
resource "digitalocean_firewall" "firewall" {
name = "${var.project_name}-${var.environment}-firewall"
inbound_rule {
protocol = "tcp"
port_range = "22"
source_addresses = ["x.x.x.x/32"]
}
dynamic "inbound_rule" {
for_each = module.ec2_node
content {
protocol = "tcp"
port_range = "1-65535"
source_addresses = [inbound_rule.value.public_ip]
}
}
dynamic "inbound_rule" {
for_each = module.ec2_node
content {
protocol = "udp"
port_range = "1-65535"
source_addresses = [inbound_rule.value.public_ip]
}
}
}

Terraform passing value to list only when a condition is true

I am trying to create a AWS VPC module using Terraform. I am making VPC secondary CIDR an optional feature of the module.
if secondary_cidr = true, then create subnets using the seocandary_cidr and network acls
The issue I am running into is with Network ACLs. Network ACLs creation using terraform uses a list subnet IDs to associate to the NACL. I want to create one network ACL to associate primary subnets and secondary subnets only when secondary_cidr=true
See the code below:
cidr1_subnets = {
CIDR1_SUBNETS = [aws_subnet.app1-az1.id, aws_subnet.app1-az2.id, aws_subnet.app1-az3.id]
}
cidr2_subnets = {
exists = {
CIDR2_SUBNETS = [aws_subnet.app2-az1.id, aws_subnet.app2-az2.id, aws_subnet.app2-az3.id]
}
not_exists = {}
}
}
resource "aws_network_acl" "app" {
vpc_id = aws_vpc.main.id
egress {
protocol = "-1"
rule_no = 100
action = "allow"
cidr_block = "0.0.0.0/0"
from_port = 0
to_port = 0
}
ingress {
protocol = "-1"
rule_no = 100
action = "allow"
cidr_block = "0.0.0.0/0"
from_port = 0
to_port = 0
}
subnet_ids = merge(
local.cidr1_subnets,
local.cidr2_subnets[var.secondary_cidr == true ? true : false]
)
}```
I think you are after the following:
subnet_ids = var.secondary_cidr == true ? merge(local.cidr1_subnets, local.cidr2_subnets) : local.cidr1_subnets
Btw, your locals and the use of merge will fail anyway, but this is a problem of other issue I guess.

Terraform dynamic tagging of EC2 resource fails with `Blocks of type "tag" are not expected here`

➜ terraform -v
Terraform v0.12.24
+ provider.aws v2.60.0
My terraform example.tf:
locals {
standard_tags = {
team = var.team
project = var.project
component = var.component
environment = var.environment
}
}
provider "aws" {
profile = "profile"
region = var.region
}
resource "aws_key_pair" "security_key" {
key_name = "security_key"
public_key = file(".ssh/key.pub")
}
# New resource for the S3 bucket our application will use.
resource "aws_s3_bucket" "project_bucket" {
# NOTE: S3 bucket names must be unique across _all_ AWS accounts, so
# this name must be changed before applying this example to avoid naming
# conflicts.
bucket = "project-bucket"
acl = "private"
}
resource "aws_security_group" "ssh_allow" {
name = "allow-all-ssh"
ingress {
cidr_blocks = [
"0.0.0.0/0"
]
from_port = 22
to_port = 22
protocol = "tcp"
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_security_group" "http_allow" {
name = "allow-all-http"
ingress {
cidr_blocks = [
"0.0.0.0/0"
]
from_port = 80
to_port = 80
protocol = "tcp"
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_instance" "example" {
ami = "ami-08ee2516c7709ea48"
instance_type = "t2.micro"
security_groups = [aws_security_group.ssh_allow.name, aws_security_group.http_allow.name]
key_name = aws_key_pair.security_key.key_name
connection {
type = "ssh"
user = "centos"
private_key = file(".ssh/key")
host = self.public_ip
}
provisioner "local-exec" {
command = "echo ${aws_instance.example.public_ip} > ip_address.txt"
}
provisioner "remote-exec" {
inline = [
"sudo yum -y install nginx",
"sudo systemctl start nginx"
]
}
depends_on = [aws_s3_bucket.project_bucket, aws_key_pair.security_key]
dynamic "tag" {
for_each = local.standard_tags
content {
key = tag.key
value = tag.value
propagate_at_launch = true
}
}
}
And when I run terraform plan
I got the following error:
➜ terraform plan
Error: Unsupported block type
on example.tf line 94, in resource "aws_instance" "example":
94: dynamic "tag" {
Blocks of type "tag" are not expected here.
There isn't a block type called tag defined in the schema for the aws_instance resource type. There is an argument called tags, which is I think the way to get the result you were looking for here:
tags = local.standard_tags
I expect you are thinking of the tag block in aws_autoscaling_group, which deviates from the usual design of tags arguments in AWS provider resources because for this resource type in particular each tag has the additional attribute propagate_at_launch. That attribute only applies to autoscaling groups because it decides whether instances launched from the autoscaling group will inherit a particular tag from the group itself.
unfortunately since the aws_instance resource's tags attribute is a map, w/in the HCL constructs atm, it cannot exist as repeatable blocks like a tag attribute in the aws_autoscaling_group example seen here in the Dynamic Nested Blocks section: https://www.hashicorp.com/blog/hashicorp-terraform-0-12-preview-for-and-for-each/
but from your comment, it seems you're trying to set the tags attribute with perhaps a map of key/value pairs? in this case, this is certainly doable 😄 you should be able to directly set the field with tags = local.standard_tags
OR if you intend to set the tags attribute with a list of key/value pairs, a for loop can work as well by doing something like:
locals {
standard_tags = [
{
name = "a"
number = 1
},
{
name = "b"
number = 2
},
{
name = "c"
number = 3
},
]
}
resource "aws_instance" "test" {
...
tags = {
for tag in local.standard_tags:
tag.name => tag.number
}
}

Terraform - Use data source output in variable default

I am creating a module to spin up a basic web server.
I am trying to get it so that if the user does not specify an AMI then the ubuntu image for that region is used.
I have a data block to get the AMI ID of the ubuntu 16.04 image for that region but I cannot set this as the default for a variable as interpolation does not work.
My module is as follows:-
main.tf
resource "aws_instance" "web" {
ami = "${var.aws_ami}"
instance_type = "${var.instance_type}"
security_groups = ["${aws_security_groups.web.id}"]
tags {
Name = "WEB_SERVER"
}
}
resource "aws_security_groups" "web" {
name = "WEB_SERVER-HTTP-HTTPS-SG"
ingress {
from_port = "${var.http_port}"
to_port = "${var.http_port}"
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = "${var.https_port}"
to_port = "${var.https_port}"
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"]
}
}
variables.tf
variable "instance_type" {
description = "The instance size to deploy. Defaults to t2.micro"
default = "t2.micro"
}
variable "http_port" {
description = "The port to use for HTTP traffic. Defaults to 80"
default = "80"
}
variable "https_port" {
description = "The port to use for HTTPS traffic. Defaults to 443"
default = "443"
}
data "aws_ami" "ubuntu" {
filter {
name = "state"
values = ["available"]
}
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-xenial-16.04-amd64-server-*"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
owners = ["099720109477"]
}
locals {
default_ami = "${data.aws_ami.ubuntu.id}"
}
variable aws_ami {
description = "The AMI used to launch the instance. Defaults to Ubuntu 16.04"
default = "${local.default_ami}"
}
Try using a ternary operator interpolation:
variable "user_specified_ami" {
default = "ami-12345678"
}
resource "aws_instance" "web" {
ami = "${var.user_specified_ami == "" ? data.aws_ami.ubuntu.id : var.user_specified_ami}"
# ... other params omitted ....
}
Set user_specified_ami's default to something to use that AMI. Set it to blank to use the AMI ID Terraform gets from the AWS provider.
i.e. if user_specified_ami is anything other blank (""), then it will be chosen for the AMI, else the AMI Terraform gets the one from AWS.
BTW, maybe you want to use the most_recent = true param in the data "aws_ami" resource?
A similar solution to the other answers, but using the coalesce function:
variable "user_specified_ami" {
default = ""
}
resource "aws_instance" "web" {
ami = coalesce(var.user_specified_ami, data.aws_ami.ubuntu.id)
}
KJH's answer works great, but it felt a bit messy to me to have that logic inline, so I made an abstraction using null_data_source. Here's what that would look like:
variable "ami" {
default = ""
}
data "null_data_source" "data_variables" {
inputs = {
ami = "${var.ami == "" ? data.aws_ami.ubuntu.id : var.ami}"
}
}
resource "aws_instance" "web" {
ami = "${data.null_data_source.data_variables.outputs["ami"]}"
# ... other params omitted ....
}

Resources