Repository navigation
CAMEL-25518: camel-hashicorp-vault - Secret refresh detects updates in any engine and before the first check - #27669
Conversation
…n any engine and before the first check HashicorpVaultPropertiesFunction recorded a secret used as hashicorp:engine:secret by its name only. The refresh task reads a name without an engine from the secret engine, so for a secret in any other engine it read secret/metadata/<name>, got nothing, and never triggered a reload (the documented auto-detection only worked for the secret engine). The task also only recorded the version it saw in its first run, so a secret updated between the startup and the first check (one refresh period, 60 s by default) was taken as the baseline and the application kept using the old value. The properties function now records the secret in the format of camel.vault.hashicorp.secrets, which the task already understands (engine:secret, or only the secret for the default secret engine), and remembers the version it resolved (unless a specific version was requested). The task starts from that version for a secret it has not seen before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
gnodet
left a comment
There was a problem hiding this comment.
Solid bug fix with good test coverage — the fake Vault HTTP server approach in the test is clever and makes the scenarios self-contained. Two items worth addressing.
This review was generated by an AI agent, Hermès on behalf of @gnodet.
…ithout engine as the default engine, log unparsable versions Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ly view of the tracked secrets Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
gnodet
left a comment
There was a problem hiding this comment.
All prior review findings are addressed.
trackedSecretNamenull engine bug: ✅ fixed —nullengine correctly maps to the key without prefix- DEBUG logging for
NumberFormatExceptionincaptureVersion: ✅ added with secret name, version value, and exception @param/@returnJavadoc ongetSecretVersion: ✅ addedgetSecrets()returning mutable view: ✅ wrapped inCollections.unmodifiableSet(secrets)in this commit
The trackedSecretName ↔ buildMetadataPath alignment is correct. The captureVersion version guard (ObjectHelper.isEmpty(version)) correctly excludes specific-version lookups. Thread-safety is sound (ConcurrentHashMap throughout). Test suite covers all key scenarios with a clean fake HTTP server approach.
This review was generated by an AI agent, Hermès on behalf of @gnodet.
|
🌟 Thank you for your contribution to the Apache Camel project! 🌟 🐫 Apache Camel Committers, please review the following items:
|
davsclaus
left a comment
There was a problem hiding this comment.
Thanks, both fixes look right: secrets in non-default engines are now tracked as engine:key, and remembering the resolved version means an update before the first check triggers a reload without causing a spurious reload at startup. Good test coverage with the in-process server, and no secret values are logged.
One optional suggestion inline.
Existing issues on main (not introduced here, worth a separate JIRA): engine is a shared mutable field set inside apply(), so concurrent lookups can mix engines, and a lookup without an engine reuses the previous call's engine (or null). Also, rawSecret.getData() NPEs when client.read returns null and a subkey is given.
Claude Code on behalf of davsclaus. This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying. It is a static review against the project conventions and does not replace static analysis or specialized review tools.
…resolved version of a secret for the first refresh check A secret resolved again after an update, but before the first check of the refresh task, no longer hides that update from the task. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
🧪 CI tested the following changed modules:
🔬 Scalpel shadow comparison — Scalpel: 9 of 704 tested, 25 compile-only — current: 9 all testedMaveniverse 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)
Modules with tests skipped (25)
All tested modules (36 modules, 5m 13s total)Total reactor time: 5m 13s
Top 20 slowest modules:
|
gnodet
left a comment
There was a problem hiding this comment.
All prior review findings are addressed in the current HEAD (67fb082):
trackedSecretNamenull engine bug: ✅ fixed —nullengine correctly maps to the key without prefix- DEBUG logging for
NumberFormatExceptionincaptureVersion: ✅ added with secret name, version value, and exception @param/@returnJavadoc ongetSecretVersion: ✅ addedgetSecrets()returning mutable view: ✅ wrapped inCollections.unmodifiableSet(secrets)in this commit- Keep oldest resolved version via
Math::min(davsclaus): ✅ applied
The trackedSecretName ↔ buildMetadataPath alignment is correct: null or "secret" engine maps to just the key; other engines map to engine:key. The versionsMap.put(secretName, lastKnownVersion) before the change-detection check is correct — it seeds the baseline so subsequent runs compare against the resolved version rather than discarding it. Pre-fix test validation by static trace confirms both rotationInNonDefaultEngineIsDetected and rotationBeforeFirstCheckIsDetected would fail without the fix.
This review was generated by an AI agent, Hermès on behalf of @gnodet.
Description
CAMEL-25518
With
camel.vault.hashicorp.refreshEnabled=true, the refresh task also tracks the secrets thehashicorp:properties function resolved (the documented behaviour whencamel.vault.hashicorp.secretsis not set). Two problems:hashicorp:kv:db#passwordasdb. The task reads a name without an engine from thesecretengine, so it polledsecret/metadata/db, got nothing, and an update of a secret in any other engine never triggered a reload.This change records the secret in the format of
camel.vault.hashicorp.secrets, which the task already parses:engine:secret, or onlysecretfor the defaultsecretengine (so the names of those secrets do not change). It also remembers the version the function resolved (fromdata.metadata.version, not when a specific@versionwas requested), and the task starts from that version for a secret it has not seen yet. A lookup without engine is recorded like a secret of the default engine, which is how the task reads a name without engine. A version that cannot be parsed is logged at DEBUG. The recorded set and the new version map are concurrent collections, as the task reads them from its own thread. New public method:HashicorpVaultPropertiesFunction.getSecretVersion(String). The component docs (and their catalog copy) and the 4.23 upgrade guide describe the change, including the new names of secrets of other engines ingetSecrets()and in the task's updates.Tests:
HashicorpVaultReloadTriggerTaskTestwith a small in-process fake of the Vault KV v2 HTTP API (JDKHttpServer, no container): an update in thekvengine, an update before the first check, the recorded names, a secret resolved at a specific version (@1) that must not be compared with that version, and an unchanged secret that must not trigger a reload (the last two pass without the change too; they guard against a reload caused by the remembered version), and a lookup without engine (hashicorp:apion a function that has no engine yet), which is recorded asapilike a secret of the default engine, not asnull:api.the refresh task must read the metadata of the engine the secret was resolved from, but read [secret/metadata/db, secret/metadata/db],the application resolved version 1 but version 2 was not detected, updates: {}, and the recorded names aredbandapiinstead ofkv:dbandapi. The lookup without engine was recorded asnull:apibefore the review follow-up (expected: <[api]> but was: <[null:api]>).Found with a TLA+ model of startup resolution, secret updates and the refresh runs: "after a complete check run with no update since it started, the application uses the current version of every secret" is violated for a
kvsecret (12 steps) and for an update before the first check (8 steps). I then reproduced both with the real classes.Affected: 4.17.0 and later (the refresh was added by CAMEL-22676).
Target
mainbranch)Tracking
Apache Camel coding standards and style
mvn clean install -DskipTestslocally from root folder and I have committed all auto-generated changes.(I built and tested camel-hashicorp-vault, including the formatter and import-sort plugins, and updated the catalog copy of the component page. I did not run the full root build.)
AI-assisted contributions
Co-authored-bytrailers) and the PR description identifies the AI tool used.This PR was prepared with Claude Code (Claude Opus 5.5). The commit carries a
Co-Authored-Bytrailer.Claude Code on behalf of allthingssecurity
🤖 Generated with Claude Code