An HTTP proxy that invokes AWS Lambda functions.
Switch branches/tags
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
examples/lambda-functions
lib
.gitignore
LICENSE
NOTICE
README.md
TUTORIAL.md
package.json

README.md

lambda-proxy

NOTE: A recent API Gateway update has improved the usage of Lambda functions from a web context considerably. Therefore this code is no longer maintained. We recommend switching to API Gateway to invoke Lambda functions from a web context.

A HTTP proxy that invokes AWS Lambda functions.

Lambda Proxy makes it easy to invoke Lambda Functions directly from your webserver (Nginx, Apache, etc.). It implements the HTTP Function Protocol.

See the Tutorial for an end-to-end example.

Installing

The preferred way to install the Lambda Proxy is to use the npm package manager for Node.js. Simply type the following into a terminal window:

sudo npm install lambda-proxy -g

Running

Run the Lambda Proxy:

lambda-proxy

Run the Lambda Proxy in background:

lambda-proxy &

Configuration

Port

By default, Lambda Proxy listens on port 8080. To change that, use the -port flag:

lambda-proxy -port 8081 &

Credentials

Lambda Proxy requires a set of AWS credentials that are authorized to invoke the Lambda functions.

With the Shared Credentials File

The easiest way to provide these credentials is via a Shared Credentials File. This file needs to be located in your home directory: ~/.aws/credentials. It contains the credentials:

[default]
aws_access_key_id = <YOUR_ACCESS_KEY_ID>
aws_secret_access_key = <YOUR_SECRET_ACCESS_KEY>

More information can be found in the AWS documentation

With environment variables

You can set your credentials in the environment variables AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY.

More information can be found in the AWS documentation

All other configuration is sent with the individual requests.

Invocation

A Lambda function is invoked, when an HTTP-request is sent to the Lambda Proxy. Configuration for the invocation is sent via HTTP headers.

Lambda function

Lambda function name

The HTTP header lambda-proxy-function defines the Lambda function to invoke. You can specify a function name (e.g. lambda-proxy-echo) or you can specify an Amazon Resource Name (ARN) of the function (e.g. arn:aws:lambda:eu-west-1:account-id:function:lambda-proxy-echo).

Qualifier

The qualifier for the Lambda function is set via the lambda-proxy-qualifier HTTP header. If this header is not present, empty or set to $LATEST, the latest version of the Lambda function is invoked. If it contains a version number or an alias for a Lambda function, the indicated Lambda function is invoked.

Region

The region in which the Lambda function is defined is indicated with the HTTP header lambda-proxy-region.

Meta-Information for the request

The Lambda function needs some meta information to handle the request, specifically the scheme and host that were used for the initial request. This allows the Lambda function for example to create correct absolute links.

Scheme

The scheme is indicated with the lambda-proxy-scheme HTTP header.

Host

The host is indicated with the lambda-proxy-host HTTP header.

URL

The URL is taken directly from the request to the Lambda Proxy and does not need to be specifically configured.

Minimal Nginx configuration:

location /some/location/ {
  proxy_set_header 'lambda-proxy-region' 'eu-west-1';
  proxy_set_header 'lambda-proxy-function' 'lambda-proxy-echo';
  proxy_set_header 'lambda-proxy-parameters' '';
  proxy_set_header 'lambda-proxy-scheme' '$scheme';
  proxy_set_header 'lambda-proxy-host' '$host';
  proxy_pass http://localhost:8080;
}

To avoid unneccessary repetition, it is recommended to move the proxy_set_header-directives to the server context and use variables to overwrite values selectively:

  server {
    set $lambdaregion 'eu-west-1';
    set $lambdaqualifier '';
    set $lambdaparameters '';

    proxy_set_header 'lambda-proxy-region' '$lambdaregion';
    proxy_set_header 'lambda-proxy-qualifier' '$lambdaqualifier';
    proxy_set_header 'lambda-proxy-function' '$lambdafunction';
    proxy_set_header 'lambda-proxy-parameters' '$lambdaparameters';
    proxy_set_header 'lambda-proxy-scheme' '$scheme';
    proxy_set_header 'lambda-proxy-host' '$host';

    location /some/location {
      set $lambdafunction 'lambda-proxy-echo';
      proxy_pass http://localhost:8080;
    }
  }

Note: If you use an additional proxy_set_header-directive inside a location-block, nginx discards all proxy_set_header-directives defined outside of the location-block, therefore you need to repeat all of them in this case.

Extended Nginx configuration example

This configuration leverages more of the nginx configuration directives and also changes the request method and sets a custom body.

