4

I am working on protecting a static website with a username and password. I created a basic HTTP Authentication for CloudFront with Lambda@Edge in NodeJS.

I am completely new to NodeJS. Initially, I had the user and the password hardcoded, and this worked properly.

'use strict';
exports.handler = (event, context, callback) => {

    // Get request and request headers
    const request = event.Records[0].cf.request;
    const headers = request.headers;

    // Configure authentication
    const authUser = 'user';
    const authPass = 'pass';

    // Construct the Basic Auth string
    const authString = 'Basic ' + new Buffer(authUser + ':' + authPass).toString('base64');

    // Require Basic authentication
    if (typeof headers.authorization == 'undefined' || headers.authorization[0].value != authString) {
        const body = 'Unauthorized';
        const response = {
            status: '401',
            statusDescription: 'Unauthorized',
            body: body,
            headers: {
                'www-authenticate': [{key: 'WWW-Authenticate', value:'Basic'}]
            },
        };
        callback(null, response);
    }

    // Continue request processing if authentication passed
    callback(null, request);
};

I stored my secrets in SSM and I want to retrieve them through the function. I tested this piece of code separately in Lambda and it returns the credentials as espected.

'use strict';
exports.handler = async (event, context, callback) => {
    const ssm = new (require('aws-sdk/clients/ssm'))();
    let userData = await ssm.getParameters({Names: ['website-user']}).promise();
    let userPass = await ssm.getParameters({Names: ['website-pass']}).promise();
    let user = userData.Parameters[0].Value;
    let pass = userPass.Parameters[0].Value;
    return {user, pass};
};

But when I stitch the two, I get 503 ERROR The request could not be satisfied. Does anyone know what I might be doing wrong? Thank you for your help!

The complete code:

'use strict';

exports.handler = async (event, context, callback) => {
    const ssm = new (require('aws-sdk/clients/ssm'))();
    let userData = await ssm.getParameters({Names: ['website-user']}).promise();
    let userPass = await ssm.getParameters({Names: ['website-pass']}).promise();
    let user = userData.Parameters[0].Value;
    let pass = userPass.Parameters[0].Value;

    // Get request and request headers
    const request = event.Records[0].cf.request;
    const headers = request.headers;

    // Construct the Basic Auth string
    let authString = 'Basic ' + new Buffer(user + ':' + pass).toString('base64');

    // Require Basic authentication
    if (typeof headers.authorization == 'undefined' || headers.authorization[0].value != authString) {
        const body = 'Unauthorized';
        const response = {
            status: '401',
            statusDescription: 'Unauthorized',
            body: body,
            headers: {
                'www-authenticate': [{key: 'WWW-Authenticate', value:'Basic'}]
            },
        };
        callback(null, response);
    }

    // Continue request processing if authentication passed
    callback(null, request);
};
John Rotenstein
  • 241,921
  • 22
  • 380
  • 470
Diana Kerrala
  • 77
  • 1
  • 7
  • I think you can not use `async` and `callback` together. What happens if you do `return response` or `return request`? – Jens Jul 29 '21 at 18:47
  • Thank you for your resonse! I tried replacing `callback` with `return response` and `return request` and I am still getting the same error. – Diana Kerrala Jul 29 '21 at 19:59
  • @Jens you pushed me in the right direction and I fixed my error. I'll post the solutiom as an answer. – Diana Kerrala Jul 29 '21 at 20:46

2 Answers2

1

After reading about promises I was able to resolve the error. Here's the solution that worked for me:

'use strict';

var AWS = require('aws-sdk');
AWS.config.update({ region: 'us-east-1' });
var ssm = new AWS.SSM();

function getParameter(param) {
  return new Promise(function (success, reject) {
    ssm.getParameter(param, function (err, data) {
      if (err) {
        reject(err);
      } else {
        success(data);
      }
    });
  });
};

exports.handler =  (event, context, callback) => {
    let request = event.Records[0].cf.request;
    let headers = request.headers;
    let user = {Name: 'user-path', WithDecryption: false};
    let pass = {Name: 'password-path', WithDecryption: false};
    let authUser;
    let authPass;
    var promises = [];
    promises.push(getParameter(user), getParameter(pass));
    
    Promise.all(promises)
    .then(function (result) {
      authUser = result[0].Parameter.Value;
      authPass = result[1].Parameter.Value;
      console.log(authUser);
      console.log(authPass);
      let authString = 'Basic ' + new Buffer(authUser + ':' + authPass).toString('base64');
      if (typeof headers.authorization == 'undefined' || headers.authorization[0].value != authString) {
        const body = 'Unauthorized';
        const response = {
          status: '401',
          statusDescription: 'Unauthorized',
          body: body,
          headers: {
          'www-authenticate': [{key: 'WWW-Authenticate', value:'Basic'}]
        },
    };
    callback(null, response);
}

  // Continue request processing if authentication passed
  callback(null, request);
})
    .catch(function (err) {
      console.log(err);
    });
};

Diana Kerrala
  • 77
  • 1
  • 7
0

Thank you for sharing the solution. And let me do my humble contribution.

To successfully authenticate additionally need the choice right version of Node.js(14.x)

Grant AWS Lambda Access to SSM Parameter Store to retrieve AWS Parameter Store: Go to Lambda function -> Configuration -> Permissions -> Role name -> Whatever-Your-Role-Name-> Add Permissions -> Create inline Policy -> JSON -> And input the policy as shown below: enter image description here

enter image description here enter image description here

{
"Version": "2012-10-17",
"Statement": [
    {
        "Effect": "Allow",
        "Action": [
            "ssm:GetParameter",
            "ssm:GetParameters",
            "ssm:GetParametersByPath",
            "ssm:PutParameter"
        ],
        "Resource": [
            "arn:aws:ssm:us-east-1:YOUR_ACCOUNT_NUMBER:PARAMETER_NAME_WITHOUT_LEADING_SLASH",
            "arn:aws:ssm:us-east-1:252522211181:parameter/web-pass"
        ]
    }
]
}

You could set "ssm:*" for the Action element in the policy to grant full parameter store access to the lambda function.

You could also set the Resource element to be *, which means the function can access all SSM parameters in the account.