diff --git a/lib/api/apiUtils/authorization/permissionChecks.js b/lib/api/apiUtils/authorization/permissionChecks.js index 2e3733e108..76ad9f0e70 100644 --- a/lib/api/apiUtils/authorization/permissionChecks.js +++ b/lib/api/apiUtils/authorization/permissionChecks.js @@ -1,12 +1,14 @@ const { evaluators, actionMaps, RequestContext } = require('arsenal').policies; const constants = require('../../../../constants'); -const { allAuthedUsersId, bucketOwnerActions, logId, publicId, - assumedRoleArnResourceType, backbeatLifecycleSessionName, arrayOfAllowed } = constants; +const { + allAuthedUsersId, bucketOwnerActions, logId, publicId, + assumedRoleArnResourceType, backbeatLifecycleSessionName, arrayOfAllowed, +} = 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(',') : []; /** * Checks the access control for a given bucket based on the request type and user's canonical ID. @@ -106,8 +108,8 @@ function checkBucketAcls(bucket, requestType, canonicalID, mainApiCall) { // 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 (requestTypeParsed === 'objectPutACL' || requestTypeParsed === 'objectGetACL' || - requestTypeParsed === 'objectGet' || requestTypeParsed === 'objectHead'); + return (requestTypeParsed === 'objectPutACL' || requestTypeParsed === 'objectGetACL' + || requestTypeParsed === 'objectGet' || requestTypeParsed === 'objectHead'); } function checkObjectAcls(bucket, objectMD, requestType, canonicalID, requesterIsNotUser, @@ -199,9 +201,9 @@ function checkObjectAcls(bucket, objectMD, requestType, canonicalID, requesterIs // 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; } @@ -298,75 +300,128 @@ function checkBucketPolicy(policy, requestType, canonicalID, arn, bucketOwner, l return permission; } -function isBucketAuthorized(bucket, requestType, canonicalID, authInfo, log, request) { - // 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); +function processBucketPolicy(requestType, bucket, canonicalID, arn, bucketOwner, log, + request, aclPermission, results, actionImplicitDenies) { const bucketPolicy = bucket.getBucketPolicy(); + let processedResult = results[requestType]; if (!bucketPolicy) { - return aclPermission; - } - const bucketPolicyPermission = checkBucketPolicy(bucketPolicy, requestType, - canonicalID, arn, bucket.getOwner(), log, request); - if (bucketPolicyPermission === 'explicitDeny') { - return false; + processedResult = actionImplicitDenies[requestType] === false && aclPermission; + } else { + const bucketPolicyPermission = checkBucketPolicy(bucketPolicy, requestType, canonicalID, arn, + bucketOwner, log, request); + + if (bucketPolicyPermission === 'explicitDeny') { + processedResult = false; + } else if (bucketPolicyPermission === 'allow') { + processedResult = true; + } else { + processedResult = actionImplicitDenies[requestType] === false && aclPermission; + } } - return (aclPermission || (bucketPolicyPermission === 'allow')); + return processedResult; } -function isObjAuthorized(bucket, objectMD, requestType, canonicalID, authInfo, log, request) { - const bucketOwner = bucket.getOwner(); - if (!objectMD) { +function isBucketAuthorized(bucket, requestTypesInput, canonicalID, authInfo, log, request, + actionImplicitDeniesInput = {}) { + const requestTypes = Array.isArray(requestTypesInput) ? requestTypesInput : [requestTypesInput]; + const actionImplicitDenies = !actionImplicitDeniesInput ? {} : actionImplicitDeniesInput; + 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 + actionImplicitDenies[_requestType] = 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, requestTypesInput, canonicalID, authInfo, actionImplicitDeniesInput = {}, + log, request) { + const requestTypes = Array.isArray(requestTypesInput) ? requestTypesInput : [requestTypesInput]; + const actionImplicitDenies = !actionImplicitDeniesInput ? {} : actionImplicitDeniesInput; + const results = {}; + return requestTypes.every(_requestType => { + // By default, all missing actions are defined as allowed from IAM, to be + // backward compatible + actionImplicitDenies[_requestType] = 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, requestTypesInput, canonicalID, authInfo, log, request, + actionImplicitDeniesInput = {}) { + const requestTypes = Array.isArray(requestTypesInput) ? requestTypesInput : [requestTypesInput]; + const actionImplicitDenies = !actionImplicitDeniesInput ? {} : actionImplicitDeniesInput; + 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 + actionImplicitDenies[_requestType] = 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 // 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 results[_requestType]; + } + // check bucket has read access + // 'bucketGet' covers listObjects and listMultipartUploads, bucket read actions + results[_requestType] = isBucketAuthorized(bucket, 'bucketGet', canonicalID, authInfo, log, request, + actionImplicitDenies); + return results[_requestType]; } - // 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 results[_requestType]; + } + // 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 results[_requestType]; + } + const aclPermission = checkObjectAcls(bucket, objectMD, parsedMethodName, + canonicalID, requesterIsNotUser, isUserUnauthenticated, mainApiCall); + return processBucketPolicy(_requestType, bucket, canonicalID, arn, bucketOwner, + log, request, aclPermission, results, actionImplicitDenies); + }); } function _checkResource(resource, bucketArn) { @@ -413,9 +468,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 = { @@ -425,4 +480,5 @@ module.exports = { checkObjectAcls, validatePolicyResource, isLifecycleSession, + evaluateBucketPolicyWithIAM, };