Skip to content

Commit 8626736

Browse files
committed
CLDSRV-873: return checksum in HeadObject
1 parent 180d940 commit 8626736

4 files changed

Lines changed: 203 additions & 1 deletion

File tree

lib/api/objectHead.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ function objectHead(authInfo, request, log, callback) {
3232
const bucketName = request.bucketName;
3333
const objectKey = request.objectKey;
3434

35+
const checksumMode = request.headers['x-amz-checksum-mode'];
36+
if (checksumMode !== undefined && checksumMode !== 'ENABLED') {
37+
log.debug('invalid x-amz-checksum-mode', { checksumMode });
38+
return callback(errors.InvalidArgument);
39+
}
40+
3541
const decodedVidResult = decodeVersionId(request.query);
3642
if (decodedVidResult instanceof Error) {
3743
log.trace('invalid versionId query', {
@@ -97,6 +103,15 @@ function objectHead(authInfo, request, log, callback) {
97103
const responseHeaders = collectResponseHeaders(objMD, corsHeaders,
98104
verCfg);
99105

106+
if (checksumMode === 'ENABLED') {
107+
const checksum = objMD.checksum;
108+
if (checksum) {
109+
responseHeaders[`x-amz-checksum-${checksum.checksumAlgorithm}`]
110+
= checksum.checksumValue;
111+
responseHeaders['x-amz-checksum-type'] = checksum.checksumType;
112+
}
113+
}
114+
100115
setExpirationHeaders(responseHeaders, {
101116
lifecycleConfig: bucket.getLifecycleConfiguration(),
102117
objectParams: {

tests/functional/aws-node-sdk/test/object/objectHead.js

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,14 @@ const {
1414
UploadPartCommand,
1515
CompleteMultipartUploadCommand,
1616
} = require('@aws-sdk/client-s3');
17-
17+
// Register the CRT-based CRC64NVME implementation with the SDK middleware.
18+
// The crc64-nvme-crt package sets its own container but the flexible-checksums
19+
// middleware has a separate container that must be patched explicitly.
20+
const { CrtCrc64Nvme } = require('@aws-sdk/crc64-nvme-crt');
21+
const { crc64NvmeCrtContainer } = require('@aws-sdk/middleware-flexible-checksums');
22+
crc64NvmeCrtContainer.CrtCrc64Nvme = CrtCrc64Nvme;
23+
24+
const { algorithms } = require('../../../../../lib/api/apiUtils/integrity/validateChecksums');
1825
const changeObjectLock = require('../../../../utilities/objectLock-util');
1926
const withV4 = require('../support/withV4');
2027
const BucketUtility = require('../../lib/utility/bucket-util');
@@ -538,6 +545,84 @@ describe('HEAD object, conditions', () => {
538545
});
539546
});
540547

548+
describe('HEAD object checksum mode', () => {
549+
withV4(sigCfg => {
550+
let bucketUtil;
551+
let s3;
552+
const checksumBucket = 'checksum-headobject-test';
553+
const checksumKey = 'checksum-test-object';
554+
const body = Buffer.from('checksum test body');
555+
556+
// Expected base64-encoded digests of `body` for each algorithm,
557+
// computed once in the before hook (crc64nvme digest is async).
558+
const expectedDigests = {};
559+
560+
before(async () => {
561+
bucketUtil = new BucketUtility('default', sigCfg);
562+
s3 = bucketUtil.s3;
563+
await s3.send(new CreateBucketCommand({ Bucket: checksumBucket }));
564+
565+
for (const { internalName } of checksumAlgorithms) {
566+
// algorithms[internalName].digest() returns a base64 string
567+
expectedDigests[internalName] =
568+
await algorithms[internalName].digest(body);
569+
}
570+
});
571+
572+
after(async () => {
573+
await bucketUtil.empty(checksumBucket);
574+
await s3.send(new DeleteBucketCommand({ Bucket: checksumBucket }));
575+
});
576+
577+
const checksumAlgorithms = [
578+
{ algorithm: 'SHA256', responseField: 'ChecksumSHA256', internalName: 'sha256' },
579+
{ algorithm: 'SHA1', responseField: 'ChecksumSHA1', internalName: 'sha1' },
580+
{ algorithm: 'CRC32', responseField: 'ChecksumCRC32', internalName: 'crc32' },
581+
{ algorithm: 'CRC32C', responseField: 'ChecksumCRC32C', internalName: 'crc32c' },
582+
{ algorithm: 'CRC64NVME', responseField: 'ChecksumCRC64NVME', internalName: 'crc64nvme' },
583+
];
584+
585+
checksumAlgorithms.forEach(({ algorithm, responseField, internalName }) => {
586+
it(`should return ${responseField} and ChecksumType when ChecksumMode is ENABLED`, async () => {
587+
const putRes = await s3.send(new PutObjectCommand({
588+
Bucket: checksumBucket,
589+
Key: checksumKey,
590+
Body: body,
591+
ChecksumAlgorithm: algorithm,
592+
}));
593+
const storedChecksum = putRes[responseField];
594+
assert(storedChecksum, `Expected ${responseField} in PutObject response`);
595+
596+
const headRes = await s3.send(new HeadObjectCommand({
597+
Bucket: checksumBucket,
598+
Key: checksumKey,
599+
ChecksumMode: 'ENABLED',
600+
}));
601+
assert.strictEqual(headRes[responseField], expectedDigests[internalName],
602+
`${responseField} value mismatch`);
603+
assert.strictEqual(headRes[responseField], storedChecksum);
604+
assert.strictEqual(headRes.ChecksumType, 'FULL_OBJECT');
605+
});
606+
});
607+
608+
it('should not return checksum headers when ChecksumMode is not set', async () => {
609+
await s3.send(new PutObjectCommand({
610+
Bucket: checksumBucket,
611+
Key: checksumKey,
612+
Body: body,
613+
ChecksumAlgorithm: 'SHA256',
614+
}));
615+
616+
const headRes = await s3.send(new HeadObjectCommand({
617+
Bucket: checksumBucket,
618+
Key: checksumKey,
619+
}));
620+
assert.strictEqual(headRes.ChecksumSHA256, undefined);
621+
assert.strictEqual(headRes.ChecksumType, undefined);
622+
});
623+
});
624+
});
625+
541626
const isCEPH = process.env.CI_CEPH !== undefined;
542627
const describeSkipIfCeph = isCEPH ? describe.skip : describe;
543628

tests/unit/api/objectHead.js

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
const assert = require('assert');
2+
const { models } = require('arsenal');
3+
const { ObjectMD, ObjectMDChecksum } = models;
4+
const { algorithms } = require('../../../lib/api/apiUtils/integrity/validateChecksums');
25

36
const { bucketPut } = require('../../../lib/api/bucketPut');
47
const { cleanup, DummyRequestLogger, makeAuthInfo } = require('../helpers');
@@ -565,6 +568,104 @@ describe('objectHead API', () => {
565568
});
566569
});
567570

571+
describe('x-amz-checksum-mode', () => {
572+
const checksumAlgorithms = [
573+
{ name: 'sha256', header: 'x-amz-checksum-sha256' },
574+
{ name: 'sha1', header: 'x-amz-checksum-sha1' },
575+
{ name: 'crc32', header: 'x-amz-checksum-crc32' },
576+
{ name: 'crc32c', header: 'x-amz-checksum-crc32c' },
577+
{ name: 'crc64nvme', header: 'x-amz-checksum-crc64nvme' },
578+
];
579+
580+
// Digests of postBody ("I am a body") for each algorithm, computed once.
581+
const expectedDigests = {};
582+
583+
before(done => {
584+
Promise.all(checksumAlgorithms.map(async ({ name }) => {
585+
expectedDigests[name] = await algorithms[name].digest(postBody);
586+
})).then(() => done(), done);
587+
});
588+
589+
checksumAlgorithms.forEach(({ name, header }) => {
590+
it(`should return ${header} and x-amz-checksum-type when mode is ENABLED`, done => {
591+
const md = new ObjectMD(mdColdHelper.baseMd)
592+
.setChecksum(new ObjectMDChecksum(name, expectedDigests[name], 'FULL_OBJECT'));
593+
mdColdHelper.putBucketMock(bucketName, null, () =>
594+
mdColdHelper.putObjectMock(bucketName, objectName, md, () => {
595+
const req = {
596+
bucketName,
597+
namespace,
598+
objectKey: objectName,
599+
headers: { 'x-amz-checksum-mode': 'ENABLED' },
600+
url: `/${bucketName}/${objectName}`,
601+
};
602+
objectHead(authInfo, req, log, (err, res) => {
603+
assert.ifError(err);
604+
assert.strictEqual(res[header], expectedDigests[name]);
605+
assert.strictEqual(res['x-amz-checksum-type'], 'FULL_OBJECT');
606+
done();
607+
});
608+
}));
609+
});
610+
});
611+
612+
it('should not return checksum headers when mode is ENABLED but object has no checksum', done => {
613+
mdColdHelper.putBucketMock(bucketName, null, () =>
614+
mdColdHelper.putObjectMock(bucketName, objectName, undefined, () => {
615+
const req = {
616+
bucketName,
617+
namespace,
618+
objectKey: objectName,
619+
headers: { 'x-amz-checksum-mode': 'ENABLED' },
620+
url: `/${bucketName}/${objectName}`,
621+
};
622+
objectHead(authInfo, req, log, (err, res) => {
623+
assert.ifError(err);
624+
checksumAlgorithms.forEach(({ header }) =>
625+
assert.strictEqual(res[header], undefined));
626+
assert.strictEqual(res['x-amz-checksum-type'], undefined);
627+
done();
628+
});
629+
}));
630+
});
631+
632+
it('should not return checksum headers when x-amz-checksum-mode is not set', done => {
633+
const md = new ObjectMD(mdColdHelper.baseMd)
634+
.setChecksum(new ObjectMDChecksum('sha256', expectedDigests.sha256, 'FULL_OBJECT'));
635+
mdColdHelper.putBucketMock(bucketName, null, () =>
636+
mdColdHelper.putObjectMock(bucketName, objectName, md, () => {
637+
const req = {
638+
bucketName,
639+
namespace,
640+
objectKey: objectName,
641+
headers: {},
642+
url: `/${bucketName}/${objectName}`,
643+
};
644+
objectHead(authInfo, req, log, (err, res) => {
645+
assert.ifError(err);
646+
checksumAlgorithms.forEach(({ header }) =>
647+
assert.strictEqual(res[header], undefined));
648+
assert.strictEqual(res['x-amz-checksum-type'], undefined);
649+
done();
650+
});
651+
}));
652+
});
653+
654+
it('should return InvalidArgument when x-amz-checksum-mode is not ENABLED', done => {
655+
const req = {
656+
bucketName,
657+
namespace,
658+
objectKey: objectName,
659+
headers: { 'x-amz-checksum-mode': 'DISABLED' },
660+
url: `/${bucketName}/${objectName}`,
661+
};
662+
objectHead(authInfo, req, log, err => {
663+
assert.strictEqual(err.is.InvalidArgument, true);
664+
done();
665+
});
666+
});
667+
});
668+
568669
[
569670
{
570671
name: 'should return content-length of 0 when requesting part 1 of empty object',

tests/unit/api/utils/metadataMockColdStorage.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ function getDeleteMarkerObjectMD(versionId) {
202202
}
203203

204204
module.exports = {
205+
baseMd,
205206
putObjectMock,
206207
getArchivedObjectMD,
207208
getRestoringObjectMD,

0 commit comments

Comments
 (0)