Skip to content

Commit 85e28c8

Browse files
committed
CLDSRV-872: store checksum in metadata for objects uploaded with PutObject
1 parent 888ea43 commit 85e28c8

File tree

5 files changed

+143
-9
lines changed

5 files changed

+143
-9
lines changed

lib/api/apiUtils/object/createAndStoreObject.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ function createAndStoreObject(bucketName, bucketMD, objectKey, objMD, authInfo,
217217
if (size === 0) {
218218
if (!dontSkipBackend[locationType]) {
219219
metadataStoreParams.contentMD5 = constants.emptyFileMd5;
220-
return next(null, null, null);
220+
return next(null, null, null, null);
221221
}
222222

223223
// Handle mdOnlyHeader as a metadata only operation. If
@@ -243,14 +243,14 @@ function createAndStoreObject(bucketName, bucketMD, objectKey, objMD, authInfo,
243243
dataStoreVersionId: versionId,
244244
dataStoreMD5: _md5,
245245
};
246-
return next(null, dataGetInfo, _md5);
246+
return next(null, dataGetInfo, _md5, null);
247247
}
248248
}
249249

250250
return dataStore(objectKeyContext, cipherBundle, request, size,
251251
streamingV4Params, backendInfo, log, next);
252252
},
253-
function processDataResult(dataGetInfo, calculatedHash, next) {
253+
function processDataResult(dataGetInfo, calculatedHash, checksum, next) {
254254
if (dataGetInfo === null || dataGetInfo === undefined) {
255255
return next(null, null);
256256
}
@@ -275,6 +275,7 @@ function createAndStoreObject(bucketName, bucketMD, objectKey, objMD, authInfo,
275275
dataGetInfoArr[0].size = mdOnlySize;
276276
}
277277
metadataStoreParams.contentMD5 = calculatedHash;
278+
metadataStoreParams.checksum = checksum;
278279
return next(null, dataGetInfoArr);
279280
},
280281
function getVersioningInfo(infoArr, next) {

lib/api/apiUtils/object/storeObject.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const { arsenalErrorFromChecksumError } = require('../../apiUtils/integrity/vali
1717
* @return {function} - calls callback with arguments:
1818
* error, dataRetrievalInfo, and completedHash (if any)
1919
*/
20-
function checkHashMatchMD5(stream, hashedStream, dataRetrievalInfo, log, cb) {
20+
function checkHashMatchMD5(stream, hashedStream, dataRetrievalInfo, checksumStream, log, cb) {
2121
const contentMD5 = stream.contentMD5;
2222
const completedHash = hashedStream.completedHash;
2323
if (contentMD5 && completedHash && contentMD5 !== completedHash) {
@@ -37,7 +37,10 @@ function checkHashMatchMD5(stream, hashedStream, dataRetrievalInfo, log, cb) {
3737
return cb(errors.BadDigest);
3838
});
3939
}
40-
return cb(null, dataRetrievalInfo, completedHash);
40+
const checksum = checksumStream.digest
41+
? { algorithm: checksumStream.algoName, value: checksumStream.digest, type: 'FULL_OBJECT' }
42+
: null;
43+
return cb(null, dataRetrievalInfo, completedHash, checksum);
4144
}
4245

4346
/**
@@ -107,7 +110,8 @@ function dataStore(objectContext, cipherBundle, stream, size,
107110
return cbOnce(arsenalErrorFromChecksumError(checksumErr));
108111
});
109112
}
110-
return checkHashMatchMD5(stream, hashedStream, dataRetrievalInfo, log, cbOnce);
113+
return checkHashMatchMD5(stream, hashedStream, dataRetrievalInfo,
114+
checksumedStream.stream, log, cbOnce);
111115
};
112116

113117
// ChecksumTransform._flush computes the digest asynchronously for

lib/services.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const { errors, s3middleware } = require('arsenal');
66
const ObjectMD = require('arsenal').models.ObjectMD;
77
const BucketInfo = require('arsenal').models.BucketInfo;
88
const ObjectMDArchive = require('arsenal').models.ObjectMDArchive;
9+
const ObjectMDChecksum = require('arsenal').models.ObjectMDChecksum;
910
const { versioning } = require('arsenal');
1011
const acl = require('./metadata/acl');
1112
const constants = require('../constants');
@@ -102,7 +103,7 @@ const services = {
102103
* @return {function} executes callback with err or ETag as arguments
103104
*/
104105
metadataStoreObject(bucketName, dataGetInfo, cipherBundle, params, cb) {
105-
const { objectKey, authInfo, size, contentMD5, metaHeaders,
106+
const { objectKey, authInfo, size, contentMD5, checksum, metaHeaders,
106107
contentType, cacheControl, contentDisposition, contentEncoding,
107108
expires, multipart, headers, overrideMetadata, log,
108109
lastModifiedDate, versioning, versionId, uploadId,
@@ -138,6 +139,9 @@ const services = {
138139
// CreationTime needs to be carried over so that it remains static
139140
.setCreationTime(creationTime)
140141
.setOriginOp(originOp);
142+
if (checksum) {
143+
md.setChecksum(new ObjectMDChecksum(checksum.algorithm, checksum.value, checksum.type));
144+
}
141145
// Sending in last modified date in object put copy since need
142146
// to return the exact date in the response
143147
if (lastModifiedDate) {

tests/functional/raw-node/test/routes/routeMetadata.js

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
const assert = require('assert');
2+
const crypto = require('crypto');
3+
const { CrtCrc64Nvme } = require('@aws-sdk/crc64-nvme-crt');
24
const http = require('http');
3-
const { CreateBucketCommand,
4-
PutObjectCommand,
5+
const { CreateBucketCommand,
6+
PutObjectCommand,
7+
DeleteObjectCommand,
58
DeleteBucketCommand } = require('@aws-sdk/client-s3');
69

710
const { makeRequest } = require('../../utils/makeRequest');
@@ -144,3 +147,78 @@ describe('metadata routes with metadata', () => {
144147
});
145148
});
146149
});
150+
151+
describe('checksum stored in object metadata after PutObject', () => {
152+
const bucketUtil = new BucketUtility('default', { signatureVersion: 'v4' });
153+
const s3 = bucketUtil.s3;
154+
155+
const bucket = 'bucket1';
156+
const objectBody = 'hello checksum';
157+
const sha256Key = 'object-with-sha256-checksum';
158+
const defaultKey = 'object-with-default-checksum';
159+
160+
let expectedCrc64nvme;
161+
162+
before(async function () {
163+
if (!process.env.S3_END_TO_END) {
164+
this.skip();
165+
}
166+
const crc = new CrtCrc64Nvme();
167+
crc.update(Buffer.from(objectBody));
168+
expectedCrc64nvme = Buffer.from(await crc.digest()).toString('base64');
169+
170+
const sha256Value = crypto.createHash('sha256').update(objectBody).digest('base64');
171+
await s3.send(new PutObjectCommand({
172+
Bucket: bucket,
173+
Key: sha256Key,
174+
Body: objectBody,
175+
ChecksumSHA256: sha256Value,
176+
}));
177+
await s3.send(new PutObjectCommand({
178+
Bucket: bucket,
179+
Key: defaultKey,
180+
Body: objectBody,
181+
}));
182+
});
183+
184+
after(async () => {
185+
if (!process.env.S3_END_TO_END) {
186+
return;
187+
}
188+
await s3.send(new DeleteObjectCommand({ Bucket: bucket, Key: sha256Key }));
189+
await s3.send(new DeleteObjectCommand({ Bucket: bucket, Key: defaultKey }));
190+
});
191+
192+
it('stores sha256 checksum in metadata when x-amz-checksum-sha256 is provided', done => {
193+
const expectedValue = crypto.createHash('sha256').update(objectBody).digest('base64');
194+
makeMetadataRequest({
195+
method: 'GET',
196+
authCredentials: metadataAuthCredentials,
197+
path: `/_/metadata/default/bucket/${bucket}/${sha256Key}`,
198+
}, (err, res) => {
199+
assert.ifError(err);
200+
assert.strictEqual(res.statusCode, 200);
201+
const md = JSON.parse(res.body);
202+
assert.strictEqual(md.checksum.checksumAlgorithm, 'sha256');
203+
assert.strictEqual(md.checksum.checksumValue, expectedValue);
204+
assert.strictEqual(md.checksum.checksumType, 'FULL_OBJECT');
205+
return done();
206+
});
207+
});
208+
209+
it('stores crc64nvme checksum in metadata when no checksum header is provided', done => {
210+
makeMetadataRequest({
211+
method: 'GET',
212+
authCredentials: metadataAuthCredentials,
213+
path: `/_/metadata/default/bucket/${bucket}/${defaultKey}`,
214+
}, (err, res) => {
215+
assert.ifError(err);
216+
assert.strictEqual(res.statusCode, 200);
217+
const md = JSON.parse(res.body);
218+
assert.strictEqual(md.checksum.checksumAlgorithm, 'crc64nvme');
219+
assert.strictEqual(md.checksum.checksumValue, expectedCrc64nvme);
220+
assert.strictEqual(md.checksum.checksumType, 'FULL_OBJECT');
221+
return done();
222+
});
223+
});
224+
});

tests/unit/api/objectPut.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const assert = require('assert');
22
const async = require('async');
3+
const crypto = require('crypto');
34
const moment = require('moment');
45
const { s3middleware, storage, versioning } = require('arsenal');
56
const sinon = require('sinon');
@@ -865,6 +866,52 @@ describe('objectPut API', () => {
865866
});
866867
});
867868
});
869+
870+
it('should store sha256 checksum in metadata when x-amz-checksum-sha256 header is provided', done => {
871+
const sha256Value = crypto.createHash('sha256').update(postBody).digest('base64');
872+
const request = new DummyRequest({
873+
bucketName,
874+
namespace,
875+
objectKey: objectName,
876+
headers: {
877+
host: `${bucketName}.s3.amazonaws.com`,
878+
'x-amz-checksum-sha256': sha256Value,
879+
},
880+
url: '/',
881+
}, postBody);
882+
883+
bucketPut(authInfo, testPutBucketRequest, log, err => {
884+
assert.ifError(err);
885+
objectPut(authInfo, request, undefined, log, err => {
886+
assert.ifError(err);
887+
metadata.getObjectMD(bucketName, objectName, {}, log, (err, md) => {
888+
assert.ifError(err);
889+
assert(md.checksum, 'checksum should be set in metadata');
890+
assert.strictEqual(md.checksum.checksumAlgorithm, 'sha256');
891+
assert.strictEqual(md.checksum.checksumValue, sha256Value);
892+
assert.strictEqual(md.checksum.checksumType, 'FULL_OBJECT');
893+
done();
894+
});
895+
});
896+
});
897+
});
898+
899+
it('should store crc64nvme checksum in metadata when no checksum header is provided', done => {
900+
bucketPut(authInfo, testPutBucketRequest, log, err => {
901+
assert.ifError(err);
902+
objectPut(authInfo, testPutObjectRequest, undefined, log, err => {
903+
assert.ifError(err);
904+
metadata.getObjectMD(bucketName, objectName, {}, log, (err, md) => {
905+
assert.ifError(err);
906+
assert(md.checksum, 'checksum should be set in metadata');
907+
assert.strictEqual(md.checksum.checksumAlgorithm, 'crc64nvme');
908+
assert(md.checksum.checksumValue, 'checksumValue should be set');
909+
assert.strictEqual(md.checksum.checksumType, 'FULL_OBJECT');
910+
done();
911+
});
912+
});
913+
});
914+
});
868915
});
869916

870917
describe('objectPut API with versioning', () => {

0 commit comments

Comments
 (0)