23

Question

If there a way to get the assigned IP address of an aws_lb resource at the time aws_lb is created by Terraform?

As in AWS documentation - NLB - To find the private IP addresses to whitelist, we can find out the IP address associated to ELB.

  1. Open the Amazon EC2 console at https://console.aws.amazon.com/ec2/.
  2. In the navigation pane, choose Network Interfaces.
  3. In the search field, type the name of your Network Load Balancer. There is one network interface per load balancer subnet.
  4. On the Details tab for each network interface, copy the address from Primary private IPv4 IP.

Background

To be able to setup security group to white list the ELB IP address as Network Load Balancer cannot not have Security Group as in Network Load Balancers don't have Security Groups.

Considered aws_network_interface but it does not work with an error.

Error: no matching network interface found

Also I think datasource assumes the resource already exists and cannot be used for the resource to be created by Terraform.

mon
  • 18,789
  • 22
  • 112
  • 205

4 Answers4

25

More elegent solution using only HCL in Terraform :

data "aws_network_interface" "lb" {
  for_each = var.subnets

  filter {
    name   = "description"
    values = ["ELB ${aws_lb.example_lb.arn_suffix}"]
  }

  filter {
    name   = "subnet-id"
    values = [each.value]
  }
}

resource "aws_security_group" "lb_sg" {
  vpc_id = var.vpc_id

  ingress {
    from_port   = 0
    to_port     = 0
    protocol    = "tcp"
    cidr_blocks = formatlist("%s/32", [for eni in data.aws_network_interface.lb : eni.private_ip])
    description = "Allow connection from NLB"
  }
}

Source : https://github.com/terraform-providers/terraform-provider-aws/issues/3007

Hope this helps.

user1297406
  • 1,241
  • 1
  • 18
  • 36
9

The solution from @user1297406 leads to an exeption. data.aws_network_interface.lb is tuple with 2 elements. Correct syntax is:

data "aws_network_interface" "lb" {
count = length(var.vpc_private_subnets)

  filter {
    name   = "description"
    values = ["ELB ${aws_alb.lb.arn_suffix}"]
  }
  filter {
    name   = "subnet-id"
    values = [var.vpc_private_subnets[count.index]]
  }
}


resource "aws_security_group_rule" "lb_sg" {
  from_port         = 0
  protocol          = "TCP"
  to_port           = 0
  type              = "ingress"
  cidr_blocks = formatlist("%s/32", data.aws_network_interface.lb.*.private_ip)
}
awiechert
  • 370
  • 3
  • 15
6

Using external provider

Get the NLB IP using Python/boto3 invoking from external provider.

nlb_private_ips.tf

variable "nlb_name" {
}
variable "vpc_id" {
}
variable "region" {
}

data "external" "get_nlb_ips" {
  program = ["python", "${path.module}/get_nlb_private_ips.py"]
  query = {
    aws_nlb_name  = "${var.nlb_name}"
    aws_vpc_id    = "${var.vpc_id}"
    aws_region    = "${var.region}"
  }
}

output "aws_nlb_ip_decoded" {
  value = "${jsondecode(data.external.get_nlb_ips.result.private_ips)}"
}

output "aws_nlb_ip_encoded" {
  value = "${data.external.get_nlb_ips.result.private_ips}"
}

get_nlb_private_ips.py

import boto3
import json
import sys


def json_serial(obj):
    """JSON serializer for objects not serializable by default json code
        Args:
            obj: object to serialize into JSON
    """
    _serialize = {
        "int": lambda o: int(o),
        "float": lambda o: float(o),
        "decimal": lambda o: float(o) if o % 1 > 0 else int(o),
        "date": lambda o: o.isoformat(),
        "datetime": lambda o: o.isoformat(),
        "str": lambda o: o,
    }
    return _serialize[type(obj).__name__.lower()](obj)


def pretty_json(dict):
    """
    Pretty print Python dictionary
    Args:
        dict: Python dictionary
    Returns:
        Pretty JSON
    """
    return json.dumps(dict, indent=2, default=json_serial, sort_keys=True, )


def get_nlb_private_ips(data):
    ec2 = boto3.client('ec2', region_name=data['aws_region'])
    response = ec2.describe_network_interfaces(
        Filters=[
            {
                'Name': 'description',
                'Values': [
                    "ELB net/{AWS_NLB_NAME}/*".format(
                        AWS_NLB_NAME=data['aws_nlb_name'])
                ]
            },
            {
                'Name': 'vpc-id',
                'Values': [
                    data['aws_vpc_id']
                ]
            },
            {
                'Name': 'status',
                'Values': [
                    "in-use"
                ]
            },
            {
                'Name': 'attachment.status',
                'Values': [
                    "attached"
                ]
            }
        ]
    )

    # print(pretty_json(response))
    interfaces = response['NetworkInterfaces']

    # ifs = list(map(lamba index: interfaces[index]['PrivateIpAddresses'], xrange(len(interfaces))))
    # --------------------------------------------------------------------------------
    # Private IP addresses associated to an interface (ENI)
    # Each association has the format:
    #   {
    #     "Association": {
    #       "IpOwnerId": "693054447076",
    #       "PublicDnsName": "ec2-52-88-47-177.us-west-2.compute.amazonaws.com",
    #       "PublicIp": "52.88.47.177"
    #     },
    #     "Primary": true,
    #     "PrivateDnsName": "ip-10-5-1-205.us-west-2.compute.internal",
    #     "PrivateIpAddress": "10.5.1.205"
    #   },
    # --------------------------------------------------------------------------------
    associations = [
        association for interface in interfaces
        for association in interface['PrivateIpAddresses']
    ]

    # --------------------------------------------------------------------------------
    # Get IP from each IP association
    # --------------------------------------------------------------------------------
    private_ips = [
        association['PrivateIpAddress'] for association in associations
    ]

    return private_ips


def load_json():
    data = json.load(sys.stdin)
    return data


def main():
    data = load_json()
    """
    print(data['aws_region'])
    print(data['aws_vpc_id'])
    print(data['aws_nlb_name'])
    """
    ips = get_nlb_private_ips(data)
    print(json.dumps({"private_ips": json.dumps(ips)}))


if __name__ == '__main__':
    main()

Using aws_network_interfaces datasource

After aws_lb has been created.

data "aws_network_interfaces" "this" {
  filter {
    name = "description"
    values = ["ELB net/${aws_lb.this.name}/*"]
  }
  filter {
    name = "vpc-id"
    values = ["${var.vpc_id}"]
  }
  filter {
    name = "status"
    values = ["in-use"]
  }
  filter {
    name = "attachment.status"
    values = ["attached"]
  }
}

locals {
  nlb_interface_ids = "${flatten(["${data.aws_network_interfaces.this.ids}"])}"
}

data "aws_network_interface" "ifs" {
  count = "${length(local.nlb_interface_ids)}"
  id = "${local.nlb_interface_ids[count.index]}"
}

output "aws_lb_network_interface_ips" {
  value = "${flatten([data.aws_network_interface.ifs.*.private_ips])}"
}
mon
  • 18,789
  • 22
  • 112
  • 205
  • 2
    Just checking but that does not work if the load balancer is being created at the same time those data sources are, right? – Cícero Neves Feb 08 '21 at 23:55
1

i would suggest to use the data "dns_a_record_set" to get the IPs:


data "dns_a_record_set" "nlb_ips" {
  host = aws_lb.<your_alb>.dns_name
}

You can find the documentation under https://registry.terraform.io/providers/hashicorp/dns/latest/docs/data-sources/dns_a_record_set