This is an automated email from the ASF dual-hosted git repository.

davsclaus 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 39f53df14149 CAMEL-26256: camel-cyberark-vault - retry a rejected 
token once and only when a new one can be obtained, and check the credentials 
at endpoint start (#29209)
39f53df14149 is described below

commit 39f53df141491ccd4c491d0086f7e41185ee8820
Author: Andrea Cosentino <[email protected]>
AuthorDate: Thu Nov 5 13:01:24 2026 +0100

    CAMEL-26256: camel-cyberark-vault - retry a rejected token once and only 
when a new one can be obtained, and check the credentials at endpoint start 
(#29209)
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../catalog/docs/cyberark-vault-component.adoc     |   8 +
 .../src/main/docs/cyberark-vault-component.adoc    |   8 +
 .../cyberark/vault/CyberArkVaultEndpoint.java      |  15 ++
 .../vault/client/impl/ConjurClientImpl.java        |  30 +++-
 .../CyberArkVaultEndpointCredentialsTest.java      | 122 ++++++++++++++
 .../impl/ConjurClientReauthenticationTest.java     | 177 +++++++++++++++++++++
 .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc    |  14 ++
 7 files changed, 367 insertions(+), 7 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/cyberark-vault-component.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/cyberark-vault-component.adoc
index 7f806962a634..7c8735d9c962 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/cyberark-vault-component.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/cyberark-vault-component.adoc
@@ -48,6 +48,14 @@ The component supports multiple authentication methods with 
CyberArk Conjur:
 2. **API Key Authentication**: Use username and API key (recommended for 
programmatic access)
 3. **Pre-authenticated Token**: Use a pre-obtained authentication token
 
+The endpoint fails to start when it has no usable credentials (an `authToken`, 
or a `username` with an `apiKey` or a
+`password`) and no `conjurClient` is set.
+
+When Conjur rejects the access token with HTTP 401, for example because it has 
expired, the component authenticates
+again with the API key or the password, and retries the call once. A 
pre-authenticated token cannot be renewed: the
+call fails with the 401 response, and a new token has to be configured. The 
`authToken` takes precedence: when it is
+set, the component uses only that token, also when a `username` with an 
`apiKey` or a `password` is set as well.
+
 === Using CyberArk Conjur Vault Property Function
 
 To use this function, you'll need to provide credentials to CyberArk Conjur 
Service as environment variables:
diff --git 
a/components/camel-cyberark-vault/src/main/docs/cyberark-vault-component.adoc 
b/components/camel-cyberark-vault/src/main/docs/cyberark-vault-component.adoc
index 7f806962a634..7c8735d9c962 100644
--- 
a/components/camel-cyberark-vault/src/main/docs/cyberark-vault-component.adoc
+++ 
b/components/camel-cyberark-vault/src/main/docs/cyberark-vault-component.adoc
@@ -48,6 +48,14 @@ The component supports multiple authentication methods with 
CyberArk Conjur:
 2. **API Key Authentication**: Use username and API key (recommended for 
programmatic access)
 3. **Pre-authenticated Token**: Use a pre-obtained authentication token
 
+The endpoint fails to start when it has no usable credentials (an `authToken`, 
or a `username` with an `apiKey` or a
+`password`) and no `conjurClient` is set.
+
+When Conjur rejects the access token with HTTP 401, for example because it has 
expired, the component authenticates
+again with the API key or the password, and retries the call once. A 
pre-authenticated token cannot be renewed: the
+call fails with the 401 response, and a new token has to be configured. The 
`authToken` takes precedence: when it is
+set, the component uses only that token, also when a `username` with an 
`apiKey` or a `password` is set as well.
+
 === Using CyberArk Conjur Vault Property Function
 
 To use this function, you'll need to provide credentials to CyberArk Conjur 
Service as environment variables:
diff --git 
a/components/camel-cyberark-vault/src/main/java/org/apache/camel/component/cyberark/vault/CyberArkVaultEndpoint.java
 
b/components/camel-cyberark-vault/src/main/java/org/apache/camel/component/cyberark/vault/CyberArkVaultEndpoint.java
index 7a522ae93f23..2a2b05e5b482 100644
--- 
a/components/camel-cyberark-vault/src/main/java/org/apache/camel/component/cyberark/vault/CyberArkVaultEndpoint.java
+++ 
b/components/camel-cyberark-vault/src/main/java/org/apache/camel/component/cyberark/vault/CyberArkVaultEndpoint.java
@@ -84,6 +84,10 @@ public class CyberArkVaultEndpoint extends DefaultEndpoint 
implements EndpointSe
     protected void doStart() throws Exception {
         super.doStart();
 
+        if (configuration.getConjurClient() == null) {
+            checkCredentials();
+        }
+
         conjurClient = configuration.getConjurClient();
         if (conjurClient == null) {
             String url = configuration.getUrl();
@@ -112,6 +116,17 @@ public class CyberArkVaultEndpoint extends DefaultEndpoint 
implements EndpointSe
         }
     }
 
+    private void checkCredentials() {
+        boolean hasLogin = ObjectHelper.isNotEmpty(configuration.getUsername())
+                && (ObjectHelper.isNotEmpty(configuration.getApiKey()) || 
ObjectHelper.isNotEmpty(configuration.getPassword()));
+        if (ObjectHelper.isEmpty(configuration.getAuthToken()) && !hasLogin) {
+            throw new IllegalArgumentException(
+                    "No credentials to authenticate with CyberArk Conjur: set 
the authToken option, the username and apiKey"
+                                               + " options, or the username 
and password options, or set the conjurClient"
+                                               + " option");
+        }
+    }
+
     public CyberArkVaultConfiguration getConfiguration() {
         return configuration;
     }
diff --git 
a/components/camel-cyberark-vault/src/main/java/org/apache/camel/component/cyberark/vault/client/impl/ConjurClientImpl.java
 
b/components/camel-cyberark-vault/src/main/java/org/apache/camel/component/cyberark/vault/client/impl/ConjurClientImpl.java
index 339f3154e367..d0d4af414f43 100644
--- 
a/components/camel-cyberark-vault/src/main/java/org/apache/camel/component/cyberark/vault/client/impl/ConjurClientImpl.java
+++ 
b/components/camel-cyberark-vault/src/main/java/org/apache/camel/component/cyberark/vault/client/impl/ConjurClientImpl.java
@@ -42,7 +42,7 @@ public class ConjurClientImpl implements ConjurClient {
     private final String username;
     private final String password;
     private final String apiKey;
-    private String authToken;
+    private volatile String authToken;
     private final HttpClient httpClient;
 
     public ConjurClientImpl(
@@ -66,6 +66,10 @@ public class ConjurClientImpl implements ConjurClient {
 
     @Override
     public String retrieveSecret(String secretId, String version) {
+        return retrieveSecret(secretId, version, true);
+    }
+
+    private String retrieveSecret(String secretId, String version, boolean 
retryOnUnauthorized) {
         try {
             // Ensure we have a valid token
             if (authToken == null) {
@@ -94,11 +98,11 @@ public class ConjurClientImpl implements ConjurClient {
 
             if (response.statusCode() == 200) {
                 return response.body();
-            } else if (response.statusCode() == 401) {
-                // Token expired, re-authenticate and retry
+            } else if (response.statusCode() == 401 && retryOnUnauthorized && 
canReauthenticate()) {
+                // Token expired, re-authenticate and retry once
                 LOG.debug("Token expired, re-authenticating");
                 authToken = authenticate();
-                return retrieveSecret(secretId, version);
+                return retrieveSecret(secretId, version, false);
             } else {
                 throw new IOException(
                         "Failed to retrieve secret: HTTP " + 
response.statusCode() + " - " + response.body());
@@ -157,6 +161,10 @@ public class ConjurClientImpl implements ConjurClient {
 
     @Override
     public void createSecret(String secretId, String secretValue) {
+        createSecret(secretId, secretValue, true);
+    }
+
+    private void createSecret(String secretId, String secretValue, boolean 
retryOnUnauthorized) {
         try {
             // Ensure we have a valid token
             if (authToken == null) {
@@ -182,11 +190,11 @@ public class ConjurClientImpl implements ConjurClient {
 
             if (response.statusCode() == 201 || response.statusCode() == 200) {
                 LOG.debug("Successfully created/updated secret: {}", secretId);
-            } else if (response.statusCode() == 401) {
-                // Token expired, re-authenticate and retry
+            } else if (response.statusCode() == 401 && retryOnUnauthorized && 
canReauthenticate()) {
+                // Token expired, re-authenticate and retry once
                 LOG.debug("Token expired, re-authenticating");
                 authToken = authenticate();
-                createSecret(secretId, secretValue);
+                createSecret(secretId, secretValue, false);
             } else {
                 throw new IOException(
                         "Failed to create/update secret: HTTP " + 
response.statusCode() + " - " + response.body());
@@ -196,6 +204,14 @@ public class ConjurClientImpl implements ConjurClient {
         }
     }
 
+    /**
+     * Whether a new access token can be obtained after Conjur rejects the 
current one. That needs an API key or a
+     * password: a client that only has a pre-authenticated token cannot get a 
new token.
+     */
+    private boolean canReauthenticate() {
+        return apiKey != null || password != null;
+    }
+
     @Override
     public void close() throws Exception {
         authToken = null;
diff --git 
a/components/camel-cyberark-vault/src/test/java/org/apache/camel/component/cyberark/vault/CyberArkVaultEndpointCredentialsTest.java
 
b/components/camel-cyberark-vault/src/test/java/org/apache/camel/component/cyberark/vault/CyberArkVaultEndpointCredentialsTest.java
new file mode 100644
index 000000000000..c178256dca54
--- /dev/null
+++ 
b/components/camel-cyberark-vault/src/test/java/org/apache/camel/component/cyberark/vault/CyberArkVaultEndpointCredentialsTest.java
@@ -0,0 +1,122 @@
+/*
+ * 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.cyberark.vault;
+
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import com.sun.net.httpserver.HttpServer;
+import org.apache.camel.CamelExecutionException;
+import org.apache.camel.Endpoint;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * An endpoint without usable credentials fails to start, with a message that 
names the options to set, instead of
+ * failing on the first message. An endpoint with an authToken uses only that 
token.
+ */
+class CyberArkVaultEndpointCredentialsTest extends CamelTestSupport {
+
+    private static final String CONJUR = 
"url=http://localhost:1&account=myaccount";;
+
+    @Test
+    void apiKeyWithoutUsernameFailsAtStart() throws Exception {
+        Endpoint endpoint = context.getComponent("cyberark-vault")
+                .createEndpoint("cyberark-vault:secret?secretId=db/password&" 
+ CONJUR + "&apiKey=my-api-key");
+
+        IllegalArgumentException e = 
assertThrows(IllegalArgumentException.class, endpoint::start);
+        assertNamesTheOptions(e);
+        assertFalse(e.getMessage().contains("my-api-key"), e.getMessage());
+    }
+
+    @Test
+    void noCredentialsFailsAtStart() {
+        CyberArkVaultConfiguration configuration = new 
CyberArkVaultConfiguration();
+        configuration.setUrl("http://localhost:1";);
+        configuration.setAccount("myaccount");
+        configuration.setSecretId("db/password");
+        CyberArkVaultEndpoint endpoint = new CyberArkVaultEndpoint(
+                "cyberark-vault:secret", 
context.getComponent("cyberark-vault"), configuration);
+
+        IllegalArgumentException e = 
assertThrows(IllegalArgumentException.class, endpoint::start);
+        assertNamesTheOptions(e);
+    }
+
+    @Test
+    void usernameAndApiKeyStart() throws Exception {
+        CyberArkVaultEndpoint endpoint = (CyberArkVaultEndpoint) 
context.getComponent("cyberark-vault")
+                .createEndpoint("cyberark-vault:secret?secretId=db/password&" 
+ CONJUR
+                                + "&username=host/myapp&apiKey=my-api-key");
+
+        endpoint.start();
+        try {
+            assertNotNull(endpoint.getConjurClient());
+        } finally {
+            endpoint.stop();
+        }
+    }
+
+    @Test
+    void authTokenTakesPrecedenceOverTheLoginCredentials() throws Exception {
+        AtomicInteger logins = new AtomicInteger();
+        HttpServer conjur = HttpServer.create(new 
InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 0);
+        conjur.createContext("/authn/", exchange -> {
+            logins.incrementAndGet();
+            exchange.getRequestBody().readAllBytes();
+            byte[] token = "new-token".getBytes(StandardCharsets.UTF_8);
+            exchange.sendResponseHeaders(200, token.length);
+            try (OutputStream out = exchange.getResponseBody()) {
+                out.write(token);
+            }
+        });
+        conjur.createContext("/secrets/", exchange -> {
+            exchange.sendResponseHeaders(401, -1);
+            exchange.close();
+        });
+        conjur.start();
+        try {
+            String uri = 
"cyberark-vault:secret?secretId=db/password&url=http://localhost:"; + 
conjur.getAddress().getPort()
+                         + 
"&account=myaccount&authToken=my-token&username=host/myapp&apiKey=my-api-key";
+
+            // the endpoint uses only the token, so the 401 fails at once, 
without a login with the API key
+            CamelExecutionException e = 
assertThrows(CamelExecutionException.class,
+                    () -> template.requestBody(uri, null, String.class));
+            String message = e.getCause().getCause().getMessage();
+            assertTrue(message.contains("HTTP 401"), message);
+            assertEquals(0, logins.get());
+        } finally {
+            conjur.stop(0);
+        }
+    }
+
+    private static void assertNamesTheOptions(IllegalArgumentException e) {
+        String message = e.getMessage();
+        assertTrue(message.contains("authToken"), message);
+        assertTrue(message.contains("username and apiKey"), message);
+        assertTrue(message.contains("username and password"), message);
+        assertTrue(message.contains("conjurClient"), message);
+    }
+}
diff --git 
a/components/camel-cyberark-vault/src/test/java/org/apache/camel/component/cyberark/vault/client/impl/ConjurClientReauthenticationTest.java
 
b/components/camel-cyberark-vault/src/test/java/org/apache/camel/component/cyberark/vault/client/impl/ConjurClientReauthenticationTest.java
new file mode 100644
index 000000000000..6c84e2a1ea40
--- /dev/null
+++ 
b/components/camel-cyberark-vault/src/test/java/org/apache/camel/component/cyberark/vault/client/impl/ConjurClientReauthenticationTest.java
@@ -0,0 +1,177 @@
+/*
+ * 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.cyberark.vault.client.impl;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpServer;
+import org.apache.camel.component.cyberark.vault.client.ConjurClient;
+import org.apache.camel.component.cyberark.vault.client.ConjurClientFactory;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * A 401 from Conjur makes the client authenticate again and retry once, and 
only when it has an API key or a password
+ * to get a new token. A fake Conjur answers the queued statuses to the secret 
requests, and then succeeds: a client
+ * that retries more than it should gets the secret instead of the expected 
failure.
+ */
+class ConjurClientReauthenticationTest {
+
+    private static final String ACCOUNT = "myaccount";
+    private static final String SECRET_ID = "db/password";
+
+    private final AtomicInteger logins = new AtomicInteger();
+    private final List<String> secretRequestTokens = new 
CopyOnWriteArrayList<>();
+    private final List<String> storedValues = new CopyOnWriteArrayList<>();
+    private final Queue<Integer> secretStatuses = new 
ConcurrentLinkedQueue<>();
+
+    private HttpServer server;
+    private String url;
+
+    @BeforeEach
+    void startConjur() throws IOException {
+        server = HttpServer.create(new 
InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 0);
+        server.createContext("/authn/", exchange -> {
+            exchange.getRequestBody().readAllBytes();
+            respond(exchange, 200, "token-" + logins.incrementAndGet());
+        });
+        server.createContext("/secrets/", exchange -> {
+            String body = new String(exchange.getRequestBody().readAllBytes(), 
StandardCharsets.UTF_8);
+            secretRequestTokens.add(tokenOf(exchange));
+            Integer status = secretStatuses.poll();
+            if ("POST".equals(exchange.getRequestMethod())) {
+                storedValues.add(body);
+                respond(exchange, status != null ? status : 201, "");
+            } else {
+                respond(exchange, status != null ? status : 200, 
"secret-value");
+            }
+        });
+        server.start();
+        url = "http://localhost:"; + server.getAddress().getPort();
+    }
+
+    @AfterEach
+    void stopConjur() {
+        server.stop(0);
+    }
+
+    @Test
+    void retrieveSecretWithOnlyATokenFailsWithTheUnauthorizedResponse() throws 
Exception {
+        secretStatuses.add(401);
+        secretStatuses.add(401);
+
+        try (ConjurClient client = ConjurClientFactory.createWithToken(url, 
ACCOUNT, "my-token")) {
+            RuntimeException e = assertThrows(RuntimeException.class, () -> 
client.retrieveSecret(SECRET_ID));
+            assertTrue(e.getCause().getMessage().contains("HTTP 401"), 
e.getCause().getMessage());
+        }
+        assertEquals(List.of("my-token"), secretRequestTokens);
+        assertEquals(0, logins.get());
+    }
+
+    @Test
+    void retrieveSecretAuthenticatesAgainAndRetriesOnce() throws Exception {
+        secretStatuses.add(401);
+
+        try (ConjurClient client = ConjurClientFactory.createWithApiKey(url, 
ACCOUNT, "host/myapp", "my-api-key")) {
+            assertEquals("secret-value", client.retrieveSecret(SECRET_ID));
+        }
+        assertEquals(List.of("token-1", "token-2"), secretRequestTokens);
+        assertEquals(2, logins.get());
+    }
+
+    @Test
+    void retrieveSecretFailsWhenTheNewTokenIsRejectedToo() throws Exception {
+        secretStatuses.add(401);
+        secretStatuses.add(401);
+
+        try (ConjurClient client = 
ConjurClientFactory.createWithCredentials(url, ACCOUNT, "admin", 
"my-password")) {
+            RuntimeException e = assertThrows(RuntimeException.class, () -> 
client.retrieveSecret(SECRET_ID));
+            assertTrue(e.getCause().getMessage().contains("HTTP 401"), 
e.getCause().getMessage());
+        }
+        assertEquals(List.of("token-1", "token-2"), secretRequestTokens);
+        assertEquals(2, logins.get());
+    }
+
+    @Test
+    void createSecretWithOnlyATokenFailsWithTheUnauthorizedResponse() throws 
Exception {
+        secretStatuses.add(401);
+        secretStatuses.add(401);
+
+        try (ConjurClient client = ConjurClientFactory.createWithToken(url, 
ACCOUNT, "my-token")) {
+            RuntimeException e = assertThrows(RuntimeException.class, () -> 
client.createSecret(SECRET_ID, "new-value"));
+            assertTrue(e.getCause().getMessage().contains("HTTP 401"), 
e.getCause().getMessage());
+        }
+        assertEquals(List.of("my-token"), secretRequestTokens);
+        assertEquals(0, logins.get());
+    }
+
+    @Test
+    void createSecretAuthenticatesAgainAndRetriesOnce() throws Exception {
+        secretStatuses.add(401);
+
+        try (ConjurClient client = ConjurClientFactory.createWithApiKey(url, 
ACCOUNT, "host/myapp", "my-api-key")) {
+            client.createSecret(SECRET_ID, "new-value");
+        }
+        assertEquals(List.of("token-1", "token-2"), secretRequestTokens);
+        assertEquals(List.of("new-value", "new-value"), storedValues);
+        assertEquals(2, logins.get());
+    }
+
+    @Test
+    void createSecretFailsWhenTheNewTokenIsRejectedToo() throws Exception {
+        secretStatuses.add(401);
+        secretStatuses.add(401);
+
+        try (ConjurClient client = 
ConjurClientFactory.createWithCredentials(url, ACCOUNT, "admin", 
"my-password")) {
+            RuntimeException e = assertThrows(RuntimeException.class, () -> 
client.createSecret(SECRET_ID, "new-value"));
+            assertTrue(e.getCause().getMessage().contains("HTTP 401"), 
e.getCause().getMessage());
+        }
+        assertEquals(List.of("token-1", "token-2"), secretRequestTokens);
+        assertEquals(2, logins.get());
+    }
+
+    private static String tokenOf(HttpExchange exchange) {
+        // Authorization: Token token="<base64 of the access token>"
+        String authorization = 
exchange.getRequestHeaders().getFirst("Authorization");
+        String encoded = authorization.substring("Token token=\"".length(), 
authorization.length() - 1);
+        return new String(Base64.getDecoder().decode(encoded), 
StandardCharsets.UTF_8);
+    }
+
+    private static void respond(HttpExchange exchange, int status, String 
body) throws IOException {
+        byte[] bytes = body.getBytes(StandardCharsets.UTF_8);
+        exchange.sendResponseHeaders(status, bytes.length == 0 ? -1 : 
bytes.length);
+        try (OutputStream out = exchange.getResponseBody()) {
+            out.write(bytes);
+        }
+    }
+}
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index d85dce0dbc0c..c2bea3562783 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -553,6 +553,20 @@ A route that relies on a regular expression as token, such 
as `\s+` or `(\W+)\s*
 In the XML and YAML DSL, a token with the escapes `\n`, `\r` or `\t` (for 
example `\r\n`) is turned into the new line,
 carriage return and tab characters, so it works as before. Before, only a 
leading `\n` was turned into a new line.
 
+=== camel-cyberark-vault - credentials are checked at endpoint start, and a 
rejected token is retried only once
+
+A `cyberark-vault` endpoint now fails to start when it has no usable 
credentials, with a message that names the
+options to set: the `authToken` option, the `username` and `apiKey` options, 
or the `username` and `password` options,
+unless the endpoint has a `conjurClient`. Before, an endpoint with an `apiKey` 
and no `username` started, and its first
+message failed with a `NullPointerException`.
+
+When CyberArk Conjur answers HTTP 401, the endpoint and the CyberArk Conjur 
Vault Property Function authenticate again
+and retry the call once, and only when they have an `apiKey` or a `password` 
to authenticate with. With only an
+`authToken`, the call now fails with the 401 response, instead of retrying 
with the same token until a
+`StackOverflowError`. Configure the `username` with an `apiKey` or a 
`password`, instead of an `authToken`, to let the
+component get a new token when the current one expires: when the `authToken` 
is set, the component uses only that
+token.
+
 === camel-main - a failure to start is logged
 
 A Camel Main application that fails to start now logs why, at ERROR: `Error 
starting Camel: ...` with the stack trace,

Reply via email to