Advertisements
RSS

How to configure AWS Lambda functions to use a outbound fixed ip

For our Serverless project running on AWS infrastructure we needed an outbound Lambda API call to a SaaS platform which demands a whitelist of the source IP addresses. Which is pretty hard since AWS has a whole range. Luckily there is a trick to target your Lambda function in a VPC which can be configured to use a elastic IP for outbound communication.

High Level Design

Steps to configure the VPC / Network

Step 1 – create/use a VPC

Our AWS Network configuration always start with an AWS VPC (Virtual Private Cloud). You can use an existing VPC and configure your subnets there, or create a new one. Network configuration on AWS can look simple with the GUI wizard, but the setup of your VPC had major impact on all your resources so for real production a thing to remember. In the example I use CIDR block 10.0.0.0/16 which gives me (way to much) options.

Step 2 – create a public and 1-n private subnets

We will need 1 public subnet to attach to a NAT Gateway and route our traffic to the Internet. The example can work with 1 private subnet which hosts your Lambda function. However for availability purposes you would want to have multiple private subnets in different availability zones for your lambda to run. In the example I use CIDR block 10.0.11.0/24 for the public subnet and 10.0.21.0/24, 10.0.22.0/24 and 10.0.23.0/24 for the 3 private subnets on each eu-west-1 availability zone.

Step 3 – Configure the Internet Gateway and the public subnet configuration

We need an Internet Gateway for our VPC, attach to our public subnet and a public route table configuration which targets all (0.0.0.0/0) outbound traffic from the public subnet.

Step 4 – Configure the NAT Gateway and the private subnet configuration

We need a NAT Gateway which uses an elastic IP address. By adding a private route table which we attach to our private subnet(s) we make sure that all functions in the VPC will use our elastic IP for outbound communication. At lease, if we make sure the private route table contains a (0.0.0.0/0) target to the NAT Gateway

Configure your Lambda function

Make sure your Lambda function(s) use the configured VPC by selecting it in the VPC pulldown and then select all the private subnet(s).

Notes:

  • Make sure your Lambda runs with the IAM AWSLambdaVPCAccessExecutionRole

CloudFormation source code

Template can be found in my aws-cloudformation Git repository as well:

Resources:
  ######################
  ## VPC basics
  ######################
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
      - Key: Name
        Value: t10-fn-vpc-dev
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
      - Key: Name
        Value: t10-fn-internetgateway-dev
  InternetGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId:
        Ref: InternetGateway
      VpcId:
        Ref: VPC

  ######################
  ## Subnet Public
  ######################
  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId:
        Ref: VPC
      AvailabilityZone: eu-west-2a
      CidrBlock: 10.0.11.0/24
      MapPublicIpOnLaunch: true
      Tags:
      - Key: Name
        Value: t10-fn-public-subnet-az1-dev
  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId:
        Ref: VPC
      Tags:
      - Key: Name
        Value: t10-fn-public-rt-dev
  PublicRouteTableRoute1:
    Type: AWS::EC2::Route
    DependsOn: InternetGateway
    Properties:
      RouteTableId:
        Ref: PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId:
        Ref: InternetGateway
  PublicRouteTableAssociation1:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId:
        Ref: PublicSubnet1
      RouteTableId:
        Ref: PublicRouteTable
  PublicElasticIP:
    Type: AWS::EC2::EIP
    Properties:
      Domain:
        Ref: VPC
  NatGateway:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId:
        Fn::GetAtt: PublicElasticIP.AllocationId
      SubnetId:
        Ref: PublicSubnet1
      Tags:
      - Key: Name
        Value: t10-fn-natgateway-dev

  ######################
  ## Subnet Private
  ######################
  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId:
        Ref: VPC
      AvailabilityZone: eu-west-2a
      CidrBlock: 10.0.21.0/24
      MapPublicIpOnLaunch: false
      Tags:
      - Key: Name
        Value: t10-fn-private-subnet-az1-dev
  PrivateSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId:
        Ref: VPC
      AvailabilityZone: eu-west-2b
      CidrBlock: 10.0.22.0/24
      MapPublicIpOnLaunch: false
      Tags:
      - Key: Name
        Value: t10-fn-private-subnet-az2-dev
  PrivateSubnet3:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId:
        Ref: VPC
      AvailabilityZone: eu-west-2c
      CidrBlock: 10.0.23.0/24
      MapPublicIpOnLaunch: false
      Tags:
      - Key: Name
        Value: t10-fn-private-subnet-az3-dev
  PrivateRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId:
        Ref: VPC
      Tags:
      - Key: Name
        Value: t10-fn-private-rt-dev
  PrivateRouteTableRoute1:
    Type: AWS::EC2::Route
    DependsOn: NatGateway
    Properties:
      RouteTableId:
        Ref: PrivateRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId:
        Ref: NatGateway
  PrivateRouteTableAssociation1:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId:
        Ref: PrivateSubnet1
      RouteTableId:
        Ref: PrivateRouteTable
  PrivateRouteTableAssociation2:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId:
        Ref: PrivateSubnet2
      RouteTableId:
        Ref: PrivateRouteTable
  PrivateRouteTableAssociation3:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId:
        Ref: PrivateSubnet3
      RouteTableId:
        Ref: PrivateRouteTable

  ######################
  ## Security NACL
  ######################
  NetworkAcl:
    Type: AWS::EC2::NetworkAcl
    Properties:
      VpcId:
        Ref: VPC
      Tags:
      - Key: Name
        Value: t10-fn-nacl-dev
  NetworkAclEntryfn100:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      CidrBlock: 0.0.0.0/0
      Egress: 'false'
      NetworkAclId:
        Ref: NetworkAcl
      Protocol: "-1"
      RuleAction: allow
      RuleNumber: "100"
  NetworkAclEntryOutbound100:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      CidrBlock: 0.0.0.0/0
      Egress: 'true'
      NetworkAclId:
        Ref: NetworkAcl
      Protocol: "-1"
      RuleAction: allow
      RuleNumber: "100"
  PrivateSubnetNetworkAclAssociation1:
    Type: AWS::EC2::SubnetNetworkAclAssociation
    Properties:
      SubnetId:
        Ref: PrivateSubnet1
      NetworkAclId:
        Ref: NetworkAcl
  PrivateSubnetNetworkAclAssociation2:
    Type: AWS::EC2::SubnetNetworkAclAssociation
    Properties:
      SubnetId:
        Ref: PrivateSubnet2
      NetworkAclId:
        Ref: NetworkAcl
  PrivateSubnetNetworkAclAssociation3:
    Type: AWS::EC2::SubnetNetworkAclAssociation
    Properties:
      SubnetId:
        Ref: PrivateSubnet3
      NetworkAclId:
        Ref: NetworkAcl

  ######################
  ## Security Group(s)
  ######################
  LambdaSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: t10-fn-lambda-sg
      GroupDescription: t10-fn-lambda-sg
      SecurityGroupIngress:
      - IpProtocol: -1
        CidrIp: 0.0.0.0/0
      VpcId:
        Ref: VPC
      Tags:
      - Key: Name
        Value: t10-fn-lambda-sg-dev

  ######################
  ## OUTPUT
  ######################

  #Outputs:
  #  VPC:
  #    Description: A reference to the created VPC
  #    Value:
  #      Ref: VPC
  #    Export:
  #      Name: t10-fn-vpc-id$

References

Advertisements
 
Leave a comment

Posted by on 01-10-2018 in AWS

 

Tags: , , , , ,

How to fix Git error ‘invalid active developer path’ after MacOS update

After upgrading to macOS Mojave,  I tried running GIT from Terminal or IDE but it kept giving the following error:

xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools), missing xcrun at: /Library/Developer/CommandLineTools/usr/bin/xcrun.

The problem is that you are using XCode and you explicit have to agree to the license agreement. So open Terminal, and run the following:

xcode-select --install

This will download and install xcode developer tools and fix the problem. As a follow on step, you may need to reset the path to Xcode if you have several versions or want the command line tools to run without Xcode.

xcode-select --switch /Applications/Xcode.app
xcode-select --switch /Library/Developer/CommandLineTools

Reference

 
Leave a comment

Posted by on 27-09-2018 in Uncategorized

 

Tags: , ,

