Compare commits

...

9 Commits

Author SHA1 Message Date
Maha Benzekri 33d7c99e0c
CLDSRV-427: Improving functions using helper function
- In this commit , I added a helper (processBucketPolicy) function
for the bycket policies checks that are shared between the
isbucketAuthorized, isObjAuthorized and evaluateBucketPolicyWithIAM
for a better code readability and to avoid long functions.
2023-11-10 11:56:32 +01:00
Maha Benzekri 6fb225eb5c
CLDSRV-427: functions refactor 2023-11-10 11:36:35 +01:00
Maha Benzekri a7396a721c
CLDSRV-427: linting fixups and retrocompatibility changes 2023-11-10 11:36:35 +01:00
Will Toozs 7aa326cba9
CLDSRV-427: update bucket/object perm checks to account for implicit …
…denies

(cherry picked from commit c01898f1a0)
2023-11-10 11:36:34 +01:00
Will Toozs 10a0672b68
CLDSRV-426: add tests for ACL permission check updates
CLDSRV-426: additionnal test for ACL permission
2023-11-10 11:36:34 +01:00
Will Toozs 698f5a44d4
CLDSRV-426: update ACL permission checks for implicitDeny logic
CLDSRV-426:fixups on ACL permission checks for implicitDeny logic

CLDSRV-426:better readability on ACL permission
2023-11-10 11:36:34 +01:00
Maha Benzekri 46ce0a9082
CLDSRV-424:CLDSRV version bump
Update lib/api/api.js

Co-authored-by: Jonathan Gramain <jonathan.gramain@scality.com>
2023-11-10 11:36:34 +01:00
Maha Benzekri 9712ebd12a
CLDSRV-424:ARSN version bump 2023-11-10 11:36:33 +01:00
Will Toozs 2b0eb9ddd1
CLDSRV-424: api call updated with implicit deny logic
change variable names for clarity

edit: update arsenal package
2023-11-10 11:36:33 +01:00
6 changed files with 548 additions and 89 deletions

View File

@ -174,6 +174,11 @@ const constants = {
'user', 'user',
'bucket', 'bucket',
], ],
arrayOfAllowed: [
'objectPutTagging',
'objectPutLegalHold',
'objectPutRetention',
],
allowedUtapiEventFilterStates: ['allow', 'deny'], allowedUtapiEventFilterStates: ['allow', 'deny'],
// The AWS assumed Role resource type // The AWS assumed Role resource type
assumedRoleArnResourceType: 'assumed-role', assumedRoleArnResourceType: 'assumed-role',

View File

