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
30 changes: 18 additions & 12 deletions .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; app_key_version_verify reads the gunbai-ci App private key at the EXACT Secret Manager version named by app_key_version, mints an installation token with it, and refuses unless GitHub accepts it and the key's rotation deadline has not passed -- no add, disable or destroy; microvm_host_converge installs the cited Firecracker release on the selected host and reads the kvm grant back, refusing by name when the grant has not landed (the grant itself is applied by the full-host apply spine); 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' pair vLLM units over the password session, workers before heads; spark_native_serving_apply relaunches group B's native four-rank arm as ONE BOUNDED TRANSACTION -- an all-host preflight that mutates nothing and refuses on any unresolved artifact from an earlier run, the incumbent UNIT preserved (plus a diagnostic inspect of the incumbent container -- the container itself cannot outlive its unit, because these run --rm in the foreground under systemd), the head applied BEFORE the workers so every rank joins the head's new rendezvous store, then a readback of the COMPLETE realization through each container's own inspect -- every field the create spec states, the effective --node-rank off that container's argv, and NCCL's typed transport verdict on that incarnation -- plus the head's front door answering with this arm's served model, and then commit or a rollback of the whole arm -- and it is a separate mode because it reloads a 328 GB arm and nobody should reach it by asking for the pair units; 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, microvm_network_observe, guest_image_observe, guest_image_converge, microvm_boot_probe, spark_grants, spark_bootstrap, spark_serving_apply, spark_native_serving_apply, spark_runtime_image_probe, dashboard_deploy, approval_broker_dark_install, rlm_launch_deployment_receipt, host_reset_return, runner_host_file_observe, runner_host_file_converge, site_pxe_edge_observe, site_pxe_edge_converge, runner_password_session_tool_converge, r2_mint_preflight, r2_object_write_mint, org_runner_roster_observe, approval_keyring_converge, mtcollins1_boot, mtcollins1_fan_observe, mtcollins1_census_image_publish, microvm_controller_app_key_converge, app_key_version_verify, r2_bucket_ensure, r2_bucket_admin_mint, fabric_writer_identity_observe]
options: [plan, launch_environment_plan, allocation_store_plan, apply, org_actions_observe, app_control_plane_observe, microvm_host_converge, microvm_network_observe, guest_image_observe, guest_image_converge, microvm_boot_probe, spark_grants, spark_bootstrap, spark_serving_apply, spark_native_serving_apply, spark_runtime_image_probe, dashboard_deploy, approval_broker_dark_install, rlm_launch_deployment_receipt, host_reset_return, runner_host_file_observe, runner_host_file_converge, site_pxe_edge_observe, site_pxe_edge_converge, runner_password_session_tool_converge, r2_mint_preflight, r2_object_write_mint, org_runner_roster_observe, approval_keyring_converge, mtcollins1_boot, mtcollins1_fan_observe, mtcollins1_census_image_publish, host_credential_custody_converge, app_key_version_verify, r2_bucket_ensure, r2_bucket_admin_mint, fabric_writer_identity_observe]
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 All @@ -25,6 +25,11 @@ on:
required: false
options: [srv1, srv2, srv3, srv4]
type: choice
credential:
description: "host_credential_custody_converge only: which rostered custody row to deliver (gunbc.host_credential_custody_converge). A closed choice, never a free-text secret name: each option carries its own SecretRef, path, owner, group, mode and directory, and a row scoped to one host refuses any other"
required: false
options: [controller_app_key, approval_ntfy_publisher_token]
type: choice
app_key_version:
description: "app_key_version_verify only: the Secret Manager version NUMBER of ci-github-app-private-key to verify. The alias latest is refused -- it names whatever is newest when it resolves, so a mint through it proves nothing about a specific key"
required: false
Expand Down Expand Up @@ -231,7 +236,7 @@ jobs:
echo "fleet-key: agent loaded (identity fleet-automation@gunbc; secret versions/1 pinned; fingerprint verified against modeled authority; key file wiped)"
env:
WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }}
if: github.event.inputs.mode == 'plan' || github.event.inputs.mode == 'launch_environment_plan' || github.event.inputs.mode == 'allocation_store_plan' || github.event.inputs.mode == 'apply' || github.event.inputs.mode == 'app_control_plane_observe' || github.event.inputs.mode == 'microvm_host_converge' || github.event.inputs.mode == 'guest_image_observe' || github.event.inputs.mode == 'guest_image_converge' || github.event.inputs.mode == 'microvm_boot_probe' || github.event.inputs.mode == 'spark_grants' || github.event.inputs.mode == 'spark_bootstrap' || github.event.inputs.mode == 'spark_serving_apply' || github.event.inputs.mode == 'spark_native_serving_apply' || github.event.inputs.mode == 'spark_runtime_image_probe' || github.event.inputs.mode == 'dashboard_deploy' || github.event.inputs.mode == 'approval_broker_dark_install' || github.event.inputs.mode == 'rlm_launch_deployment_receipt' || github.event.inputs.mode == 'host_reset_return' || github.event.inputs.mode == 'runner_host_file_observe' || github.event.inputs.mode == 'runner_host_file_converge' || github.event.inputs.mode == 'site_pxe_edge_observe' || github.event.inputs.mode == 'site_pxe_edge_converge' || github.event.inputs.mode == 'runner_password_session_tool_converge' || github.event.inputs.mode == 'r2_mint_preflight' || github.event.inputs.mode == 'r2_object_write_mint' || github.event.inputs.mode == 'approval_keyring_converge' || github.event.inputs.mode == 'mtcollins1_boot' || github.event.inputs.mode == 'microvm_controller_app_key_converge'
if: github.event.inputs.mode == 'plan' || github.event.inputs.mode == 'launch_environment_plan' || github.event.inputs.mode == 'allocation_store_plan' || github.event.inputs.mode == 'apply' || github.event.inputs.mode == 'app_control_plane_observe' || github.event.inputs.mode == 'microvm_host_converge' || github.event.inputs.mode == 'guest_image_observe' || github.event.inputs.mode == 'guest_image_converge' || github.event.inputs.mode == 'microvm_boot_probe' || github.event.inputs.mode == 'spark_grants' || github.event.inputs.mode == 'spark_bootstrap' || github.event.inputs.mode == 'spark_serving_apply' || github.event.inputs.mode == 'spark_native_serving_apply' || github.event.inputs.mode == 'spark_runtime_image_probe' || github.event.inputs.mode == 'dashboard_deploy' || github.event.inputs.mode == 'approval_broker_dark_install' || github.event.inputs.mode == 'rlm_launch_deployment_receipt' || github.event.inputs.mode == 'host_reset_return' || github.event.inputs.mode == 'runner_host_file_observe' || github.event.inputs.mode == 'runner_host_file_converge' || github.event.inputs.mode == 'site_pxe_edge_observe' || github.event.inputs.mode == 'site_pxe_edge_converge' || github.event.inputs.mode == 'runner_password_session_tool_converge' || github.event.inputs.mode == 'r2_mint_preflight' || github.event.inputs.mode == 'r2_object_write_mint' || github.event.inputs.mode == 'approval_keyring_converge' || github.event.inputs.mode == 'mtcollins1_boot' || github.event.inputs.mode == 'host_credential_custody_converge'
timeout-minutes: 5
- name: Fleet converge plan (membership_reconcile → artifact)
id: plan
Expand Down Expand Up @@ -1016,26 +1021,27 @@ jobs:
retention-days: 30
if: always() && (github.event.inputs.mode == 'runner_password_session_tool_converge')
timeout-minutes: 10
- name: "MicroVM controller App key: deliver root:root 0400 from Secret Manager, read owner, mode, bytes and staging back"
id: microvm_controller_app_key_converge
- name: "Host credential custody: deliver the chosen rostered credential from Secret Manager at its row's owner, group and mode, read owner, mode, directory, bytes and staging back"
id: host_credential_custody_converge
run: |
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/runner/microvm_controller_app_key_converge.dag --function microvm_controller_app_key_converge_ci_wet
cat "$ROOT/target/microvm-controller-app-key-converge-receipt.txt"
"$ROOT/target/release/gunbc" run --source-root "$ROOT/dag" --source-root "$ROOT/src/v2" --entry dag/gunbc/fleet/host_credential_custody_converge.dag --function host_credential_custody_converge_ci_wet
cat "$ROOT/target/host-credential-custody-converge-receipt.txt"
env:
FLEET_CONVERGE_EXPECTED_HOST: ${{ github.event.inputs.host }}
FLEET_CONVERGE_CUSTODY_CREDENTIAL: ${{ github.event.inputs.credential }}
WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }}
if: github.event.inputs.mode == 'microvm_controller_app_key_converge'
if: github.event.inputs.mode == 'host_credential_custody_converge'
timeout-minutes: 15
- name: Upload microVM controller App key custody receipt
id: microvm_controller_app_key_receipt_upload
- name: Upload host credential custody receipt
id: host_credential_custody_receipt_upload
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
with:
name: microvm-controller-app-key-converge-receipt
path: target/microvm-controller-app-key-converge-receipt.txt
name: host-credential-custody-converge-receipt
path: target/host-credential-custody-converge-receipt.txt
if-no-files-found: error
retention-days: 30
if: always() && (github.event.inputs.mode == 'microvm_controller_app_key_converge')
if: always() && (github.event.inputs.mode == 'host_credential_custody_converge')
timeout-minutes: 10
- name: Upload runner host file converge receipt
id: runner_host_file_receipt_upload
Expand Down
26 changes: 21 additions & 5 deletions dag/extdeps/posix/identity.dag
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module extdeps.posix.identity

