Repository navigation
CAMEL-25644: camel-ssh - match known_hosts entries by exact host pattern - #27821
Conversation
ResourceBasedSSHKeyVerifier parsed the known_hosts resource with its own parser: it compared the host field with String.contains, used only the first matching line, did not tell [host]:port entries from plain ones, skipped comments, markers and hashed entries, and looked up an endpoint configured with an address by the name of a reverse lookup. Read and match the entries with Apache MINA SSHD's KnownHostEntry against the host and port configured on the endpoint, consider every entry of the host, reject a key listed in a matching @Revoked entry, and keep rejecting a host that has entries but presents a key none of them lists, whatever failOnUnknownHost is set to. Document the supported format and add the upgrade-guide notes for 4.24, 4.22.2 and 4.18.6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Andrea Cosentino <ancosen@gmail.com>
|
🌟 Thank you for your contribution to the Apache Camel project! 🌟 🐫 Apache Camel Committers, please review the following items:
|
gnodet-bot
left a comment
There was a problem hiding this comment.
Solid improvement overall — the delegation to MINA SSHD's own parser eliminates a class of bugs cleanly. One correctness issue in the revoked-entry handling.
This review was generated by an AI agent, Hermès on behalf of @gnodet.
luigidemasi
left a comment
There was a problem hiding this comment.
Requesting changes for the host-key validation issues below, verified against aa8050b.
The existing camel-ssh suite passes: 54 tests, zero failures/errors. Additional verifier probes and real loopback SSH connections exposed a regression that accepts an unlisted key and incomplete wildcard revocation enforcement. Both connection probes reached password authentication with synthetic credentials. The negated-pattern regression was also checked against the preceding implementation, which rejected the same connection.
Please address the inline findings and add regression coverage. The revocation tests should cover nondefault ports and both entry orders: an experimental mutation that returns immediately on an allowed key, before considering later revocations, still passes all 14 verifier tests.
CI at submission: Java 17/25 builds, documentation validation, and license checks are queued; dependency review passed; Sonar is skipped. I ran the component suite, not the full reactor.
AI-generated review by Codex on behalf of @luigidemasi.
davsclaus
left a comment
There was a problem hiding this comment.
Thanks @oscerd. Replacing the String.contains lookup and the hand-written key decoder with Apache MINA SSHD's KnownHostEntry and KeyUtils.compareKeys is the right direction. The new tests cover exact names, addresses, ports, hashed entries and several keys per host. The upgrade-guide entry is on main in the 4.24 guide and in the 4.22.2 and 4.18.6 sections, which matches the backport policy. I read the SSHD 2.20.0 sources (KnownHostEntry, HostPatternsHolder, KnownHostHashValue, KnownHostsServerKeyVerifier, AbstractClientSession.checkKeys) to check the open threads. CI is still running.
The open threads
- @luigidemasi,
![excluded.example.com]:2222,[db.example.com]:2222: I agree.HostPatternsHolder.toPatternonly reads the[host]:portform when[is the first character. For![...]it then rejects[as an invalid pattern character.readKnownHostEntriescatches that, logs a WARN and drops the whole line, so a listed host becomes an unknown host. One way to keep this safe: parse line by line withKnownHostEntry.parseKnownHostEntry, and when a line cannot be parsed, do not fall through to thefailOnUnknownHost=falseacceptance for that verification. Another way is to rewrite a leading![before handing the pattern to SSHD. - @luigidemasi,
@revoked *and non-default ports: I agree. A pattern without a port gets port 0, andisPortMatchmaps 0 to 22, so*never matches port 2222. OpenSSH matches patterns against the string[host]:portfor a non-default port, so*matches there. See also my inline comment on the docs, because the same gap affects plain wildcard entries. - @luigidemasi, unknown markers: I agree.
parseKnownHostEntrykeeps any marker text. OpenSSH accepts only@revokedand@cert-authorityand skips a line with any other marker. Treating onlymarker == nullas a trusted entry fixes it. SSHD's ownKnownHostsServerKeyVerifierhas the same gap, so this is not something the library already solves. - gnodet-bot, a host with only
@revokedentries: I disagree that this is a bug in the code. OpenSSH does not count@revokedlines when it decides whether a host is known; it only checks them for revocation. SSHD'sKnownHostsServerKeyVerifier.acceptKnownHostEntriesdoes the same: with no non-revoked match it callsacceptUnknownHostKey. The current code follows both. The docs and the PR description say "a host that has entries", which includes@revokedlines, so the wording should change rather than the code (inline comment). The stricter behaviour is also defensible, but then the docs should say that it differs from OpenSSH. - gnodet-bot,
<p/>: this is common Camel javadoc style (several hundred files incamel-apiuse it), so I would leave it.
Can Camel reuse the library instead of its own parser? It already does. This PR reads the entries with SSHD's parser and only keeps the decision logic (about 40 lines) in Camel. The other option is to extend SSHD's KnownHostsServerKeyVerifier, as MinaSftpServerKeyVerifier in camel-mina-sftp does, and override reloadKnownHosts to read the Camel resource. That would make the two components behave the same. But it is built around a Path: when an unknown host is accepted, acceptUnknownHostKey writes the new entry back to that file. It also uses the same isHostMatch, so it has the same gaps with port wildcards, ![...] patterns and unknown markers. So the approach in this PR is fine. The three gaps above need a few lines in Camel, and could also be reported to SSHD, because camel-mina-sftp has them too.
Smaller points
SSHPublicKeyHolderis no longer used anywhere in Camel. Keeping it is right, but consider marking it@Deprecated, so it can be removed later.- Please add tests for the three cases above:
@revoked *on ports 22 and 2222 in both entry orders, a![...]:portentry, and an@unknownmarker, each with both values offailOnUnknownHost. The module uses JUnit assertions, so the new tests inResourceBasedSSHKeyVerifierTestfollow the module's style.
Checked against the project conventions in CLAUDE.md. This is a rules-and-conventions review and does not replace CodeRabbit, Sourcery or SonarCloud.
Claude Code on behalf of @davsclaus.
This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.
Read the known_hosts lines in the verifier instead of with MINA SSHD's KnownHostEntry, whose pattern parser drops a line with a negated port-qualified pattern such as ![host]:2222 and only applies a pattern without a port, * included, to port 22. Each host pattern, and each hashed entry, is now matched against the name OpenSSH looks the server up by: host for port 22 and [host]:port for another port. A resource or a line that cannot be read, including one with a marker other than @Revoked or @cert-authority, now makes every connection fail, so a parse failure can no longer turn a listed host into an unknown one. Keys are still resolved and compared with MINA SSHD. Say in the docs, the option descriptions and the upgrade notes that only entries without a marker list a host, as in OpenSSH. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Andrea Cosentino <ancosen@gmail.com>
davsclaus
left a comment
There was a problem hiding this comment.
Thanks @oscerd. I re-read ResourceBasedSSHKeyVerifier at 3700546. SSHD's HostPatternsHolder has the gaps found in the last round, so reading the lines in Camel and matching each pattern against host / [host]:port is a reasonable trade. The parser stays small.
What I checked:
- The three findings from @luigidemasi are fixed in the code.
![excluded.example.com]:2222,[db.example.com]:2222is read per pattern, with!handled before the match.*is matched against[db.example.com]:2222, so@revoked *applies on port 2222. Only@revokedand@cert-authorityare accepted as markers; any other marker makes the line unreadable. The tests cover both values offailOnUnknownHost, both entry orders, and ports 22 and 2222. - A host with only
@revokedentries stays unknown, as in OpenSSH. The docs, thefailOnUnknownHostdescription and javadoc, and the upgrade notes now say "entries without a marker". The 4.18, 4.22 and 4.24 notes have the same text, and the generated metadata and the catalog copy ofssh-component.adocare in sync. - The name to match comes from
SshdSocketAddress.toSshdSocketAddress, which usesgetHostString(), so there is no reverse lookup. Hashed entries hash the same lower-casedhost/[host]:portstring that OpenSSH hashes. matchesPatternis a plain backtracking glob for*and?. A negated pattern excludes the entry, and an empty pattern is skipped.- The class javadoc now uses
<p>, and the verifier only accepts after it has read every line.
Notes, not blocking:
- An unreadable line, or a resource that cannot be read, now rejects every connection whatever
failOnUnknownHostis set to. That is stricter than OpenSSH, which skips a line it cannot parse. It also turns a missing resource from "accepted, with a DEBUG log" into "every connection fails" in the 4.18.x and 4.22.x patch releases. It is the safer choice, and the docs and upgrade notes say so, so I am fine with it. The WARN names the line number, which will help users find the line. SSHPublicKeyHolderis still unused and not marked@Deprecated. Optional.
@luigidemasi, your three findings look fixed to me at this commit. Please have a look when you can.
CI is still running.
Checked against the project conventions in CLAUDE.md. This is a rules-and-conventions review and does not replace CodeRabbit, Sourcery or SonarCloud.
Claude Code on behalf of @davsclaus.
This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.
gnodet-bot
left a comment
There was a problem hiding this comment.
Re-review after commit 3700546.
Clean rewrite. All prior findings are addressed. The custom known_hosts parser correctly implements OpenSSH matching semantics with proper negation handling, hashed entry support, revocation logic, and port-qualified patterns. No real issues found.
This review was generated by an AI agent, Hermès on behalf of @gnodet.
luigidemasi
left a comment
There was a problem hiding this comment.
Re-reviewed 3700546. The three findings from my previous review are fixed: negated port-qualified patterns retain their key constraints, wildcard revocation applies on nondefault ports in both entry orders, and unsupported markers reject the connection.
Verification:
- All 60 camel-ssh tests pass.
- All original verifier probes now pass.
- The original loopback SSH probes now reject the unlisted/revoked keys before password authentication; the test servers receive no synthetic password.
- Five tests in the updated verifier suite fail against the initial PR implementation.
- The previously surviving early-accept mutation now fails two tests.
- 100,000 comparisons of the new glob matcher against an independent dynamic-programming implementation pass.
I am still requesting changes for the two findings below. The malformed-hash finding is a gap in the replacement parser's new fail-closed contract. The IPv6 finding is pre-existing: I reproduced it against the code before this PR as well, so it must not be described as a regression introduced here. It remains directly relevant to the configured-host lookup and key-pinning guarantees being documented by this change.
CI at submission: Java 17/25 builds, documentation validation, and licensing checks remain queued; dependency review passed; Sonar is skipped. I ran the component suite, not the full reactor.
AI-generated review by Codex on behalf of @luigidemasi.
…hashed entries The verifier took the host from the address the client connected to, whose host string writes an IPv6 address such as ::1 as 0:0:0:0:0:0:0:1, so an entry for [::1]:port was never found. The producer and the consumer now pass the host configured on the endpoint to the verifier, which uses it as written (without the brackets of an IPv6 address) with the port the client connected to. The existing constructors still use the connected address. A host field that starts with | must be a |1|salt|hash entry whose salt and hash both decode to 20 bytes; anything else makes the line unreadable, which makes every connection fail. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Andrea Cosentino <ancosen@gmail.com>
luigidemasi
left a comment
There was a problem hiding this comment.
Re-reviewed a1d570a. All findings from my previous reviews are addressed, and I found no further critical or major issues.
Both the producer and consumer now preserve the configured host for known_hosts lookup, including compressed IPv6 addresses. Malformed hashed entries, including unsupported prefixes and invalid salt/digest lengths, fail closed. The earlier negated-pattern, revocation and marker fixes remain effective.
Validation: the complete camel-ssh component suite passed (65 tests, zero failures/errors/skips, including all four IPv6 connection tests). Independent loopback SSH probes confirmed that matching IPv6 keys authenticate and rejected peers receive no test password. Additional verifier probes passed, and targeted mutations confirmed that regression tests detect lost host propagation, missing digest-length validation and premature key acceptance.
Hosted CI remains pending: Java 17/25 builds, documentation validation and license checks are queued. Dependency review passed; Sonar analysis was skipped. Local validation covered camel-ssh, not the full reactor.
AI-generated review by Codex on behalf of @luigidemasi.
davsclaus
left a comment
There was a problem hiding this comment.
Thanks @oscerd. I re-read the changes since my approval (3700546..a1d570a, one new commit, no rebase), and the two findings from @luigidemasi that it addresses.
I went through my previous points:
- The three findings from the first round, the
@revoked-only wording, and the<p>javadoc: still fixed. The new commit does not touch that logic. - Unreadable line or resource fails every connection: unchanged, and now also covers malformed hashed hosts. Fine with me, as before.
SSHPublicKeyHoldernot marked@Deprecated: still optional.
What the new commit does, and what I checked:
- Hashed entries: a host field that starts with
|must be|1|salt|hash, with salt and hash both 20 bytes, otherwise the line is unreadable. HMAC-SHA1 gives 20 bytes, so this is exact.split("\\|", -1)keeps an empty hash as"", which decodes to 0 bytes and is rejected. OpenSSH only skips a bad hashed line, so this is stricter, but it matches the fail-closed rule the docs describe.rejectsEveryServerWhenALineCannotBeReadcovers empty, short and long digests and|2|, alone and after a valid entry. - Configured host:
SshProducerandSshConsumerpassendpoint.getHost()to a new four-argument constructor, and the existing constructors keep the old behaviour, so the public API stays compatible.SshHelperconnects with the sameconfiguration.getHost(), so the verifier looks up the name the client was asked to connect to. Brackets are removed and the name is lower-cased before the[host]:portform is built. That fixes the::1vs0:0:0:0:0:0:0:1case @luigidemasi found. SshComponentKnownHostIPv6Testfollows the module's JUnit style, usesMockEndpoint.assertIsSatisfiedwith noThread.sleep, and skips itself with an assumption when::1is not reachable. The test server binds to the wildcard address, so the test does run where IPv6 is enabled.- The docs and the three upgrade-guide notes (4.24, 4.22, 4.18) have the same new sentence.
New findings (non-blocking):
ssh-component.adoc:180says "an IPv6 address such as::1(or[::1]in the endpoint uri)". The javadoc ofSshComponentKnownHostIPv6Testsays the endpoint uri does not accept an IPv6 address, and the test sets the host withendpoint.setHost("::1"). That matches what I read in core:UnsafeUriCharactersEncodertreats[and]as unsafe and encodes them, sonew URI(...)inSshConfiguration.configuregets no host. Unless you checked thatssh://user@[::1]:2222works, I suggest dropping "(or[::1]in the endpoint uri)" from the docs (inline comment). The bracket stripping intoKnownHostsNamecan stay forsetHost("[::1]").- A question, not a blocker:
SshClient.setUpDefaultClient()uses SSHD'sDefaultConfigFileHostEntryResolver, which reads~/.ssh/configof the JVM user. WithHost db/HostName db.example.comthere, Camel connects todb.example.com, but the verifier now looks updb. OpenSSH looks up theHostName(db.example.com). At 3700546 the verifier used the connected address, so it matched OpenSSH in this case. It is a rare setup, and withfailOnUnknownHost=trueit fails closed. With the defaultfalsethe server is an unknown host, so any key is accepted with a WARN. If you think it is worth it, the upgrade note could say that the host is matched as configured on the endpoint, without~/.ssh/configaliases. - Reminder: CAMEL-25644 only has fix version 4.24.0. Add 4.22.2 and 4.18.6 when the backports are merged.
CI: no build has finished on this PR yet. The runs for aa8050b and 3700546 were cancelled. The run for a1d570a failed in the "Get Atlassian dependency version" step (./mvnw help:evaluate -N -f parent/pom.xml), before Maven compiled anything, and the JDK 25 job was cancelled by fail-fast. This PR does not touch parent/pom.xml, so the failure is not caused by the PR. It is an infrastructure failure. The same step failed once before, on 2026-10-09, in an unrelated run. The build needs a re-run before merging.
Checked against the project conventions in CLAUDE.md. This is a rules-and-conventions review and does not replace CodeRabbit, Sourcery or SonarCloud.
Claude Code on behalf of @davsclaus.
This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.
|
🧪 CI tested the following changed modules:
🔬 Scalpel shadow comparison — Scalpel: 12 of 706 tested, 23 compile-only — current: 12 all testedMaveniverse Scalpel detected 12 affected modules (current approach: 12). Skip-tests mode would test 12 modules (5 direct + 11 downstream), skip tests for 23 (generated code, meta-modules) Modules Scalpel would test (12)
Modules with tests skipped (23)
All tested modules (39 modules, 6m 26s total)Total reactor time: 6m 26s
Top 20 slowest modules:
|
Outdated: this review was on aa8050b. gnodet-bot's re-review on 3700546 found all its findings addressed. A re-review of the current head a1d570a is requested below. (Claude Code on behalf of @davsclaus)
|
@gnodet-bot please re-review the current head a1d570a. Your last review was on 3700546, and one commit has been added since: the configured host is kept for the known_hosts lookup (IPv6 included), and malformed hashed entries now fail closed. I dismissed your CHANGES_REQUESTED review on aa8050b because your re-review on 3700546 already found its findings addressed. @oscerd one thread is still open: my suggestion on Claude Code on behalf of @davsclaus |
When ~/.ssh/config gives a HostName for the host configured on the endpoint, SSHD connects to that HostName, and OpenSSH looks the server up by it. The verifier now uses the host name or address the client connects to, never resolved through DNS, as before the previous commit. Only an IPv6 address configured on the endpoint keeps its written spelling, because the connected address writes it in its full form. The docs no longer say that an IPv6 address can be given in the endpoint uri, which does not accept one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Andrea Cosentino <ancosen@gmail.com>
davsclaus
left a comment
There was a problem hiding this comment.
Thanks @oscerd. I re-read the changes since my approval (a1d570a..1d26dce, 1 new commit):
- The docs no longer say that an IPv6 address can be given in the endpoint uri, which resolves my thread.
- The verifier now looks the server up by the host name the client connects to, so a
HostNamefrom~/.ssh/configis used as OpenSSH does. I checked SSHD 2.20.0:AbstractClientSession.checkKeys()passes the target server address (SshdSocketAddress.toInetSocketAddress()) to the verifier when it is set, andtoSshdSocketAddresstakesInetSocketAddress.getHostString(), so the name is never taken from a reverse lookup. An IPv6 address configured on the endpoint keeps its written spelling, as before. looksUpTheHostNameTheClientConnectsTocovers theHostNamecase, and the three upgrade-guide notes (4.18, 4.22, 4.24) say the same.
CI is green.
Claude Code on behalf of @davsclaus.
This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.
luigidemasi
left a comment
There was a problem hiding this comment.
The previously reported findings are addressed. However, I found one remaining major issue in the interaction between SSH config aliases and IPv6 host-key lookup.
P1: Preserve the effective IPv6 HostName when checking known_hosts
In ResourceBasedSSHKeyVerifier.toKnownHostsName(), the IPv6 spelling is preserved only when the endpoint’s configured host contains :. An endpoint using an alias such as review-ipv6, with this SSH configuration, bypasses that branch:
Host review-ipv6
HostName ::1The verifier then looks up [0:0:0:0:0:0:0:1]:port, missing a valid [::1]:port entry.
I reproduced this through the Camel SSH producer:
- With the default
failOnUnknownHost=false, a different server key passed verification and the server received the synthetic password. - With
failOnUnknownHost=true, the legitimate key was rejected. - Using the expanded address in known_hosts restored correct key checking.
This also reproduces against the implementation before this PR. It is an unresolved pre-existing case that my earlier review missed, rather than a regression introduced by this commit.
Please:
- Preserve the effective
HostNameselected by SSHD before socket-address conversion loses its IPv6 spelling, and use it consistently for producer and consumer verification. - Add connection-level regression coverage for an alias resolving to compressed IPv6, with matching and different keys under both values of
failOnUnknownHost. - Assert that rejected peers receive no password, while retaining the existing direct-IPv6 and hostname-alias coverage.
Validation: only camel-ssh was built; all 66 tests passed. Hosted Java 17/25 builds, documentation, dependency and license checks passed; Sonar was skipped.
AI-generated review by Codex on behalf of @luigidemasi.
…gives An alias in ~/.ssh/config whose HostName is an IPv6 address, such as HostName ::1, was looked up through the connected address, which writes the address in its full form, so an entry for [::1]:port was not found. The verifier now resolves the configured host through the host configuration that the client of the session used for the connection (~/.ssh/config by default, or the resolver of a custom client builder), and uses the HostName it gives, as written; without an entry, it uses the configured host as written. The connection tests cover an alias resolving to ::1 and an alias resolving to a host name, and assert that a server whose key is rejected receives no credentials. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Andrea Cosentino <ancosen@gmail.com>
|
@luigidemasi fixed in ef7e70d:
Claude Code on behalf of Andrea Cosentino (@oscerd) |
gnodet-bot
left a comment
There was a problem hiding this comment.
This PR rewrites the camel-ssh known_hosts verification to implement correct OpenSSH semantics with a fail-closed design. Solid work.
What was reviewed:
- The new
DefaultKnownHostsVerifiercorrectly handles@revokedentries — a host appearing only in@revokedentries is treated as unknown (not blocked), matching OpenSSH behaviour isSameKey()returnsfalsefor null keys, so an unsupported key type in a revoked entry doesn't create a bypass — the host falls to thefailOnUnknownHostcheckmatchesPattern()implements a standard two-pointer wildcard algorithm for*/?— correct for edge caseseffectiveHostName()resolves IPv6 aliases throughHostConfigEntryResolver.resolveEffectiveHost()- Thread safety looks sound:
verifyServerKeyuses only local variables (hostListed,keyListed,sameKey) and immutable instance fields - The
rejectsEveryServerWhenALineCannotBeReadtest covers the critical fail-closed path (malformed line after a valid matching entry) - Migration notes are duplicated across the 4.18, 4.22, and 4.24 upgrade guides — appropriate given the security impact
- Catalog JSON changes appear auto-generated from updated Javadoc descriptions
No issues found. ✅
This review was generated by an AI agent, Hermès on behalf of @gnodet.
luigidemasi
left a comment
There was a problem hiding this comment.
The IPv6-alias issue from my previous review is fixed. The original Camel producer reproduction now rejects different server keys before transmitting credentials, under both values of failOnUnknownHost.
Required / P2: Fail closed when effective-host lookup fails
In ResourceBasedSSHKeyVerifier.effectiveHostName(), the new lookup catches IOException and RuntimeException, logs a warning, and returns the endpoint’s original host.
For an alias, that fallback changes the identity being checked. The connection was established using the resolved target, but verification now searches known_hosts for the alias. If only the target is listed, the default failOnUnknownHost=false accepts a different key.
I reproduced this through the Camel producer with a resolver that:
- Successfully resolves the alias to
localhostfor connection establishment. - Throws an injected
IOExceptionwhen the verifier resolves it again.
The current head transmitted the synthetic password to a server presenting a different key. The preceding commit rejected that key. This reproduction requires a resolver failure; it does not demonstrate that a remote peer alone can trigger the failure.
Please:
- Retain the effective target selected for that connection, preserving its spelling before socket-address conversion, and use that identity for verification.
- Reject verification if the effective target cannot be established reliably. A lookup failure must not fall through to permissive unknown-host acceptance.
- Add regression coverage for this failure path, including both values of
failOnUnknownHost, and assert that a different key receives no credentials. Retain the working IPv6-alias and hostname-alias tests.
Validation: only camel-ssh was built; all 72 tests passed. Hosted Java 17/25 builds, documentation, dependency and license checks passed; Sonar was skipped.
AI-generated review by Codex on behalf of @luigidemasi.
… connection The verifier resolved the configured host a second time, and fell back to the configured alias when that lookup failed, so a resolver that answered for the connection but not for the verifier made the verifier look up another host than the one connected to. The producer and the consumer now resolve the host configuration once, as SshClient.connect(username, host, port) does, connect with that entry and put it in the connection context. The verifier reads it from the session and uses its HostName as written, without resolving again; a host reached through a jump host uses the TARGET_SERVER that SSHD keeps as written, and the jump host its own address. A host configuration that cannot resolve the host now fails the connection before any server is contacted. The constructor that took the configured host is removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Andrea Cosentino <ancosen@gmail.com>
|
@luigidemasi fixed in 593f670, without a second lookup:
Claude Code on behalf of Andrea Cosentino (@oscerd) |
gnodet-bot
left a comment
There was a problem hiding this comment.
✅ Review of commit 593f670 (fix for CAMEL-25644)
The commit correctly resolves the P2 security finding. The architecture change from "resolve twice with fallback" to "resolve once at connection time, store in session context" is fundamentally sound.
Key points verified:
resolveHostConfig()is called beforeclient.connect(). If it throwsIOException, the exception propagates immediately — no TCP connection is established and no server key verification occurs. This eliminates the fallback-to-alias security bug.HOST_CONFIG_ENTRYis correctly plumbed viaAttributeRepository.ofKeyValuePair(...)into the session context, and retrieved intoKnownHostsName()viasession.getConnectionContext().- The ProxyJump guard is correct: jump host sessions do not have
TARGET_SERVERset; target sessions do. The conditionhostConfig.getProxyJump() == null || hostConfig.getProxyJump().isEmpty()correctly prevents using the target'sHostNamefor jump host verification. - The 4-arg constructor removal is not a public API break — it was introduced earlier in this same PR and never existed on
main. - Test coverage is thorough:
brokenAliastests confirm the P2 regression scenario (IOException → no connection, no credentials sent);resolvingOnce()structurally asserts the resolver is called exactly once.
No new issues found.
This review was generated by an AI agent, Hermès on behalf of @gnodet.
davsclaus
left a comment
There was a problem hiding this comment.
Thanks @oscerd. I re-read the changes since my approval (1d26dce..593f670, 2 new commits, no rebase), and the two findings from @luigidemasi they address:
- IPv6 alias (P1): fixed. The verifier no longer uses the connected address, which writes
::1in full, but theHostNameof the host configuration the connection was made with, as written.SshComponentKnownHostIPv6Testcovers an alias resolving to::1with the listed key and another key, under both values offailOnUnknownHost. - Fallback when the lookup fails (P2): fixed by design in 593f670.
SshHelperresolves the host configuration once, connects with that entry and puts it in the connection context, and the verifier reads it fromsession.getConnectionContext()without resolving again. I checked SSHD 2.20.0:resolveHostConfigbuilds the same synthetic entry asSshClient.resolveHost. A resolver error now fails the exchange before any server is contacted, and thebrokenAliastests show the server gets no credentials under eitherfailOnUnknownHostvalue. - Jump hosts: the target uses
TARGET_SERVER, which SSHD sets for a proxied session, and a hop with aProxyJumpfalls back to its own address.ResourceBasedSSHKeyVerifierTestcovers both. - The 4-arg constructor added earlier in this PR is gone, so there is no API change against
main, and the docs and the 4.18/4.22/4.24 upgrade guide entries still match. The new assertions that a rejected peer receives no credentials are a good addition.
No new findings. Optional: HOST_CONFIG_ENTRY is public with @since 4.24. If this is backported to 4.18.x/4.22.x, it could be package-private, as only SshHelper sets it.
CI: the build on JDK 17 and 25 is still running on 593f670; doc validation, license and dependency review are green. @luigidemasi's change request is on ef7e70d and still needs his re-review.
Claude Code on behalf of @davsclaus.
This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.
✅ Generated files are up to dateAn earlier CI run reported uncommitted generated changes; the latest run no longer does. |
Signed-off-by: Andrea Cosentino <ancosen@gmail.com>
luigidemasi
left a comment
There was a problem hiding this comment.
Re-reviewed 76753a9, including the fix in 593f670. The latest resolver-failure finding is addressed, and I have no remaining blocking findings from this review.
The producer and consumer now resolve the host configuration once, connect with that result, and retain it in the session connection context for host-key verification. The verifier no longer performs a second lookup or falls back to the alias after a lookup error. An initial resolution failure stops connection establishment.
Verification:
- The original resolver-failure reproduction fails against the preceding implementation and passes against the new sources. Each tested connection resolves its target exactly once.
- Different server keys receive no password under either value of
failOnUnknownHost; matching keys reach authentication. Initial resolver failures send no credentials. - The independent IPv6-alias reproduction still passes. Two updated connection tests fail against the preceding implementation.
- All 74 camel-ssh tests passed, with no skips, in a standalone harness compiling the current production, generated and test Java sources against the previously working 4.23.0-SNAPSHOT dependency set.
Validation limitation: the normal module-only Maven build stopped before compilation because camel-package-maven-plugin:4.24.0-SNAPSHOT was unavailable locally. The harness result is not a successful Maven build with the current 4.24 snapshot dependencies. No full reactor build was run.
Hosted CI at submission: Java 17/25 builds, documentation validation and license checks remain queued; dependency review passed; Sonar analysis was skipped.
AI-generated review by Codex on behalf of @luigidemasi.
|
Approved and ready to merge, but it now conflicts with main in Claude Code on behalf of Claus Ibsen |
🔄 Backport BotThis bugfix targets
Add a Backport PRs will be created automatically on merge (once a port label is added). Comment ℹ️ If you push additional commits after |
|
@oscerd this is approved and green, but it now has a merge conflict with main. Could you rebase it? I will merge it once it is green again. Claude Code on behalf of Claus Ibsen |
Signed-off-by: Andrea Cosentino <ancosen@gmail.com> # Conflicts: # docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
camel-4.22.2 has been cut, so the camel-4.22.x backport of this change ships in 4.22.3. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Andrea Cosentino <ancosen@gmail.com>
Signed-off-by: Andrea Cosentino <ancosen@gmail.com> # Conflicts: # docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc # docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
|
@davsclaus the conflict is resolved. I merged the current
No code changed. On the new head, the full reactor build ( Claude Code on behalf of Andrea Cosentino (@oscerd) |
davsclaus
left a comment
There was a problem hiding this comment.
Thanks @oscerd. I re-read the changes since my approval of 76753a9: two merges of main and e182197.
- The merges bring in no change of the PR's own: the lines the PR changes against
mainare the same as at 76753a9. - e182197 moves the camel-ssh note in the 4.22 guide from the 4.22.1 to 4.22.2 section to "Upgrading from 4.22.2 to 4.22.3", because 4.22.2 has been released without it. The text is unchanged. The 4.18 note stays under 4.18.6, and the 4.24 note is in place. This matches where the backports will ship.
The build jobs are still running on the merge with the latest main.
Claude Code on behalf of Claus Ibsen
CAMEL-25644
When
knownHostsResourceis set,ResourceBasedSSHKeyVerifierparsed theknown_hostsresource with its own parser:String.containsand used only the first matching line;[host]:portentries from plain ones;host type key(comments, markers, extra whitespace, CRLF);Changes
hostfile.c,match_hostname):HostNamethat~/.ssh/configgives for it), never resolved through DNS, writtenhostfor port 22 and[host]:portfor another port.~/.ssh/configby default, or the resolver of a customclientBuilder), asSshClient.connect(username, host, port)does. They connect with that entry and put it in the connection context (ResourceBasedSSHKeyVerifier.HOST_CONFIG_ENTRY).HostNameas written. It never resolves again. A host reached through a jump host uses SSHD'sTARGET_SERVER; the jump host itself uses its own address.::1, configured on the endpoint or given asHostName, is not expanded to0:0:0:0:0:0:0:1. A host configuration that cannot resolve the host fails the connection before any server is contacted.*and?are wildcards,!excludes the hosts a pattern matches, and hashed|1|salt|hashentries (20-byte salt and digest) are supported.![excluded.example.com]:2222,[db.example.com]:2222works, and*applies to every host and port.PublicKeyEntry,KeyUtils.compareKeys), the same code that decodes the key the server presents.@revokedentry, whatever the value offailOnUnknownHost;failOnUnknownHost.failOnUnknownHostonly decides for a host without an entry without a marker. As in OpenSSH (hostfile.c,check_hostkeys_by_key_or_type),@revokedand@cert-authorityentries do not list a host.@cert-authorityentries are ignored: host certificates are not supported, as before.@marker, a missing key, a key that is not valid base64, or a hashed host that is not a|1|entry with a 20-byte salt and digest. A key of a type that SSHD does not support only lists the host.loadKey) is removed.SSHPublicKeyHolderis no longer used by the verifier; it is kept because it is a public class.knownHostsResourceandfailOnUnknownHostnow state these rules, andssh-component.adochas a new "Server host key verification" section.Behaviour changes
The upgrade-guide note is added on
mainin three places: the 4.24 guide, the new "Upgrading from 4.22.2 to 4.22.3" section (4.22.2 has been cut), and the "Upgrading from 4.18.5 to 4.18.6" section. The fix is to be backported tocamel-4.22.xandcamel-4.18.x. The note covers these changes:db.example.comonly applies to port 22;Tests
ResourceBasedSSHKeyVerifierTest, 22 tests, covers:[host]:port, including*on another port;failOnUnknownHost, with the revocation before and after the accepted entry;@revokedentries;|2|prefix, and an unreadable resource;@cert-authorityentries, and unknown versus listed hosts with both values offailOnUnknownHost;::1,[::1], a host name, or none);known_hosts_types(RSA, DSA, ECDSA 256/384/521, Ed25519).aa8050b), the 5 tests added in the first review round fail.SshComponentKnownHostTestandSshComponentKnownHostEdDSATest:[localhost]:<port>entries for the server's own host key. Theknown_hosts_validandknown_hosts_eddsafixtures are removed.SshComponentKnownHostIPv6Testconnects over::1with the listed key and another key, under both values offailOnUnknownHost. It does this with the endpoint configured as::1, and with an alias that the client's host configuration resolves toHostName ::1. For the alias, the host configuration answers only once. It skips itself where the IPv6 loopback address is not available. Onef7e70d36ac7, which looked the host up a second time, the alias cases fail.SshComponentKnownHostTestadds a hostname alias (db-alias→HostName localhost), and a host configuration that cannot resolve the host, under both values offailOnUnknownHost. Every connection test asserts whether the test server received credentials, a password or a public key. Rejected peers and unresolved hosts receive none.mvn clean install -DskipTests -DskipITs: BUILD SUCCESS, 706 modules (last run on 21d7485, after merging currentmain). The regenerated catalog (component JSON and doc mirror) and the component and endpoint DSL builders are included.Claude Code on behalf of Andrea Cosentino (@oscerd)
🤖 Generated with Claude Code