AWS Serverless
Introduction
This is an example of how to set up a serverless framework function within AWS
Setup
Within AWS there are example templates for each piece of the infrastructure. For this we need to create a
- S3 Bucket to hold the code
- IAM::Role to describe the permissions
- Lambda The function to run
- Bucket Policy This grants access from the bucket to the lambda function
Templates
Here are the templates I used. I have highlighted where they differ from the provided examples
Stack cowsay-bucket
S3 Bucket
Example can be found here
This needs to be in its own stack and create prior to the other resources.
{
"Resources": {
"cowsayBucket": {
"Type": "AWS::S3::Bucket",
"Properties": {
"BucketName": "cowsayBucket"
}
}
}
}
Stack cowsay-lambda
This stack or group of resources holds the lamda, bucket policy and role. The lambda function references the bucket created above.
IAM::Role
Example can be found here
{
"Resources": {
"cowsayIamRole": {
"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:*"
],
"Resource": "arn:aws:logs:*:*:*"
}
]
}
}
]
}
}
}
}
Lambda Function
Example can be found here
{
"Resources": {
"cowsayLambda": {
"Type": "AWS::Lambda::Function",
"Properties": {
"Code": {
"S3Bucket": "cowsaybucket",
"S3Key": "cowsay.zip"
},
"Handler": "index.handler",
"Runtime": "nodejs14.x",
"Role": {
"Fn::GetAtt": [
"cowsayIamRole",
"Arn"
]
}
}
}
}
}
Adding the Role to the Lambda
Now we have the base templates we need to add properties to allow them to interact
{
"Resources": {
"cowsayBucket": {
"Type": "AWS::S3::Bucket",
"Properties": {
"BucketName": "cowsayBucket"
}
},
"Role": {
"Ref": "cowsayIamRole"
}
}
}
Bucket Policy
We need to allow lambda function access to the bucket. We do this with a the bucket
{
"Resources": {
"cowsayBucketPolicy": {
"Type": "AWS::S3::BucketPolicy",
"Properties": {
"Bucket": "cowsaybucket",
"PolicyDocument": {
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "serverlessrepo.amazonaws.com"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::cowsaybucket/*",
"Condition": {
"StringEquals": {
"aws:SourceArn": {
"Fn::GetAtt": [
"cowsayLambda",
"Arn"
]
}
}
}
}
]
}
}
}
}
}
The Code
Do do this we
- Create a node js package
- Create a function
- Create a template to run it locally
Create a node js package
Just a straight forward package
{
"name": "cowsay",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"cowsay": "^1.4.0"
}
}
Create a function
const cowsay = require('cowsay');
exports.handler = function(event, context) {
// Get a cow from cowsay!
let cow = cowsay.say({
text: "I'm a lambda moooodule",
e: "oO",
T: "U "
});
// Replace line breaks to HTML line breaks
cow = cow.replace(/[\n|\r|\n\r]/g, '<br />');
// Create the HTML
var html = '<html><head><title>Cowsay Lambda!</title></head>' +
`<body><h1>Welcome to Cowsay Lambda!</h1>${cow}</body></html>`;
// New Source 1
// Return the HTML response
context.succeed(html);
};
Create a template to run it locally
See here for documentation.
AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Description: An AWS Serverless Specification template describing your function.
Resources:
myfirstlambda:
Type: 'AWS::Serverless::Function'
Properties:
Handler: index.handler
Runtime: nodejs14.x
CodeUri: .
Description: ''
MemorySize: 128
Timeout: 3
We need to create a function. We create a package and function
Uploading the code
We need to upload the code to the S3Bucket we created above. We need to zip it up making sure that index.js is at the root and not in a folder. Remember you need the code as well. It does not build it in the cloud.
cd <myproject>
zip -r cowsay.zip *
We can upload it by right-clicking lambda and choosing upload lambda
Running the Code
To run the code locally type
sam local invoke
To run the code on AWS right click on the lambda within AWS Explorer in VS Code
Configuration File
service: myservice # name-of-service
custom: # These are custom variables which are used throughout the template
stage: ${opt:stage, self:provider.stage} # Stage
serverless-offline: # Configuration for running locally
noPrependStageInUrl: true # Don't prepend http routes with the stage
noAuth: true # Turns off all authorizers
port: 4033 # Port to run on
aws-profile:
dev: bibble-nonprod # Profiles to use Defined in the .aws folder
prod: bibble-prod
mysecretvarable: ${ssm:/.../secretsmanager/my-secret-file/my-secret~true} # Name of secret to retrieve (~true = decrypt)
vpcId: # VPC defined in AWS
dev: "vpc-XXX"
prod: "vpc-XXX"
subnetId: # VPC subnet defined in AWS (associated with VPC)
dev: "subnet-XXX"
prod: "subnet-XXX"
securityGroupId: # Security Group define in AWS and associated with VPC and defines firewall
dev: "sg-XXX"
prod: "sg-XXX"
audience: # In AWS API Gateway these are defined under the routes -> authorization
dev: "api://XXX" // The value is defined in AWS and must match the Azure Registration's Application ID URI
prod: "api://XXX"
plugins: // Plugins to run in Serverless Framework
- serverless-bundle
- serverless-offline
provider:
name: aws // Provider
stage: dev // Default stage
profile: ${self:custom.aws-profile.${self:custom.stage}} // Profile
runtime: nodejs12.x // Hashing function to use
lambdaHashingVersion: 20201221 // Not required anymore
region: ap-southeast-2 // Region to deploy to
tracing:
apiGateway: true // Debug option for API
lambda: true // Debug option for Lambda function
environment: // Environment Variables set up (examples left in)
DOMAIN_SUFFIX: bibble
stage: ${self:custom.stage} // Stage defined above
APIKey: ${self:custom.bibbleSecret.APIKey} // Held in Secret Credentials
Username: ${self:custom.bibbleSecret.Username} // Held in Secret Credentials
Password: ${self:custom.bibbleSecret.Password} // Held in Secret Credentials
deploymentBucket: // Bucket to hold the code
name: mybucketname-${opt:stage, self:provider.stage} // Bucket name
httpApi: // Define the httpApi
payload: '2.0' // Version
authorizers: // Authorizer
accessTokenAzure: // Name
identitySource: $request.header.Authorization // Where to find the Authorization
issuerUrl: https://sts.windows.net/xxxxxx // This is an App Registration with a Directory (Tenant) Id which matches in Azure
audience:
- ${self:custom.audience.${self:custom.stage}}
functions:
myfunction-list: // Function to execute
handler: ./request/myfunctionlist.handler // Handler in defined
timeout: 28 // Timeout for handler
vpc:
securityGroupIds:
- !GetAtt lambdaSecurityGroup.GroupId // Defined in Resources
subnetIds:
- ${self:custom.subnetId.${self:custom.stage}} // vpc Subnet defined under custom
events: // Triggers for this API
- httpApi:
method: GET // Method
path: /myfunctionlist // Endpoint
authorizer: // Authorizer
name: accessTokenAzure // Defined under httpApi
resources:
Description: My Function description
Resources:
lambdaSecurityGroup:
Type: "AWS::EC2::SecurityGroup" // This is a definition of an EC2 Security group
Properties:
GroupName: ${self:service}-${self:provider.stage}-lambda // This is just name
GroupDescription: Allow all inbound/outbound // This is just a description
SecurityGroupIngress: // This adds filtering to the Group on Entering boundary
- CidrIp: 0.0.0.0/0 // Anything entering is ok
IpProtocol: '-1' // All protocols
SecurityGroupEgress: // This adds filtering to the Group on Exiting boundary
- CidrIp: 0.0.0.0/0 // Anything leaving is ok
IpProtocol: '-1' // All protocols
VpcId: ${self:custom.vpcId.${self:custom.stage}} // vpc defined above