2.0.0 • Published 7 years ago

akamai-g2o v2.0.0

Weekly downloads
4
License
MIT
Repository
github
Last release
7 years ago

Build Status

Akamai G2O for Node JS

G2O (Ghost to Origin, aka Signature Header Authentication) is an Akamai protocol that provides request authentication between Akamai caching proxies and the origin.

This library provides a simple middleware function that provides compliant G2O validation for applications. The following NodeJS server frameworks are supported:

  • vanilla NodeJS HTTP server
  • Express 4
  • Restify 4

How the protocol works

From the documentation:

This feature configures edge servers to include two special headers in requests to the origin server. One of these headers contains basic information about the specific request. The second header contains similar information encrypted with a shared secret. This allows the origin server to perform several levels of authentication to ensure that the request is coming directly from an edge server without tampering.

What this library does not support

The reference documentation specifies that the X-Akamai-G2O-Auth-Data header should be stored to prevent replay attacks. This is not supported (yet), because the library does not make assumptions about its environment and the storage engines that may be available. It does however provide a way of extending the validation mechanism. See the extraCheck option for more details.

How to use this library

First, install it:

npm install --save akamai-g2o

It is assumed that you are familiar with G2O concepts, which aren't covered in depth here.

This simple example sets up an application to use G2O with default parameters.

const app = require("express")();
const g2o = require("akamai-g2o");

app.use(g2o({
   key: {
    "nonce1": "s3cr3tk3y",
    "nonce2": "s3cr3tk3y2"
   }
}));

// ... setup routes and listen

Requests that fail authentication will result in a 403 response.

Requests that pass authentication will be extended with a g2o attribute with the following structure:

{
    data: {}, // see the Data object description section for details
    signature: "base64 encoded signature generated by the middleware",
    authenticated: true|false,
    message: "present only when authenticated is false"
}

See the options section for more advanced configuration.

Options

key

Mandatory; Object or Function.

When providing an Object, it should map nonces to keys (see the official G2O documentation for more information about nonces).

It is also possible to provide a Function for more flexibility. It should have the following signature:

function (req, data, callback)

req is the request object, typically an instance of http.IncomingMessage.

data is an Object representing the data in the dataString. It is described in more detail in the Data object description.

callback takes an Error or null as its first parameter and the key as its second.

strict

NO LONGER SUPPORTED

If you want to implement not strict logic see: onUnauthenticated.

dataHeader

Optional; String (default: "X-Akamai-G2O-Auth-Data")

By default, Ghost sends the request data in the X-Akamai-G2O-Auth-Data header. Setting this option instructs the middleware to retrieve a different header.

signHeader

Optional; String (default: "X-Akamai-G2O-Auth-Sign")

By default, Ghost sends the request signature in the X-Akamai-G2O-Auth-Sign header. Setting this option instructs the middleware to retrieve a different header.

signString

Optional; Function (default: req => req.url)

By default, Ghost signs the request URL as represented in the status line of the request (the URL path).

Ghost can be configured to use any part or combination of parts of the request, in which case you should provide a Function with the following signature:

function (req) => String

checkTime

Optional; Boolean (default: true)

If true, the request will fail if the request time is more than 30s distant from the current server time.

timeWindow

Optional; Number (default: 30)

Number of seconds before or after which the difference between the server and request times will trigger an authentication error when checkTime is true.

extraCheck

Optional; Function (default: null)

If provided, the function will be called after all checks have been completed. The signature should be:

function (req, callback)

An example implementation that prevents replay attacks might look like this:

var g2o = require("akamai-g2o");
var app = require("express")();

var previousAuthDataValues = {};

app.use(g2o({
   key: {
    "nonce1": "s3cr3tk3y",
    "nonce2": "s3cr3tk3y2"
   },
   extraCheck: function (req, callback) {
       if (req.g2o.data.raw in previousAuthDataValues) {
           callback(new Error("replayed request"));
       } else {
           callback();
       }
   }
}));

onUnauthenticated

Optional; Function (default:

function (req, res, next) {
    var statusCode = 403;
    if (typeof res.status === "function") {
        // Express has a status() helper function
        res.status(statusCode);
    } else {
        res.statusCode = statusCode;
    }
    res.end();
}

)

If provided, the function will be called after all checks have been completed and found to be unauthenticated. An unauthenticated g2o data will have a message property, that is the failure reason. The signature should be:

function (req, res, next)

An example implementation that:

  • logs out the failed request
  • uses a different status code
  • only fails request if strict
var g2o = require("akamai-g2o");
var app = require("express")();
var strict = true; // strict controlled outside, but maybe some config.

app.use(g2o({
   key: {
    "nonce1": "s3cr3tk3y",
    "nonce2": "s3cr3tk3y2",
   },
   onUnauthenticated: function (req, res, next) {
      var g2oResponse = Object.assign(
         {},
         {
            strict: strict,
            clientIp: req.ip, // for if there are no g2o header fallback to server known ip.
            forwardAddresses: req.forwardAddresses,
            uri: req.originalUrl, // note this is for express 4.0, else it is req.url
         },
         req.g2o
      );

      console.log('g2o unauthenticated', g2oResponse); // logging

      if (strict) { // only fail request if strict
         res.status(407).end(); // different status code
      } else {
         next();
      }
   }
}));

Data object description

Ghost is required to send a header containing authentication information which is used to generate the signature. This library parses the contents of the header and makes it available as an Object with the following structure:

{
    // the value of the raw header
    raw: "1, 1.2.3.4, 2.3.4.5, 123456789, 42314563, nonce1",
    // specifies the hashing function to use
    version: int(1..5),
    // edge server IP address
    edgeIp: String,
    // client IP address
    clientIp: String,
    // request time, as a Date object
    time: Date,
    // request unique identifier
    uniqueId: String,
    // nonce referencing the key to use
    nonce: String
}

Contributing

Don't break the unit tests, be as clean as you can, be nice.

npm run -s test