Skip to content

fix(deploy): grant logging.viewer and document TokenCreator for single-node tier - #1324

Open
ptone wants to merge 3 commits into
mainfrom
scion/sn-111-deploy-iam
Open

fix(deploy): grant logging.viewer and document TokenCreator for single-node tier#1324
ptone wants to merge 3 commits into
mainfrom
scion/sn-111-deploy-iam

Conversation

@ptone

@ptone ptone commented Aug 28, 2026

Copy link
Copy Markdown
Owner

Summary

  • Grants roles/logging.viewer to the instance SA — non-fatal: on failure, emits a WARNING naming the role, states the deploy is otherwise complete, and prints the exact manual grant command
  • Documents roles/iam.serviceAccountTokenCreator as a post-deploy instruction with a copy-pasteable gcloud command (hub SA email pre-filled, only TARGET_SA as placeholder)
  • Adds comment blocks for two declined roles (iam.serviceAccountAdmin, monitoring.viewer) with stated reasons and manual grant commands
  • Discloses when the Compute Engine default SA is in use: names it, states it commonly holds broad project permissions, and points at --service-account for operators who want a scoped identity
  • Adds di_resolve_instance_sa pure function with tests, plus two di_main-level tests that run the full deploy flow through Step 5b

Why not copy the multi-node script

The IAM matrix (#111) identified four roles the multi-node scripts/cloudrun/deploy.sh grants that would be over-granted on the single-node tier:

Role Why not needed
roles/run.admin Sandbox runtime shells out to local binary, no Cloud Run API calls
roles/cloudsql.client Single-node uses SQLite, no Cloud SQL
roles/iap.tunnelResourceAccessor IAP exec is wired to CloudRunRuntime, not CloudRunSandboxRuntime
roles/storage.objectAdmin Only needed when --storage-bucket is configured (not by default)

Why TokenCreator is documented, not granted

The IAM Credentials API checks iam.serviceAccounts.getAccessToken permission on the target SA (projects/-/serviceAccounts/{email} at gcp_token_iam.go:66), not at the project level. A project-level serviceAccountTokenCreator grant would let the hub SA impersonate every SA in the project. The least-privilege shape is a per-target-SA binding, but the script runs before any target SA exists.

The verification gate makes this viable: the API returns verificationFailed: true with both emails at SA registration time, and every assignment surface (create, patch, project-default) returns 400 with "not verified; verify it before assigning to agents". An unverified SA cannot reach agent startup.

Note: The API returns everything the operator needs at registration time. Whether the UI renders verificationFailed clearly is unverified and should be checked separately.

Why the logging.viewer grant is non-fatal

An operator who lacks resourcemanager.projects.setIamPolicy on their project (common in shared/corporate GCP projects) must not be prevented from deploying. The logging.viewer role backs a log-viewer UI panel that is not on the S1 path.

Testability note

The Step 5b tests override di_wait_for_iap and di_assert_perimeter because those functions use curl against the real instance URL and cannot be stubbed through the _DI_API_BASE / _DI_TOKENINFO_URL seams. This is a testability defect in the script, not in the test -- any future test that needs to exercise a step after Step 4 will hit the same wall. The two functions are independently tested via stub HTTP servers; what is missing is a seam that lets di_main-level tests reach the late steps without overriding the functions.

Test plan

  • bash -n syntax check passes
  • TestScriptResolveInstanceSA_Explicit and _Default
  • TestScriptStep5bGrantSucceeds - full di_main flow, grant with correct args, deploy completes
  • TestScriptStep5bGrantFailsNonFatally - full di_main flow, grant fails, deploy completes (exit 0), WARNING emitted
  • All 44 pre-existing deploy script tests pass unchanged

Scion Agent (sn-deploysh-batch) added 3 commits August 28, 2026 19:12
…e-node tier (#111)

The single-node deploy.sh granted NO IAM roles to the instance service
account. The hub auto-detects GCP features via the metadata server and
enables them, but without the corresponding permissions the features
fail at runtime — not at startup.

This commit:

- Resolves the instance SA email (from --service-account or the Compute
  Engine default) and grants roles/logging.viewer at the project level.
  The hub's log query UI (pkg/hub/logquery.go) is auto-enabled when
  ResolveProjectID() succeeds, which it always does on Cloud Run via
  the metadata server. Without this role, the UI returns errors.

- Documents but does NOT grant roles/iam.serviceAccountTokenCreator.
  The hub needs this role to impersonate target SAs for agent GCP
  identity (pkg/hub/gcp_token_iam.go), but the IAM API checks the
  permission on the TARGET SA resource, not at the project level. A
  project-level grant would let the hub SA impersonate any SA in the
  project, which is too broad for a one-command deploy. Instead, the
  script prints a copy-pasteable post-deploy instruction with the hub
  SA email already filled in and only TARGET_SA as a placeholder.

- Adds comment blocks for two declined roles (serviceAccountAdmin for
  SA minting, monitoring.viewer for metrics dashboard) explaining why
  each is not granted and providing the manual grant command.

The verification gate on unverified SAs is well-built: registration
returns verificationFailed with both emails and the exact missing role;
assignment returns 400 at every surface (create, patch, project-default).
An unverified SA cannot reach agent startup. This makes the "document,
don't grant" approach viable — the operator always sees why and what to
do about it.

Does NOT fix ptone's live crash on sn-harness-lab — he worked around
that by granting TokenCreator manually. This fixes the gap that made
the manual grant necessary.
Two problems found by architect review:

1. The logging.viewer grant was fatal to the deploy under set -euo
   pipefail. An operator who lacks resourcemanager.projects.setIamPolicy
   (common in shared/corporate GCP projects) could no longer deploy at
   all — converting "a UI panel is unprovisioned" into "the product does
   not install." The grant is now non-fatal but loud: on failure it emits
   a WARNING naming the role, what it enables, states the deploy is
   otherwise complete, and gives the exact manual command. This is the
   same treatment TokenCreator already gets in this PR.

2. No test reached Step 5b — the grant had never executed in any test.
   Added two di_main-level tests that run the FULL deploy flow through
   Step 5b:
   - TestScriptStep5bGrantSucceeds: verifies the gcloud call is made
     with the correct args, the success message is printed, and the
     deploy completes.
   - TestScriptStep5bGrantFailsNonFatally: verifies the deploy completes
     (exit 0) even when the grant fails, that a WARNING is emitted
     naming the role, and that the manual command is printed.

   Both tests override di_wait_for_iap and di_assert_perimeter (already
   independently tested) to avoid curl to the real instance URL, and use
   a full gcloud stub that handles all steps through Step 5b.
…mitted

When no --service-account is provided, the instance runs as the Compute
Engine default SA, which commonly holds project Editor. The script now
tells the operator this: names the SA as the Compute Engine default,
states it commonly holds broad project permissions, notes that the hub
and every agent use it, and points at --service-account for a scoped
alternative.

This is disclosure, not enforcement — the operator is told what they
have. The question of whether this tier should mint a dedicated,
minimally-scoped SA is a design decision tracked separately (#116).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant