[ 
https://issues.apache.org/jira/browse/CAMEL-25770?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Claus Ibsen resolved CAMEL-25770.
---------------------------------
    Resolution: Fixed

> 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
>            Priority: Major
>             Fix For: 4.24.0
>
>
> 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)

Reply via email to