Skip to content

Commit 356adb8

Browse files
authored
Merge pull request #82 from craftship/default-encryption
S3 Package: Server-side Encryption
2 parents 3316a85 + 8fb728c commit 356adb8

File tree

5 files changed

+185
-22
lines changed

5 files changed

+185
-22
lines changed

.serverless_plugins/codebox-tools/index.js

Lines changed: 62 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ class CodeboxTools {
3232
},
3333
},
3434
},
35+
encrypt: {
36+
usage: 'Re-encrypts all files within package storage.',
37+
lifecycleEvents: [
38+
'encrypt',
39+
],
40+
},
3541
index: {
3642
usage: 'Re-indexes all packages into Codebox Insights, license required.',
3743
lifecycleEvents: [
@@ -57,6 +63,7 @@ class CodeboxTools {
5763
this.hooks = {
5864
'codebox:domain:migrate': () => this.migrate(),
5965
'codebox:index:index': () => this.index(),
66+
'codebox:encrypt:encrypt': () => this.encrypt(),
6067
};
6168
}
6269

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

7279
data.Contents.forEach((item) => {
73-
if (item.Key.indexOf('index.json') > -1) {
74-
objectPromises.push(
75-
new Promise((resolve, reject) => {
76-
this.s3.getObject({
77-
Bucket: this.bucket,
78-
Key: item.Key,
79-
}).promise().then((obj) => {
80-
resolve({
81-
key: item.Key,
82-
json: JSON.parse(obj.Body.toString()),
83-
});
84-
}).catch(reject);
85-
}));
86-
}
80+
objectPromises.push(
81+
new Promise((resolve, reject) => {
82+
this.s3.getObject({
83+
Bucket: this.bucket,
84+
Key: item.Key,
85+
}).promise().then((obj) => {
86+
resolve({
87+
key: item.Key,
88+
data: obj.Body,
89+
});
90+
}).catch(reject);
91+
}));
8792
});
8893

8994
if (data.IsTruncated) {
@@ -94,14 +99,45 @@ class CodeboxTools {
9499
});
95100
}
96101

102+
encrypt() {
103+
return this._getObjects()
104+
.then((items) => {
105+
const putPromises = [];
106+
107+
items.forEach((item) => {
108+
putPromises.push(
109+
this.s3.putObject({
110+
Bucket: this.bucket,
111+
Key: item.key,
112+
Body: item.data,
113+
ServerSideEncryption: 'AES256',
114+
}).promise());
115+
});
116+
117+
return putPromises;
118+
}).then((promises) => {
119+
return Promise.all(promises)
120+
.then(() => this.serverless.cli.log('Encrypted all current files for registry'))
121+
})
122+
.catch(err => {
123+
this.serverless.cli.log(`Failed file encryption migration ${err.message}`)
124+
});
125+
}
126+
97127
index() {
98128
return this._getObjects()
99129
.then((items) => {
100130
const fetchPromises = [];
101131

102132
items.forEach((item) => {
103-
const version = item.json.versions[
104-
item.json['dist-tags'].latest
133+
if (item.key.indexOf('index.json') === -1) {
134+
return;
135+
}
136+
137+
const json = JSON.parse(item.data.toString());
138+
139+
const version = json.versions[
140+
json['dist-tags'].latest
105141
];
106142

107143
const logBody = {
@@ -114,7 +150,7 @@ class CodeboxTools {
114150
dependencies: version.dependencies,
115151
homepage: version.homepage,
116152
repository: version.repository,
117-
'dist-tags': item.json['dist-tags'],
153+
'dist-tags': json['dist-tags'],
118154
};
119155

120156
const reqBody = JSON.stringify({
@@ -165,10 +201,15 @@ class CodeboxTools {
165201
const putPromises = [];
166202

167203
items.forEach((item) => {
204+
if (item.key.indexOf('index.json') === -1) {
205+
return;
206+
}
207+
168208
const newItem = Object.assign({}, item);
209+
const json = JSON.parse(newItem.data.toString());
169210

170-
Object.keys(item.json.versions).forEach((name) => {
171-
const version = item.json.versions[name];
211+
Object.keys(json.versions).forEach((name) => {
212+
const version = json.versions[name];
172213

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

181-
newItem.json.versions[name] = version;
222+
json.versions[name] = version;
182223
}
183224
});
184225

185226
putPromises.push(
186227
this.s3.putObject({
187228
Bucket: this.bucket,
188229
Key: newItem.key,
189-
Body: JSON.stringify(newItem.json),
230+
Body: JSON.stringify(json),
190231
}).promise());
191232
});
192233

@@ -241,7 +282,6 @@ class CodeboxTools {
241282
this.serverless.cli.log(`Domain updated for ${this.options.host}`);
242283
})
243284
.catch((err) => {
244-
console.log(err);
245285
this.serverless.cli.log(`Domain update failed for ${this.options.host}`);
246286
this.serverless.cli.log(err.message);
247287
});

serverless.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,30 @@ resources:
135135
Properties:
136136
AccessControl: Private
137137
BucketName: ${self:provider.environment.bucket}
138+
PackageStoragePolicy:
139+
Type: "AWS::S3::BucketPolicy"
140+
DependsOn: "PackageStorage"
141+
Properties:
142+
Bucket:
143+
Ref: "PackageStorage"
144+
PolicyDocument:
145+
Statement:
146+
- Sid: DenyIncorrectEncryptionHeader
147+
Effect: Deny
148+
Principal: "*"
149+
Action: "s3:PutObject"
150+
Resource: "arn:aws:s3:::${self:provider.environment.bucket}/*"
151+
Condition:
152+
StringNotEquals:
153+
"s3:x-amz-server-side-encryption": AES256
154+
- Sid: DenyUnEncryptedObjectUploads
155+
Effect: Deny
156+
Principal: "*"
157+
Action: "s3:PutObject"
158+
Resource: "arn:aws:s3:::${self:provider.environment.bucket}/*"
159+
Condition:
160+
"Null":
161+
"s3:x-amz-server-side-encryption": true
138162

139163
custom:
140164
webpackIncludeModules: true

src/adapters/s3.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export default class Storage {
1515
return this.S3.putObject({
1616
Key: key,
1717
Body: encoding === 'base64' ? new Buffer(data, 'base64') : data,
18+
ServerSideEncryption: 'AES256',
1819
}).promise();
1920
}
2021

test/adapters/s3.test.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ describe('S3', () => {
3737
assert(putObjectStub.calledWithExactly({
3838
Key: 'foo-key',
3939
Body: new Buffer('test', 'base64'),
40+
ServerSideEncryption: 'AES256',
4041
}));
4142
});
4243
});
@@ -53,6 +54,7 @@ describe('S3', () => {
5354
assert(putObjectStub.calledWithExactly({
5455
Key: 'foo-key',
5556
Body: 'test',
57+
ServerSideEncryption: 'AES256',
5658
}));
5759
});
5860
});

test/serverless_plugins/codebox-tools/index.test.js

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,102 @@ describe('Plugin: CodeboxTools', () => {
130130
});
131131
});
132132
});
133+
describe('#encrypt()', () => {
134+
context('keys', () => {
135+
let subject;
136+
let serverlessStub;
137+
let serverlessLogStub;
138+
let putObjectStub;
139+
let listObjectsStub;
140+
let getObjectStub;
141+
let mockData;
142+
143+
beforeEach(() => {
144+
mockData = new Buffer('{"versions":{"1.0.0":{"name":"foo", "dist":{"tarball":"http://old-host/registry/foo/-/bar-1.0.0.tgz"}}}}');
145+
serverlessLogStub = stub();
146+
serverlessStub = createServerlessStub(
147+
spy(() => {
148+
putObjectStub = stub().returns({
149+
promise: () => Promise.resolve(),
150+
});
151+
152+
listObjectsStub = stub().returns({
153+
promise: () => Promise.resolve({
154+
IsTruncated: false,
155+
Contents: [{
156+
Key: 'foo/index.json',
157+
}],
158+
}),
159+
});
160+
161+
getObjectStub = stub().returns({
162+
promise: () => Promise.resolve({
163+
Body: mockData,
164+
}),
165+
});
166+
167+
const awsS3Instance = createStubInstance(AWS.S3);
168+
awsS3Instance.listObjectsV2 = listObjectsStub;
169+
awsS3Instance.putObject = putObjectStub;
170+
awsS3Instance.getObject = getObjectStub;
171+
172+
return awsS3Instance;
173+
}),
174+
stub(),
175+
serverlessLogStub,
176+
);
177+
178+
subject = new CodeboxTools(serverlessStub, {
179+
host: 'example.com',
180+
stage: 'test',
181+
path: '/foo',
182+
});
183+
});
184+
185+
it('should store packages encrypted correctly', async () => {
186+
await subject.encrypt();
187+
188+
assert(putObjectStub.calledWithExactly({
189+
Bucket: 'foo-bucket',
190+
Key: 'foo/index.json',
191+
Body: mockData,
192+
ServerSideEncryption: 'AES256',
193+
}));
194+
});
195+
});
196+
197+
context('error', () => {
198+
let subject;
199+
let serverlessStub;
200+
let serverlessLogStub;
201+
let listObjectsStub;
202+
203+
beforeEach(() => {
204+
serverlessLogStub = stub();
205+
serverlessStub = createServerlessStub(
206+
spy(() => {
207+
listObjectsStub = stub().returns({
208+
promise: () => Promise.reject(new Error('Foo')),
209+
});
210+
211+
const awsS3Instance = createStubInstance(AWS.S3);
212+
awsS3Instance.listObjectsV2 = listObjectsStub;
213+
214+
return awsS3Instance;
215+
}), stub(), serverlessLogStub);
216+
217+
subject = new CodeboxTools(serverlessStub, { host: 'example.com' });
218+
});
219+
220+
it('should log error correctly', async () => {
221+
try {
222+
await subject.encrypt();
223+
} catch (err) {
224+
assert(serverlessLogStub.calledWithExactly('Failed file encryption migration Foo'));
225+
}
226+
});
227+
});
228+
});
133229

134230
describe('#migrate()', () => {
135231
context('has no keys', () => {

0 commit comments

Comments
 (0)