Support explicit native mesh SSH authentication modes #73

Open
opened 2026-10-04 17:55:44 +00:00 by kayg · 7 comments
Owner

Problem and evidence

crates/ssh/src/client.rs:181 authenticates only with the owning infraternal agent's Ed25519 public key. It has no network-authentication mode for native mesh SSH, so ordinary sshd reached over a mesh IP and native mesh SSH are different cases.

Current vendor docs distinguish the native modes: Tailscale SSH uses SSH authentication type none when Tailscale has authorized the peer. NetBird SSH defaults to IdP JWT authentication; --disable-ssh-auth selects machine-identity access governed by NetBird network ACLs.

Recommendation

Detect a supported mesh-SSH banner as an untrusted compatibility hint, then use an explicit supported network-auth mode for the target. Support the owner-selected SSH none path when the peer is configured for network-policy authentication; report a clear unsupported/configuration error for a JWT mode the client cannot perform. Keep the existing host-key Trust step and verify the presented key on every connection.

Payoff and estimate

Lets users reach native mesh SSH hosts without weakening SSH host identity checks or silently changing auth. Estimate: 3–5 engineering days including real Tailscale and NetBird mode tests.

Priority: Next milestone.

2026-10-04 owner decisions: The owner asked to detect the SSH banner and use network auth with a Trust step. This matches the machine-policy path. A banner alone cannot prove which NetBird mode is enabled; NetBird's documented default is JWT, so the implementation must distinguish that from machine-identity mode.

Report: arch-review report
Source: arch-review 2026-10-04

## Problem and evidence `crates/ssh/src/client.rs:181` authenticates only with the owning infraternal agent's Ed25519 public key. It has no network-authentication mode for native mesh SSH, so ordinary sshd reached over a mesh IP and native mesh SSH are different cases. Current vendor docs distinguish the native modes: [Tailscale SSH](https://tailscale.com/docs/features/tailscale-ssh) uses SSH authentication type `none` when Tailscale has authorized the peer. [NetBird SSH](https://docs.netbird.io/manage/peers/ssh) defaults to IdP JWT authentication; `--disable-ssh-auth` selects machine-identity access governed by NetBird network ACLs. ## Recommendation Detect a supported mesh-SSH banner as an untrusted compatibility hint, then use an explicit supported network-auth mode for the target. Support the owner-selected SSH `none` path when the peer is configured for network-policy authentication; report a clear unsupported/configuration error for a JWT mode the client cannot perform. Keep the existing host-key Trust step and verify the presented key on every connection. ## Payoff and estimate Lets users reach native mesh SSH hosts without weakening SSH host identity checks or silently changing auth. Estimate: 3–5 engineering days including real Tailscale and NetBird mode tests. **Priority:** Next milestone. **2026-10-04 owner decisions:** The owner asked to detect the SSH banner and use network auth with a Trust step. This matches the machine-policy path. A banner alone cannot prove which NetBird mode is enabled; NetBird's documented default is JWT, so the implementation must distinguish that from machine-identity mode. **Report:** [arch-review report](/mnt/hdd/infraternal/jobs/arch-review/report.md) **Source: arch-review 2026-10-04**
Author
Owner

Starting self-install-merge on self-install at 5b5cf987fc; merging dev 87bb5bd2b7. Retain network/key SSH auth and pins with delegated infraternal agent install/uninstall. Requested checks only; no push.

Starting self-install-merge on self-install at 5b5cf987fc4d3aac23d0505617256eca6cc0bea8; merging dev 87bb5bd2b7410d3a571a903f6cf6a53d1112ca6f. Retain network/key SSH auth and pins with delegated infraternal agent install/uninstall. Requested checks only; no push.
Author
Owner

Conflict reconciliation: kept dev decisions §§31–43 and renumbered self-install to §44. Retained banner detection, none-auth admission, explicit trust, auth override and exact pin checks. InstallRemote now combines the fixed per-job auth with delegated install token delivery; start only confirms readiness; removal delegates uninstall using the same auth. Worker fake records auth through every remote operation. Replaced the obsolete dummy-binary linger regression with real compiled-CLI reenrollment checks in the full integration scenario, and run that scenario against both real OpenSSH key auth and a loopback russh none-auth server executing as sshtarget. The loopback server records offered methods, so the complete network installation asserts no public key was offered. Updated stale install/start documentation after the textual auto-merge. No new hot path.

Conflict reconciliation: kept dev decisions §§31–43 and renumbered self-install to §44. Retained banner detection, none-auth admission, explicit trust, auth override and exact pin checks. InstallRemote now combines the fixed per-job auth with delegated install token delivery; start only confirms readiness; removal delegates uninstall using the same auth. Worker fake records auth through every remote operation. Replaced the obsolete dummy-binary linger regression with real compiled-CLI reenrollment checks in the full integration scenario, and run that scenario against both real OpenSSH key auth and a loopback russh none-auth server executing as sshtarget. The loopback server records offered methods, so the complete network installation asserts no public key was offered. Updated stale install/start documentation after the textual auto-merge. No new hot path.
Author
Owner

