Skip to content

Commit

Permalink
Merge branch 'bugfix/CLDSRV-583' into q/8.8
Browse files Browse the repository at this point in the history
  • Loading branch information
bert-e committed Nov 25, 2024
2 parents f6870d1 + c6e8841 commit 738d99f
Show file tree
Hide file tree
Showing 5 changed files with 196 additions and 39 deletions.
28 changes: 28 additions & 0 deletions lib/api/apiUtils/authorization/prepareRequestContexts.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const { policies } = require('arsenal');
const { config } = require('../../../Config');
const { hasGovernanceBypassHeader } = require('../object/objectLockHelpers');

const { RequestContext, requestUtils } = policies;
let apiMethodAfterVersionCheck;
Expand Down Expand Up @@ -193,13 +194,28 @@ function prepareRequestContexts(apiMethod, request, sourceBucket,
const putObjectLockRequestContext =
generateRequestContext('objectPutRetention');
requestContexts.push(putObjectLockRequestContext);
if (hasGovernanceBypassHeader(request.headers)) {
const checkUserGovernanceBypassRequestContext =
generateRequestContext('bypassGovernanceRetention');
requestContexts.push(checkUserGovernanceBypassRequestContext);
}
}
if (request.headers['x-amz-version-id']) {
const putObjectVersionRequestContext =
generateRequestContext('objectPutTaggingVersion');
requestContexts.push(putObjectVersionRequestContext);
}
}
} else if (apiMethodAfterVersionCheck === 'objectPutRetention' ||
apiMethodAfterVersionCheck === 'objectPutRetentionVersion') {
const putRetentionRequestContext =
generateRequestContext(apiMethodAfterVersionCheck);
requestContexts.push(putRetentionRequestContext);
if (hasGovernanceBypassHeader(request.headers)) {
const checkUserGovernanceBypassRequestContext =
generateRequestContext('bypassGovernanceRetention');
requestContexts.push(checkUserGovernanceBypassRequestContext);
}
} else if (apiMethodAfterVersionCheck === 'initiateMultipartUpload' ||
apiMethodAfterVersionCheck === 'objectPutPart' ||
apiMethodAfterVersionCheck === 'completeMultipartUpload'
Expand Down Expand Up @@ -232,11 +248,23 @@ function prepareRequestContexts(apiMethod, request, sourceBucket,
generateRequestContext('objectPutTaggingVersion');
requestContexts.push(putObjectVersionRequestContext);
}
// AWS only returns an object lock error if a version id
// is specified, else continue to create a delete marker
} else if (sourceVersionId && apiMethodAfterVersionCheck === 'objectDeleteVersion') {
const deleteRequestContext =
generateRequestContext(apiMethodAfterVersionCheck);
requestContexts.push(deleteRequestContext);
if (hasGovernanceBypassHeader(request.headers)) {
const checkUserGovernanceBypassRequestContext =
generateRequestContext('bypassGovernanceRetention');
requestContexts.push(checkUserGovernanceBypassRequestContext);
}
} else {
const requestContext =
generateRequestContext(apiMethodAfterVersionCheck);
requestContexts.push(requestContext);
}

return requestContexts;
}

Expand Down
24 changes: 3 additions & 21 deletions lib/api/objectDelete.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@ const { decodeVersionId, preprocessingVersioningDelete }
= require('./apiUtils/object/versioning');
const { standardMetadataValidateBucketAndObj } = require('../metadata/metadataUtils');
const monitoring = require('../utilities/monitoringHandler');
const { hasGovernanceBypassHeader, checkUserGovernanceBypass, ObjectLockInfo }
const { hasGovernanceBypassHeader, ObjectLockInfo }
= require('./apiUtils/object/objectLockHelpers');
const { isRequesterNonAccountUser } = require('./apiUtils/authorization/permissionChecks');
const { config } = require('../Config');
const { _bucketRequiresOplogUpdate } = require('./apiUtils/object/deleteObject');

Expand Down Expand Up @@ -50,6 +49,7 @@ function objectDeleteInternal(authInfo, request, log, isExpiration, cb) {
return cb(decodedVidResult);
}
const reqVersionId = decodedVidResult;
const hasGovernanceBypass = hasGovernanceBypassHeader(request.headers);

const valParams = {
authInfo,
Expand Down Expand Up @@ -101,25 +101,7 @@ function objectDeleteInternal(authInfo, request, log, isExpiration, cb) {
return next(null, bucketMD, objMD);
});
},
function checkGovernanceBypassHeader(bucketMD, objectMD, next) {
// AWS only returns an object lock error if a version id
// is specified, else continue to create a delete marker
if (!reqVersionId) {
return next(null, null, bucketMD, objectMD);
}
const hasGovernanceBypass = hasGovernanceBypassHeader(request.headers);
if (hasGovernanceBypass && isRequesterNonAccountUser(authInfo)) {
return checkUserGovernanceBypass(request, authInfo, bucketMD, objectKey, log, err => {
if (err) {
log.debug('user does not have BypassGovernanceRetention and object is locked');
return next(err, bucketMD);
}
return next(null, hasGovernanceBypass, bucketMD, objectMD);
});
}
return next(null, hasGovernanceBypass, bucketMD, objectMD);
},
function evaluateObjectLockPolicy(hasGovernanceBypass, bucketMD, objectMD, next) {
function evaluateObjectLockPolicy(bucketMD, objectMD, next) {
// AWS only returns an object lock error if a version id
// is specified, else continue to create a delete marker
if (!reqVersionId) {
Expand Down
20 changes: 3 additions & 17 deletions lib/api/objectPutRetention.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@ const { errors, s3middleware } = require('arsenal');

const { decodeVersionId, getVersionIdResHeader, getVersionSpecificMetadataOptions } =
require('./apiUtils/object/versioning');
const { ObjectLockInfo, checkUserGovernanceBypass, hasGovernanceBypassHeader } =
const { ObjectLockInfo, hasGovernanceBypassHeader } =
require('./apiUtils/object/objectLockHelpers');
const { standardMetadataValidateBucketAndObj } = require('../metadata/metadataUtils');
const { pushMetric } = require('../utapi/utilities');
const getReplicationInfo = require('./apiUtils/object/getReplicationInfo');
const collectCorsHeaders = require('../utilities/collectCorsHeaders');
const metadata = require('../metadata/wrapper');
const { isRequesterNonAccountUser } = require('./apiUtils/authorization/permissionChecks');
const { config } = require('../Config');

const { parseRetentionXml } = s3middleware.retention;
Expand Down Expand Up @@ -49,6 +48,8 @@ function objectPutRetention(authInfo, request, log, callback) {
request,
};

const hasGovernanceBypass = hasGovernanceBypassHeader(request.headers);

return async.waterfall([
next => {
log.trace('parsing retention information');
Expand Down Expand Up @@ -94,21 +95,6 @@ function objectPutRetention(authInfo, request, log, callback) {
return next(null, bucket, retentionInfo, objectMD);
}),
(bucket, retentionInfo, objectMD, next) => {
const hasGovernanceBypass = hasGovernanceBypassHeader(request.headers);
if (hasGovernanceBypass && isRequesterNonAccountUser(authInfo)) {
return checkUserGovernanceBypass(request, authInfo, bucket, objectKey, log, err => {
if (err) {
if (err.is.AccessDenied) {
log.debug('user does not have BypassGovernanceRetention and object is locked');
}
return next(err, bucket);
}
return next(null, bucket, retentionInfo, hasGovernanceBypass, objectMD);
});
}
return next(null, bucket, retentionInfo, hasGovernanceBypass, objectMD);
},
(bucket, retentionInfo, hasGovernanceBypass, objectMD, next) => {
const objLockInfo = new ObjectLockInfo({
mode: objectMD.retentionMode,
date: objectMD.retentionDate,
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zenko/cloudserver",
"version": "8.8.36",
"version": "8.8.37",
"description": "Zenko CloudServer, an open-source Node.js implementation of a server handling the Amazon S3 protocol",
"main": "index.js",
"engines": {
Expand Down
161 changes: 161 additions & 0 deletions tests/unit/api/apiUtils/authorization/prepareRequestContexts.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,167 @@ describe('prepareRequestContexts', () => {
assert.strictEqual(results[2].getAction(), 'scality:GetObjectArchiveInfo');
});

it('should return s3:PutObjectRetention with header x-amz-object-lock-mode', () => {
const apiMethod = 'objectPut';
const request = makeRequest({
'x-amz-object-lock-mode': 'GOVERNANCE',
'x-amz-object-lock-retain-until-date': '2021-12-31T23:59:59.000Z',
});
const results = prepareRequestContexts(apiMethod, request, sourceBucket, sourceObject, sourceVersionId);

assert.strictEqual(results.length, 2);
const expectedAction1 = 's3:PutObject';
const expectedAction2 = 's3:PutObjectRetention';
assert.strictEqual(results[0].getAction(), expectedAction1);
assert.strictEqual(results[1].getAction(), expectedAction2);
});

it('should return s3:PutObjectRetention and s3:BypassGovernanceRetention for objectPut ' +
'with header x-amz-bypass-governance-retention', () => {
const apiMethod = 'objectPut';
const request = makeRequest({
'x-amz-object-lock-mode': 'GOVERNANCE',
'x-amz-object-lock-retain-until-date': '2021-12-31T23:59:59.000Z',
'x-amz-bypass-governance-retention': 'true',
});
const results = prepareRequestContexts(apiMethod, request, sourceBucket, sourceObject, sourceVersionId);

assert.strictEqual(results.length, 3);
const expectedAction1 = 's3:PutObject';
const expectedAction2 = 's3:PutObjectRetention';
const expectedAction3 = 's3:BypassGovernanceRetention';
assert.strictEqual(results[0].getAction(), expectedAction1);
assert.strictEqual(results[1].getAction(), expectedAction2);
assert.strictEqual(results[2].getAction(), expectedAction3);
});

it('should return s3:PutObjectRetention and s3:BypassGovernanceRetention for objectPut ' +
'with header x-amz-bypass-governance-retention with version id specified', () => {
const apiMethod = 'objectPut';
const request = makeRequest({
'x-amz-object-lock-mode': 'GOVERNANCE',
'x-amz-object-lock-retain-until-date': '2021-12-31T23:59:59.000Z',
'x-amz-bypass-governance-retention': 'true',
}, {
versionId: 'vid1',
});
const results = prepareRequestContexts(apiMethod, request, sourceBucket, sourceObject, sourceVersionId);

assert.strictEqual(results.length, 3);
const expectedAction1 = 's3:PutObject';
const expectedAction2 = 's3:PutObjectRetention';
const expectedAction3 = 's3:BypassGovernanceRetention';
assert.strictEqual(results[0].getAction(), expectedAction1);
assert.strictEqual(results[1].getAction(), expectedAction2);
assert.strictEqual(results[2].getAction(), expectedAction3);
});

it('should return s3:PutObjectRetention with header x-amz-object-lock-mode for objectPutRetention action', () => {
const apiMethod = 'objectPutRetention';
const request = makeRequest({
'x-amz-object-lock-mode': 'GOVERNANCE',
'x-amz-object-lock-retain-until-date': '2021-12-31T23:59:59.000Z',
});
const results = prepareRequestContexts(apiMethod, request, sourceBucket, sourceObject, sourceVersionId);

assert.strictEqual(results.length, 1);
const expectedAction = 's3:PutObjectRetention';
assert.strictEqual(results[0].getAction(), expectedAction);
});

it('should return s3:PutObjectRetention and s3:BypassGovernanceRetention for objectPutRetention ' +
'with header x-amz-bypass-governance-retention', () => {
const apiMethod = 'objectPutRetention';
const request = makeRequest({
'x-amz-object-lock-mode': 'GOVERNANCE',
'x-amz-object-lock-retain-until-date': '2021-12-31T23:59:59.000Z',
'x-amz-bypass-governance-retention': 'true',
});
const results = prepareRequestContexts(apiMethod, request, sourceBucket, sourceObject, sourceVersionId);

assert.strictEqual(results.length, 2);
const expectedAction1 = 's3:PutObjectRetention';
const expectedAction2 = 's3:BypassGovernanceRetention';
assert.strictEqual(results[0].getAction(), expectedAction1);
assert.strictEqual(results[1].getAction(), expectedAction2);
});

it('should return s3:PutObjectRetention and s3:BypassGovernanceRetention for objectPutRetention ' +
'with header x-amz-bypass-governance-retention with version id specified', () => {
const apiMethod = 'objectPutRetention';
const request = makeRequest({
'x-amz-object-lock-mode': 'GOVERNANCE',
'x-amz-object-lock-retain-until-date': '2021-12-31T23:59:59.000Z',
'x-amz-bypass-governance-retention': 'true',
}, {
versionId: 'vid1',
});
const results = prepareRequestContexts(apiMethod, request, sourceBucket, sourceObject, sourceVersionId);

assert.strictEqual(results.length, 2);
const expectedAction1 = 's3:PutObjectRetention';
const expectedAction2 = 's3:BypassGovernanceRetention';
assert.strictEqual(results[0].getAction(), expectedAction1);
assert.strictEqual(results[1].getAction(), expectedAction2);
});

it('should return s3:DeleteObject for objectDelete method', () => {
const apiMethod = 'objectDelete';
const request = makeRequest();
const results = prepareRequestContexts(apiMethod, request, sourceBucket,
sourceObject, sourceVersionId);

assert.strictEqual(results.length, 1);
assert.strictEqual(results[0].getAction(), 's3:DeleteObject');
});

it('should return s3:DeleteObjectVersion for objectDelete method with version id specified', () => {
const apiMethod = 'objectDelete';
const request = makeRequest({}, {
versionId: 'vid1',
});
const results = prepareRequestContexts(apiMethod, request, sourceBucket,
sourceObject, sourceVersionId);

assert.strictEqual(results.length, 1);
assert.strictEqual(results[0].getAction(), 's3:DeleteObjectVersion');
});

// Now it shuld include the bypass header if set
it('should return s3:DeleteObjectVersion and s3:BypassGovernanceRetention for objectDelete method ' +
'with version id specified and x-amz-bypass-governance-retention header', () => {
const apiMethod = 'objectDelete';
const request = makeRequest({
'x-amz-bypass-governance-retention': 'true',
}, {
versionId: 'vid1',
});
const results = prepareRequestContexts(apiMethod, request, sourceBucket,
sourceObject, sourceVersionId);

assert.strictEqual(results.length, 2);
const expectedAction1 = 's3:DeleteObjectVersion';
const expectedAction2 = 's3:BypassGovernanceRetention';
assert.strictEqual(results[0].getAction(), expectedAction1);
assert.strictEqual(results[1].getAction(), expectedAction2);
});

// When there is no version ID, AWS does not return any error if the object
// is locked, but creates a delete marker
it('should only return s3:DeleteObject for objectDelete method ' +
'with x-amz-bypass-governance-retention header and no version id', () => {
const apiMethod = 'objectDelete';
const request = makeRequest({
'x-amz-bypass-governance-retention': 'true',
});
const results = prepareRequestContexts(apiMethod, request, sourceBucket,
sourceObject, sourceVersionId);

assert.strictEqual(results.length, 1);
const expectedAction = 's3:DeleteObject';
assert.strictEqual(results[0].getAction(), expectedAction);
});

['initiateMultipartUpload', 'objectPutPart', 'completeMultipartUpload'].forEach(apiMethod => {
it(`should return s3:PutObjectVersion request context action for ${apiMethod} method ` +
'with x-scal-s3-version-id header', () => {
Expand Down

0 comments on commit 738d99f

Please sign in to comment.