import std.types { String, Int, Bool }
import std.types { String, NonEmptyStr, Int, Bool }
import extdeps.external_authority { ExternalAuthority, ExternalModelScope, ExternalSubjectRef }
import std.decl_ref { DeclarationRef, WholeDeclaration }
import extdeps.uri { Uri, Https }
Expand Down Expand Up @@ -107,9 +107,25 @@ fn posix_group_id_text(group: PosixGroupId) -> String {
integer_nat_to_decimal_string(value: group.value)
}

// THE ONE SPELLING OF chown(1)'s OWNER[:GROUP] SEPARATOR. Both forms below -- numeric ids and names
// -- render through this, so the grammar lives here once whichever form a caller holds.
fn posix_owner_pair_text(user: String, group: String) -> String {
concat(user, concat(":", group))
}

fn posix_owner_spec_text(spec: PosixOwnerSpec) -> String {
concat(
posix_user_id_text(user: spec.user),
concat(":", posix_group_id_text(group: spec.group)),
)
posix_owner_pair_text(user: posix_user_id_text(user: spec.user), group: posix_group_id_text(group: spec.group))
}

// THE NAME FORM OF THE SAME PAIR. chown(1) accepts OWNER[:GROUP] as names or as numeric ids, and the
// two are different facts (getpwnam maps one to the other on ONE host). A service account's ids are
// allocated per host, so a consumer that declares principals across hosts can only declare NAMES.
// It renders through posix_owner_pair_text like the numeric form.
type PosixOwnerNames {
user: NonEmptyStr
group: NonEmptyStr
}

fn posix_owner_names_text(names: PosixOwnerNames) -> String {
posix_owner_pair_text(user: names.user as String, group: names.group as String)
}
22 changes: 18 additions & 4 deletions dag/extdeps/tools/chown.dag
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import std.types { String, NonEmptyStr, List }
import extdeps.external_authority { ExternalAuthority, ExternalModelScope, ExternalSubjectRef }
import std.decl_ref { DeclarationRef, WholeDeclaration }
import extdeps.uri { Uri, Https }
import extdeps.posix.identity { PosixOwnerSpec, posix_owner_spec_text }
import extdeps.posix.identity { PosixOwnerSpec, posix_owner_spec_text, PosixOwnerNames, posix_owner_names_text }

data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority {
uri: Uri {
Expand All @@ -30,7 +30,21 @@ data chown_binary_path: NonEmptyStr = "/usr/bin/chown"
// THE OWNER ARGUMENT IS A TYPED PAIR, NOT A STRING THE CALLER ASSEMBLED. Callers used to pass an
// `owner: String` built as uid + ":" + gid at the call site, which put chown's argument grammar in
// the caller and left every caller free to get it wrong silently. Here the ":" form is produced
// once, from a PosixOwnerSpec that could only be constructed from decoded ids.
fn chown_argv(owner: PosixOwnerSpec, path: String) -> List<String> {
[chown_binary_path as String, posix_owner_spec_text(spec: owner), path]
// once (extdeps.posix.identity posix_owner_pair_text), from either a PosixOwnerSpec that could only
// be constructed from decoded ids, or PosixOwnerNames for principals declared by name because their
// ids are allocated per host. chown(1) accepts both; one argv serves both.
type ChownOwner
= ChownOwnerIds { spec: PosixOwnerSpec }
| ChownOwnerNames { names: PosixOwnerNames }

fn chown_owner_text(owner: ChownOwner) -> String {
match owner {
ChownOwnerIds { spec: s } => posix_owner_spec_text(spec: s)
ChownOwnerNames { names: n } => posix_owner_names_text(names: n)
}
}

fn chown_argv(owner: ChownOwner, path: String) -> List<String> {
[chown_binary_path as String, chown_owner_text(owner: owner), path]
}

5 changes: 4 additions & 1 deletion dag/gunbc/auth/approval_keyring_converge.dag
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import gunbc.auth.approval_request_submission {
import extdeps.filesystem.filesystem_io { Filesystem }
import extdeps.tools.sha256sum { sha256sum_file_digest_via_shell, Sha256FileDigest, Sha256FileDigestUnavailable }
import extdeps.sudo.elevation { sudo_elevate }
import extdeps.tools.chown { chown_argv, ChownOwnerNames }
import extdeps.posix.identity { PosixOwnerNames }
import gunbc.fleet_intent_network { operator_host_srv1 }
import gunbc.actions_run_binding { actions_variable_read, ActionsVariablePresent, ActionsVariableAbsent, fleet_converge_expected_host_env_name, actions_variable_trimmed }
import gunbc.auth.approval_keyring_pins {
Expand Down Expand Up @@ -143,8 +145,9 @@ fn claim_store_root_mkdir_argv() -> List<String> {
]
}

// Through extdeps.tools.chown chown_argv's name arm, so the OWNER:GROUP grammar is not spelled here.
fn keyring_chown_argv(path: NonEmptyStr) -> List<String> {
["chown", concat(fleet_posix_root_user.name as String, concat(":", fleet_posix_operator_user.name as String)), path as String]
chown_argv(owner: ChownOwnerNames { names: PosixOwnerNames { user: fleet_posix_root_user.name, group: fleet_posix_operator_user.name } }, path: path as String)
}

fn ntfy_group_admit_argv() -> List<String> {
Expand Down
Loading