Skip to content

CAMEL-25552: camel-hashicorp-vault - the properties function no longer shares the secret engine between lookups - #27679

Open
davsclaus wants to merge 3 commits into
mainfrom
fix/CAMEL-25552
Open

davsclaus wants to merge 3 commits into
mainfrom
fix/CAMEL-25552

Conversation

@davsclaus

@davsclaus davsclaus commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

The hashicorp properties function is shared by all lookups, but kept the parsed secret engine in a field, so concurrent lookups for different engines could read from the wrong engine, and a lookup without engine used the engine of the previous lookup (or null).

  • The engine is now local to each lookup; a lookup without engine (hashicorp:mysecret, hashicorp:mysecret#field, hashicorp:mysecret@2) uses the secret engine, as the secret refresh task already assumes.
  • A missing secret with a field (hashicorp:secret:missing#field), or a response without data (with or without a field), now resolves to no value / the default value instead of failing with a NullPointerException, so optional placeholders work.
  • Docs: fixed the {{hashicorp:route:default@2}} example (parsed as engine route) to {{hashicorp:secret:route:default@2}}, and documented the default engine.
  • Upgrade guide entry for 4.23.

Tests: HashicorpVaultPropertiesFunctionTest (fake VaultTemplate, no Docker) covers engine isolation (sequential and concurrent), the default engine, versions, missing secret/field with and without default, and responses without data.

Rebased on #27669 (CAMEL-25518, merged): the engine passed to getSecretFromSource is now also the one used for the name the secret refresh task tracks (trackedSecretName), and a stale test comment about the null engine is updated. HashicorpVaultPropertiesFunctionTest and HashicorpVaultReloadTriggerTaskTest pass (14 tests).

Fixes CAMEL-25552

Claude Code on behalf of davsclaus

🤖 Generated with Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

🌟 Thank you for your contribution to the Apache Camel project! 🌟
🤖 CI automation will test this PR automatically.

🐫 Apache Camel Committers, please review the following items:

  • First-time contributors require MANUAL approval for the GitHub Actions to run
  • You can use the command /component-test (camel-)component-name1 (camel-)component-name2.. to request a test from the test bot although they are normally detected and executed by CI.
  • You can label PRs using skip-tests and test-dependents to fine-tune the checks executed by this PR.
  • Build and test logs are available in the summary page. Only Apache Camel committers have access to the summary.

⚠️ Be careful when sharing logs. Review their contents before sharing them publicly.

@github-actions

github-actions Bot commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

🧪 CI tested the following changed modules:

  • catalog/camel-catalog
  • components/camel-hashicorp-vault
  • docs

🔬 Scalpel shadow comparison — Scalpel: 9 of 704 tested, 25 compile-only — current: 9 all tested

Maveniverse Scalpel detected 9 affected modules (current approach: 9).

Skip-tests mode would test 9 modules (3 direct + 8 downstream), skip tests for 25 (generated code, meta-modules)

Modules Scalpel would test (9)
  • camel-hashicorp-vault ← components/camel-hashicorp-vault/src/main/docs/hashicorp-vault-component.adoc, components/camel-hashicorp-vault/src/main/java/org/apache/camel/component/hashicorp/vault/HashicorpVaultPropertiesFunction.java, components/camel-hashicorp-vault/src/test/java/org/apache/camel/component/hashicorp/vault/HashicorpVaultPropertiesFunctionTest.java, components/camel-hashicorp-vault/src/test/java/org/apache/camel/component/hashicorp/vault/HashicorpVaultReloadTriggerTaskTest.java
  • camel-jbang-mcp ← downstream of org.apache.camel:camel-catalog
  • camel-jbang-plugin-mcp ← downstream of org.apache.camel:camel-jbang-core
  • camel-jbang-plugin-route-parser ← downstream of org.apache.camel:camel-route-parser
  • camel-jbang-plugin-tui ← downstream of org.apache.camel:camel-catalog
  • camel-jbang-plugin-validate ← downstream of org.apache.camel:camel-yaml-dsl-validator
  • camel-launcher-container ← downstream of org.apache.camel:camel-launcher
  • camel-yaml-dsl-validator ← downstream of org.apache.camel:camel-catalog
  • camel-yaml-dsl-validator-maven-plugin ← downstream of org.apache.camel:camel-yaml-dsl-validator
Modules with tests skipped (25)
  • apache-camel
  • camel-allcomponents
  • camel-catalog-console
  • camel-catalog-maven
  • camel-catalog-suggest
  • camel-componentdsl
  • camel-endpointdsl
  • camel-endpointdsl-support
  • camel-itest
  • camel-jbang-core
  • camel-jbang-it
  • camel-jbang-main
  • camel-jbang-plugin-edit
  • camel-jbang-plugin-generate
  • camel-jbang-plugin-kubernetes
  • camel-jbang-plugin-test
  • camel-kamelet-main
  • camel-launcher
  • camel-report-maven-plugin
  • camel-route-parser
  • camel-yaml-dsl
  • camel-yaml-dsl-deserializers
  • camel-yaml-dsl-maven-plugin
  • coverage
  • dummy-component

ℹ️ Shadow mode — Scalpel observes but does not affect test execution. Learn more

All tested modules (36 modules, 5m 38s total)

Total reactor time: 5m 38s

Module Duration Status
Camel :: Launcher 51.0s SUCCESS
Camel :: JBang :: MCP 42.8s SUCCESS
Camel :: Catalog :: Camel Catalog 23.1s SUCCESS
Camel :: HashiCorp :: Key Vault 21.9s SUCCESS
Camel :: Component DSL 21.3s SUCCESS
Camel :: YAML DSL :: Validator 20.7s SUCCESS
Camel :: YAML DSL 20.4s SUCCESS
Camel :: JBang :: Plugin :: Validate 17.8s SUCCESS
Camel :: JBang :: Plugin :: Kubernetes 16.7s SUCCESS
Camel :: Docs 15.6s SUCCESS
Camel :: JBang :: Plugin :: Testing 15.1s SUCCESS
Camel :: Kamelet Main 11.7s SUCCESS
Camel :: Catalog :: Camel Route Parser 9.1s SUCCESS
Camel :: YAML DSL :: Deserializers 8.9s SUCCESS
Camel :: Catalog :: Camel Report Maven Plugin 7.5s SUCCESS
Camel :: YAML DSL :: Validator Maven Plugin 6.9s SUCCESS
Camel :: All Components Sync point 5.7s SUCCESS
Camel :: YAML DSL :: Maven Plugins 3.9s SUCCESS
Camel :: Catalog :: Suggest (deprecated) 2.4s SUCCESS
Camel :: Catalog :: Maven 2.1s SUCCESS
Camel :: Coverage 1.6s SUCCESS
Camel :: JBang :: Plugin :: MCP 1.6s SUCCESS
Camel :: JBang :: Plugin :: Edit 1.4s SUCCESS
Camel :: Assembly 1.3s SUCCESS
Camel :: Catalog :: Dummy Component 1.1s SUCCESS
Camel :: JBang :: Plugin :: Generate 1.1s SUCCESS
Camel :: JBang :: Integration tests 1.0s SUCCESS
Camel :: Catalog :: Console 1.0s SUCCESS
Camel :: Endpoint DSL :: Support 1.0s SUCCESS
Camel :: Launcher :: Container 0.8s SUCCESS
Camel :: JBang :: Main 0.7s SUCCESS
Camel :: JBang :: Plugin :: Route Parser 0.5s SUCCESS
Camel :: Endpoint DSL n/a
Camel :: Integration Tests n/a
Camel :: JBang :: Core n/a
Camel :: JBang :: Plugin :: TUI n/a

Top 20 slowest modules:

  • Camel :: Launcher (51.0s)
  • Camel :: JBang :: MCP (42.8s)
  • Camel :: Catalog :: Camel Catalog (23.1s)
  • Camel :: HashiCorp :: Key Vault (21.9s)
  • Camel :: Component DSL (21.3s)
  • Camel :: YAML DSL :: Validator (20.7s)
  • Camel :: YAML DSL (20.4s)
  • Camel :: JBang :: Plugin :: Validate (17.8s)
  • Camel :: JBang :: Plugin :: Kubernetes (16.7s)
  • Camel :: Docs (15.6s)
  • Camel :: JBang :: Plugin :: Testing (15.1s)
  • Camel :: Kamelet Main (11.7s)
  • Camel :: Catalog :: Camel Route Parser (9.1s)
  • Camel :: YAML DSL :: Deserializers (8.9s)
  • Camel :: Catalog :: Camel Report Maven Plugin (7.5s)
  • Camel :: YAML DSL :: Validator Maven Plugin (6.9s)
  • Camel :: All Components Sync point (5.7s)
  • Camel :: YAML DSL :: Maven Plugins (3.9s)
  • Camel :: Catalog :: Suggest (deprecated) (2.4s)
  • Camel :: Catalog :: Maven (2.1s)

⚙️ View full build and test results

davsclaus and others added 2 commits October 11, 2026 09:03
…r shares the secret engine between lookups

The engine of a hashicorp: lookup was kept in a field of the shared properties function, so concurrent lookups could read from the wrong engine and a lookup without engine used the engine of the lookup before. The engine is now local to each lookup and defaults to the 'secret' engine. A missing secret with a field now resolves to no value (or the default value) instead of failing with a NullPointerException. Fixed the docs example of a default value with a version, which was missing the engine.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Claus Ibsen <claus.ibsen@gmail.com>
…se on CAMEL-25518

A lookup without engine now reads from the default secret engine instead of a null engine.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Claus Ibsen <claus.ibsen@gmail.com>

@gnodet gnodet left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good fix overall — the root cause (shared mutable engine field) is correctly identified and addressed.

What the PR gets right:

  • Moving engine from an instance field to a local variable in apply() is the right fix for the concurrency bug: the method is documented (and tested) as shared across concurrent lookups, so any per-call state must be local.
  • The DEFAULT_ENGINE = "secret" constant makes the fallback explicit and readable.
  • The instanceof Map<?,?> data guard on the subkey path eliminates the raw cast + potential NPE in a clean, idiomatic way.
  • The concurrent stress test (concurrentLookupsReadFromTheirOwnEngine) is exactly the right regression test and validates the fix properly.
  • The @version handling in the non-subkey/non-# branch is a useful extension.

Minor observation — consistency of null-safety hardening:

The subkey path now has thorough null guards:

if (rawSecret != null && rawSecret.getData() != null
        && rawSecret.getData().get("data") instanceof Map<?, ?> data) {

However, the non-subkey path (a few lines above, where there is no # in the key) retains an unguarded call:

if (ObjectHelper.isNotEmpty(rawSecret)) {
    returnValue = rawSecret.getData().get("data").toString();
}

This was already there before this PR and is therefore pre-existing, not a regression. However, since the PR already touched the surrounding code, it would be more consistent to apply the same guard here. rawSecret.getData() could return null if Vault returns a response without a data map, and .get("data") could return null even when the map exists, causing a NullPointerException on .toString().

Suggested hardening (optional, can be a follow-up):

if (rawSecret != null && rawSecret.getData() != null
        && rawSecret.getData().get("data") instanceof Map<?, ?> data) {
    returnValue = data.toString();
}

This review was generated by an AI agent, Hermès on behalf of @gnodet.

@gnodet gnodet added the lts label Oct 11, 2026
… to no value also without a field

Address review: the lookup without a field read rawSecret.getData().get("data") unguarded, so a response without data failed with a NullPointerException. Both paths now read the secret data once, null-safe.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Claus Ibsen <claus.ibsen@gmail.com>
@github-actions github-actions Bot removed the lts label Oct 11, 2026

@gnodet gnodet left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review after e9c57b3: the previous finding (inconsistent null-safety between the subkey and non-subkey paths of getSecretFromSource) is fully addressed. Both paths now use the same null-safe chain — rawSecret → responseData → secretData — so a missing secret or a response without a data map correctly resolves to no value instead of throwing a NullPointerException.

The new else if (remainder.contains("@")) branch correctly handles bare key@version lookups (no engine, no subkey) that previously fell through without version extraction. Test coverage is comprehensive: engine isolation under concurrency, the default engine, versions, missing secret/field with and without defaults, and responses without data. All good to merge.

Metadata: bug fix on main — consider backporting to camel-4.22.x (no port/camel-4.22.x label yet).

This review was generated by an AI agent, Hermès on behalf of @gnodet.

@davsclaus
davsclaus requested a review from oscerd October 11, 2026 11:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants