2
votes

I'm writing a lambda to update an RDS instance. Apparently the RDS instance must be in a VPC, and the lambda must be in the VPC so it can access the RDS. Alright then. So I configure the lambda to be in the VPC, and then it doesn't have permissions to open network interfaces on the EC2, so I have to give the lambda a role which has those permissions.

Here's my serverless.yml:

service: users-to-rds

plugins:
  - serverless-plugin-typescript

provider:
  name: aws
  runtime: nodejs6.10
  stage: dev
  timeout: 30
  region: ap-southeast-2
  iamRoleStatements:
    - Effect: Allow
      Action:
        - "S3:*"
      Resource: arn:aws:s3:::*
    - Effect: Allow
      Action:
        - "rds:*"
      Resource: "*"
  vpc:
    securityGroupIds:
      - sg-stuff
    subnetIds:
      - subnet-stuff
      - subnet-stuff
      - subnet-stuff

functions:
  writeToDB:
    handler: write-users-to-rds.writeToDB
    events:
      - sns: userlists
    role:
      Fn::GetAtt: [ "doStuffFromInsideVPC", "Arn" ]

Resources:
  SNSInvokeLambdaPermission:
    Type: "AWS::Lambda::Permission"
    Properties:
      Action: "lambda:InvokeFunction"
      Principal: "sns.amazonaws.com"
      SourceArn:
        Ref: UserListNotification
      FunctionName:
        Fn::GetAtt: [ "UserListToRDS", "Arn" ]
  doStuffFromInsideVPC:
    Type: "AWS::IAM::Role"
    Properties:
      AssumeRolePolicyDocument:
        Version: '2017'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: doStuffFromInsideVPCPolicy
          PolicyDocument:
            Version: '2017'
            Statement:
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource:
                  - 'Fn::Join':
                    - ':'
                    - - 'arn:aws:logs'
                      - Ref: 'AWS::Region'
                      - Ref: 'AWS::AccountId'
                      - 'log-group:/aws/lambda/*:*:*'
              - Effect: Allow
                Action:
                  - ec2:CreateNetworkInterface
                  - ec2:DescribeNetworkInterfaces
                  - ec2:DetachNetworkInterface
                  - ec2:DeleteNetworkInterface
                Resource: "*"

When I do serverless deploy it makes the zip which gets uploaded to AWS and then I am told:

The CloudFormation template is invalid: Template error: instance of Fn::GetAtt references undefined resource doStuffFromInsideVPC

which is of course complete nonsense. There it is right there. Apparently there used to be a bug where CloudFormation hadn't created the role resource in time, so serverless was updated to add a DependsOn which causes it to wait until the role exists. And looking at .serverless/cloudformation-template-update-stack.json it seems to have a DependsOn where it needs it.

{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Description": "The AWS CloudFormation template for this Serverless application",
  "Resources": {
    "ServerlessDeploymentBucket": {
      "Type": "AWS::S3::Bucket",
      "Properties": {
        "AccelerateConfiguration": {
          "AccelerationStatus": "Suspended"
        }
      }
    },
    "WriteToDBLogGroup": {
      "Type": "AWS::Logs::LogGroup",
      "Properties": {
        "LogGroupName": "/aws/lambda/users-to-rds-dev-writeToDB"
      }
    },
    "WriteToDBLambdaFunction": {
      "Type": "AWS::Lambda::Function",
      "Properties": {
        "Code": {
          "S3Bucket": {
            "Ref": "ServerlessDeploymentBucket"
          },
          "S3Key": "serverless/users-to-rds/dev/1515307801502-2018-01-07T06:50:01.502Z/users-to-rds.zip"
        },
        "FunctionName": "users-to-rds-dev-writeToDB",
        "Handler": "write-users-to-rds.writeToDB",
        "MemorySize": 1024,
        "Role": {
          "Fn::GetAtt": [
            "doStuffFromInsideVPC",
            "Arn"
          ]
        },
        "Runtime": "nodejs6.10",
        "Timeout": 30,
        "VpcConfig": {
          "SecurityGroupIds": [
            "sg-stuff"
          ],
          "SubnetIds": [
            "subnet-stuff",
            "subnet-stuff",
            "subnet-stuff"
          ]
        }
      },
      "DependsOn": [
        "WriteToDBLogGroup",
        "doStuffFromInsideVPC"
      ]
    },
    "WriteToDBLambdaVersionAOwtYsOPCw4hVAkxPXSzE66bZXvdbu2xQUzGAe58E": {
      "Type": "AWS::Lambda::Version",
      "DeletionPolicy": "Retain",
      "Properties": {
        "FunctionName": {
          "Ref": "WriteToDBLambdaFunction"
        },
        "CodeSha256": "7gOzejhkEn37BviNCIVy1lw32IwPs9/oyeaPBQjlqVA="
      }
    },
    "SNSTopicUserlists": {
      "Type": "AWS::SNS::Topic",
      "Properties": {
        "TopicName": "userlists",
        "DisplayName": "",
        "Subscription": [
          {
            "Endpoint": {
              "Fn::GetAtt": [
                "WriteToDBLambdaFunction",
                "Arn"
              ]
            },
            "Protocol": "lambda"
          }
        ]
      }
    },
    "WriteToDBLambdaPermissionUserlistsSNS": {
      "Type": "AWS::Lambda::Permission",
      "Properties": {
        "FunctionName": {
          "Fn::GetAtt": [
            "WriteToDBLambdaFunction",
            "Arn"
          ]
        },
        "Action": "lambda:InvokeFunction",
        "Principal": "sns.amazonaws.com",
        "SourceArn": {
          "Fn::Join": [
            "",
            [
              "arn:aws:sns:",
              {
                "Ref": "AWS::Region"
              },
              ":",
              {
                "Ref": "AWS::AccountId"
              },
              ":",
              "userlists"
            ]
          ]
        }
      }
    }
  },
  "Outputs": {
    "ServerlessDeploymentBucketName": {
      "Value": {
        "Ref": "ServerlessDeploymentBucket"
      }
    },
    "WriteToDBLambdaFunctionQualifiedArn": {
      "Description": "Current Lambda function version",
      "Value": {
        "Ref": "WriteToDBLambdaVersionAOwtYsOPCw4hVAkxPXSzE66bZXvdbu2xQUzGAe58E"
      }
    }
  }
}

Any ideas, anyone? Thank you.

1

1 Answers

3
votes

According to the serverless.yaml reference, resources should be defined in the following way:

resources:
  Resources:
    YourResource:
    ...

Try this one in your case:

resources:
  Resources:
    SNSInvokeLambdaPermission:
      Type: "AWS::Lambda::Permission"
      ...