Conflict reconciliation: kept dev decisions §§31–43 and renumbered self-install to §44. Retained banner detection, none-auth admission, explicit trust, auth override and exact pin checks. InstallRemote now combines the fixed per-job auth with delegated install token delivery; start only confirms readiness; removal delegates uninstall using the same auth. Worker fake records auth through every remote operation. Replaced the obsolete dummy-binary linger regression with real compiled-CLI reinstall checks in the full integration scenario, and run that scenario against both real OpenSSH key auth and a loopback russh none-auth server executing as sshtarget. The loopback server records offered methods, so the complete network installation asserts no public key was offered. Updated stale install/start documentation after the textual auto-merge. No new hot path.

Network SSH can omit pam_systemd and XDG_RUNTIME_DIR. Install, readiness and removal now preserve an existing runtime path or use /run/user/; the loopback fixture intentionally has no PAM session or runtime environment, so it tests that fallback. Source: https://www.freedesktop.org/software/systemd/man/latest/pam_systemd.html . Initial clippy found items_after_statements in the new test import; fixed by moving it to module imports. Install suite passed: cargo test: 56 passed, 4 ignored, 295 filtered out (15 suites, 8.34s).

Conflict reconciliation: kept dev decisions §§31–43 and renumbered self-install to §44. Retained banner detection, none-auth admission, explicit trust, auth override and exact pin checks. InstallRemote now combines the fixed per-job auth with delegated install token delivery; start only confirms readiness; removal delegates uninstall using the same auth. Worker fake records auth through every remote operation. Replaced the obsolete dummy-binary linger regression with real compiled-CLI reinstall checks in the full integration scenario, and run that scenario against both real OpenSSH key auth and a loopback russh none-auth server executing as sshtarget. The loopback server records offered methods, so the complete network installation asserts no public key was offered. Updated stale install/start documentation after the textual auto-merge. No new hot path. Network SSH can omit pam_systemd and XDG_RUNTIME_DIR. Install, readiness and removal now preserve an existing runtime path or use /run/user/<SSH uid>; the loopback fixture intentionally has no PAM session or runtime environment, so it tests that fallback. Source: https://www.freedesktop.org/software/systemd/man/latest/pam_systemd.html . Initial clippy found items_after_statements in the new test import; fixed by moving it to module imports. Install suite passed: cargo test: 56 passed, 4 ignored, 295 filtered out (15 suites, 8.34s).
Author
Owner

Real network installation exposed two PAM-less startup issues in delegated self-install: show-user fails when no logind user object exists, and enable-linger starts its manager asynchronously. Self-install now confirms absent users through a successful live list-users query (malformed listing or failed bus queries fail closed), then waits up to ten seconds for a real user-manager bus reply before daemon-reload. Environment replies are discarded without logging. The same readiness helper serves server and infraternal agent installation. Sources: https://github.com/systemd/systemd/blob/main/src/login/loginctl.c and https://github.com/systemd/systemd/blob/main/man/loginctl.xml .

The expanded ignored compiled-binary test now passes both OpenSSH key mode and loopback network none mode, each with real sshtarget systemd service, enrollment, mTLS tunnel, inventory and delegated uninstall. Network mode authorizes no install key and asserts no publickey attempt. Linger ownership failure/reinstall/removal coverage was moved here from the obsolete dummy-binary bootstrap test. Latest output:

test rootless_install_trust_inventory_and_remove_over_real_sshd ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 29.40s

sshtarget authorized_keys restored using sudo -n cp -a; sudo -n cmp returned exit 0. Final-source clippy and verify fast are running. No merge gate or push.

Real network installation exposed two PAM-less startup issues in delegated self-install: show-user fails when no logind user object exists, and enable-linger starts its manager asynchronously. Self-install now confirms absent users through a successful live list-users query (malformed listing or failed bus queries fail closed), then waits up to ten seconds for a real user-manager bus reply before daemon-reload. Environment replies are discarded without logging. The same readiness helper serves server and infraternal agent installation. Sources: https://github.com/systemd/systemd/blob/main/src/login/loginctl.c and https://github.com/systemd/systemd/blob/main/man/loginctl.xml . The expanded ignored compiled-binary test now passes both OpenSSH key mode and loopback network none mode, each with real sshtarget systemd service, enrollment, mTLS tunnel, inventory and delegated uninstall. Network mode authorizes no install key and asserts no publickey attempt. Linger ownership failure/reinstall/removal coverage was moved here from the obsolete dummy-binary bootstrap test. Latest output: ``` test rootless_install_trust_inventory_and_remove_over_real_sshd ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 29.40s ``` sshtarget authorized_keys restored using sudo -n cp -a; sudo -n cmp returned exit 0. Final-source clippy and verify fast are running. No merge gate or push.
Author
Owner

Final-source fast gate failed in unchanged crates/server/src/stream.rs timing coverage; the initial run passed. The final run warned load5 above the 8 online CPUs. No test changes made. Rerunning the same required gate. Exact failure excerpt:

