Skip to content

Commit e529da9

Browse files
committed
CLDSRV-729: don't require content-md5 header in put-bucket-cors and delete-objects
1 parent a578cea commit e529da9

File tree

4 files changed

+164
-14
lines changed

4 files changed

+164
-14
lines changed

lib/api/bucketPutCors.js

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,14 @@ function bucketPutCors(authInfo, request, log, callback) {
3333
return callback(errors.MissingRequestBodyError);
3434
}
3535

36-
const md5 = crypto.createHash('md5')
37-
.update(request.post, 'utf8').digest('base64');
38-
if (md5 !== request.headers['content-md5']) {
39-
log.debug('bad md5 digest', { error: errors.BadDigest });
40-
monitoring.promMetrics('PUT', bucketName, 400, 'putBucketCors');
41-
return callback(errors.BadDigest);
36+
if (request.headers['content-md5']) {
37+
const md5 = crypto.createHash('md5')
38+
.update(request.post, 'utf8').digest('base64');
39+
if (md5 !== request.headers['content-md5']) {
40+
log.debug('bad md5 digest', { error: errors.BadDigest });
41+
monitoring.promMetrics('PUT', bucketName, 400, 'putBucketCors');
42+
return callback(errors.BadDigest);
43+
}
4244
}
4345

4446
if (parseInt(request.headers['content-length'], 10) > 65536) {

lib/api/multiObjectDelete.js

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -467,12 +467,15 @@ function multiObjectDelete(authInfo, request, log, callback) {
467467
'multiObjectDelete');
468468
return callback(errors.MissingRequestBodyError);
469469
}
470-
const md5 = crypto.createHash('md5')
471-
.update(request.post, 'utf8').digest('base64');
472-
if (md5 !== request.headers['content-md5']) {
473-
monitoring.promMetrics('DELETE', request.bucketName, 400,
474-
'multiObjectDelete');
475-
return callback(errors.BadDigest);
470+
471+
if (request.headers['content-md5']) {
472+
const md5 = crypto.createHash('md5')
473+
.update(request.post, 'utf8').digest('base64');
474+
if (md5 !== request.headers['content-md5']) {
475+
monitoring.promMetrics('DELETE', request.bucketName, 400,
476+
'multiObjectDelete');
477+
return callback(errors.BadDigest);
478+
}
476479
}
477480

478481
const bucketName = request.bucketName;

tests/unit/api/bucketPutCors.js

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ describe('putBucketCORS API', () => {
5555
.createBucketCorsRequest('PUT', bucketName);
5656
bucketPutCors(authInfo, testBucketPutCorsRequest, log, err => {
5757
if (err) {
58-
process.stdout.write(`Err putting website config ${err}`);
58+
process.stdout.write(`Err putting bucket cors ${err}`);
5959
return done(err);
6060
}
6161
return metadata.getBucket(bucketName, log, (err, bucket) => {
@@ -70,11 +70,33 @@ describe('putBucketCORS API', () => {
7070
});
7171
});
7272

73-
it('should return BadDigest if md5 is omitted', done => {
73+
it('should accept request if md5 is omitted', done => {
7474
const corsUtil = new CorsConfigTester();
7575
const testBucketPutCorsRequest = corsUtil
7676
.createBucketCorsRequest('PUT', bucketName);
7777
testBucketPutCorsRequest.headers['content-md5'] = undefined;
78+
bucketPutCors(authInfo, testBucketPutCorsRequest, log, err => {
79+
if (err) {
80+
process.stdout.write(`Err putting bucket cors ${err}`);
81+
return done(err);
82+
}
83+
return metadata.getBucket(bucketName, log, (err, bucket) => {
84+
if (err) {
85+
process.stdout.write(`Err retrieving bucket MD ${err}`);
86+
return done(err);
87+
}
88+
const uploadedCors = bucket.getCors();
89+
assert.deepStrictEqual(uploadedCors, corsUtil.getCors());
90+
return done();
91+
});
92+
});
93+
});
94+
95+
it('should reject request if md5 is mismatch', done => {
96+
const corsUtil = new CorsConfigTester();
97+
const testBucketPutCorsRequest = corsUtil
98+
.createBucketCorsRequest('PUT', bucketName);
99+
testBucketPutCorsRequest.headers['content-md5'] = 'wrong md5';
78100
_testPutBucketCors(authInfo, testBucketPutCorsRequest,
79101
log, 'BadDigest', done);
80102
});

tests/unit/api/multiObjectDelete.js

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,3 +355,126 @@ describe('decodeObjectVersion function helper', () => {
355355
assert.deepStrictEqual(ret[1], undefined);
356356
});
357357
});
358+
359+
describe('multiObjectDelete function', () => {
360+
afterEach(() => {
361+
sinon.restore();
362+
});
363+
364+
it('should not authorize the bucket and initial IAM authorization results', done => {
365+
const post = '<Delete><Object><Key>objectname</Key></Object></Delete>';
366+
const request = new DummyRequest({
367+
bucketName: 'bucketname',
368+
objectKey: 'objectname',
369+
parsedHost: 'localhost',
370+
headers: {
371+
'content-md5': crypto.createHash('md5').update(post, 'utf8').digest('base64')
372+
},
373+
post,
374+
socket: {
375+
remoteAddress: '127.0.0.1',
376+
},
377+
url: '/bucketname',
378+
});
379+
const authInfo = makeAuthInfo('123456');
380+
381+
sinon.stub(metadataWrapper, 'getBucket').callsFake((bucketName, log, cb) =>
382+
cb(null, new BucketInfo(
383+
'bucketname',
384+
'123456',
385+
'accountA',
386+
new Date().toISOString(),
387+
15,
388+
)));
389+
390+
multiObjectDelete.multiObjectDelete(authInfo, request, log, (err, res) => {
391+
// Expected result is an access denied on the object, and no error, as the API was authorized
392+
assert.strictEqual(err, null);
393+
assert.strictEqual(
394+
res.includes('<Error><Key>objectname</Key><Code>AccessDenied</Code>'),
395+
true
396+
);
397+
done();
398+
});
399+
});
400+
401+
it('should accept request when content-md5 header is missing', done => {
402+
const post = '<Delete><Object><Key>objectname</Key></Object></Delete>';
403+
const testObjectKey = 'objectname';
404+
const testBucketName = 'test-bucket';
405+
406+
const request = new DummyRequest({
407+
bucketName: testBucketName,
408+
objectKey: testObjectKey,
409+
parsedHost: 'localhost',
410+
headers: {
411+
// No content-md5 header
412+
},
413+
post,
414+
socket: {
415+
remoteAddress: '127.0.0.1',
416+
},
417+
url: `/${testBucketName}`,
418+
});
419+
420+
// Use the same canonicalID for both authInfo and bucket owner to avoid AccessDenied
421+
const testAuthInfo = makeAuthInfo(canonicalID);
422+
423+
// Create bucket with proper ownership
424+
const testBucketRequest = new DummyRequest({
425+
bucketName: testBucketName,
426+
namespace,
427+
headers: {},
428+
url: `/${testBucketName}`,
429+
});
430+
431+
// Create object to delete
432+
const testObjectRequest = new DummyRequest({
433+
bucketName: testBucketName,
434+
namespace,
435+
objectKey: testObjectKey,
436+
headers: {},
437+
url: `/${testBucketName}/${testObjectKey}`,
438+
}, postBody);
439+
440+
bucketPut(testAuthInfo, testBucketRequest, log, () => {
441+
objectPut(testAuthInfo, testObjectRequest, undefined, log, () => {
442+
multiObjectDelete.multiObjectDelete(testAuthInfo, request, log, (err, res) => {
443+
// Request should succeed even without content-md5 header
444+
assert.strictEqual(err, null);
445+
assert.strictEqual(typeof res, 'string');
446+
// Should contain successful deletion response
447+
assert.strictEqual(res.includes('<Deleted><Key>objectname</Key></Deleted>'), true);
448+
done();
449+
});
450+
});
451+
});
452+
});
453+
454+
it('should reject request with BadDigest error when content-md5 header mismatches', done => {
455+
const post = '<Delete><Object><Key>objectname</Key></Object></Delete>';
456+
const incorrectMd5 = 'incorrectMd5Hash';
457+
458+
const request = new DummyRequest({
459+
bucketName: 'bucketname',
460+
objectKey: 'objectname',
461+
parsedHost: 'localhost',
462+
headers: {
463+
'content-md5': incorrectMd5
464+
},
465+
post,
466+
socket: {
467+
remoteAddress: '127.0.0.1',
468+
},
469+
url: '/bucketname',
470+
});
471+
const authInfo = makeAuthInfo('123456');
472+
473+
multiObjectDelete.multiObjectDelete(authInfo, request, log, (err, res) => {
474+
// Should return BadDigest error for mismatched content-md5
475+
assert.strictEqual(err.is.BadDigest, true);
476+
assert.strictEqual(res, undefined);
477+
done();
478+
});
479+
});
480+
});

0 commit comments

Comments
 (0)