DaneTheory
12/20/2017 - 5:58 AM

S3 Bucket Auth Lamda Func Cloudfront config function

S3 Bucket Auth Lamda Func Cloudfront config function

let plainTextEmailAddress = null
let cloudFrontPrivateKey = null
const KMS = require('aws-sdk/clients/kms')
const kms = new KMS({apiVersion: '2014-11-01'})   // <==== TODO: CHECK TO SEE IF apiVersion is correct!!!!!!!!!!!!
const crypto = require('crypto')

const headers = {
  "Content-Type": "application/json",
  "Access-Control-Allow-Headers": "Content-Type",
  "Access-Control-Allow-Methods": "OPTIONS,POST",
  "Access-Control-Allow-Origin": "*"              // <==== ENABLES CORS! WHOOOOOOO!!!!!
}

const BAD_REQUEST_RESPONSE = {
  statusCode: 400,
  body: JSON.stringify({
    error: "Invalid Request"
  }),
  headers
}

const TWENTY_FOUR_HOURS = 60 * 60 * 24


/*******************************************************************************************
*
* ====    DECRYPT STUFFS TODO: Is it necessarry to decrypt email address?????? ========
*
********************************************************************************************/
const DecryptEmailAddress = (cb) => {
  if (plainTextEmailAddress && cloudFrontPrivateKey) {
    console.log("Email address already decrypted")
    cb()
  } else {
    console.log("Email address decryption and CloudFront private key generation")
    const params = {
      CiphertextBlob: new Buffer(process.env.ENCRYPTED_EMAIL, 'base64')     // <=== Basic auth here.
    }
    kms.decrypt(params, (err, data) => {
      if (err) {
        console.error("Failed to decrypt email", err)
        throw err
      } else {
        plainTextEmailAddress = data.Plaintext.toString()
        console.log("Successfully decrypted/cached Email")
        const keyParams = {
          CiphertextBlob: new Buffer(process.env.ENCRYPTED_CLOUDFRONT_PRIVATE_KEY, 'base64')    // <=== Basic auth here.
        }
        kms.decrypt(keyParams, (err, data) => {
          if (err) {
            console.error("Failed to decrypt Cloudfront Private Key", err)
            throw err
          } else {
            cloudFrontPrivateKey = data.Plaintext.toString()
            console.log("Successfully decrypted/cached Cloudfront Private Key")
            cb()
          }
        })
      }
    })
  }
}

/*******************************************************************************************
 * Generate policy that allows access to S3 bucket CloudFront Origin domain
 * See: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-setting-signed-cookie-canned-policy.html#private-content-canned-policy-signature-cookies
 * @param {*} expires Unix timestamp for expiry
 ********************************************************************************************/
const makePolicy = (expires) => {
  const policy = JSON.stringify({
    Statement: [{
      Resource: `https://${process.env.CLOUDFRONT_DOMAIN_NAME}/*`,
      Condition: {
        DateLessThan: {
          "AWS:EpochTime": expires
        }
      }
    }]
  })
  console.log("Using Policy", policy)
  return policy
}

/********************************************************************************************
 * Sign policy to allow access to S3 bucket CloudFront Origin domain
 * See: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-setting-signed-cookie-canned-policy.html#private-content-canned-policy-signature-cookies
 * @param {*} expires Unix timestamp for expiry
 *********************************************************************************************/
const makeSignature = (policy, expires) => {
  const signature = crypto.createSign('RSA-SHA1')
  signature.update(policy)
  return signature
    .sign(cloudFrontPrivateKey, 'base64')
    .replace(/\+/g, "-")
    .replace(/=/g, "_")
    .replace(/\//g, "~")
}

exports.handler = (event, context, cb) => {
  DecryptEmailAddress(() => {
    try {
      if (event.body) {
        const body = JSON.parse(event.body)
        if (body.email) {
          if (body.email === plainTextEmailAddress && plainTextEmailAddress !== null && plainTextEmailAddress !== "") {
            const expires = Math.floor((new Date()).getTime() / 1000) + TWENTY_FOUR_HOURS
            const policy = makePolicy(expires)
            const signature = makeSignature(policy, expires)
            const keyPairId = process.env.CLOUDFRONT_KEYPAIR_ID
            cb(null, {
              statusCode: 200,
              headers,
              body: JSON.stringify({
                expires,
                signature,
                keyPairId,
                policy: new Buffer(policy)
                          .toString("base64")
                          .replace(/\+/g, "-")
                          .replace(/=/g, "_")
                          .replace(/\//g, "~")
              })
            })
          } else {
            console.error("Error: Either 1.) Invalid email address provided, 2.) Email address set to null, 3.) Email address is empty string")
            cb(null, {
              statusCode: 401,
              headers,
              body: JSON.stringify({
                error: "Unauthorized"
              })
            })
         }
        } else {
          console.error("No email address present within in body")
          cb(null, BAD_REQUEST_RESPONSE)
        }
      } else {
        console.error("No request body defined")
        cb(null, BAD_REQUEST_RESPONSE)
      }
    } catch (err) {
      console.error(err)
      cb(null, {
        statusCode: 500,
        headers,
        body: JSON.stringify({
          error: "Internal server error"
        })
      })
    }
  })
}