Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

INTEGRATION [PR#5470 > development/8.7] Improvement/cldsrv 430 delete api implicit deny #5477

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions lib/api/bucketDelete.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const { errors } = require('arsenal');

const collectCorsHeaders = require('../utilities/collectCorsHeaders');
const deleteBucket = require('./apiUtils/bucket/bucketDeletion');
const { metadataValidateBucket } = require('../metadata/metadataUtils');
const { standardMetadataValidateBucket } = require('../metadata/metadataUtils');
const { pushMetric } = require('../utapi/utilities');
const monitoring = require('../utilities/monitoringHandler');

Expand Down Expand Up @@ -34,7 +34,7 @@ function bucketDelete(authInfo, request, log, cb) {
request,
};

return metadataValidateBucket(metadataValParams, log,
return standardMetadataValidateBucket(metadataValParams, request.actionImplicitDenies, log,
(err, bucketMD) => {
const corsHeaders = collectCorsHeaders(request.headers.origin,
request.method, bucketMD);
Expand Down
3 changes: 2 additions & 1 deletion lib/api/bucketDeleteCors.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ function bucketDeleteCors(authInfo, request, log, callback) {
}
log.trace('found bucket in metadata');

if (!isBucketAuthorized(bucket, requestType, canonicalID, authInfo, log, request)) {
if (!isBucketAuthorized(bucket, requestType, canonicalID, authInfo, log, request,
request.actionImplicitDenies)) {
log.debug('access denied for user on bucket', {
requestType,
method: 'bucketDeleteCors',
Expand Down
4 changes: 2 additions & 2 deletions lib/api/bucketDeleteEncryption.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const async = require('async');

const metadata = require('../metadata/wrapper');
const { metadataValidateBucket } = require('../metadata/metadataUtils');
const { standardMetadataValidateBucket } = require('../metadata/metadataUtils');
const { pushMetric } = require('../utapi/utilities');
const collectCorsHeaders = require('../utilities/collectCorsHeaders');
const { checkExpectedBucketOwner } = require('./apiUtils/authorization/bucketOwner');
Expand All @@ -26,7 +26,7 @@ function bucketDeleteEncryption(authInfo, request, log, callback) {
};

return async.waterfall([
next => metadataValidateBucket(metadataValParams, log, next),
next => standardMetadataValidateBucket(metadataValParams, request.actionImplicitDenies, log, next),
(bucket, next) => checkExpectedBucketOwner(request.headers, bucket, log, err => next(err, bucket)),
(bucket, next) => {
const sseConfig = bucket.getServerSideEncryption();
Expand Down
4 changes: 2 additions & 2 deletions lib/api/bucketDeleteLifecycle.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const metadata = require('../metadata/wrapper');
const { metadataValidateBucket } = require('../metadata/metadataUtils');
const { standardMetadataValidateBucket } = require('../metadata/metadataUtils');
const { pushMetric } = require('../utapi/utilities');
const collectCorsHeaders = require('../utilities/collectCorsHeaders');
const monitoring = require('../utilities/monitoringHandler');
Expand All @@ -21,7 +21,7 @@ function bucketDeleteLifecycle(authInfo, request, log, callback) {
requestType: 'bucketDeleteLifecycle',
request,
};
return metadataValidateBucket(metadataValParams, log, (err, bucket) => {
return standardMetadataValidateBucket(metadataValParams, request.actionImplicitDenies, log, (err, bucket) => {
const corsHeaders = collectCorsHeaders(headers.origin, method, bucket);
if (err) {
log.debug('error processing request', {
Expand Down
4 changes: 2 additions & 2 deletions lib/api/bucketDeletePolicy.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const metadata = require('../metadata/wrapper');
const { metadataValidateBucket } = require('../metadata/metadataUtils');
const { standardMetadataValidateBucket } = require('../metadata/metadataUtils');
const collectCorsHeaders = require('../utilities/collectCorsHeaders');

/**
Expand All @@ -19,7 +19,7 @@ function bucketDeletePolicy(authInfo, request, log, callback) {
requestType: 'bucketDeletePolicy',
request,
};
return metadataValidateBucket(metadataValParams, log, (err, bucket) => {
return standardMetadataValidateBucket(metadataValParams, request.actionImplicitDenies, log, (err, bucket) => {
const corsHeaders = collectCorsHeaders(headers.origin, method, bucket);
if (err) {
log.debug('error processing request', {
Expand Down
4 changes: 2 additions & 2 deletions lib/api/bucketDeleteReplication.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const metadata = require('../metadata/wrapper');
const { metadataValidateBucket } = require('../metadata/metadataUtils');
const { standardMetadataValidateBucket } = require('../metadata/metadataUtils');
const { pushMetric } = require('../utapi/utilities');
const collectCorsHeaders = require('../utilities/collectCorsHeaders');
const monitoring = require('../utilities/monitoringHandler');
Expand All @@ -21,7 +21,7 @@ function bucketDeleteReplication(authInfo, request, log, callback) {
requestType: 'bucketDeleteReplication',
request,
};
return metadataValidateBucket(metadataValParams, log, (err, bucket) => {
return standardMetadataValidateBucket(metadataValParams, request.actionImplicitDenies, log, (err, bucket) => {
const corsHeaders = collectCorsHeaders(headers.origin, method, bucket);
if (err) {
log.debug('error processing request', {
Expand Down
3 changes: 2 additions & 1 deletion lib/api/bucketDeleteWebsite.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ function bucketDeleteWebsite(authInfo, request, log, callback) {
}
log.trace('found bucket in metadata');

if (!isBucketAuthorized(bucket, requestType, canonicalID, authInfo, log, request)) {
if (!isBucketAuthorized(bucket, requestType, canonicalID, authInfo, log, request,
request.actionImplicitDenies)) {
log.debug('access denied for user on bucket', {
requestType,
method: 'bucketDeleteWebsite',
Expand Down
112 changes: 64 additions & 48 deletions lib/api/multiObjectDelete.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const collectCorsHeaders = require('../utilities/collectCorsHeaders');
const metadata = require('../metadata/wrapper');
const services = require('../services');
const vault = require('../auth/vault');
const { isBucketAuthorized } =
const { isBucketAuthorized, evaluateBucketPolicyWithIAM } =
require('./apiUtils/authorization/permissionChecks');
const { preprocessingVersioningDelete }
= require('./apiUtils/object/versioning');
Expand Down Expand Up @@ -492,15 +492,46 @@ function multiObjectDelete(authInfo, request, log, callback) {
return next(null, quietSetting, objects);
});
},
function checkPolicies(quietSetting, objects, next) {
function checkBucketMetadata(quietSetting, objects, next) {
const errorResults = [];
return metadata.getBucket(bucketName, log, (err, bucketMD) => {
if (err) {
log.trace('error retrieving bucket metadata',
{ error: err });
return next(err);
}
// check whether bucket has transient or deleted flag
if (bucketShield(bucketMD, 'objectDelete')) {
return next(errors.NoSuchBucket);
}
if (!isBucketAuthorized(bucketMD, 'objectDelete', canonicalID, authInfo, log, request,
request.actionImplicitDenies)) {
log.trace("access denied due to bucket acl's");
// if access denied at the bucket level, no access for
// any of the objects so all results will be error results
objects.forEach(entry => {
errorResults.push({
entry,
error: errors.AccessDenied,
});
});
// by sending an empty array as the objects array
// async.forEachLimit below will not actually
// make any calls to metadata or data but will continue on
// to the next step to build xml
return next(null, quietSetting, errorResults, [], bucketMD);
}
return next(null, quietSetting, errorResults, objects, bucketMD);
});
},
function checkPolicies(quietSetting, errorResults, objects, bucketMD, next) {
// track keys that are still on track to be deleted
const inPlay = [];
const errorResults = [];
// if request from account, no need to check policies
// all objects are inPlay so send array of object keys
// as inPlay argument
if (!isRequesterNonAccountUser(authInfo)) {
return next(null, quietSetting, errorResults, objects);
return next(null, quietSetting, errorResults, objects, bucketMD);
}

// TODO: once arsenal's extractParams is separated from doAuth
Expand Down Expand Up @@ -544,7 +575,7 @@ function multiObjectDelete(authInfo, request, log, callback) {
error: errors.AccessDenied });
});
// send empty array for inPlay
return next(null, quietSetting, errorResults, []);
return next(null, quietSetting, errorResults, [], bucketMD);
}
if (err) {
log.trace('error checking policies', {
Expand All @@ -562,6 +593,13 @@ function multiObjectDelete(authInfo, request, log, callback) {
});
return next(errors.InternalError);
}
// Convert authorization results into an easier to handle format
const actionImplicitDenies = authorizationResults.reduce((acc, curr, idx) => {
const apiMethod = requestContextParams[idx].apiMethod;
// eslint-disable-next-line no-param-reassign
acc[apiMethod] = curr.isImplicit;
return acc;
}, {});
for (let i = 0; i < authorizationResults.length; i++) {
const result = authorizationResults[i];
// result is { isAllowed: true,
Expand All @@ -577,7 +615,26 @@ function multiObjectDelete(authInfo, request, log, callback) {
key: result.arn.slice(slashIndex + 1),
versionId: result.versionId,
};
if (result.isAllowed) {
// Deny immediately if there is an explicit deny
if (!result.isImplicit && !result.isAllowed) {
errorResults.push({
entry,
error: errors.AccessDenied,
});
continue;
}

// Evaluate against the bucket policies
const areAllActionsAllowed = evaluateBucketPolicyWithIAM(
bucketMD,
Object.keys(actionImplicitDenies),
canonicalID,
authInfo,
actionImplicitDenies,
log,
request);

if (areAllActionsAllowed) {
if (validObjectKeys.includes(entry.key)) {
inPlayInternal.push(entry.key);
} else {
Expand All @@ -590,50 +647,9 @@ function multiObjectDelete(authInfo, request, log, callback) {
});
}
}
return next(null, quietSetting, errorResults, inPlay);
return next(null, quietSetting, errorResults, inPlay, bucketMD);
});
},
function checkBucketMetadata(quietSetting, errorResults, inPlay, next) {
// if no objects in play, no need to check ACLs / get metadata,
// just move on if there is no Origin header
if (inPlay.length === 0 && !request.headers.origin) {
return next(null, quietSetting, errorResults, inPlay,
undefined);
}
return metadata.getBucket(bucketName, log, (err, bucketMD) => {
if (err) {
log.trace('error retrieving bucket metadata',
{ error: err });
return next(err);
}
// check whether bucket has transient or deleted flag
if (bucketShield(bucketMD, 'objectDelete')) {
return next(errors.NoSuchBucket);
}
// if no objects in play, no need to check ACLs
if (inPlay.length === 0) {
return next(null, quietSetting, errorResults, inPlay,
bucketMD);
}
if (!isBucketAuthorized(bucketMD, 'objectDelete', canonicalID, authInfo, log, request)) {
log.trace("access denied due to bucket acl's");
// if access denied at the bucket level, no access for
// any of the objects so all results will be error results
inPlay.forEach(entry => {
errorResults.push({
entry,
error: errors.AccessDenied,
});
});
// by sending an empty array as the inPlay array
// async.forEachLimit below will not actually
// make any calls to metadata or data but will continue on
// to the next step to build xml
return next(null, quietSetting, errorResults, [], bucketMD);
}
return next(null, quietSetting, errorResults, inPlay, bucketMD);
});
},
function handleInternalFiles(quietSetting, errorResults, inPlay, bucketMD, next) {
return async.each(inPlayInternal,
(localInPlay, next) => deleteVeeamCapabilities(bucketName, localInPlay, bucketMD, log, next),
Expand Down
4 changes: 2 additions & 2 deletions lib/api/objectDelete.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const { pushMetric } = require('../utapi/utilities');
const createAndStoreObject = require('./apiUtils/object/createAndStoreObject');
const { decodeVersionId, preprocessingVersioningDelete }
= require('./apiUtils/object/versioning');
const { metadataValidateBucketAndObj } = require('../metadata/metadataUtils');
const { standardMetadataValidateBucketAndObj } = require('../metadata/metadataUtils');
const monitoring = require('../utilities/monitoringHandler');
const { hasGovernanceBypassHeader, checkUserGovernanceBypass, ObjectLockInfo }
= require('./apiUtils/object/objectLockHelpers');
Expand Down Expand Up @@ -63,7 +63,7 @@ function objectDeleteInternal(authInfo, request, log, isExpiration, cb) {
const canonicalID = authInfo.getCanonicalID();
return async.waterfall([
function validateBucketAndObj(next) {
return metadataValidateBucketAndObj(valParams, log,
return standardMetadataValidateBucketAndObj(valParams, request.actionImplicitDenies, log,
(err, bucketMD, objMD) => {
if (err) {
return next(err, bucketMD);
Expand Down
4 changes: 2 additions & 2 deletions lib/api/objectDeleteTagging.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const { errors } = require('arsenal');
const { decodeVersionId, getVersionIdResHeader, getVersionSpecificMetadataOptions }
= require('./apiUtils/object/versioning');

const { metadataValidateBucketAndObj } = require('../metadata/metadataUtils');
const { standardMetadataValidateBucketAndObj } = require('../metadata/metadataUtils');
const { pushMetric } = require('../utapi/utilities');
const monitoring = require('../utilities/monitoringHandler');
const collectCorsHeaders = require('../utilities/collectCorsHeaders');
Expand Down Expand Up @@ -49,7 +49,7 @@ function objectDeleteTagging(authInfo, request, log, callback) {
};

return async.waterfall([
next => metadataValidateBucketAndObj(metadataValParams, log,
next => standardMetadataValidateBucketAndObj(metadataValParams, request.actionImplicitDenies, log,
(err, bucket, objectMD) => {
if (err) {
log.trace('request authorization failed',
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.7.34",
"version": "8.7.35",
"description": "Zenko CloudServer, an open-source Node.js implementation of a server handling the Amazon S3 protocol",
"main": "index.js",
"engines": {
Expand Down
2 changes: 2 additions & 0 deletions tests/unit/api/bucketDelete.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ describe('bucketDelete API', () => {
namespace,
headers: {},
url: `/${bucketName}`,
actionImplicitDenies: false,
};

const initiateRequest = {
Expand All @@ -95,6 +96,7 @@ describe('bucketDelete API', () => {
objectKey: objectName,
headers: { host: `${bucketName}.s3.amazonaws.com` },
url: `/${objectName}?uploads`,
actionImplicitDenies: false,
};

it('should return an error if the bucket is not empty', done => {
Expand Down
1 change: 1 addition & 0 deletions tests/unit/api/bucketDeleteCors.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const testBucketPutRequest = {
bucketName,
headers: { host: `${bucketName}.s3.amazonaws.com` },
url: '/',
actionImplicitDenies: false,
};
const testBucketPutCorsRequest =
corsUtil.createBucketCorsRequest('PUT', bucketName);
Expand Down
1 change: 1 addition & 0 deletions tests/unit/api/bucketDeleteEncryption.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const bucketPutRequest = {
bucketName,
headers: { host: `${bucketName}.s3.amazonaws.com` },
url: '/',
actionImplicitDenies: false,
};

describe('bucketDeleteEncryption API', () => {
Expand Down
1 change: 1 addition & 0 deletions tests/unit/api/bucketDeleteLifecycle.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ function _makeRequest(includeXml) {
bucketName,
headers: { host: `${bucketName}.s3.amazonaws.com` },
url: '/',
actionImplicitDenies: false,
};
if (includeXml) {
request.post = '<LifecycleConfiguration ' +
Expand Down
1 change: 1 addition & 0 deletions tests/unit/api/bucketDeletePolicy.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ function _makeRequest(includePolicy) {
bucketName,
headers: { host: `${bucketName}.s3.amazonaws.com` },
url: '/',
actionImplicitDenies: false,
};
if (includePolicy) {
const examplePolicy = {
Expand Down
2 changes: 2 additions & 0 deletions tests/unit/api/bucketDeleteWebsite.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const testBucketPutRequest = {
bucketName,
headers: { host: `${bucketName}.s3.amazonaws.com` },
url: '/',
actionImplicitDenies: false,
};
const testBucketDeleteWebsiteRequest = {
bucketName,
Expand All @@ -28,6 +29,7 @@ const testBucketDeleteWebsiteRequest = {
},
url: '/?website',
query: { website: '' },
actionImplicitDenies: false,
};
const testBucketPutWebsiteRequest = Object.assign({ post: config.getXml() },
testBucketDeleteWebsiteRequest);
Expand Down
1 change: 1 addition & 0 deletions tests/unit/api/objectDeleteTagging.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const testBucketPutRequest = {
bucketName,
headers: { host: `${bucketName}.s3.amazonaws.com` },
url: '/',
actionImplicitDenies: false,
};

const testPutObjectRequest = new DummyRequest({
Expand Down
Loading