0

Serverless – own plugin to attach AWS API Gateway Basic Request Validation

Hey,

I have not had so much time to do as many posts as I wanted since I started my recent project of my own app on AWS using serverless framework. As every good engineer while making my solution I looked at many open source options and serverless seemed to be really good … until I wanted to do something which no one seems to have been doing before ( every one of us know it right ? ) …

So what was that special thing ? Well nothing fancy – just wanted to “attach AWS API Gateway Basic Request Validation” … So I thought … how hard can it be 🙂

As everyone I used “google” to tell me who has done something like that before …. and thats how I visited issue related directly to my problem => https://github.com/serverless/serverless/issues/3464 

Had a conversation there with all interested and we all seemed to agree that there is nothing that would work ( at that specific moment 🙂 )

Well by default ( at the moment of writing of this article ) serverless does not support this “out of the box” therefore this has kicked my off to create my own plugin. Since being developer is not my primary focus ( as I do it only as hobbyist 🙂 ) it was a bit of challenge. But it was completed with success. So what was needed to happen to create it ( if you want to skip the story just scroll down 🙂 ) ?

I started by looking into creating a form of “hello world” plugin that would help me to understand how to approach this in best way. Resources for this can be found on the official site of serverless

But I had a feeling this was quite incomplete to create fully functional plugin. So I spend quite a while browsing internet and reading how people created their own plugins from really simple ones – into more advanced which rocks&roll 😉  Here I think the resource that will show you more detailed steps can be found here

With that bits of knowledge I went to the official repo page of plugins and browsed through repositories which were there. This gave me better idea about what I needed to use.

Having all that knowledge I compiled my action plan which basically was:

  • Create plugin core structure
  • Add resource to all functions of CloudFormation template based on serverless.yml
  • Merge template on hook being called before:deploy
  • Publish for people & get a beer 🙂

This is how I managed to create my plugin available in github https://github.com/RafPe/serverless-reqvalidator-plugin and via npm:

npm install serverless-reqvalidator-plugin

All it does require is extremely simple we start off by creating custom resource in serverless.yml

    xMyRequestValidator:  
      Type: "AWS::ApiGateway::RequestValidator"
      Properties:
        Name: 'my-req-validator'
        RestApiId: 
          Ref: ApiGatewayRestApi
        ValidateRequestBody: true
        ValidateRequestParameters: false

With that one done we add plugin to be enabled

plugins:
    - serverless-reqvalidator-plugin

And then in our functions we specify validator

  debug:
    handler: apis/admin/debug/debug.debug
    timeout: 10
    events:
      - http:
          path: admin/debug
          method: get
          cors: true
          private: true 
          reqValidatorName: 'xMyRequestValidator'

 

 

 

 

Voilla 😉 And the code in the plugin that does the magic ? You would not believe how much of code that is ….

resources[methodName].Properties.RequestValidatorId = {"Ref": `${event.http.reqValidatorName}`};

 

 

And thats it folks 😉 Enjoy and happy coding 🙂

 

0

AWS – Writing better code in Node.js for Lambda functions

Hey,

Been a while but finally its time to post some of technical information I accumulated since my last post. Today we will focus on improvements in code from the time you released something aka version 1 into a code base which you can run tests against.

 

The past …

In my last post I described some modular code I started to work on for use with Lambda ( available here ) The code back then contained a lot of redundant blocks ( which I was aware of 😉  ) however it enabled me to focus on making it better. Just to get everyone idea how it did look like here is snippet from initial code commit

....  // code removed for demo purposes // .... 

