Skip to content

Commit

Permalink
Merge pull request #82 from craftship/default-encryption
Browse files Browse the repository at this point in the history
S3 Package: Server-side Encryption
  • Loading branch information
jonsharratt authored May 13, 2017
2 parents 3316a85 + 8fb728c commit 356adb8
Show file tree
Hide file tree
Showing 5 changed files with 185 additions and 22 deletions.
84 changes: 62 additions & 22 deletions .serverless_plugins/codebox-tools/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ class CodeboxTools {
},
},
},
encrypt: {
usage: 'Re-encrypts all files within package storage.',
lifecycleEvents: [
'encrypt',
],
},
index: {
usage: 'Re-indexes all packages into Codebox Insights, license required.',
lifecycleEvents: [
Expand All @@ -57,6 +63,7 @@ class CodeboxTools {
this.hooks = {
'codebox:domain:migrate': () => this.migrate(),
'codebox:index:index': () => this.index(),
'codebox:encrypt:encrypt': () => this.encrypt(),
};
}

Expand All @@ -70,20 +77,18 @@ class CodeboxTools {
const objectPromises = [];

data.Contents.forEach((item) => {
if (item.Key.indexOf('index.json') > -1) {
objectPromises.push(
new Promise((resolve, reject) => {
this.s3.getObject({
Bucket: this.bucket,
Key: item.Key,
}).promise().then((obj) => {
resolve({
key: item.Key,
json: JSON.parse(obj.Body.toString()),
});
}).catch(reject);
}));
}
objectPromises.push(
new Promise((resolve, reject) => {
this.s3.getObject({
Bucket: this.bucket,
Key: item.Key,
}).promise().then((obj) => {
resolve({
key: item.Key,
data: obj.Body,
});
}).catch(reject);
}));
});

if (data.IsTruncated) {
Expand All @@ -94,14 +99,45 @@ class CodeboxTools {
});
}

encrypt() {
return this._getObjects()
.then((items) => {
const putPromises = [];

items.forEach((item) => {
putPromises.push(
this.s3.putObject({
Bucket: this.bucket,
Key: item.key,
Body: item.data,
ServerSideEncryption: 'AES256',
}).promise());
});

return putPromises;
}).then((promises) => {
return Promise.all(promises)
.then(() => this.serverless.cli.log('Encrypted all current files for registry'))
})
.catch(err => {
this.serverless.cli.log(`Failed file encryption migration ${err.message}`)
});
}

index() {
return this._getObjects()
.then((items) => {
const fetchPromises = [];

items.forEach((item) => {
const version = item.json.versions[
item.json['dist-tags'].latest
if (item.key.indexOf('index.json') === -1) {
return;
}

const json = JSON.parse(item.data.toString());

const version = json.versions[
json['dist-tags'].latest
];

const logBody = {
Expand All @@ -114,7 +150,7 @@ class CodeboxTools {
dependencies: version.dependencies,
homepage: version.homepage,
repository: version.repository,
'dist-tags': item.json['dist-tags'],
'dist-tags': json['dist-tags'],
};

const reqBody = JSON.stringify({
Expand Down Expand Up @@ -165,10 +201,15 @@ class CodeboxTools {
const putPromises = [];

items.forEach((item) => {
if (item.key.indexOf('index.json') === -1) {
return;
}

const newItem = Object.assign({}, item);
const json = JSON.parse(newItem.data.toString());

Object.keys(item.json.versions).forEach((name) => {
const version = item.json.versions[name];
Object.keys(json.versions).forEach((name) => {
const version = json.versions[name];

if (version.dist && version.dist.tarball) {
const currentHost = version.dist.tarball.split('/')[2];
Expand All @@ -178,15 +219,15 @@ class CodeboxTools {
.replace(currentHost, this.options.host)
.replace(currentProtocol, 'https:');

newItem.json.versions[name] = version;
json.versions[name] = version;
}
});

putPromises.push(
this.s3.putObject({
Bucket: this.bucket,
Key: newItem.key,
Body: JSON.stringify(newItem.json),
Body: JSON.stringify(json),
}).promise());
});

Expand Down Expand Up @@ -241,7 +282,6 @@ class CodeboxTools {
this.serverless.cli.log(`Domain updated for ${this.options.host}`);
})
.catch((err) => {
console.log(err);
this.serverless.cli.log(`Domain update failed for ${this.options.host}`);
this.serverless.cli.log(err.message);
});
Expand Down
24 changes: 24 additions & 0 deletions serverless.yml
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,30 @@ resources:
Properties:
AccessControl: Private
BucketName: ${self:provider.environment.bucket}
PackageStoragePolicy:
Type: "AWS::S3::BucketPolicy"
DependsOn: "PackageStorage"
Properties:
Bucket:
Ref: "PackageStorage"
PolicyDocument:
Statement:
- Sid: DenyIncorrectEncryptionHeader
Effect: Deny
Principal: "*"
Action: "s3:PutObject"
Resource: "arn:aws:s3:::${self:provider.environment.bucket}/*"
Condition:
StringNotEquals:
"s3:x-amz-server-side-encryption": AES256
- Sid: DenyUnEncryptedObjectUploads
Effect: Deny
Principal: "*"
Action: "s3:PutObject"
Resource: "arn:aws:s3:::${self:provider.environment.bucket}/*"
Condition:
"Null":
"s3:x-amz-server-side-encryption": true

custom:
webpackIncludeModules: true
1 change: 1 addition & 0 deletions src/adapters/s3.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export default class Storage {
return this.S3.putObject({
Key: key,
Body: encoding === 'base64' ? new Buffer(data, 'base64') : data,
ServerSideEncryption: 'AES256',
}).promise();
}

Expand Down
2 changes: 2 additions & 0 deletions test/adapters/s3.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ describe('S3', () => {
assert(putObjectStub.calledWithExactly({
Key: 'foo-key',
Body: new Buffer('test', 'base64'),
ServerSideEncryption: 'AES256',
}));
});
});
Expand All @@ -53,6 +54,7 @@ describe('S3', () => {
assert(putObjectStub.calledWithExactly({
Key: 'foo-key',
Body: 'test',
ServerSideEncryption: 'AES256',
}));
});
});
Expand Down
96 changes: 96 additions & 0 deletions test/serverless_plugins/codebox-tools/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,102 @@ describe('Plugin: CodeboxTools', () => {
});
});
});
describe('#encrypt()', () => {
context('keys', () => {
let subject;
let serverlessStub;
let serverlessLogStub;
let putObjectStub;
let listObjectsStub;
let getObjectStub;
let mockData;

beforeEach(() => {
mockData = new Buffer('{"versions":{"1.0.0":{"name":"foo", "dist":{"tarball":"http://old-host/registry/foo/-/bar-1.0.0.tgz"}}}}');
serverlessLogStub = stub();
serverlessStub = createServerlessStub(
spy(() => {
putObjectStub = stub().returns({
promise: () => Promise.resolve(),
});

listObjectsStub = stub().returns({
promise: () => Promise.resolve({
IsTruncated: false,
Contents: [{
Key: 'foo/index.json',
}],
}),
});

getObjectStub = stub().returns({
promise: () => Promise.resolve({
Body: mockData,
}),
});

const awsS3Instance = createStubInstance(AWS.S3);
awsS3Instance.listObjectsV2 = listObjectsStub;
awsS3Instance.putObject = putObjectStub;
awsS3Instance.getObject = getObjectStub;

return awsS3Instance;
}),
stub(),
serverlessLogStub,
);

subject = new CodeboxTools(serverlessStub, {
host: 'example.com',
stage: 'test',
path: '/foo',
});
});

it('should store packages encrypted correctly', async () => {
await subject.encrypt();

assert(putObjectStub.calledWithExactly({
Bucket: 'foo-bucket',
Key: 'foo/index.json',
Body: mockData,
ServerSideEncryption: 'AES256',
}));
});
});

context('error', () => {
let subject;
let serverlessStub;
let serverlessLogStub;
let listObjectsStub;

beforeEach(() => {
serverlessLogStub = stub();
serverlessStub = createServerlessStub(
spy(() => {
listObjectsStub = stub().returns({
promise: () => Promise.reject(new Error('Foo')),
});

const awsS3Instance = createStubInstance(AWS.S3);
awsS3Instance.listObjectsV2 = listObjectsStub;

return awsS3Instance;
}), stub(), serverlessLogStub);

subject = new CodeboxTools(serverlessStub, { host: 'example.com' });
});

it('should log error correctly', async () => {
try {
await subject.encrypt();
} catch (err) {
assert(serverlessLogStub.calledWithExactly('Failed file encryption migration Foo'));
}
});
});
});

describe('#migrate()', () => {
context('has no keys', () => {
Expand Down

0 comments on commit 356adb8

Please sign in to comment.