---- stream::tests::contended_native_close_fence_preserves_original_watcher_deadline stdout ----
742-
743:thread 'stream::tests::contended_native_close_fence_preserves_original_watcher_deadline' (2108935) panicked at crates/server/src/stream.rs:4633:14:
744-native dispatch did not reach the database barrier by C+650: Elapsed(())
745-
746-
747:failures:
748-    stream::tests::contended_native_close_fence_preserves_original_watcher_deadline
749-
750:test result: FAILED. 228 passed; 1 failed; 5 ignored; 0 measured; 0 filtered out; finished in 26.38s
751-
752:error: test failed, to rerun pass `-p infraternal-server --lib`
test stream::tests::forced_close_survives_full_queues_and_already_marked_local_close ... ok
test stream::sweep_tests::additional_remote_ssh_target_survives_sweeps_until_its_grant_is_withdrawn ... ok
test stream::tests::full_priority_queue_forces_connection_shutdown_after_retry_bound ... ok
test stream::tests::interactive_data_does_not_wait_to_fill_a_batch ... ok
test stream::tests::infraternal_agent_grant_rejection_is_audited_before_relay ... ok
test stream::sweep_tests::remote_exec_follows_refresh_lineage_and_never_skips_termination ... ok
test stream::tests::forced_close_audit_failure_shuts_down_without_waiting_for_full_queue ... ok
test stream::tests::out_of_range_resize_closes_only_its_channel_with_invalid_value ... ok
test stream::tests::queued_client_data_is_discarded_when_revoked_before_capacity_returns ... ok
test stream::tests::ready_batch_preserves_control_other_channel_and_invalid_input ... ok
test stream::sweep_tests::remote_ssh_follows_refresh_lineage_and_never_skips_termination ... ok
test stream::tests::rejects_malformed_hop_control_payloads ... ok
test stream::tests::pair_released_after_agent_close_is_still_audited_and_closed ... ok
test stream::tests::native_close_backpressure_preserves_forbidden_watcher_deadline ... ok
test storage::prelogin_bounds_tests::prelogin_admission_caps_and_indexed_expiry_converge ... ok
test stream::tests::stalled_close_drops_tcp_while_connection_operation_is_pending ... ok
test stream::tests::late_registration_after_observed_withdrawal_retires_within_one_second ... ok
test stream::tests::ready_batch_spends_each_frames_original_credit ... ok
test stream::tests::revocation_while_batch_waits_for_capacity_rejects_every_frame ... ok
test stream::tests::permitted_native_close_preserves_status_and_reason ... ok
test stream::tests::replaced_tunnel_force_closes_relayed_client_channels_within_one_second ... ok
test stream::tests::stopped_agent_identity_force_closes_relayed_client_channels_within_one_second ... ok
test stream::tests::writer_discards_queued_control_and_data_after_forbidden_close ... ok
test stream::tests::relay_forwards_empty_eof_then_close_to_the_public_client ... ok
test stream::tests::tunnel_stop_during_native_close_read_keeps_watcher_ownership ... ok
test stream::tests::stalled_forced_close_audit_is_bounded_and_does_not_hold_agent_close ... ok
test stream::tests::unconfirmable_native_close_fails_closed_without_forwarding_its_reason ... ok
test stream::watcher::tests::back_to_back_passes_close_the_client_inside_one_second ... ok
test stream::watcher::tests::coalesced_identity_stops_do_not_reset_route_deadlines ... ok
test stream::watcher::tests::coalesced_stops_audit_oldest_deadline_before_newer_routes ... ok
test stream::watcher::tests::identity_stop_during_a_slow_pass_closes_relayed_channels_host_offline ... ok
test stream::watcher::tests::identity_stop_during_failed_host_read_keeps_host_offline ... ok
test stream::watcher::tests::delayed_stop_observation_fails_closed_at_original_deadline ... ok
test stream::watcher::tests::identity_stop_during_failed_read_keeps_host_offline ... ok
test stream::watcher::tests::repeated_withdrawal_waits_for_the_first_attachment_audits ... ok
test stream::watcher::tests::late_registration_on_stopped_route_does_not_reset_deadline ... ok
test stream::watcher::tests::scheduler_recovery_after_a_long_stall_closes_nothing ... ok
test stream::watcher::tests::slow_reads_with_wakeups_and_new_channels_close_nothing ... ok
test tests::api_success_defaults_to_no_store_and_spa_fallback_stays_available ... ok
test stream::watcher::tests::teardown_stops_processes_and_joins_the_writer_before_bounded_concurrent_detaches ... ok
test tests::join_token_name_limit_counts_utf8_characters ... ok
test stream::watcher::tests::stopped_route_retirement_overrides_authorization_cause_and_deadline ... ok
test tests::lowercase_idempotency_ulids_share_the_canonical_row ... ok
test stream::watcher::tests::unconfirmed_worst_case_closes_the_client_inside_one_second ... ok
test tests::device_poll_admission_precedes_failed_response_actor_authentication ... ok
test tests::unknown_api_route_returns_json_error_and_never_spa_html ... ok
test tests::unmatched_api_errors_keep_no_store ... ok
test tests::wrong_api_method_returns_error_and_allow_header ... ok
test tests::openapi_documents_idempotency_headers_and_bearer_security ... ok
test tests::openapi_documents_refresh_and_rate_limit_transports ... ok
test tunnel::tests::agent_opened_channels_are_refused_and_unknown_ids_are_protocol_errors ... ok
test tunnel::tests::benchmark_tunnel_credit_accounting ... ignored, benchmark only on the perf-test VM
test tunnel::provider_rpc_tests::slow_remote_actions_confirm_success_within_the_action_deadline ... ok
test tunnel::tests::failed_opens_release_slots_and_tunnel_budget_across_clients ... ok
test tests::openapi_documents_shared_error_responses ... ok
test stream::tests::withdrawal_during_successful_native_confirmation_keeps_watcher_ownership ... ok
test tunnel::tests::tunnel_ping_carries_the_current_unix_time ... ok
test tunnel::tests::websocket_reads_map_to_contract_close_codes ... ok
test stream::watcher::tests::shared_rechecks_do_not_grow_with_connections_and_keep_the_bound ... ok
test tunnel::tests::identity_stop_during_reconnect_marks_pinned_old_routes ... ok
test tunnel::tests::shutdown_marks_pinned_routes_before_releasing_the_tunnel ... ok
test tunnel::tests::session_termination_requires_confirmation_and_releases_reply_slots ... ok
test stream::tests::stream_data_fence_cost ... ok
test stream::tests::native_termination_and_watcher_both_deliver_one_audited_forbidden_close ... ok
test tunnel::provider_rpc_tests::remote_inspect_and_actions_use_signed_provider_rpc_with_typed_errors ... ok

