Skip to content

GCP one-token bootstrap of the keyless BMC assimilator (API-enablement + SA + IAM + WIF as .dag, proven live) - #5712

Merged
briansrls merged 36 commits into
mainfrom
session/neat-boar-71
Jun 24, 2026
Merged

briansrls merged 36 commits into
mainfrom
session/neat-boar-71

Conversation

@gunbai-bot

@gunbai-bot gunbai-bot Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

What

Collapses the entire operator-fenced WIF runbook (docs/runbooks/bmc-assimilator-wif-setup.md — six manual gcloud blocks) into one admin-token .dag orchestration over the proven v1 REST executor. The only manual input is the initial admin access token, acquired via the operator's existing gcloud login (shell.GCloud.AuthPrintAccessToken — the "one button").

Zero host/.rs changes — the REST executor already serializes list/map/record JSON bodies; this is pure .dag modeling.

New surfaces

File Adds
extdeps/cloud/gcp/serviceusage.dag ServiceUsage.BatchEnableServices (API enablement)
extdeps/cloud/gcp/iam_admin.dag IamAdmin.{CreateServiceAccount, CreateWorkloadIdentityPool, CreateWorkloadIdentityPoolProvider}
extdeps/cloud/gcp/secret_manager.dag SecretManager.SetSecretIamPolicy (resource-level least-priv)
extdeps/cloud/gcp/gcp.dag shared GcpOperation; GcpService.{GcpIamCredentials, GcpServiceUsage}; gcp_service_api_id
gunbc/assimilate/bmc_bootstrap_provision.dag orchestration: enable → SA → bind → pool → provider

API enablement is a dependency of usage

bmc_required_gcp_apis is derived from bmc_assimilate_service_deps (the GcpService set the assimilate path uses) via gcp_service_api_id — single authority on the GcpService enum + wire label. No hand-typed .googleapis.com strings.

Proven live (2026-06-24, project gunbai-secrets)

All 5 ops dispatched through the v1 REST executor with one admin token:

  • batchEnable → operation returned (list body serialized as JSON array, incl. the derived list)
  • CreateServiceAccount → bmc-assimilator@… minted (nested body)
  • SetSecretIamPolicy → etag returned; both least-priv roles bound on bmc-srv3-admin only (List<GcpBinding> records → array of objects)
  • CreateWorkloadIdentityPool + CreateWorkloadIdentityPoolProvider → provider ACTIVE with correct attributeMapping/condition/issuer (Map → object)

Known first-run race (documented): a freshly-created SA takes a few seconds to propagate before the IAM binding can reference it — re-run once and it converges (a 409-tolerant single-pass converge is the noted follow-up).

Witness (green by execution)

dsl/test/claim/bmc_bootstrap_provision_witness_test.dag: least-privilege (exactly 2 roles, SA-scoped), broad-role exclusion, closed API set with a discriminating negative (compute.googleapis.com rejected), and the dependency-of-usage derivation.

🤖 Generated with Claude Code

Brian Searls and others added 30 commits June 22, 2026 20:58
…nly validation

Models the onboarding of the operator's new Altra server (BMC 192.168.1.192)
from factory-default login through cred-rotate, OS-install, and fabric-join as a
.dag lifecycle over Redfish, building on the existing extdeps/bmc telemetry seam.

- extdeps/bmc/types.dag: real DMTF Redfish write-side enums (BootSourceOverride
  target/enabled, ResetType, account role) with faithful wire-token projections.
- extdeps/bmc/http.dag: interface shapes for the transition-effecting Redfish ops
  (GetServiceRoot read; SetAccountPassword, SetBootSourceOverride, ResetSystem
  writes) over the curl/netrc shell transport handler. Secrets ride a runtime
  request_body_file, never argv or the repo.
- gunbc/bmc_onboarding.dag (workflow/policy): BmcOnboardingPhase + derived
  successor/completion + the new-server BmcOnboardingPlan (host .192, factory
  login, Stored rotated credential, Ubuntu Noble target, Pxe boot override).
- gunbc/tools/bmc_onboard.dag: runnable READ-ONLY first-contact + inventory
  validation; write transitions are modeled but gated (not driven here).
