From 92cfa20e8f7f8d12b9f9b78d5100b6522ad3b886 Mon Sep 17 00:00:00 2001 From: Robby Cochran Date: Thu, 17 Sep 2026 11:32:10 -0700 Subject: [PATCH 1/3] debug(acs-ci): probe OpenShell metadata endpoint --- tasks/acs-ci-nightly/workflow/preflight.yaml | 22 ++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tasks/acs-ci-nightly/workflow/preflight.yaml b/tasks/acs-ci-nightly/workflow/preflight.yaml index cd09eac..fc5ddeb 100644 --- a/tasks/acs-ci-nightly/workflow/preflight.yaml +++ b/tasks/acs-ci-nightly/workflow/preflight.yaml @@ -62,6 +62,28 @@ agent: fi echo + echo "GCE metadata configuration" + echo "GCE_METADATA_ROOT: ${GCE_METADATA_ROOT:-unset}" + echo "GCE_METADATA_IP: ${GCE_METADATA_IP:-unset}" + echo "GCE_METADATA_HOST: ${GCE_METADATA_HOST:-unset}" + for metadata_url in \ + "http://127.0.0.1:8174/" \ + "http://127.0.0.1:8174/computeMetadata/v1/instance/service-accounts" \ + "http://127.0.0.1:8174/computeMetadata/v1/instance/service-accounts/default/token"; do + metadata_error=/tmp/preflight-metadata-error + metadata_status=$(curl --noproxy '*' --silent --show-error --output /dev/null \ + --write-out '%{http_code}' --connect-timeout 3 --max-time 5 \ + -H 'Metadata-Flavor: Google' "$metadata_url" 2>"$metadata_error") + metadata_exit=$? + if [ "$metadata_exit" -eq 0 ]; then + echo "Metadata: $metadata_url (HTTP $metadata_status)" + else + echo "Metadata: $metadata_url (curl exit $metadata_exit)" + sed -n '1,5p' "$metadata_error" + fi + done + echo + echo "GCS: gsutil ls -b gs://$GCS_BUCKET" if env GCE_METADATA_ROOT=127.0.0.1:8174 \ GCE_METADATA_IP=127.0.0.1:8174 \ From 85c8b3c33a66e31f9e6de4df51e007927f65901d Mon Sep 17 00:00:00 2001 From: Robby Cochran Date: Thu, 17 Sep 2026 11:34:26 -0700 Subject: [PATCH 2/3] fix(acs-ci): avoid config interpolation in metadata probe --- tasks/acs-ci-nightly/workflow/preflight.yaml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tasks/acs-ci-nightly/workflow/preflight.yaml b/tasks/acs-ci-nightly/workflow/preflight.yaml index fc5ddeb..b2097fa 100644 --- a/tasks/acs-ci-nightly/workflow/preflight.yaml +++ b/tasks/acs-ci-nightly/workflow/preflight.yaml @@ -63,9 +63,12 @@ agent: echo echo "GCE metadata configuration" - echo "GCE_METADATA_ROOT: ${GCE_METADATA_ROOT:-unset}" - echo "GCE_METADATA_IP: ${GCE_METADATA_IP:-unset}" - echo "GCE_METADATA_HOST: ${GCE_METADATA_HOST:-unset}" + echo -n "GCE_METADATA_ROOT: " + printenv GCE_METADATA_ROOT 2>/dev/null || echo unset + echo -n "GCE_METADATA_IP: " + printenv GCE_METADATA_IP 2>/dev/null || echo unset + echo -n "GCE_METADATA_HOST: " + printenv GCE_METADATA_HOST 2>/dev/null || echo unset for metadata_url in \ "http://127.0.0.1:8174/" \ "http://127.0.0.1:8174/computeMetadata/v1/instance/service-accounts" \ From 8bd79feb8caa36fdc681908cf779bbdf30c7fa9d Mon Sep 17 00:00:00 2001 From: Robby Cochran Date: Thu, 17 Sep 2026 11:43:16 -0700 Subject: [PATCH 3/3] feat(acs-ci): use gcloud storage for Prow results --- images/stackrox/README.md | 12 +++--- images/stackrox/sandbox-stackrox-ci/CLAUDE.md | 6 +-- .../stackrox/sandbox-stackrox-ci/Dockerfile | 20 ++++++--- tasks/acs-ci-nightly/README.md | 2 +- tasks/acs-ci-nightly/openshell/README.md | 13 +++--- tasks/acs-ci-nightly/openshell/policy.yaml | 2 + tasks/acs-ci-nightly/workflow/CI-NIGHTLY.md | 2 +- tasks/acs-ci-nightly/workflow/harness.yaml | 10 ----- tasks/acs-ci-nightly/workflow/preflight.yaml | 43 +------------------ 9 files changed, 35 insertions(+), 75 deletions(-) diff --git a/images/stackrox/README.md b/images/stackrox/README.md index 3d35469..74e2db2 100644 --- a/images/stackrox/README.md +++ b/images/stackrox/README.md @@ -28,12 +28,12 @@ An opt-in image based on the StackRox `rox-ci-image` build image the StackRox CI toolchain (Go, compilers, make, and scanner build tools) and adds the OpenShell sandbox contract, coding agents, `gh`, `uv`, `ajv-cli`, the GitHub skill, Atlassian MCP, Google Workspace CLI, and the `gopls` MCP server. -Go module and build caches stay below `/sandbox`. It deliberately does not -install `gcloud` or copy service-account keys; OpenShell providers own those -credentials and inference routes. It includes a root-owned, isolated Python -3.13 `gsutil` environment under `/opt` for read-only Prow result analysis. -The task policy allows only its fixed executables, not a writable `/sandbox` -subtree. +Go module and build caches stay below `/sandbox`. It includes a pinned +`gcloud` CLI for read-only Prow result analysis and retains a root-owned, +isolated Python 3.13 `gsutil` environment for legacy workflows. OpenShell +providers own credentials and inference routes; no service-account keys are +copied into the sandbox. The task policy allows only its fixed executables, +not a writable `/sandbox` subtree. The `rox-ci-image` build currently provides an amd64 toolchain, so this profile is published for `linux/amd64` only. It is an experimental alternative to diff --git a/images/stackrox/sandbox-stackrox-ci/CLAUDE.md b/images/stackrox/sandbox-stackrox-ci/CLAUDE.md index 95abc7f..ed9c9e6 100644 --- a/images/stackrox/sandbox-stackrox-ci/CLAUDE.md +++ b/images/stackrox/sandbox-stackrox-ci/CLAUDE.md @@ -16,9 +16,9 @@ are not part of the image. - `gh` — GitHub CLI. Use the bundled GitHub skill for REST-only API access. - `gws` — Google Workspace CLI when the provider is attached. -- `python3`, `uv`, `node`, `npm`, `go`, `gopls`, `ajv`, `git`, `curl` +- `gcloud`, `python3`, `uv`, `node`, `npm`, `go`, `gopls`, `ajv`, `git`, `curl` - `claude`, `opencode`, `codex`, and `copilot` coding agents - Atlassian and Go-analysis MCP servers through `.mcp.json` when configured -The OpenShell Vertex provider supplies model access and credentials. The image -does not install `gcloud` or copy service-account keys into the sandbox. +The OpenShell Vertex and Google Cloud providers supply model access and +credentials. The image does not copy service-account keys into the sandbox. diff --git a/images/stackrox/sandbox-stackrox-ci/Dockerfile b/images/stackrox/sandbox-stackrox-ci/Dockerfile index 5e43ade..e2a4b83 100644 --- a/images/stackrox/sandbox-stackrox-ci/Dockerfile +++ b/images/stackrox/sandbox-stackrox-ci/Dockerfile @@ -70,6 +70,17 @@ RUN curl -fsSL "https://github.com/cli/cli/releases/download/v${GH_VERSION}/gh_$ && install -m 0755 /tmp/gh-extract/gh /usr/bin/gh \ && rm -rf /tmp/gh-extract /tmp/gh.tgz +# Google Cloud CLI for read-only Prow result access. Use the standalone +# archive so the version and checksum are explicit; credentials remain +# provider-owned and are resolved through OpenShell's GCP metadata emulator. +ARG GCLOUD_VERSION=585.0.0 +ARG GCLOUD_SHA256_AMD64=82be040c2d899ddb7560c1532e809a685e2c605b97b8c24570a68638d634dcbf +RUN curl -fsSL "https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-cli-linux-x86_64.tar.gz" -o /tmp/google-cloud-cli.tgz \ + && echo "${GCLOUD_SHA256_AMD64} /tmp/google-cloud-cli.tgz" | sha256sum -c - \ + && tar -xzf /tmp/google-cloud-cli.tgz --no-same-owner --no-same-permissions -C /opt \ + && test -x /opt/google-cloud-sdk/bin/gcloud \ + && rm -f /tmp/google-cloud-cli.tgz + # Use the same uv-managed Python contract as the community base image. The # rox-ci-image system Python is 3.9, while current MCP integrations require # 3.10+. @@ -81,10 +92,9 @@ ENV UV_PYTHON_INSTALL_DIR=/sandbox/.uv/python RUN uv python install "${PYTHON_VERSION}" \ && uv cache clean -# gsutil currently supports Python through 3.13, while the shared image uses -# Python 3.14 for its other integrations. Keep the Prow client isolated in a -# compatible, root-owned environment so ACS CI triage can read test results -# without adding gcloud or service-account files to the image. +# Keep the legacy standalone gsutil client available during migration. It is +# isolated in a root-owned environment so older workflows can still read +# results while ACS CI moves to gcloud storage. ARG GSUTIL_PYTHON_VERSION=3.13.7 ARG GSUTIL_VERSION=5.35 RUN install -d -o root -g root /opt/gsutil-python /opt/gsutil \ @@ -160,7 +170,7 @@ RUN curl -fsSL "https://github.com/googleworkspace/cli/releases/download/v${GWS_ && rm -rf /tmp/gws-extract \ && rm -f /tmp/gws.tgz -ENV PATH="/sandbox/.venv/bin:/opt/gsutil/bin:/usr/local/go/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" \ +ENV PATH="/opt/google-cloud-sdk/bin:/sandbox/.venv/bin:/opt/gsutil/bin:/usr/local/go/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" \ VIRTUAL_ENV=/sandbox/.venv \ GOPATH=/sandbox/.cache/go \ GOCACHE=/sandbox/.cache/go-build \ diff --git a/tasks/acs-ci-nightly/README.md b/tasks/acs-ci-nightly/README.md index f712b61..e6f6e94 100644 --- a/tasks/acs-ci-nightly/README.md +++ b/tasks/acs-ci-nightly/README.md @@ -47,7 +47,7 @@ The Atlassian profile in `openshell/providers/` contains metadata only. It does not create providers or contain credentials. Provider credentials must never be placed in workflow environment variables, payloads, agent arguments, or artifacts. The Prow provider uses the upstream `google-cloud` profile so its -refresh and gsutil-compatible metadata behavior stay aligned with OpenShell. +refresh and `gcloud storage` metadata behavior stay aligned with OpenShell. The workflow uses the shared `sandbox-stackrox-ci` image. Because image publication is independent of task publication, the trusted caller must set diff --git a/tasks/acs-ci-nightly/openshell/README.md b/tasks/acs-ci-nightly/openshell/README.md index df4a0fd..afe33c4 100644 --- a/tasks/acs-ci-nightly/openshell/README.md +++ b/tasks/acs-ci-nightly/openshell/README.md @@ -12,14 +12,11 @@ and attaches those instances; it does not provision or manage their credentials. The built-in Google Cloud profile supplies the gateway-managed metadata path -that gsutil uses. The workflow's Boto configuration enables gsutil's -`[GoogleCompute]` metadata credential lookup without placing a credential in -the sandbox, and the workflow sets both legacy metadata variables explicitly -for gsutil's metadata client. They point at OpenShell's loopback emulator. The -task policy binds that provider instance only to the read-only -`test-platform-results-public` endpoints. -The task also points Google Cloud CLI tools at OpenShell's combined CA bundle -so `gsutil` verifies the sandbox proxy certificate without disabling TLS. +that `gcloud storage` uses. The task policy binds that provider instance only +to the read-only `test-platform-results-public` endpoints. The task also +points Google Cloud CLI tools at OpenShell's combined CA bundle so the +sandbox verifies the proxy certificate without disabling TLS. The image keeps +standalone `gsutil` only as a temporary compatibility fallback. The `github_git` policy is intentionally unauthenticated and read-only because the StackRox repositories used by this task are public. The Atlassian and Prow diff --git a/tasks/acs-ci-nightly/openshell/policy.yaml b/tasks/acs-ci-nightly/openshell/policy.yaml index bb00eae..6e64dfa 100644 --- a/tasks/acs-ci-nightly/openshell/policy.yaml +++ b/tasks/acs-ci-nightly/openshell/policy.yaml @@ -123,6 +123,8 @@ network_policies: method: GET path: /download/storage/v1/b/test-platform-results-public/** binaries: + - { path: /opt/google-cloud-sdk/bin/gcloud } + - { path: /opt/google-cloud-sdk/platform/bundledpythonunix/bin/python3 } - { path: /opt/gsutil/bin/gsutil } - { path: /opt/gsutil/bin/python } - { path: /usr/bin/curl } diff --git a/tasks/acs-ci-nightly/workflow/CI-NIGHTLY.md b/tasks/acs-ci-nightly/workflow/CI-NIGHTLY.md index 76397b5..eb68b83 100644 --- a/tasks/acs-ci-nightly/workflow/CI-NIGHTLY.md +++ b/tasks/acs-ci-nightly/workflow/CI-NIGHTLY.md @@ -11,7 +11,7 @@ Run only the CI failure analysis portion of the ACS triage agent. `git clone` or `git fetch` for these public repositories; do not run `gh auth login` or push to them. 4. Find failures from the last 24 hours in the Prow nightly jobs under - `gs://${GCS_BUCKET:-test-platform-results}/logs/`. + `gs://${GCS_BUCKET:-test-platform-results}/logs/` using `gcloud storage`. 5. Spawn the repository's CI analysis agents as instructed and wait for their results. 6. Write exactly `/sandbox/acs-triage-agent/artifacts/ci-triage.json` using diff --git a/tasks/acs-ci-nightly/workflow/harness.yaml b/tasks/acs-ci-nightly/workflow/harness.yaml index 93512a6..4c08206 100644 --- a/tasks/acs-ci-nightly/workflow/harness.yaml +++ b/tasks/acs-ci-nightly/workflow/harness.yaml @@ -24,9 +24,6 @@ sandbox: GCS_BUCKET: test-platform-results-public CLOUDSDK_CORE_CUSTOM_CA_CERTS_FILE: /etc/openshell-tls/ca-bundle.pem CURL_CA_BUNDLE: /etc/openshell-tls/ca-bundle.pem - BOTO_CONFIG: /tmp/openshell-boto.cfg - GCE_METADATA_ROOT: 127.0.0.1:8174 - GCE_METADATA_IP: 127.0.0.1:8174 source: repo: https://github.com/stackrox/acs-triage-agent.git @@ -36,13 +33,6 @@ source: payloads: - source: CI-NIGHTLY.md destination: /sandbox/CI-NIGHTLY.md - - content: | - [Boto] - ca_certificates_file = /etc/openshell-tls/ca-bundle.pem - - [GoogleCompute] - service_account = default - destination: /tmp/openshell-boto.cfg agent: type: claude diff --git a/tasks/acs-ci-nightly/workflow/preflight.yaml b/tasks/acs-ci-nightly/workflow/preflight.yaml index b2097fa..8762ae4 100644 --- a/tasks/acs-ci-nightly/workflow/preflight.yaml +++ b/tasks/acs-ci-nightly/workflow/preflight.yaml @@ -13,18 +13,6 @@ sandbox: GCS_BUCKET: test-platform-results-public CLOUDSDK_CORE_CUSTOM_CA_CERTS_FILE: /etc/openshell-tls/ca-bundle.pem CURL_CA_BUNDLE: /etc/openshell-tls/ca-bundle.pem - BOTO_CONFIG: /tmp/openshell-boto.cfg - GCE_METADATA_ROOT: 127.0.0.1:8174 - GCE_METADATA_IP: 127.0.0.1:8174 - -payloads: - - content: | - [Boto] - ca_certificates_file = /etc/openshell-tls/ca-bundle.pem - - [GoogleCompute] - service_account = default - destination: /tmp/openshell-boto.cfg agent: type: /bin/sh @@ -62,35 +50,8 @@ agent: fi echo - echo "GCE metadata configuration" - echo -n "GCE_METADATA_ROOT: " - printenv GCE_METADATA_ROOT 2>/dev/null || echo unset - echo -n "GCE_METADATA_IP: " - printenv GCE_METADATA_IP 2>/dev/null || echo unset - echo -n "GCE_METADATA_HOST: " - printenv GCE_METADATA_HOST 2>/dev/null || echo unset - for metadata_url in \ - "http://127.0.0.1:8174/" \ - "http://127.0.0.1:8174/computeMetadata/v1/instance/service-accounts" \ - "http://127.0.0.1:8174/computeMetadata/v1/instance/service-accounts/default/token"; do - metadata_error=/tmp/preflight-metadata-error - metadata_status=$(curl --noproxy '*' --silent --show-error --output /dev/null \ - --write-out '%{http_code}' --connect-timeout 3 --max-time 5 \ - -H 'Metadata-Flavor: Google' "$metadata_url" 2>"$metadata_error") - metadata_exit=$? - if [ "$metadata_exit" -eq 0 ]; then - echo "Metadata: $metadata_url (HTTP $metadata_status)" - else - echo "Metadata: $metadata_url (curl exit $metadata_exit)" - sed -n '1,5p' "$metadata_error" - fi - done - echo - - echo "GCS: gsutil ls -b gs://$GCS_BUCKET" - if env GCE_METADATA_ROOT=127.0.0.1:8174 \ - GCE_METADATA_IP=127.0.0.1:8174 \ - timeout 30s /opt/gsutil/bin/gsutil ls -b "gs://$GCS_BUCKET" \ + echo "GCS: gcloud storage ls gs://$GCS_BUCKET" + if timeout 30s gcloud storage ls "gs://$GCS_BUCKET" \ >/dev/null 2>"$gcs_error"; then echo "GCS: PASS" else