failures:

---- stream::tests::contended_native_close_fence_preserves_original_watcher_deadline stdout ----

thread 'stream::tests::contended_native_close_fence_preserves_original_watcher_deadline' (2108935) panicked at crates/server/src/stream.rs:4633:14:
native dispatch did not reach the database barrier by C+650: Elapsed(())


failures:
    stream::tests::contended_native_close_fence_preserves_original_watcher_deadline

test result: FAILED. 228 passed; 1 failed; 5 ignored; 0 measured; 0 filtered out; finished in 26.38s

error: test failed, to rerun pass `-p infraternal-server --lib`
Final-source fast gate failed in unchanged crates/server/src/stream.rs timing coverage; the initial run passed. The final run warned load5 above the 8 online CPUs. No test changes made. Rerunning the same required gate. Exact failure excerpt: ```text ---- stream::tests::contended_native_close_fence_preserves_original_watcher_deadline stdout ---- 742- 743:thread 'stream::tests::contended_native_close_fence_preserves_original_watcher_deadline' (2108935) panicked at crates/server/src/stream.rs:4633:14: 744-native dispatch did not reach the database barrier by C+650: Elapsed(()) 745- 746- 747:failures: 748- stream::tests::contended_native_close_fence_preserves_original_watcher_deadline 749- 750:test result: FAILED. 228 passed; 1 failed; 5 ignored; 0 measured; 0 filtered out; finished in 26.38s 751- 752:error: test failed, to rerun pass `-p infraternal-server --lib` test stream::tests::forced_close_survives_full_queues_and_already_marked_local_close ... ok test stream::sweep_tests::additional_remote_ssh_target_survives_sweeps_until_its_grant_is_withdrawn ... ok test stream::tests::full_priority_queue_forces_connection_shutdown_after_retry_bound ... ok test stream::tests::interactive_data_does_not_wait_to_fill_a_batch ... ok test stream::tests::infraternal_agent_grant_rejection_is_audited_before_relay ... ok test stream::sweep_tests::remote_exec_follows_refresh_lineage_and_never_skips_termination ... ok test stream::tests::forced_close_audit_failure_shuts_down_without_waiting_for_full_queue ... ok test stream::tests::out_of_range_resize_closes_only_its_channel_with_invalid_value ... ok test stream::tests::queued_client_data_is_discarded_when_revoked_before_capacity_returns ... ok test stream::tests::ready_batch_preserves_control_other_channel_and_invalid_input ... ok test stream::sweep_tests::remote_ssh_follows_refresh_lineage_and_never_skips_termination ... ok test stream::tests::rejects_malformed_hop_control_payloads ... ok test stream::tests::pair_released_after_agent_close_is_still_audited_and_closed ... ok test stream::tests::native_close_backpressure_preserves_forbidden_watcher_deadline ... ok test storage::prelogin_bounds_tests::prelogin_admission_caps_and_indexed_expiry_converge ... ok test stream::tests::stalled_close_drops_tcp_while_connection_operation_is_pending ... ok test stream::tests::late_registration_after_observed_withdrawal_retires_within_one_second ... ok test stream::tests::ready_batch_spends_each_frames_original_credit ... ok test stream::tests::revocation_while_batch_waits_for_capacity_rejects_every_frame ... ok test stream::tests::permitted_native_close_preserves_status_and_reason ... ok test stream::tests::replaced_tunnel_force_closes_relayed_client_channels_within_one_second ... ok test stream::tests::stopped_agent_identity_force_closes_relayed_client_channels_within_one_second ... ok test stream::tests::writer_discards_queued_control_and_data_after_forbidden_close ... ok test stream::tests::relay_forwards_empty_eof_then_close_to_the_public_client ... ok test stream::tests::tunnel_stop_during_native_close_read_keeps_watcher_ownership ... ok test stream::tests::stalled_forced_close_audit_is_bounded_and_does_not_hold_agent_close ... ok test stream::tests::unconfirmable_native_close_fails_closed_without_forwarding_its_reason ... ok test stream::watcher::tests::back_to_back_passes_close_the_client_inside_one_second ... ok test stream::watcher::tests::coalesced_identity_stops_do_not_reset_route_deadlines ... ok test stream::watcher::tests::coalesced_stops_audit_oldest_deadline_before_newer_routes ... ok test stream::watcher::tests::identity_stop_during_a_slow_pass_closes_relayed_channels_host_offline ... ok test stream::watcher::tests::identity_stop_during_failed_host_read_keeps_host_offline ... ok test stream::watcher::tests::delayed_stop_observation_fails_closed_at_original_deadline ... ok test stream::watcher::tests::identity_stop_during_failed_read_keeps_host_offline ... ok test stream::watcher::tests::repeated_withdrawal_waits_for_the_first_attachment_audits ... ok test stream::watcher::tests::late_registration_on_stopped_route_does_not_reset_deadline ... ok test stream::watcher::tests::scheduler_recovery_after_a_long_stall_closes_nothing ... ok test stream::watcher::tests::slow_reads_with_wakeups_and_new_channels_close_nothing ... ok test tests::api_success_defaults_to_no_store_and_spa_fallback_stays_available ... ok test stream::watcher::tests::teardown_stops_processes_and_joins_the_writer_before_bounded_concurrent_detaches ... ok test tests::join_token_name_limit_counts_utf8_characters ... ok test stream::watcher::tests::stopped_route_retirement_overrides_authorization_cause_and_deadline ... ok test tests::lowercase_idempotency_ulids_share_the_canonical_row ... ok test stream::watcher::tests::unconfirmed_worst_case_closes_the_client_inside_one_second ... ok test tests::device_poll_admission_precedes_failed_response_actor_authentication ... ok test tests::unknown_api_route_returns_json_error_and_never_spa_html ... ok test tests::unmatched_api_errors_keep_no_store ... ok test tests::wrong_api_method_returns_error_and_allow_header ... ok test tests::openapi_documents_idempotency_headers_and_bearer_security ... ok test tests::openapi_documents_refresh_and_rate_limit_transports ... ok test tunnel::tests::agent_opened_channels_are_refused_and_unknown_ids_are_protocol_errors ... ok test tunnel::tests::benchmark_tunnel_credit_accounting ... ignored, benchmark only on the perf-test VM test tunnel::provider_rpc_tests::slow_remote_actions_confirm_success_within_the_action_deadline ... ok test tunnel::tests::failed_opens_release_slots_and_tunnel_budget_across_clients ... ok test tests::openapi_documents_shared_error_responses ... ok test stream::tests::withdrawal_during_successful_native_confirmation_keeps_watcher_ownership ... ok test tunnel::tests::tunnel_ping_carries_the_current_unix_time ... ok test tunnel::tests::websocket_reads_map_to_contract_close_codes ... ok test stream::watcher::tests::shared_rechecks_do_not_grow_with_connections_and_keep_the_bound ... ok test tunnel::tests::identity_stop_during_reconnect_marks_pinned_old_routes ... ok test tunnel::tests::shutdown_marks_pinned_routes_before_releasing_the_tunnel ... ok test tunnel::tests::session_termination_requires_confirmation_and_releases_reply_slots ... ok test stream::tests::stream_data_fence_cost ... ok test stream::tests::native_termination_and_watcher_both_deliver_one_audited_forbidden_close ... ok test tunnel::provider_rpc_tests::remote_inspect_and_actions_use_signed_provider_rpc_with_typed_errors ... ok failures: ---- stream::tests::contended_native_close_fence_preserves_original_watcher_deadline stdout ---- thread 'stream::tests::contended_native_close_fence_preserves_original_watcher_deadline' (2108935) panicked at crates/server/src/stream.rs:4633:14: native dispatch did not reach the database barrier by C+650: Elapsed(()) failures: stream::tests::contended_native_close_fence_preserves_original_watcher_deadline test result: FAILED. 228 passed; 1 failed; 5 ignored; 0 measured; 0 filtered out; finished in 26.38s error: test failed, to rerun pass `-p infraternal-server --lib` ```
Author
Owner

