Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvement/cldsrv 451 prep 7.70 #5349

Draft
wants to merge 13 commits into
base: improvement/CLDSRV-431-misc-api-impDeny
Choose a base branch
from
Draft
118 changes: 59 additions & 59 deletions lib/api/apiUtils/authorization/permissionChecks.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,20 @@ const publicReadBuckets = process.env.ALLOW_PUBLIC_READ_BUCKETS

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;
const requestTypeParsed = requestType.endsWith('Version') ?
requestType.slice(0, -7) : requestType;
if (bucket.getOwner() === canonicalID) {
return true;
}
// Backward compatibility
const arrayOfAllowed = [
'objectGetTagging',
'objectPutTagging',
'objectPutLegalHold',
'objectPutRetention',
];
if (mainApiCall === 'objectGet') {
if (requestTypeParsed === 'objectGetTagging') {
if (arrayOfAllowed.includes(requestTypeParsed)) {
return true;
}
}
Expand Down Expand Up @@ -196,9 +197,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;
}
Expand Down Expand Up @@ -355,70 +356,33 @@ function isBucketAuthorized(bucket, requestTypes, canonicalID, authInfo, actionI
return Object.keys(results).every(key => results[key] === true);
}


function isObjAuthorized(bucket, objectMD, requestTypes, canonicalID, authInfo, actionImplicitDenies, log, request) {
function evaluateBucketPolicyWithIAM(bucket, requestTypes, canonicalID, authInfo, actionImplicitDenies, log, request) {
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) {
if (iamAuthzResults === false) {
// eslint-disable-next-line no-param-reassign
actionImplicitDenies = {};
iamAuthzResults = {};
}
// 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 = {};
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 (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,
actionImplicitDenies, log, request);
return;
}
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) {
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;
results[_requestType] = actionImplicitDenies[_requestType] === false;
return;
}
const bucketPolicyPermission = checkBucketPolicy(bucketPolicy, _requestType,
Expand All @@ -433,40 +397,76 @@ function isObjAuthorized(bucket, objectMD, requestTypes, canonicalID, authInfo,
results[_requestType] = true;
return;
}
results[_requestType] = actionImplicitDenies[_requestType] === false && aclPermission;
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 evaluateBucketPolicyWithIAM(bucket, requestTypes, canonicalID, authInfo, actionImplicitDenies, log, request) {
function isObjAuthorized(bucket, objectMD, requestTypes, canonicalID, authInfo, actionImplicitDenies, log, request) {
if (!Array.isArray(requestTypes)) {
// eslint-disable-next-line no-param-reassign
requestTypes = [requestTypes];
}
if (actionImplicitDenies === false) {
// 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 = {};
}
// 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 = {};
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 (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,
actionImplicitDenies, log, request);
return;
}
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) {
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;
results[_requestType] = actionImplicitDenies[_requestType] === false && aclPermission;
return;
}
const bucketPolicyPermission = checkBucketPolicy(bucketPolicy, _requestType,
Expand All @@ -481,7 +481,7 @@ function evaluateBucketPolicyWithIAM(bucket, requestTypes, canonicalID, authInfo
results[_requestType] = true;
return;
}
results[_requestType] = actionImplicitDenies[_requestType] === false;
results[_requestType] = actionImplicitDenies[_requestType] === false && aclPermission;
});

// final result is true if all the results are true
Expand Down Expand Up @@ -539,10 +539,10 @@ function isLifecycleSession(arn) {

module.exports = {
isBucketAuthorized,
evaluateBucketPolicyWithIAM,
isObjAuthorized,
checkBucketAcls,
checkObjectAcls,
validatePolicyResource,
isLifecycleSession,
evaluateBucketPolicyWithIAM,
};
2 changes: 1 addition & 1 deletion lib/api/apiUtils/object/abortMultipartUpload.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ function abortMultipartUpload(authInfo, bucketName, objectKey, uploadId, log,
bucketName,
objectKey,
uploadId,
preciseRequestType: 'multipartDelete',
preciseRequestType: request.apiMethods || 'multipartDelete',
request,
};
// For validating the request at the destinationBucket level
Expand Down
2 changes: 1 addition & 1 deletion lib/api/bucketDelete.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ function bucketDelete(authInfo, request, log, cb) {
const metadataValParams = {
authInfo,
bucketName,
requestType: 'bucketDelete',
requestType: request.apiMethods || 'bucketDelete',
request,
};

Expand Down
2 changes: 1 addition & 1 deletion lib/api/bucketDeleteCors.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ function bucketDeleteCors(authInfo, request, log, callback) {
}
log.trace('found bucket in metadata');

if (!isBucketAuthorized(bucket, requestType, canonicalID, authInfo,
if (!isBucketAuthorized(bucket, request.apiMethods || requestType, canonicalID, authInfo,
request.actionImplicitDenies, log, request)) {
log.debug('access denied for user on bucket', {
requestType,
Expand Down
2 changes: 1 addition & 1 deletion lib/api/bucketDeleteEncryption.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ function bucketDeleteEncryption(authInfo, request, log, callback) {
const metadataValParams = {
authInfo,
bucketName,
requestType: 'bucketDeleteEncryption',
requestType: request.apiMethods || 'bucketDeleteEncryption',
request,
};

Expand Down
2 changes: 1 addition & 1 deletion lib/api/bucketDeleteLifecycle.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ function bucketDeleteLifecycle(authInfo, request, log, callback) {
const metadataValParams = {
authInfo,
bucketName,
requestType: 'bucketDeleteLifecycle',
requestType: request.apiMethods || 'bucketDeleteLifecycle',
request,
};
return metadataValidateBucket(metadataValParams, request.actionImplicitDenies, log, (err, bucket) => {
Expand Down
2 changes: 1 addition & 1 deletion lib/api/bucketDeletePolicy.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ function bucketDeletePolicy(authInfo, request, log, callback) {
const metadataValParams = {
authInfo,
bucketName,
requestType: 'bucketDeletePolicy',
requestType: request.apiMethods || 'bucketDeletePolicy',
request,
};
return metadataValidateBucket(metadataValParams, request.actionImplicitDenies, log, (err, bucket) => {
Expand Down
2 changes: 1 addition & 1 deletion lib/api/bucketDeleteReplication.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ function bucketDeleteReplication(authInfo, request, log, callback) {
const metadataValParams = {
authInfo,
bucketName,
requestType: 'bucketDeleteReplication',
requestType: request.apiMethods || 'bucketDeleteReplication',
request,
};
return metadataValidateBucket(metadataValParams, request.actionImplicitDenies, log, (err, bucket) => {
Expand Down
55 changes: 55 additions & 0 deletions lib/api/bucketDeleteTagging.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
const collectCorsHeaders = require('../utilities/collectCorsHeaders');
const { metadataValidateBucket } = require('../metadata/metadataUtils');
const { pushMetric } = require('../utapi/utilities');
const metadata = require('../metadata/wrapper');
const util = require('node:util');
const monitoring = require('../utilities/metrics');

/**
* Bucket Delete Tagging - Delete a bucket's Tagging
* @param {AuthInfo} authInfo - Instance of AuthInfo class with requester's info
* @param {object} request - http request object
* @param {object} log - Werelogs logger
* @param {function} callback - callback to server
* @return {undefined}
*/
async function bucketDeleteTagging(authInfo, request, log, callback) {
const bucketName = request.bucketName;
let error = null;
log.debug('processing request', { method: 'bucketDeleteTagging', bucketName });

let bucket;
const metadataValidateBucketPromise = util.promisify(metadataValidateBucket);
let updateBucketPromise = util.promisify(metadata.updateBucket);
// necessary to bind metadata as updateBucket calls 'this', causing undefined otherwise
updateBucketPromise = updateBucketPromise.bind(metadata);
const metadataValParams = {
authInfo,
bucketName,
requestType: request.apiMethods || 'bucketDeleteTagging',
};

try {
bucket = await metadataValidateBucketPromise(metadataValParams, request.actionImplicitDenies, log);
bucket.setTags([]);
// eslint-disable-next-line no-unused-expressions
await updateBucketPromise(bucket.getName(), bucket, log);
pushMetric('deleteBucketTagging', log, {
authInfo,
bucket: bucketName,
});
monitoring.promMetrics(
'DELETE', bucketName, '200', 'deleteBucketTagging');
} catch (err) {
error = err;
log.error('error processing request', { error: err,
method: 'deleteBucketTagging', bucketName });
monitoring.promMetrics('DELETE', bucketName, err.code,
'deleteBucketTagging');
}
const corsHeaders = collectCorsHeaders(request.headers.origin,
request.method, bucket);
return callback(error, corsHeaders);
}

module.exports = bucketDeleteTagging;
2 changes: 1 addition & 1 deletion lib/api/bucketDeleteWebsite.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ function bucketDeleteWebsite(authInfo, request, log, callback) {
}
log.trace('found bucket in metadata');

if (!isBucketAuthorized(bucket, requestType, canonicalID, authInfo,
if (!isBucketAuthorized(bucket, request.apiMethods || requestType, canonicalID, authInfo,
request.actionImplicitDenies, log, request)) {
log.debug('access denied for user on bucket', {
requestType,
Expand Down
2 changes: 1 addition & 1 deletion lib/api/bucketGet.js
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ function bucketGet(authInfo, request, log, callback) {
const metadataValParams = {
authInfo,
bucketName,
requestType: 'bucketGet',
requestType: request.apiMethods || 'bucketGet',
request,
};
const listParams = {
Expand Down
2 changes: 1 addition & 1 deletion lib/api/bucketGetACL.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ function bucketGetACL(authInfo, request, log, callback) {
const metadataValParams = {
authInfo,
bucketName,
requestType: 'bucketGetACL',
requestType: request.apiMethods || 'bucketGetACL',
request,
};
const grantInfo = {
Expand Down
2 changes: 1 addition & 1 deletion lib/api/bucketGetCors.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ function bucketGetCors(authInfo, request, log, callback) {
const corsHeaders = collectCorsHeaders(request.headers.origin,
request.method, bucket);

if (!isBucketAuthorized(bucket, requestType, canonicalID, authInfo,
if (!isBucketAuthorized(bucket, request.apiMethods || requestType, canonicalID, authInfo,
request.actionImplicitDenies, log, request)) {
log.debug('access denied for user on bucket', {
requestType,
Expand Down
2 changes: 1 addition & 1 deletion lib/api/bucketGetEncryption.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ function bucketGetEncryption(authInfo, request, log, callback) {
const metadataValParams = {
authInfo,
bucketName,
requestType: 'bucketGetEncryption',
requestType: request.apiMethods || 'bucketGetEncryption',
request,
};

Expand Down
2 changes: 1 addition & 1 deletion lib/api/bucketGetLifecycle.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ function bucketGetLifecycle(authInfo, request, log, callback) {
const metadataValParams = {
authInfo,
bucketName,
requestType: 'bucketGetLifecycle',
requestType: request.apiMethods || 'bucketGetLifecycle',
request,
};
return metadataValidateBucket(metadataValParams, request.actionImplicitDenies, log, (err, bucket) => {
Expand Down
Loading