Skip to content

Commit 6e38f5b

Browse files
committed
CLDSRV-1021: Evaluate the bucket policy against the copy source
1 parent 3ce2d93 commit 6e38f5b

5 files changed

Lines changed: 38 additions & 15 deletions

File tree

‎lib/api/apiUtils/authorization/permissionChecks.js‎

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,8 @@ function _checkPrincipals(canonicalID, arn, principal, bucketOwnerCanonicalID) {
445445
// └───────┼──────────────────┘ │
446446
// └──────────────────────────────────────────┘
447447
//
448-
function checkBucketPolicy(policy, requestType, canonicalID, arn, bucketOwner, log, request, actionImplicitDenies) {
448+
function checkBucketPolicy(policy, requestType, canonicalID, arn, bucketOwner, log, request, actionImplicitDenies,
449+
policyTarget) {
449450
let permission = checkBucketPolicyResult.DEFAULT_DENY;
450451
// if requester is user within bucket owner account, actions should be
451452
// allowed unless explicitly denied (assumes allowed by IAM policy)
@@ -454,10 +455,18 @@ function checkBucketPolicy(policy, requestType, canonicalID, arn, bucketOwner, l
454455
}
455456
let copiedStatement = JSON.parse(JSON.stringify(policy.Statement));
456457

458+
// The policy target, when set, is the resource being authorized when it
459+
// differs from the one the request targets (e.g. the source of a copy)
460+
let bucketName = request?.bucketName;
461+
let objectKey = request?.objectKey;
462+
if (policyTarget) {
463+
bucketName = policyTarget.bucketName;
464+
objectKey = policyTarget.objectKey;
465+
}
457466
const ip = request ? requestUtils.getClientIp(request, config) : undefined;
458467
const isSecure = request ? requestUtils.getHttpProtocolSecurity(request, config) : undefined;
459468
const requestContext = request ? new RequestContext(request.headers, request.query,
460-
request.bucketName, request.objectKey, ip,
469+
bucketName, objectKey, ip,
461470
isSecure, request.resourceType, 's3', null, null,
462471
null, null, null, null, null, null, null, null, null,
463472
request.objectLockRetentionDays) : undefined;
@@ -506,7 +515,7 @@ function checkBucketPolicy(policy, requestType, canonicalID, arn, bucketOwner, l
506515
}
507516

508517
function processBucketPolicy(requestType, bucket, canonicalID, arn, bucketOwner, log,
509-
request, aclPermission, results, actionImplicitDenies) {
518+
request, aclPermission, results, actionImplicitDenies, policyTarget) {
510519
const bucketPolicy = bucket.getBucketPolicy();
511520
let processedResult = results[requestType];
512521
let aclRequired = false;
@@ -515,7 +524,7 @@ function processBucketPolicy(requestType, bucket, canonicalID, arn, bucketOwner,
515524
aclRequired = true;
516525
} else {
517526
const bucketPolicyPermission = checkBucketPolicy(bucketPolicy, requestType, canonicalID, arn,
518-
bucketOwner, log, request, actionImplicitDenies);
527+
bucketOwner, log, request, actionImplicitDenies, policyTarget);
519528

520529
if (bucketPolicyPermission === checkBucketPolicyResult.EXPLICIT_DENY) {
521530
processedResult = false;
@@ -534,7 +543,7 @@ function processBucketPolicy(requestType, bucket, canonicalID, arn, bucketOwner,
534543
}
535544

536545
function isBucketAuthorized(bucket, requestTypesInput, canonicalID, authInfo, log, request,
537-
actionImplicitDeniesInput = {}, isWebsite = false) {
546+
actionImplicitDeniesInput = {}, isWebsite = false, policyTarget = undefined) {
538547
const requestTypes = Array.isArray(requestTypesInput) ? requestTypesInput : [requestTypesInput];
539548
const actionImplicitDenies = !actionImplicitDeniesInput ? {} : actionImplicitDeniesInput;
540549
const mainApiCall = requestTypes[0];
@@ -568,7 +577,7 @@ function isBucketAuthorized(bucket, requestTypesInput, canonicalID, authInfo, lo
568577
actionImplicitDenies.objectGet = actionImplicitDenies.objectGet || false;
569578
}
570579
const { allowed, aclRequired } = processBucketPolicy(_requestType, bucket, canonicalID, arn,
571-
bucket.getOwner(), log, request, aclPermission, results, actionImplicitDenies);
580+
bucket.getOwner(), log, request, aclPermission, results, actionImplicitDenies, policyTarget);
572581
if (aclRequired && request?.serverAccessLog) {
573582
// eslint-disable-next-line no-param-reassign
574583
request.serverAccessLog.aclRequired = 'Yes';
@@ -597,7 +606,7 @@ function evaluateBucketPolicyWithIAM(bucket, requestTypesInput, canonicalID, aut
597606
}
598607

599608
function isObjAuthorized(bucket, objectMD, requestTypesInput, canonicalID, authInfo, log, request,
600-
actionImplicitDeniesInput = {}, isWebsite = false) {
609+
actionImplicitDeniesInput = {}, isWebsite = false, policyTarget = undefined) {
601610
const requestTypes = Array.isArray(requestTypesInput) ? requestTypesInput : [requestTypesInput];
602611
const actionImplicitDenies = !actionImplicitDeniesInput ? {} : actionImplicitDeniesInput;
603612
const results = {};
@@ -617,7 +626,7 @@ function isObjAuthorized(bucket, objectMD, requestTypesInput, canonicalID, authI
617626
permission = 'objectPut';
618627
}
619628
results[_requestType] = isBucketAuthorized(bucket, permission, canonicalID, authInfo, log, request,
620-
actionImplicitDenies, isWebsite);
629+
actionImplicitDenies, isWebsite, policyTarget);
621630
// User is already authorized on the bucket for FULL_CONTROL or WRITE or
622631
// bucket has canned ACL public-read-write
623632
if ((parsedMethodName === 'objectPut' || parsedMethodName === 'objectDelete')
@@ -651,7 +660,7 @@ function isObjAuthorized(bucket, objectMD, requestTypesInput, canonicalID, authI
651660
const aclPermission = checkObjectAcls(bucket, objectMD, parsedMethodName,
652661
canonicalID, requesterIsNotUser, isUserUnauthenticated, mainApiCall);
653662
const { allowed, aclRequired } = processBucketPolicy(_requestType, bucket, canonicalID, arn, bucketOwner,
654-
log, request, aclPermission, results, actionImplicitDenies);
663+
log, request, aclPermission, results, actionImplicitDenies, policyTarget);
655664
if (aclRequired && request?.serverAccessLog) {
656665
// eslint-disable-next-line no-param-reassign
657666
request.serverAccessLog.aclRequired = 'Yes';

‎lib/api/objectCopy.js‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,9 @@ function objectCopy(authInfo, request, sourceBucket, sourceObject, sourceVersion
243243
*/
244244
checkQuota: true,
245245
request,
246+
// The request targets the destination, the bucket policy must be
247+
// evaluated against the source
248+
policyTarget: { bucketName: sourceBucket, objectKey: sourceObject },
246249
};
247250
const valPutParams = {
248251
authInfo,

‎lib/api/objectPutCopyPart.js‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ function objectPutCopyPart(authInfo, request, sourceBucket, sourceObject, reqVer
5656
*/
5757
checkQuota: true,
5858
request,
59+
// The request targets the destination, the bucket policy must be
60+
// evaluated against the source
61+
policyTarget: { bucketName: sourceBucket, objectKey: sourceObject },
5962
};
6063

6164
const partNumber = Number.parseInt(request.query.partNumber, 10);

‎lib/metadata/metadataUtils.js‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,8 @@ function metadataGetObjects(bucketName, objectsKeys, log, cb) {
202202
* @param {string} params.requestType - type of request
203203
* @param {string} [params.preciseRequestType] - precise type of request
204204
* @param {object} params.request - http request object
205+
* @param {object} [params.policyTarget] - resource to evaluate the bucket
206+
* policy against when it differs from the request target
205207
* @param {RequestLogger} log - request logger
206208
* @param {object} actionImplicitDenies - identity authorization results
207209
* @return {ArsenalError|null} returns a validation error, or null if validation OK
@@ -212,7 +214,7 @@ function metadataGetObjects(bucketName, objectsKeys, log, cb) {
212214
* - AccessDenied: bucket is not authorized
213215
*/
214216
function validateBucket(bucket, params, log, actionImplicitDenies = {}) {
215-
const { authInfo, preciseRequestType, request } = params;
217+
const { authInfo, preciseRequestType, request, policyTarget } = params;
216218
let requestType = params.requestType;
217219
if (bucketShield(bucket, requestType)) {
218220
log.debug('bucket is shielded from request', {
@@ -249,6 +251,8 @@ function validateBucket(bucket, params, log, actionImplicitDenies = {}) {
249251
log,
250252
request,
251253
actionImplicitDenies,
254+
false,
255+
policyTarget,
252256
)
253257
) {
254258
log.debug('access denied for user on bucket', { requestType });
@@ -349,13 +353,18 @@ function checkRateLimitIfNeeded(request, authInfo, bucketMD, log, callback) {
349353
* @param {object} [params.serverAccessLogOptions] - options for server access logging
350354
* @param {boolean} [params.serverAccessLogOptions.copySource] - if true, stores source bucket
351355
* information in request.sourceServerAccessLog for copy operations
356+
* @param {object} [params.policyTarget] - resource to evaluate the bucket policy against
357+
* when it differs from the request target, e.g. the source of a copy
358+
* @param {string} params.policyTarget.bucketName - name of bucket
359+
* @param {string} params.policyTarget.objectKey - name of object
352360
* @param {boolean} actionImplicitDenies - identity authorization results
353361
* @param {RequestLogger} log - request logger
354362
* @param {function} callback - callback
355363
* @return {undefined} - and call callback with params err, bucket md
356364
*/
357365
function standardMetadataValidateBucketAndObj(params, actionImplicitDenies, log, callback) {
358-
const { authInfo, bucketName, objectKey, versionId, getDeleteMarker, request, withVersionId } = params;
366+
const { authInfo, bucketName, objectKey, versionId, getDeleteMarker, request, withVersionId, policyTarget } =
367+
params;
359368
let requestType = params.requestType;
360369
if (!Array.isArray(requestType)) {
361370
requestType = [requestType];
@@ -433,6 +442,8 @@ function standardMetadataValidateBucketAndObj(params, actionImplicitDenies, log,
433442
log,
434443
request,
435444
actionImplicitDenies,
445+
false,
446+
policyTarget,
436447
)
437448
) {
438449
log.debug('access denied for user on object', { requestType });

‎tests/unit/api/objectCopy.js‎

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -152,10 +152,7 @@ describe('objectCopy with versioning', () => {
152152
});
153153
});
154154

155-
// TODO: S3C-9965
156-
// Skipped because the policy is not checked correctly
157-
// When source bucket policy is checked destination arn is used
158-
it.skip('should set bucketOwnerId if requesting account differs from dest bucket owner', done => {
155+
it('should set bucketOwnerId if requesting account differs from dest bucket owner', done => {
159156
const authInfo2 = makeAuthInfo('accessKey2');
160157
const testObjectCopyRequest = _createObjectCopyRequest(destBucketName);
161158
const testPutSrcPolicyRequest = new DummyRequest({

0 commit comments

Comments
 (0)