Merged dev (87bb5bd2b7) into self-install (base 5b5cf987fc). Head: bc828c8b3cde57c2f7f3533131039d22f6cf1eb1. Nothing pushed.

Conflict resolutions:

  • DECISIONS.md: retained dev §§31–43 and appended the self-install decision as §44.
  • crates/server/src/install.rs: retained dev's connect/banner/auth/host-key flow and self-install's token mint/delivery/enrollment during Install. Start confirms service readiness. Pass job.auth through all delegated operations and retries.
  • crates/server/src/install_bootstrap.rs: combined auth selection/error classification with delegated agent install/agent uninstall, preserving exact pins and ownership checks. The obsolete dummy-binary linger test was moved into the compiled CLI integration scenario. Network loopback upload/refusal coverage remains.
  • crates/server/src/install_worker_tests.rs: token-delivery/enrollment hooks remain on Install; the fake records auth on install, start, upload, observe, probe and remove. Existing network/key override, retry, authorization-loss and host-key tests retained.

Fresh PAM-less network SSH needed three setup fixes: runtime-directory fallback before delegated service operations; successful live logind listing to distinguish an absent user object from bus errors; bounded user-manager readiness after enabling linger. The shared CLI readiness helper serves both server and infraternal agent installation. No new hot path. Sources: pam_systemd, loginctl source, loginctl manual source. DESIGN and contracts now describe delegated step ordering and auth behavior.

