Skip to content

INTEGRATION [PR#6239 > development/9.3] Send signatureVersion and authType unswapped to Vault checkPolicies - #6245

Open
bert-e wants to merge 15 commits into
development/9.3from
w/9.3/improvement/CLDSRV-963-fix-swapped-auth-params-check-policies
Open

INTEGRATION [PR#6239 > development/9.3] Send signatureVersion and authType unswapped to Vault checkPolicies#6245
bert-e wants to merge 15 commits into
development/9.3from
w/9.3/improvement/CLDSRV-963-fix-swapped-auth-params-check-policies

Conversation

@bert-e

@bert-e bert-e commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

This pull request has been created automatically.
It is linked to its parent pull request #6239.

Do not edit this pull request directly.
If you need to amend/cancel the changeset on branch
w/9.3/improvement/CLDSRV-963-fix-swapped-auth-params-check-policies, please follow this
procedure:

 git fetch
 git checkout w/9.3/improvement/CLDSRV-963-fix-swapped-auth-params-check-policies
 # <amend or cancel the changeset by _adding_ new commits>
 git push origin w/9.3/improvement/CLDSRV-963-fix-swapped-auth-params-check-policies

Please always comment pull request #6239 instead of this one.

multiObjectDelete assigned authType to signatureVersion and vice versa
when building the checkPolicies request context, so Vault validated and
evaluated each field against the wrong value.

Issue: CLDSRV-963
…ed-auth-params-check-policies' into w/9.2/improvement/CLDSRV-963-fix-swapped-auth-params-check-policies
…-swapped-auth-params-check-policies' into w/9.3/improvement/CLDSRV-963-fix-swapped-auth-params-check-policies
@codecov

codecov Bot commented Aug 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.46690% with 142 lines in your changes missing coverage. Please review.
✅ Project coverage is 85.19%. Comparing base (fec79cf) to head (cbc8519).
⚠️ Report is 366 commits behind head on development/9.3.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
lib/api/multiObjectDelete.js 75.97% 43 Missing ⚠️
lib/routes/routeBackbeat.js 78.17% 43 Missing ⚠️
lib/services.js 80.00% 24 Missing ⚠️
lib/metadata/metadataUtils.js 94.39% 6 Missing ⚠️
lib/api/api.js 94.79% 5 Missing ⚠️
lib/api/bucketGet.js 91.52% 5 Missing ⚠️
lib/api/objectGetLegalHold.js 87.50% 5 Missing ⚠️
lib/api/apiUtils/bucket/bucketCreation.js 95.29% 4 Missing ⚠️
lib/api/bucketUpdateQuota.js 95.00% 3 Missing ⚠️
lib/api/objectGetAttributes.js 96.92% 2 Missing ⚠️
... and 2 more
Additional details and impacted files

Impacted file tree graph

Files with missing lines Coverage Δ
lib/Config.js 80.17% <100.00%> (+0.13%) ⬆️
...i/apiUtils/authorization/prepareRequestContexts.js 95.58% <100.00%> (+0.23%) ⬆️
lib/api/apiUtils/object/lifecycle.js 100.00% <100.00%> (ø)
lib/api/apiUtils/object/objectAttributes.js 100.00% <100.00%> (ø)
lib/api/bucketPut.js 100.00% <100.00%> (+3.88%) ⬆️
lib/api/metadataSearch.js 76.38% <100.00%> (ø)
lib/routes/veeam/list.js 97.67% <ø> (ø)
lib/utilities/reportHandler.js 69.41% <100.00%> (+0.14%) ⬆️
lib/utilities/serverAccessLogger.js 84.87% <ø> (ø)
lib/api/backbeat/listLifecycleNonCurrents.js 95.00% <94.11%> (+0.35%) ⬆️
... and 11 more
@@                 Coverage Diff                 @@
##           development/9.3    #6245      +/-   ##
===================================================
+ Coverage            84.83%   85.19%   +0.36%     
===================================================
  Files                  204      206       +2     
  Lines                13217    13424     +207     
===================================================
+ Hits                 11212    11437     +225     
+ Misses                2005     1987      -18     
Flag Coverage Δ
file-ft-tests 68.33% <70.69%> (+0.15%) ⬆️
file-ft-tests-null-compat 68.89% <71.05%> (+0.26%) ⬆️
kmip-ft-tests 28.36% <22.94%> (-0.06%) ⬇️
mongo-v0-ft-tests 69.54% <71.31%> (+0.18%) ⬆️
mongo-v1-ft-tests 69.53% <71.22%> (+0.18%) ⬆️
multiple-backend 36.83% <46.86%> (+1.14%) ⬆️
s3c-ft-tests-v0 64.10% <74.49%> (+0.60%) ⬆️
s3c-ft-tests-v0-null-compat 64.13% <74.58%> (+0.55%) ⬆️
s3c-ft-tests-v1 64.05% <74.40%> (+0.58%) ⬆️
sur-tests 35.98% <38.65%> (-0.99%) ⬇️
sur-tests-inflights 37.81% <38.92%> (-0.03%) ⬇️
unit 71.08% <72.37%> (+0.74%) ⬆️
utapi-v2-tests 34.62% <38.21%> (-0.08%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

callback => initializeMultiObjectDeleteWithBatchingSupport(bucketName, inPlay, log, callback),
(cache, callback) =>
async.forEachLimit(
inPlay,
Comment on lines +268 to +548
config.multiObjectDeleteConcurrency,
(entry, moveOn) => {
async.waterfall(
[
callback => callback(...decodeObjectVersion(entry, bucketName)),
// for obj deletes, no need to check acl's at object level
// (authority is at the bucket level for obj deletes)
(versionId, callback) =>
metadataUtils.metadataGetObject(
bucketName,
entry.key,
versionId,
cache,
log,
(err, objMD) => callback(err, objMD, versionId),
),
(objMD, versionId, callback) => {
if (!objMD) {
const verCfg = bucket.getVersioningConfiguration();
// To adhere to AWS behavior, create a delete marker
// if trying to delete an object that does not exist
// when versioning has been configured
if (verCfg && !entry.versionId) {
log.debug('trying to delete specific version ' + 'that does not exist');
return callback(null, objMD, versionId);
}
// otherwise if particular key does not exist, AWS
// returns success for key so add to successfullyDeleted
// list and move on
successfullyDeleted.push({ entry });
return callback(skipError);
}
if (
versionId &&
objMD.location &&
Array.isArray(objMD.location) &&
objMD.location[0]
) {
// we need this information for data deletes to AWS
// eslint-disable-next-line no-param-reassign
objMD.location[0].deleteVersion = true;
}
return callback(null, objMD, versionId);
},
(objMD, versionId, callback) => {
// AWS only returns an object lock error if a version id
// is specified, else continue to create a delete marker
if (!versionId || !bucket.isObjectLockEnabled()) {
return callback(null, null, objMD, versionId);
}
const hasGovernanceBypass = hasGovernanceBypassHeader(request.headers);
if (hasGovernanceBypass && isRequesterNonAccountUser(authInfo)) {
return checkUserGovernanceBypass(
request,
authInfo,
bucket,
entry.key,
log,
error => {
if (error && error.is.AccessDenied) {
log.debug(
'user does not have BypassGovernanceRetention and object is locked',
{ error },
);
return callback(objectLockedError);
}
if (error) {
return callback(error);
}
return callback(null, hasGovernanceBypass, objMD, versionId);
},
);
}
return callback(null, hasGovernanceBypass, objMD, versionId);
},
(hasGovernanceBypass, objMD, versionId, callback) => {
// AWS only returns an object lock error if a version id
// is specified, else continue to create a delete marker
if (!versionId || !bucket.isObjectLockEnabled()) {
return callback(null, objMD, versionId);
}
const objLockInfo = new ObjectLockInfo({
mode: objMD.retentionMode,
date: objMD.retentionDate,
legalHold: objMD.legalHold || false,
});

// If the object can not be deleted raise an error
if (!objLockInfo.canModifyObject(hasGovernanceBypass)) {
log.debug('trying to delete locked object');
return callback(objectLockedError);
}
// If the object can not be deleted raise an error
if (!objLockInfo.canModifyObject(hasGovernanceBypass)) {
log.debug('trying to delete locked object');
return callback(objectLockedError);
}

return callback(null, objMD, versionId);
},
(objMD, versionId, callback) => {
const bytes = processBytesToWrite('objectDelete', bucket, versionId, 0, objMD);
return validateQuotas(request, bucket, request.accountQuotas, ['objectDelete'],
'objectDelete', bytes, false, log, err => callback(err, objMD, versionId));
},
(objMD, versionId, callback) => {
const options = preprocessingVersioningDelete(
bucketName, bucket, objMD, versionId, config.nullVersionCompatMode);
const deleteInfo = {};
if (options && options.deleteData) {
options.overheadField = overheadField;
deleteInfo.deleted = true;
if (!_deleteRequiresOplogUpdate(objMD, bucket)) {
options.doesNotNeedOpogUpdate = true;
}
if (objMD.uploadId) {
options.replayId = objMD.uploadId;
}
return services.deleteObject(bucketName, objMD,
entry.key, options, config.multiObjectDeleteEnableOptimizations, log,
's3:ObjectRemoved:Delete', (err, toDelete) => {
if (err) {
return callback(err);
return callback(null, objMD, versionId);
},
(objMD, versionId, callback) => {
const bytes = processBytesToWrite('objectDelete', bucket, versionId, 0, objMD);
return validateQuotas(
request,
bucket,
request.accountQuotas,
['objectDelete'],
'objectDelete',
bytes,
false,
log,
err => callback(err, objMD, versionId),
);
},
(objMD, versionId, callback) => {
const options = preprocessingVersioningDelete(
bucketName,
bucket,
objMD,
versionId,
config.nullVersionCompatMode,
);
const deleteInfo = {};
if (options && options.deleteData) {
options.overheadField = overheadField;
deleteInfo.deleted = true;
if (!_deleteRequiresOplogUpdate(objMD, bucket)) {
options.doesNotNeedOpogUpdate = true;
}
if (objMD.uploadId) {
options.replayId = objMD.uploadId;
}
return services.deleteObject(
bucketName,
objMD,
entry.key,
options,
config.multiObjectDeleteEnableOptimizations,
log,
's3:ObjectRemoved:Delete',
(err, toDelete) => {
if (err) {
return callback(err);
}
if (toDelete) {
deleteFromStorage = deleteFromStorage.concat(toDelete);
}
return callback(null, objMD, deleteInfo);
},
);
}
deleteInfo.newDeleteMarker = true;
// This call will create a delete-marker
return createAndStoreObject(
bucketName,
bucket,
entry.key,
objMD,
authInfo,
canonicalID,
null,
request,
deleteInfo.newDeleteMarker,
null,
overheadField,
log,
's3:ObjectRemoved:DeleteMarkerCreated',
(err, result) => callback(err, objMD, deleteInfo, result.versionId),
);
},
],
(err, objMD, deleteInfo, versionId) => {
if (err === skipError) {
// Object doesn't exist - log without object size (AWS behavior)
queueInternalLogRequest(request, {
objectKey: entry.key,
objectSize: null,
error: null,
});
return moveOn();
} else if (err === objectLockedError) {
errorResults.push({ entry, error: errors.AccessDenied, objectLocked: true });
// Log locked object with size if available
const objectSize =
objMD && objMD['content-length'] ? objMD['content-length'] : null;
queueInternalLogRequest(request, {
objectKey: entry.key,
objectSize,
error: errors.AccessDenied,
});
return moveOn();
} else if (err) {
log.error('error deleting object', { error: err, entry });
errorResults.push({ entry, error: err });
// Log error case with size if available
const objectSize =
objMD && objMD['content-length'] ? objMD['content-length'] : null;
queueInternalLogRequest(request, { objectKey: entry.key, objectSize, error: err });
return moveOn();
}
if (toDelete) {
deleteFromStorage = deleteFromStorage.concat(toDelete);
if (deleteInfo.deleted && objMD['content-length']) {
numOfObjectsRemoved++;
totalContentLengthDeleted += objMD['content-length'];
}
return callback(null, objMD, deleteInfo);
});
}
deleteInfo.newDeleteMarker = true;
// This call will create a delete-marker
return createAndStoreObject(bucketName, bucket, entry.key,
objMD, authInfo, canonicalID, null, request,
deleteInfo.newDeleteMarker, null, overheadField, log,
's3:ObjectRemoved:DeleteMarkerCreated', (err, result) =>
callback(err, objMD, deleteInfo, result.versionId));
},
], (err, objMD, deleteInfo, versionId) => {
if (err === skipError) {
// Object doesn't exist - log without object size (AWS behavior)
queueInternalLogRequest(request, { objectKey: entry.key, objectSize: null, error: null });
return moveOn();
} else if (err === objectLockedError) {
errorResults.push({ entry, error: errors.AccessDenied, objectLocked: true });
// Log locked object with size if available
const objectSize = objMD && objMD['content-length'] ? objMD['content-length'] : null;
queueInternalLogRequest(request, { objectKey: entry.key, objectSize, error: errors.AccessDenied });
return moveOn();
} else if (err) {
log.error('error deleting object', { error: err, entry });
errorResults.push({ entry, error: err });
// Log error case with size if available
const objectSize = objMD && objMD['content-length'] ? objMD['content-length'] : null;
queueInternalLogRequest(request, { objectKey: entry.key, objectSize, error: err });
return moveOn();
}
if (deleteInfo.deleted && objMD['content-length']) {
numOfObjectsRemoved++;
totalContentLengthDeleted += objMD['content-length'];
}
let isDeleteMarker;
let deleteMarkerVersionId;
// - If trying to delete an object that does not exist (if a new
// delete marker was created)
// - Or if an object exists but no version was specified
// return DeleteMarkerVersionId equals the versionID of the marker
// you just generated and DeleteMarker tag equals true
if (deleteInfo.newDeleteMarker) {
isDeleteMarker = true;
deleteMarkerVersionId = versionIdUtils.encode(versionId);
// In this case we are putting a new object (i.e., the delete
// marker), so we decrement the numOfObjectsRemoved value.
numOfObjectsRemoved--;
// If trying to delete a delete marker, DeleteMarkerVersionId equals
// deleteMarker's versionID and DeleteMarker equals true
} else if (objMD && objMD.isDeleteMarker) {
isDeleteMarker = true;
deleteMarkerVersionId = entry.versionId;
}
successfullyDeleted.push({
entry, isDeleteMarker,
deleteMarkerVersionId,
});
// Queue successful deletion with object size
const objectSize = objMD && objMD['content-length'] ? objMD['content-length'] : null;
queueInternalLogRequest(request, { objectKey: entry.key, objectSize, error: null });
return moveOn();
});
},
// end of forEach func
err => {
// Batch delete all objects
const onDone = () => callback(err, quietSetting, errorResults, numOfObjectsRemoved,
successfullyDeleted, totalContentLengthDeleted, bucket);

if (err && deleteFromStorage.length === 0) {
log.trace('no objects to delete from data backend');
return onDone();
}
// If error but we have objects in the list, delete them to ensure
// consistent state.
log.trace('deleting objects from data backend');
let isDeleteMarker;
let deleteMarkerVersionId;
// - If trying to delete an object that does not exist (if a new
// delete marker was created)
// - Or if an object exists but no version was specified
// return DeleteMarkerVersionId equals the versionID of the marker
// you just generated and DeleteMarker tag equals true
if (deleteInfo.newDeleteMarker) {
isDeleteMarker = true;
deleteMarkerVersionId = versionIdUtils.encode(versionId);
// In this case we are putting a new object (i.e., the delete
// marker), so we decrement the numOfObjectsRemoved value.
numOfObjectsRemoved--;
// If trying to delete a delete marker, DeleteMarkerVersionId equals
// deleteMarker's versionID and DeleteMarker equals true
} else if (objMD && objMD.isDeleteMarker) {
isDeleteMarker = true;
deleteMarkerVersionId = entry.versionId;
}
successfullyDeleted.push({
entry,
isDeleteMarker,
deleteMarkerVersionId,
});
// Queue successful deletion with object size
const objectSize = objMD && objMD['content-length'] ? objMD['content-length'] : null;
queueInternalLogRequest(request, { objectKey: entry.key, objectSize, error: null });
return moveOn();
},
);
},
// end of forEach func
err => {
// Batch delete all objects
const onDone = () =>
callback(
err,
quietSetting,
errorResults,
numOfObjectsRemoved,
successfullyDeleted,
totalContentLengthDeleted,
bucket,
);

// Split the array into chunks
const chunks = [];
while (deleteFromStorage.length > 0) {
chunks.push(deleteFromStorage.splice(0, config.multiObjectDeleteConcurrency));
}
if (err && deleteFromStorage.length === 0) {
log.trace('no objects to delete from data backend');
return onDone();
}
// If error but we have objects in the list, delete them to ensure
// consistent state.
log.trace('deleting objects from data backend');

return async.each(chunks, (chunk, done) => data.batchDelete(chunk, null, null,
logger.newRequestLoggerFromSerializedUids(log.getSerializedUids()), done),
err => {
if (err) {
log.error('error deleting objects from data backend', { error: err });
return onDone(err);
// Split the array into chunks
const chunks = [];
while (deleteFromStorage.length > 0) {
chunks.push(deleteFromStorage.splice(0, config.multiObjectDeleteConcurrency));
}
return onDone();
});
}),
], (err, ...results) => {
// if general error from metadata return error
if (err) {
monitoring.promMetrics('DELETE', bucketName, err.code,
'multiObjectDelete');
return next(err);
}
return next(null, ...results);
});

return async.each(
chunks,
(chunk, done) =>
data.batchDelete(
chunk,
null,
null,
logger.newRequestLoggerFromSerializedUids(log.getSerializedUids()),
done,
),
err => {
if (err) {
log.error('error deleting objects from data backend', { error: err });
return onDone(err);
}
return onDone();
},
);
},
),
],
(err, ...results) => {
// if general error from metadata return error
callback => callback(...decodeObjectVersion(entry, bucketName)),
// for obj deletes, no need to check acl's at object level
// (authority is at the bucket level for obj deletes)
(versionId, callback) =>
Comment on lines +278 to +286
entry.key,
versionId,
cache,
log,
(err, objMD) => callback(err, objMD, versionId),
),
(objMD, versionId, callback) => {
if (!objMD) {
const verCfg = bucket.getVersioningConfiguration();
Comment on lines +287 to +314
// To adhere to AWS behavior, create a delete marker
// if trying to delete an object that does not exist
// when versioning has been configured
if (verCfg && !entry.versionId) {
log.debug('trying to delete specific version ' + 'that does not exist');
return callback(null, objMD, versionId);
}
// otherwise if particular key does not exist, AWS
// returns success for key so add to successfullyDeleted
// list and move on
successfullyDeleted.push({ entry });
return callback(skipError);
}
if (
versionId &&
objMD.location &&
Array.isArray(objMD.location) &&
objMD.location[0]
) {
// we need this information for data deletes to AWS
// eslint-disable-next-line no-param-reassign
objMD.location[0].deleteVersion = true;
}
return callback(null, objMD, versionId);
},
(objMD, versionId, callback) => {
// AWS only returns an object lock error if a version id
// is specified, else continue to create a delete marker
Comment on lines 594 to +630
if (err) {
log.trace('error checking policies', {
error: err,
method: 'multiObjectDelete.checkPolicies',
});
log.trace('error retrieving bucket metadata', { error: err });
return next(err);
}
if (objects.length !== authorizationResults.length) {
log.error('vault did not return correct number of ' +
'authorization results', {
authorizationResultsLength:
authorizationResults.length,
objectsLength: objects.length,
});
return next(errors.InternalError);
// check whether bucket has transient or deleted flag
if (bucketShield(bucketMD, 'objectDelete')) {
return next(errors.NoSuchBucket);
}
// Convert authorization results into an easier to handle format
const actionImplicitDenies = authorizationResults.reduce((acc, curr, idx) => {
const apiMethod = authorizationResults[idx].action;
// eslint-disable-next-line no-param-reassign
acc[apiMethod] = curr.isImplicit;
return acc;
}, {});
for (let i = 0; i < authorizationResults.length; i++) {
const result = authorizationResults[i];
// result is { isAllowed: true,
// arn: arn:aws:s3:::bucket/object,
// versionId: sampleversionId } unless not allowed
// in which case no isAllowed key will be present
const slashIndex = result.arn.indexOf('/');
if (slashIndex === -1) {
log.error('wrong arn format from vault');
return next(errors.InternalError);
}
const entry = {
key: result.arn.slice(slashIndex + 1),
versionId: result.versionId,
};
// Deny immediately if there is an explicit deny
if (!result.isImplicit && !result.isAllowed) {
metadataUtils.storeServerAccessLogInfo(request, bucketMD, raftSessionId);
// The implicit deny flag is ignored in the DeleteObjects API, as authorization only
// affects the objects.
if (!isBucketAuthorized(bucketMD, 'objectDelete', canonicalID, authInfo, log, request)) {
log.trace("access denied due to bucket acl's");
// if access denied at the bucket level, no access for
// any of the objects so all results will be error results
objects.forEach(entry => {
errorResults.push({
entry,
error: errors.AccessDenied,
});
continue;
});
// by sending an empty array as the objects array
// async.forEachLimit below will not actually
// make any calls to metadata or data but will continue on
// to the next step to build xml
return next(null, quietSetting, errorResults, [], bucketMD);
}
return next(null, quietSetting, errorResults, objects, bucketMD);
});
},
function checkPolicies(quietSetting, errorResults, objects, bucketMD, next) {
// track keys that are still on track to be deleted
const inPlay = [];
Comment on lines +631 to +758
// if request from account, no need to check policies
// all objects are inPlay so send array of object keys
// as inPlay argument
if (!isRequesterNonAccountUser(authInfo)) {
return next(null, quietSetting, errorResults, objects, bucketMD);
}

// TODO: once arsenal's extractParams is separated from doAuth
// function, refactor so only extract once and send
// params on to this api
const authParams = auth.server.extractParams(request, log, 's3', request.query);
const requestContextParams = {
constantParams: {
headers: request.headers,
query: request.query,
generalResource: request.bucketName,
requesterIp: ip,
sslEnabled: isSecure,
apiMethod: 'objectDelete',
awsService: 's3',
locationConstraint: null,
requesterInfo: authInfo,
signatureVersion: authParams.params.data.signatureVersion,
authType: authParams.params.data.authType,
signatureAge: authParams.params.data.signatureAge,
},
parameterize: {
// eslint-disable-next-line
specificResource: objects.map(entry => {
return {
key: entry.key,
versionId: entry.versionId,
};
}),
},
};
return vault.checkPolicies(
requestContextParams,
authInfo.getArn(),
log,
(err, authorizationResults) => {
// there were no policies so received a blanket AccessDenied
if (err?.is?.AccessDenied) {
objects.forEach(entry => {
errorResults.push({
entry,
error: errors.AccessDenied,
});
});
// send empty array for inPlay
return next(null, quietSetting, errorResults, [], bucketMD);
}
if (err) {
log.trace('error checking policies', {
error: err,
method: 'multiObjectDelete.checkPolicies',
});
return next(err);
}
if (objects.length !== authorizationResults.length) {
log.error('vault did not return correct number of ' + 'authorization results', {
authorizationResultsLength: authorizationResults.length,
objectsLength: objects.length,
});
return next(errors.InternalError);
}
// Convert authorization results into an easier to handle format
const actionImplicitDenies = authorizationResults.reduce((acc, curr, idx) => {
const apiMethod = authorizationResults[idx].action;
// eslint-disable-next-line no-param-reassign
acc[apiMethod] = curr.isImplicit;
return acc;
}, {});
for (let i = 0; i < authorizationResults.length; i++) {
const result = authorizationResults[i];
// result is { isAllowed: true,
// arn: arn:aws:s3:::bucket/object,
// versionId: sampleversionId } unless not allowed
// in which case no isAllowed key will be present
const slashIndex = result.arn.indexOf('/');
if (slashIndex === -1) {
log.error('wrong arn format from vault');
return next(errors.InternalError);
}
const entry = {
key: result.arn.slice(slashIndex + 1),
versionId: result.versionId,
};
// Deny immediately if there is an explicit deny
if (!result.isImplicit && !result.isAllowed) {
errorResults.push({
entry,
error: errors.AccessDenied,
});
continue;
}

// Evaluate against the bucket policies
const areAllActionsAllowed = evaluateBucketPolicyWithIAM(
bucketMD,
Object.keys(actionImplicitDenies),
canonicalID,
authInfo,
actionImplicitDenies,
log,
request);
// Evaluate against the bucket policies
const areAllActionsAllowed = evaluateBucketPolicyWithIAM(
bucketMD,
Object.keys(actionImplicitDenies),
canonicalID,
authInfo,
actionImplicitDenies,
log,
request,
);

if (areAllActionsAllowed) {
if (validObjectKeys.includes(entry.key)) {
inPlayInternal.push(entry.key);
if (areAllActionsAllowed) {
if (validObjectKeys.includes(entry.key)) {
inPlayInternal.push(entry.key);
} else {
inPlay.push(entry);
}
} else {
inPlay.push(entry);
errorResults.push({
entry,
error: errors.AccessDenied,
});
}
} else {
errorResults.push({
entry,
error: errors.AccessDenied,
});
}
}
return next(null, quietSetting, errorResults, inPlay, bucketMD);
});
},
function handleInternalFiles(quietSetting, errorResults, inPlay, bucketMD, next) {
return async.each(inPlayInternal,
(localInPlay, next) => deleteVeeamCapabilities(bucketName, localInPlay, bucketMD, log, next),
err => next(err, quietSetting, errorResults, inPlay, bucketMD));
},
function getObjMetadataAndDeleteStep(quietSetting, errorResults, inPlay,
bucket, next) {
return getObjMetadataAndDelete(authInfo, canonicalID, request,
bucketName, bucket, quietSetting, errorResults, inPlay,
log, next);
return next(null, quietSetting, errorResults, inPlay, bucketMD);
},
);
},
function handleInternalFiles(quietSetting, errorResults, inPlay, bucketMD, next) {
return async.each(
inPlayInternal,
Comment on lines +759 to +765
(localInPlay, next) => deleteVeeamCapabilities(bucketName, localInPlay, bucketMD, log, next),
err => next(err, quietSetting, errorResults, inPlay, bucketMD),
);
},
function getObjMetadataAndDeleteStep(quietSetting, errorResults, inPlay, bucket, next) {
return getObjMetadataAndDelete(
authInfo,
(localInPlay, next) => deleteVeeamCapabilities(bucketName, localInPlay, bucketMD, log, next),
err => next(err, quietSetting, errorResults, inPlay, bucketMD),
);
},
Comment on lines +766 to +779
canonicalID,
request,
bucketName,
bucket,
quietSetting,
errorResults,
inPlay,
log,
next,
);
},
],
(
err,
@benzekrimaha
benzekrimaha force-pushed the w/9.3/improvement/CLDSRV-963-fix-swapped-auth-params-check-policies branch 2 times, most recently from 39a5e96 to 3d83c11 Compare August 4, 2026 18:48
@benzekrimaha
benzekrimaha force-pushed the w/9.3/improvement/CLDSRV-963-fix-swapped-auth-params-check-policies branch from 3d83c11 to dbea941 Compare August 4, 2026 18:52
awsService: 's3',
locationConstraint: null,
requesterInfo: authInfo,
signatureVersion: authParams.params.data.signatureVersion,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same swap bug exists in lib/api/bucketPut.js lines 129-130:

signatureVersion: authParams.params.data.authType,
authType: authParams.params.data.signatureVersion,

Should be fixed there too for consistency.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: the same swap in lib/api/bucketPut.js is now corrected on the source PR (#6239) with a matching unit test, and propagated to the integration branches.

Comment thread lib/api/bucketPut.js
}
return waterfall(
[
next => _parseXML(request, log, next),
Comment thread lib/api/bucketPut.js
Comment on lines +235 to +255
(locationConstraint, next) => {
if (!isRequesterNonAccountUser(authInfo)) {
return next(null, locationConstraint);
}

const authParams = auth.server.extractParams(request, log, 's3', request.query);
const requestConstantParams = authBucketPut(
authParams, bucketName, locationConstraint, request, authInfo
);
const authParams = auth.server.extractParams(request, log, 's3', request.query);
const requestConstantParams = authBucketPut(
authParams,
bucketName,
locationConstraint,
request,
authInfo,
);

return vault.checkPolicies(
requestConstantParams.map(_buildConstantParams),
authInfo.getArn(),
log,
_handleAuthResults(locationConstraint, log, next),
);
},
(locationConstraint, next) => createBucket(authInfo, bucketName,
request.headers, locationConstraint, log, (err, previousBucket) => {
// if bucket already existed, gather any relevant cors
// headers
const corsHeaders = collectCorsHeaders(
request.headers.origin, request.method, previousBucket);
if (err) {
return next(err, corsHeaders);
}
pushMetric('createBucket', log, {
authInfo,
bucket: bucketName,
});
monitoring.promMetrics('PUT', bucketName, '200', 'createBucket');
return next(null, corsHeaders);
}),
], callback);
return vault.checkPolicies(
requestConstantParams.map(_buildConstantParams),
authInfo.getArn(),
log,
_handleAuthResults(locationConstraint, log, next),
);
},
Comment thread lib/api/bucketPut.js
Comment on lines +256 to +270
(locationConstraint, next) =>
createBucket(authInfo, bucketName, request.headers, locationConstraint, log, (err, previousBucket) => {
// if bucket already existed, gather any relevant cors
// headers
const corsHeaders = collectCorsHeaders(request.headers.origin, request.method, previousBucket);
if (err) {
return next(err, corsHeaders);
}
pushMetric('createBucket', log, {
authInfo,
bucket: bucketName,
});
monitoring.promMetrics('PUT', bucketName, '200', 'createBucket');
return next(null, corsHeaders);
}),
benzekrimaha and others added 6 commits August 5, 2026 12:29
…licies' into tmp/octopus/w/9.2/improvement/CLDSRV-963-fix-swapped-auth-params-check-policies
…eck-policies' into tmp/octopus/w/9.3/improvement/CLDSRV-963-fix-swapped-auth-params-check-policies
…licies' into tmp/octopus/w/9.2/improvement/CLDSRV-963-fix-swapped-auth-params-check-policies
…eck-policies' into tmp/octopus/w/9.3/improvement/CLDSRV-963-fix-swapped-auth-params-check-policies
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants