CLDSRV-945: Support cross account rate limiting#6225
Conversation
Hello tmacro,My role is to assist you with the merge of this Available options
Available commands
Status report is not available. |
❌ 1 Tests Failed:
View the top 2 failed test(s) by shortest run time
View the full list of 1 ❄️ flaky test(s)
To view more test analytics, go to the Test Analytics Dashboard |
Request integration branchesWaiting for integration branch creation to be requested by the user. To request integration branches, please comment on this pull request with the following command: Alternatively, the |
| } | ||
| return next(null, userInfo, authResultsWithTags, streamingV4Params, infos); | ||
| }, | ||
| ), | ||
| (userInfo, authorizationResults, streamingV4Params, infos, next) => | ||
| handleAuthorizationResults( | ||
| request, | ||
| authorizationResults, | ||
| apiMethod, | ||
| returnTagCount, | ||
| log, | ||
| (err, res) => { | ||
| request.accountQuotas = infos?.accountQuota; | ||
| request.accountLimits = infos?.limits; | ||
| if (err) { |
| return next(err); | ||
| } | ||
| returnTagCount = res.returnTagCount; | ||
| return next(null, userInfo, authorizationResults, streamingV4Params); | ||
| }, | ||
| ), | ||
| ], | ||
| (err, userInfo, authorizationResults, streamingV4Params) => { | ||
| if (err) { | ||
| return callback(err); | ||
| } | ||
| const methodCallback = (err, ...results) => | ||
| async.forEachLimit( | ||
| request.finalizerHooks, | ||
| 5, | ||
| (hook, done) => hook(err, done), | ||
| () => callback(err, ...results), |
| if (apiMethod === 'objectCopy' || apiMethod === 'objectPutCopyPart') { | ||
| return apiHandler(userInfo, request, sourceBucket, sourceObject, sourceVersionId, log, methodCallback); | ||
| } | ||
| if (apiMethod === 'objectGet') { |
| return process.nextTick(() => next(null, masterMD)); | ||
| async.waterfall( | ||
| [ | ||
| next => metadata.getObjectMD(bucketName, objectKey, {}, log, next), |
| (masterMD, next) => { | ||
| if (masterMD.isNull || !masterMD.versionId) { | ||
| log.debug('null version is master version'); | ||
| return process.nextTick(() => next(null, masterMD)); | ||
| } | ||
| if (masterMD.nullVersionId) { | ||
| // the latest version is not the null version, but null version exists | ||
| // NOTE: for backward-compat with old null version scheme | ||
| log.debug('get the null version via nullVersionId'); | ||
| const getOptions = { | ||
| versionId: masterMD.nullVersionId, | ||
| }; | ||
| return metadata.getObjectMD(bucketName, objectKey, getOptions, log, next); | ||
| } | ||
| return next(errors.NoSuchKey); | ||
| }, |
| next => { | ||
| // versionId may be 'null', which asks metadata to fetch the null key specifically | ||
| const getOptions = { versionId }; | ||
| if (getDeleteMarker) { | ||
| getOptions.getDeleteMarker = true; | ||
| } | ||
| return metadata.getBucketAndObjectMD( | ||
| bucketName, | ||
| objectKey, | ||
| getOptions, | ||
| log, | ||
| (err, getResult, raftSessionId) => { | ||
| if (err) { | ||
| // if some implicit iamAuthzResults, return AccessDenied | ||
| // before leaking any state information | ||
| if (actionImplicitDenies && Object.values(actionImplicitDenies).some(v => v === true)) { | ||
| return next(errors.AccessDenied); | ||
| } | ||
| return next(err); | ||
| } | ||
| return next(err); | ||
| return next(null, getResult, raftSessionId); | ||
| }, | ||
| ); | ||
| }, |
| (getResult, raftSessionId, next) => { | ||
| const bucket = getResult.bucket ? BucketInfo.deSerialize(getResult.bucket) : undefined; | ||
| if (!bucket) { | ||
| log.debug('bucketAttrs is undefined', { | ||
| bucket: bucketName, | ||
| method: 'metadataValidateBucketAndObj', | ||
| }); | ||
| return next(errors.NoSuchBucket, raftSessionId); | ||
| } | ||
| const validationError = validateBucket(bucket, params, log, actionImplicitDenies); | ||
| if (validationError) { | ||
| return next(validationError, bucket, raftSessionId); | ||
| } | ||
|
|
||
| // Rate limiting check if not already done in api.js | ||
| return checkRateLimitIfNeeded(request, authInfo, bucket, log, err => { | ||
| if (err) { | ||
| return next(err, bucket); | ||
| } | ||
| return next(null, getResult, raftSessionId); | ||
| }); | ||
| }, | ||
| (getResult, raftSessionId, next) => { | ||
| const bucket = getResult.bucket ? | ||
| BucketInfo.deSerialize(getResult.bucket) : undefined; | ||
| if (!bucket) { | ||
| log.debug('bucketAttrs is undefined', { | ||
| bucket: bucketName, | ||
| method: 'metadataValidateBucketAndObj', | ||
| }); | ||
| return next(errors.NoSuchBucket, raftSessionId); | ||
| } | ||
| const validationError = validateBucket(bucket, params, log, actionImplicitDenies); | ||
| if (validationError) { | ||
| return next(validationError, bucket, raftSessionId); | ||
| } | ||
|
|
||
| // Rate limiting check if not already done in api.js | ||
| return checkRateLimitIfNeeded(request, authInfo, bucket, log, err => { | ||
| if (err) { | ||
| return next(err, bucket); | ||
| // Continue with object metadata processing | ||
| const objMD = getResult.obj ? JSON.parse(getResult.obj) : undefined; | ||
| if (!objMD && versionId === 'null') { | ||
| return getNullVersionFromMaster(bucketName, objectKey, log, (err, nullVer) => | ||
| next(err, bucket, nullVer, raftSessionId), | ||
| ); | ||
| } | ||
| return next(null, bucket, objMD, raftSessionId); | ||
| }); | ||
| }, |
| (bucket, objMD, raftSessionId, next) => { | ||
| const objMetadata = objMD; | ||
| const canonicalID = authInfo.getCanonicalID(); | ||
| if ( | ||
| !isObjAuthorized( | ||
| bucket, | ||
| objMetadata, | ||
| requestType, | ||
| canonicalID, | ||
| authInfo, | ||
| log, | ||
| request, | ||
| actionImplicitDenies, | ||
| ) | ||
| ) { | ||
| log.debug('access denied for user on object', { requestType }); | ||
| return next(errors.AccessDenied, bucket, undefined, raftSessionId); | ||
| } | ||
|
|
||
| // Continue with object metadata processing | ||
| const objMD = getResult.obj ? JSON.parse(getResult.obj) : undefined; | ||
| if (!objMD && versionId === 'null') { | ||
| return getNullVersionFromMaster(bucketName, objectKey, log, | ||
| (err, nullVer) => next(err, bucket, nullVer, raftSessionId)); | ||
| if (!objMetadata) { | ||
| return next(null, bucket, objMetadata, raftSessionId); | ||
| } | ||
| return next(null, bucket, objMD, raftSessionId); | ||
| }); | ||
| }, | ||
| (bucket, objMD, raftSessionId, next) => { | ||
| const objMetadata = objMD; | ||
| const canonicalID = authInfo.getCanonicalID(); | ||
| if (!isObjAuthorized(bucket, objMetadata, requestType, canonicalID, authInfo, log, request, | ||
| actionImplicitDenies)) { | ||
| log.debug('access denied for user on object', { requestType }); | ||
| return next(errors.AccessDenied, bucket, undefined, raftSessionId); | ||
| } | ||
|
|
||
| if (!objMetadata) { | ||
| return next(null, bucket, objMetadata, raftSessionId); | ||
| } | ||
| let returnTagCount = false; | ||
| if (params.returnTagCount) { | ||
| // If returnTagCount is true we know that Vault authorized the request so it is not an implicitDeny. | ||
| const implicitDeny = false; | ||
| if (requestType.some(r => r === 'objectGet')) { | ||
| returnTagCount = isObjAuthorized( | ||
| bucket, | ||
| objMetadata, | ||
| ['objectGetTagging'], | ||
| canonicalID, | ||
| authInfo, | ||
| log, | ||
| request, | ||
| implicitDeny, | ||
| ); | ||
| } else if (requestType.some(r => r === 'objectGetVersion')) { | ||
| returnTagCount = isObjAuthorized( | ||
| bucket, | ||
| objMetadata, | ||
| ['objectGetTaggingVersion'], | ||
| canonicalID, | ||
| authInfo, | ||
| log, | ||
| request, | ||
| implicitDeny, | ||
| ); | ||
| } | ||
|
|
||
| let returnTagCount = false; | ||
| if (params.returnTagCount) { | ||
| // If returnTagCount is true we know that Vault authorized the request so it is not an implicitDeny. | ||
| const implicitDeny = false; | ||
| if (requestType.some(r => r === 'objectGet')) { | ||
| returnTagCount = isObjAuthorized(bucket, objMetadata, ['objectGetTagging'], canonicalID, authInfo, | ||
| log, request, implicitDeny); | ||
| } else if (requestType.some(r => r === 'objectGetVersion')) { | ||
| returnTagCount = isObjAuthorized(bucket, objMetadata, ['objectGetTaggingVersion'], | ||
| canonicalID, authInfo, log, request, implicitDeny); | ||
| objMetadata.returnTagCount = returnTagCount; | ||
| } | ||
|
|
||
| objMetadata.returnTagCount = returnTagCount; | ||
| } | ||
| return next(null, bucket, objMetadata, raftSessionId); | ||
| }, | ||
| (bucket, objMD, raftSessionId, next) => { | ||
| const needQuotaCheck = requestType => requestType.some(type => actionNeedQuotaCheck[type] || | ||
| actionWithDataDeletion[type]); | ||
| const checkQuota = params.checkQuota === undefined ? needQuotaCheck(requestType) : params.checkQuota; | ||
| // withVersionId cover cases when an object is being restored with a specific version ID. | ||
| // In this case, the storage space was already accounted for when the RestoreObject API call | ||
| // was made, so we don't need to add any inflight, but quota must be evaluated. | ||
| if (!checkQuota) { | ||
| return next(null, bucket, objMD, raftSessionId); | ||
| return next(null, bucket, objMetadata, raftSessionId); | ||
| }, |
| (bucket, objMD, raftSessionId, next) => { | ||
| const needQuotaCheck = requestType => | ||
| requestType.some(type => actionNeedQuotaCheck[type] || actionWithDataDeletion[type]); | ||
| const checkQuota = params.checkQuota === undefined ? needQuotaCheck(requestType) : params.checkQuota; | ||
| // withVersionId cover cases when an object is being restored with a specific version ID. | ||
| // In this case, the storage space was already accounted for when the RestoreObject API call | ||
| // was made, so we don't need to add any inflight, but quota must be evaluated. | ||
| if (!checkQuota) { | ||
| return next(null, bucket, objMD, raftSessionId); | ||
| } | ||
| const contentLength = processBytesToWrite( | ||
| request.apiMethod, | ||
| bucket, | ||
| versionId, | ||
| request?.parsedContentLength || 0, | ||
| objMD, | ||
| params.destObjMD, | ||
| ); | ||
| return validateQuotas( | ||
| request, | ||
| bucket, | ||
| request.accountQuotas, | ||
| requestType, | ||
| request.apiMethod, | ||
| contentLength, | ||
| withVersionId, | ||
| log, | ||
| err => next(err, bucket, objMD, raftSessionId), | ||
| ); | ||
| }, |
anurag4DSB
left a comment
There was a problem hiding this comment.
Thanks for making prettier a separate commit :)
|
/approve |
Build failedThe build for commit did not succeed in branch w/9.4/improvement/CLDSRV-945/support_cross_account_rate_limiting The following options are set: approve |
In the queueThe changeset has received all authorizations and has been added to the The changeset will be merged in:
The following branches will NOT be impacted:
This pull request does not target the following hotfix branch(es) so they
There is no action required on your side. You will be notified here once IMPORTANT Please do not attempt to modify this pull request.
If you need this pull request to be removed from the queue, please contact a The following options are set: approve |
Queue build failedThe corresponding build for the queue failed:
Remove the pull request from the queue
|
|
I have successfully merged the changeset of this pull request
The following branches have NOT changed:
Please check the status of the associated issue CLDSRV-945. Goodbye tmacro. |
targetAccountif it has been previously cached.targetAccountdiffers from the requesting account targetAccount's rate limit config is retrieved and returned in response