Skip to content

Commit b32a8f6

Browse files
committed
CLDSRV-674: check x-amz-tagging-count permissions in bucket policies
1 parent 96557d4 commit b32a8f6

5 files changed

Lines changed: 35 additions & 11 deletions

File tree

lib/api/api.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,8 +152,6 @@ const api = {
152152
log.trace('get object authorization denial from Vault');
153153
return errors.AccessDenied;
154154
}
155-
// TODO add support for returnTagCount in the bucket policy
156-
// checks
157155
isImplicitDeny[authResults[0].action] = authResults[0].isImplicit;
158156
// second item checks s3:GetObject(Version)Tagging action
159157
if (!authResults[1].isAllowed) {
@@ -281,6 +279,9 @@ const api = {
281279
sourceObject, sourceVersionId, log, callback);
282280
}
283281
if (apiMethod === 'objectGet') {
282+
// remove objectGetTagging/objectGetTaggingVersion from apiMethods, these were added by
283+
// prepareRequestContexts to determine the value of returnTagCount.
284+
request.apiMethods = request.apiMethods.filter(methodName => !methodName.includes('Tagging'));
284285
return this[apiMethod](userInfo, request, returnTagCount, log, callback);
285286
}
286287
return this[apiMethod](userInfo, request, log, callback);

lib/api/apiUtils/authorization/permissionChecks.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const {
1414
const publicReadBuckets = process.env.ALLOW_PUBLIC_READ_BUCKETS
1515
? process.env.ALLOW_PUBLIC_READ_BUCKETS.split(',') : [];
1616

17+
// WARNING: enum order matters DO NOT change.
1718
const checkPrincipalResult = Object.freeze({
1819
KO: 0,
1920
CROSS_ACCOUNT_OK: 1,
@@ -288,7 +289,7 @@ function _isRootUser(arn) {
288289
return false;
289290
}
290291

291-
// Vault returns the following arn when the account makes requests 'arn:aws:iam::295412255825:/master/',
292+
// Vault returns the following arn when the account makes requests 'arn:aws:iam::123456789012:/accountName/',
292293
// with an empty resource type ('user/' prefix missing).
293294
const arns = arn.split(':');
294295
if (arns.length < 6) {
@@ -312,7 +313,7 @@ function _isRootUser(arn) {
312313
* @return {checkPrincipalResult} OK if it is not cross-account, CROSS_ACCOUNT_OK otherwise.
313314
*/
314315
function _checkCrossAccount(requesterARN, requesterCanonicalID, bucketOwnerCanonicalID) {
315-
// Vault returns ARNs like 'arn:aws:iam::123456789012:/master/' for root accounts
316+
// Vault returns ARNs like 'arn:aws:iam::123456789012:/accountName/' for root accounts
316317
// with an empty resource type (missing 'user/' prefix)
317318
if (!_isRootUser(requesterARN)) {
318319
return bucketOwnerCanonicalID === requesterCanonicalID ?
@@ -347,7 +348,7 @@ function _checkPrincipalAWS(principal, requesterARN, requesterCanonicalID, bucke
347348
return _checkCrossAccount(requesterARN, requesterCanonicalID, bucketOwnerCanonicalID);
348349
}
349350

350-
if (principal.endsWith('root') && _getAccountId(principal) === _getAccountId(requesterARN)) {
351+
if (principal.endsWith(':root') && _getAccountId(principal) === _getAccountId(requesterARN)) {
351352
return _checkCrossAccount(requesterARN, requesterCanonicalID, bucketOwnerCanonicalID);
352353
}
353354

@@ -409,7 +410,7 @@ function _checkPrincipals(canonicalID, arn, principal, bucketOwnerCanonicalID) {
409410
return checkPrincipalResult.KO;
410411
}
411412

412-
// checkBucketPolicy FSM.
413+
// checkBucketPolicy Finite State Machine.
413414
// ┌───────────────────────────┐
414415
// │ ▼
415416
// │ ┌───────┐

lib/api/objectGet.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ function objectGet(authInfo, request, returnTagCount, log, callback) {
5151
getDeleteMarker: true,
5252
requestType: request.apiMethods || 'objectGet',
5353
request,
54+
returnTagCount,
5455
};
5556

5657
return standardMetadataValidateBucketAndObj(mdValParams, request.actionImplicitDenies, log,
@@ -97,7 +98,8 @@ function objectGet(authInfo, request, returnTagCount, log, callback) {
9798
return callback(headerValResult.error, null, corsHeaders);
9899
}
99100
const responseMetaHeaders = collectResponseHeaders(objMD,
100-
corsHeaders, verCfg, returnTagCount);
101+
corsHeaders, verCfg,
102+
returnTagCount && objMD.returnTagCount); // IAM and Bucket policy should both authorize tagging.
101103

102104
setExpirationHeaders(responseMetaHeaders, {
103105
lifecycleConfig: bucket.getLifecycleConfiguration(),

lib/metadata/metadataUtils.js

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,13 +230,33 @@ function standardMetadataValidateBucketAndObj(params, actionImplicitDenies, log,
230230
return next(null, bucket, objMD);
231231
},
232232
(bucket, objMD, next) => {
233+
const objMetadata = objMD;
233234
const canonicalID = authInfo.getCanonicalID();
234-
if (!isObjAuthorized(bucket, objMD, requestType, canonicalID, authInfo, log, request,
235+
if (!isObjAuthorized(bucket, objMetadata, requestType, canonicalID, authInfo, log, request,
235236
actionImplicitDenies)) {
236237
log.debug('access denied for user on object', { requestType });
237238
return next(errors.AccessDenied, bucket);
238239
}
239-
return next(null, bucket, objMD);
240+
241+
if (!objMetadata) {
242+
return next(null, bucket, objMetadata);
243+
}
244+
245+
let returnTagCount = false;
246+
if (params.returnTagCount) {
247+
// If returnTagCount is true we know that Vault authorized the request so it is not an implicitDeny.
248+
const implicitDeny = false;
249+
if (requestType.some(r => r === 'objectGet')) {
250+
returnTagCount = isObjAuthorized(bucket, objMetadata, ['objectGetTagging'], canonicalID, authInfo,
251+
log, request, implicitDeny);
252+
} else if (requestType.some(r => r === 'objectGetVersion')) {
253+
returnTagCount = isObjAuthorized(bucket, objMetadata, ['objectGetTaggingVersion'],
254+
canonicalID, authInfo, log, request, implicitDeny);
255+
}
256+
257+
objMetadata.returnTagCount = returnTagCount;
258+
}
259+
return next(null, bucket, objMetadata);
240260
},
241261
], (err, bucket, objMD) => {
242262
if (err) {

tests/unit/api/apiUtils/permissionChecks.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ describe('checkBucketPolicy Principal logic', () => {
267267
},
268268
requestType: 'bucketGet',
269269
canonicalID: '79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be',
270-
arn: 'arn:aws:iam::123456789012:/master/',
270+
arn: 'arn:aws:iam::123456789012:/accountName/',
271271
bucketOwner: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
272272
log: stubLog,
273273
request: null,
@@ -296,7 +296,7 @@ describe('checkBucketPolicy Principal logic', () => {
296296
},
297297
requestType: 'bucketGet',
298298
canonicalID: '79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be',
299-
arn: 'arn:aws:iam::123456789012:/master/',
299+
arn: 'arn:aws:iam::123456789012:/accountName/',
300300
bucketOwner: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
301301
log: stubLog,
302302
request: null,

0 commit comments

Comments
 (0)