- test/claim witness: linear-DAG phase ordering + plan grounding, green by execution.

Grounded against the live BMC at 192.168.1.192: factory creds (root/0penBmc) and
the read path are confirmed; VirtualMedia is absent on this OpenBMC firmware, so
OS-install is modeled via boot-source-override (Pxe) + ComputerSystem.Reset.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
bmc_onboarding_next_phase is now the sole authority for the linear successor
relation; the standalone bmc_onboarding_phase_order list duplicated it. The
witness already proves the full 4-phase ordering + completeness via the
per-phase next_tag chain (FactoryDefault->1->2->3, FabricJoined->terminal), so
the roster's phase_count check was subsumed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… name, §5)

The tool only performs the read-only FactoryDefault validation (GetServiceRoot +
GetSystem); it does not drive cred-rotate/OS-install/fabric-join. Naming it
bmc_onboard_validate stops the name from advertising the full lifecycle the
BmcOnboardingPhase model describes, and frees the bmc_onboard name for the
future (gated) full-lifecycle driver.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ate)

The predicate had one caller (the witness) and the witness's next_tag chain
already proves completion (FabricJoined -> -1 = terminal; others -> 1/2/3).
Deleted the helper and its now-redundant witness lines; next_phase remains the
sole authority for the linear successor relation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…le-verified, live-gated)

Assemble the credential-rotation leg of orchestration C over new_altra_onboarding_plan,
now that Lane B (#5634) landed the Redfish auth-as-Secret seam on main:
materialize the netrc + PATCH body via Filesystem.Write (executable file effect),
SetAccountPassword (Redfish write), then reauth with the new credential to VERIFY
the rotation took — fail-closed if rejected. The minted Secret is declassified to
String exactly once, explicitly (the Secret type forbids accidental exposure).

Verified by execution: gunbc compile --source-root dsl => 464 modules, 471 files,
0 diagnostics — the legs typecheck and compose. LIVE execution is operator-fenced
(first destructive write); live-correctness of account_id/body shape is confirmed
only by the gated run against .192, not this typecheck. Not a *_test.dag, so it does
NOT auto-enroll as a floor witness (no false CI-coverage claim).

§5 debt (named): the netrc + body files transiently hold the credential on disk at
default umask with no post-run unlink; dissolution = mode-0600 file write + unlink leg.

gen+store leg (entropy mint #5633 -> base64 -> GCP store) wires in once #5633 lands;
os-install leg pends Lane E (#5638).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e→rotate→reauth→os-install)

Assemble the full lifecycle as one .dag workflow over new_altra_onboarding_plan +
srv3_os_install_plan, now that all four lanes (entropy #5633, auth-Secret #5634,
OS-install #5638) landed on main:

  acquire     — GetServiceRoot + factory-login GetSystem (read; proven live earlier)
  gen         — mint a credential from OS entropy (extdeps.entropy Urandom), FAIL-CLOSED
                on the Optional (never a fabricated/empty credential — dissolves the
                witness scaffold's empty-string arm per cool-lynx's dissolution trigger)
  store       — base64 of the same octets -> GCP AddVersion (durability) under the
                plan's secret id; token via gcloud
  rotate+reauth — Filesystem.Write netrc + PATCH body, SetAccountPassword, reauth with the
                NEW credential to verify the rotation took (fail-closed)
  os-install  — re-materialize netrc with the NEW credential (factory netrc is now stale),
                SetBootSourceOverride(Pxe,Once) + ResetSystem(ForceRestart) via the Lane-E
                wire fns to boot srv3 into the PXE/autoinstall path

Legs chain on ProcessExit so any failure short-circuits. The minted Secret is
declassified to String exactly once, explicitly (the type forbids accidental leak).

Verified by execution: gunbc compile --source-root dsl => 480 modules, 488 files,
0 diagnostics. The pure wire-shape builders (netrc line, Redfish PATCH/POST JSON
bodies, GCP secret name) have by-execution witnesses — all 5 green via --claim-run.
The live Redfish/GCP/entropy legs are OPERATOR-FENCED (destructive); their live
correctness is confirmed only by the gated run against .192, not this typecheck.

§5 debt (named): netrc/body files transiently hold the credential on disk at default
umask with no unlink; dissolution = mode-0600 file write + unlink leg.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…cred-only entry point

Address review #32107:
- bmc_credential_bytes: Int gets a 🟡 marker riding extdeps.entropy's
  entropy_count_bytes_unit_debt (same bytesize-argv-interpolation dissolution) —
  no longer an unmarked *_bytes-on-Int.
- the concat-built Redfish PATCH/POST bodies get a 🟡 dissolve-on marker (safe for
  the current base64url-credential + enum-wire call sites, which the body-shape
  witnesses pin; dissolution = a structured JSON-object encoder authority).

Also adds bmc_assimilate_srv3_credential — a credential-only entry point (acquire ->
gen -> store(read-back gated) -> rotate -> reauth, STOPPING before os-install) so the
live BMC credential assimilation can run while no PXE/install server exists yet
(running the full bmc_onboard_srv3 would reset srv3 into a dead PXE boot). Refactors
the shared store+rotate into bmc_store_and_rotate (no duplication).

Compile clean (488 files, 0 diagnostics); body-shape witnesses green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The auth_input fix's call-site edit split the let-binding across two lines; rustfmt
wants it on one (fits in width). cargo fmt --all --check now clean — this was the
rust_tests CI failure on 311ff38 (fmt gate), not a logic issue.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…§2, review #32137)

mint_credential_octets base64_decode'd Urandom.octets_b64 to List<UInt8>, then
credential_from_octets base64_encode'd it straight back — base64_encode∘base64_decode
is identity, so the octets intermediate (and the Optional failure mode that could only
trip on a base64_decode bug, never on real Urandom output) bought nothing. Collapse to
one fn: mint_bmc_credential() = Urandom.ReadBytes(count).octets_b64 as Secret. The
credential IS the base64 entropy string directly — same string set on the BMC, stored
as the GCP payload, and compared in the read-back gate (identity preserved; the BMC
password is byte-for-byte what it was). Drops the std.encoding + std.integer{UInt8}
imports and the unused ExitFailure. Fail-closed now lives at the Urandom service call
(nonzero exit raises). Compile 488/0, body witnesses green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Brian Searls and others added 6 commits June 24, 2026 00:08
…t + SA + IAM + WIF modeled as .dag, proven live

Collapses the entire operator-fenced WIF runbook (every manual `gcloud` block) into a
single admin-token .dag orchestration over the proven v1 REST executor. The only manual
input is the initial admin access token, acquired via the operator's existing gcloud login
(shell.GCloud.AuthPrintAccessToken). No host/.rs changes — the REST executor already
serializes list/map/record JSON bodies.

New surfaces (all extdeps interface shapes + one workflow orchestration):
- extdeps/cloud/gcp/serviceusage.dag — ServiceUsage.BatchEnableServices
- extdeps/cloud/gcp/iam_admin.dag — IamAdmin.{CreateServiceAccount,CreateWorkloadIdentityPool,CreateWorkloadIdentityPoolProvider}
- extdeps/cloud/gcp/secret_manager.dag — SecretManager.SetSecretIamPolicy (resource-level least-priv)
- gcp.dag — shared GcpOperation + GcpService.{GcpIamCredentials,GcpServiceUsage} + gcp_service_api_id
- gunbc/assimilate/bmc_bootstrap_provision.dag — enable -> SA -> bind -> pool -> provider

API enablement is a DEPENDENCY OF USAGE, not a hand-typed list: bmc_required_gcp_apis is
derived from bmc_assimilate_service_deps (the GcpService set the path uses) via
gcp_service_api_id, single-authority on the GcpService enum.

PROVEN LIVE 2026-06-24 against gunbai-secrets: all 5 ops dispatched; SA minted, both
least-priv roles bound on bmc-srv3-admin only, WIF provider ACTIVE. Witness
bmc_bootstrap_provision_witness_test.dag green by execution (least-priv + closed-API-set +
derivation, with discriminating negatives). §3 single authority preserved (federation facts).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@briansrls
briansrls merged commit 49ddcbc into main Jun 24, 2026
2 checks passed
@briansrls
briansrls deleted the session/neat-boar-71 branch June 24, 2026 13:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant