This is an automated email from the ASF dual-hosted git repository.
oscerd pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new b6ea388b59dc CAMEL-24193: camel-aws-secrets-manager - fix
SQS-notification secret refresh NPE and queue draining (#24847)
b6ea388b59dc is described below
commit b6ea388b59dc0a0259058bd84f354ad19fbbcc6b
Author: Andrea Cosentino <[email protected]>
AuthorDate: Fri Jul 17 15:04:35 2026 +0200
CAMEL-24193: camel-aws-secrets-manager - fix SQS-notification secret
refresh NPE and queue draining (#24847)
In the SQS-notification branch of CloudTrailReloadTriggerTask:
- A QueueDoesNotExistException from receiveMessage was caught and only
logged,
leaving the result null, which was then dereferenced by the message loop
(NPE on every poll). The loop is now guarded against a null receive
result.
- deleteMessage ran only for the first matching message when reload was
enabled, so non-matching messages (and everything when reload was
disabled)
were never deleted and were redelivered on every poll. Every examined
message
is now deleted so the queue is drained; a malformed message body is logged
and discarded instead of aborting the whole batch.
- The per-message ObjectMapper allocation is replaced with a single reused
instance.
Adds CloudTrailReloadTriggerTaskSqsTest covering the drain behaviour and the
missing-queue path.
Co-Authored-By: Claude Fable 5 <[email protected]>
Signed-off-by: Andrea Cosentino <[email protected]>
---
.../camel-aws/camel-aws-secrets-manager/pom.xml | 6 ++
.../vault/CloudTrailReloadTriggerTask.java | 41 +++++----
.../vault/CloudTrailReloadTriggerTaskSqsTest.java | 98 ++++++++++++++++++++++
3 files changed, 123 insertions(+), 22 deletions(-)
diff --git a/components/camel-aws/camel-aws-secrets-manager/pom.xml
b/components/camel-aws/camel-aws-secrets-manager/pom.xml
index 96ddacfba160..43aaed0b9341 100644
--- a/components/camel-aws/camel-aws-secrets-manager/pom.xml
+++ b/components/camel-aws/camel-aws-secrets-manager/pom.xml
@@ -90,6 +90,12 @@
<artifactId>camel-test-spring-junit6</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-junit-jupiter</artifactId>
+ <version>${mockito-version}</version>
+ <scope>test</scope>
+ </dependency>
<!-- test infra -->
<dependency>
diff --git
a/components/camel-aws/camel-aws-secrets-manager/src/main/java/org/apache/camel/component/aws/secretsmanager/vault/CloudTrailReloadTriggerTask.java
b/components/camel-aws/camel-aws-secrets-manager/src/main/java/org/apache/camel/component/aws/secretsmanager/vault/CloudTrailReloadTriggerTask.java
index 5c194115f0ea..0795267005bc 100644
---
a/components/camel-aws/camel-aws-secrets-manager/src/main/java/org/apache/camel/component/aws/secretsmanager/vault/CloudTrailReloadTriggerTask.java
+++
b/components/camel-aws/camel-aws-secrets-manager/src/main/java/org/apache/camel/component/aws/secretsmanager/vault/CloudTrailReloadTriggerTask.java
@@ -92,6 +92,7 @@ public class CloudTrailReloadTriggerTask extends
ServiceSupport implements Camel
private static final String CAMEL_AWS_VAULT_URI_ENDPOINT_OVERRIDE =
"CAMEL_AWS_VAULT_URI_ENDPOINT_OVERRIDE";
private static final Logger LOG =
LoggerFactory.getLogger(CloudTrailReloadTriggerTask.class);
+ private static final ObjectMapper MAPPER = new ObjectMapper();
private static final String SECRETSMANAGER_AMAZONAWS_COM =
"secretsmanager.amazonaws.com";
private static final String SECRETSMANAGER_UPDATE_EVENT = "PutSecretValue";
@@ -329,38 +330,34 @@ public class CloudTrailReloadTriggerTask extends
ServiceSupport implements Camel
LOG.info("Queue does not exist.");
}
- for (Message message : messageResult.messages()) {
- ObjectMapper mapper = new ObjectMapper();
- JsonNode event = null;
- try {
- event = mapper.readTree(message.body());
- } catch (JsonProcessingException e) {
- throw new RuntimeException(e);
- }
- if (ObjectHelper.isNotEmpty(event.get("detail"))) {
- JsonNode innerDetail = event.get("detail");
- if
(innerDetail.get("eventSource").asText().equalsIgnoreCase(SECRETSMANAGER_AMAZONAWS_COM))
{
- if
(innerDetail.get("eventName").asText().equalsIgnoreCase(SECRETSMANAGER_UPDATE_EVENT)
- || innerDetail.get("eventName").asText()
-
.equalsIgnoreCase(SECRETSMANAGER_UPDATE_SECRET_EVENT)) {
+ if (messageResult != null) {
+ for (Message message : messageResult.messages()) {
+ try {
+ JsonNode event = MAPPER.readTree(message.body());
+ JsonNode innerDetail = event.get("detail");
+ if (ObjectHelper.isNotEmpty(innerDetail)
+ &&
innerDetail.get("eventSource").asText().equalsIgnoreCase(SECRETSMANAGER_AMAZONAWS_COM)
+ &&
(innerDetail.get("eventName").asText().equalsIgnoreCase(SECRETSMANAGER_UPDATE_EVENT)
+ ||
innerDetail.get("eventName").asText()
+
.equalsIgnoreCase(SECRETSMANAGER_UPDATE_SECRET_EVENT))) {
String name =
innerDetail.get("requestParameters").get("secretId").asText();
if (matchSecret(name)) {
updates.put(name,
Instant.parse(innerDetail.get("eventTime").asText()));
if (isReloadEnabled()) {
LOG.info("Update for AWS secret: {}
detected, triggering CamelContext reload", name);
triggerReloading = true;
- DeleteMessageRequest.Builder
deleteRequest
- =
DeleteMessageRequest.builder().queueUrl(queueUrl)
-
.receiptHandle(message.receiptHandle());
-
- LOG.trace("Deleting message with
receipt handle {}...", message.receiptHandle());
-
-
sqsClient.deleteMessage(deleteRequest.build());
}
- break;
}
}
+ } catch (JsonProcessingException e) {
+ LOG.warn("Unable to parse SQS message body,
discarding message: {}", e.getMessage());
}
+ // Always delete the examined message so the queue is
drained rather than redelivering it
+ // on every poll (matched -> reload recorded above;
unmatched or malformed -> discarded).
+ LOG.trace("Deleting message with receipt handle
{}...", message.receiptHandle());
+ sqsClient.deleteMessage(
+
DeleteMessageRequest.builder().queueUrl(queueUrl)
+
.receiptHandle(message.receiptHandle()).build());
}
}
} catch (Exception e) {
diff --git
a/components/camel-aws/camel-aws-secrets-manager/src/test/java/org/apache/camel/component/aws/secretsmanager/vault/CloudTrailReloadTriggerTaskSqsTest.java
b/components/camel-aws/camel-aws-secrets-manager/src/test/java/org/apache/camel/component/aws/secretsmanager/vault/CloudTrailReloadTriggerTaskSqsTest.java
new file mode 100644
index 000000000000..3ae4f097ddfc
--- /dev/null
+++
b/components/camel-aws/camel-aws-secrets-manager/src/test/java/org/apache/camel/component/aws/secretsmanager/vault/CloudTrailReloadTriggerTaskSqsTest.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.aws.secretsmanager.vault;
+
+import java.lang.reflect.Field;
+
+import org.apache.camel.impl.DefaultCamelContext;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.mockito.junit.jupiter.MockitoSettings;
+import org.mockito.quality.Strictness;
+import software.amazon.awssdk.services.sqs.SqsClient;
+import software.amazon.awssdk.services.sqs.model.DeleteMessageRequest;
+import software.amazon.awssdk.services.sqs.model.Message;
+import software.amazon.awssdk.services.sqs.model.QueueDoesNotExistException;
+import software.amazon.awssdk.services.sqs.model.ReceiveMessageRequest;
+import software.amazon.awssdk.services.sqs.model.ReceiveMessageResponse;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+@MockitoSettings(strictness = Strictness.LENIENT)
+public class CloudTrailReloadTriggerTaskSqsTest {
+
+ @Mock
+ private SqsClient sqsClient;
+
+ private static String event(String secretId) {
+ return
"{\"detail\":{\"eventSource\":\"secretsmanager.amazonaws.com\",\"eventName\":\"PutSecretValue\","
+ + "\"requestParameters\":{\"secretId\":\"" + secretId +
"\"},\"eventTime\":\"2026-01-01T00:00:00Z\"}}";
+ }
+
+ private CloudTrailReloadTriggerTask task(String secrets) throws Exception {
+ CloudTrailReloadTriggerTask task = new CloudTrailReloadTriggerTask();
+ task.setCamelContext(new DefaultCamelContext());
+ setField(task, "useSqsNotification", true);
+ setField(task, "queueUrl", "http://localhost/queue");
+ setField(task, "sqsClient", sqsClient);
+ setField(task, "secrets", secrets);
+ return task;
+ }
+
+ private static void setField(Object target, String name, Object value)
throws Exception {
+ Field f = CloudTrailReloadTriggerTask.class.getDeclaredField(name);
+ f.setAccessible(true);
+ f.set(target, value);
+ }
+
+ private static Message message(String secretId, String receiptHandle) {
+ return
Message.builder().body(event(secretId)).receiptHandle(receiptHandle).build();
+ }
+
+ @Test
+ public void drainsEveryExaminedMessageIncludingNonMatching() throws
Exception {
+ CloudTrailReloadTriggerTask task = task("tracked-secret");
+ // One message matches the tracked secret, one does not. Both must be
deleted so the queue is drained.
+ when(sqsClient.receiveMessage(any(ReceiveMessageRequest.class)))
+ .thenReturn(ReceiveMessageResponse.builder()
+ .messages(message("other-secret", "rh1"),
message("tracked-secret", "rh2"))
+ .build());
+
+ task.run();
+
+ verify(sqsClient,
times(2)).deleteMessage(any(DeleteMessageRequest.class));
+ }
+
+ @Test
+ public void doesNotThrowOrDeleteWhenQueueDoesNotExist() throws Exception {
+ CloudTrailReloadTriggerTask task = task("tracked-secret");
+ when(sqsClient.receiveMessage(any(ReceiveMessageRequest.class)))
+
.thenThrow(QueueDoesNotExistException.builder().message("missing").build());
+
+ // A missing queue must not lead to a NullPointerException on the
(null) receive result.
+ assertDoesNotThrow(task::run);
+ verify(sqsClient,
never()).deleteMessage(any(DeleteMessageRequest.class));
+ }
+}