diff --git a/README.md b/README.md index 3400c2f..7cb9277 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,12 @@ The client specified MUST implement functions called `getObject` and `putObject` *Default:* the default S3 library is `aws-sdk` +### serverSideEncryption + +The Server-side encryption algorithm used when storing this object in S3 (e.g., AES256, aws:kms). Possible values include: + - "AES256" + - "aws:kms" + ### How do I activate a revision? A user can activate a revision by either: @@ -239,7 +245,7 @@ You can deploy your Ember application to S3 and still use the history-api for pr ### With Cloudfront A Cloudfront Custom Error Response can handle catching the 404 error that occurs when a request is made to a pretty URL and can allow that request to be handled by index.html and in turn Ember. -A Custom Error Response can be created for your CloudFront distrubution in the AWS console by navigating to: +A Custom Error Response can be created for your CloudFront distrubution in the AWS console by navigating to: Cloudfront > `Distribution ID` > Error Pages > Create Custom Error Response. diff --git a/index.js b/index.js index 2dc703c..0cf5473 100644 --- a/index.js +++ b/index.js @@ -36,16 +36,17 @@ module.exports = { requiredConfig: ['bucket', 'region'], upload: function(/* context */) { - var bucket = this.readConfig('bucket'); - var prefix = this.readConfig('prefix'); - var acl = this.readConfig('acl'); - var cacheControl = this.readConfig('cacheControl'); - var revisionKey = this.readConfig('revisionKey'); - var distDir = this.readConfig('distDir'); - var filePattern = this.readConfig('filePattern'); - var gzippedFiles = this.readConfig('gzippedFiles'); - var allowOverwrite = this.readConfig('allowOverwrite'); - var filePath = joinUriSegments(distDir, filePattern); + var bucket = this.readConfig('bucket'); + var prefix = this.readConfig('prefix'); + var acl = this.readConfig('acl'); + var cacheControl = this.readConfig('cacheControl'); + var revisionKey = this.readConfig('revisionKey'); + var distDir = this.readConfig('distDir'); + var filePattern = this.readConfig('filePattern'); + var gzippedFiles = this.readConfig('gzippedFiles'); + var allowOverwrite = this.readConfig('allowOverwrite'); + var serverSideEncryption = this.readConfig('serverSideEncryption'); + var filePath = joinUriSegments(distDir, filePattern); var options = { bucket: bucket, @@ -59,6 +60,10 @@ module.exports = { allowOverwrite: allowOverwrite }; + if (serverSideEncryption) { + options.serverSideEncryption = serverSideEncryption; + } + this.log('preparing to upload revision to S3 bucket `' + bucket + '`', { verbose: true }); var s3 = new this.S3({ plugin: this }); @@ -66,11 +71,12 @@ module.exports = { }, activate: function(/* context */) { - var bucket = this.readConfig('bucket'); - var prefix = this.readConfig('prefix'); - var acl = this.readConfig('acl'); - var revisionKey = this.readConfig('revisionKey'); - var filePattern = this.readConfig('filePattern'); + var bucket = this.readConfig('bucket'); + var prefix = this.readConfig('prefix'); + var acl = this.readConfig('acl'); + var revisionKey = this.readConfig('revisionKey'); + var filePattern = this.readConfig('filePattern'); + var serverSideEncryption = this.readConfig('serverSideEncryption'); var options = { bucket: bucket, @@ -80,6 +86,10 @@ module.exports = { revisionKey: revisionKey, }; + if (serverSideEncryption) { + options.serverSideEncryption = serverSideEncryption; + } + this.log('preparing to activate `' + revisionKey + '`', { verbose: true }); var s3 = new this.S3({ plugin: this }); diff --git a/lib/s3.js b/lib/s3.js index 7253854..652d5b1 100644 --- a/lib/s3.js +++ b/lib/s3.js @@ -35,17 +35,18 @@ module.exports = CoreObject.extend({ }, upload: function(options) { - var client = this._client; - var plugin = this._plugin; - var bucket = options.bucket; - var acl = options.acl; - var cacheControl = options.cacheControl; - var allowOverwrite = options.allowOverwrite; - var key = options.filePattern + ":" + options.revisionKey; - var revisionKey = joinUriSegments(options.prefix, key); - var putObject = Promise.denodeify(client.putObject.bind(client)); - var gzippedFilePaths = options.gzippedFilePaths || []; - var isGzipped = gzippedFilePaths.indexOf(options.filePattern) !== -1; + var client = this._client; + var plugin = this._plugin; + var bucket = options.bucket; + var acl = options.acl; + var cacheControl = options.cacheControl; + var allowOverwrite = options.allowOverwrite; + var key = options.filePattern + ":" + options.revisionKey; + var revisionKey = joinUriSegments(options.prefix, key); + var putObject = Promise.denodeify(client.putObject.bind(client)); + var gzippedFilePaths = options.gzippedFilePaths || []; + var isGzipped = gzippedFilePaths.indexOf(options.filePattern) !== -1; + var serverSideEncryption = options.serverSideEncryption; var params = { Bucket: bucket, @@ -55,6 +56,10 @@ module.exports = CoreObject.extend({ CacheControl: cacheControl }; + if (serverSideEncryption) { + params.ServerSideEncryption = serverSideEncryption; + } + if (isGzipped) { params.ContentEncoding = 'gzip'; } @@ -77,18 +82,19 @@ module.exports = CoreObject.extend({ }, activate: function(options) { - var plugin = this._plugin; - var client = this._client; - var bucket = options.bucket; - var acl = options.acl; - var prefix = options.prefix; - var filePattern = options.filePattern; - var key = filePattern + ":" + options.revisionKey; - - var revisionKey = joinUriSegments(prefix, key); - var indexKey = joinUriSegments(prefix, filePattern); - var copySource = encodeURIComponent([bucket, revisionKey].join('/')); - var copyObject = Promise.denodeify(client.copyObject.bind(client)); + var plugin = this._plugin; + var client = this._client; + var bucket = options.bucket; + var acl = options.acl; + var prefix = options.prefix; + var filePattern = options.filePattern; + var key = filePattern + ":" + options.revisionKey; + var serverSideEncryption = options.serverSideEncryption; + + var revisionKey = joinUriSegments(prefix, key); + var indexKey = joinUriSegments(prefix, filePattern); + var copySource = encodeURIComponent([bucket, revisionKey].join('/')); + var copyObject = Promise.denodeify(client.copyObject.bind(client)); var params = { Bucket: bucket, @@ -97,6 +103,10 @@ module.exports = CoreObject.extend({ ACL: acl, }; + if (serverSideEncryption) { + params.ServerSideEncryption = serverSideEncryption; + } + return this.fetchRevisions(options).then(function(revisions) { var found = revisions.map(function(element) { return element.revision; }).indexOf(options.revisionKey); if (found >= 0) { diff --git a/tests/unit/index-nodetest.js b/tests/unit/index-nodetest.js index 0191ab9..2be395f 100644 --- a/tests/unit/index-nodetest.js +++ b/tests/unit/index-nodetest.js @@ -125,6 +125,25 @@ describe('s3-index plugin', function() { }); }); + it('detects serverSideEncryption when defined', function() { + context.config['s3-index'].serverSideEncryption = 'AES256'; + var promise = plugin.upload(context); + + return assert.isFulfilled(promise) + .then(function() { + assert.equal(s3Options.serverSideEncryption, 'AES256', 'serverSideEncryption passed correctly'); + }); + }); + + it('filters serverSideEncryption when not defined', function() { + var promise = plugin.upload(context); + + return assert.isFulfilled(promise) + .then(function() { + assert.equal(s3Options.hasOwnProperty('serverSideEncryption'), false, 'serverSideEncryption filtered correctly'); + }); + }); + it('passes cacheControl options based on the cacheControl option to the s3-abstraction', function() { var cacheControl = 'max-age=3600'; context.config['s3-index'].cacheControl = cacheControl; @@ -192,6 +211,25 @@ describe('s3-index plugin', function() { assert.deepEqual(s3Options, expected); }); }); + + it('detects serverSideEncryption when defined', function() { + context.config['s3-index'].serverSideEncryption = 'AES256'; + var promise = plugin.activate(context); + + return assert.isFulfilled(promise) + .then(function() { + assert.equal(s3Options.serverSideEncryption, 'AES256', 'serverSideEncryption passed correctly'); + }); + }); + + it('filters serverSideEncryption when not defined', function() { + var promise = plugin.activate(context); + + return assert.isFulfilled(promise) + .then(function() { + assert.equal(s3Options.hasOwnProperty('serverSideEncryption'), false, 'serverSideEncryption filtered correctly'); + }); + }); }); describe('#fetchInitialRevisions', function() { diff --git a/tests/unit/lib/s3-nodetest.js b/tests/unit/lib/s3-nodetest.js index b0014d6..cc5b00d 100644 --- a/tests/unit/lib/s3-nodetest.js +++ b/tests/unit/lib/s3-nodetest.js @@ -125,6 +125,25 @@ describe('s3', function() { }); }); + it('detects serverSideEncryption when defined', function() { + options.serverSideEncryption = 'AES256'; + var promise = subject.upload(options); + + return assert.isFulfilled(promise) + .then(function() { + assert.equal(s3Params.ServerSideEncryption, 'AES256', 'serverSideEncryption passed correctly'); + }); + }); + + it('filters serverSideEncryption when not defined', function() { + var promise = subject.upload(options); + + return assert.isFulfilled(promise) + .then(function() { + assert.equal(s3Params.hasOwnProperty('serverSideEncryption'), false, 'serverSideEncryption filtered correctly'); + }); + }); + it('detects `filePattern` other than `index.html` in order to customize ContentType', function() { var filePath = 'tests/unit/fixtures/test.tar'; @@ -346,6 +365,25 @@ describe('s3', function() { assert.equal(copyParams.ACL, acl); }); }); + + it('detects serverSideEncryption when defined', function() { + options.serverSideEncryption = 'AES256'; + var promise = subject.activate(options); + + return assert.isFulfilled(promise) + .then(function() { + assert.equal(copyParams.ServerSideEncryption, 'AES256', 'serverSideEncryption passed correctly'); + }); + }); + + it('filters serverSideEncryption when not defined', function() { + var promise = subject.activate(options); + + return assert.isFulfilled(promise) + .then(function() { + assert.equal(copyParams.hasOwnProperty('serverSideEncryption'), false, 'serverSideEncryption filtered correctly'); + }); + }); }); describe('with an invalid revision key', function() {