Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
c5dedfd
File Mt. Collins boot through the approval store, and converge the sr…
Sep 16, 2026
c2752a4
Align the approval filer with part C's wire: nested request string, k…
Sep 17, 2026
42ca23d
Match the optional BMC username before interpolating ipmitool SOL.
Sep 17, 2026
aa216f1
Give the boot a real approval window and refuse an unread GET standing.
Sep 17, 2026
ea1bcbf
Consume the BMC secret pin and the keyring digest wall from their own…
Sep 17, 2026
9beccad
Serve POST /approvals and GET /approvals/{id}, and pass the grant int…
Sep 17, 2026
89fa365
Consume C's minted submission MAC pin instead of wrapping an unpinned…
Sep 17, 2026
80166ba
Derive loopback origin and keyring host from their existing authorities.
Sep 17, 2026
f0f79a5
Keep the keyring off fleet_converge_receipt so ci_spec stays acyclic.
Sep 17, 2026
534ee8c
Hold SOL by a live ipmitool pid, and refuse clock-unreadable as an ex…
Sep 17, 2026
772c1b6
Emit fleet-converge modes and pin SOL activate to the BMC password file.
Sep 17, 2026
0afbcef
Install the approval keyring over fleet SSH as the administrator, not…
Sep 17, 2026
1c1b696
Admit the host-env-name rebind and name String's declaring module in …
Sep 17, 2026
ac724f6
Own keyring chown from fleet POSIX accounts and drop the duplicated S…
Sep 17, 2026
b453b08
Drop consumed #11440 wave rows and bind edac Unit to std.types.
Sep 17, 2026
3063119
Admit the boot from GET decision fields, not the 0600 store.
Sep 17, 2026
8900cd4
Share the env-path read and size the boot step from the poll window.
Sep 17, 2026
0697a7b
Track TimestampAdd on the same offset-Nat dissolve-on as TimestampOff…
Sep 17, 2026
6c8f1ce
Name the HTTP grant client on the frontier and refuse boot off srv1.
Sep 17, 2026
5da0c3f
Digest both staged MAC keys before install and name the boot POST bou…
Sep 17, 2026
820f674
Claim the Mt. Collins boot grant on the durable slot before any contr…
Sep 17, 2026
494e260
Route staged MAC-key digest checks through the role standing folds.
Sep 17, 2026
67b6ec3
Refuse non-canonical stored instants and duplicate kv standing lines.
Sep 17, 2026
d81ee9b
Delete approval_issuance_frontier now that issue_links signs and publ…
Sep 17, 2026
0dde6bb
One MAC-key digest standing fold, and cite the pin instead of its hex.
Sep 17, 2026
bd5004f
Put POST filed-token and GET standing on one kv grammar.
Sep 17, 2026
bdad98a
Carry request authority into admit_submission; drop the tautological …
Sep 17, 2026
f19badf
Do not file or admit poll when no approval capability can be minted.
Sep 17, 2026
7dd61ba
Remove unattached and in-body // annotations that fail parse.
Sep 17, 2026
ee458e1
Recheck SOL hold immediately before the BMC write.
Sep 17, 2026
798e28c
Align hermetic floor claims with loopback emit and unmocked Filesyste…
Sep 17, 2026
6eba64e
Mark the approval-keyring and Mt. Collins boot GHA runners as bash-em…
Sep 17, 2026
4d4d976
Reclaim a stale BMC SOL session before activate.
Sep 17, 2026
fb48eef
Create /var/lib/gunbc/approvals for the unit user during keyring conv…
Sep 17, 2026
0753f2e
Discriminate a live ipmitool SOL pid from a nonempty capture in the w…
Sep 17, 2026
b48a34e
Import NonEmptyStr for the SOL ipmitool prefix.
Sep 17, 2026
31ee49d
Admit the unit user to gunbc-ntfy instead of retaking the publisher t…
Sep 17, 2026
b15308d
Retry a transient unreadable approval status inside the poll window.
Sep 17, 2026
8cf0d03
Drop the unreadable-status re-poll.
Sep 17, 2026
8197df7
Carry approval authority on the claims, not StoredApprovalRequest.
Sep 17, 2026
22874ea
Stop claiming srv1-live binds every interface.
Sep 17, 2026
a1a6948
Seal keyring files at 640 and stop claiming the claim root namespaces…
Sep 18, 2026
86705a1
Re-authorize the boot grant on the claim clock before attach.
Sep 18, 2026
f0b9e25
Drop the foreign-authority witness that still named a field C does no…
Sep 18, 2026
b2669db
Regenerate fleet-converge.yml after the rebase onto C.
Sep 18, 2026
64cb8b9
Create the authorization-claim store as ghrunner-owned 0700 on keyrin…
Sep 18, 2026
8434042
Stop polling un-approvable slots and bind the boot grant to a complet…
Sep 18, 2026
8f6d88f
Delete consumed namespace-wave admissions on the roster this change a…
Sep 18, 2026
0ee1bc6
Observe the SOL lease from the collector this run holds, not from sol…
Sep 18, 2026
1ea2fde
Move SOL process-hold out of the IPMI interface onto a marked gunbc s…
Sep 18, 2026
d09e88e
Refuse an unreadable MegaRAC session GET instead of treating it as re…
Sep 18, 2026
2c79dc0
Stop naming uncited MegaRAC wires as refusals, and match firmware rea…
Sep 18, 2026
dcead2f
Drive reset from the roster row's boot options and parameter-5 expect…
Sep 18, 2026
67af974
Keep the boot-step timeout as a Minute carrier until the GHA field.
Sep 18, 2026
0d93fa3
Do not treat a sentinel CSRF 401 as a released MegaRAC session.
Sep 18, 2026
ccfebdb
Read RUNNER_TEMP from the Actions environment authority.
Sep 18, 2026
760eb65
Keep escalation HTTP status exhaustive over EscalationSlot.
Sep 18, 2026
681d5f6
Merge origin/main; drop consumed #11549 namespace admission and bind …
Sep 18, 2026
488168b
Keep the CD selector measurement on the selector, not the other param…
Sep 18, 2026
748106a
Bind Unit from std.types in coreutils_stat.
Sep 18, 2026
ae7c1d5
Observe the SOL pid file once for the lease verdict.
Sep 18, 2026
57552d1
Wait for the host-capture terminal, stamp it now, and consume IpmiBoo…
Sep 18, 2026
39b6297
Read the boot-terminal oracle from the census, EDAC, and Ubuntu media…
Sep 18, 2026
e73145f
Project IpmiBootSelection through ChassisBootDevWithOptions and apply…
Sep 18, 2026
5901fb4
Drop unused IpmiBootSelection.options_wire.
Sep 18, 2026
42d6140
Put the IpmiBootSelection data rows on one line.
Sep 18, 2026
85c45f8
Require a section-bound EDAC/nproc host-capture verdict before comple…
Sep 18, 2026
b9a7ca8
Escape curl's %{http_code} in MegaRAC GetSession so the argv is not a…
Sep 18, 2026
a379908
Anchor the host-capture envelope to the marker line and derive the IP…
Sep 18, 2026
d8e723e
Derive ipmitool parameter-5 readback words from the boot-selection axes.
Sep 18, 2026
20862c4
Merge origin/main into approval-loop-d (0A/0B landed); keep the Unit …
Sep 19, 2026
bd804b6
MegaRAC session release is a present->absent transition: observe GET …
Sep 19, 2026
f5974a5
MegaRAC release re-observation uses a route this firmware serves: GET…
Sep 19, 2026
3e535a3
One claim/settle pair for every authorization slot (gunbc.authorizati…
Sep 19, 2026
b2ef96b
Merge origin/main into approval-loop-d; ci_spec target roster is the …
Sep 19, 2026
0daa22a
namespace: delete the consumed enrollment_id_for_code admission (#116…
Sep 19, 2026
dda6e77
Merge origin/main into approval-loop-d (0C/0D/#11620 landed); fleet_c…
Sep 20, 2026
ce0b794
scoped_authorization: the claim slot key carries its scope again ('sc…
Sep 20, 2026
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
69 changes: 68 additions & 1 deletion .github/workflows/fleet-converge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ on:
mode:
description: plan shows membership hunks; apply executes a prior plan artifact; org_actions_observe validates the org credential and refuses on runner-group drift; org_runner_roster_observe reads the organization's self-hosted runner registrations to exhaustion under the same token and refuses unless the roster is complete; app_control_plane_observe reads the gunbai-ci App registration and webhook config with a short-lived App JWT and refuses on hook-config drift; microvm_host_converge installs the cited Firecracker release on the selected host and refuses the kvm grant by name; guest_image_observe reports the micro-VM base artifacts and their measured digests on the selected host and builds nothing; guest_image_converge builds the runner guest image there; microvm_boot_probe boots that image and reads its serial console; spark_grants and spark_bootstrap prepare the selected Spark; spark_serving_apply applies the promoted fabric groups' vLLM units over the password session, workers before heads; spark_runtime_image_probe pulls the pinned runtime image on the selected Spark and reads its capabilities from inside its own digest, changing nothing; host_reset_return drives a rostered reset subject through its controller and measures the return FROM A PEER, and takes reset_observer rather than host because the observer must not be the subject; runner_host_file_observe reads the runner teardown drop-in, the needrestart deferral and the loaded teardown on the selected host as its administrator and writes nothing; runner_host_file_converge writes whichever of those two files differs, reloads systemd only if the drop-in changed, and refuses unless the manager then reports the declared teardown
required: true
options: [plan, launch_environment_plan, allocation_store_plan, apply, org_actions_observe, app_control_plane_observe, microvm_host_converge, guest_image_observe, guest_image_converge, microvm_boot_probe, spark_grants, spark_bootstrap, spark_serving_apply, spark_runtime_image_probe, dashboard_deploy, rlm_launch_deployment_receipt, host_reset_return, runner_host_file_observe, runner_host_file_converge, runner_password_session_tool_converge, r2_mint_preflight, r2_object_write_mint, org_runner_roster_observe]
options: [plan, launch_environment_plan, allocation_store_plan, apply, org_actions_observe, app_control_plane_observe, microvm_host_converge, guest_image_observe, guest_image_converge, microvm_boot_probe, spark_grants, spark_bootstrap, spark_serving_apply, spark_runtime_image_probe, dashboard_deploy, rlm_launch_deployment_receipt, host_reset_return, runner_host_file_observe, runner_host_file_converge, runner_password_session_tool_converge, r2_mint_preflight, r2_object_write_mint, org_runner_roster_observe, approval_keyring_converge, mtcollins1_boot]
type: choice
target:
description: "Spark target host for the spark_* modes, naming the administrator credential the run materializes (any rostered Spark: srv5, srv6, srv7, srv8, srv9, srv10, srv11, srv12); apply reads the subject off the plan artifact"
Expand Down Expand Up @@ -494,6 +494,73 @@ jobs:
retention-days: 30
if: always() && github.event.inputs.mode == 'r2_object_write_mint'
timeout-minutes: 10
- name: Materialize approval MAC keys on srv1 (SM pinned -> sha256 prefix -> /etc/gunbc-roadmap 0640)
id: approval_keyring_converge
run: |
# 🟡 dissolve-on: gunbc_ci_approval_keyring_converge_invoke - orch-emitted foreign-executor (GitHub Actions run:) credential runner: WIF access token by env, two PINNED secret versions (capability and submission MAC keys) fetched over curl, 0600 files under RUNNER_TEMP with a trap armed BEFORE the credentials touch disk. The pipeline steps are modeled (gunbc_ci_approval_keyring_converge_prelude) but the runner transport itself remains hand-shell; DISSOLVES WHEN bash-emit (#5828 / ROADMAP 6-shell-slice0 / shell-to-intent Phase 2) realizes the credential runner through orchestration emit or typed host_effect_apply without a medium-as-string concat scaffold
set -euo pipefail
umask 077
HDR_FILE="$RUNNER_TEMP/approval-keyring-auth-header"
RESP_FILE="$RUNNER_TEMP/approval-keyring-sm.json"
CAP_FILE="$RUNNER_TEMP/approval-capability-mac-key"
SUB_FILE="$RUNNER_TEMP/approval-submission-mac-key"
trap 'rm -f "$HDR_FILE" "$RESP_FILE" "$CAP_FILE" "$SUB_FILE"' EXIT
printf 'Authorization: Bearer %s\n' "$WIF_ACCESS_TOKEN" > "$HDR_FILE"
DEST_FILE="$CAP_FILE"; curl -sSf -o "$RESP_FILE" -H @"$HDR_FILE" "https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/approval-capability-mac-key/versions/1:access"; python3 -c 'import sys,json,base64; sys.stdout.buffer.write(base64.b64decode(json.load(open(sys.argv[1]))["payload"]["data"]))' "$RESP_FILE" > "$DEST_FILE"
chmod 600 "$CAP_FILE"
DEST_FILE="$SUB_FILE"; curl -sSf -o "$RESP_FILE" -H @"$HDR_FILE" "https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/approval-submission-mac-key/versions/1:access"; python3 -c 'import sys,json,base64; sys.stdout.buffer.write(base64.b64decode(json.load(open(sys.argv[1]))["payload"]["data"]))' "$RESP_FILE" > "$DEST_FILE"
chmod 600 "$SUB_FILE"
export GUNBC_APPROVAL_CAPABILITY_MAC_KEY_FILE="$CAP_FILE"
export GUNBC_APPROVAL_SUBMISSION_MAC_KEY_FILE="$SUB_FILE"
ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
"$ROOT/target/release/gunbc" run --source-root "$ROOT/dag" --source-root "$ROOT/src/v2" --entry dag/gunbc/auth/approval_keyring_converge.dag --function approval_keyring_converge_wet
env:
WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }}
FLEET_CONVERGE_EXPECTED_HOST: ${{ github.event.inputs.host }}
if: github.event.inputs.mode == 'approval_keyring_converge'
timeout-minutes: 5
- name: Mt. Collins unit 1 approval-gated diskless boot (file request, poll, grant, attach, CD handoff)
id: mtcollins1_boot
run: |
# 🟡 dissolve-on: gunbc_ci_mtcollins1_boot_invoke - orch-emitted foreign-executor (GitHub Actions run:) credential runner: WIF access token by env, PINNED BMC and submission-MAC secret versions fetched over curl, 0600 files under RUNNER_TEMP with a trap armed BEFORE the credentials touch disk. SOL hold is gunbc.machine_intake.sol_hold ActivateHeld (its own scaffold). The pipeline steps are modeled (gunbc_ci_mtcollins1_boot_credential_prelude) but the runner transport itself remains hand-shell; DISSOLVES WHEN bash-emit (#5828 / ROADMAP 6-shell-slice0 / shell-to-intent Phase 2) realizes the credential runner through orchestration emit or typed host_effect_apply without a medium-as-string concat scaffold
set -euo pipefail
umask 077
HDR_FILE="$RUNNER_TEMP/mtcollins1-boot-auth-header"
RESP_FILE="$RUNNER_TEMP/mtcollins1-boot-sm.json"
DEST_FILE="$RUNNER_TEMP/mtcollins1-bmc-credential"
SUB_FILE="$RUNNER_TEMP/approval-submission-mac-key"
SOL_PID_FILE="$RUNNER_TEMP/mtcollins1-sol.pid"
SOL_OUT="$GITHUB_WORKSPACE/artifacts/mtcollins1-boot-sol.capture"
trap 'if [ -f "$SOL_PID_FILE" ]; then kill "$(cat "$SOL_PID_FILE")" 2>/dev/null || true; fi; rm -f "$HDR_FILE" "$RESP_FILE" "$DEST_FILE" "$SUB_FILE" "$SOL_PID_FILE"' EXIT
printf 'Authorization: Bearer %s\n' "$WIF_ACCESS_TOKEN" > "$HDR_FILE"
curl -sSf -o "$RESP_FILE" -H @"$HDR_FILE" "https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/bmc-mtcollins1-gunbc/versions/2:access"; python3 -c 'import sys,json,base64; sys.stdout.buffer.write(base64.b64decode(json.load(open(sys.argv[1]))["payload"]["data"]))' "$RESP_FILE" > "$DEST_FILE"
chmod 600 "$DEST_FILE"
export GUNBC_HOST_RESET_BMC_CREDENTIAL_FILE="$DEST_FILE"
HOLD_BMC="$DEST_FILE"
DEST_FILE="$SUB_FILE"; curl -sSf -o "$RESP_FILE" -H @"$HDR_FILE" "https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/approval-submission-mac-key/versions/1:access"; python3 -c 'import sys,json,base64; sys.stdout.buffer.write(base64.b64decode(json.load(open(sys.argv[1]))["payload"]["data"]))' "$RESP_FILE" > "$DEST_FILE"; DEST_FILE="$HOLD_BMC"
chmod 600 "$SUB_FILE"
export GUNBC_APPROVAL_SUBMISSION_MAC_KEY_FILE="$SUB_FILE"
mkdir -p "$GITHUB_WORKSPACE/artifacts"
: > "$SOL_OUT"
export GUNBC_MTCOLLINS1_SOL_CAPTURE="$SOL_OUT"
export GUNBC_MTCOLLINS1_SOL_PID_FILE="$SOL_PID_FILE"
ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
"$ROOT/target/release/gunbc" run --source-root "$ROOT/dag" --source-root "$ROOT/src/v2" --entry dag/gunbc/machine_intake/mtcollins1_boot_run.dag --function mtcollins1_boot_wet
env:
WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }}
FLEET_CONVERGE_EXPECTED_HOST: ${{ github.event.inputs.host }}
if: github.event.inputs.mode == 'mtcollins1_boot'
timeout-minutes: 38
- name: Upload Mt. Collins unit 1 boot SOL capture and receipts
id: mtcollins1_boot_receipt_upload
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
with:
name: mtcollins1-boot-receipts
path: artifacts/mtcollins1-boot-*
if-no-files-found: warn
retention-days: 30
if: always() && github.event.inputs.mode == 'mtcollins1_boot'
timeout-minutes: 10
- name: "Runner micro-VM guest image: observe base artifacts and their digests"
id: guest_image_observe
run: |
Expand Down
20 changes: 20 additions & 0 deletions dag/extdeps/bmc/ipmi.dag
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,26 @@ service diagnostic.ipmi.Tool {
}
}