How to use multiple resources definitions in the Serverless Framework

In other Serverless projects we use a single resource definition to include AWS CloudFormation scripts in our build/deploy. Where the resource file contained component definitions like DDBTable: in the root.

resources:
  Resources: ${file(resources.yml)}

However a new project contains a lot of AWS configurations so I decided to split it up. You can do that using the next syntax. However make sure your files start with the Resources: data type.

resources:
  - ${file(resources/resources-vpc.yml)}
  - ${file(resources/resources-db.yml)}

You can even mix and match inline and external resources

resources:
  - Resource:
      ApiGatewayRestApi:
        Type: AWS::ApiGateway::RestApi
  - ${file(resources/resources-vpc.yml)}
  - ${file(resources/resources-db.yml)}

Reference(s)

 
Leave a comment

Posted by on 07-09-2018 in Serverless

 

Tags: , , ,

Example AWS CloudFormation template for network load balancer


We needed a public network load balancer with SSL (through AWS Certificate Manager) and took me some retry’s to get it right since most examples are based upon the classic or application load balancer so here to share:

  Terra10NetworkLoadBalancer:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Name: t10-networkloadbalancer
      Scheme: internet-facing
      Subnets: !Ref Terra10Subnet
      Type: network
      Tags:
        - Key: Name
          Value: t10-networklb
  Terra10NetworkLoadBalancerTargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Name: t10-networklb-target
      Port: 443
      Protocol: TCP
      VpcId: !ImportValue t10-vpc-id
      TargetGroupAttributes:
        - Key: deregistration_delay.timeout_seconds
          Value: 60
      Targets:
      - Id: !Ref Terra10EC2Instance1
        Port: 443
      - Id: !Ref Terra10EC2Instance2
        Port: 443  
      Tags:
        - Key: Name
          Value: t10-networklb-target
  Terra10NetworkLoadBalancerListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
      - Type: forward
        TargetGroupArn: !Ref Terra10NetworkLoadBalancerTargetGroup
      LoadBalancerArn: !Ref Terra10NetworkLoadBalancer
      Port: '443'
      Protocol: TCP
  Terra10NetworkLoadBalancerListenerCert:
    Type: AWS::ElasticLoadBalancingV2::ListenerCertificate
    Properties:
      Certificates:
        - CertificateArn: arn:aws:acm:eu-west-1:xxxaccountxxx:certificate/123456....
      ListenerArn: !Ref Terra10NetworkLoadBalancerListener

 

Reference

 
Leave a comment

Posted by on 29-08-2018 in AWS, CloudFormation

 

Tags: ,

How to share values between seperated AWS CloudFormation stacks

In AWS CloudFormation templates you often have the need to make a reference to an earlier created component. For instance the unique ID of a vpc, subnet, security group or instance. You have two choices: continue with separated stacks or combine them to create a nested stack. In our case we wanted to keep seperated stacks so needed a way to export/import settings from an earlier network related CloudFormation stack holding our VPC and Subnet Identifier which the new stack uses. To share information between stacks we can export output values from the first stack and import these values in new stacks. Other stacks that are in the same AWS account and region can import the exported values.

Example of an VPC ID which we export in our network stack (named t10-cf-vpc):

######################
## OUTPUT
######################
Outputs:
    VPC:
        Description: A reference to the created VPC
        Value: !Ref VPC
        Export:
          Name: t10-vpc-id

You can easily check if the export succeeded by using the AWS CloudFormation GUI

