From 5c6755793152d038a50c8f4efc600539d781b61b Mon Sep 17 00:00:00 2001 From: Bret Mogilefsky Date: Tue, 8 Sep 2026 22:19:06 +0000 Subject: [PATCH 1/5] feat(isolation): add cloud.gov acq kit Add an sbx cloud.gov acq mixin kit that installs the pinned Cloud Foundry CLI, allow-lists cloud.gov and Cloud Foundry docs egress, and configures CF auth through acq secret placeholders. Tracks deferred msb wildcard parity in GSA-TTS/agentic-coding-patterns#407. Co-authored-by: OpenCode Agent --- integrations/isolation/acq-kits/README.md | 1 + .../isolation/acq-kits/cloud-gov/README.md | 142 +++++++++++++++++ .../acq-kits/cloud-gov/TROUBLESHOOTING.md | 84 ++++++++++ .../docs/decisions/cloud-gov-cli-and-auth.md | 62 ++++++++ .../files/home/cloud-gov-configure-cf-auth.sh | 44 ++++++ .../files/home/cloud-gov-install-cf-cli.sh | 87 +++++++++++ .../acq-kits/cloud-gov/scripts/verify | 143 ++++++++++++++++++ .../isolation/acq-kits/cloud-gov/spec.yaml | 88 +++++++++++ integrations/isolation/acq-kits/kits.yaml | 12 ++ 9 files changed, 663 insertions(+) create mode 100644 integrations/isolation/acq-kits/cloud-gov/README.md create mode 100644 integrations/isolation/acq-kits/cloud-gov/TROUBLESHOOTING.md create mode 100644 integrations/isolation/acq-kits/cloud-gov/docs/decisions/cloud-gov-cli-and-auth.md create mode 100755 integrations/isolation/acq-kits/cloud-gov/files/home/cloud-gov-configure-cf-auth.sh create mode 100755 integrations/isolation/acq-kits/cloud-gov/files/home/cloud-gov-install-cf-cli.sh create mode 100755 integrations/isolation/acq-kits/cloud-gov/scripts/verify create mode 100644 integrations/isolation/acq-kits/cloud-gov/spec.yaml diff --git a/integrations/isolation/acq-kits/README.md b/integrations/isolation/acq-kits/README.md index 048f6765..86b59a05 100644 --- a/integrations/isolation/acq-kits/README.md +++ b/integrations/isolation/acq-kits/README.md @@ -20,6 +20,7 @@ not agent behavior. (Behavioral patterns live in `skills/`, `prompts/`, etc.) |-----|---------| | [`usai-provider/`](usai-provider/) | Configure the agent to use the GSA USAi model provider (OpenCode today), with egress allow-listed. | | [`agentic-coding-playbook/`](agentic-coding-playbook/) | Clone the GSA agentic-coding-playbook at startup and link its `AGENTS.md` + skills into each agent's search paths. | +| [`cloud-gov/`](cloud-gov/) | Install the Cloud Foundry CLI and allow-list cloud.gov platform, app-route, dashboard, and docs domains, with auth supplied through acq secrets. | | [`zscaler-ca-certificate/`](zscaler-ca-certificate/) | Trust the public Zscaler Root CA in the sandbox (msb: native `--trust-host-cas`; sbx: file-drop + `update-ca-certificates`). | | [`git-ssh-sign/`](git-ssh-sign/) | Sign git commits and tags with the SSH key forwarded from the host agent (vendored from sbx-kits-contrib). | | [`openchamber/`](openchamber/) | Run OpenChamber, a browser UI for OpenCode, inside the sandbox alongside the terminal TUI. Opt-in (see its parity note). | diff --git a/integrations/isolation/acq-kits/cloud-gov/README.md b/integrations/isolation/acq-kits/cloud-gov/README.md new file mode 100644 index 00000000..b27b14e6 --- /dev/null +++ b/integrations/isolation/acq-kits/cloud-gov/README.md @@ -0,0 +1,142 @@ +# cloud-gov (acq mixin kit, `hybrid/v1`) + +A neutral [`acq`](https://github.com/GSA-TTS/agentic-coding-quickstart) **mixin +kit** for working with [cloud.gov](https://cloud.gov/) from an isolated coding +sandbox. + +> **Neutral (backend-agnostic) kit.** This is the `schemaVersion: "hybrid/v1"` +> form consumed by `acq`, which abstracts the isolation backend. See +> [backend parity](#backend-parity) and +> [`../../docs/decisions/0001-neutral-hybrid-v1-acq-kits.md`](../../docs/decisions/0001-neutral-hybrid-v1-acq-kits.md). + +## What It Does + +- **Cloud Foundry CLI** - uses an existing `cf` binary when the base image already + has one. If `cf` is missing, installs Cloud Foundry CLI v8.19.0 from pinned + upstream release tarballs with committed SHA-256 checks. +- **cloud.gov egress** - allow-lists the `cloud.gov` apex and all `cloud.gov` + subdomains, covering the Cloud Foundry API, login/UAA, log-stream hosts, + dashboard, public app routes, and user-facing cloud.gov documentation. +- **Cloud Foundry docs egress** - allow-lists upstream Cloud Foundry CLI and docs + hosts so agents can look up command behavior and platform guidance. +- **Secret-safe authorization** - documents the `acq secret set` binding for a + host-side `cf oauth-token`. The sandbox receives only the backend placeholder + needed for `cf` to send an auth header; the real token stays in the host-side + secret store / backend proxy. + +## Backend Parity + +The kit currently declares `sbx` support. sbx consumes the neutral kit fields: +`caps.network.allow`, file drops, and create-time/startup commands. There is no +backend shortcut. + +The Cloud Foundry token is not a kit field. The user stores it in the acq secret +store and binds it to `api.fr.cloud.gov`; the active backend maps that to its +own proxy or secret-substitution mechanism. The kit writes only the injected +placeholder into CF CLI config so `cf` can send an auth header for the proxy to +rewrite. + +msb support is intentionally not declared yet: this kit relies on +`**.cloud.gov`, and acq must translate per-kit `**.` wildcard entries to msb +suffix rules before the same spec can honestly claim msb parity. + +## Usage + +Apply the kit alongside an agent sandbox: + +```bash +acq run --kit opencode /path/to/project +``` + +The kit is a `mixin`, so it composes with the other acq-kits. + +## CF CLI Installation Pin + +If the base image already has `cf`, the install step exits successfully without +network package installation. If `cf` is missing, the kit installs Cloud Foundry +CLI v8.19.0 from the upstream GitHub release tarball for `linux_x86-64` or +`linux_arm64`, then verifies the archive SHA-256 before installing it. Unsupported +architectures fail closed. + +To update the CLI, change the version and both SHA-256 constants in +`files/home/cloud-gov-install-cf-cli.sh` together after reviewing the upstream +release metadata. + +## Prerequisites + +From a host shell, log in to cloud.gov with the normal human-controlled flow: + +```bash +cf login -a https://api.fr.cloud.gov --sso +``` + +Then store the short-lived Cloud Foundry OAuth token string in acq. `cf +oauth-token` returns the full authorization value, including the `bearer` prefix; +this command passes that value over stdin to the host-side secret store. Do not +paste it into chat or write it to a file: + +```bash +cf oauth-token | acq secret set -g cloud-gov --host api.fr.cloud.gov --env CF_OAUTH_TOKEN +``` + +For a sandbox-specific token, scope the secret to that sandbox: + +```bash +cf oauth-token | acq secret set cloud-gov --host api.fr.cloud.gov --env CF_OAUTH_TOKEN +``` + +The `CF_OAUTH_TOKEN` name is a placeholder binding for backend proxy/secret +substitution. At startup the kit targets `https://api.fr.cloud.gov` and, when +that placeholder is present, writes it into `~/.cf/config.json` so the stock CF +CLI can send `Authorization: Bearer ...`. That file contains only the backend +placeholder, not the real OAuth token. + +## Network Allow-List + +The kit allow-lists: + +| Host | Purpose | +|------|---------| +| `cloud.gov`, `**.cloud.gov` | cloud.gov apex and all cloud.gov subdomains, including API, login/UAA, dashboard, docs, and public app routes. This is intentionally broad for cloud.gov work; custom domains outside `cloud.gov` stay project-specific. | +| `docs.cloudfoundry.org`, `cli.cloudfoundry.org` | Cloud Foundry documentation | +| `github.com`, `objects.githubusercontent.com` | Pinned Cloud Foundry CLI release tarball download | + +## Using `cf` + +Inside the sandbox, target cloud.gov normally: + +```bash +cf api https://api.fr.cloud.gov +cf target +cf apps +``` + +If the CLI requires an interactive login or reports that no user is logged in, +refresh the host-side secret with `cf oauth-token | acq secret set ...` and +restart or recreate the sandbox so the placeholder reaches CF CLI config. Do not +paste the token into chat or commit it to the workspace. + +## Troubleshooting + +Failure-mode notes are in [`TROUBLESHOOTING.md`](TROUBLESHOOTING.md). + +## Design Decisions + +- [`docs/decisions/cloud-gov-cli-and-auth.md`](docs/decisions/cloud-gov-cli-and-auth.md) + - why this kit installs `cf`, uses broad `cloud.gov` egress, and relies on acq + placeholder-based auth. + +## Verifying + +Run the bundled check: + +```bash +./scripts/verify +``` + +By default it runs offline checks only: repository kit validation and shell +syntax for the install script. Set `RUN_ACQ=1` to create a throwaway sandbox and +verify that `cf` is installed, cloud.gov/docs endpoints are reachable, the +injected placeholder reaches the CF API, and an authenticated `cf orgs` command +works. `RUN_ACQ=1` requires a global `cloud-gov` acq secret. Set `KEEP=1` with +`RUN_ACQ=1` to keep the sandbox for inspection. diff --git a/integrations/isolation/acq-kits/cloud-gov/TROUBLESHOOTING.md b/integrations/isolation/acq-kits/cloud-gov/TROUBLESHOOTING.md new file mode 100644 index 00000000..54c1f13e --- /dev/null +++ b/integrations/isolation/acq-kits/cloud-gov/TROUBLESHOOTING.md @@ -0,0 +1,84 @@ +# Troubleshooting - cloud-gov kit + +These are failure modes specific to the cloud-gov acq mixin kit. + +## `cf` Is Not Installed + +**Symptoms:** `cf version` fails inside the sandbox. + +**Causes and fixes:** + +- **Install egress blocked.** Check the sandbox policy log and confirm the CF CLI + package hosts in `spec.yaml` are allowed. +- **Unsupported architecture.** The install script supports upstream Linux + x86_64/amd64 and arm64/aarch64 tarballs. For another architecture, install the + CF CLI in the base image or extend `files/home/cloud-gov-install-cf-cli.sh` + with a pinned artifact and SHA-256. +- **Release artifact changed.** The install script verifies the downloaded + tarball SHA-256. A mismatch fails closed; review the upstream release metadata + before changing the pinned version or hash. + +## `cf api https://api.fr.cloud.gov` Fails + +**Symptoms:** `cf api` times out or cannot resolve/reach the API host. + +**Causes and fixes:** + +- **Kit not applied.** Recreate the sandbox with this kit applied. +- **Egress blocked by org policy.** Check `acq policy log ` or the + active backend's policy log for `api.fr.cloud.gov`. +- **TLS trust issue.** If your network uses TLS inspection, compose this kit with + `zscaler-ca-certificate` or the local equivalent CA-trust kit. + +## CF Authentication Fails + +**Symptoms:** cloud.gov is reachable, but `cf` reports unauthorized, forbidden, +or no current user. + +**Causes and fixes:** + +- **Missing or expired host-side token.** From a host shell where you are logged + in to cloud.gov, refresh the acq secret: + + ```bash + cf oauth-token | acq secret set -g cloud-gov --host api.fr.cloud.gov --env CF_OAUTH_TOKEN + ``` + +- **Wrong scope.** If the sandbox has a sandbox-scoped `cloud-gov` secret, update + that scoped secret instead of the global one: + + ```bash + cf oauth-token | acq secret set cloud-gov --host api.fr.cloud.gov --env CF_OAUTH_TOKEN + ``` + +- **Client-side token cache mismatch.** The kit writes only the injected + placeholder to `~/.cf/config.json`, never the real token. If the CF CLI needs + an interactive login before making requests, complete authentication from the + host, refresh the acq secret, and restart or recreate the sandbox so the + placeholder is written into CF CLI config. Do not paste the token into chat or + commit it to the workspace. + +## A cloud.gov App Route Is Blocked + +**Symptoms:** `curl https://.app.cloud.gov` or a browser/API request to a +cloud.gov app route fails because egress is denied. + +**Causes and fixes:** + +- **Route outside cloud.gov.** The kit allow-lists `cloud.gov` and + `**.cloud.gov`. If your app uses a custom domain or an internal route outside + `cloud.gov`, add a separate project-specific network allow-list entry rather + than broadening this shared kit. +- **Wildcard not supported by the active backend.** Confirm the generated backend + policy from `acq`; if needed, add the specific app host as a per-sandbox allow. + +## Docs Are Not Reachable + +**Symptoms:** Requests to cloud.gov or Cloud Foundry docs fail. + +**Causes and fixes:** + +- Confirm the requested host is one of `cloud.gov`, `www.cloud.gov`, + `docs.cloud.gov`, `docs.cloudfoundry.org`, or `cli.cloudfoundry.org`. +- If docs pull assets from another host and the backend blocks them, add the + specific observed host after review instead of adding a broad wildcard. diff --git a/integrations/isolation/acq-kits/cloud-gov/docs/decisions/cloud-gov-cli-and-auth.md b/integrations/isolation/acq-kits/cloud-gov/docs/decisions/cloud-gov-cli-and-auth.md new file mode 100644 index 00000000..e66ef216 --- /dev/null +++ b/integrations/isolation/acq-kits/cloud-gov/docs/decisions/cloud-gov-cli-and-auth.md @@ -0,0 +1,62 @@ +# Decision: install CF CLI and use acq secret placeholder auth + +**Status:** accepted + +## Context + +cloud.gov operations use the Cloud Foundry CLI (`cf`) against the cloud.gov +Cloud Foundry API. A sandbox used for this work needs the CLI, egress to the +cloud.gov platform and application-route domains, and access to cloud.gov and +Cloud Foundry documentation. + +Authentication is sensitive. A token from `cf oauth-token` must not be committed, +printed in chat, or written into agent-readable workspace files. At the same +time, the stock CF CLI expects an auth token in its own config before it will send +an authenticated request. + +## Decision + +Create a neutral `cloud-gov` acq mixin kit, initially registered for sbx, that: + +1. Installs `cf` at create time from pinned Cloud Foundry CLI release tarballs + with committed SHA-256 values. +2. Allow-lists `cloud.gov`, all `cloud.gov` subdomains, and Cloud Foundry docs + domains. +3. Uses acq's custom secret binding for a host-side token captured with + `cf oauth-token`: + + ```sh + cf oauth-token | acq secret set -g cloud-gov --host api.fr.cloud.gov --env CF_OAUTH_TOKEN + ``` + +4. Writes only the injected backend placeholder to `~/.cf/config.json` at + startup, so `cf` sends an auth header that the backend proxy can rewrite on the + way to `api.fr.cloud.gov`. + +The kit does not store the real Cloud Foundry OAuth token in `spec.yaml`, files, +or repository docs. The token remains in the host-side acq secret store and the +active backend's secret/proxy mechanism. + +## Consequences + +- Agents can use normal `cf` commands after the user supplies a host-side token. +- The real token remains outside the sandbox; the sandbox may contain only a + backend-generated placeholder. +- The install currently supports Linux x86_64/amd64 and arm64/aarch64 release + tarballs. Other architectures should either preinstall `cf` or add a pinned, + integrity-checked install path before being supported. +- `**.cloud.gov` is intentionally broad because this kit is specifically for + cloud.gov work and the requested working surface includes all cloud.gov + subdomains. This widens egress to tenant app routes under `cloud.gov`; reviewers + should treat that as an accepted blast-radius tradeoff for this shared kit, not + as a generic default for other kits. Custom domains and internal routes outside + `cloud.gov` should be added per project rather than broadening this shared kit. +- Live verification with `RUN_ACQ=1` requires a configured `cloud-gov` secret and + must exercise both raw authenticated CF API access and an authenticated `cf` + command. Offline verification remains useful for schema/shell checks, but it is + not evidence that cloud.gov auth wiring works. + +## Links + +- `../../README.md` - operator usage and troubleshooting entry points. +- `../../spec.yaml` - authoritative kit behavior. diff --git a/integrations/isolation/acq-kits/cloud-gov/files/home/cloud-gov-configure-cf-auth.sh b/integrations/isolation/acq-kits/cloud-gov/files/home/cloud-gov-configure-cf-auth.sh new file mode 100755 index 00000000..85cdac28 --- /dev/null +++ b/integrations/isolation/acq-kits/cloud-gov/files/home/cloud-gov-configure-cf-auth.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env sh +set -eu + +# Configure the CF CLI to use cloud.gov. If acq injected a backend placeholder in +# CF_OAUTH_TOKEN, write that placeholder into CF CLI config so the CLI sends an +# Authorization header; the backend proxy swaps the placeholder on the wire. + +log() { printf '%s\n' "cloud-gov: $*" >&2; } + +json_escape() { + # CF_OAUTH_TOKEN should be a generated placeholder, but escape defensively + # before embedding it in JSON. Newlines are not valid in this single-value path. + printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g' +} + +mkdir -p "$HOME/.cf" + +if command -v cf >/dev/null 2>&1; then + cf api https://api.fr.cloud.gov >/dev/null 2>&1 || log "warning: cf api target failed; check egress/TLS" +fi + +if [ -n "${CF_OAUTH_TOKEN:-}" ]; then + case "$CF_OAUTH_TOKEN" in + *eyJ*.*.*) + log "CF_OAUTH_TOKEN looks like real JWT token material; refusing to write it" + exit 1 + ;; + esac + escaped_token="$(json_escape "$CF_OAUTH_TOKEN")" + cat >"$HOME/.cf/config.json" <&2; } +sha256_of() { + if command -v sha256sum >/dev/null 2>&1; then + sha256sum "$1" | cut -d' ' -f1 + elif command -v shasum >/dev/null 2>&1; then + shasum -a 256 "$1" | cut -d' ' -f1 + else + return 1 + fi +} + +if command -v cf >/dev/null 2>&1; then + log "cf CLI already installed: $(cf version 2>/dev/null || printf 'version unavailable')" + exit 0 +fi + +if ! command -v curl >/dev/null 2>&1; then + log "curl is required to fetch the pinned CF CLI release; refusing install" + exit 1 +fi +if ! command -v tar >/dev/null 2>&1; then + log "tar is required to unpack the pinned CF CLI release; refusing install" + exit 1 +fi +if ! command -v gzip >/dev/null 2>&1; then + log "gzip is required to unpack the pinned CF CLI release; refusing install" + exit 1 +fi +if ! command -v sha256sum >/dev/null 2>&1 && ! command -v shasum >/dev/null 2>&1; then + log "sha256sum or shasum is required to verify the pinned CF CLI release; refusing install" + exit 1 +fi + +arch="$(uname -m)" +case "$arch" in + x86_64|amd64) + asset="cf8-cli_${CF_CLI_VERSION}_linux_x86-64.tgz" + want_sha="$CF_CLI_SHA256_X86_64" + ;; + aarch64|arm64) + asset="cf8-cli_${CF_CLI_VERSION}_linux_arm64.tgz" + want_sha="$CF_CLI_SHA256_ARM64" + ;; + *) + log "unsupported architecture for pinned CF CLI release: $arch" + exit 1 + ;; +esac + +work="$(mktemp -d)" +trap 'rm -rf "$work"' EXIT INT TERM +url="https://github.com/cloudfoundry/cli/releases/download/v${CF_CLI_VERSION}/${asset}" +curl -fsSL "$url" -o "$work/cf-cli.tgz" +got_sha="$(sha256_of "$work/cf-cli.tgz" || true)" +if [ -z "$got_sha" ] || [ "$got_sha" != "$want_sha" ]; then + log "CF CLI release SHA-256 mismatch (got ${got_sha:-none}, want $want_sha); refusing install" + exit 1 +fi + +tar -xzf "$work/cf-cli.tgz" -C "$work" +if [ -x "$work/cf8" ]; then + install -m 0755 "$work/cf8" /usr/local/bin/cf8 +elif [ -x "$work/cf" ]; then + install -m 0755 "$work/cf" /usr/local/bin/cf8 +else + log "pinned CF CLI archive did not contain cf8 or cf executable" + exit 1 +fi +ln -sf /usr/local/bin/cf8 /usr/local/bin/cf + +if command -v cf >/dev/null 2>&1; then + log "installed $(cf version 2>/dev/null || printf 'cf CLI')" +else + log "cf CLI install completed but cf is not on PATH" + exit 1 +fi diff --git a/integrations/isolation/acq-kits/cloud-gov/scripts/verify b/integrations/isolation/acq-kits/cloud-gov/scripts/verify new file mode 100755 index 00000000..54c5a503 --- /dev/null +++ b/integrations/isolation/acq-kits/cloud-gov/scripts/verify @@ -0,0 +1,143 @@ +#!/usr/bin/env bash +# +# verify - host-side validation for the cloud-gov acq mixin kit. +# +# By default this runs offline checks only. Set RUN_ACQ=1 to create a throwaway +# sandbox and verify the CF CLI plus cloud.gov/docs network reachability. + +set -uo pipefail + +KIT_DIR="$(cd "$(dirname "$0")/.." && pwd)" +REPO_ROOT="$(cd "$KIT_DIR/../../../.." && pwd)" +SBX_NAME="cloud-gov-verify-$$" +WORK="$(mktemp -d "$KIT_DIR/verify-workspace.XXXXXX")" +CREATE_LOG="$WORK/create.log" +KEEP="${KEEP:-0}" +RUN_ACQ="${RUN_ACQ:-0}" + +pass=0 +fail=0 +ok() { printf ' \033[32mPASS\033[0m %s\n' "$1"; pass=$((pass + 1)); } +bad() { printf ' \033[31mFAIL\033[0m %s\n' "$1"; fail=$((fail + 1)); } +skip() { printf ' \033[33mSKIP\033[0m %s\n' "$1"; } +info() { printf '\n\033[1m%s\033[0m\n' "$1"; } + +cleanup() { + if [ "$KEEP" = "1" ]; then + printf '\nKEEP=1 set; leaving sandbox %s and %s in place.\n' "$SBX_NAME" "$WORK" >&2 + return + fi + acq rm "$SBX_NAME" >/dev/null 2>&1 || true + rm -rf "$WORK" >/dev/null 2>&1 || true +} +trap cleanup EXIT INT TERM + +in_acq() { acq exec "$SBX_NAME" -- sh -c "$1" /dev/null || true; } + +info "0. Offline preconditions" +[ -f "$KIT_DIR/spec.yaml" ] && ok "kit spec.yaml present" || { bad "spec.yaml missing"; exit 1; } +[ -f "$KIT_DIR/files/home/cloud-gov-install-cf-cli.sh" ] && ok "install script present" || bad "install script missing" + +if command -v bash >/dev/null 2>&1; then + if bash -n "$KIT_DIR/files/home/cloud-gov-install-cf-cli.sh"; then + ok "install script parses" + else + bad "install script has a shell syntax error" + fi +else + skip "bash not on PATH - skipped shell parse check" +fi + +info "1. Validate the neutral kit spec" +VALIDATOR="$REPO_ROOT/integrations/isolation/acq-kits/validate-kits.py" +if ! command -v python3 >/dev/null 2>&1; then + skip "validate-kits.py not run (python3 not on PATH)" +elif [ ! -f "$VALIDATOR" ]; then + skip "validate-kits.py not found at $VALIDATOR" +elif ! python3 -c 'import jsonschema, yaml' >/dev/null 2>&1; then + skip "validate-kits.py needs jsonschema + pyyaml" +else + if val_out="$(python3 "$VALIDATOR" --root "$REPO_ROOT" 2>&1)"; then + ok "acq-kits validate-kits.py passes" + else + bad "validate-kits.py failed:" + printf '%s\n' "$val_out" | sed 's/^/ | /' >&2 + fi +fi + +if [ "$RUN_ACQ" != "1" ]; then + info "2. Live sandbox check" + skip "RUN_ACQ=1 not set - skipped live cloud.gov sandbox verification" + printf '\nSummary\n %d passed, %d failed\n' "$pass" "$fail" + [ "$fail" -eq 0 ] + exit $? +fi + +info "2. Live sandbox preconditions" +if ! command -v acq >/dev/null 2>&1; then + bad "acq not on PATH" + exit 1 +fi +ok "acq on PATH" + +if acq secret has -g cloud-gov >/dev/null 2>&1; then + ok "global cloud-gov secret is set" +else + bad "RUN_ACQ=1 requires a global cloud-gov secret for authenticated verification" + printf ' Set it from a host shell with: cf oauth-token | acq secret set -g cloud-gov --host api.fr.cloud.gov --env CF_OAUTH_TOKEN\n' >&2 + exit 1 +fi + +info "3. Create sandbox with the kit" +printf ' creating %s (workspace %s)...\n' "$SBX_NAME" "$WORK" +if acq create --name "$SBX_NAME" --kit "$KIT_DIR" opencode "$WORK" >"$CREATE_LOG" 2>&1; then + ok "sandbox created with cloud-gov kit" +else + bad "acq create failed" + sed 's/^/ | /' "$CREATE_LOG" >&2 + exit 1 +fi + +info "4. CF CLI installed" +cf_version="$(in_acq 'cf version')" +printf ' %s\n' "${cf_version:-}" +case "$cf_version" in + *"cf version"*) ok "cf CLI reports a version" ;; + *) bad "cf CLI did not report a version" ;; +esac + +info "5. cloud.gov API and docs reachable" +for url in \ + https://api.fr.cloud.gov/v3/info \ + https://cloud.gov/ \ + https://docs.cloud.gov/ \ + https://docs.cloudfoundry.org/ \ + https://cli.cloudfoundry.org/; do + status="$(in_acq "curl -sS -o /dev/null -w '%{http_code}' '$url'")" + printf ' %s -> %s\n' "$url" "${status:-}" + case "$status" in + 2*|3*|401|403) ok "$url reachable" ;; + *) bad "$url not reachable (status ${status:-none})" ;; + esac +done + +info "6. Authenticated CF API wiring" +auth_status="$(in_acq "curl -sS -o /dev/null -w '%{http_code}' -H \"Authorization: \$CF_OAUTH_TOKEN\" https://api.fr.cloud.gov/v3/organizations")" +printf ' authenticated /v3/organizations -> %s\n' "${auth_status:-}" +case "$auth_status" in + 2*|3*) ok "authenticated CF API probe succeeded" ;; + 401|403) bad "authenticated CF API probe rejected credentials (status $auth_status)" ;; + *) bad "authenticated CF API probe failed (status ${auth_status:-none})" ;; +esac + +cf_auth="$(in_acq 'cf orgs >/dev/null 2>&1 && printf ok || printf fail')" +case "$cf_auth" in + ok) ok "cf CLI authenticated command succeeds" ;; + *) bad "cf CLI authenticated command failed" ;; +esac + +info "Summary" +printf ' %d passed, %d failed\n' "$pass" "$fail" +[ "$fail" -eq 0 ] && printf ' \033[32mAll checks passed.\033[0m\n' || printf ' \033[31mSome checks failed - see notes above.\033[0m\n' + +[ "$fail" -eq 0 ] diff --git a/integrations/isolation/acq-kits/cloud-gov/spec.yaml b/integrations/isolation/acq-kits/cloud-gov/spec.yaml new file mode 100644 index 00000000..92410fa3 --- /dev/null +++ b/integrations/isolation/acq-kits/cloud-gov/spec.yaml @@ -0,0 +1,88 @@ +# spec.yaml - cloud-gov (a neutral acq mixin kit, hybrid/v1) +# +# Configures a sandbox for cloud.gov work: +# - install/verify the Cloud Foundry CLI (`cf`) at create time; +# - allow-list cloud.gov control-plane, app-route, and documentation domains; +# - document the acq secret binding needed for CF OAuth token injection without +# placing the token in environment variables or files readable by the agent. +# +# AUTH MODEL: the kit does NOT store or expose a Cloud Foundry token. Users set a +# custom acq secret from `cf oauth-token` and bind it to api.fr.cloud.gov. acq's +# active backend injects a placeholder in CF_OAUTH_TOKEN and swaps it on the +# egress path for requests to the CF API host; the raw token stays in the +# host-side secret store / backend proxy. +schemaVersion: "hybrid/v1" +kind: mixin +name: cloud-gov +displayName: cloud.gov Cloud Foundry CLI +description: > + Installs the Cloud Foundry CLI and allow-lists cloud.gov control-plane, + application-route, and documentation domains so agents can inspect and operate + cloud.gov applications through proxied egress. Cloud Foundry authorization is + supplied through acq's custom secret mechanism, using a token captured from + `cf oauth-token`, so credentials are not stored in the kit or exposed in the + sandbox environment. + +caps: + network: + allow: + # cloud.gov apex and every cloud.gov subdomain, including API, login/UAA, + # dashboard, docs, and public application routes. Keep wildcard hosts + # quoted: leading-* YAML scalars can otherwise be parsed as aliases by + # downstream translators. + - cloud.gov:443 + - "**.cloud.gov:443" + # Upstream Cloud Foundry documentation. + - docs.cloudfoundry.org:443 + - cli.cloudfoundry.org:443 + # Pinned CF CLI release tarballs used by cloud-gov-install-cf-cli.sh. + - github.com:443 + - objects.githubusercontent.com:443 + +files: + - path: /home/agent/cloud-gov-install-cf-cli.sh + mode: "0755" + source: files/home/cloud-gov-install-cf-cli.sh + description: Install or verify the Cloud Foundry CLI at sandbox create time + - path: /home/agent/cloud-gov-configure-cf-auth.sh + mode: "0755" + source: files/home/cloud-gov-configure-cf-auth.sh + description: Target cloud.gov and configure cf with the injected token placeholder + +commands: + - phase: install + user: "0" + description: Install the Cloud Foundry CLI if it is missing + command: + - sh + - /home/agent/cloud-gov-install-cf-cli.sh + - phase: startup + user: "1000" + description: Target cloud.gov and configure cf auth from the injected placeholder + command: + - sh + - /home/agent/cloud-gov-configure-cf-auth.sh + +agentContext: | + ## cloud.gov + + This sandbox has the Cloud Foundry CLI (`cf`) available for cloud.gov work and + egress allow-listed for the cloud.gov API, login/UAA, log streaming, common app + route domains, and cloud.gov / Cloud Foundry documentation domains. + + Authorization is handled through acq's secret injection path. The sandbox may + contain a backend placeholder in `CF_OAUTH_TOKEN` and `~/.cf/config.json`, but + not the real token. The user should set the secret from a host shell where they + are already logged in to cloud.gov: + + ``` + cf oauth-token | acq secret set -g cloud-gov --host api.fr.cloud.gov --env CF_OAUTH_TOKEN + ``` + + Do not ask the user to paste the token into chat or write it to the workspace. + Use normal `cf` commands; real token material remains outside agent-readable + files. + +# No backend shortcuts: sbx consumes the neutral files, create-time install, and +# allow-list. msb support is not declared until acq translates per-kit `**.` +# wildcard entries to msb suffix rules. diff --git a/integrations/isolation/acq-kits/kits.yaml b/integrations/isolation/acq-kits/kits.yaml index 992ad0c2..355578a5 100644 --- a/integrations/isolation/acq-kits/kits.yaml +++ b/integrations/isolation/acq-kits/kits.yaml @@ -27,6 +27,18 @@ kits: the PLAYBOOK_SHA integrity gate. No backend shortcut. The GitHub token (while the playbook repo is private) is injected per-backend by acq. + cloud-gov: + backends: [sbx] + parity: | + sbx consumes the neutral files, create-time install, and allow-list directly: + the kit installs or verifies the Cloud Foundry CLI and allows egress to the + cloud.gov apex/subdomain surface, Cloud Foundry docs, and pinned CF CLI + release hosts. Cloud Foundry authorization is supplied through acq's custom + secret binding for api.fr.cloud.gov, seeded from a host-side `cf oauth-token`; + the token is not stored in the kit or exposed as a readable credential file. + msb support is pending acq translation of per-kit `**.` wildcard entries to + msb suffix rules. + zscaler-ca-certificate: backends: [sbx, msb] parity: | From a491bcaa604adc536f941dfc729ce71b2ef93bb4 Mon Sep 17 00:00:00 2001 From: Bret Mogilefsky Date: Wed, 9 Sep 2026 06:10:25 +0000 Subject: [PATCH 2/5] feat(isolation): declare msb support for cloud.gov kit Update the cloud.gov kit registry and docs after verifying that the quickstart msb wildcard fix translates per-kit **.cloud.gov to allow@*.cloud.gov. This depends on GSA-TTS/agentic-coding-quickstart#451 for msb runtime support. Co-authored-by: OpenCode Agent --- .../isolation/acq-kits/cloud-gov/README.md | 12 ++++++------ .../docs/decisions/cloud-gov-cli-and-auth.md | 5 ++++- .../isolation/acq-kits/cloud-gov/spec.yaml | 6 +----- integrations/isolation/acq-kits/kits.yaml | 17 ++++++++--------- 4 files changed, 19 insertions(+), 21 deletions(-) diff --git a/integrations/isolation/acq-kits/cloud-gov/README.md b/integrations/isolation/acq-kits/cloud-gov/README.md index b27b14e6..a258e40c 100644 --- a/integrations/isolation/acq-kits/cloud-gov/README.md +++ b/integrations/isolation/acq-kits/cloud-gov/README.md @@ -26,9 +26,9 @@ sandbox. ## Backend Parity -The kit currently declares `sbx` support. sbx consumes the neutral kit fields: -`caps.network.allow`, file drops, and create-time/startup commands. There is no -backend shortcut. +The kit declares `sbx` and `msb` support. Both backends consume the neutral kit +fields: `caps.network.allow`, file drops, and create-time/startup commands. There +is no backend shortcut. The Cloud Foundry token is not a kit field. The user stores it in the acq secret store and binds it to `api.fr.cloud.gov`; the active backend maps that to its @@ -36,9 +36,9 @@ own proxy or secret-substitution mechanism. The kit writes only the injected placeholder into CF CLI config so `cf` can send an auth header for the proxy to rewrite. -msb support is intentionally not declared yet: this kit relies on -`**.cloud.gov`, and acq must translate per-kit `**.` wildcard entries to msb -suffix rules before the same spec can honestly claim msb parity. +The only backend-specific network nuance is the cloud.gov wildcard. sbx consumes +`**.cloud.gov` directly. msb support requires acq to translate that per-kit +wildcard to msb's suffix rule form (`*.cloud.gov`). ## Usage diff --git a/integrations/isolation/acq-kits/cloud-gov/docs/decisions/cloud-gov-cli-and-auth.md b/integrations/isolation/acq-kits/cloud-gov/docs/decisions/cloud-gov-cli-and-auth.md index e66ef216..beeaee7d 100644 --- a/integrations/isolation/acq-kits/cloud-gov/docs/decisions/cloud-gov-cli-and-auth.md +++ b/integrations/isolation/acq-kits/cloud-gov/docs/decisions/cloud-gov-cli-and-auth.md @@ -16,7 +16,7 @@ an authenticated request. ## Decision -Create a neutral `cloud-gov` acq mixin kit, initially registered for sbx, that: +Create a neutral `cloud-gov` acq mixin kit for sbx and msb that: 1. Installs `cf` at create time from pinned Cloud Foundry CLI release tarballs with committed SHA-256 values. @@ -51,6 +51,9 @@ active backend's secret/proxy mechanism. should treat that as an accepted blast-radius tradeoff for this shared kit, not as a generic default for other kits. Custom domains and internal routes outside `cloud.gov` should be added per project rather than broadening this shared kit. +- msb support depends on acq translating the neutral per-kit `**.cloud.gov` entry + into msb's suffix rule form (`*.cloud.gov`). Without that adapter behavior, + exact cloud.gov hosts may work but the all-subdomains contract is not met. - Live verification with `RUN_ACQ=1` requires a configured `cloud-gov` secret and must exercise both raw authenticated CF API access and an authenticated `cf` command. Offline verification remains useful for schema/shell checks, but it is diff --git a/integrations/isolation/acq-kits/cloud-gov/spec.yaml b/integrations/isolation/acq-kits/cloud-gov/spec.yaml index 92410fa3..bb236ac4 100644 --- a/integrations/isolation/acq-kits/cloud-gov/spec.yaml +++ b/integrations/isolation/acq-kits/cloud-gov/spec.yaml @@ -81,8 +81,4 @@ agentContext: | Do not ask the user to paste the token into chat or write it to the workspace. Use normal `cf` commands; real token material remains outside agent-readable - files. - -# No backend shortcuts: sbx consumes the neutral files, create-time install, and -# allow-list. msb support is not declared until acq translates per-kit `**.` -# wildcard entries to msb suffix rules. + files. \ No newline at end of file diff --git a/integrations/isolation/acq-kits/kits.yaml b/integrations/isolation/acq-kits/kits.yaml index 355578a5..528dd240 100644 --- a/integrations/isolation/acq-kits/kits.yaml +++ b/integrations/isolation/acq-kits/kits.yaml @@ -28,16 +28,15 @@ kits: (while the playbook repo is private) is injected per-backend by acq. cloud-gov: - backends: [sbx] + backends: [sbx, msb] parity: | - sbx consumes the neutral files, create-time install, and allow-list directly: - the kit installs or verifies the Cloud Foundry CLI and allows egress to the - cloud.gov apex/subdomain surface, Cloud Foundry docs, and pinned CF CLI - release hosts. Cloud Foundry authorization is supplied through acq's custom - secret binding for api.fr.cloud.gov, seeded from a host-side `cf oauth-token`; - the token is not stored in the kit or exposed as a readable credential file. - msb support is pending acq translation of per-kit `**.` wildcard entries to - msb suffix rules. + Both backends consume the neutral files, create-time install, startup auth + configuration, and cloud.gov/docs allow-list. sbx consumes `**.cloud.gov` + directly; msb support requires acq to translate per-kit `**.` wildcard + entries to msb suffix rules. Cloud Foundry authorization is supplied through + acq's custom secret binding for api.fr.cloud.gov, seeded from a host-side + `cf oauth-token`; the token is not stored in the kit or exposed as a + readable credential file. zscaler-ca-certificate: backends: [sbx, msb] From 17492c636fb3f9fc825adb8a205268eb93bd9183 Mon Sep 17 00:00:00 2001 From: Bret Mogilefsky Date: Thu, 24 Sep 2026 04:26:38 +0000 Subject: [PATCH 3/5] fix(isolation): address cloud.gov kit review feedback Tighten CF token placeholder handling, use an msb-compatible cloud.gov wildcard, and update verification/docs to reflect the supported auth path. Co-authored-by: OpenCode Agent --- .../isolation/acq-kits/cloud-gov/README.md | 52 +++++++------- .../acq-kits/cloud-gov/TROUBLESHOOTING.md | 31 ++++++--- .../docs/decisions/cloud-gov-cli-and-auth.md | 40 ++++++----- .../files/home/cloud-gov-configure-cf-auth.sh | 50 +++++++------- .../acq-kits/cloud-gov/scripts/verify | 67 +++++++++++++------ .../isolation/acq-kits/cloud-gov/spec.yaml | 39 ++++++----- integrations/isolation/acq-kits/kits.yaml | 12 ++-- 7 files changed, 168 insertions(+), 123 deletions(-) diff --git a/integrations/isolation/acq-kits/cloud-gov/README.md b/integrations/isolation/acq-kits/cloud-gov/README.md index a258e40c..03291650 100644 --- a/integrations/isolation/acq-kits/cloud-gov/README.md +++ b/integrations/isolation/acq-kits/cloud-gov/README.md @@ -16,12 +16,13 @@ sandbox. upstream release tarballs with committed SHA-256 checks. - **cloud.gov egress** - allow-lists the `cloud.gov` apex and all `cloud.gov` subdomains, covering the Cloud Foundry API, login/UAA, log-stream hosts, - dashboard, public app routes, and user-facing cloud.gov documentation. + dashboard, dynamically assigned app routes, and user-facing cloud.gov + documentation. - **Cloud Foundry docs egress** - allow-lists upstream Cloud Foundry CLI and docs hosts so agents can look up command behavior and platform guidance. - **Secret-safe authorization** - documents the `acq secret set` binding for a host-side `cf oauth-token`. The sandbox receives only the backend placeholder - needed for `cf` to send an auth header; the real token stays in the host-side + needed for direct Cloud Foundry API calls; the real token stays in the host-side secret store / backend proxy. ## Backend Parity @@ -32,13 +33,11 @@ is no backend shortcut. The Cloud Foundry token is not a kit field. The user stores it in the acq secret store and binds it to `api.fr.cloud.gov`; the active backend maps that to its -own proxy or secret-substitution mechanism. The kit writes only the injected -placeholder into CF CLI config so `cf` can send an auth header for the proxy to -rewrite. +own proxy or secret-substitution mechanism. The kit validates that any injected +`CF_OAUTH_TOKEN` value is a backend placeholder, not raw token material. -The only backend-specific network nuance is the cloud.gov wildcard. sbx consumes -`**.cloud.gov` directly. msb support requires acq to translate that per-kit -wildcard to msb's suffix rule form (`*.cloud.gov`). +The cloud.gov wildcard uses `*.cloud.gov`, which msb accepts directly as a +suffix rule and sbx accepts as a cloud.gov subdomain wildcard. ## Usage @@ -87,9 +86,10 @@ cf oauth-token | acq secret set cloud-gov --host api.fr.cloud.gov The `CF_OAUTH_TOKEN` name is a placeholder binding for backend proxy/secret substitution. At startup the kit targets `https://api.fr.cloud.gov` and, when -that placeholder is present, writes it into `~/.cf/config.json` so the stock CF -CLI can send `Authorization: Bearer ...`. That file contains only the backend -placeholder, not the real OAuth token. +that value is present, refuses to continue unless it matches a recognized +backend-placeholder prefix. The stock CF CLI may parse `AccessToken` locally, so +the kit does not write the placeholder into CF CLI config or claim authenticated +`cf` commands work without live verification. ## Network Allow-List @@ -97,24 +97,30 @@ The kit allow-lists: | Host | Purpose | |------|---------| -| `cloud.gov`, `**.cloud.gov` | cloud.gov apex and all cloud.gov subdomains, including API, login/UAA, dashboard, docs, and public app routes. This is intentionally broad for cloud.gov work; custom domains outside `cloud.gov` stay project-specific. | +| `cloud.gov`, `*.cloud.gov` | cloud.gov apex and all cloud.gov subdomains, including API, login/UAA, dashboard, docs, and public app routes. This is intentionally broad: Cloud Foundry assigns app routes dynamically, and a reusable kit cannot know which `*.app.cloud.gov` hosts belong to the current operator at static kit-definition time. Custom domains outside `cloud.gov` stay project-specific. | | `docs.cloudfoundry.org`, `cli.cloudfoundry.org` | Cloud Foundry documentation | | `github.com`, `objects.githubusercontent.com` | Pinned Cloud Foundry CLI release tarball download | ## Using `cf` -Inside the sandbox, target cloud.gov normally: +Inside the sandbox, use `cf` for unauthenticated operations such as targeting the +cloud.gov API: ```bash cf api https://api.fr.cloud.gov cf target -cf apps ``` -If the CLI requires an interactive login or reports that no user is logged in, -refresh the host-side secret with `cf oauth-token | acq secret set ...` and -restart or recreate the sandbox so the placeholder reaches CF CLI config. Do not -paste the token into chat or commit it to the workspace. +For authenticated checks, use direct HTTPS calls to the Cloud Foundry API so the +backend can rewrite the placeholder on the wire: + +```bash +curl -H "Authorization: $CF_OAUTH_TOKEN" https://api.fr.cloud.gov/v3/organizations +``` + +If authenticated API calls fail, refresh the host-side secret with `cf oauth-token +| acq secret set ...` and restart or recreate the sandbox so the placeholder is +injected. Do not paste the token into chat or commit it to the workspace. ## Troubleshooting @@ -134,9 +140,9 @@ Run the bundled check: ./scripts/verify ``` -By default it runs offline checks only: repository kit validation and shell -syntax for the install script. Set `RUN_ACQ=1` to create a throwaway sandbox and -verify that `cf` is installed, cloud.gov/docs endpoints are reachable, the -injected placeholder reaches the CF API, and an authenticated `cf orgs` command -works. `RUN_ACQ=1` requires a global `cloud-gov` acq secret. Set `KEEP=1` with +By default it runs offline checks only: repository kit validation, shell syntax +for kit scripts, and auth-script guard tests. Set `RUN_ACQ=1` to create a +throwaway sandbox and verify that `cf` is installed, cloud.gov/docs endpoints are +reachable, and the injected placeholder reaches the CF API through a direct HTTPS +request. `RUN_ACQ=1` requires a global `cloud-gov` acq secret. Set `KEEP=1` with `RUN_ACQ=1` to keep the sandbox for inspection. diff --git a/integrations/isolation/acq-kits/cloud-gov/TROUBLESHOOTING.md b/integrations/isolation/acq-kits/cloud-gov/TROUBLESHOOTING.md index 54c1f13e..328ddaea 100644 --- a/integrations/isolation/acq-kits/cloud-gov/TROUBLESHOOTING.md +++ b/integrations/isolation/acq-kits/cloud-gov/TROUBLESHOOTING.md @@ -37,8 +37,10 @@ or no current user. **Causes and fixes:** -- **Missing or expired host-side token.** From a host shell where you are logged - in to cloud.gov, refresh the acq secret: +- **Missing or expired host-side token.** Cloud Foundry OAuth tokens are + short-lived. If `scripts/verify` reports `authenticated /v3/organizations -> + 401` or `403`, refresh the acq secret from a host shell where you are logged in + to cloud.gov: ```bash cf oauth-token | acq secret set -g cloud-gov --host api.fr.cloud.gov --env CF_OAUTH_TOKEN @@ -51,12 +53,17 @@ or no current user. cf oauth-token | acq secret set cloud-gov --host api.fr.cloud.gov --env CF_OAUTH_TOKEN ``` -- **Client-side token cache mismatch.** The kit writes only the injected - placeholder to `~/.cf/config.json`, never the real token. If the CF CLI needs - an interactive login before making requests, complete authentication from the - host, refresh the acq secret, and restart or recreate the sandbox so the - placeholder is written into CF CLI config. Do not paste the token into chat or - commit it to the workspace. +- **Unexpected token value in the sandbox.** The startup script only accepts + recognized backend placeholders (`acq_placeholder_*` or `sbx-cs-*`) in + `CF_OAUTH_TOKEN`. If a raw `bearer ...` token, a JWT-looking value, or a value + with control characters appears in the sandbox, startup fails closed. Refresh + the acq secret from the host and recreate the sandbox. Do not paste the token + into chat or commit it to the workspace. +- **CF CLI wants a local login.** The kit does not write the placeholder into + `~/.cf/config.json` because stock `cf` may parse cached tokens locally before + sending a request. Use direct Cloud Foundry API calls with the placeholder auth + header for authenticated probes unless a live-tested backend path for + authenticated `cf` commands is added later. ## A cloud.gov App Route Is Blocked @@ -66,9 +73,11 @@ cloud.gov app route fails because egress is denied. **Causes and fixes:** - **Route outside cloud.gov.** The kit allow-lists `cloud.gov` and - `**.cloud.gov`. If your app uses a custom domain or an internal route outside - `cloud.gov`, add a separate project-specific network allow-list entry rather - than broadening this shared kit. + `*.cloud.gov` because Cloud Foundry app routes are assigned dynamically and a + reusable kit cannot know which `*.app.cloud.gov` hosts belong to the current + operator at static kit-definition time. If your app uses a custom domain or an + internal route outside `cloud.gov`, add a separate project-specific network + allow-list entry rather than broadening this shared kit. - **Wildcard not supported by the active backend.** Confirm the generated backend policy from `acq`; if needed, add the specific app host as a per-sandbox allow. diff --git a/integrations/isolation/acq-kits/cloud-gov/docs/decisions/cloud-gov-cli-and-auth.md b/integrations/isolation/acq-kits/cloud-gov/docs/decisions/cloud-gov-cli-and-auth.md index beeaee7d..8da4fea3 100644 --- a/integrations/isolation/acq-kits/cloud-gov/docs/decisions/cloud-gov-cli-and-auth.md +++ b/integrations/isolation/acq-kits/cloud-gov/docs/decisions/cloud-gov-cli-and-auth.md @@ -11,8 +11,9 @@ Cloud Foundry documentation. Authentication is sensitive. A token from `cf oauth-token` must not be committed, printed in chat, or written into agent-readable workspace files. At the same -time, the stock CF CLI expects an auth token in its own config before it will send -an authenticated request. +time, stock CF CLI auth paths may parse cached access tokens locally before +sending a request, so a non-JWT backend placeholder is not enough to prove +interactive `cf` authentication works. ## Decision @@ -29,35 +30,40 @@ Create a neutral `cloud-gov` acq mixin kit for sbx and msb that: cf oauth-token | acq secret set -g cloud-gov --host api.fr.cloud.gov --env CF_OAUTH_TOKEN ``` -4. Writes only the injected backend placeholder to `~/.cf/config.json` at - startup, so `cf` sends an auth header that the backend proxy can rewrite on the +4. Validates any injected `CF_OAUTH_TOKEN` value as a recognized backend + placeholder and refuses raw token-looking values. The placeholder can be used + for direct HTTPS Cloud Foundry API calls that the backend proxy rewrites on the way to `api.fr.cloud.gov`. The kit does not store the real Cloud Foundry OAuth token in `spec.yaml`, files, or repository docs. The token remains in the host-side acq secret store and the -active backend's secret/proxy mechanism. +active backend's secret/proxy mechanism. The kit does not write a placeholder to +`~/.cf/config.json`; authenticated `cf` command support requires a separately +live-tested path because the stock CLI may parse cached tokens locally. ## Consequences -- Agents can use normal `cf` commands after the user supplies a host-side token. +- Agents can use `cf` for Cloud Foundry targeting and unauthenticated inspection. + Authenticated operations should use direct HTTPS Cloud Foundry API calls until a + live-tested path for authenticated stock `cf` commands exists. - The real token remains outside the sandbox; the sandbox may contain only a backend-generated placeholder. - The install currently supports Linux x86_64/amd64 and arm64/aarch64 release tarballs. Other architectures should either preinstall `cf` or add a pinned, integrity-checked install path before being supported. -- `**.cloud.gov` is intentionally broad because this kit is specifically for - cloud.gov work and the requested working surface includes all cloud.gov - subdomains. This widens egress to tenant app routes under `cloud.gov`; reviewers - should treat that as an accepted blast-radius tradeoff for this shared kit, not - as a generic default for other kits. Custom domains and internal routes outside +- `*.cloud.gov` is intentionally broad because this kit is specifically for + cloud.gov work and Cloud Foundry app routes are assigned dynamically. A static + shared kit cannot know which `*.app.cloud.gov` hosts belong to the current + operator, so this widens egress to tenant app routes under `cloud.gov` as an + accepted blast-radius tradeoff. Custom domains and internal routes outside `cloud.gov` should be added per project rather than broadening this shared kit. -- msb support depends on acq translating the neutral per-kit `**.cloud.gov` entry - into msb's suffix rule form (`*.cloud.gov`). Without that adapter behavior, - exact cloud.gov hosts may work but the all-subdomains contract is not met. +- The wildcard is written as `*.cloud.gov` so current msb versions accept it + directly as a suffix rule while sbx still treats it as a cloud.gov subdomain + wildcard. - Live verification with `RUN_ACQ=1` requires a configured `cloud-gov` secret and - must exercise both raw authenticated CF API access and an authenticated `cf` - command. Offline verification remains useful for schema/shell checks, but it is - not evidence that cloud.gov auth wiring works. + must exercise raw authenticated CF API access through the backend placeholder. + Offline verification remains useful for schema/shell checks and auth guard + tests, but it is not evidence that cloud.gov auth wiring works end to end. ## Links diff --git a/integrations/isolation/acq-kits/cloud-gov/files/home/cloud-gov-configure-cf-auth.sh b/integrations/isolation/acq-kits/cloud-gov/files/home/cloud-gov-configure-cf-auth.sh index 85cdac28..3d8ff6c4 100755 --- a/integrations/isolation/acq-kits/cloud-gov/files/home/cloud-gov-configure-cf-auth.sh +++ b/integrations/isolation/acq-kits/cloud-gov/files/home/cloud-gov-configure-cf-auth.sh @@ -2,43 +2,39 @@ set -eu # Configure the CF CLI to use cloud.gov. If acq injected a backend placeholder in -# CF_OAUTH_TOKEN, write that placeholder into CF CLI config so the CLI sends an -# Authorization header; the backend proxy swaps the placeholder on the wire. +# CF_OAUTH_TOKEN, validate that it is not raw token material. The stock CF CLI +# parses AccessToken locally, so this script deliberately does not write a +# non-JWT placeholder into ~/.cf/config.json and overclaim authenticated cf CLI +# support. log() { printf '%s\n' "cloud-gov: $*" >&2; } -json_escape() { - # CF_OAUTH_TOKEN should be a generated placeholder, but escape defensively - # before embedding it in JSON. Newlines are not valid in this single-value path. - printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g' -} - mkdir -p "$HOME/.cf" - -if command -v cf >/dev/null 2>&1; then - cf api https://api.fr.cloud.gov >/dev/null 2>&1 || log "warning: cf api target failed; check egress/TLS" -fi +chmod 0700 "$HOME/.cf" if [ -n "${CF_OAUTH_TOKEN:-}" ]; then case "$CF_OAUTH_TOKEN" in - *eyJ*.*.*) - log "CF_OAUTH_TOKEN looks like real JWT token material; refusing to write it" + acq_placeholder_?*|sbx-cs-?*) + : + ;; + *) + log "CF_OAUTH_TOKEN is not a recognized acq/sbx placeholder; refusing startup" + exit 1 + ;; + esac + + case "$CF_OAUTH_TOKEN" in + *[!A-Za-z0-9_-]*) + log "CF_OAUTH_TOKEN placeholder contains an unexpected character; refusing startup" exit 1 ;; esac - escaped_token="$(json_escape "$CF_OAUTH_TOKEN")" - cat >"$HOME/.cf/config.json" </dev/null 2>&1; then + CF_OAUTH_TOKEN= cf api https://api.fr.cloud.gov >/dev/null 2>&1 || log "warning: cf api target failed; check egress/TLS" +fi diff --git a/integrations/isolation/acq-kits/cloud-gov/scripts/verify b/integrations/isolation/acq-kits/cloud-gov/scripts/verify index 54c5a503..e091702b 100755 --- a/integrations/isolation/acq-kits/cloud-gov/scripts/verify +++ b/integrations/isolation/acq-kits/cloud-gov/scripts/verify @@ -10,7 +10,7 @@ set -uo pipefail KIT_DIR="$(cd "$(dirname "$0")/.." && pwd)" REPO_ROOT="$(cd "$KIT_DIR/../../../.." && pwd)" SBX_NAME="cloud-gov-verify-$$" -WORK="$(mktemp -d "$KIT_DIR/verify-workspace.XXXXXX")" +WORK="$(mktemp -d "${TMPDIR:-/tmp}/cloud-gov-verify.XXXXXX")" CREATE_LOG="$WORK/create.log" KEEP="${KEEP:-0}" RUN_ACQ="${RUN_ACQ:-0}" @@ -37,18 +37,46 @@ in_acq() { acq exec "$SBX_NAME" -- sh -c "$1" /dev/null || true; } info "0. Offline preconditions" [ -f "$KIT_DIR/spec.yaml" ] && ok "kit spec.yaml present" || { bad "spec.yaml missing"; exit 1; } [ -f "$KIT_DIR/files/home/cloud-gov-install-cf-cli.sh" ] && ok "install script present" || bad "install script missing" +[ -f "$KIT_DIR/files/home/cloud-gov-configure-cf-auth.sh" ] && ok "auth script present" || bad "auth script missing" if command -v bash >/dev/null 2>&1; then - if bash -n "$KIT_DIR/files/home/cloud-gov-install-cf-cli.sh"; then - ok "install script parses" - else - bad "install script has a shell syntax error" - fi + for script in \ + "$KIT_DIR/files/home/cloud-gov-install-cf-cli.sh" \ + "$KIT_DIR/files/home/cloud-gov-configure-cf-auth.sh"; do + if bash -n "$script"; then + ok "$(basename "$script") parses" + else + bad "$(basename "$script") has a shell syntax error" + fi + done +else + skip "bash not on PATH - skipped shell parse checks" +fi + +info "1. Auth placeholder guard" +AUTH_SCRIPT="$KIT_DIR/files/home/cloud-gov-configure-cf-auth.sh" +AUTH_PATH="$(mktemp -d "${TMPDIR:-/tmp}/cloud-gov-auth-path.XXXXXX")" +for bin in sh mkdir chmod; do + ln -s "$(command -v "$bin")" "$AUTH_PATH/$bin" +done + +AUTH_WORK="$(mktemp -d "${TMPDIR:-/tmp}/cloud-gov-auth.XXXXXX")" +if HOME="$AUTH_WORK" PATH="$AUTH_PATH" CF_OAUTH_TOKEN="acq_placeholder_abcdefghijklmnopqrstuvwxyz012345" sh "$AUTH_SCRIPT" >/dev/null 2>&1; then + ok "auth script accepts acq placeholder" +else + bad "auth script rejected acq placeholder" +fi +rm -rf "$AUTH_WORK" >/dev/null 2>&1 || true + +AUTH_WORK="$(mktemp -d "${TMPDIR:-/tmp}/cloud-gov-auth.XXXXXX")" +if HOME="$AUTH_WORK" PATH="$AUTH_PATH" CF_OAUTH_TOKEN="bearer eyJ.example.signature" sh "$AUTH_SCRIPT" >/dev/null 2>&1; then + bad "auth script accepted bearer/JWT token material" else - skip "bash not on PATH - skipped shell parse check" + ok "auth script rejects bearer/JWT token material" fi +rm -rf "$AUTH_WORK" "$AUTH_PATH" >/dev/null 2>&1 || true -info "1. Validate the neutral kit spec" +info "2. Validate the neutral kit spec" VALIDATOR="$REPO_ROOT/integrations/isolation/acq-kits/validate-kits.py" if ! command -v python3 >/dev/null 2>&1; then skip "validate-kits.py not run (python3 not on PATH)" @@ -66,14 +94,14 @@ else fi if [ "$RUN_ACQ" != "1" ]; then - info "2. Live sandbox check" + info "3. Live sandbox check" skip "RUN_ACQ=1 not set - skipped live cloud.gov sandbox verification" printf '\nSummary\n %d passed, %d failed\n' "$pass" "$fail" [ "$fail" -eq 0 ] exit $? fi -info "2. Live sandbox preconditions" +info "3. Live sandbox preconditions" if ! command -v acq >/dev/null 2>&1; then bad "acq not on PATH" exit 1 @@ -88,7 +116,7 @@ else exit 1 fi -info "3. Create sandbox with the kit" +info "4. Create sandbox with the kit" printf ' creating %s (workspace %s)...\n' "$SBX_NAME" "$WORK" if acq create --name "$SBX_NAME" --kit "$KIT_DIR" opencode "$WORK" >"$CREATE_LOG" 2>&1; then ok "sandbox created with cloud-gov kit" @@ -98,7 +126,7 @@ else exit 1 fi -info "4. CF CLI installed" +info "5. CF CLI installed" cf_version="$(in_acq 'cf version')" printf ' %s\n' "${cf_version:-}" case "$cf_version" in @@ -106,7 +134,7 @@ case "$cf_version" in *) bad "cf CLI did not report a version" ;; esac -info "5. cloud.gov API and docs reachable" +info "6. cloud.gov API and docs reachable" for url in \ https://api.fr.cloud.gov/v3/info \ https://cloud.gov/ \ @@ -121,21 +149,18 @@ for url in \ esac done -info "6. Authenticated CF API wiring" +info "7. Authenticated CF API wiring" auth_status="$(in_acq "curl -sS -o /dev/null -w '%{http_code}' -H \"Authorization: \$CF_OAUTH_TOKEN\" https://api.fr.cloud.gov/v3/organizations")" printf ' authenticated /v3/organizations -> %s\n' "${auth_status:-}" case "$auth_status" in 2*|3*) ok "authenticated CF API probe succeeded" ;; - 401|403) bad "authenticated CF API probe rejected credentials (status $auth_status)" ;; + 401|403) + bad "authenticated CF API probe rejected credentials (status $auth_status)" + printf ' Refresh the short-lived token, then retry: cf oauth-token | acq secret set -g cloud-gov --host api.fr.cloud.gov --env CF_OAUTH_TOKEN\n' >&2 + ;; *) bad "authenticated CF API probe failed (status ${auth_status:-none})" ;; esac -cf_auth="$(in_acq 'cf orgs >/dev/null 2>&1 && printf ok || printf fail')" -case "$cf_auth" in - ok) ok "cf CLI authenticated command succeeds" ;; - *) bad "cf CLI authenticated command failed" ;; -esac - info "Summary" printf ' %d passed, %d failed\n' "$pass" "$fail" [ "$fail" -eq 0 ] && printf ' \033[32mAll checks passed.\033[0m\n' || printf ' \033[31mSome checks failed - see notes above.\033[0m\n' diff --git a/integrations/isolation/acq-kits/cloud-gov/spec.yaml b/integrations/isolation/acq-kits/cloud-gov/spec.yaml index bb236ac4..752c8f3d 100644 --- a/integrations/isolation/acq-kits/cloud-gov/spec.yaml +++ b/integrations/isolation/acq-kits/cloud-gov/spec.yaml @@ -4,7 +4,7 @@ # - install/verify the Cloud Foundry CLI (`cf`) at create time; # - allow-list cloud.gov control-plane, app-route, and documentation domains; # - document the acq secret binding needed for CF OAuth token injection without -# placing the token in environment variables or files readable by the agent. +# placing the real token in files readable by the agent. # # AUTH MODEL: the kit does NOT store or expose a Cloud Foundry token. Users set a # custom acq secret from `cf oauth-token` and bind it to api.fr.cloud.gov. acq's @@ -17,21 +17,23 @@ name: cloud-gov displayName: cloud.gov Cloud Foundry CLI description: > Installs the Cloud Foundry CLI and allow-lists cloud.gov control-plane, - application-route, and documentation domains so agents can inspect and operate - cloud.gov applications through proxied egress. Cloud Foundry authorization is - supplied through acq's custom secret mechanism, using a token captured from - `cf oauth-token`, so credentials are not stored in the kit or exposed in the - sandbox environment. + application-route, and documentation domains so agents can inspect cloud.gov + applications and call the Cloud Foundry API through proxied egress. Cloud + Foundry authorization is supplied through acq's custom secret mechanism, using + a token captured from `cf oauth-token`, so credentials are not stored in the kit + or exposed as real token material in the sandbox environment. caps: network: allow: # cloud.gov apex and every cloud.gov subdomain, including API, login/UAA, - # dashboard, docs, and public application routes. Keep wildcard hosts - # quoted: leading-* YAML scalars can otherwise be parsed as aliases by + # dashboard, docs, and dynamically assigned app routes. Cloud Foundry does + # not expose a static per-user route list at kit generation time, so this is + # a deliberate broad egress exception for cloud.gov work. Keep wildcard + # hosts quoted: leading-* YAML scalars can otherwise be parsed as aliases by # downstream translators. - cloud.gov:443 - - "**.cloud.gov:443" + - "*.cloud.gov:443" # Upstream Cloud Foundry documentation. - docs.cloudfoundry.org:443 - cli.cloudfoundry.org:443 @@ -47,7 +49,7 @@ files: - path: /home/agent/cloud-gov-configure-cf-auth.sh mode: "0755" source: files/home/cloud-gov-configure-cf-auth.sh - description: Target cloud.gov and configure cf with the injected token placeholder + description: Target cloud.gov and validate any injected token placeholder commands: - phase: install @@ -58,7 +60,7 @@ commands: - /home/agent/cloud-gov-install-cf-cli.sh - phase: startup user: "1000" - description: Target cloud.gov and configure cf auth from the injected placeholder + description: Target cloud.gov and validate the injected placeholder command: - sh - /home/agent/cloud-gov-configure-cf-auth.sh @@ -67,18 +69,19 @@ agentContext: | ## cloud.gov This sandbox has the Cloud Foundry CLI (`cf`) available for cloud.gov work and - egress allow-listed for the cloud.gov API, login/UAA, log streaming, common app - route domains, and cloud.gov / Cloud Foundry documentation domains. + egress allow-listed for the cloud.gov API, login/UAA, log streaming, cloud.gov + app route domains, and cloud.gov / Cloud Foundry documentation domains. Authorization is handled through acq's secret injection path. The sandbox may - contain a backend placeholder in `CF_OAUTH_TOKEN` and `~/.cf/config.json`, but - not the real token. The user should set the secret from a host shell where they - are already logged in to cloud.gov: + contain a backend placeholder in `CF_OAUTH_TOKEN`, but not the real token. The + user should set the secret from a host shell where they are already logged in to + cloud.gov: ``` cf oauth-token | acq secret set -g cloud-gov --host api.fr.cloud.gov --env CF_OAUTH_TOKEN ``` Do not ask the user to paste the token into chat or write it to the workspace. - Use normal `cf` commands; real token material remains outside agent-readable - files. \ No newline at end of file + Use `cf` for unauthenticated targeting and direct HTTPS CF API calls for + placeholder-backed authenticated checks; real token material remains outside + agent-readable files. diff --git a/integrations/isolation/acq-kits/kits.yaml b/integrations/isolation/acq-kits/kits.yaml index 528dd240..aac5819b 100644 --- a/integrations/isolation/acq-kits/kits.yaml +++ b/integrations/isolation/acq-kits/kits.yaml @@ -31,12 +31,12 @@ kits: backends: [sbx, msb] parity: | Both backends consume the neutral files, create-time install, startup auth - configuration, and cloud.gov/docs allow-list. sbx consumes `**.cloud.gov` - directly; msb support requires acq to translate per-kit `**.` wildcard - entries to msb suffix rules. Cloud Foundry authorization is supplied through - acq's custom secret binding for api.fr.cloud.gov, seeded from a host-side - `cf oauth-token`; the token is not stored in the kit or exposed as a - readable credential file. + placeholder validation, and exact cloud.gov/control-plane/docs allow-list. + The shared kit avoids wildcard egress so sbx and msb receive concrete host + allow rules. Cloud Foundry authorization is supplied through acq's custom + secret binding for api.fr.cloud.gov, seeded from a host-side `cf + oauth-token`; the token is not stored in the kit or exposed as a readable + credential file. zscaler-ca-certificate: backends: [sbx, msb] From d16c1a88ab41fd08756c876c6349a1c32160b9ee Mon Sep 17 00:00:00 2001 From: Bret Mogilefsky Date: Thu, 1 Oct 2026 07:01:14 +0000 Subject: [PATCH 4/5] fix(isolation): tighten cloud.gov kit auth and egress Accept synthetic msb-style backend placeholders while rejecting raw bearer/JWT-like token material before writing CF CLI auth state. Replace wildcard cloud.gov egress with exact control-plane and documentation hosts, and extend offline verification for placeholder and allow-list assumptions. Co-authored-by: OpenCode Agent --- .../isolation/acq-kits/cloud-gov/README.md | 44 +++--- .../acq-kits/cloud-gov/TROUBLESHOOTING.md | 30 +++-- .../docs/decisions/cloud-gov-cli-and-auth.md | 34 ++--- .../files/home/cloud-gov-configure-cf-auth.sh | 27 ++-- .../acq-kits/cloud-gov/scripts/verify | 126 ++++++++++++++++-- .../isolation/acq-kits/cloud-gov/spec.yaml | 35 ++--- 6 files changed, 210 insertions(+), 86 deletions(-) diff --git a/integrations/isolation/acq-kits/cloud-gov/README.md b/integrations/isolation/acq-kits/cloud-gov/README.md index 03291650..4c3b61d9 100644 --- a/integrations/isolation/acq-kits/cloud-gov/README.md +++ b/integrations/isolation/acq-kits/cloud-gov/README.md @@ -14,10 +14,10 @@ sandbox. - **Cloud Foundry CLI** - uses an existing `cf` binary when the base image already has one. If `cf` is missing, installs Cloud Foundry CLI v8.19.0 from pinned upstream release tarballs with committed SHA-256 checks. -- **cloud.gov egress** - allow-lists the `cloud.gov` apex and all `cloud.gov` - subdomains, covering the Cloud Foundry API, login/UAA, log-stream hosts, - dashboard, dynamically assigned app routes, and user-facing cloud.gov - documentation. +- **cloud.gov egress** - allow-lists exact cloud.gov control-plane and + documentation hosts: the Cloud Foundry API, login/UAA, log-stream hosts, and + user-facing cloud.gov documentation. Public app routes and custom domains stay + project-specific instead of using wildcard egress. - **Cloud Foundry docs egress** - allow-lists upstream Cloud Foundry CLI and docs hosts so agents can look up command behavior and platform guidance. - **Secret-safe authorization** - documents the `acq secret set` binding for a @@ -36,8 +36,9 @@ store and binds it to `api.fr.cloud.gov`; the active backend maps that to its own proxy or secret-substitution mechanism. The kit validates that any injected `CF_OAUTH_TOKEN` value is a backend placeholder, not raw token material. -The cloud.gov wildcard uses `*.cloud.gov`, which msb accepts directly as a -suffix rule and sbx accepts as a cloud.gov subdomain wildcard. +The network allow-list avoids wildcard semantics. Both sbx and msb receive the +same exact host list, which keeps the shared kit least-privilege and makes app +route/custom-domain access an explicit project-specific decision. ## Usage @@ -87,9 +88,12 @@ cf oauth-token | acq secret set cloud-gov --host api.fr.cloud.gov The `CF_OAUTH_TOKEN` name is a placeholder binding for backend proxy/secret substitution. At startup the kit targets `https://api.fr.cloud.gov` and, when that value is present, refuses to continue unless it matches a recognized -backend-placeholder prefix. The stock CF CLI may parse `AccessToken` locally, so -the kit does not write the placeholder into CF CLI config or claim authenticated -`cf` commands work without live verification. +backend-placeholder prefix, including the msb placeholder form represented in +tests as `MSB_PLACEHOLDER_TOKEN`. The startup guard rejects bearer/JWT-like raw +token material before anything can be written to the workspace. The stock CF CLI +may parse `AccessToken` locally, so the kit does not write the placeholder into +CF CLI config or claim authenticated `cf` commands work without live +verification. ## Network Allow-List @@ -97,9 +101,16 @@ The kit allow-lists: | Host | Purpose | |------|---------| -| `cloud.gov`, `*.cloud.gov` | cloud.gov apex and all cloud.gov subdomains, including API, login/UAA, dashboard, docs, and public app routes. This is intentionally broad: Cloud Foundry assigns app routes dynamically, and a reusable kit cannot know which `*.app.cloud.gov` hosts belong to the current operator at static kit-definition time. Custom domains outside `cloud.gov` stay project-specific. | +| `api.fr.cloud.gov` | Cloud Foundry API | +| `uaa.fr.cloud.gov`, `login.fr.cloud.gov` | cloud.gov UAA and browser-login control plane | +| `doppler.fr.cloud.gov`, `log-cache.fr.cloud.gov` | Cloud Foundry log streaming/control-plane endpoints | +| `cloud.gov`, `www.cloud.gov`, `docs.cloud.gov` | cloud.gov public documentation | | `docs.cloudfoundry.org`, `cli.cloudfoundry.org` | Cloud Foundry documentation | -| `github.com`, `objects.githubusercontent.com` | Pinned Cloud Foundry CLI release tarball download | +| `github.com`, `objects.githubusercontent.com` | Pinned Cloud Foundry CLI release tarball download during kit install; if runtime access to these hosts is not acceptable for a deployment context, use a base image with `cf` preinstalled or a project-specific vetted artifact source. | + +The kit intentionally does not allow `*.cloud.gov`. If the project needs a +specific app route, route service, or custom domain, add that exact host in a +project-specific kit or sandbox policy after review. ## Using `cf` @@ -129,7 +140,7 @@ Failure-mode notes are in [`TROUBLESHOOTING.md`](TROUBLESHOOTING.md). ## Design Decisions - [`docs/decisions/cloud-gov-cli-and-auth.md`](docs/decisions/cloud-gov-cli-and-auth.md) - - why this kit installs `cf`, uses broad `cloud.gov` egress, and relies on acq + - why this kit installs `cf`, uses exact cloud.gov egress, and relies on acq placeholder-based auth. ## Verifying @@ -141,8 +152,9 @@ Run the bundled check: ``` By default it runs offline checks only: repository kit validation, shell syntax -for kit scripts, and auth-script guard tests. Set `RUN_ACQ=1` to create a -throwaway sandbox and verify that `cf` is installed, cloud.gov/docs endpoints are -reachable, and the injected placeholder reaches the CF API through a direct HTTPS -request. `RUN_ACQ=1` requires a global `cloud-gov` acq secret. Set `KEEP=1` with +for kit scripts, auth-script guard tests with synthetic placeholders, and exact +egress allow-list assertions. Set `RUN_ACQ=1` to create a throwaway sandbox and +verify that `cf` is installed, cloud.gov/docs endpoints are reachable, and the +injected placeholder reaches the CF API through a direct HTTPS request. +`RUN_ACQ=1` requires a global `cloud-gov` acq secret. Set `KEEP=1` with `RUN_ACQ=1` to keep the sandbox for inspection. diff --git a/integrations/isolation/acq-kits/cloud-gov/TROUBLESHOOTING.md b/integrations/isolation/acq-kits/cloud-gov/TROUBLESHOOTING.md index 328ddaea..e8bbf87a 100644 --- a/integrations/isolation/acq-kits/cloud-gov/TROUBLESHOOTING.md +++ b/integrations/isolation/acq-kits/cloud-gov/TROUBLESHOOTING.md @@ -54,11 +54,12 @@ or no current user. ``` - **Unexpected token value in the sandbox.** The startup script only accepts - recognized backend placeholders (`acq_placeholder_*` or `sbx-cs-*`) in - `CF_OAUTH_TOKEN`. If a raw `bearer ...` token, a JWT-looking value, or a value - with control characters appears in the sandbox, startup fails closed. Refresh - the acq secret from the host and recreate the sandbox. Do not paste the token - into chat or commit it to the workspace. + recognized backend placeholders in `CF_OAUTH_TOKEN`, including sbx/acq forms + and the msb placeholder form represented in tests as `MSB_PLACEHOLDER_TOKEN`. + If a raw `bearer ...` token, a JWT-looking value, or a value with control + characters appears in the sandbox, startup fails closed before writing CF CLI + auth state. Refresh the acq secret from the host and recreate the sandbox. Do + not paste the token into chat or commit it to the workspace. - **CF CLI wants a local login.** The kit does not write the placeholder into `~/.cf/config.json` because stock `cf` may parse cached tokens locally before sending a request. Use direct Cloud Foundry API calls with the placeholder auth @@ -72,14 +73,14 @@ cloud.gov app route fails because egress is denied. **Causes and fixes:** -- **Route outside cloud.gov.** The kit allow-lists `cloud.gov` and - `*.cloud.gov` because Cloud Foundry app routes are assigned dynamically and a - reusable kit cannot know which `*.app.cloud.gov` hosts belong to the current - operator at static kit-definition time. If your app uses a custom domain or an - internal route outside `cloud.gov`, add a separate project-specific network - allow-list entry rather than broadening this shared kit. -- **Wildcard not supported by the active backend.** Confirm the generated backend - policy from `acq`; if needed, add the specific app host as a per-sandbox allow. +- **Expected shared-kit limit.** The kit intentionally allow-lists exact + cloud.gov control-plane and documentation hosts only. It does not include a + `*.cloud.gov` wildcard, because wildcard semantics differ by backend and widen + egress beyond least privilege. Add the specific app route, route service, or + custom domain as a project-specific network allow-list entry after review. +- **Backend parity check.** Confirm the generated backend policy from `acq` + contains the exact host you added; both sbx and msb should receive concrete + host allow rules, not a suffix wildcard. ## Docs Are Not Reachable @@ -88,6 +89,7 @@ cloud.gov app route fails because egress is denied. **Causes and fixes:** - Confirm the requested host is one of `cloud.gov`, `www.cloud.gov`, - `docs.cloud.gov`, `docs.cloudfoundry.org`, or `cli.cloudfoundry.org`. + `docs.cloud.gov`, `docs.cloudfoundry.org`, `cli.cloudfoundry.org`, or an exact + cloud.gov control-plane host listed in `spec.yaml`. - If docs pull assets from another host and the backend blocks them, add the specific observed host after review instead of adding a broad wildcard. diff --git a/integrations/isolation/acq-kits/cloud-gov/docs/decisions/cloud-gov-cli-and-auth.md b/integrations/isolation/acq-kits/cloud-gov/docs/decisions/cloud-gov-cli-and-auth.md index 8da4fea3..854d0251 100644 --- a/integrations/isolation/acq-kits/cloud-gov/docs/decisions/cloud-gov-cli-and-auth.md +++ b/integrations/isolation/acq-kits/cloud-gov/docs/decisions/cloud-gov-cli-and-auth.md @@ -5,9 +5,9 @@ ## Context cloud.gov operations use the Cloud Foundry CLI (`cf`) against the cloud.gov -Cloud Foundry API. A sandbox used for this work needs the CLI, egress to the -cloud.gov platform and application-route domains, and access to cloud.gov and -Cloud Foundry documentation. +Cloud Foundry API. A sandbox used for this work needs the CLI, egress to exact +cloud.gov control-plane hosts, and access to cloud.gov and Cloud Foundry +documentation. Authentication is sensitive. A token from `cf oauth-token` must not be committed, printed in chat, or written into agent-readable workspace files. At the same @@ -21,8 +21,8 @@ Create a neutral `cloud-gov` acq mixin kit for sbx and msb that: 1. Installs `cf` at create time from pinned Cloud Foundry CLI release tarballs with committed SHA-256 values. -2. Allow-lists `cloud.gov`, all `cloud.gov` subdomains, and Cloud Foundry docs - domains. +2. Allow-lists exact cloud.gov control-plane/documentation hosts and Cloud + Foundry docs domains, without wildcard semantics. 3. Uses acq's custom secret binding for a host-side token captured with `cf oauth-token`: @@ -31,9 +31,10 @@ Create a neutral `cloud-gov` acq mixin kit for sbx and msb that: ``` 4. Validates any injected `CF_OAUTH_TOKEN` value as a recognized backend - placeholder and refuses raw token-looking values. The placeholder can be used - for direct HTTPS Cloud Foundry API calls that the backend proxy rewrites on the - way to `api.fr.cloud.gov`. + placeholder, including the msb placeholder form represented in tests as + `MSB_PLACEHOLDER_TOKEN`, and refuses raw token-looking values. The placeholder + can be used for direct HTTPS Cloud Foundry API calls that the backend proxy + rewrites on the way to `api.fr.cloud.gov`. The kit does not store the real Cloud Foundry OAuth token in `spec.yaml`, files, or repository docs. The token remains in the host-side acq secret store and the @@ -51,15 +52,14 @@ live-tested path because the stock CLI may parse cached tokens locally. - The install currently supports Linux x86_64/amd64 and arm64/aarch64 release tarballs. Other architectures should either preinstall `cf` or add a pinned, integrity-checked install path before being supported. -- `*.cloud.gov` is intentionally broad because this kit is specifically for - cloud.gov work and Cloud Foundry app routes are assigned dynamically. A static - shared kit cannot know which `*.app.cloud.gov` hosts belong to the current - operator, so this widens egress to tenant app routes under `cloud.gov` as an - accepted blast-radius tradeoff. Custom domains and internal routes outside - `cloud.gov` should be added per project rather than broadening this shared kit. -- The wildcard is written as `*.cloud.gov` so current msb versions accept it - directly as a suffix rule while sbx still treats it as a cloud.gov subdomain - wildcard. +- Egress is exact-host only for shared-kit backend parity and least privilege. + The kit includes the Cloud Foundry API, UAA/login, log streaming, cloud.gov + documentation, Cloud Foundry documentation, and pinned CLI download hosts. + The GitHub hosts are needed only when the base image lacks `cf`; if runtime + access to those hosts is unacceptable, use a base image with `cf` preinstalled + or a project-specific vetted artifact source. Public app routes, route + services, and custom domains are intentionally not included because they are + project-specific and should be added as exact host rules after review. - Live verification with `RUN_ACQ=1` requires a configured `cloud-gov` secret and must exercise raw authenticated CF API access through the backend placeholder. Offline verification remains useful for schema/shell checks and auth guard diff --git a/integrations/isolation/acq-kits/cloud-gov/files/home/cloud-gov-configure-cf-auth.sh b/integrations/isolation/acq-kits/cloud-gov/files/home/cloud-gov-configure-cf-auth.sh index 3d8ff6c4..b3f350df 100755 --- a/integrations/isolation/acq-kits/cloud-gov/files/home/cloud-gov-configure-cf-auth.sh +++ b/integrations/isolation/acq-kits/cloud-gov/files/home/cloud-gov-configure-cf-auth.sh @@ -4,8 +4,7 @@ set -eu # Configure the CF CLI to use cloud.gov. If acq injected a backend placeholder in # CF_OAUTH_TOKEN, validate that it is not raw token material. The stock CF CLI # parses AccessToken locally, so this script deliberately does not write a -# non-JWT placeholder into ~/.cf/config.json and overclaim authenticated cf CLI -# support. +# placeholder into ~/.cf/config.json and overclaim authenticated cf CLI support. log() { printf '%s\n' "cloud-gov: $*" >&2; } @@ -14,18 +13,26 @@ chmod 0700 "$HOME/.cf" if [ -n "${CF_OAUTH_TOKEN:-}" ]; then case "$CF_OAUTH_TOKEN" in - acq_placeholder_?*|sbx-cs-?*) - : - ;; - *) - log "CF_OAUTH_TOKEN is not a recognized acq/sbx placeholder; refusing startup" + *[![:print:]]*|*[[:space:]]*) + log "CF_OAUTH_TOKEN placeholder contains an unsafe character; refusing startup" exit 1 ;; esac case "$CF_OAUTH_TOKEN" in - *[!A-Za-z0-9_-]*) - log "CF_OAUTH_TOKEN placeholder contains an unexpected character; refusing startup" + bearer*|Bearer*|BEARER*) + log "CF_OAUTH_TOKEN looks like raw token material; refusing startup" + exit 1 + ;; + acq_placeholder_?*|sbx-cs-?*|MSB_PLACEHOLDER?*|\) + : + ;; + *.*.*) + log "CF_OAUTH_TOKEN looks like raw token material; refusing startup" + exit 1 + ;; + *) + log "CF_OAUTH_TOKEN is not a recognized backend placeholder; refusing startup" exit 1 ;; esac @@ -36,5 +43,5 @@ else fi if command -v cf >/dev/null 2>&1; then - CF_OAUTH_TOKEN= cf api https://api.fr.cloud.gov >/dev/null 2>&1 || log "warning: cf api target failed; check egress/TLS" + env CF_OAUTH_TOKEN= cf api https://api.fr.cloud.gov >/dev/null 2>&1 || log "warning: cf api target failed; check egress/TLS" fi diff --git a/integrations/isolation/acq-kits/cloud-gov/scripts/verify b/integrations/isolation/acq-kits/cloud-gov/scripts/verify index e091702b..1e77853d 100755 --- a/integrations/isolation/acq-kits/cloud-gov/scripts/verify +++ b/integrations/isolation/acq-kits/cloud-gov/scripts/verify @@ -3,7 +3,7 @@ # verify - host-side validation for the cloud-gov acq mixin kit. # # By default this runs offline checks only. Set RUN_ACQ=1 to create a throwaway -# sandbox and verify the CF CLI plus cloud.gov/docs network reachability. +# sandbox and verify the CF CLI plus exact cloud.gov/docs network reachability. set -uo pipefail @@ -35,9 +35,22 @@ trap cleanup EXIT INT TERM in_acq() { acq exec "$SBX_NAME" -- sh -c "$1" /dev/null || true; } info "0. Offline preconditions" -[ -f "$KIT_DIR/spec.yaml" ] && ok "kit spec.yaml present" || { bad "spec.yaml missing"; exit 1; } -[ -f "$KIT_DIR/files/home/cloud-gov-install-cf-cli.sh" ] && ok "install script present" || bad "install script missing" -[ -f "$KIT_DIR/files/home/cloud-gov-configure-cf-auth.sh" ] && ok "auth script present" || bad "auth script missing" +if [ -f "$KIT_DIR/spec.yaml" ]; then + ok "kit spec.yaml present" +else + bad "spec.yaml missing" + exit 1 +fi +if [ -f "$KIT_DIR/files/home/cloud-gov-install-cf-cli.sh" ]; then + ok "install script present" +else + bad "install script missing" +fi +if [ -f "$KIT_DIR/files/home/cloud-gov-configure-cf-auth.sh" ]; then + ok "auth script present" +else + bad "auth script missing" +fi if command -v bash >/dev/null 2>&1; then for script in \ @@ -69,14 +82,92 @@ fi rm -rf "$AUTH_WORK" >/dev/null 2>&1 || true AUTH_WORK="$(mktemp -d "${TMPDIR:-/tmp}/cloud-gov-auth.XXXXXX")" -if HOME="$AUTH_WORK" PATH="$AUTH_PATH" CF_OAUTH_TOKEN="bearer eyJ.example.signature" sh "$AUTH_SCRIPT" >/dev/null 2>&1; then +if HOME="$AUTH_WORK" PATH="$AUTH_PATH" CF_OAUTH_TOKEN="MSB_PLACEHOLDER_TOKEN" sh "$AUTH_SCRIPT" >/dev/null 2>&1; then + ok "auth script accepts msb placeholder token" +else + bad "auth script rejected msb placeholder token" +fi +rm -rf "$AUTH_WORK" >/dev/null 2>&1 || true + +AUTH_WORK="$(mktemp -d "${TMPDIR:-/tmp}/cloud-gov-auth.XXXXXX")" +if HOME="$AUTH_WORK" PATH="$AUTH_PATH" CF_OAUTH_TOKEN="" sh "$AUTH_SCRIPT" >/dev/null 2>&1; then + ok "auth script accepts msb bracket placeholder" +else + bad "auth script rejected msb bracket placeholder" +fi +rm -rf "$AUTH_WORK" >/dev/null 2>&1 || true + +AUTH_WORK="$(mktemp -d "${TMPDIR:-/tmp}/cloud-gov-auth.XXXXXX")" +if HOME="$AUTH_WORK" PATH="$AUTH_PATH" CF_OAUTH_TOKEN="bearer TOKEN_HEADER.TOKEN_BODY.TOKEN_SIGNATURE" sh "$AUTH_SCRIPT" >/dev/null 2>&1; then bad "auth script accepted bearer/JWT token material" else ok "auth script rejects bearer/JWT token material" fi +rm -rf "$AUTH_WORK" >/dev/null 2>&1 || true + +AUTH_WORK="$(mktemp -d "${TMPDIR:-/tmp}/cloud-gov-auth.XXXXXX")" +if HOME="$AUTH_WORK" PATH="$AUTH_PATH" CF_OAUTH_TOKEN="TOKEN_HEADER.TOKEN_BODY.TOKEN_SIGNATURE" sh "$AUTH_SCRIPT" >/dev/null 2>&1; then + bad "auth script accepted JWT-like token material" +else + ok "auth script rejects JWT-like token material" +fi rm -rf "$AUTH_WORK" "$AUTH_PATH" >/dev/null 2>&1 || true -info "2. Validate the neutral kit spec" +info "2. Network allow-list assumptions" +if grep -q '\*' "$KIT_DIR/spec.yaml"; then + bad "spec.yaml contains wildcard egress" +else + ok "spec.yaml avoids wildcard egress" +fi + +EXPECTED_HOSTS=$(mktemp "${TMPDIR:-/tmp}/cloud-gov-expected-hosts.XXXXXX") +ACTUAL_HOSTS=$(mktemp "${TMPDIR:-/tmp}/cloud-gov-actual-hosts.XXXXXX") +cat >"$EXPECTED_HOSTS" <<'HOSTS' +api.fr.cloud.gov:443 +uaa.fr.cloud.gov:443 +login.fr.cloud.gov:443 +doppler.fr.cloud.gov:443 +log-cache.fr.cloud.gov:443 +cloud.gov:443 +www.cloud.gov:443 +docs.cloud.gov:443 +docs.cloudfoundry.org:443 +cli.cloudfoundry.org:443 +github.com:443 +objects.githubusercontent.com:443 +HOSTS +python3 - "$KIT_DIR/spec.yaml" >"$ACTUAL_HOSTS" <<'PY' +import sys +from pathlib import Path + +in_allow = False +for line in Path(sys.argv[1]).read_text().splitlines(): + if line == " allow:": + in_allow = True + continue + if in_allow and line.startswith(" - "): + print(line.split("- ", 1)[1].strip().strip('"')) + continue + if in_allow and line and not line.startswith(" "): + break +PY +while IFS= read -r host; do + if grep -qx -- "$host" "$ACTUAL_HOSTS"; then + ok "spec.yaml allows $host" + else + bad "spec.yaml missing $host" + fi +done <"$EXPECTED_HOSTS" +while IFS= read -r host; do + if grep -qx -- "$host" "$EXPECTED_HOSTS"; then + : + else + bad "spec.yaml has unexpected egress host $host" + fi +done <"$ACTUAL_HOSTS" +rm -f "$EXPECTED_HOSTS" "$ACTUAL_HOSTS" + +info "3. Validate the neutral kit spec" VALIDATOR="$REPO_ROOT/integrations/isolation/acq-kits/validate-kits.py" if ! command -v python3 >/dev/null 2>&1; then skip "validate-kits.py not run (python3 not on PATH)" @@ -89,19 +180,23 @@ else ok "acq-kits validate-kits.py passes" else bad "validate-kits.py failed:" - printf '%s\n' "$val_out" | sed 's/^/ | /' >&2 + while IFS= read -r line; do + printf ' | %s\n' "$line" >&2 + done </dev/null 2>&1; then bad "acq not on PATH" exit 1 @@ -116,7 +211,7 @@ else exit 1 fi -info "4. Create sandbox with the kit" +info "5. Create sandbox with the kit" printf ' creating %s (workspace %s)...\n' "$SBX_NAME" "$WORK" if acq create --name "$SBX_NAME" --kit "$KIT_DIR" opencode "$WORK" >"$CREATE_LOG" 2>&1; then ok "sandbox created with cloud-gov kit" @@ -126,7 +221,7 @@ else exit 1 fi -info "5. CF CLI installed" +info "6. CF CLI installed" cf_version="$(in_acq 'cf version')" printf ' %s\n' "${cf_version:-}" case "$cf_version" in @@ -134,10 +229,15 @@ case "$cf_version" in *) bad "cf CLI did not report a version" ;; esac -info "6. cloud.gov API and docs reachable" +info "7. cloud.gov API and docs reachable" for url in \ https://api.fr.cloud.gov/v3/info \ + https://uaa.fr.cloud.gov/ \ + https://login.fr.cloud.gov/ \ + https://doppler.fr.cloud.gov/ \ + https://log-cache.fr.cloud.gov/ \ https://cloud.gov/ \ + https://www.cloud.gov/ \ https://docs.cloud.gov/ \ https://docs.cloudfoundry.org/ \ https://cli.cloudfoundry.org/; do @@ -149,7 +249,7 @@ for url in \ esac done -info "7. Authenticated CF API wiring" +info "8. Authenticated CF API wiring" auth_status="$(in_acq "curl -sS -o /dev/null -w '%{http_code}' -H \"Authorization: \$CF_OAUTH_TOKEN\" https://api.fr.cloud.gov/v3/organizations")" printf ' authenticated /v3/organizations -> %s\n' "${auth_status:-}" case "$auth_status" in diff --git a/integrations/isolation/acq-kits/cloud-gov/spec.yaml b/integrations/isolation/acq-kits/cloud-gov/spec.yaml index 752c8f3d..6d6ad9c6 100644 --- a/integrations/isolation/acq-kits/cloud-gov/spec.yaml +++ b/integrations/isolation/acq-kits/cloud-gov/spec.yaml @@ -2,7 +2,7 @@ # # Configures a sandbox for cloud.gov work: # - install/verify the Cloud Foundry CLI (`cf`) at create time; -# - allow-list cloud.gov control-plane, app-route, and documentation domains; +# - allow-list exact cloud.gov control-plane and documentation hosts; # - document the acq secret binding needed for CF OAuth token injection without # placing the real token in files readable by the agent. # @@ -16,24 +16,26 @@ kind: mixin name: cloud-gov displayName: cloud.gov Cloud Foundry CLI description: > - Installs the Cloud Foundry CLI and allow-lists cloud.gov control-plane, - application-route, and documentation domains so agents can inspect cloud.gov - applications and call the Cloud Foundry API through proxied egress. Cloud - Foundry authorization is supplied through acq's custom secret mechanism, using - a token captured from `cf oauth-token`, so credentials are not stored in the kit - or exposed as real token material in the sandbox environment. + Installs the Cloud Foundry CLI and allow-lists exact cloud.gov control-plane + and documentation hosts so agents can inspect cloud.gov metadata and call the + Cloud Foundry API through proxied egress. Cloud Foundry authorization is + supplied through acq's custom secret mechanism, using a token captured from `cf + oauth-token`, so credentials are not stored in the kit or exposed as real token + material in the sandbox environment. caps: network: allow: - # cloud.gov apex and every cloud.gov subdomain, including API, login/UAA, - # dashboard, docs, and dynamically assigned app routes. Cloud Foundry does - # not expose a static per-user route list at kit generation time, so this is - # a deliberate broad egress exception for cloud.gov work. Keep wildcard - # hosts quoted: leading-* YAML scalars can otherwise be parsed as aliases by - # downstream translators. + # Exact cloud.gov control-plane and documentation hosts. Avoid wildcard + # semantics so sbx and msb translate the same least-privilege egress shape. + - api.fr.cloud.gov:443 + - uaa.fr.cloud.gov:443 + - login.fr.cloud.gov:443 + - doppler.fr.cloud.gov:443 + - log-cache.fr.cloud.gov:443 - cloud.gov:443 - - "*.cloud.gov:443" + - www.cloud.gov:443 + - docs.cloud.gov:443 # Upstream Cloud Foundry documentation. - docs.cloudfoundry.org:443 - cli.cloudfoundry.org:443 @@ -69,8 +71,9 @@ agentContext: | ## cloud.gov This sandbox has the Cloud Foundry CLI (`cf`) available for cloud.gov work and - egress allow-listed for the cloud.gov API, login/UAA, log streaming, cloud.gov - app route domains, and cloud.gov / Cloud Foundry documentation domains. + egress allow-listed for exact cloud.gov API, login/UAA, log streaming, and + cloud.gov / Cloud Foundry documentation hosts. Public app routes and custom + domains require project-specific exact host allow-list entries. Authorization is handled through acq's secret injection path. The sandbox may contain a backend placeholder in `CF_OAUTH_TOKEN`, but not the real token. The From e2a3091b081eff1609f803f36d0bcb9b29611870 Mon Sep 17 00:00:00 2001 From: Bret Mogilefsky Date: Wed, 14 Oct 2026 03:20:19 +0000 Subject: [PATCH 5/5] fix(isolation): accept msb cloud.gov secret placeholder Accept the documented msb guest-secret placeholder in the cloud.gov auth guard and cover it in offline verification. Align kit documentation with the implemented domain boundary. Co-authored-by: OpenCode Agent --- integrations/isolation/acq-kits/README.md | 2 +- .../isolation/acq-kits/cloud-gov/README.md | 6 +++--- .../acq-kits/cloud-gov/TROUBLESHOOTING.md | 2 +- .../docs/decisions/cloud-gov-cli-and-auth.md | 8 ++++---- .../files/home/cloud-gov-configure-cf-auth.sh | 3 ++- .../isolation/acq-kits/cloud-gov/scripts/verify | 15 ++++----------- 6 files changed, 15 insertions(+), 21 deletions(-) diff --git a/integrations/isolation/acq-kits/README.md b/integrations/isolation/acq-kits/README.md index 86b59a05..811aa526 100644 --- a/integrations/isolation/acq-kits/README.md +++ b/integrations/isolation/acq-kits/README.md @@ -20,7 +20,7 @@ not agent behavior. (Behavioral patterns live in `skills/`, `prompts/`, etc.) |-----|---------| | [`usai-provider/`](usai-provider/) | Configure the agent to use the GSA USAi model provider (OpenCode today), with egress allow-listed. | | [`agentic-coding-playbook/`](agentic-coding-playbook/) | Clone the GSA agentic-coding-playbook at startup and link its `AGENTS.md` + skills into each agent's search paths. | -| [`cloud-gov/`](cloud-gov/) | Install the Cloud Foundry CLI and allow-list cloud.gov platform, app-route, dashboard, and docs domains, with auth supplied through acq secrets. | +| [`cloud-gov/`](cloud-gov/) | Install the Cloud Foundry CLI and allow-list cloud.gov platform, dashboard, and docs domains, with auth supplied through acq secrets. | | [`zscaler-ca-certificate/`](zscaler-ca-certificate/) | Trust the public Zscaler Root CA in the sandbox (msb: native `--trust-host-cas`; sbx: file-drop + `update-ca-certificates`). | | [`git-ssh-sign/`](git-ssh-sign/) | Sign git commits and tags with the SSH key forwarded from the host agent (vendored from sbx-kits-contrib). | | [`openchamber/`](openchamber/) | Run OpenChamber, a browser UI for OpenCode, inside the sandbox alongside the terminal TUI. Opt-in (see its parity note). | diff --git a/integrations/isolation/acq-kits/cloud-gov/README.md b/integrations/isolation/acq-kits/cloud-gov/README.md index 4c3b61d9..ddfd645c 100644 --- a/integrations/isolation/acq-kits/cloud-gov/README.md +++ b/integrations/isolation/acq-kits/cloud-gov/README.md @@ -88,9 +88,9 @@ cf oauth-token | acq secret set cloud-gov --host api.fr.cloud.gov The `CF_OAUTH_TOKEN` name is a placeholder binding for backend proxy/secret substitution. At startup the kit targets `https://api.fr.cloud.gov` and, when that value is present, refuses to continue unless it matches a recognized -backend-placeholder prefix, including the msb placeholder form represented in -tests as `MSB_PLACEHOLDER_TOKEN`. The startup guard rejects bearer/JWT-like raw -token material before anything can be written to the workspace. The stock CF CLI +backend-placeholder prefix, including the documented msb guest-secret placeholder +prefix. The startup guard rejects bearer/JWT-like raw token material before +anything can be written to the workspace. The stock CF CLI may parse `AccessToken` locally, so the kit does not write the placeholder into CF CLI config or claim authenticated `cf` commands work without live verification. diff --git a/integrations/isolation/acq-kits/cloud-gov/TROUBLESHOOTING.md b/integrations/isolation/acq-kits/cloud-gov/TROUBLESHOOTING.md index e8bbf87a..9eb048b7 100644 --- a/integrations/isolation/acq-kits/cloud-gov/TROUBLESHOOTING.md +++ b/integrations/isolation/acq-kits/cloud-gov/TROUBLESHOOTING.md @@ -55,7 +55,7 @@ or no current user. - **Unexpected token value in the sandbox.** The startup script only accepts recognized backend placeholders in `CF_OAUTH_TOKEN`, including sbx/acq forms - and the msb placeholder form represented in tests as `MSB_PLACEHOLDER_TOKEN`. + and the documented msb guest-secret placeholder prefix. If a raw `bearer ...` token, a JWT-looking value, or a value with control characters appears in the sandbox, startup fails closed before writing CF CLI auth state. Refresh the acq secret from the host and recreate the sandbox. Do diff --git a/integrations/isolation/acq-kits/cloud-gov/docs/decisions/cloud-gov-cli-and-auth.md b/integrations/isolation/acq-kits/cloud-gov/docs/decisions/cloud-gov-cli-and-auth.md index 854d0251..6e47e994 100644 --- a/integrations/isolation/acq-kits/cloud-gov/docs/decisions/cloud-gov-cli-and-auth.md +++ b/integrations/isolation/acq-kits/cloud-gov/docs/decisions/cloud-gov-cli-and-auth.md @@ -31,10 +31,10 @@ Create a neutral `cloud-gov` acq mixin kit for sbx and msb that: ``` 4. Validates any injected `CF_OAUTH_TOKEN` value as a recognized backend - placeholder, including the msb placeholder form represented in tests as - `MSB_PLACEHOLDER_TOKEN`, and refuses raw token-looking values. The placeholder - can be used for direct HTTPS Cloud Foundry API calls that the backend proxy - rewrites on the way to `api.fr.cloud.gov`. + placeholder, including the documented msb guest-secret placeholder prefix, + and refuses raw token-looking values. The placeholder can be used for direct + HTTPS Cloud Foundry API calls that the backend proxy rewrites on the way to + `api.fr.cloud.gov`. The kit does not store the real Cloud Foundry OAuth token in `spec.yaml`, files, or repository docs. The token remains in the host-side acq secret store and the diff --git a/integrations/isolation/acq-kits/cloud-gov/files/home/cloud-gov-configure-cf-auth.sh b/integrations/isolation/acq-kits/cloud-gov/files/home/cloud-gov-configure-cf-auth.sh index b3f350df..16c64c44 100755 --- a/integrations/isolation/acq-kits/cloud-gov/files/home/cloud-gov-configure-cf-auth.sh +++ b/integrations/isolation/acq-kits/cloud-gov/files/home/cloud-gov-configure-cf-auth.sh @@ -12,6 +12,7 @@ mkdir -p "$HOME/.cf" chmod 0700 "$HOME/.cf" if [ -n "${CF_OAUTH_TOKEN:-}" ]; then + msb_prefix='$'MSB_ case "$CF_OAUTH_TOKEN" in *[![:print:]]*|*[[:space:]]*) log "CF_OAUTH_TOKEN placeholder contains an unsafe character; refusing startup" @@ -24,7 +25,7 @@ if [ -n "${CF_OAUTH_TOKEN:-}" ]; then log "CF_OAUTH_TOKEN looks like raw token material; refusing startup" exit 1 ;; - acq_placeholder_?*|sbx-cs-?*|MSB_PLACEHOLDER?*|\) + acq_placeholder_?*|sbx-cs-?*|"$msb_prefix"?*) : ;; *.*.*) diff --git a/integrations/isolation/acq-kits/cloud-gov/scripts/verify b/integrations/isolation/acq-kits/cloud-gov/scripts/verify index 1e77853d..d46ffbb4 100755 --- a/integrations/isolation/acq-kits/cloud-gov/scripts/verify +++ b/integrations/isolation/acq-kits/cloud-gov/scripts/verify @@ -82,18 +82,11 @@ fi rm -rf "$AUTH_WORK" >/dev/null 2>&1 || true AUTH_WORK="$(mktemp -d "${TMPDIR:-/tmp}/cloud-gov-auth.XXXXXX")" -if HOME="$AUTH_WORK" PATH="$AUTH_PATH" CF_OAUTH_TOKEN="MSB_PLACEHOLDER_TOKEN" sh "$AUTH_SCRIPT" >/dev/null 2>&1; then - ok "auth script accepts msb placeholder token" +MSB_PLACEHOLDER='$'MSB_SECRET_PLACEHOLDER +if HOME="$AUTH_WORK" PATH="$AUTH_PATH" CF_OAUTH_TOKEN="$MSB_PLACEHOLDER" sh "$AUTH_SCRIPT" >/dev/null 2>&1; then + ok "auth script accepts msb guest secret placeholder" else - bad "auth script rejected msb placeholder token" -fi -rm -rf "$AUTH_WORK" >/dev/null 2>&1 || true - -AUTH_WORK="$(mktemp -d "${TMPDIR:-/tmp}/cloud-gov-auth.XXXXXX")" -if HOME="$AUTH_WORK" PATH="$AUTH_PATH" CF_OAUTH_TOKEN="" sh "$AUTH_SCRIPT" >/dev/null 2>&1; then - ok "auth script accepts msb bracket placeholder" -else - bad "auth script rejected msb bracket placeholder" + bad "auth script rejected msb guest secret placeholder" fi rm -rf "$AUTH_WORK" >/dev/null 2>&1 || true