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 315adc6c8394 chore: camel-jbang - the screens mask the secrets of an
endpoint URI (#29597)
315adc6c8394 is described below
commit 315adc6c8394a8b9aeeba0d12218dd41800769d9
Author: Claus Ibsen <[email protected]>
AuthorDate: Wed Nov 11 10:29:31 2026 +0100
chore: camel-jbang - the screens mask the secrets of an endpoint URI
(#29597)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Claus Ibsen <[email protected]>
---
.../dsl/jbang/core/commands/tui/StatusParser.java | 41 ++++++++++++-------
.../dsl/jbang/core/commands/tui/Timeline.java | 4 +-
.../jbang/core/commands/tui/StatusParserTest.java | 47 ++++++++++++++++++++++
.../dsl/jbang/core/commands/tui/TimelineTest.java | 11 +++++
4 files changed, 86 insertions(+), 17 deletions(-)
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParser.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParser.java
index ace32c6c4231..b549c4d05c8b 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParser.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParser.java
@@ -34,6 +34,7 @@ import org.apache.camel.dsl.jbang.core.common.EndpointScope;
import org.apache.camel.dsl.jbang.core.common.ProcessHelper;
import org.apache.camel.dsl.jbang.run.common.VersionHelper;
import org.apache.camel.util.TimeUtils;
+import org.apache.camel.util.URISupport;
import org.apache.camel.util.json.JsonArray;
import org.apache.camel.util.json.JsonObject;
import org.apache.camel.util.json.Jsoner;
@@ -271,7 +272,7 @@ final class StatusParser {
ri.description = rj.getString("description");
ri.source = rj.getString("source");
ri.group = rj.getString("group");
- ri.from = rj.getString("from");
+ ri.from = masked(rj, "from");
ri.state = rj.getString("state");
ri.supportsSuspension =
rj.getBooleanOrDefault("supportsSuspension", false);
ri.uptime = rj.getString("uptime");
@@ -328,7 +329,7 @@ final class StatusParser {
pi.processor = pj.getString("processor");
pi.source = pj.getString("source");
pi.level = pj.getIntegerOrDefault("level", 0);
- pi.uri = pj.getString("uri");
+ pi.uri = masked(pj, "uri");
// the body types the step has been reached with, and
their kinds (CAMEL-25750)
pi.bodyTypes = stringList(pj.get("bodyTypes"));
pi.bodyKinds = stringList(pj.get("bodyKinds"));
@@ -466,7 +467,7 @@ final class StatusParser {
JsonObject cj = (JsonObject) c;
ConsumerInfo ci = new ConsumerInfo();
ci.id = cj.getString("id");
- ci.uri = cj.getString("uri");
+ ci.uri = masked(cj, "uri");
ci.state = cj.getString("state");
// the consumers console says clazz (class in older
versions)
ci.className = cj.getStringOrDefault("clazz",
cj.getString("class"));
@@ -543,7 +544,7 @@ final class StatusParser {
for (Object p : producerList) {
JsonObject pj = (JsonObject) p;
ProducerInfo pi = new ProducerInfo();
- pi.uri = pj.getString("uri");
+ pi.uri = masked(pj, "uri");
pi.state = pj.getString("state");
pi.className = pj.getStringOrDefault("clazz",
pj.getString("class"));
pi.routeId = pj.getString("routeId");
@@ -585,7 +586,7 @@ final class StatusParser {
RouteControllerInfo rc = new RouteControllerInfo();
rc.routeId = rj.getString("routeId");
rc.status = rj.getString("status");
- rc.uri = rj.getString("uri");
+ rc.uri = masked(rj, "uri");
rc.supervising = rj.getString("supervising");
rc.attempts = rj.getLongOrDefault("attempts", 0);
rc.lastAttempt = rj.getLongOrDefault("lastAttempt", 0);
@@ -609,7 +610,7 @@ final class StatusParser {
for (Object e : endpointList) {
JsonObject ej = (JsonObject) e;
EndpointInfo ep = new EndpointInfo();
- ep.uri = ej.getString("uri");
+ ep.uri = masked(ej, "uri");
ep.direction = ej.getString("direction");
ep.routeId = ej.getString("routeId");
ep.hits = TuiHelper.objToLong(ej.get("hits"));
@@ -648,8 +649,8 @@ final class StatusParser {
si.direction = sj.getString("direction");
si.hosted = Boolean.TRUE.equals(sj.get("hosted"));
si.protocol = sj.getString("protocol");
- si.serviceUrl = sj.getString("serviceUrl");
- si.endpointUri = sj.getString("endpointUri");
+ si.serviceUrl = masked(sj, "serviceUrl");
+ si.endpointUri = masked(sj, "endpointUri");
si.routeId = sj.getString("routeId");
si.hits = TuiHelper.objToLong(sj.get("hits"));
info.services.add(si);
@@ -797,7 +798,7 @@ final class StatusParser {
JsonObject rj = (JsonObject) r;
HttpEndpointInfo ep = new HttpEndpointInfo();
ep.fromRest = true;
- ep.url = rj.getString("url");
+ ep.url = masked(rj, "url");
ep.method = rj.getString("method");
if (ep.method != null) {
ep.method = ep.method.toUpperCase(Locale.ENGLISH);
@@ -838,6 +839,16 @@ final class StatusParser {
fixRestUrlPorts(info);
}
+ /**
+ * An endpoint URI or URL of the status, with its secrets masked as Camel
masks them in its own logs
+ * (password=xxxxxx): the integration gives some as configured (the
endpoints and services it has, with
+ * password=RAW(admin)), and the screens are not only for the developer of
the integration.
+ */
+ static String masked(JsonObject json, String key) {
+ String value = json.getString(key);
+ return value != null ? URISupport.sanitizeUri(value) : null;
+ }
+
/**
* Configuration properties.
*/
@@ -1030,7 +1041,7 @@ final class StatusParser {
} else {
entry.direction = entry.remoteEndpoint ? "*-->" : "*-> ";
}
- String uri = json.getString("endpointUri");
+ String uri = masked(json, "endpointUri");
if (uri != null) {
entry.processor = indent + "from[" + uri + "]";
} else {
@@ -1138,7 +1149,7 @@ final class StatusParser {
}
String indent = " ".repeat(entry.nodeLevel);
if (entry.first) {
- String uri = json.getString("endpointUri");
+ String uri = masked(json, "endpointUri");
if (uri != null) {
entry.processor = indent + "from[" + uri + "]";
} else {
@@ -1318,7 +1329,7 @@ final class StatusParser {
ep.fromRest = false;
ep.management = management;
ep.server = phpObj.getString("server");
- ep.url = ej.getString("url");
+ ep.url = masked(ej, "url");
ep.path = ej.getString("path");
ep.method = ej.getString("verbs");
ep.consumes = ej.getString("consumes");
@@ -1423,7 +1434,7 @@ final class StatusParser {
for (Object c : consumers) {
JsonObject cj = (JsonObject) c;
String routeId = cj.getString("routeId");
- String uri = cj.getString("uri");
+ String uri = masked(cj, "uri");
JsonArray workers = (JsonArray) cj.get("workers");
if (workers != null) {
for (Object w : workers) {
@@ -1538,8 +1549,8 @@ final class StatusParser {
if (elapsed != null) {
ei.elapsed = elapsed;
}
- ei.endpointUri = ej.getString("endpointUri");
- ei.fromEndpointUri = ej.getString("fromEndpointUri");
+ ei.endpointUri = masked(ej, "endpointUri");
+ ei.fromEndpointUri = masked(ej, "fromEndpointUri");
JsonObject ex = (JsonObject) ej.get("exception");
if (ex != null) {
ei.exceptionType = ex.getString("type");
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/Timeline.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/Timeline.java
index c6e4892b962f..7cb307733900 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/Timeline.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/Timeline.java
@@ -293,12 +293,12 @@ final class Timeline {
if (e.get("endpointSends") instanceof List<?> sends) {
for (Object o : sends) {
if (o instanceof JsonObject s) {
- calls.add(new Call(s.getString("endpointUri"),
s.getLongOrDefault("elapsed", 0)));
+ calls.add(new Call(StatusParser.masked(s, "endpointUri"),
s.getLongOrDefault("elapsed", 0)));
}
}
}
return new Kept(
- e.getString("exchangeId"), e.getString("routeId"),
e.getString("fromEndpointUri"),
+ e.getString("exchangeId"), e.getString("routeId"),
StatusParser.masked(e, "fromEndpointUri"),
e.getLongOrDefault("timestamp", 0),
e.getLongOrDefault("elapsed", 0), e.getBooleanOrDefault("failed", false),
e.getBooleanOrDefault("handled", false),
e.getBooleanOrDefault("redelivered", false),
e.getString("exception"), e.getString("chaosRule"),
List.copyOf(calls));
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParserTest.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParserTest.java
index dc90a7f944f5..22a9250cc499 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParserTest.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParserTest.java
@@ -75,6 +75,53 @@ class StatusParserTest {
assertEquals(List.of("remote", "internal", "local"),
info.endpoints.stream().map(ep -> ep.scope).toList());
}
+ @Test
+ void theSecretsOfAnEndpointAreMasked() {
+ // connect-service/ftp: the endpoints and services the integration has
carry its URI as configured
+ String uri =
"ftp://localhost:2121/courier?passiveMode=true&password=RAW(admin)&username=RAW(admin)";
+ JsonObject ep = new JsonObject();
+ ep.put("uri", uri);
+ JsonArray endpoints = new JsonArray();
+ endpoints.add(ep);
+ JsonObject endpointHolder = new JsonObject();
+ endpointHolder.put("endpoints", endpoints);
+ JsonObject service = new JsonObject();
+ service.put("endpointUri", uri);
+ service.put("serviceUrl", "ftp://admin:secret@localhost:2121");
+ JsonArray services = new JsonArray();
+ services.add(service);
+ JsonObject serviceHolder = new JsonObject();
+ serviceHolder.put("services", services);
+ JsonObject root = new JsonObject();
+ root.put("context", new JsonObject());
+ root.put("endpoints", endpointHolder);
+ root.put("services", serviceHolder);
+
+ IntegrationInfo info =
StatusParser.parseIntegration(ProcessHandle.current(), root);
+
+ String masked =
"ftp://localhost:2121/courier?passiveMode=true&password=xxxxxx&username=xxxxxx";
+ assertEquals(masked, info.endpoints.get(0).uri);
+ assertEquals(masked, info.services.get(0).endpointUri);
+ assertFalse(info.services.get(0).serviceUrl.contains("secret"),
info.services.get(0).serviceUrl);
+ }
+
+ @Test
+ void theSecretsOfTheEndpointsOfAnErrorAreMasked() {
+ JsonObject error = new JsonObject();
+ error.put("routeId", "courier");
+ error.put("endpointUri",
"ftp://localhost:2121/courier?password=RAW(admin)");
+ error.put("fromEndpointUri",
"sftp://localhost:2222/inbox?password=RAW(secret)");
+ JsonArray errors = new JsonArray();
+ errors.add(error);
+ JsonObject root = new JsonObject();
+ root.put("errors", errors);
+
+ ErrorInfo ei = StatusParser.parseErrors(root).get(0);
+
+ assertEquals("ftp://localhost:2121/courier?password=xxxxxx",
ei.endpointUri);
+ assertEquals("sftp://localhost:2222/inbox?password=xxxxxx",
ei.fromEndpointUri);
+ }
+
@Test
void aFailedReloadIsReadAsCamelPsReadsIt() {
JsonObject root = new JsonObject();
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/TimelineTest.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/TimelineTest.java
index c8880cc97e2a..1f7c5efc4309 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/TimelineTest.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/TimelineTest.java
@@ -116,6 +116,17 @@ class TimelineTest {
assertThat(b.calls()).containsExactly(new
Timeline.Call("http://stock", 850), new Timeline.Call("kafka:orders", 5));
}
+ @Test
+ void theSecretsOfTheEndpointsAnExchangeCalledAreMasked() {
+ timeline.merge("1234", new TimelineJson(S0, 1)
+ .exchange("A", "orders", 0, 20, "completed", null, null,
+ "ftp://localhost:2121/courier?password=RAW(admin)@5")
+ .json(), false);
+
+ Timeline.Kept a = timeline.exchanges("1234", S0, S0).get(0);
+ assertThat(a.calls()).containsExactly(new
Timeline.Call("ftp://localhost:2121/courier?password=xxxxxx", 5));
+ }
+
@Test
void
theIntegrationRecordsTheFaultsOfTheChaosRulesAndTheRoutesTheyHadDown() throws
Exception {
JsonObject json = new TimelineJson(S0 + 2, 3).route("orders",
"completed", 0, 1)