or using the AWS CLI to get a list

 
jvzoggel$ aws cloudformation list-exports
{
"Exports": [
{
"ExportingStackId": "arn:aws:cloudformation:xxxxx:xxxxxxx:stack/t10-cf-vpc/xxxxxxxx",
"Name": "t10-vpc-id",
"Value": "vpc-xxxxxxx"
},
.....

Importing the values in new stacks

The new CloudFormation stack can make use of the exported value simple by using the !ImportValue function

Parameters:
  NetworkStackNameParameter:
    Description: Reference to the vpc-10 stack
    Type: String
    Default: 't10-cf-vpc'

Resources:
  MyTerra10SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: MyTerra10SecurityGroup
      GroupDescription: MyTerra10SecurityGroup
      VpcId: !ImportValue t10-vpc-id

Note: After another stack imports an output value, you can’t delete the stack that is exporting the output value or modify the exported output value.

References:

 
Leave a comment

Posted by on 28-08-2018 in AWS

 

Tags: , ,

Using CloudFormation to bootstrap EC2 instances with scripts from CodeCommit

While spinning up EC2 instances you can bootstrap them with packages, files, etc in different ways. For our stack we wanted to pull scripts from an AWS CodeCommit to make life easier.

The (bash) scripts are stored in our CodeCommit so first we need to make sure the EC2 instances, while spinning up, are allowed to access the repository. So we created an IAM Policy with these sufficient rights and attach the policy to a IAM role which we can use to attach to our EC2 instances.

AWS IAM Policy:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "codecommit:GitPull"
            ],
            "Resource": "arn:aws:codecommit:*:*:terra10-scripts"
        },
        {
            "Effect": "Allow",
            "Action": [
                "codecommit:Get*",
                "codecommit:BatchGetRepositories",
                "codecommit:List*"
            ],
            "Resource": "*"
        }
    ]
}

We make sure the EC2 instances uses the new IAM Role by defining IamInstanceProfile with our example IAM Role t10-ec2-role in the CloudFormation template. Further on by using the UserData segment we can execute scripts during bootstrap of the server. Install the AWSCLI is required for the credential helper

T10Controller1:
  Type: AWS::EC2::Instance
  Properties:
    ImageId: !Ref HostAMI
    InstanceType: t2.micro
    IamInstanceProfile: t10-ec2-role
    PrivateIpAddress: 10.0.11.11   
    Tags:
      - Key: Name
        Value: t10-k8s-controller1
    UserData:
      Fn::Base64: !Sub |
        #!bin/bash -xe
        apt-get update
        apt-get -y install awscli
        cd /tmp
        echo "######## git pull AWS CodeCommit files"
        sudo git config --global credential.helper '!aws codecommit credential-helper $@'
        sudo git config --global credential.UseHttpPath true
        sudo git clone https://git-codecommit.xxxxxx.amazonaws.com/v1/repos/terra10-scripts /tmp/terra10-scripts

 

 
Leave a comment

Posted by on 26-08-2018 in AWS

 

Tags: , , , ,

AWS CloudFormation error “The parameter groupName cannot be used with the parameter subnet”

When trying to start some EC2 instance through CloudFormation I kept getting the error “The parameter groupName cannot be used with the parameter subnet”.  

The (YAML) AWS CloudFormation looks something like this:

Resources:
  KubernetesControllerSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: t10-sg-k8s-controller
      GroupDescription: t10-sg-k8s-controller
      ......
      Tags:
        - Key: Name
          Value: t10-sg-k8s-controller
  EC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: ami-20ee5e5d
      InstanceType: t2.micro
      KeyName: t10_kubernetes
      PrivateIpAddress: 10.0.11.11
      SubnetId:
        Fn::ImportValue:
          !Sub "t10-vpc-k8s-subnet1-id"
      SecurityGroupIds: - !Ref KubernetesControllerSecurityGroup
      Tags:
        - Key: Name
          Value: t10-k8s-controller1

So the error ended in a Google search with many hits, many questions, many suggestions, but very few real answers.

Until I saw this answer from johnhunsley:
I believe you have created a Security Group without specifying a VPC ID. You have then attempted to create a launch config which launches instances into a subnet within a VPC. Therefore, when It attempts to assign the security group to those instances it fails because it expects the security group ID rather than the name.

So I think the response from AWS is in the running for the “Worst Error Message Ever” but the solution is very simple. Don’t make the mistake of not specifying your custom VPC ID when creating a new security group.

Resources:
  KubernetesControllerSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: t10-sg-k8s-controller
      GroupDescription: t10-sg-k8s-controller
      ......
      VpcId: !ImportValue t10-vpc-id
      Tags:
        - Key: Name
          Value: t10-sg-k8s-controller

References

johnhunsley @ https://github.com/boto/boto/issues/350

 
Leave a comment

Posted by on 15-08-2018 in AWS, CloudFormation

 

Tags: , , ,