operation ChassisBootDevWithOptions {
input { bmc_host: NonEmptyStr, username: NonEmptyStr, password_file: NonEmptyStr, device: NonEmptyStr, options: NonEmptyStr }
output { stdout: String from "stdout", success: Bool from "exit_success" }
transport shell { argv: ["ipmitool", "-H", "{bmc_host}", "-I", "lanplus", "-U", "{username}", "-f", "{password_file}", "chassis", "bootdev", "{device}", "options={options}"] }
exit {
0 => Unit
nonzero => String "ipmitool chassis bootdev with options failed"
}
}

operation SolDeactivate {
input { bmc_host: NonEmptyStr, username: NonEmptyStr, password_file: NonEmptyStr }
output { stdout: String from "stdout", success: Bool from "exit_success" }
transport shell { argv: ["ipmitool", "-H", "{bmc_host}", "-I", "lanplus", "-U", "{username}", "-f", "{password_file}", "sol", "deactivate"] }
exit {
0 => Unit
nonzero => String "ipmitool sol deactivate failed"
}
}

operation ChassisBootParamGet {
input { bmc_host: NonEmptyStr, username: NonEmptyStr, password_file: NonEmptyStr, parameter: NonEmptyStr }
output { stdout: String from "stdout", success: Bool from "exit_success" }
Expand Down
143 changes: 143 additions & 0 deletions dag/extdeps/bmc/ipmi_boot_selection.dag
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
module extdeps.bmc.ipmi_boot_selection

import std.types { NonEmptyStr, String }
import extdeps.external_authority { ExternalAuthority }
import extdeps.uri { Uri, Https }
import extdeps.toolchain.types { Architecture, Aarch64 }

// THE CLI SURFACE IS ipmitool's BOOT-FLAG ENCODER, not the IPMI wire itself. The IPMI 2.0
// chassis boot parameter has separate valid, persistence and boot-type bits; ipmitool exposes
// them through the final `options=...` argv word. The exact implementation authority is the
// upstream source whose option table names `valid`, `persistent` and `efiboot` and whose
// set-bootdev path sets the valid bit before writing parameter 5.
data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority {
uri: Uri {
scheme: Https
locator: "github.com/ipmitool/ipmitool/blob/master/lib/ipmi_chassis.c"
}
}

type IpmiBootDevice
= IpmiBootPxe
| IpmiBootCdrom

type IpmiFirmwareBootMode
= IpmiBootLegacy
| IpmiBootEfi

type IpmiBootPersistence
= IpmiBootOnce
| IpmiBootPersistent

// A VALID BOOT SELECTION, NOT THREE INDEPENDENT SCALARS. This record is sole_constructor so a
// caller cannot place Aarch64 beside Legacy or manufacture an opaque options word. The only
// constructor currently exported is aarch64_uefi_once_boot_selection: its signature has no mode
// or persistence parameter, hence `(Aarch64, Legacy, Cdrom)` and persistent intake overrides
// have no source spelling at all. A future x86 legacy requirement adds a different constructor
// here; it does not widen this one with a Boolean or raw-string escape hatch.
//
// The wire and read-back spellings travel with the semantic axes because they are one CLI
// serialization. A caller selects `IpmiBootCdrom`; it never authors `cdrom`,
// an `options=` word, or one of the four strings used to adjudicate parameter 5.
// The chassis-bootdev device word is `ipmi_boot_device_label`; the options token is
// `ipmi_boot_selection_options_token`; parameter-5 readback words are
// `ipmi_boot_expected_*_rendering`. None of those strings is stored beside the axes.
type IpmiBootSelection sole_constructor {
architecture: Architecture
device: IpmiBootDevice
firmware_mode: IpmiFirmwareBootMode
persistence: IpmiBootPersistence
}

fn aarch64_uefi_once_boot_selection(device: IpmiBootDevice) -> IpmiBootSelection {
match device {
IpmiBootPxe => IpmiBootSelection {
architecture: Aarch64,
device: IpmiBootPxe,
firmware_mode: IpmiBootEfi,
persistence: IpmiBootOnce,
}
IpmiBootCdrom => IpmiBootSelection {
architecture: Aarch64,
device: IpmiBootCdrom,
firmware_mode: IpmiBootEfi,
persistence: IpmiBootOnce,
}
}
}

fn ipmi_boot_device_label(device: IpmiBootDevice) -> NonEmptyStr {
match device {
IpmiBootPxe => "pxe"
IpmiBootCdrom => "cdrom"
}
}

fn ipmi_boot_mode_label(mode: IpmiFirmwareBootMode) -> NonEmptyStr {
match mode {
IpmiBootLegacy => "legacy"
IpmiBootEfi => "efi"
}
}

fn ipmi_boot_persistence_label(persistence: IpmiBootPersistence) -> NonEmptyStr {
match persistence {
IpmiBootOnce => "once"
IpmiBootPersistent => "persistent"
}
}

fn ipmi_boot_selection_label(selection: IpmiBootSelection) -> NonEmptyStr {
join([
ipmi_boot_device_label(device: selection.device) as String,
"/",
ipmi_boot_mode_label(mode: selection.firmware_mode) as String,
"/",
ipmi_boot_persistence_label(persistence: selection.persistence) as String,
], "") as NonEmptyStr
}

fn ipmi_boot_selection_options_token(selection: IpmiBootSelection) -> NonEmptyStr {
match selection.firmware_mode {
IpmiBootEfi =>
match selection.persistence {
IpmiBootOnce => "efiboot"
IpmiBootPersistent => "efiboot,persistent"
}
IpmiBootLegacy =>
match selection.persistence {
IpmiBootOnce => "valid"
IpmiBootPersistent => "persistent"
}
}
}

data ipmi_boot_flag_valid_rendering: NonEmptyStr = "Boot Flag Valid"

fn ipmi_boot_expected_flag_rendering(selection: IpmiBootSelection) -> NonEmptyStr {
match selection.device {
IpmiBootPxe => ipmi_boot_flag_valid_rendering
IpmiBootCdrom => ipmi_boot_flag_valid_rendering
}
}

fn ipmi_boot_expected_device_rendering(selection: IpmiBootSelection) -> NonEmptyStr {
match selection.device {
IpmiBootPxe => "Force PXE"
IpmiBootCdrom => "Force Boot from CD/DVD"
}
}

fn ipmi_boot_expected_mode_rendering(selection: IpmiBootSelection) -> NonEmptyStr {
match selection.firmware_mode {
IpmiBootEfi => "BIOS EFI boot"
IpmiBootLegacy => "BIOS PC Compatible (legacy) boot"
}
}

fn ipmi_boot_expected_persistence_rendering(selection: IpmiBootSelection) -> NonEmptyStr {
match selection.persistence {
IpmiBootOnce => "Options apply to only next boot"
IpmiBootPersistent => "Options apply to all future boots"
}
}
33 changes: 23 additions & 10 deletions dag/extdeps/bmc/megarac.dag
Original file line number Diff line number Diff line change
Expand Up @@ -405,14 +405,14 @@ data megarac_ipmi_sel_layout: MegaRacEventLogLayout = MegaRacIpmiSelPayloadPrese
// per probe and closing none. Every operation sequence that opens a session owes a close on every
// exit, including refusals — an operation that failed still allocated the session that carried it.
//
// THE CREDENTIAL IS FORM-ENCODED IN THE SESSION CALL AND NOWHERE ELSE. It reaches argv there, which
// is a real weakness and is stated rather than hidden: curl offers no netrc equivalent for a
// form-encoded POST body, and the alternative spellings (--data @file) put the credential in a file
// whose lifetime this operation does not control. Every operation AFTER the session carries no
// credential at all — the cookie is the bearer — so the exposure is one call, not the whole flow.
// The next-rung repair is a transport that reads a form body from a file the caller materializes
// and removes, which is the same missing mechanism the removed ipmi UserSetPassword operation
// needs.
// THE CREDENTIAL IS READ FROM A CALLER FILE ON OpenSession AND NOWHERE ELSE. curl --data-urlencode
// password@FILE keeps the secret out of argv; every later call carries only the cookie. THE FILE IS
// THE PASSWORD BYTES EXACTLY: curl(1) documents name@file as URL-encoding the file's content
// INCLUDING any newline, so a file ending in \n posts password=...%0A and the controller refuses the
// login as a wrong password with no hint (review 68456). The caller's prelude writes the Secret
// Manager payload bytes verbatim (base64-decoded, no trailer), so the contract lives on the stored
// payload: it is minted without a trailing newline, and boot 22 (run 35447067113) is the executed
// positive control that the stored bytes log in.
//
// StartMedia's json_body MUST CARRY image_redirection AND ITS ABSENCE IS OPAQUE. The contract note
// on the operation row above records that omitting it makes the firmware refuse with codes
Expand All @@ -423,17 +423,30 @@ data megarac_ipmi_sel_layout: MegaRacEventLogLayout = MegaRacIpmiSelPayloadPrese
service megarac.Media {

operation OpenSession {
input { bmc_host: NonEmptyStr, username: NonEmptyStr, password: NonEmptyStr, cookie_jar: NonEmptyStr }
input { bmc_host: NonEmptyStr, username: NonEmptyStr, password_file: NonEmptyStr, cookie_jar: NonEmptyStr }
output { body: String from "stdout", success: Bool from "exit_success" }
transport shell {
argv: ["curl", "--fail-with-body", "-sS", "-k", "-c", "{cookie_jar}", "-X", "POST", "-d", "username={username}", "-d", "password={password}", "https://{bmc_host}/api/session"]
argv: ["curl", "--fail-with-body", "-sS", "-k", "-c", "{cookie_jar}", "-X", "POST", "--data-urlencode", "username={username}", "--data-urlencode", "password@{password_file}", "https://{bmc_host}/api/session"]
}
exit {
0 => Unit
nonzero => String "megarac session POST failed"
}
}

operation ProbeSessionOnMediaRoute {
input { bmc_host: NonEmptyStr, cookie_jar: NonEmptyStr, csrf_token: NonEmptyStr }
output { body: String from "stdout", success: Bool from "exit_success" }
readonly
transport shell {
argv: ["curl", "--fail-with-body", "-sS", "-k", "-b", "{cookie_jar}", "-H", "X-CSRFTOKEN: {csrf_token}", "-w", "\\n%\{http_code}", "https://{bmc_host}/api/settings/media/remote/configurations"]
}
exit {
0 => Unit
nonzero => String "megarac session probe on the media route failed"
}
}

operation GetMediaGeneral {
input { bmc_host: NonEmptyStr, cookie_jar: NonEmptyStr, csrf_token: NonEmptyStr }
output { body: String from "stdout", success: Bool from "exit_success" }
Expand Down
Loading