shashank created CAMEL-25770:
--------------------------------
Summary: camel-azure-storage-blob, camel-azure-storage-datalake,
camel-google-storage, camel-minio - one listed object that cannot be got
(access denied, archived) fails every poll and the consumer never consumes the
bucket again
Key: CAMEL-25770
URL: https://issues.apache.org/jira/browse/CAMEL-25770
Project: Camel
Issue Type: Bug
Components: camel-azure, camel-google-storage, camel-minio
Reporter: shashank
Assignee: shashank
The same defect as CAMEL-25558 (camel-aws2-s3) and CAMEL-25760
(camel-alibaba-oss, camel-huaweicloud-obs) in four more object-store consumers.
Each lists the whole bucket / container / file system at every poll, so one
listed item whose get fails every time fails every poll and the consumer is
stuck for good. The fix in each module mirrors CAMEL-25558: skip and report the
item, fail the poll only when none of the listed items could be got. One pull
request per module.
h3. camel-azure-storage-blob
{{BlobConsumer.poll}} (main e13303ef1ef7) lists the whole container at every
poll ({{createBatchExchangesFromContainer}}, lines 124-145; {{listBlobs}}
streams every page) and gets every listed blob in {{addExchange}} (lines
148-168; the get is {{BlobOperations.getBlob}}, or the download to
{{fileDir}}). A blob that no longer exists (404) is skipped since CAMEL-25551;
any other failure of the get is rethrown, and only a 404 of the container is
turned into an empty poll (line 93):
{code:java}
try {
for (BlobItem blobItem : blobs) {
addExchange(blobItem.getName(), blobContainerClient, exchanges, true);
}
} catch (Exception e) {
// none of the blobs of this poll is processed
releaseNotProcessed(exchanges);
throw e;
}
{code}
As the next poll lists the whole container again, one blob whose get fails
every time stops the consumer from consuming any blob, for good. The most
common case is a blob in the archive tier: a lifecycle management rule moves
old blobs to the archive tier, and reading an archived blob fails with 409
BlobArchived until it is rehydrated. Others: access denied for that blob (an
ABAC condition on blob index tags), a blob encrypted with a customer-provided
key. The consumer keeps running and logs one WARN per poll.
camel-aws2-s3 had the same behaviour and CAMEL-25558 changed it: a listed
object whose get fails is skipped for that poll and reported to the exception
handler with its key, the other listed objects are consumed, and the poll fails
only when none of the listed objects could be got. CAMEL-25760 does the same
for camel-alibaba-oss and camel-huaweicloud-obs.
*Fix*: the same in {{BlobConsumer.createBatchExchangesFromContainer}}: a failed
get of one listed blob is collected, the blob is skipped and reported to the
consumer's exception handler ("Error getting blob <name> from container
<container>. The blob is skipped for this poll."), and the other blobs are
consumed; when none of the listed blobs could be got, the poll fails with the
first failure as before. A failure with {{blobName}} set, and a failure of the
listing, are not changed. Upgrade note in the 4.24 guide.
*Tests*: new {{BlobConsumerBlobThatCannotBeGotTest}} (2 tests, the mocked
{{BlobServiceClient}} of {{BlobConsumerInProgressTest}}; the get of an archived
blob throws a {{BlobStorageException}} with status 409).
{{blobThatCannotBeGotDoesNotBlockTheOtherBlobs}} (a.txt, b.txt archived, c.txt)
fails on main: {{mock://result Received message count. Expected: <2> but was:
<0>}}. {{pollFailsWhenNoListedBlobCanBeGot}} passes on main and with the fix;
it guards the case the fix must not swallow. camel-azure-storage-blob unit
tests: 66 run, 0 failures.
*How it was found*: variant analysis of CAMEL-25558 (the fix of camel-aws2-s3)
across the object-store consumers, rule {{listing-poll-rethrows-one-item}} (a
Semgrep rule: a {{ScheduledBatchPollingConsumer}} that gets every listed item
inside one try block and rethrows the first failure). The JUnit tests above are
the evidence; the 409 BlobArchived answer is from the Azure Storage
documentation and was not run against Azure.
*Affected*: main and camel-4.23.x (the same rethrow); camel-4.22.x and
camel-4.18.x have no per-blob catch at all, so there a blob deleted after the
listing fails the poll too (read, not run).
h3. camel-azure-storage-datalake
{{DataLakeConsumer.poll}} (main e13303ef1ef7, lines 54-79) lists the whole file
system at every poll ({{createBatchExchangesFromPath}}, lines 82-101) and gets
every listed file with {{createExchangeFromFile}} (lines 103-122; the get is
{{DataLakeFileOperations.getFile}}, or the download to {{fileDir}}). Nothing
catches the failure of one file:
{code:java}
for (PathItem pathItem : items) {
if (!pathItem.isDirectory()) {
exchanges.add(createExchangeFromFile(pathItem.getName(),
dataLakeFileSystemClient));
}
}
{code}
A {{DataLakeStorageException}} with status 404 (a file deleted after the
listing) ends the whole poll with no exchange (lines 71-77; the exchanges
already created for the files listed before it are dropped, not routed), and
any other failure fails the poll. As the next poll lists the file system again,
one file whose get fails every time stops the consumer from consuming any file,
for good. In Data Lake Storage Gen2 every file has its own POSIX ACL, so a file
the consumer's identity may list but not read (403
AuthorizationPermissionMismatch) is a normal state, as is a file encrypted with
a customer-provided key. The consumer keeps running and logs one WARN per poll.
camel-aws2-s3 had the same behaviour and CAMEL-25558 changed it: a listed
object whose get fails is skipped for that poll and reported to the exception
handler with its key, the other listed objects are consumed, and the poll fails
only when none of the listed objects could be got. CAMEL-25760 does the same
for camel-alibaba-oss and camel-huaweicloud-obs.
*Fix*: the same in {{DataLakeConsumer.createBatchExchangesFromPath}}: a failed
get of one listed file is collected, the file is skipped and reported to the
consumer's exception handler ("Error getting file <name> from file system <fs>.
The file is skipped for this poll."), and the other files are consumed; a file
deleted after the listing (404) is skipped without failing the poll; when none
of the listed files could be got, the poll fails with the first failure as
before. A failure with {{fileName}} set, and a failure of the listing, are not
changed. Upgrade note in the 4.24 guide.
*Tests*: new {{DataLakeConsumerFileThatCannotBeGotTest}} (3 tests, a mocked
{{DataLakeServiceClient}}; the read of a forbidden file throws a
{{DataLakeStorageException}} with status 403).
{{fileThatCannotBeGotDoesNotBlockTheOtherFiles}} (a.txt, b.txt forbidden,
c.txt) fails on main: {{mock://result Received message count 0, expected at
least 2}}. {{fileDeletedAfterTheListingIsSkipped}} (a.txt, b.txt answering 404,
c.txt; nothing is reported) fails on main the same way.
{{pollFailsWhenNoListedFileCanBeGot}} passes on main and with the fix; it
guards the case the fix must not swallow. camel-azure-storage-datalake unit
tests: 19 run, 0 failures.
*How it was found*: variant analysis of CAMEL-25558 (the fix of camel-aws2-s3)
across the object-store consumers, rule {{listing-poll-uncaught-item-get}} (a
Semgrep rule: a {{ScheduledBatchPollingConsumer}} that creates the exchange of
every listed item, which downloads it, with no per-item catch). The JUnit tests
above are the evidence; the 403 answer is from the Azure Storage documentation
and was not run against Azure.
*Affected*: main, camel-4.23.x, camel-4.22.x and camel-4.18.x (the same loop;
read, not run).
h3. camel-google-storage
{{GoogleCloudStorageConsumer.poll}} (main e13303ef1ef7, lines 122-151) lists
the whole bucket at every poll ({{page.iterateAll()}}) and downloads every
listed object in {{createExchanges}} / {{addExchange}} (lines 160-209; the
download is {{blob.downloadTo}} in {{populateExchange}}, lines 392 and 401). An
object that no longer exists (404) is skipped since CAMEL-25519, and an object
whose name is rejected as a local path is skipped; any other failure of the
download is rethrown and fails the poll:
{code:java}
} catch (Exception e) {
LOG.warn("Error getting object due: {}", e.getMessage(), e);
// none of the objects of this poll is processed
releaseInProgress(answer);
throw e;
}
{code}
As the next poll lists the whole bucket again, one object whose download fails
every time (access denied for that object, an object encrypted with a
customer-supplied key (CSEK), an object whose Cloud KMS key version was
disabled) stops the consumer from consuming any object of the bucket, for good.
The consumer keeps running and logs a WARN with a stack trace per poll, until
someone removes the object.
camel-aws2-s3 had the same behaviour and CAMEL-25558 changed it: a listed
object whose get fails is skipped for that poll and reported to the exception
handler with its key, the other listed objects are consumed, and the poll fails
only when none of the listed objects could be got. CAMEL-25760 does the same
for camel-alibaba-oss and camel-huaweicloud-obs.
*Fix*: the same in {{GoogleCloudStorageConsumer.createExchanges}}: a failed
download of one listed object is collected, the object is skipped and reported
to the consumer's exception handler ("Error getting object <name> from bucket
<bucket>. The object is skipped for this poll."), and the other objects are
consumed; when none of the listed objects could be got, the poll fails with the
first failure as before. A failure of the listing, and a failure with
{{objectName}} set, still fail the poll. Upgrade note in the 4.24 guide.
*Tests*: new {{ConsumerObjectThatCannotBeGotTest}} (2 tests, with the module's
in-process {{LocalStorageHelper}} storage, which can now refuse the reads of an
object with 403). {{objectThatCannotBeGotDoesNotBlockTheOtherObjects}} (a.txt,
b.txt refused, c.txt) fails on main: {{mock://result Received message count.
Expected: <2> but was: <0>}}. {{pollFailsWhenNoListedObjectCanBeGot}} passes on
main and with the fix; it guards the case the fix must not swallow.
camel-google-storage unit tests: 63 run, 0 failures.
*How it was found*: variant analysis of CAMEL-25558 (the fix of camel-aws2-s3)
across the object-store consumers, rule {{listing-poll-rethrows-one-item}} (a
Semgrep rule: a {{ScheduledBatchPollingConsumer}} that gets every listed item
inside one try block and rethrows the first failure). The JUnit tests above are
the evidence; the status codes of Cloud Storage were not run against the
service.
*Affected*: main, camel-4.23.x, camel-4.22.x and camel-4.18.x (the same rethrow
in {{createExchanges}}; read, not run).
h3. camel-minio
{{MinioConsumer}} (main e13303ef1ef7) stats every listed object when it creates
the exchanges ({{createExchanges}} / {{addExchange}}, lines 201-254, the stat
is {{MinioEndpoint.getObjectStat}}), and gets the body of each object when it
routes the batch ({{processBatch}}, lines 289-331). An object that no longer
exists is skipped since CAMEL-25512. Any other failure is rethrown:
{code:java}
} catch (Exception e) {
...
LOG.warn("Error getting MinioObject due: {}", e.getMessage());
// this and the remaining exchanges of the batch are not processed
releaseInProgress(srcObjectName, exchanges);
throw e;
}
{code}
So one object whose stat or get fails every time (access denied for that object
by a bucket or IAM policy, an object encrypted with a customer key (SSE-C)
other than the one of the endpoint) fails every poll: a failed stat drops every
exchange of the poll, and a failed get stops the batch, so the objects listed
after it are not consumed. The next poll lists the same objects again (with the
default {{deleteAfterRead=true}} the objects before it are gone, so the bad
object comes first and nothing after it is ever consumed). The consumer keeps
running and logs a WARN at every poll; it lasts until someone removes the
object.
camel-aws2-s3 had the same behaviour and CAMEL-25558 changed it: a listed
object whose get fails is skipped for that poll and reported to the exception
handler with its key, the other listed objects are consumed, and the poll fails
only when none of the listed objects could be got. CAMEL-25760 does the same
for camel-alibaba-oss and camel-huaweicloud-obs.
*Fix*: the same in {{MinioConsumer}}, for both places: a failed stat (in
{{createExchanges}}) or get (in {{processBatch}}) of one listed object is
collected, the object is skipped (released from the in-progress set) and
reported to the consumer's exception handler ("Error getting object <name> from
bucket <bucket>. The object is skipped for this poll."), and the other objects
are consumed; when none of the objects could be got, the poll fails with the
first failure as before. A failure of the listing itself, and a failure with
{{objectName}} set, still fail the poll. Upgrade note in the 4.24 guide.
*Tests*: new {{MinioConsumerObjectThatCannotBeGotTest}} (3 tests, the real
Minio client against the in-process fake S3 server of the module's tests, which
can now refuse the stats and gets of an object with 403 AccessDenied).
{{objectWhoseBodyCannotBeGotDoesNotBlockTheOtherObjects}} (a.txt, b.txt whose
get is refused, c.txt) fails on main: {{mock://result Received message count.
Expected: <2> but was: <1>}} (c.txt is never consumed).
{{objectThatCannotBeStatedDoesNotBlockTheOtherObjects}} fails on main:
{{Expected: <2> but was: <0>}}. {{pollFailsWhenNoListedObjectCanBeGot}} passes
on main and with the fix; it guards the case the fix must not swallow.
camel-minio unit tests: 10 run, 0 failures.
*How it was found*: variant analysis of CAMEL-25558 (the fix of camel-aws2-s3)
across the object-store consumers, rule {{listing-poll-rethrows-one-item}} (a
Semgrep rule: a {{ScheduledBatchPollingConsumer}} that gets every listed item
inside one try block and rethrows the first failure). The JUnit tests above are
the evidence; the S3 status codes are those of the S3 API and were not run
against a MinIO server.
*Affected*: main, camel-4.23.x, camel-4.22.x and camel-4.18.x (the same rethrow
in {{createExchanges}} and {{processBatch}}; on 4.22.x and 4.18.x, which do not
have CAMEL-25512, an object deleted after the listing fails the poll too; read,
not run). The component is deprecated since 4.23 (CAMEL-24750) but still
shipped; CAMEL-25512 was fixed in it after the deprecation.
_Filed with Claude Code on behalf of allthingssecurity._
--
This message was sent by Atlassian Jira
(v8.20.10#820010)