Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
459 changes: 278 additions & 181 deletions dag/extdeps/bmc/openbmc_fan_control.dag

Large diffs are not rendered by default.

9 changes: 0 additions & 9 deletions dag/extdeps/bmc/openbmc_operation.dag
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,6 @@ type OpenBmcOperation
sensor_path: NonEmptyStr,
threshold: OpenBmcThresholdProperty,
}
| OpenBmcFanConfigScalar {
config_path: NonEmptyStr,
query: OpenBmcFanConfigQuery,
}
| OpenBmcFanConfigValidate { config_path: NonEmptyStr }
| OpenBmcFanConfigVerify {
config_path: NonEmptyStr,
desired: OpenBmcFanConfigDesired,
}
| OpenBmcPathIsWritable { path: NonEmptyStr }
| OpenBmcPathExists { path: NonEmptyStr }
| OpenBmcPathIsNonemptyFile { path: NonEmptyStr }
Expand Down
302 changes: 1 addition & 301 deletions dag/extdeps/bmc/openbmc_password_ssh_transport.dag

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions dag/extdeps/tools/hostname.dag
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,17 @@ data extdeps_hostnamectl_external_authority_anchor: ExternalAuthority = External
}
}

// TWO READ ROWS BECAUSE THEY ANSWER DIFFERENT QUESTIONS. Bare `hostname` prints whatever the host is
// configured to call itself, which may be fully qualified; `hostname -s` prints the first label only.
// The reach probe compares its read for EXACT equality against the enrolled fleet slot label (the
// short name a host is known by on the LAN), so it needs the configured name the host itself reports
// — not a re-derived first label — and the probe therefore owns this row, the bare spelling, while a
// converge that matches by short label owns `hostname_short_read_argv`. Collapsing the two would
// silently change which name a caller compared.
fn hostname_read_argv() -> List<String> {
[hostname_program().invocation as String]
}

fn hostname_short_read_argv() -> List<String> {
["hostname", "-s"]
}
Expand Down
13 changes: 13 additions & 0 deletions dag/extdeps/tools/id.dag
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,19 @@ fn id_uid_argv() -> List<String> {
[id_program().invocation as String, "-u"]
}

// NO OPERAND, DELIBERATELY: id(1) with no operand answers from the CALLING PROCESS's own
// credentials, established at its exec and never changing afterwards. The reach probe asks "which
// account did the transport actually reach", and the only account that can say is the one the session
// IS — so the probe supplies no user and reads the login name the session itself carries. A user
// operand would ask the account database about a different account and answer the wrong question.
//
// -un, NOT -u: -n selects NAMES over numeric uids (the probe records the login name it was granted,
// not a per-host uid), and -u then prints that name alone rather than the full `id` record. One flag
// for the name, one for the field; the probe trims the newline the remote shell leaves on stdout.
fn id_user_name_argv() -> List<String> {
[id_program().invocation as String, "-un"]
}

fn id_gid_argv() -> List<String> {
[id_program().invocation as String, "-g"]
}
Expand Down
14 changes: 9 additions & 5 deletions dag/gunbc/host/host_reach_identity_probe.dag
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
module gunbc.host_reach_identity_probe

import extdeps.filesystem.filesystem_io { Filesystem }
import extdeps.tools.id { id_user_name_argv }
import extdeps.tools.hostname { hostname_read_argv }
import std.algebra { trim }
import std.process { ProcessExit, ExitSuccess, exit_failure }
import gunbc.typed_argv_exec {
Expand Down Expand Up @@ -326,11 +328,13 @@ type FleetReachProbeRun {
// receipt stays keyed by fleet IDENTITY — addressing is transport, never identity.
// Repairs #1 + #2: consult fleet_probe_target_for FIRST. A refused target (unobserved
// principal) yields ProbeTargetUnobserved and NEVER touches SSH. A ready target runs two
// typed argv ops against the SAME explicit principal@endpoint — `hostname` for host
// identity and `id -un` for the account actually reached — so ProbeReachable carries both
// dimensions and the receipt carries the requested principal for the identity verdict.
// typed argv ops against the SAME explicit principal@endpoint — `hostname_read_argv` for
// host identity and `id_user_name_argv` for the account actually reached — so ProbeReachable
// carries both dimensions and the receipt carries the requested principal for the identity
// verdict. The argv is spelled by the tool module that owns the tool, not hand-typed here:
// the probe no longer decides how `hostname` or `id` get their flags.
fn probe_reachable_with_principal(target: String, hostname_stdout: String) -> ReachProbeOutcome {
match typed_argv_probe_over_ssh(host: target as NonEmptyStr, argv: ["id", "-un"]) {
match typed_argv_probe_over_ssh(host: target as NonEmptyStr, argv: id_user_name_argv()) {
TypedArgvExecConverged { result: idr } =>
if idr.success {
ProbeReachable { observed_short_hostname: trim(hostname_stdout), observed_principal: trim(idr.stdout) }
Expand All @@ -343,7 +347,7 @@ fn probe_reachable_with_principal(target: String, hostname_stdout: String) -> Re

fn probe_ready_target_once(host: String, endpoint: String, remote_principal: String) -> ReachProbeOutcome {
let target = fleet_probe_ready_ssh_target(t_host: host, endpoint: endpoint, remote_principal: remote_principal)
match typed_argv_probe_over_ssh(host: target as NonEmptyStr, argv: ["hostname"]) {
match typed_argv_probe_over_ssh(host: target as NonEmptyStr, argv: hostname_read_argv()) {
TypedArgvExecConverged { result: r } =>
if r.success {
probe_reachable_with_principal(target: target, hostname_stdout: r.stdout)
Expand Down
16 changes: 16 additions & 0 deletions dag/test/claim/host/host_reach_identity_probe_witness_test.dag
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ module test.claim.host_reach_identity_probe_witness

import gunbc.network_identity_subsumption { srv3_post_install_lease_table_fixture }
import gunbc.srv3_os_install_diagnostic { srv3_install_hang_no_router_lease_ms }
import extdeps.tools.id { id_user_name_argv }
import extdeps.tools.hostname { hostname_read_argv }
import std.types { Bool, String, NonEmptyStr, List }
import std.upsert_decision { ObservationVerdict, Converged, Drifted, Conflict, Inaccessible, UnknownRefused }
import product.host_identity { HostIdentity }
Expand Down Expand Up @@ -251,3 +253,17 @@ test fn probe_endpoint_agrees_with_the_projection_it_consumes() -> Bool {
fleet_probe_endpoint_for(host: "srv5") == spark_endpoint_or_empty(binding: srv5_router_binding)
&& fleet_probe_endpoint_for(host: "srv6") == spark_endpoint_or_empty(binding: srv6_router_binding)
}

// THE HANDBACK CONTROL FOR THE HAND-ARGV DISSOLUTION (roadmap shell-dag-host-reach-identity-probe).
// probe_ready_target_once and probe_reachable_with_principal no longer spell their argv by hand:
// they call hostname_read_argv() and id_user_name_argv() respectively, so the probe's argv IS the
// builder's argv. This test pins each builder's MATERIALIZED argv to the exact words the probe has
// always executed, so the flags cannot drift out from under the builder: a mutant that swaps
// id_user_name_argv to ["id","-u"] (the numeric uid), drops -n, or hands hostname_read_argv a -s
// (first label) instead of the full configured name typechecks and reds here. The -s decision the
// row's first_slice names resolves to the BARE read: the probe compares its output for exact
// equality against the enrolled short slot label, and -s would re-derive a label the probe must
// instead read as the host reports it.
test fn the_probe_argv_is_spelled_by_its_tool_module_builders() -> Bool {
id_user_name_argv() == ["id", "-un"] && hostname_read_argv() == ["hostname"]
}
Loading