http {

  upstream lambda-proxy {
    server localhost:8080;
  }

  server {
    set $lambdaregion 'eu-west-1';
    set $lambdaqualifier '';
    set $lambdaparameters '';

    proxy_set_header 'lambda-proxy-region' '$lambdaregion';
    proxy_set_header 'lambda-proxy-qualifier' '$lambdaqualifier';
    proxy_set_header 'lambda-proxy-function' '$lambdafunction';
    proxy_set_header 'lambda-proxy-parameters' '$lambdaparameters';
    proxy_set_header 'lambda-proxy-scheme' '$scheme';
    proxy_set_header 'lambda-proxy-host' '$host';

    location /some/location {
      set $lambdafunction 'lambda-proxy-echo';
      proxy_pass http://lambda-proxy;
    }

    location /some/other/location {
      set $lambdafunction 'lambda-proxy-echo';
      set $lambdaqualifier 'PROD'; # the qualifier for the Lambda function, use an empty string '' for $LATEST (because $ can not get escaped in the nginx configuration)
      set $lambdaparameters '{"real_ip": "$realip_remote_addr"}';
      proxy_method POST; # switch the request to POST to send custom body
      proxy_set_body '{ "some": "json" }'; # set a custom JSON body with nginx variables
      proxy_pass http://lambda-proxy;
    }
  }
}

Minimal Apache configuration

TBD

Lambda Input/Output structure

The Input/Output structure is defined in the HTTP Function Protocol.

Input: Lambda Event

The Lambda function is invoked with this data structure as the event.

{
  method: 'GET | POST | ...',
  scheme: 'http | https | ...',
  host: 'STRING_VALUE', // the originally requested host
  url: 'STRING_VALUE', // the request url, starting with "/", eg. "/some/url?with=parameter&s=attached"
  httpVersion: '1.0 | ...',
  parameters: {
    'STRING_VALUE': value, // 'name': value
    /* more parameters */
  },
  headers: {
    'STRING_VALUE': 'STRING_VALUE', // 'headername': 'headervalue'
    /* more headers */
  },
  body: 'STRING_VALUE'
}

The difference between headers/body and parameters is that headers and body may contain unchecked content that a malicious client sent, whereas parameters represents content that was added by the server that invokes the Lambda function. In terms of responsibility, the Lambda function is responsible for securely parsing, sanitizing and handling header and body values. On the other hand the Lambda function can treat the value of parameters as having been scrutinized by the server that invokes the Lambda version.

When the body is a JSON-Document, you must parse it yourself:

JSON.parse(event.body);

Output:

The Lambda Function must return a JSON document with this structure in the callback:

{
  status: Number, // a valid HTTP status code
  headers: {
    'STRING_VALUE': 'STRING_VALUE', // 'headername': 'headervalue'
    /* more headers */
  },
  body: 'STRING_VALUE'
}

If you want to return an object as a JSON-document in the body, use `JSON.stringify':

  body: JSON.stringify(object),

Example 1: Returning HTML

const result = {
  status: 200,
  headers: {'Content-Type': 'text/html'},
  body: '<html><head><title>Title</title></head><body><h1>Body</h1></body></html>'
};
callback(null, result);

Example 2: Returning JSON

const data = { key: 'value'};
const result = {
  status: 200,
  headers: {'Content-Type': 'text/json'},
  body: JSON.stringify(data),
};
callback(null, result);

Example 3: Errors

If the output from the Lambda function does not conform to the HTTP-Lambda Gateway Protocol, the response is an HTTP status code 500 with an error message in the body.

Why?

Invoking AWS Lambda functions from an HTTP context currently requires using the AWS API Gateway. While the API Gateway is a good solution for REST-style APIs, its complex configuration makes it hard to use from a more document-oriented context. This is a shame, because Lambda functions could become what PHP currently is: An easy way to add server-side scripting to websites in a serverless world.

The Lambda Proxy let's you skip the step of defining an API in API Gateway. Instead, you just proxy any HTTP request to the Lambda Proxy and it invokes the Lambda function for you. All configuration, e.g. which Lambda function to call, which AWS region to use etc. is configured in your webserver configuration. This removes any complexity between your webserver and the Lambda function.

Security

The Lambda Proxy opens a configurable port. Whoever can send HTTP requests to this port can invoke all Lambda functions that can be invoked with the supplied AWS credentials. Therefore it is probably not a good idea to expose this port to the internet. By default, the Lambda Proxy listens on port 8080 on localhost.

Since the parameters-object is trusted by the Lambda function to not contain malicious content, the header lambda-proxy-parameters MUST be set inside the webserver to something save or to an empty string. Leaving it unset allows a malicious client to provide this header and set an arbitrary value.