Compare commits
15 Commits
developmen
...
improvemen
Author | SHA1 | Date |
---|---|---|
Maha Benzekri | 0328e260de | |
Maha Benzekri | 1960bb31df | |
Maha Benzekri | b37f9ae85a | |
Will Toozs | fe6578b276 | |
Will Toozs | da526b5092 | |
Will Toozs | 159906dc74 | |
Will Toozs | 3ff2b5e9ed | |
Will Toozs | f69f72363b | |
Will Toozs | 7f9562bfda | |
Will Toozs | c01898f1a0 | |
Will Toozs | 0f64e7c337 | |
Will Toozs | de334d16e9 | |
Will Toozs | e99bb1e1cc | |
Will Toozs | cd6320c432 | |
Will Toozs | 3bc66f8cea |
|
@ -107,6 +107,7 @@ const api = {
|
|||
// no need to check auth on website or cors preflight requests
|
||||
if (apiMethod === 'websiteGet' || apiMethod === 'websiteHead' ||
|
||||
apiMethod === 'corsPreflight') {
|
||||
request.actionImplicitDenies = false;
|
||||
return this[apiMethod](request, log, callback);
|
||||
}
|
||||
|
||||
|
@ -129,15 +130,25 @@ const api = {
|
|||
|
||||
const requestContexts = prepareRequestContexts(apiMethod, request,
|
||||
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) {
|
||||
let returnTagCount = true;
|
||||
const isImplicitDeny = {};
|
||||
let isOnlyImplicitDeny = true;
|
||||
if (apiMethod === 'objectGet') {
|
||||
// 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');
|
||||
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
|
||||
if (!authResults[1].isAllowed) {
|
||||
log.trace('get tagging authorization denial ' +
|
||||
|
@ -146,13 +157,25 @@ const api = {
|
|||
}
|
||||
} else {
|
||||
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');
|
||||
return errors.AccessDenied;
|
||||
} else 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([
|
||||
|
@ -230,7 +253,16 @@ const api = {
|
|||
if (checkedResults instanceof Error) {
|
||||
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') {
|
||||
request._response = response;
|
||||
|
|
|
@ -1,20 +1,41 @@
|
|||
const { evaluators, actionMaps, RequestContext } = require('arsenal').policies;
|
||||
const constants = require('../../../../constants');
|
||||
|
||||
const { allAuthedUsersId, bucketOwnerActions, logId, publicId,
|
||||
assumedRoleArnResourceType, backbeatLifecycleSessionName } = constants;
|
||||
const {
|
||||
allAuthedUsersId, bucketOwnerActions, logId, publicId,
|
||||
assumedRoleArnResourceType, backbeatLifecycleSessionName,
|
||||
} = constants;
|
||||
|
||||
// whitelist buckets to allow public read on objects
|
||||
const publicReadBuckets = process.env.ALLOW_PUBLIC_READ_BUCKETS ?
|
||||
process.env.ALLOW_PUBLIC_READ_BUCKETS.split(',') : [];
|
||||
const publicReadBuckets = process.env.ALLOW_PUBLIC_READ_BUCKETS
|
||||
? 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) {
|
||||
return true;
|
||||
}
|
||||
// Backward compatibility
|
||||
const arrayOfAllowed = [
|
||||
'objectPutTagging',
|
||||
'objectPutLegalHold',
|
||||
'objectPutRetention',
|
||||
];
|
||||
if (mainApiCall === 'objectGet') {
|
||||
if (requestTypeParsed === 'objectGetTagging') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (mainApiCall === 'objectPut') {
|
||||
if (arrayOfAllowed.includes(requestTypeParsed)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const bucketAcl = bucket.getAcl();
|
||||
if (requestType === 'bucketGet' || requestType === 'bucketHead') {
|
||||
if (requestTypeParsed === 'bucketGet' || requestTypeParsed === 'bucketHead') {
|
||||
if (bucketAcl.Canned === 'public-read'
|
||||
|| bucketAcl.Canned === 'public-read-write'
|
||||
|| (bucketAcl.Canned === 'authenticated-read'
|
||||
|
@ -32,7 +53,7 @@ function checkBucketAcls(bucket, requestType, canonicalID) {
|
|||
return true;
|
||||
}
|
||||
}
|
||||
if (requestType === 'bucketGetACL') {
|
||||
if (requestTypeParsed === 'bucketGetACL') {
|
||||
if ((bucketAcl.Canned === 'log-delivery-write'
|
||||
&& canonicalID === logId)
|
||||
|| bucketAcl.FULL_CONTROL.indexOf(canonicalID) > -1
|
||||
|
@ -48,7 +69,7 @@ function checkBucketAcls(bucket, requestType, canonicalID) {
|
|||
}
|
||||
}
|
||||
|
||||
if (requestType === 'bucketPutACL') {
|
||||
if (requestTypeParsed === 'bucketPutACL') {
|
||||
if (bucketAcl.FULL_CONTROL.indexOf(canonicalID) > -1
|
||||
|| bucketAcl.WRITE_ACP.indexOf(canonicalID) > -1) {
|
||||
return true;
|
||||
|
@ -62,11 +83,7 @@ function checkBucketAcls(bucket, requestType, canonicalID) {
|
|||
}
|
||||
}
|
||||
|
||||
if (requestType === 'bucketDelete' && bucket.getOwner() === canonicalID) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (requestType === 'objectDelete' || requestType === 'objectPut') {
|
||||
if (requestTypeParsed === 'objectDelete' || requestTypeParsed === 'objectPut') {
|
||||
if (bucketAcl.Canned === 'public-read-write'
|
||||
|| bucketAcl.FULL_CONTROL.indexOf(canonicalID) > -1
|
||||
|| bucketAcl.WRITE.indexOf(canonicalID) > -1) {
|
||||
|
@ -86,11 +103,12 @@ function checkBucketAcls(bucket, requestType, canonicalID) {
|
|||
// objectPutACL, objectGetACL, objectHead or objectGet, the bucket
|
||||
// authorization check should just return true so can move on to check
|
||||
// rights at the object level.
|
||||
return (requestType === 'objectPutACL' || requestType === 'objectGetACL' ||
|
||||
requestType === 'objectGet' || requestType === 'objectHead');
|
||||
return (requestTypeParsed === 'objectPutACL' || requestTypeParsed === 'objectGetACL'
|
||||
|| requestTypeParsed === 'objectGet' || requestTypeParsed === 'objectHead');
|
||||
}
|
||||
|
||||
function checkObjectAcls(bucket, objectMD, requestType, canonicalID) {
|
||||
function checkObjectAcls(bucket, objectMD, requestType, canonicalID, requesterIsNotUser,
|
||||
isUserUnauthenticated, mainApiCall) {
|
||||
const bucketOwner = bucket.getOwner();
|
||||
// acls don't distinguish between users and accounts, so both should be allowed
|
||||
if (bucketOwnerActions.includes(requestType)
|
||||
|
@ -100,6 +118,15 @@ function checkObjectAcls(bucket, objectMD, requestType, canonicalID) {
|
|||
if (objectMD['owner-id'] === canonicalID) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Backward compatibility
|
||||
if (mainApiCall === 'objectGet') {
|
||||
if ((isUserUnauthenticated || (requesterIsNotUser && bucketOwner === objectMD['owner-id']))
|
||||
&& requestType === 'objectGetTagging') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!objectMD.acl) {
|
||||
return false;
|
||||
}
|
||||
|
@ -169,9 +196,9 @@ function checkObjectAcls(bucket, objectMD, requestType, canonicalID) {
|
|||
// allow public reads on buckets that are whitelisted for anonymous reads
|
||||
// TODO: remove this after bucket policies are implemented
|
||||
const bucketAcl = bucket.getAcl();
|
||||
const allowPublicReads = publicReadBuckets.includes(bucket.getName()) &&
|
||||
bucketAcl.Canned === 'public-read' &&
|
||||
(requestType === 'objectGet' || requestType === 'objectHead');
|
||||
const allowPublicReads = publicReadBuckets.includes(bucket.getName())
|
||||
&& bucketAcl.Canned === 'public-read'
|
||||
&& (requestType === 'objectGet' || requestType === 'objectHead');
|
||||
if (allowPublicReads) {
|
||||
return true;
|
||||
}
|
||||
|
@ -268,75 +295,194 @@ function checkBucketPolicy(policy, requestType, canonicalID, arn, bucketOwner, l
|
|||
return permission;
|
||||
}
|
||||
|
||||
function isBucketAuthorized(bucket, requestType, canonicalID, authInfo, log, request) {
|
||||
function isBucketAuthorized(bucket, 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 = {};
|
||||
}
|
||||
// By default, all missing actions are defined as allowed from IAM, to be
|
||||
// backward compatible
|
||||
requestTypes.forEach(requestType => {
|
||||
if (actionImplicitDenies[requestType] === undefined) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
actionImplicitDenies[requestType] = false;
|
||||
}
|
||||
});
|
||||
const mainApiCall = requestTypes[0];
|
||||
const results = {};
|
||||
requestTypes.forEach(_requestType => {
|
||||
// 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) {
|
||||
return true;
|
||||
}
|
||||
const aclPermission = checkBucketAcls(bucket, requestType, canonicalID);
|
||||
const bucketPolicy = bucket.getBucketPolicy();
|
||||
if (!bucketPolicy) {
|
||||
return aclPermission;
|
||||
}
|
||||
const bucketPolicyPermission = checkBucketPolicy(bucketPolicy, requestType,
|
||||
canonicalID, arn, bucket.getOwner(), log, request);
|
||||
if (bucketPolicyPermission === 'explicitDeny') {
|
||||
return false;
|
||||
}
|
||||
return (aclPermission || (bucketPolicyPermission === 'allow'));
|
||||
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;
|
||||
}
|
||||
const aclPermission = checkBucketAcls(bucket, _requestType, canonicalID, mainApiCall);
|
||||
const bucketPolicy = bucket.getBucketPolicy();
|
||||
if (!bucketPolicy) {
|
||||
results[_requestType] = actionImplicitDenies[_requestType] === false && aclPermission;
|
||||
return;
|
||||
}
|
||||
const bucketPolicyPermission = checkBucketPolicy(bucketPolicy, _requestType,
|
||||
canonicalID, arn, bucket.getOwner(), log, request);
|
||||
if (bucketPolicyPermission === 'explicitDeny') {
|
||||
results[_requestType] = false;
|
||||
return;
|
||||
}
|
||||
// If the bucket policy returns an allow, we accept the request, as the
|
||||
// IAM response here is either Allow or implicit deny.
|
||||
if (bucketPolicyPermission === 'allow') {
|
||||
results[_requestType] = true;
|
||||
return;
|
||||
}
|
||||
results[_requestType] = actionImplicitDenies[_requestType] === false && aclPermission;
|
||||
});
|
||||
|
||||
// final result is true if all the results are true
|
||||
return Object.keys(results).every(key => results[key] === true);
|
||||
}
|
||||
|
||||
function isObjAuthorized(bucket, objectMD, requestType, canonicalID, authInfo, log, request) {
|
||||
const bucketOwner = bucket.getOwner();
|
||||
if (!objectMD) {
|
||||
function evaluateBucketPolicyWithIAM(bucket, requestTypes, canonicalID, authInfo, actionImplicitDenies, log, request) {
|
||||
if (!Array.isArray(requestTypes)) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
requestTypes = [requestTypes];
|
||||
}
|
||||
if (actionImplicitDenies === false) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
actionImplicitDenies = {};
|
||||
}
|
||||
// By default, all missing actions are defined as allowed from IAM, to be
|
||||
// backward compatible
|
||||
requestTypes.forEach(requestType => {
|
||||
if (actionImplicitDenies[requestType] === undefined) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
actionImplicitDenies[requestType] = false;
|
||||
}
|
||||
});
|
||||
|
||||
const results = {};
|
||||
requestTypes.forEach(_requestType => {
|
||||
let arn = null;
|
||||
if (authInfo) {
|
||||
arn = authInfo.getArn();
|
||||
}
|
||||
const bucketPolicy = bucket.getBucketPolicy();
|
||||
if (!bucketPolicy) {
|
||||
results[_requestType] = actionImplicitDenies[_requestType] === false;
|
||||
return;
|
||||
}
|
||||
const bucketPolicyPermission = checkBucketPolicy(bucketPolicy, _requestType,
|
||||
canonicalID, arn, bucket.getOwner(), log, request);
|
||||
if (bucketPolicyPermission === 'explicitDeny') {
|
||||
results[_requestType] = false;
|
||||
return;
|
||||
}
|
||||
// If the bucket policy returns an allow, we accept the request, as the
|
||||
// IAM response here is either Allow or implicit deny.
|
||||
if (bucketPolicyPermission === 'allow') {
|
||||
results[_requestType] = true;
|
||||
return;
|
||||
}
|
||||
results[_requestType] = actionImplicitDenies[_requestType] === false;
|
||||
});
|
||||
|
||||
// final result is true if all the results are true
|
||||
return Object.keys(results).every(key => results[key] === true);
|
||||
}
|
||||
|
||||
function isObjAuthorized(bucket, objectMD, requestTypes, canonicalID, authInfo, log, request, actionImplicitDenies) {
|
||||
if (!Array.isArray(requestTypes)) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
requestTypes = [requestTypes];
|
||||
}
|
||||
// By default, all missing actions are defined as allowed from IAM, to be
|
||||
// backward compatible
|
||||
if (!actionImplicitDenies) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
actionImplicitDenies = {};
|
||||
}
|
||||
requestTypes.forEach(requestType => {
|
||||
if (actionImplicitDenies[requestType] === undefined) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
actionImplicitDenies[requestType] = false;
|
||||
}
|
||||
});
|
||||
const results = {};
|
||||
const mainApiCall = requestTypes[0];
|
||||
requestTypes.forEach(_requestType => {
|
||||
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
|
||||
// bucket has canned ACL public-read-write
|
||||
if (requestType === 'objectPut' || requestType === 'objectDelete') {
|
||||
return true;
|
||||
if (parsedMethodName === 'objectPut' || parsedMethodName === 'objectDelete') {
|
||||
results[_requestType] = actionImplicitDenies[_requestType] === false;
|
||||
return;
|
||||
}
|
||||
// check bucket has read access
|
||||
// 'bucketGet' covers listObjects and listMultipartUploads, bucket read actions
|
||||
results[_requestType] = isBucketAuthorized(bucket, 'bucketGet', canonicalID, authInfo, log, request, false);
|
||||
return;
|
||||
}
|
||||
// check bucket has read access
|
||||
// 'bucketGet' covers listObjects and listMultipartUploads, bucket read actions
|
||||
return isBucketAuthorized(bucket, 'bucketGet', canonicalID, authInfo, log, request);
|
||||
}
|
||||
let requesterIsNotUser = true;
|
||||
let arn = null;
|
||||
if (authInfo) {
|
||||
requesterIsNotUser = !authInfo.isRequesterAnIAMUser();
|
||||
arn = authInfo.getArn();
|
||||
}
|
||||
if (objectMD['owner-id'] === canonicalID && requesterIsNotUser) {
|
||||
return true;
|
||||
}
|
||||
// account is authorized if:
|
||||
// - requesttype is included in bucketOwnerActions and
|
||||
// - account is the bucket owner
|
||||
// - requester is account, not user
|
||||
if (bucketOwnerActions.includes(requestType)
|
||||
let requesterIsNotUser = true;
|
||||
let arn = null;
|
||||
let isUserUnauthenticated = false;
|
||||
if (authInfo) {
|
||||
requesterIsNotUser = !authInfo.isRequesterAnIAMUser();
|
||||
arn = authInfo.getArn();
|
||||
isUserUnauthenticated = arn === undefined;
|
||||
}
|
||||
if (objectMD['owner-id'] === canonicalID && requesterIsNotUser) {
|
||||
results[_requestType] = actionImplicitDenies[_requestType] === false;
|
||||
return;
|
||||
}
|
||||
// account is authorized if:
|
||||
// - requesttype is included in bucketOwnerActions and
|
||||
// - account is the bucket owner
|
||||
// - requester is account, not user
|
||||
if (bucketOwnerActions.includes(parsedMethodName)
|
||||
&& (bucketOwner === canonicalID)
|
||||
&& requesterIsNotUser) {
|
||||
return true;
|
||||
}
|
||||
const aclPermission = checkObjectAcls(bucket, objectMD, requestType,
|
||||
canonicalID);
|
||||
const bucketPolicy = bucket.getBucketPolicy();
|
||||
if (!bucketPolicy) {
|
||||
return aclPermission;
|
||||
}
|
||||
const bucketPolicyPermission = checkBucketPolicy(bucketPolicy, requestType,
|
||||
canonicalID, arn, bucket.getOwner(), log, request);
|
||||
if (bucketPolicyPermission === 'explicitDeny') {
|
||||
return false;
|
||||
}
|
||||
return (aclPermission || (bucketPolicyPermission === 'allow'));
|
||||
results[_requestType] = actionImplicitDenies[_requestType] === false;
|
||||
return;
|
||||
}
|
||||
const aclPermission = checkObjectAcls(bucket, objectMD, parsedMethodName,
|
||||
canonicalID, requesterIsNotUser, isUserUnauthenticated, mainApiCall);
|
||||
const bucketPolicy = bucket.getBucketPolicy();
|
||||
if (!bucketPolicy) {
|
||||
results[_requestType] = actionImplicitDenies[_requestType] === false && aclPermission;
|
||||
return;
|
||||
}
|
||||
const bucketPolicyPermission = checkBucketPolicy(bucketPolicy, _requestType,
|
||||
canonicalID, arn, bucket.getOwner(), log, request);
|
||||
if (bucketPolicyPermission === 'explicitDeny') {
|
||||
results[_requestType] = false;
|
||||
return;
|
||||
}
|
||||
// If the bucket policy returns an allow, we accept the request, as the
|
||||
// IAM response here is either Allow or implicit deny.
|
||||
if (bucketPolicyPermission === 'allow') {
|
||||
results[_requestType] = true;
|
||||
return;
|
||||
}
|
||||
results[_requestType] = actionImplicitDenies[_requestType] === false && aclPermission;
|
||||
});
|
||||
|
||||
// final result is true if all the results are true
|
||||
return Object.keys(results).every(key => results[key] === true);
|
||||
}
|
||||
|
||||
function _checkResource(resource, bucketArn) {
|
||||
|
@ -383,9 +529,9 @@ function isLifecycleSession(arn) {
|
|||
const resourceType = resourceNames[0];
|
||||
const sessionName = resourceNames[resourceNames.length - 1];
|
||||
|
||||
return (service === 'sts' &&
|
||||
resourceType === assumedRoleArnResourceType &&
|
||||
sessionName === backbeatLifecycleSessionName);
|
||||
return (service === 'sts'
|
||||
&& resourceType === assumedRoleArnResourceType
|
||||
&& sessionName === backbeatLifecycleSessionName);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
@ -395,4 +541,6 @@ module.exports = {
|
|||
checkObjectAcls,
|
||||
validatePolicyResource,
|
||||
isLifecycleSession,
|
||||
evaluateBucketPolicyWithIAM,
|
||||
};
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
"homepage": "https://github.com/scality/S3#readme",
|
||||
"dependencies": {
|
||||
"@hapi/joi": "^17.1.0",
|
||||
"arsenal": "git+https://github.com/scality/arsenal#7.10.47",
|
||||
"arsenal": "git+https://github.com/scality/arsenal#df5ff0f4006ee0a21269e139567fd5c425a4225f",
|
||||
"async": "~2.5.0",
|
||||
"aws-sdk": "2.905.0",
|
||||
"azure-storage": "^2.1.0",
|
||||
|
|
|
@ -0,0 +1,236 @@
|
|||
const assert = require('assert');
|
||||
const { checkBucketAcls, checkObjectAcls } = require('../../../lib/api/apiUtils/authorization/permissionChecks');
|
||||
const constants = require('../../../constants');
|
||||
|
||||
const { bucketOwnerActions } = 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 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 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,
|
||||
},
|
||||
{
|
||||
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,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
16
yarn.lock
16
yarn.lock
|
@ -488,9 +488,9 @@ arraybuffer.slice@~0.0.7:
|
|||
optionalDependencies:
|
||||
ioctl "^2.0.2"
|
||||
|
||||
"arsenal@git+https://github.com/scality/arsenal#7.10.47":
|
||||
version "7.10.47"
|
||||
resolved "git+https://github.com/scality/arsenal#3f24336b83581d121f52146b8003e0a68d9ce876"
|
||||
"arsenal@git+https://github.com/scality/arsenal#df5ff0f4006ee0a21269e139567fd5c425a4225f":
|
||||
version "7.10.48"
|
||||
resolved "git+https://github.com/scality/arsenal#df5ff0f4006ee0a21269e139567fd5c425a4225f"
|
||||
dependencies:
|
||||
"@types/async" "^3.2.12"
|
||||
"@types/utf8" "^3.0.1"
|
||||
|
@ -506,7 +506,7 @@ arraybuffer.slice@~0.0.7:
|
|||
bson "4.0.0"
|
||||
debug "~2.6.9"
|
||||
diskusage "^1.1.1"
|
||||
fcntl "github:scality/node-fcntl#0.2.0"
|
||||
fcntl "github:scality/node-fcntl#0.2.2"
|
||||
hdclient scality/hdclient#1.1.0
|
||||
https-proxy-agent "^2.2.0"
|
||||
ioredis "^4.28.5"
|
||||
|
@ -1918,6 +1918,14 @@ fast-levenshtein@~2.0.6:
|
|||
nan "^2.3.2"
|
||||
node-gyp "^8.0.0"
|
||||
|
||||
"fcntl@github:scality/node-fcntl#0.2.2":
|
||||
version "0.2.1"
|
||||
resolved "https://codeload.github.com/scality/node-fcntl/tar.gz/b1335ca204c6265cedc50c26020c4d63aabe920e"
|
||||
dependencies:
|
||||
bindings "^1.1.1"
|
||||
nan "^2.3.2"
|
||||
node-gyp "^8.0.0"
|
||||
|
||||
fecha@^4.2.0:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/fecha/-/fecha-4.2.3.tgz#4d9ccdbc61e8629b259fdca67e65891448d569fd"
|
||||
|
|
Loading…
Reference in New Issue