Use case:
My Target Groups attached to my Load Balancer route to a private internal port that is not on the security group white listing. Due to Network Load Balancer's use of the EC2 connected Security Group. I don't want to whitelist the private internal port to the internet for possible bypass.
This can be done manually in the aws console by finding the ENI by looking up the ELB (type)/(name)/(random string) getting the private ip4 and then adding them manually to the security group.
But this defeats the purpose of having a cloudformation script that automatically stands up everything to work.
in this example we want to get the private ip4 address of a ELBv2 (network). Full working example: https://github.com/qld-gov-au/quickstart-atlassian-bitbucket/blob/d6ebe59b5ccdd204a7edc72ab6f0f89d575ac6f8/templates/quickstart-bitbucket-dc.template.yaml
(non-gist version below)
#Network Load Balancer health checks, need internal ip to approve connectivity
InternalNLBIp4List:
DependsOn: NetworkLoadBalancerELB2
Type: Custom::InternalNLBIp4ListCollector
Version: 1.0
Properties:
ServiceToken: !GetAtt InternalNLBIp4ListCollector.Arn
ELBv2Arn: !Ref NetworkLoadBalancerELB2
StackName: !Ref 'AWS::StackName'
InternalNLBIp4ListCollector:
Type: "AWS::Lambda::Function"
Properties:
Handler: index.lambda_handler
Role: !GetAtt InternalNLBIp4ListCollectorExecutionRole.Arn
Runtime: python3.7
Timeout: 120
Code:
ZipFile: |
import cfnresponse
import boto3
def lambda_handler(event, context):
elbv2 = boto3.client('elbv2')
ec2 = boto3.client('ec2')
elb2arn = event['ResourceProperties']['ELBv2Arn']
response = elbv2.describe_load_balancers(LoadBalancerArns=[elb2arn])
name = response['LoadBalancers'][0]['LoadBalancerName']
elbtype = response['LoadBalancers'][0]['Type']
filters = [{'Name': 'description', 'Values': ['ELB '+ elbtype[0:3] + '/' + name + '*']}]
eni_response = ec2.describe_network_interfaces(Filters=filters)
ip_addresses = [eni['PrivateIpAddress'] for eni in eni_response['NetworkInterfaces']]
ip_addresses_cidr = [eni['PrivateIpAddress'] + '/32' for eni in eni_response['NetworkInterfaces']]
print (ip_addresses)
responseData = {}
responseData['PrivateIpAddresses'] = ip_addresses
responseData['PrivateIpCidrAddresses'] = ip_addresses_cidr
cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData)
InternalNLBIp4ListCollectorExecutionRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
Path: "/"
Policies:
- PolicyName: root
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: !Sub "arn:${AWS::Partition}:logs:*:${AWS::AccountId}:log-group:/aws/lambda/*InternalNLBIp4ListCollector*"
- Effect: Allow
Action:
- "elasticloadbalancing:DescribeLoadBalancers"
- "ec2:DescribeNetworkInterfaces"
Resource: "*"
#NLB ip's need to be whitelisted to allow health checks to pass
SecurityGroupIngressNLB:
DependsOn:
- InternalNLBIp4List
- SecurityGroup
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: !Ref SecurityGroup
IpProtocol: "-1"
FromPort: -1
ToPort: -1
CidrIp: !Select [ 0, !GetAtt InternalNLBIp4List.PrivateIpCidrAddresses ]
SecurityGroupIngressNLB2: #ELB in 2 subnets, will have 2 ip's
DependsOn:
- InternalNLBIp4List
- SecurityGroup
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: !Ref SecurityGroup
IpProtocol: "-1"
FromPort: -1
ToPort: -1
CidrIp: !Select [ 1, !GetAtt InternalNLBIp4List.PrivateIpCidrAddresses ]