Tests:

  • Workspace clippy cargo clippy --workspace --all-targets -- -D warnings: passed.
  • cargo test -p infraternal-server install: cargo test: 56 passed, 4 ignored, 295 filtered out (15 suites, 8.34s).
  • cargo test -p infraternal-server install -- --ignored: four real OpenSSH tests passed (trust, atomic upload, ownership fencing and convergent cleanup).
  • cargo test -p infraternal-server real_network_auth -- --nocapture: two loopback tests passed (none-auth upload/removal, refusal and key-mode auth exclusivity).
  • cargo test -p infraternal --test agent_install -- --ignored: passed, running the full installation scenario sequentially in both key and network modes. Both install a real sshtarget user service, enroll, establish mTLS, expose inventory, exercise linger ownership failure/reinstall and delegate uninstall. Network mode adds no install key and records no publickey attempt.
  • Logind listing parser regression passed; malformed output fails closed.
  • External sudo -n cp -a authorized_keys backup restored with sudo -n cp -a; sudo -n cmp passed (exit 0).

Gate: final-source INFRATERNAL_VERIFY_RUN_ID=self-install-merge scripts/verify fast passed all 19 stages in 380.861s (exit 0). No verify merge was run.

Exact targeted test outputs:

install-ignored:

test install_bootstrap::tests::real_test_sshd_requires_explicit_trust_before_authentication ... ok
test install_bootstrap::tests::real_removal_reports_incomplete_cleanup_and_converges ... ok
test install_bootstrap::tests::real_remote_paths_are_fenced_by_installation_ownership ... ok
test install_bootstrap::tests::real_rootless_upload_requires_pin_and_atomically_installs_verified_binary ... ok
test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 230 filtered out; finished in 32.26s

network:

test install_bootstrap::tests::real_network_auth_refusal_is_actionable_and_key_mode_never_tries_none ... ok
test install_bootstrap::tests::real_network_auth_uploads_and_removes_without_offering_a_key ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 232 filtered out; finished in 0.84s

real-install:

test rootless_install_trust_inventory_and_remove_over_real_sshd ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 29.40s

clippy:

    Finished `dev` profile [unoptimized] target(s) in 7.58s

An earlier final-source fast run failed in the unchanged stream::tests::contended_native_close_fence_preserves_original_watcher_deadline test (native dispatch did not reach the database barrier by C+650: Elapsed(())) under a load warning. The same gate passed on rerun without changing any test or source. The existing cookie@0.6.0 low-severity advisory remains report-only. No new benchmark or performance claim: all additions are setup work.

Exact final gate output:

WARN load5=20.29 exceeds online CPU count=8; timing-sensitive checks may be delayed
PASS fast: migrations are append-only (0.323s)
PASS fast: workspace package dependencies (0.622s)
PASS fast: gate target-directory and launch diagnostics tests (0.314s)
PASS fast: JavaScript dependencies (0.021s)
PASS fast: advisory review policy tests (0.183s)
SUPPLY_CHAIN_REVIEWS advisory exceptions are within their review windows
SUPPLY_CHAIN cargo-deny: cargo-deny 0.20.2
SUPPLY_CHAIN_NOTICES generated THIRD_PARTY_NOTICES.md from 81 frontend packages
SUPPLY_CHAIN_NOTICES copied source: 47 calternal-derived files
SUPPLY_CHAIN_NOTICES license files: 54 distinct texts; 0 packages need manual license review
advisories ok
SUPPLY_CHAIN_RUST_ADVISORIES passed with refreshed advisory data
licenses ok
SUPPLY_CHAIN_RUST_LICENSES passed
bun audit v1.4.2 (744846f84)

cookie@0.6.0
  workspace:@infraternal/web > @sveltejs/kit > cookie
  low: cookie accepts cookie name, path, and domain with out of bounds characters (<0.7.0) - https://github.com/advisories/GHSA-pxg6-pf52-xh8x

1 vulnerability (1 low)

  bun audit fix           upgrade the vulnerable packages within their ranges
  bun audit fix --latest  also cross major versions
