Commit graph

4 commits

Author SHA256 Message Date
df5a2a6f88 feat(m3): bascule-shell exports DEFCON env vars from posture-current.json
bascule-shell loads /opt/substrate/posture/current.json
(BASCULE_POSTURE_FILE override) at session start and exports:

  BASCULE_DEFCON_LEVEL          numeric global level (1..5)
  BASCULE_POSTURE_LEVEL         alias (already shipped in M1)
  BASCULE_CAPABILITY_CEILING    CAP_NONE..CAP_GOVERN
  BASCULE_CEREMONY_REQUIRED     "0" / "1"
  BASCULE_MAX_SESSION_TTL       minutes, omitted when 0

Fail-soft: missing/malformed file degrades to peacetime defaults so
the shell exec path stays alive on misconfigured hosts.

The new posture.rs module is a tiny inline snapshot loader (60 LOC,
serde_json on top of an already-present dep) — bascule-oss does not
pull libgsh as a dep, so the JSON wire format produced by
substrate-operator is the contract. gsh and bascule-shell share that
contract, not Rust types.

Stacked on feat/m2-roles-export.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Claude Code <claude@guildhouse.dev>
2026-04-07 19:02:34 -04:00
56529626f6 feat(m2): bascule-shell exports BASCULE_ROLES for gsh's role check
bascule-shell::set_env now populates BASCULE_ROLES so gsh's
M2 role-aware classifier has something to match against.

Precedence:
  1. Caller-set BASCULE_ROLES wins (env var preserved as-is).
  2. Otherwise derive a default from auth_method:
       oidc-entra | oidc-cached | kerberos -> operator
       ssh-key                              -> apprentice
       _                                    -> apprentice

The auth-method fallback is intentionally minimal — bascule-oss
Identity has no real roles field, and proper role provisioning
(Entra group claims, SPIFFE workload roles) lands in M5. This
default at least populates the env var so M2's role-deny path
is exercised end-to-end on existing dev shells instead of
silently empty.

Stacked on feat/m1-session-sat.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Claude Code <claude@guildhouse.dev>
2026-04-07 17:52:50 -04:00
999c78ef4c feat(m1): bascule-shell composes a real SAT anchored on session_leaf
Replaces the opaque BASCULE_ATTESTATION_HASH (a SHA over a
"pcrN:val;ima:hash;" evidence string) with a real proto-canonical
SatBundle composed from the operator's identity + local platform
attestation, anchored on the L4 SessionClaim.

bascule-core::sat (NEW): pure composer module.
- build_session_claim(SessionInputs) -> SessionClaim builds the
  L4 leaf from {principal, auth_method, actor_type,
  identity_verified, platform_attested, software_verified,
  nonce_seed}, computes posture per SAT-SPEC-0002 §7, and
  populates the L1/L2/L3 binding fields with zero-padded
  placeholders until upstream producers exist.
- compose_local(SessionClaim) -> ComposedSat assembles the proto
  SatBundle via SatBundleBuilder. Hot path stays local per ADR D9
  (zero network); QM's gRPC ComposeSat is the warm-path surface.
- 7 unit tests cover layer/actor wiring, posture math at each
  evidence level, deterministic nonce, sat_hash uniqueness across
  principal changes.

bascule-shell: composes the SAT in main() right before execvp
of the inner shell — that's the OSS equivalent of an "Authenticated
-> ShellActive" transition (the OSS Bascule has no russh state
machine; it's a CLI wrapper). Exports the new env var surface:

  BASCULE_SAT_HASH            hex of proto sat_hash (canonical)
  BASCULE_SESSION_CLAIM_HASH  hex of L4 leaf hash
  BASCULE_SESSION_ID          UUID from SessionClaim
  BASCULE_POSTURE_LEVEL       SAT-SPEC-0002 §7 posture

  BASCULE_ATTESTATION_HASH    retained as compat alias (gsh /
                              dashboard consumers); now points at
                              the proto sat_hash, not the old
                              evidence-string SHA.

Cross-workspace path dep: substrate-proto via
../substrate-project/substrate/crates/substrate-proto. CI mounts
~/projects as one volume so the path resolves. Switching to a git
dep is post-MVP.

Note: russh-keys pulls `home` which requires Rust 1.88; CI bumps
the docker image accordingly. No code change.

Tested:
  cargo build -p bascule-core -p bascule-shell             clean
  cargo test  -p bascule-core --lib sat                    7/7

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Claude Code <claude@guildhouse.dev>
2026-04-07 14:38:20 -04:00
043b9b9bdc feat: bascule-shell — identity-aware shell with TPM attestation
New crate: bascule-shell (471 lines, 1.8MB binary)
  Login shell that detects identity + platform attestation at startup.
  Wraps bash/zsh/fish — operator works normally, identity travels with them.

Identity detection (priority order):
  1. Entra via WSL2 interop
  2. Azure CLI
  3. Kerberos TGT
  4. Cached OIDC token
  5. System user (fallback)

Platform attestation:
  TPM 2.0 PCR values via tpm2_pcrread (PCRs 0,1,2,7,10,14)
  IMA measurement log hash + count
  Keylime agent state
  Entra device compliance (WSL2 only)
  Composite SHA-256 hash over all evidence

Shell features:
  Banner with identity + attestation summary
  BASCULE_* env vars injected into inner shell
  --info mode for dry-run display
  --json mode for machine-readable output
  --exec mode for single-command execution
  Configurable via ~/.config/bascule/shell.toml

Tested on Fedora with real TPM 2.0:
  6 PCRs successfully read from hardware
  All env vars propagated to inner shell
  1.8MB binary, 0 substrate deps

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 09:47:46 -04:00