M12.5+M12.6: AnvilConfig 0.3.0 — multi-IdentityFile, StrictHostKeyChecking, parse-and-warn#2
Merged
Conversation
…ecking) The semver-minor API break that brings AnvilConfig in line with ssh_config(5) semantics. Lands as anvil-ssh 0.3.0 per Gitway PRD §5.8.1 and the M12 plan. config.rs (breaking): - `identity_file: Option<PathBuf>` -> `identity_files: Vec<PathBuf>`. OpenSSH allows multiple IdentityFile directives; the resolver and the auth path now honour the full list in source order. - `skip_host_check: bool` -> `strict_host_key_checking: StrictHostKeyChecking`. The new enum encodes Yes / No / AcceptNew, matching ssh_config(5). - New `apply_ssh_config(&ResolvedSshConfig)` builder method. Layers ssh_config-derived defaults (HostName/Port/User overrides; IdentityFile-extends; StrictHostKeyChecking override; first UserKnownHostsFile fills custom_known_hosts if not already set). Algorithm directives + ConnectTimeout / ConnectionAttempts are recorded in ResolvedSshConfig but not yet plumbed to the session (M17 / M18). - New builder methods: add_identity_file (additive), identity_files (replace-all), strict_host_key_checking. - 0.2.x compatibility shims (#[deprecated]): * AnvilConfig::identity_file() returns first identity_files entry. * AnvilConfig::skip_host_check() returns true iff policy is No. * AnvilConfigBuilder::identity_file(path) clears and pushes single. * AnvilConfigBuilder::skip_host_check(bool) maps to Yes/No. Removal target: 1.0. auth.rs: - find_identity now iterates config.identity_files in order. When the explicit list is non-empty, the well-known default search at ~/.ssh/id_* is suppressed (matches OpenSSH: explicit IdentityFile silences the default fallback). session.rs: - Handler reworked to carry policy + host + custom_known_hosts. StrictHostKeyChecking::No matches the 0.2.x skip-check path. StrictHostKeyChecking::AcceptNew with a writable custom_known_hosts records the first-seen fingerprint and accepts; without that path it warns and falls back to Yes semantics. Existing-but-mismatched fingerprints are always rejected regardless of policy. - connect() tolerates an empty fingerprint set when policy is AcceptNew + custom_known_hosts is set; the handler will record on first sight. hostkey.rs: - New crate-private append_known_host(path, host, fingerprint) — the minimum write surface for AcceptNew. Creates parent dirs and appends one `host SHA256:<fp>` line. Locking + duplicate detection are deferred to the post-M12 TOFU UX polish. Cargo.toml: 0.2.0 -> 0.3.0. CHANGELOG.md: 0.3.0 entry covering the API break + migration table showing the 0.2.x -> 0.3.0 mapping for both replaced fields. Tests: +9 config tests (default policy, multi-add, replace-list, deprecated shims for both fields, all three policy variants, apply_ssh_config layering + identity_files extension + custom- known-hosts non-overwrite); +1 auth test (multi-identity-files probe). Updated test_connection.rs integration tests to use the new strict_host_key_checking() builder method. Total now 153 lib tests, 0 failures, 1 ignored (pre-existing RSA SSHSIG limitation). Plan: M12.5 of let-us-plan-on-bright-cosmos.md. Cuts anvil-ssh 0.3.0; M12.7 (Gitway-side) will pick it up.
…rectives apply_ssh_config now emits a single log::warn at apply time when the ResolvedSshConfig contains algorithm directives (HostKeyAlgorithms, KexAlgorithms, Ciphers, MACs — landing in M17) or timing directives (ConnectTimeout, ConnectionAttempts — landing in M18) that Anvil parses losslessly for gitway config show but does not yet honor in the connection path. The warn fires at the bridge between ssh_config and AnvilConfig — gitway config show and similar inspection callers do not trigger it. Plan: M12.6 of let-us-plan-on-bright-cosmos.md.
7 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two M12 sub-milestones combined in one PR (single file diff between them — keeping them separate would have been more friction than value):
identity_file→identity_files: Vec<PathBuf>,skip_host_check: bool→strict_host_key_checking: StrictHostKeyChecking. Cuts anvil-ssh 0.3.0.log::warnfires once perapply_ssh_config()call when the resolved config contains directives Anvil parses but does not yet honor (HostKeyAlgorithms,KexAlgorithms,Ciphers,MACs→ M17;ConnectTimeout,ConnectionAttempts→ M18).This is the second of the M12 PRs. The first (M12.1-4:
ssh_configparser + resolver, additive types) merged as #1. Subsequent work (gitway config show, NFR-24 diag wiring, NFR-15 bench, matrix completion) follows in M12.7-9 across this repo and Steelbore/Gitway.M12.5 — Breaking changes (with deprecated shims)
AnvilConfig.identity_file: Option<PathBuf>AnvilConfig.identity_files: Vec<PathBuf>AnvilConfig.skip_host_check: boolAnvilConfig.strict_host_key_checking: StrictHostKeyCheckingAnvilConfigBuilder::identity_file(p)add_identity_file(p)/identity_files(vec)AnvilConfigBuilder::skip_host_check(b)strict_host_key_checking(StrictHostKeyChecking)The deprecated builder methods + accessors stay as
#[deprecated]shims so 0.2.x code keeps compiling (with warnings). Removal target: 1.0.M12.5 — New additions
AnvilConfigBuilder::apply_ssh_config(&ResolvedSshConfig)— layers ssh_config-derived defaults into the builder (HostName/Port/User override, IdentityFile-extend, StrictHostKeyChecking override, first UserKnownHostsFile fillscustom_known_hostsif unset). Call before CLI overrides if you want CLI to win.StrictHostKeyChecking::AcceptNewminimal write path — whencustom_known_hostsis set, the first-seen fingerprint of an unknown host is recorded; without that path set, the connect downgrades toYessemantics with a warning. Full TOFU UX (interactive prompt + display) is post-M12.add_identity_file()/identity_files()/strict_host_key_checking()builder methods.M12.5 —
auth.rssemanticsfind_identitynow iteratesconfig.identity_filesin order. When the explicit list is non-empty, the well-known default search (~/.ssh/id_*) is suppressed — this matches OpenSSH: an explicitIdentityFile/--identitysilences the default fallback. The default search runs only when no explicit identities were configured.M12.6 — Warn on parsed-but-not-yet-honored directives
apply_ssh_config()calls a small helper that emits onelog::warn!per pending milestone when the resolved config contains directives Anvil knows about but doesn't yet plumb through:The warn fires at the bridge between ssh_config and
AnvilConfig—gitway config showand similar inspection paths (which callresolve()directly without applying it to a builder) don't trigger it.Test surface
configtests (default policy, multi-add, replace-list, deprecated shims for both replaced fields, all three policy variants,apply_ssh_configlayering + identity_files extension + custom-known-hosts non-overwrite) and 1 newauthtest (multi-identity probe).strict_host_key_checking()builder method.ssh-key 0.6.7.Test plan
cargo fmt --all -- --checkclean.cargo clippy --all-targets --all-features -- -D warningsclean.cargo test --lib --tests --lockedgreen on all platforms.v0.3.0, publish to crates.io.🤖 Generated with Claude Code