@ -107,6 +107,7 @@ const api = {
// no need to check auth on website or cors preflight requests // no need to check auth on website or cors preflight requests
if (apiMethod === 'websiteGet' || apiMethod === 'websiteHead' || if (apiMethod === 'websiteGet' || apiMethod === 'websiteHead' ||
apiMethod === 'corsPreflight') { apiMethod === 'corsPreflight') {
request.actionImplicitDenies = false;
return this[apiMethod](request, log, callback); return this[apiMethod](request, log, callback);
} }
@ -129,15 +130,25 @@ const api = {
const requestContexts = prepareRequestContexts(apiMethod, request, const requestContexts = prepareRequestContexts(apiMethod, request,
sourceBucket, sourceObject, sourceVersionId); sourceBucket, sourceObject, sourceVersionId);
// Extract all the _apiMethods and store them in an array
const apiMethods = requestContexts ? requestContexts.map(context => context._apiMethod) : [];
// Attach the names to the current request
// eslint-disable-next-line no-param-reassign
request.apiMethods = apiMethods;
function checkAuthResults(authResults) { function checkAuthResults(authResults) {
let returnTagCount = true; let returnTagCount = true;
const isImplicitDeny = {};
let isOnlyImplicitDeny = true;
if (apiMethod === 'objectGet') { if (apiMethod === 'objectGet') {
// first item checks s3:GetObject(Version) action // first item checks s3:GetObject(Version) action
if (!authResults[0].isAllowed) { if (!authResults[0].isAllowed && !authResults[0].isImplicit) {
log.trace('get object authorization denial from Vault'); log.trace('get object authorization denial from Vault');
return errors.AccessDenied; return errors.AccessDenied;
} }
// TODO add support for returnTagCount in the bucket policy
// checks
isImplicitDeny[authResults[0].action] = authResults[0].isImplicit;
// second item checks s3:GetObject(Version)Tagging action // second item checks s3:GetObject(Version)Tagging action
if (!authResults[1].isAllowed) { if (!authResults[1].isAllowed) {
log.trace('get tagging authorization denial ' + log.trace('get tagging authorization denial ' +
@ -146,13 +157,26 @@ const api = {
} }
} else { } else {
for (let i = 0; i < authResults.length; i++) { for (let i = 0; i < authResults.length; i++) {
if (!authResults[i].isAllowed) { isImplicitDeny[authResults[i].action] = true;
if (!authResults[i].isAllowed && !authResults[i].isImplicit) {
// Any explicit deny rejects the current API call
log.trace('authorization denial from Vault'); log.trace('authorization denial from Vault');
return errors.AccessDenied; return errors.AccessDenied;
} }
if (authResults[i].isAllowed) {
// If the action is allowed, the result is not implicit
// Deny.
isImplicitDeny[authResults[i].action] = false;
isOnlyImplicitDeny = false;
}
} }
} }
return returnTagCount; // These two APIs cannot use ACLs or Bucket Policies, hence, any
// implicit deny from vault must be treated as an explicit deny.
if ((apiMethod === 'bucketPut' || apiMethod === 'serviceGet') && isOnlyImplicitDeny) {
return errors.AccessDenied;
}
return { returnTagCount, isImplicitDeny };
} }
return async.waterfall([ return async.waterfall([
@ -230,7 +254,16 @@ const api = {
if (checkedResults instanceof Error) { if (checkedResults instanceof Error) {
return callback(checkedResults); return callback(checkedResults);
} }
returnTagCount = checkedResults; returnTagCount = checkedResults.returnTagCount;
request.actionImplicitDenies = checkedResults.isImplicitDeny;
} else {
// create an object of keys apiMethods with all values to false:
// for backward compatibility, all apiMethods are allowed by default
// thus it is explicitly allowed, so implicit deny is false
request.actionImplicitDenies = apiMethods.reduce((acc, curr) => {
acc[curr] = false;
return acc;
}, {});
} }
if (apiMethod === 'objectPut' || apiMethod === 'objectPutPart') { if (apiMethod === 'objectPut' || apiMethod === 'objectPutPart') {
request._response = response; request._response = response;

View File

@ -1,20 +1,35 @@
const { evaluators, actionMaps, RequestContext } = require('arsenal').policies; const { evaluators, actionMaps, RequestContext } = require('arsenal').policies;
const constants = require('../../../../constants'); const constants = require('../../../../constants');
const { allAuthedUsersId, bucketOwnerActions, logId, publicId, const {
assumedRoleArnResourceType, backbeatLifecycleSessionName } = constants; allAuthedUsersId, bucketOwnerActions, logId, publicId,
assumedRoleArnResourceType, backbeatLifecycleSessionName, arrayOfAllowed,
} = constants;
// whitelist buckets to allow public read on objects // whitelist buckets to allow public read on objects
const publicReadBuckets = process.env.ALLOW_PUBLIC_READ_BUCKETS ? const publicReadBuckets = process.env.ALLOW_PUBLIC_READ_BUCKETS
process.env.ALLOW_PUBLIC_READ_BUCKETS.split(',') : []; ? process.env.ALLOW_PUBLIC_READ_BUCKETS.split(',') : [];
function checkBucketAcls(bucket, requestType, canonicalID) { function checkBucketAcls(bucket, requestType, canonicalID, mainApiCall) {
// Same logic applies on the Versioned APIs, so let's simplify it.
const requestTypeParsed = requestType.endsWith('Version')
? requestType.slice(0, -7) : requestType;
if (bucket.getOwner() === canonicalID) { if (bucket.getOwner() === canonicalID) {
return true; return true;
} }
if (mainApiCall === 'objectGet') {
if (requestTypeParsed === 'objectGetTagging') {
return true;
}
}
if (mainApiCall === 'objectPut') {
if (arrayOfAllowed.includes(requestTypeParsed)) {
return true;
}
}
const bucketAcl = bucket.getAcl(); const bucketAcl = bucket.getAcl();
if (requestType === 'bucketGet' || requestType === 'bucketHead') { if (requestTypeParsed === 'bucketGet' || requestTypeParsed === 'bucketHead') {
if (bucketAcl.Canned === 'public-read' if (bucketAcl.Canned === 'public-read'
|| bucketAcl.Canned === 'public-read-write' || bucketAcl.Canned === 'public-read-write'
|| (bucketAcl.Canned === 'authenticated-read' || (bucketAcl.Canned === 'authenticated-read'
@ -32,7 +47,7 @@ function checkBucketAcls(bucket, requestType, canonicalID) {
return true; return true;
} }
} }
if (requestType === 'bucketGetACL') { if (requestTypeParsed === 'bucketGetACL') {
if ((bucketAcl.Canned === 'log-delivery-write' if ((bucketAcl.Canned === 'log-delivery-write'
&& canonicalID === logId) && canonicalID === logId)
|| bucketAcl.FULL_CONTROL.indexOf(canonicalID) > -1 || bucketAcl.FULL_CONTROL.indexOf(canonicalID) > -1
@ -48,7 +63,7 @@ function checkBucketAcls(bucket, requestType, canonicalID) {
} }
} }
if (requestType === 'bucketPutACL') { if (requestTypeParsed === 'bucketPutACL') {
if (bucketAcl.FULL_CONTROL.indexOf(canonicalID) > -1 if (bucketAcl.FULL_CONTROL.indexOf(canonicalID) > -1
|| bucketAcl.WRITE_ACP.indexOf(canonicalID) > -1) { || bucketAcl.WRITE_ACP.indexOf(canonicalID) > -1) {
return true; return true;
@ -62,11 +77,7 @@ function checkBucketAcls(bucket, requestType, canonicalID) {
} }
} }
if (requestType === 'bucketDelete' && bucket.getOwner() === canonicalID) { if (requestTypeParsed === 'objectDelete' || requestTypeParsed === 'objectPut') {
return true;
}
if (requestType === 'objectDelete' || requestType === 'objectPut') {
if (bucketAcl.Canned === 'public-read-write' if (bucketAcl.Canned === 'public-read-write'
|| bucketAcl.FULL_CONTROL.indexOf(canonicalID) > -1 || bucketAcl.FULL_CONTROL.indexOf(canonicalID) > -1
|| bucketAcl.WRITE.indexOf(canonicalID) > -1) { || bucketAcl.WRITE.indexOf(canonicalID) > -1) {
@ -86,11 +97,12 @@ function checkBucketAcls(bucket, requestType, canonicalID) {
// objectPutACL, objectGetACL, objectHead or objectGet, the bucket // objectPutACL, objectGetACL, objectHead or objectGet, the bucket
// authorization check should just return true so can move on to check // authorization check should just return true so can move on to check
// rights at the object level. // rights at the object level.
return (requestType === 'objectPutACL' || requestType === 'objectGetACL' || return (requestTypeParsed === 'objectPutACL' || requestTypeParsed === 'objectGetACL'
requestType === 'objectGet' || requestType === 'objectHead'); || requestTypeParsed === 'objectGet' || requestTypeParsed === 'objectHead');
} }
function checkObjectAcls(bucket, objectMD, requestType, canonicalID) { function checkObjectAcls(bucket, objectMD, requestType, canonicalID, requesterIsNotUser,
isUserUnauthenticated, mainApiCall) {
const bucketOwner = bucket.getOwner(); const bucketOwner = bucket.getOwner();
// acls don't distinguish between users and accounts, so both should be allowed // acls don't distinguish between users and accounts, so both should be allowed
if (bucketOwnerActions.includes(requestType) if (bucketOwnerActions.includes(requestType)
@ -100,6 +112,15 @@ function checkObjectAcls(bucket, objectMD, requestType, canonicalID) {
if (objectMD['owner-id'] === canonicalID) { if (objectMD['owner-id'] === canonicalID) {
return true; return true;
} }
// Backward compatibility
if (mainApiCall === 'objectGet') {
if ((isUserUnauthenticated || (requesterIsNotUser && bucketOwner === objectMD['owner-id']))
&& requestType === 'objectGetTagging') {
return true;
}
}
if (!objectMD.acl) { if (!objectMD.acl) {
return false; return false;
} }
@ -169,9 +190,9 @@ function checkObjectAcls(bucket, objectMD, requestType, canonicalID) {
// allow public reads on buckets that are whitelisted for anonymous reads // allow public reads on buckets that are whitelisted for anonymous reads
// TODO: remove this after bucket policies are implemented // TODO: remove this after bucket policies are implemented
const bucketAcl = bucket.getAcl(); const bucketAcl = bucket.getAcl();
const allowPublicReads = publicReadBuckets.includes(bucket.getName()) && const allowPublicReads = publicReadBuckets.includes(bucket.getName())
bucketAcl.Canned === 'public-read' && && bucketAcl.Canned === 'public-read'
(requestType === 'objectGet' || requestType === 'objectHead'); && (requestType === 'objectGet' || requestType === 'objectHead');
if (allowPublicReads) { if (allowPublicReads) {
return true; return true;
} }
@ -268,75 +289,157 @@ function checkBucketPolicy(policy, requestType, canonicalID, arn, bucketOwner, l
return permission; return permission;
} }
function isBucketAuthorized(bucket, requestType, canonicalID, authInfo, log, request) { function processBucketPolicy(requestType, bucket, canonicalID, arn, bucketOwner, log,
// Check to see if user is authorized to perform a request, aclPermission, results, actionImplicitDenies) {
// particular action on bucket based on ACLs.
// TODO: Add IAM checks
let requesterIsNotUser = true;
let arn = null;
if (authInfo) {
requesterIsNotUser = !authInfo.isRequesterAnIAMUser();
arn = authInfo.getArn();
}
// if the bucket owner is an account, users should not have default access
if ((bucket.getOwner() === canonicalID) && requesterIsNotUser) {
return true;
}
const aclPermission = checkBucketAcls(bucket, requestType, canonicalID);
const bucketPolicy = bucket.getBucketPolicy(); const bucketPolicy = bucket.getBucketPolicy();
if (!bucketPolicy) { if (!bucketPolicy) {
return aclPermission; // eslint-disable-next-line no-param-reassign
results[requestType] = actionImplicitDenies[requestType] === false && aclPermission;
} else {
const bucketPolicyPermission = checkBucketPolicy(bucketPolicy, requestType, canonicalID, arn,
bucketOwner, log, request);
if (bucketPolicyPermission === 'explicitDeny') {
// eslint-disable-next-line no-param-reassign
results[requestType] = false;
} else if (bucketPolicyPermission === 'allow') {
// eslint-disable-next-line no-param-reassign
results[requestType] = true;
} else {
// eslint-disable-next-line no-param-reassign
results[requestType] = actionImplicitDenies[requestType] === false && aclPermission;
}
} }
const bucketPolicyPermission = checkBucketPolicy(bucketPolicy, requestType, return results[requestType];
canonicalID, arn, bucket.getOwner(), log, request);
if (bucketPolicyPermission === 'explicitDeny') {
return false;
}
return (aclPermission || (bucketPolicyPermission === 'allow'));
} }
function isObjAuthorized(bucket, objectMD, requestType, canonicalID, authInfo, log, request) { function isBucketAuthorized(bucket, requestTypes, canonicalID, authInfo, log, request, actionImplicitDenies = {}) {
const bucketOwner = bucket.getOwner(); if (!Array.isArray(requestTypes)) {
if (!objectMD) { // eslint-disable-next-line no-param-reassign
requestTypes = [requestTypes];
}
if (!actionImplicitDenies) {
// eslint-disable-next-line no-param-reassign
actionImplicitDenies = {};
}
const mainApiCall = requestTypes[0];
const results = {};
return requestTypes.every(_requestType => {
// By default, all missing actions are defined as allowed from IAM, to be
// backward compatible
if (actionImplicitDenies[_requestType] === undefined) {
// eslint-disable-next-line no-param-reassign
actionImplicitDenies[_requestType] = false;
}
// Check to see if user is authorized to perform a
// particular action on bucket based on ACLs.
// TODO: Add IAM checks
let requesterIsNotUser = true;
let arn = null;
if (authInfo) {
requesterIsNotUser = !authInfo.isRequesterAnIAMUser();
arn = authInfo.getArn();
}
// if the bucket owner is an account, users should not have default access
if ((bucket.getOwner() === canonicalID) && requesterIsNotUser) {
results[_requestType] = actionImplicitDenies[_requestType] === false;
return results[_requestType];
}
const aclPermission = checkBucketAcls(bucket, _requestType, canonicalID, mainApiCall);
return processBucketPolicy(_requestType, bucket, canonicalID, arn, bucket.getOwner(), log,
request, aclPermission, results, actionImplicitDenies);
});
}
function evaluateBucketPolicyWithIAM(bucket, requestTypes, canonicalID, authInfo, actionImplicitDenies = {},
log, request) {
if (!Array.isArray(requestTypes)) {
// eslint-disable-next-line no-param-reassign
requestTypes = [requestTypes];
}
if (!actionImplicitDenies) {
// eslint-disable-next-line no-param-reassign
actionImplicitDenies = {};
}
const results = {};
return requestTypes.every(_requestType => {
// By default, all missing actions are defined as allowed from IAM, to be
// backward compatible
if (actionImplicitDenies[_requestType] === undefined) {
// eslint-disable-next-line no-param-reassign
actionImplicitDenies[_requestType] = false;
}
let arn = null;
if (authInfo) {
arn = authInfo.getArn();
}
return processBucketPolicy(_requestType, bucket, canonicalID, arn, bucket.getOwner(), log,
request, true, results, actionImplicitDenies);
});
}
function isObjAuthorized(bucket, objectMD, requestTypes, canonicalID, authInfo, log, request,
actionImplicitDenies = {}) {
if (!Array.isArray(requestTypes)) {
// eslint-disable-next-line no-param-reassign
requestTypes = [requestTypes];
}
if (!actionImplicitDenies) {
// eslint-disable-next-line no-param-reassign
actionImplicitDenies = {};
}
const results = {};
const mainApiCall = requestTypes[0];
return requestTypes.every(_requestType => {
// By default, all missing actions are defined as allowed from IAM, to be
// backward compatible
if (actionImplicitDenies[_requestType] === undefined) {
// eslint-disable-next-line no-param-reassign
actionImplicitDenies[_requestType] = false;
}
const parsedMethodName = _requestType.endsWith('Version')
? _requestType.slice(0, -7) : _requestType;
const bucketOwner = bucket.getOwner();
if (!objectMD) {
// User is already authorized on the bucket for FULL_CONTROL or WRITE or // User is already authorized on the bucket for FULL_CONTROL or WRITE or
// bucket has canned ACL public-read-write // bucket has canned ACL public-read-write
if (requestType === 'objectPut' || requestType === 'objectDelete') { if (parsedMethodName === 'objectPut' || parsedMethodName === 'objectDelete') {
return true; results[_requestType] = actionImplicitDenies[_requestType] === false;
return results[_requestType];
}
// check bucket has read access
// 'bucketGet' covers listObjects and listMultipartUploads, bucket read actions
results[_requestType] = isBucketAuthorized(bucket, 'bucketGet', canonicalID, authInfo, log, request);
return results[_requestType];
} }
// check bucket has read access let requesterIsNotUser = true;
// 'bucketGet' covers listObjects and listMultipartUploads, bucket read actions let arn = null;
return isBucketAuthorized(bucket, 'bucketGet', canonicalID, authInfo, log, request); let isUserUnauthenticated = false;
} if (authInfo) {
let requesterIsNotUser = true; requesterIsNotUser = !authInfo.isRequesterAnIAMUser();
let arn = null; arn = authInfo.getArn();
if (authInfo) { isUserUnauthenticated = arn === undefined;
requesterIsNotUser = !authInfo.isRequesterAnIAMUser(); }
arn = authInfo.getArn(); if (objectMD['owner-id'] === canonicalID && requesterIsNotUser) {
} results[_requestType] = actionImplicitDenies[_requestType] === false;
if (objectMD['owner-id'] === canonicalID && requesterIsNotUser) { return results[_requestType];
return true; }
} // account is authorized if:
// account is authorized if: // - requesttype is included in bucketOwnerActions and
// - requesttype is included in bucketOwnerActions and // - account is the bucket owner
// - account is the bucket owner // - requester is account, not user
// - requester is account, not user if (bucketOwnerActions.includes(parsedMethodName)
if (bucketOwnerActions.includes(requestType)
&& (bucketOwner === canonicalID) && (bucketOwner === canonicalID)
&& requesterIsNotUser) { && requesterIsNotUser) {
return true; results[_requestType] = actionImplicitDenies[_requestType] === false;
} return results[_requestType];
const aclPermission = checkObjectAcls(bucket, objectMD, requestType, }
canonicalID); const aclPermission = checkObjectAcls(bucket, objectMD, parsedMethodName,
const bucketPolicy = bucket.getBucketPolicy(); canonicalID, requesterIsNotUser, isUserUnauthenticated, mainApiCall);
if (!bucketPolicy) { return processBucketPolicy(_requestType, bucket, canonicalID, arn, bucketOwner,
return aclPermission; log, request, aclPermission, results, actionImplicitDenies);
} });
const bucketPolicyPermission = checkBucketPolicy(bucketPolicy, requestType,
canonicalID, arn, bucket.getOwner(), log, request);
if (bucketPolicyPermission === 'explicitDeny') {
return false;
}
return (aclPermission || (bucketPolicyPermission === 'allow'));
} }
function _checkResource(resource, bucketArn) { function _checkResource(resource, bucketArn) {
@ -383,9 +486,9 @@ function isLifecycleSession(arn) {
const resourceType = resourceNames[0]; const resourceType = resourceNames[0];
const sessionName = resourceNames[resourceNames.length - 1]; const sessionName = resourceNames[resourceNames.length - 1];
return (service === 'sts' && return (service === 'sts'
resourceType === assumedRoleArnResourceType && && resourceType === assumedRoleArnResourceType
sessionName === backbeatLifecycleSessionName); && sessionName === backbeatLifecycleSessionName);
} }
module.exports = { module.exports = {
@ -395,4 +498,5 @@ module.exports = {
checkObjectAcls, checkObjectAcls,
validatePolicyResource, validatePolicyResource,
isLifecycleSession, isLifecycleSession,
evaluateBucketPolicyWithIAM,
}; };

View File

@ -20,7 +20,7 @@
"homepage": "https://github.com/scality/S3#readme", "homepage": "https://github.com/scality/S3#readme",
"dependencies": { "dependencies": {
"@hapi/joi": "^17.1.0", "@hapi/joi": "^17.1.0",
"arsenal": "git+https://github.com/scality/arsenal#7.10.48", "arsenal": "git+https://github.com/scality/arsenal#7.10.49",
"async": "~2.5.0", "async": "~2.5.0",
"aws-sdk": "2.905.0", "aws-sdk": "2.905.0",
"azure-storage": "^2.1.0", "azure-storage": "^2.1.0",

View File

@ -0,0 +1,317 @@
const assert = require('assert');
const { checkBucketAcls, checkObjectAcls } = require('../../../lib/api/apiUtils/authorization/permissionChecks');
const constants = require('../../../constants');
const { bucketOwnerActions, logId } = constants;
describe('checkBucketAcls', () => {
const mockBucket = {
getOwner: () => 'ownerId',
getAcl: () => ({
Canned: '',
FULL_CONTROL: [],
READ: [],
READ_ACP: [],
WRITE: [],
WRITE_ACP: [],
}),
};
const testScenarios = [
{
description: 'should return true if bucket owner matches canonicalID',
input: {
bucketAcl: {}, requestType: 'anyType', canonicalID: 'ownerId', mainApiCall: 'anyApiCall',
},
expected: true,
},
{
description: 'should return true for objectGetTagging when mainApiCall is objectGet',
input: {
bucketAcl: {}, requestType: 'objectGetTagging', canonicalID: 'anyId', mainApiCall: 'objectGet',
},
expected: true,
},
{
description: 'should return true for objectPutTagging when mainApiCall is objectPut',
input: {
bucketAcl: {}, requestType: 'objectPutTagging', canonicalID: 'anyId', mainApiCall: 'objectPut',
},
expected: true,
},
{
description: 'should return true for objectPutLegalHold when mainApiCall is objectPut',
input: {
bucketAcl: {}, requestType: 'objectPutLegalHold', canonicalID: 'anyId', mainApiCall: 'objectPut',
},
expected: true,
},
{
description: 'should return true for objectPutRetention when mainApiCall is objectPut',
input: {
bucketAcl: {}, requestType: 'objectPutRetention', canonicalID: 'anyId', mainApiCall: 'objectPut',
},
expected: true,
},
{
description: 'should return true for bucketGet if canned acl is public-read-write',
input: {
bucketAcl: { Canned: 'public-read-write' },
requestType: 'bucketGet',
canonicalID: 'anyId',
mainApiCall: 'anyApiCall',
},
expected: true,
},
{
description: 'should return true for bucketGet if canned acl is authenticated-read and id is not publicId',
input: {
bucketAcl: { Canned: 'authenticated-read' },
requestType: 'bucketGet',
canonicalID: 'anyIdNotPublic',
mainApiCall: 'anyApiCall',
},
expected: true,
},
{
description: 'should return true for bucketHead if canned acl is public-read',
input: {
bucketAcl: { Canned: 'public-read' },
requestType: 'bucketHead',
canonicalID: 'anyId',
mainApiCall: 'anyApiCall',
},
expected: true,
},
{
description: 'should return false for bucketPut even if canonicalID has FULL_CONTROL and write access ',
input: {
bucketAcl: {
FULL_CONTROL: ['anyId'],
WRITE: ['anyId'],
},
requestType: 'bucketPut',
canonicalID: 'anyId',
mainApiCall: 'anyApiCall',
},
expected: false,
},
{
description: 'should return true for log-delivery-write ACL when canonicalID matches logId',
input: {
bucketAcl: { Canned: 'log-delivery-write' },
requestType: 'bucketGetACL',
canonicalID: logId,
mainApiCall: 'anyApiCall',
},
expected: true,
},
{
description: 'should return false when the canonicalID is not the owner and has no ACL permissions',
input: {
bucketAcl: {
FULL_CONTROL: ['someOtherId'],
WRITE: ['someOtherId'],
},
requestType: 'objectPut',
canonicalID: 'anyId',
mainApiCall: 'anyApiCall',
},
expected: false,
},
{
description: 'should return false for bucketPutACL if canonicalID does not have ACL permissions',
input: {
bucketAcl: {
FULL_CONTROL: ['someOtherId'],
WRITE_ACP: ['someOtherId'],
},
requestType: 'bucketPutACL',
canonicalID: 'anyId',
mainApiCall: 'anyApiCall',
},
expected: false,
},
{
description: 'should return true for bucketGet if canonicalID has FULL_CONTROL access',
input: {
bucketAcl: { FULL_CONTROL: ['anyId'], READ: [] },
requestType: 'bucketGet',
canonicalID: 'anyId',
mainApiCall: 'anyApiCall',
},
expected: true,
},
{
description: 'should return true for bucketGetACL if canonicalID has FULL_CONTROL',
input: {
bucketAcl: { FULL_CONTROL: ['anyId'], READ_ACP: [] },
requestType: 'bucketGetACL',
canonicalID: 'anyId',
mainApiCall: 'anyApiCall',
},
expected: true,
},
{
description: 'should return true for objectDelete if bucketAcl.Canned is public-read-write',
input: {
bucketAcl: { Canned: 'public-read-write' },
requestType: 'objectDelete',
canonicalID: 'anyId',
mainApiCall: 'anyApiCall',
},
expected: true,
},
{
description: 'should return true for requestType ending with "Version"',
input: {
bucketAcl: {},
requestType: 'objectGetVersion',
canonicalID: 'anyId',
mainApiCall: 'objectGet',
},
expected: true,
},
{
description: 'should return true for objectPutACL',
input: {
bucketAcl: {},
requestType: 'objectPutACL',
canonicalID: 'anyId',
mainApiCall: 'anyApiCall',
},
expected: true,
},
{
description: 'should return true for objectGetACL',
input: {
bucketAcl: {},
requestType: 'objectGetACL',
canonicalID: 'anyId',
mainApiCall: 'anyApiCall',
},
expected: true,
},
{
description: 'should return false for unmatched scenarios',
input: {
bucketAcl: {},
requestType: 'unmatchedRequest',
canonicalID: 'anyId',
mainApiCall: 'anyApiCall',
},
expected: false,
},
];
testScenarios.forEach(scenario => {
it(scenario.description, () => {
// Mock the bucket based on the test scenario's input
mockBucket.getAcl = () => scenario.input.bucketAcl;
const result = checkBucketAcls(mockBucket,
scenario.input.requestType, scenario.input.canonicalID, scenario.input.mainApiCall);
assert.strictEqual(result, scenario.expected);
});
});
});
describe('checkObjectAcls', () => {
const mockBucket = {
getOwner: () => 'bucketOwnerId',
getName: () => 'bucketName',
getAcl: () => ({ Canned: '' }),
};
const mockObjectMD = {
'owner-id': 'objectOwnerId',
'acl': {
Canned: '',
FULL_CONTROL: [],
READ: [],
READ_ACP: [],
WRITE: [],
WRITE_ACP: [],
},
};
it('should return true if request type is in bucketOwnerActions and bucket owner matches canonicalID', () => {
assert.strictEqual(checkObjectAcls(mockBucket, mockObjectMD, bucketOwnerActions[0],
'bucketOwnerId', false, false, 'anyApiCall'), true);
});
it('should return true if objectMD owner matches canonicalID', () => {
assert.strictEqual(checkObjectAcls(mockBucket, mockObjectMD, 'anyType',
'objectOwnerId', false, false, 'anyApiCall'), true);
});
it('should return true for objectGetTagging when mainApiCall is objectGet and conditions met', () => {
assert.strictEqual(checkObjectAcls(mockBucket, mockObjectMD, 'objectGetTagging',
'anyIdNotPublic', true, true, 'objectGet'), true);
});
it('should return false if no acl provided in objectMD', () => {
const objMDWithoutAcl = Object.assign({}, mockObjectMD);
delete objMDWithoutAcl.acl;
assert.strictEqual(checkObjectAcls(mockBucket, objMDWithoutAcl, 'anyType',
'anyId', false, false, 'anyApiCall'), false);
});
const tests = [
{
acl: 'public-read', reqType: 'objectGet', id: 'anyIdNotPublic', expected: true,
},
{
acl: 'public-read-write', reqType: 'objectGet', id: 'anyIdNotPublic', expected: true,
},
{
acl: 'authenticated-read', reqType: 'objectGet', id: 'anyIdNotPublic', expected: true,
},
{
acl: 'bucket-owner-read', reqType: 'objectGet', id: 'bucketOwnerId', expected: true,
},
{
acl: 'bucket-owner-full-control', reqType: 'objectGet', id: 'bucketOwnerId', expected: true,
},
{
aclList: ['someId', 'anyIdNotPublic'],
aclField: 'FULL_CONTROL',
reqType: 'objectGet',
id: 'anyIdNotPublic',
expected: true,
},
{
aclList: ['someId', 'anyIdNotPublic'],
aclField: 'READ',
reqType: 'objectGet',
id: 'anyIdNotPublic',
expected: true,
},
{ reqType: 'objectPut', id: 'anyId', expected: true },
{ reqType: 'objectDelete', id: 'anyId', expected: true },
{
aclList: ['anyId'], aclField: 'FULL_CONTROL', reqType: 'objectPutACL', id: 'anyId', expected: true,
},
{
aclList: ['anyId'], aclField: 'FULL_CONTROL', reqType: 'objectGetACL', id: 'anyId', expected: true,
},
{
acl: '', reqType: 'objectGet', id: 'randomId', expected: false,
},
];
tests.forEach(test => {
it(`should return ${test.expected} for ${test.reqType} with ACL as ${test.acl
|| (`${test.aclField}:${JSON.stringify(test.aclList)}`)}`, () => {
if (test.acl) {
mockObjectMD.acl.Canned = test.acl;
} else if (test.aclList && test.aclField) {
mockObjectMD.acl[test.aclField] = test.aclList;
}
assert.strictEqual(
checkObjectAcls(mockBucket, mockObjectMD, test.reqType, test.id, false, false, 'anyApiCall'),
test.expected,
);
});
});
});

View File

@ -488,9 +488,9 @@ arraybuffer.slice@~0.0.7:
optionalDependencies: optionalDependencies:
ioctl "^2.0.2" ioctl "^2.0.2"
"arsenal@git+https://github.com/scality/arsenal#7.10.48": "arsenal@git+https://github.com/scality/arsenal#7.10.49":
version "7.10.48" version "7.10.49"
resolved "git+https://github.com/scality/arsenal#f49cea3914390880008e3d41cedb1a02f9d99f39" resolved "git+https://github.com/scality/arsenal#fbf5562a1180055249745881c1a324562d7cdc8a"
dependencies: dependencies:
"@types/async" "^3.2.12" "@types/async" "^3.2.12"
"@types/utf8" "^3.0.1" "@types/utf8" "^3.0.1"