var timestamp = new Date().getTime();
  const uniqueId  = uuid.v1();

  console.log(`[CreatePlatformEndpoint] [${timestamp}] [${uniqueId}][Info] Starting execution`);
  
      var responseCode = 400;
      var responseBody = "";
  
      var response = {
        statusCode: responseCode,
        body:       responseBody
      };

      if ( !isDef(event.body) )
        { 

          console.log(`[CreatePlatformEndpoint] [${timestamp}] [${uniqueId}][Error] Missing body information (EC.001)`);

          let errorData = {
            code: "EC.001",
            data: {
              message: "Missing body"
            }
          }

          response.body = {
            action:  "CreatePlatformEndpoint",
            status:  "error",
            error:   errorData,
          }

          response.body = JSON.stringify(response.body)

          callback(null,response); 

        }

      var jsonBody = JSON.parse(event.body);

      console.log(`[CreatePlatformEndpoint] [${timestamp}] [${uniqueId}][Info] Parsed body from request`);

      if ( !isDef(jsonBody.deviceToken) || !isDef(jsonBody.platformApplicationArn))
        {

          console.log(`[CreatePlatformEndpoint] [${timestamp}] [${uniqueId}][Error] Missing required parameters in body (EC.002)`);
          
          let errorData = {
            code: "EC.002",
            data: {
              message: "Missing required parameters in body"
            }
          }

          response.body = {
            action:  "CreatePlatformEndpoint",
            status:  "error",
            error:   errorData,
          }

          response.body = JSON.stringify(response.body)

          callback(null,response);

And now this is just only portion of  code which been redundant. It was just in single file. Now imagine we have multiple components with multiple files and we need to make a single change into logic which is repeated across all those ?! Madness :/

Therefore it took some time ( as I’m far away from being a js developer 😉 ) but I changed …

Making it better …

by creating classes and also in this way trying to regain control on controlling the lifetime of object’ instances. This also enabled me to start writing tests for my code which I’m really happy about as it help so much in test driven development ( this was inspired by the following article )

  • Write your business logic so that it is separate from your FaaS provider (e.g., AWS Lambda), to keep it provider-independent, reusable and more easily testable.

  • When your business logic is written separately from the FaaS provider, you can write traditional Unit Tests to ensure it is working properly.

  • Write Integration Tests to verify integrations with other services are working correctly.

 

So how does the new code looks like ? Take a sneak peak on  snippet from shared resource

const uuid = require('uuid');

class xSharedFunctions { 
    
    constructor(component,callback,disableLogging){
        
                this.callback       =  callback;
                this.component      =  (component === null || component === undefined ) ? 'undefined' : component ;
                this.disableLogging =  disableLogging
                
            }

    generateSuccessResponse(dataSuc,respCode){
            let that = this;

            var responseCode = (respCode === null || respCode === undefined  ) ? 200:respCode ;
            var responseBody = "";
        
            var response = {
              statusCode: responseCode,
              headers: {
                "Access-Control-Allow-Origin" : "*",      // Required for CORS support to work
                "Access-Control-Allow-Credentials" : true // Required for cookies, authorization headers with HTTPS
              },
              body:       responseBody
            };
            
            response.body = {
                component: that.component,
                status:  "success",
                data: dataSuc
            };
            
            response.body = JSON.stringify(response.body);
        
            return response;
    }

// REST OF CODE COMES HERE ....

 

Once that is in place we can go ahead and try to …

 

… use the code in our modules/applications

To get all required references we required our resources and use their functions

'use strict';

var xSharedFunctions = require('../xRes/shared/xSharedFunctions');
var xSnsEndpointManager = require('../xRes/xSnsEndpointManager');


const uuid      = require('uuid');
const component  = 'sns'

var xSharedFnc = new xSharedFunctions('sns');

module.exports.create = (event, context, callback) => {
  const uniqueId      = uuid.v1();
  var xSnsEndpointMgr = new xSnsEndpointManager(uniqueId,callback);

  xSharedFnc.logmsg(uniqueId,'info','Starting execution');
  xSharedFnc.logmsg(uniqueId,'info',`${JSON.stringify(event)}`);

// REST OF CODE COMES HERE ...

 

with keeping the above in mind we should not forget to …

 

… test our code 🙂

And that is why for example I got rests which looks like the following now ( using Mocha and Babel ) …

// CODE REMOVED FOR VISIBILITY ... 


describe('xSnsEndpointManager', function() {
    
        describe('#createPlatformEndpoint()', function() {
    
                before(function () {
                    


                    AWS.mock('SNS', 'createPlatformEndpoint', function (params, callback) {
                    callback(null, '{"ResponseMetadata":{"RequestId":"efdb1199-f10e-5b0b-bff9-43addbda438b"},"EndpointArn":"arn:aws:sns:eu-west-1:12345:endpoint/APNS_SANDBOX/blah-app/c08d3ccd-3e07-328c-a77d-20b2a790122f"}')
                    })

                })


                it('should create endpoint if token provided', function(){

                    var xSnsEndpointMgr = new xSnsEndpointManager('1234',function(dummy,responseCallback){
                        expect(responseCallback.statusCode).to.equal(201);

                        let result = JSON.parse(responseCallback.body);
                        expect(result.component).to.equal('sns');
                        expect(result.status).to.equal('success');

                        let resultData = JSON.parse(result.data);
                        expect(resultData.EndpointArn).not.to.equal(null);
                       
                    },true);

                    let res = xSnsEndpointMgr.createPlatformEndpoint('eee','eee');
                    
                });

                after(function () {
                    AWS.restore('SNS', 'createPlatformEndpoint')
                  })

          });
    
});

// CODE REMOVED FOR VISIBILITY ...

 

Closing thoughts …

So as you can see it all starts to look nice and definitely will get you further if you implement tests. For those interested to see how do I do things here are the links to my repositores on git

 

I hope someone would be able to reuse something for their own needs 😉 Happy coding!

 

1

Serverless REST api for Amazon SNS

Hi!

So it has been a while since I posted some technical posts. Now … this is something that touches us all – the lack of time in the jungle of ongoing projects 🙂 However today we will look into something which I find quite nice for developing of new applications.

Solution is based on serverless framework. Now before we go on – we all know that serverless is a nice catchy word for ‘someone’ else computer and operation problem :)’ . But idea is simple – I’m using AWS – and there it spins me up lambda functions with associated API gateway.

I decided to create this solution to have unified way of deploying and interacting with AWS services in a way that would be easiest for me to consume. However for someone else it might not be the best. Also to be on safe side – this code is really version v1.0.0 so it will get updates as we go ( PR always welcome )

The repository for this write up is available under https://github.com/RafPe/serverless-api-sns

Solution folder structure

The solution structure is created as follows

total 32
-rw-r--r--    1 rafpe  450652656   1.0K Sep  9 17:49 LICENSE
-rw-r--r--    1 rafpe  450652656   2.2K Sep 10 15:07 README.md
drwxr-xr-x  234 rafpe  450652656   7.8K Sep  5 23:53 node_modules
-rw-r--r--    1 rafpe  450652656   255B Sep 10 14:19 package.json
-rw-r--r--    1 rafpe  450652656   3.6K Sep 10 14:21 serverless.yml
drwxr-xr-x    7 rafpe  450652656   238B Sep 10 14:52 sns

and the SNS folder:

├── attributes
├── endpoint
│   ├── create.js
│   ├── delete.js
│   └── list.js
├── messages
├── models
│   └── endpoint.create.json
└── topics

Code

Now since this is not a coding school and I have used really simple code I will not be going into details there. I just might say code has some portions which are repeated and could be wrapped into common methods 😉 did not have time to take a look into that one yet.

For the rest it is using standard aws libraries to execute most of the actions

 

Serverless.yml

Is the heart of your deployment. It describes what will be created and how to link those things together. For more advanced examples you should check out docs.serverless.com

# Welcome to Serverless!
#
# This file is the main config file for your service.
# It's very minimal at this point and uses default values.
# You can always add more config options for more control.
# We've included some commented out config examples here.
# Just uncomment any of them to get that config option.
#
# For full config options, check the docs:
#    docs.serverless.com
#
# Happy Coding!

service: api

# You can pin your service to only deploy with a specific Serverless version
# Check out our docs for more details
# frameworkVersion: "=X.X.X"

provider:
  name: aws
  role: xmyCustRole1 
  apiKeys:
    - myApiKey  
  runtime: nodejs6.10
  region: eu-west-1  


stage: dev



functions:
  create:
    handler: sns/endpoint/create.create
    events:
      - http:
          path: endpoint/create
          method: post
          cors: true
          private: true

  delete:
    handler: sns/endpoint/delete.delete
    events:
      - http:
          path: endpoint/delete
          method: delete
          cors: true
          private: true


  list:
    handler: sns/endpoint/list.list
    events:
      - http:
          path: endpoint/list
          method: post
          cors: true
          private: true


resources:
  Resources:
    # PetsModelNoFlatten: 
    #   Type: "AWS::ApiGateway::Model"
    #   Properties: 
    #     RestApiId: {Ref: ApiGatewayRestApi}
    #     ContentType: "application/json"
    #     Description: "Schema for Pets example"
    #     Name: "PetsModelNoFlatten"
    #     Schema: 
    #       Fn::Join: 
    #         - ""
    #         - 
    #           - "{"
    #           - "   \"$schema\": \"http://json-schema.org/draft-04/schema#\","
    #           - "   \"title\": \"PetsModelNoFlatten\","
    #           - "   \"type\": \"array\","
    #           - "   \"items\": {"
    #           - "       \"type\": \"object\","
    #           - "       \"properties\": {"
    #           - "           \"number\": { \"type\": \"integer\" },"
    #           - "           \"class\": { \"type\": \"string\" },"
    #           - "           \"salesPrice\": { \"type\": \"number\" }"
    #           - "       }"
    #           - "   }"
    #           - "}"
    xmyCustRole1:
      Type: AWS::IAM::Role
      Properties:
        Path: /my/cust/path/
        RoleName: xmyCustRole1
        AssumeRolePolicyDocument:
          Version: '2012-10-17'
          Statement:
            - Effect: Allow
              Principal:
                Service:
                  - lambda.amazonaws.com
              Action: sts:AssumeRole
        Policies:
          - PolicyName: myPolicyName
            PolicyDocument:
              Version: '2012-10-17'
              Statement:
                - Effect: Allow # note that these rights are given in the default policy and are required if you want logs out of your lambda(s)
                  Action:
                    - logs:CreateLogGroup
                    - logs:CreateLogStream
                    - logs:PutLogEvents
                  Resource: arn:aws:logs:*:*:log-group:/aws/lambda/*:*:*
                - Effect: Allow # note that these rights are given in the default policy and are required if you want logs out of your lambda(s)
                  Action:
                    - sns:CreatePlatformEndpoint
                  Resource: arn:aws:sns:*:*:*           
                - Effect: "Allow"
                  Action:
                    - "s3:PutObject"
                  Resource:
                    Fn::Join:
                      - ""
                      - - "arn:aws:s3:::"
                        - "Ref" : "ServerlessDeploymentBucket"

                        

 

IAM policy

To make this all a bit more secure I defined here my specific IAM Role with custom permissions for actions – So if you would need to extend permisions required you would need to look into that resources as well

 

Validations

In my code you will find that I validate if specific parameters are received from the request. Now this is again something that

  1. Could be done better by taking this logic out into common functions or …
  2. even better to use the API gateway validators

I therefore went ahead and created my self json schema using the following online schema generator. With that one done I had to ‘escape’ those characters and then create a policy using serverless resource

resources:
  Resources:
    PetsModelNoFlatten: 
      Type: "AWS::ApiGateway::Model"
      Properties: 
        RestApiId: {Ref: ApiGatewayRestApi}
        ContentType: "application/json"
        Description: "Schema for Pets example"
        Name: "PetsModelNoFlatten"
        Schema: 
          Fn::Join: 
            - ""
            - 
              - "{"
              - "   \"$schema\": \"http://json-schema.org/draft-04/schema#\","
              - "   \"title\": \"PetsModelNoFlatten\","
              - "   \"type\": \"array\","
              - "   \"items\": {"
              - "       \"type\": \"object\","
              - "       \"properties\": {"
              - "           \"number\": { \"type\": \"integer\" },"
              - "           \"class\": { \"type\": \"string\" },"
              - "           \"salesPrice\": { \"type\": \"number\" }"
              - "       }"
              - "   }"
              - "}"

This is all nice but the problem I experience now is that I cannot in programatic way find out how to apply required body validators to specific methods using serverless. Maybe something I will find out later.

 

Deploying

Deploying is easy as running

serverless deploy

and the output should look like

Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service .zip file to S3 (37.84 KB)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
..........................
Serverless: Stack update finished...
Service Information
service: api
stage: dev
region: eu-west-1
api keys:
  myApiKey: ID0d9P4Vgi82l2YvndLwi81FA63lCup1adNQX7eD
endpoints:
  POST - https://isr61ohvhl.execute-api.eu-west-1.amazonaws.com/dev/endpoint/create
  DELETE - https://isr61ohvhl.execute-api.eu-west-1.amazonaws.com/dev/endpoint/delete
  POST - https://isr61ohvhl.execute-api.eu-west-1.amazonaws.com/dev/endpoint/list
functions:
  create: api-dev-create
  delete: api-dev-delete
  list: api-dev-list

 

Fun

Now this is the part I like the most 🙂 Fun starts here when you play around with the working solution. If you got any feedback I would be more than happy to hear about it.

 

 

 

 

 

0

AWS – API Gateway returning 502 from Lambda proxy

Hey,

If you have been scratching your head why does API Gateway returns 502 and within your code there are no exceptions ?

Does your API gateway response contain something like below ?

 

Then make sure that you are returning correct response object containing body and status code i.e.

      var response = {
        statusCode: 200,
        body:       '\0/'
      };

If you still see problem then consider if you are returning complex objects in your body ? If so the following should be additionally applied before returning

response.body = JSON.stringify(response.body)

And thats it 🙂 Solved the problem for me