-
Notifications
You must be signed in to change notification settings - Fork 255
Expand file tree
/
Copy pathbucketCreation.js
More file actions
272 lines (260 loc) · 11.3 KB
/
bucketCreation.js
File metadata and controls
272 lines (260 loc) · 11.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
const assert = require('assert');
const async = require('async');
const { errors } = require('arsenal');
const acl = require('../../../metadata/acl');
const BucketInfo = require('arsenal').models.BucketInfo;
const constants = require('../../../../constants');
const createKeyForUserBucket = require('./createKeyForUserBucket');
const { parseBucketEncryptionHeaders } = require('./bucketEncryption');
const metadata = require('../../../metadata/wrapper');
const kms = require('../../../kms/wrapper');
const isLegacyAWSBehavior = require('../../../utilities/legacyAWSBehavior');
const { config } = require('../../../Config');
const usersBucket = constants.usersBucket;
const oldUsersBucket = constants.oldUsersBucket;
const userBucketOwner = 'admin';
function addToUsersBucket(canonicalID, bucketName, log, cb) {
// BACKWARD: Simplify once do not have to deal with old
// usersbucket name and old splitter
// Get new format usersBucket to see if it exists
return metadata.getBucket(usersBucket, log, (err, usersBucketAttrs) => {
if (err && !err.is.NoSuchBucket && !err.is.BucketAlreadyExists) {
return cb(err);
}
const splitter = usersBucketAttrs ?
constants.splitter : constants.oldSplitter;
let key = createKeyForUserBucket(canonicalID, splitter, bucketName);
const omVal = { creationDate: new Date().toJSON() };
// If the new format usersbucket does not exist, try to put the
// key in the old usersBucket using the old splitter.
// Otherwise put the key in the new format usersBucket
const usersBucketBeingCalled = usersBucketAttrs ?
usersBucket : oldUsersBucket;
return metadata.putObjectMD(usersBucketBeingCalled, key,
omVal, {}, log, err => {
if (err && err.is.NoSuchBucket) {
// There must be no usersBucket so createBucket
// one using the new format
log.trace('users bucket does not exist, ' +
'creating users bucket');
key = `${canonicalID}${constants.splitter}` +
`${bucketName}`;
const creationDate = new Date().toJSON();
const freshBucket = new BucketInfo(usersBucket,
userBucketOwner, userBucketOwner, creationDate,
BucketInfo.currentModelVersion());
return metadata.createBucket(usersBucket,
freshBucket, log, err => {
// Note: In the event that two
// users' requests try to create the
// usersBucket at the same time,
// this will prevent one of the users
// from getting a BucketAlreadyExists
// error with respect
// to the usersBucket.
// TODO: move to `.is` once BKTCLT-9 is done and bumped in Cloudserver
if (err && !err.BucketAlreadyExists) {
log.error('error from metadata', {
error: err,
});
return cb(err);
}
log.trace('Users bucket created');
// Finally put the key in the new format
// usersBucket
return metadata.putObjectMD(usersBucket,
key, omVal, {}, log, cb);
});
}
return cb(err);
});
});
}
function removeTransientOrDeletedLabel(bucket, log, callback) {
log.trace('removing transient or deleted label from bucket attributes');
const bucketName = bucket.getName();
bucket.removeTransientFlag();
bucket.removeDeletedFlag();
return metadata.updateBucket(bucketName, bucket, log, callback);
}
function freshStartCreateBucket(bucket, canonicalID, log, callback) {
const bucketName = bucket.getName();
metadata.createBucket(bucketName, bucket, log, err => {
if (err) {
log.debug('error from metadata', { error: err });
return callback(err);
}
log.trace('created bucket in metadata');
return addToUsersBucket(canonicalID, bucketName, log, err => {
if (err) {
return callback(err);
}
return removeTransientOrDeletedLabel(bucket, log, callback);
});
});
}
/**
* Finishes creating a bucket in transient state
* by putting an object in users bucket representing the created bucket
* and removing transient attribute of the created bucket
* @param {object} bucketMD - either the bucket metadata sent in the new request
* or the existing metadata if no new metadata sent
* (for example in an objectPut)
* @param {string} canonicalID - bucket owner's canonicalID
* @param {object} log - Werelogs logger
* @param {function} callback - callback with error or null as arguments
* @return {undefined}
*/
function cleanUpBucket(bucketMD, canonicalID, log, callback) {
const bucketName = bucketMD.getName();
return addToUsersBucket(canonicalID, bucketName, log, err => {
if (err) {
return callback(err);
}
return removeTransientOrDeletedLabel(bucketMD, log, callback);
});
}
/**
* Manage the server side encryption on bucket creation, as a side effect
* a bucket key is created in the kms
* @param {string} bucketName - name of bucket
* @param {object} headers - request headers
* @param {function} log - Werelogs logger
* @param {function} cb - called on completion
* @returns {undefined}
* @callback called with (err, sseInfo: object)
*/
function bucketLevelServerSideEncryption(bucketName, headers, log, cb) {
kms.bucketLevelEncryption(
bucketName, headers, log, (err, sseInfo) => {
if (err) {
log.debug('error getting bucket encryption info', {
error: err,
});
return cb(err);
}
return cb(null, sseInfo);
});
}
/**
* Creates bucket
* @param {AuthInfo} authInfo - Instance of AuthInfo class with
* requester's info
* @param {string} bucketName - name of bucket
* @param {object} headers - request headers
* @param {string} locationConstraint - locationConstraint provided in
* request body xml (if provided)
* @param {function} log - Werelogs logger
* @param {function} cb - callback to bucketPut
* @return {undefined}
*/
function createBucket(authInfo, bucketName, headers,
locationConstraint, log, cb) {
log.trace('Creating bucket');
assert.strictEqual(typeof bucketName, 'string');
const canonicalID = authInfo.getCanonicalID();
const ownerDisplayName =
authInfo.getAccountDisplayName();
const creationDate = new Date().toJSON();
const headerObjectLock = headers['x-amz-bucket-object-lock-enabled'];
const objectLockEnabled
= headerObjectLock && headerObjectLock.toLowerCase() === 'true';
const bucket = new BucketInfo(bucketName, canonicalID, ownerDisplayName,
creationDate, BucketInfo.currentModelVersion(), null, null, null, null,
null, null, null, null, null, null, null, null, objectLockEnabled);
if (locationConstraint !== undefined) {
bucket.setLocationConstraint(locationConstraint);
}
if (objectLockEnabled) {
// default versioning configuration AWS sets
// when a bucket is created with object lock
const versioningConfiguration = {
Status: 'Enabled',
MfaDelete: 'Disabled',
};
bucket.setVersioningConfiguration(versioningConfiguration);
}
const parseAclParams = {
headers,
resourceType: 'bucket',
acl: bucket.acl,
log,
};
async.parallel({
prepareNewBucketMD: function prepareNewBucketMD(callback) {
acl.parseAclFromHeaders(parseAclParams, (err, parsedACL) => {
if (err) {
log.debug('error parsing acl from headers', {
error: err,
});
return callback(err);
}
bucket.setFullAcl(parsedACL);
return callback(null, bucket);
});
},
getAnyExistingBucketInfo: function getAnyExistingBucketInfo(callback) {
metadata.getBucket(bucketName, log, (err, data) => {
// TODO: move to `.is` once BKTCLT-9 is done and bumped in Cloudserver
if (err && err.NoSuchBucket) {
return callback(null, 'NoBucketYet');
}
if (err) {
return callback(err);
}
return callback(null, data);
});
},
},
// Function to run upon finishing both parallel requests
(err, results) => {
if (err) {
return cb(err);
}
const existingBucketMD = results.getAnyExistingBucketInfo;
if (existingBucketMD instanceof BucketInfo &&
existingBucketMD.getOwner() !== canonicalID) {
// return existingBucketMD to collect cors headers
return cb(errors.BucketAlreadyExists, existingBucketMD);
}
const newBucketMD = results.prepareNewBucketMD;
if (existingBucketMD === 'NoBucketYet') {
const sseConfig = parseBucketEncryptionHeaders(headers);
sseConfig.algorithm = sseConfig.algorithm ?? config.defaultBucketSseConfig.algorithm ?? null;
sseConfig.mandatory = sseConfig.mandatory ?? config.defaultBucketSseConfig.mandatory ?? false;
return bucketLevelServerSideEncryption(
bucketName, sseConfig, log,
(err, sseInfo) => {
if (err) {
return cb(err);
}
newBucketMD.setServerSideEncryption(sseInfo);
log.trace(
'new bucket without flags; adding transient label');
newBucketMD.addTransientFlag();
return freshStartCreateBucket(newBucketMD, canonicalID,
log, cb);
});
}
if (existingBucketMD.hasTransientFlag() ||
existingBucketMD.hasDeletedFlag()) {
log.trace('bucket has transient flag or deleted flag. cleaning up');
return cleanUpBucket(newBucketMD, canonicalID, log, cb);
}
// If bucket already exists in non-transient and non-deleted
// state and owned by requester, then return BucketAlreadyOwnedByYou
// error unless old AWS behavior (us-east-1)
// Existing locationConstraint must have legacyAwsBehavior === true
// New locationConstraint should have legacyAwsBehavior === true
if (isLegacyAWSBehavior(locationConstraint) &&
isLegacyAWSBehavior(existingBucketMD.getLocationConstraint())) {
log.trace('returning 200 instead of 409 to mirror us-east-1');
return cb(null, existingBucketMD);
}
return cb(errors.BucketAlreadyOwnedByYou, existingBucketMD);
});
}
module.exports = {
cleanUpBucket,
createBucket,
};