SUPPLY_CHAIN_BUN_AUDIT findings reported; this check is report-only
SUPPLY_CHAIN_PASS Rust advisory/license checks complete; Bun findings are report-only
PASS fast: supply-chain checks (4.866s)
PASS fast: build web app (14.371s)
PASS fast: cargo fmt --check (2.015s)
PASS fast: cargo clippy (29.414s)
PASS fast: cargo test (213.000s)
PASS fast: svelte-check (23.066s)
PASS fast: UI package svelte-check (10.299s)
PASS fast: stream package typecheck (15.669s)
PASS fast: Vitest (16.194s)
PASS fast: UI package Vitest (12.504s)
PASS fast: stream package Vitest (3.379s)
PASS fast: OpenAPI and TypeScript client drift (25.022s)
PASS fast: terminal TypeScript (7.068s)
PASS fast: terminal Vitest (2.531s)

Exact gate summary:

Gate: fast
Stages: 19
Elapsed  Stage
0.323s   fast: migrations are append-only
0.622s   fast: workspace package dependencies
0.314s   fast: gate target-directory and launch diagnostics tests
0.021s   fast: JavaScript dependencies
0.183s   fast: advisory review policy tests
4.866s   fast: supply-chain checks
14.371s  fast: build web app
2.015s   fast: cargo fmt --check
29.414s  fast: cargo clippy
213.000s fast: cargo test
23.066s  fast: svelte-check
10.299s  fast: UI package svelte-check
15.669s  fast: stream package typecheck
16.194s  fast: Vitest
12.504s  fast: UI package Vitest
3.379s   fast: stream package Vitest
25.022s  fast: OpenAPI and TypeScript client drift
7.068s   fast: terminal TypeScript
2.531s   fast: terminal Vitest
Total: 380.861s
Merged `dev` (87bb5bd2b7410d3a571a903f6cf6a53d1112ca6f) into `self-install` (base 5b5cf987fc4d3aac23d0505617256eca6cc0bea8). Head: `bc828c8b3cde57c2f7f3533131039d22f6cf1eb1`. Nothing pushed. Conflict resolutions: - `DECISIONS.md`: retained dev §§31–43 and appended the self-install decision as §44. - `crates/server/src/install.rs`: retained dev's connect/banner/auth/host-key flow and self-install's token mint/delivery/enrollment during Install. Start confirms service readiness. Pass job.auth through all delegated operations and retries. - `crates/server/src/install_bootstrap.rs`: combined auth selection/error classification with delegated `agent install`/`agent uninstall`, preserving exact pins and ownership checks. The obsolete dummy-binary linger test was moved into the compiled CLI integration scenario. Network loopback upload/refusal coverage remains. - `crates/server/src/install_worker_tests.rs`: token-delivery/enrollment hooks remain on Install; the fake records auth on install, start, upload, observe, probe and remove. Existing network/key override, retry, authorization-loss and host-key tests retained. Fresh PAM-less network SSH needed three setup fixes: runtime-directory fallback before delegated service operations; successful live logind listing to distinguish an absent user object from bus errors; bounded user-manager readiness after enabling linger. The shared CLI readiness helper serves both server and infraternal agent installation. No new hot path. Sources: [pam_systemd](https://www.freedesktop.org/software/systemd/man/latest/pam_systemd.html), [loginctl source](https://github.com/systemd/systemd/blob/main/src/login/loginctl.c), [loginctl manual source](https://github.com/systemd/systemd/blob/main/man/loginctl.xml). DESIGN and contracts now describe delegated step ordering and auth behavior. Tests: - Workspace clippy `cargo clippy --workspace --all-targets -- -D warnings`: passed. - `cargo test -p infraternal-server install`: `cargo test: 56 passed, 4 ignored, 295 filtered out (15 suites, 8.34s)`. - `cargo test -p infraternal-server install -- --ignored`: four real OpenSSH tests passed (trust, atomic upload, ownership fencing and convergent cleanup). - `cargo test -p infraternal-server real_network_auth -- --nocapture`: two loopback tests passed (none-auth upload/removal, refusal and key-mode auth exclusivity). - `cargo test -p infraternal --test agent_install -- --ignored`: passed, running the full installation scenario sequentially in both key and network modes. Both install a real sshtarget user service, enroll, establish mTLS, expose inventory, exercise linger ownership failure/reinstall and delegate uninstall. Network mode adds no install key and records no publickey attempt. - Logind listing parser regression passed; malformed output fails closed. - External `sudo -n cp -a` authorized_keys backup restored with `sudo -n cp -a`; `sudo -n cmp` passed (exit 0). Gate: final-source `INFRATERNAL_VERIFY_RUN_ID=self-install-merge scripts/verify fast` passed all 19 stages in 380.861s (exit 0). No verify merge was run. Exact targeted test outputs: `install-ignored`: ```text test install_bootstrap::tests::real_test_sshd_requires_explicit_trust_before_authentication ... ok test install_bootstrap::tests::real_removal_reports_incomplete_cleanup_and_converges ... ok test install_bootstrap::tests::real_remote_paths_are_fenced_by_installation_ownership ... ok test install_bootstrap::tests::real_rootless_upload_requires_pin_and_atomically_installs_verified_binary ... ok test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 230 filtered out; finished in 32.26s ``` `network`: ```text test install_bootstrap::tests::real_network_auth_refusal_is_actionable_and_key_mode_never_tries_none ... ok test install_bootstrap::tests::real_network_auth_uploads_and_removes_without_offering_a_key ... ok test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 232 filtered out; finished in 0.84s ``` `real-install`: ```text test rootless_install_trust_inventory_and_remove_over_real_sshd ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 29.40s ``` `clippy`: ```text Finished `dev` profile [unoptimized] target(s) in 7.58s ``` An earlier final-source fast run failed in the unchanged `stream::tests::contended_native_close_fence_preserves_original_watcher_deadline` test (`native dispatch did not reach the database barrier by C+650: Elapsed(())`) under a load warning. The same gate passed on rerun without changing any test or source. The existing cookie@0.6.0 low-severity advisory remains report-only. No new benchmark or performance claim: all additions are setup work. Exact final gate output: ```text WARN load5=20.29 exceeds online CPU count=8; timing-sensitive checks may be delayed PASS fast: migrations are append-only (0.323s) PASS fast: workspace package dependencies (0.622s) PASS fast: gate target-directory and launch diagnostics tests (0.314s) PASS fast: JavaScript dependencies (0.021s) PASS fast: advisory review policy tests (0.183s) SUPPLY_CHAIN_REVIEWS advisory exceptions are within their review windows SUPPLY_CHAIN cargo-deny: cargo-deny 0.20.2 SUPPLY_CHAIN_NOTICES generated THIRD_PARTY_NOTICES.md from 81 frontend packages SUPPLY_CHAIN_NOTICES copied source: 47 calternal-derived files SUPPLY_CHAIN_NOTICES license files: 54 distinct texts; 0 packages need manual license review advisories ok SUPPLY_CHAIN_RUST_ADVISORIES passed with refreshed advisory data licenses ok SUPPLY_CHAIN_RUST_LICENSES passed bun audit v1.4.2 (744846f84) cookie@0.6.0 workspace:@infraternal/web > @sveltejs/kit > cookie low: cookie accepts cookie name, path, and domain with out of bounds characters (<0.7.0) - https://github.com/advisories/GHSA-pxg6-pf52-xh8x 1 vulnerability (1 low) bun audit fix upgrade the vulnerable packages within their ranges bun audit fix --latest also cross major versions SUPPLY_CHAIN_BUN_AUDIT findings reported; this check is report-only SUPPLY_CHAIN_PASS Rust advisory/license checks complete; Bun findings are report-only PASS fast: supply-chain checks (4.866s) PASS fast: build web app (14.371s) PASS fast: cargo fmt --check (2.015s) PASS fast: cargo clippy (29.414s) PASS fast: cargo test (213.000s) PASS fast: svelte-check (23.066s) PASS fast: UI package svelte-check (10.299s) PASS fast: stream package typecheck (15.669s) PASS fast: Vitest (16.194s) PASS fast: UI package Vitest (12.504s) PASS fast: stream package Vitest (3.379s) PASS fast: OpenAPI and TypeScript client drift (25.022s) PASS fast: terminal TypeScript (7.068s) PASS fast: terminal Vitest (2.531s) ``` Exact gate summary: ```text Gate: fast Stages: 19 Elapsed Stage 0.323s fast: migrations are append-only 0.622s fast: workspace package dependencies 0.314s fast: gate target-directory and launch diagnostics tests 0.021s fast: JavaScript dependencies 0.183s fast: advisory review policy tests 4.866s fast: supply-chain checks 14.371s fast: build web app 2.015s fast: cargo fmt --check 29.414s fast: cargo clippy 213.000s fast: cargo test 23.066s fast: svelte-check 10.299s fast: UI package svelte-check 15.669s fast: stream package typecheck 16.194s fast: Vitest 12.504s fast: UI package Vitest 3.379s fast: stream package Vitest 25.022s fast: OpenAPI and TypeScript client drift 7.068s fast: terminal TypeScript 2.531s fast: terminal Vitest Total: 380.861s ```
Author
Owner

Final cleanup: the shared authorized_keys file changed again during the later gate run after the earlier successful restoration. No install test process remained active. Restored the job-start backup again with sudo -n cp -a and repeated sudo -n cmp successfully (exit 0). Head bc828c8b3c; worktree clean; no push. Full report: /mnt/hdd/infraternal/jobs/self-install-merge/result.md.

Final cleanup: the shared authorized_keys file changed again during the later gate run after the earlier successful restoration. No install test process remained active. Restored the job-start backup again with sudo -n cp -a and repeated sudo -n cmp successfully (exit 0). Head bc828c8b3cde57c2f7f3533131039d22f6cf1eb1; worktree clean; no push. Full report: /mnt/hdd/infraternal/jobs/self-install-merge/result.md.
Commenting is not possible because the repository is archived.
No labels
No milestone
No project
No assignees
1 participant
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kayg/infraternal-v0#73
No description provided.