documents/dev/snippets/aws/CloudFront DB other account.md

Cloudfront Shared DB auth

On consumer account

Create an s3 bucket, cloudfront distribution (allow S3 to automatically update bucket policy), and lambda

In S3 bucket, under permissions, edit bucket policy Add the role associated with lambda, give it s3:GetObject access

Copy lambda code over Under configuration, permissions, click execution role name

Attach policies, add AmazonS3ReadOnlyAccess, AmazonDynamoDBReadOnlyAccess

Edit trust relationship, add edgelambda

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": [
          "lambda.amazonaws.com",
          "edgelambda.amazonaws.com"
        ]
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

Click on the default policy name (AWSLambdaBasicExecutionRole...), edit policy, go to JSON tab Add statement to allow consuming dynamo-reader role

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "logs:CreateLogGroup",
            "Resource": "arn:aws:logs:us-east-1:432174144495:*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": [
                "arn:aws:logs:us-east-1:432174144495:log-group:/aws/lambda/cfViewerRequest:*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": "sts:AssumeRole",
            "Resource": "arn:aws:iam::997540067939:role/dynamo-reader"
        }
    ]
}

Note the lambda execution role ARN arn:aws:iam::432174144495:role/service-role/cfViewerRequest-role-21xgwbmi

On DesignTech AWS account

Edit dynamo-reader trust relationship, add the consumer role to trust t relationship https://console.aws.amazon.com/iam/home#/roles/dynamo-reader

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::432174144495:role/service-role/cfViewerRequest-role-21xgwbmi",
        "Service": [
          "lambda.amazonaws.com",
          "edgelambda.amazonaws.com"
        ]
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

Lambda Edge

const AWS = require('aws-sdk');
const bcrypt = require('./bcrypt')

const TABLE_USER = 'partner_users';
const RoleArn = 'arn:aws:iam::997540067939:role/dynamo-reader';

let ddb;

const getDb = async ()=> {
    if(typeof ddb !== 'undefined') return ddb;

    // Create the STS service object    
    const sts = new AWS.STS({apiVersion: '2011-06-15'});

    //Assume Role
    const data = await sts.assumeRole({
        RoleArn,
        RoleSessionName: 'session1',
        DurationSeconds: 900
    }).promise();
    
    // instantiate dynamo with credentials
    ddb = new AWS.DynamoDB.DocumentClient({
      apiVersion: '2012-08-10',
      region: 'us-east-1',
      sslEnabled: false,
      paramValidation: false,
      convertResponseTypes: false,
      
      // role credentials
      accessKeyId: data.Credentials.AccessKeyId,
      secretAccessKey: data.Credentials.SecretAccessKey,
      sessionToken: data.Credentials.SessionToken
    });
    return ddb;
};

const GetItemById = async (TableName, id)=> {
  try {
    const ddb = await getDb();
    const data = await ddb.get({ TableName, Key: { id } }).promise();
    return data.Item;
  } catch (e) {
    return null;
  }
};

const getUser = async (id)=> {
  return await GetItemById(TABLE_USER, id);
};

exports.handler = async(event, context) => {
  // unauth response
  let response = {
    status: '401',
    statusDescription: 'Unauthorized',
    body: 'Unauthorized',
    headers: {
      'www-authenticate': [{ key: 'WWW-Authenticate', value: 'Basic' }]
    },
  };

  // Get the request and its headers
  const request = event.Records[0].cf.request;
  const headers = request.headers;
  
  // auto-fetch index.html
  request.uri = request.uri.replace(/\/$/, '/index.html').replace(/\.net$/, '.net/index.html');

  // Challenge for auth if auth credentials are absent
  if(typeof headers.authorization === 'undefined') return response;

  // decode auth key, check against db
  const encodedStr = headers.authorization[0].value.substr(6);
  const buffer = new Buffer(encodedStr, 'base64');
  const decoded = buffer.toString('ascii').split(':');
  const login = decoded[0];
  const password = decoded[1];

  // check login
  let isAuthSuccessful = false;
  let user = await getUser(login);
  if(user) {
    let {org, email, name, exp, pwd} = user;
    let expDate = new Date(exp);
    isAuthSuccessful = bcrypt.compareSync(password, pwd) && !!exp && (Date.now() < expDate);
  }

  if(!isAuthSuccessful) return response;

  return request;
};

Lambda Example

const AWS = require('aws-sdk');
const bcrypt = require('./bcrypt')

const TABLE_USER = 'partner_users';
const TABLE_ORG = 'partner_orgs';
const RoleArn = 'arn:aws:iam::997540067939:role/dynamo-reader';

let ddb;

async function getDb() {
    if(typeof ddb !== 'undefined') return ddb;

    // Create the STS service object    
    const sts = new AWS.STS({apiVersion: '2011-06-15'});

    //Assume Role
    const data = await sts.assumeRole({
        RoleArn,
        RoleSessionName: 'session1',
        DurationSeconds: 900
    }).promise();
    
    ddb = new AWS.DynamoDB.DocumentClient({
      apiVersion: '2012-08-10',
      region: 'us-east-1',
      sslEnabled: false,
      paramValidation: false,
      convertResponseTypes: false,
      
      // role credentials
      accessKeyId: data.Credentials.AccessKeyId,
      secretAccessKey: data.Credentials.SecretAccessKey,
      sessionToken: data.Credentials.SessionToken
    });
    return ddb;
}

const GetItemById = async (TableName, id)=> {
  try {
    const ddb = await getDb();
    const data = await ddb.get({ TableName, Key: { id } }).promise();
    return data.Item;
  } catch (e) {
    return 'la';
  }
};

const getUser = async (id)=> {
  let user = await GetItemById(TABLE_USER, id);
  return user;
};

exports.handler = async(event, context) => {

  // unauth response
  let response = {
    statusCode: 401,
    statusDescription: 'Unauthorized',
    body: 'Unauthorized',
    headers: {
      'WWW-Authenticate': 'Basic'
    },
  };

  // Get the request and its headers
  const headers = event.headers;

  // Challenge for auth if auth credentials are absent
  if(typeof headers.authorization === 'undefined') return response;

  // decode auth key, check against db
  const encodedStr = headers.authorization.substr(6);
  const buffer = new Buffer(encodedStr, 'base64');
  const decoded = buffer.toString('ascii').split(':');
  const login = decoded[0];
  const password = decoded[1];

  // check login
  let isAuthSuccessful = false;
  let user = await getUser(login);
  if(user) {
    let {org, email, name, exp, pwd, orgName, codename, policies} = user;
    let expDate = new Date(user.exp);
    isAuthSuccessful = bcrypt.compareSync(password, user.pwd) && !!user.exp && (Date.now() < expDate);
  }

  if(!isAuthSuccessful) return response;

  return {
      statusCode: 200,
      body: JSON.stringify({message: 'success!', user}),
  };
};