Here is how you generate a policy document using node and serverless
"use strict"; const uniqid = require('uniqid'); const crypto = require('crypto'); class Token { /** * @param {Object} config SSM Parameter store JSON config */ constructor(config) { // Ensure some required properties are set in the SSM configuration object this.constructor._validateConfig(config); this.region = config.region; // AWS region e.g. us-west-2 this.bucket = config.bucket; // Bucket name only this.bucketAcl = config.bucketAcl; // Bucket access policy [private, public-read] this.accessKey = config.accessKey; // Access key this.secretKey = config.secretKey; // Access key secret // Create a really unique videoKey, with folder prefix this.key = uniqid() + uniqid.process(); // The policy requires the date to be this format e.g. 20181109 const date = new Date().toISOString(); this.dateString = date.substr(0, 4) + date.substr(5, 2) + date.substr(8, 2); // The number of minutes the policy will need to be used by before it expires this.policyExpireMinutes = 15; // HMAC encryption algorithm used to encrypt everything in the request this.encryptionAlgorithm = 'sha256'; // Client uses encryption algorithm key while making request to S3 this.clientEncryptionAlgorithm = 'AWS4-HMAC-SHA256'; } /** * Returns the parameters that FE will use to directly upload to s3 * * @returns {Object} */ getS3FormParameters() { const credentialPath = this._amazonCredentialPath(); const policy = this._s3UploadPolicy(credentialPath); const policyBase64 = new Buffer(JSON.stringify(policy)).toString('base64'); const signature = this._s3UploadSignature(policyBase64); return { 'key': this.key, 'acl': this.bucketAcl, 'success_action_status': '201', 'policy': policyBase64, 'endpoint': "https://" + this.bucket + ".s3-accelerate.amazonaws.com", 'x-amz-algorithm': this.clientEncryptionAlgorithm, 'x-amz-credential': credentialPath, 'x-amz-date': this.dateString + 'T000000Z', 'x-amz-signature': signature } } /** * Ensure all required properties are set in SSM Parameter Store Config * * @param {Object} config * @private */ static _validateConfig(config) { if (!config.hasOwnProperty('bucket')) { throw "'bucket' is required in SSM Parameter Store Config"; } if (!config.hasOwnProperty('region')) { throw "'region' is required in SSM Parameter Store Config"; } if (!config.hasOwnProperty('accessKey')) { throw "'accessKey' is required in SSM Parameter Store Config"; } if (!config.hasOwnProperty('secretKey')) { throw "'secretKey' is required in SSM Parameter Store Config"; } } /** * Create a special string called a credentials path used in constructing an upload policy * * @returns {String} * @private */ _amazonCredentialPath() { return this.accessKey + '/' + this.dateString + '/' + this.region + '/s3/aws4_request'; } /** * Create an upload policy * * @param {String} credentialPath * * @returns {{expiration: string, conditions: *[]}} * @private */ _s3UploadPolicy(credentialPath) { return { expiration: this._getPolicyExpirationISODate(), conditions: [ {bucket: this.bucket}, {key: this.key}, {acl: this.bucketAcl}, {success_action_status: "201"}, {'x-amz-algorithm': 'AWS4-HMAC-SHA256'}, {'x-amz-credential': credentialPath}, {'x-amz-date': this.dateString + 'T000000Z'} ], } } /** * ISO formatted date string of when the policy will expire * * @returns {String} * @private */ _getPolicyExpirationISODate() { return new Date((new Date).getTime() + (this.policyExpireMinutes * 60 * 1000)).toISOString(); } /** * HMAC encode a string by a given key * * @param {String} key * @param {String} string * * @returns {String} * @private */ _encryptHmac(key, string) { const hmac = crypto.createHmac( this.encryptionAlgorithm, key ); hmac.end(string); return hmac.read(); } /** * Create an upload signature from provided params * https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html#signing-request-intro * * @param policyBase64 * * @returns {String} * @private */ _s3UploadSignature(policyBase64) { const dateKey = this._encryptHmac('AWS4' + this.secretKey, this.dateString); const dateRegionKey = this._encryptHmac(dateKey, this.region); const dateRegionServiceKey = this._encryptHmac(dateRegionKey, 's3'); const signingKey = this._encryptHmac(dateRegionServiceKey, 'aws4_request'); return this._encryptHmac(signingKey, policyBase64).toString('hex'); } } module.exports = Token;
The configuration object used is stored in SSM Parameter Store and looks like this
{ "bucket": "my-bucket-name", "region": "us-west-2", "bucketAcl": "private", "accessKey": "MY_ACCESS_KEY", "secretKey": "MY_SECRET_ACCESS_KEY", }