Skip to content

CTM-397: Replace GKE/Helm Galaxy path with GCE VM-based deployment - #4904

Open
LizBaldo wants to merge 55 commits into
developfrom
CTM-397-deploy-galaxy-on-GCE
Open

CTM-397: Replace GKE/Helm Galaxy path with GCE VM-based deployment#4904
LizBaldo wants to merge 55 commits into
developfrom
CTM-397-deploy-galaxy-on-GCE

Conversation

@LizBaldo

@LizBaldo LizBaldo commented Apr 7, 2026

Copy link
Copy Markdown
Collaborator

Summary

Replaces the GKE/Helm-based Galaxy deployment path with a GCE VM-based deployment using galaxy-k8s-boot. Rather than provisioning a full GKE cluster and deploying Galaxy via Helm, Leo now creates a single GCE VM that runs Galaxy via Ansible/microk8s.

What changed

Galaxy VM provisioning (GKEInterpreter.installGalaxyVm)

  • Creates a GCE VM with a boot disk, data disk, and PostgreSQL disk
  • Passes galaxy-user-email GCE metadata (from app.auditInfo.creator) so the workspace user becomes the Galaxy admin
  • Gets or creates a galaxy-batch-runner service account in the user's project
  • Grants the pet SA roles/batch.jobsEditor at the project level and roles/iam.serviceAccountUser on the Batch SA, so the VM can submit GCP Batch jobs
  • Creates an NFS firewall rule (leonardo-galaxy-allow-nfs-for-batch) so Batch VMs can reach the Galaxy VM's NFS server (TCP/UDP 2049 and 111)
  • Polls the instance until it has an external IP; stores it as loadBalancerIp so the Leo proxy can reach the VM across VPC boundaries

Network topology and IP choice

  • Leo's GKE cluster is in Leo's GCP project; Galaxy VMs are created in the user's workspace project. The two VPCs are not peered, so the VM's internal IP (10.x.x.x) is not routable from Leo's pod
  • The leonardo-allow-http firewall rule (TCP port 80, source 0.0.0.0/0, targeting VMs with the leonardo network tag) allows Leo to reach the VM's external IP. Galaxy VMs are created with the leonardo tag
  • Both the readiness health check (isVmReachable) and the Akka HTTP proxy use the external IP

⚠️ Security note: leonardo-allow-http currently allows port 80 from 0.0.0.0/0, meaning the Galaxy VM is reachable directly from the internet, bypassing Leo's workspace-level authorization. This is tracked as a follow-up — options include restricting source ranges to Leo's GKE node CIDR, a GCP service-account-based firewall rule, or a shared-secret header enforced by Galaxy's nginx. See PR discussion for details.

Galaxy VM readiness health check

  • isProxyAvailable routes through Leo's own proxy hostname; in BEE environments the wildcard DNS resolves to the ingress controller's external IP, unreachable via hairpin NAT from within the GKE pod → TCP timeout
  • Added AppDAO.isVmReachable(ip, port) — a direct http4s HTTP GET to http://<externalIp>:80/. No proxy hostname resolution required. MockAppDAO returns IO.pure(isUp) for tests

Leo proxy: HTTP support for Galaxy VM backends

  • Added useHttp: Boolean = false to HostReady — when true the proxy connects via plain HTTP port 80 (ws:// for WebSocket) instead of HTTPS port 443
  • KubernetesDnsCache sets useHttp = true for AppType.Galaxy apps and maps the fake proxy hostname to the VM's external IP
  • ProxyService.handleHttpRequest / handleWebSocketRequest branch on useHttp; all non-Galaxy backends are unchanged

Leo proxy: path handling for Galaxy VM (ProxyService.proxyAppRequest)

  • Leo forwards the full path (e.g. /proxy/google/v1/apps/{project}/{app}/galaxy/...) to the Galaxy VM unchanged
  • galaxy-k8s-boot's ansible playbook configures Galaxy's nginx ingress.path to the value of galaxy_prefix, which Leo passes as a GCE metadata item (galaxy-url-prefix). Galaxy's nginx therefore serves at the full Leo proxy path, so all requests route correctly without any path rewriting in Leo

NFS PVC size: GB → GiB conversion fix (GKEInterpreter.installGalaxyVm)

  • pvSizeGi = nfsDisk.size.gb - 11 treated decimal GB as binary GiB. For a 500 GB disk: the disk holds ~466 GiB but Leo requested 489 GiB → NFS provisioner fails with insufficient available space, leaving all Galaxy pods Pending
  • Fixed to convert first: diskSizeGiB = (nfsDisk.size.gb.toLong * 1000^3) / 1024^3, then subtract 11 GiB overhead

Lifecycle: restore from existing disks

  • restore = msg.appType == AppType.Galaxy && msg.createDisk.isEmpty: when a Galaxy app is created without a new disk, the disks already exist (prior app was deleted keeping disks)
  • In restore mode: skips creating the PostgreSQL disk; passes restore_galaxy=true metadata to Ansible
  • CreateAppParams.restore: Boolean propagates the flag through to installGalaxyVm

Lifecycle: delete keeping disks

  • DeleteAppMessage(diskId = None) → VM is deleted, both disks are preserved
  • DeleteAppMessage(diskId = Some(...)) → VM + both disks deleted

Config cleanup

  • Removed gcpBatchServiceAccountEmail from reference.conf / GalaxyVmConfig / Config.scala — Leo now creates the SA dynamically via getOrCreateServiceAccount instead of relying on a pre-configured email

Architecture notes

Property GKE Galaxy (before) GCE VM Galaxy (now)
Backend GKE cluster + Helm Single GCE VM (galaxy-k8s-boot anvil branch)
VM bootstrap N/A GCE user-data cloud-init via guest agent
Proxy backend protocol HTTPS port 443 (nginx ingress TLS) HTTP port 80 (nginx reverse proxy)
Backend IP Ingress load balancer IP (external) VM external IP
Proxy reachability Via GKE LoadBalancer service IP Via leonardo-allow-http firewall (0.0.0.0/0 → port 80, leonardo tag)
Readiness check isProxyAvailable via proxy hostname isVmReachable direct HTTP to external IP
Batch jobs N/A GCP Batch via galaxy-batch-runner SA

Security comparison: old GKE-based vs. new VM-based

Property Old (GKE-based) New (VM-based)
Protocol HTTPS (mTLS) Plain HTTP
Target IP GKE load balancer (external) VM external IP
Port exposed 443 80
Firewall source range 0.0.0.0/0 0.0.0.0/0
Certificate validation Yes — Leo-issued cert on nginx None

Regressions introduced:

  1. No encryption — traffic between Leo's pod and the Galaxy VM crosses the public internet in plaintext
  2. Direct VM access — port 80 is open to 0.0.0.0/0, so anyone who discovers the VM's external IP can reach Galaxy directly, bypassing Leo's authentication

Alternatives for follow-up:

  • Option A — VPC peering / Private Service Connect (recommended structural fix): peer Leo's project VPC with the user's workspace VPC so Leo can reach the VM on its internal IP; the 0.0.0.0/0 firewall rule is no longer needed
  • Option B — HTTPS on the Galaxy VM (closest to old security posture): configure nginx on the Galaxy VM with Leo's CA certificate (as Jupyter VMs do); flip useHttp = false for Galaxy; requires provisioning Leo certs onto the VM during installGalaxyVm
  • Option C — GCP IAP or Cloud Armor (lower-effort mitigation): restricts direct VM access without VPC changes, but does not encrypt the Leo→VM leg

Test plan

  • Unit tests pass (GKEInterpreterSpec, LeoPubsubMessageSubscriberSpec)
  • Scala formatting clean
  • BEE: create Galaxy app → VM boots, Ansible runs, status goes to Running
  • BEE: access Galaxy through Leo proxy URL
  • BEE: verify workspace user email is the Galaxy admin (not a hardcoded address)
  • BEE: delete app keeping disks → VM deleted, disks remain
  • BEE: re-create app from existing disks → restore_galaxy=true passed to Ansible, Galaxy restores state
  • Follow-up: restrict leonardo-allow-http source range from 0.0.0.0/0 to Leo's GKE node CIDR

@LizBaldo
LizBaldo requested a review from a team as a code owner April 7, 2026 17:39
@codecov

codecov Bot commented Apr 8, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 78.81356% with 50 lines in your changes missing coverage. Please review.
✅ Project coverage is 73.85%. Comparing base (39490bf) to head (0b55121).

Files with missing lines Patch % Lines
.../dsde/workbench/leonardo/util/GKEInterpreter.scala 85.43% 22 Missing ⚠️
...itute/dsde/workbench/leonardo/dao/HttpAppDAO.scala 0.00% 9 Missing ⚠️
...workbench/leonardo/http/service/ProxyService.scala 57.89% 8 Missing ⚠️
...dsde/workbench/leonardo/http/api/ProxyRoutes.scala 50.00% 6 Missing ⚠️
...de/workbench/leonardo/dns/KubernetesDnsCache.scala 0.00% 3 Missing ⚠️
...e/dsde/workbench/leonardo/dao/HttpJupyterDAO.scala 0.00% 2 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop    #4904      +/-   ##
===========================================
- Coverage    74.07%   73.85%   -0.23%     
===========================================
  Files          131      131              
  Lines        11101    11202     +101     
  Branches       891      918      +27     
===========================================
+ Hits          8223     8273      +50     
- Misses        2878     2929      +51     
Files with missing lines Coverage Δ
...titute/dsde/workbench/leonardo/config/Config.scala 97.80% <100.00%> (+0.04%) ⬆️
...orkbench/leonardo/config/KubernetesAppConfig.scala 95.00% <ø> (ø)
...stitute/dsde/workbench/leonardo/dao/ProxyDAO.scala 25.00% <ø> (ø)
...bench/leonardo/db/KubernetesServiceDbQueries.scala 96.82% <100.00%> (+0.05%) ⬆️
...orkbench/leonardo/db/PersistentDiskComponent.scala 97.29% <100.00%> (-2.02%) ⬇️
...stitute/dsde/workbench/leonardo/http/package.scala 88.46% <100.00%> (+0.22%) ⬆️
...ch/leonardo/http/service/LeoAppServiceInterp.scala 84.38% <100.00%> (+0.03%) ⬆️
.../leonardo/monitor/LeoPubsubMessageSubscriber.scala 76.80% <100.00%> (-0.20%) ⬇️
...workbench/leonardo/util/BuildHelmChartValues.scala 97.87% <ø> (-0.47%) ⬇️
...tute/dsde/workbench/leonardo/util/GKEAlgebra.scala 80.00% <ø> (-20.00%) ⬇️
... and 6 more

... and 1 file with indirect coverage changes


Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 39490bf...0b55121. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@LizBaldo
LizBaldo requested review from afgane and lucymcnatt April 8, 2026 15:58
@LizBaldo

Copy link
Copy Markdown
Collaborator Author

I am currently blocked fro testing further because of a lack of permission on the galaxy image to use on the boot VM:
Required 'compute.images.useReadOnly' permission for 'projects/anvil-and-terra-development/global/images/galaxy-k8s-boot-v2026-02-25'

I think I would need the Galaxy team to make the image allAuthenticatedUsers in the anvil-and-terra-development project

@LizBaldo

Copy link
Copy Markdown
Collaborator Author

I am currently blocked fro testing further because of a lack of permission on the galaxy image to use on the boot VM: Required 'compute.images.useReadOnly' permission for 'projects/anvil-and-terra-development/global/images/galaxy-k8s-boot-v2026-02-25'

I think I would need the Galaxy team to make the image allAuthenticatedUsers in the anvil-and-terra-development project

The Galaxy team made the image public so I am currently unblocked :)

@aednichols

Copy link
Copy Markdown
Contributor

the Galaxy VM is reachable directly from the internet

I'm not sure this is a concern, don't the instances live in a VPC with NAT?

@LizBaldo

Copy link
Copy Markdown
Collaborator Author

the Galaxy VM is reachable directly from the internet

I'm not sure this is a concern, don't the instances live in a VPC with NAT?

I agree, but this is a departure from how we used to handle it, I am not sure that the compliance review covered this so I want to triple check before merging

@LizBaldo

Copy link
Copy Markdown
Collaborator Author

@afgane I was able to deploy a new Galaxy app, then delete it while keeping the disk and then recreating a new app using the same disk 🎉 I'll do one final round of testing once you have the latest image and then we should be good to go :)

Liz Baldo and others added 19 commits July 30, 2026 14:16
At Galaxy VM creation, grant the pet SA roles/batch.jobsEditor on the
user's project so it can submit and monitor GCP Batch jobs. When the
Batch SA lives in the same project, also grant serviceAccountUser on it;
cross-project Batch SAs must have that binding configured externally.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…, gcpBatchSaProject parsing

- Pass app.auditInfo.creator as galaxy-user-email GCE metadata so the
  actual workspace user (not dev@galaxyproject.org) becomes the Galaxy admin
- Fix scala.io.Source resource leak in installGalaxyVm using scala.util.Using
- Update sourceImage to galaxy-k8s-boot-v2026-06-10 and gitBranch to "anvil"
- Fix HOST_IP to use GCE metadata server instead of external ifconfig.me
- Fix gcpBatchSaProject SA email parsing: lift(1) + stripSuffix instead of
  lastOption + replace to avoid matching suffix in unexpected positions
- Correct stale comments: galaxy_url_prefix → galaxy_prefix, dev → anvil branch,
  wrong "internal IP" comment corrected to "external IP"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The pre-baked galaxy-k8s-boot image carries cloud-init state from its build,
so cloud-init treats new VM launches as subsequent boots and skips runcmd —
causing "No startup scripts to run" in Guest Agent logs and a VM that never
bootstraps Galaxy.

Fix: pass the bootstrap script as the "startup-script" metadata key instead
of "user-data". The GCE Guest Agent always executes startup-script on boot,
regardless of cloud-init state.

galaxy-user-data.sh is reformatted from cloud-config YAML to a plain bash
script. The sudo -u debian block now uses a single-quoted heredoc delimiter
(<<'DEBIAN_EOF') to avoid apostrophes in comments breaking shell quoting.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Liz Baldo and others added 5 commits July 30, 2026 14:16
Picks up rke2 v1.36.2+rke2r1, Helm v4.2.2, ingress-nginx 4.13.2, and
Galaxy Helm chart 6.8.1 baked into the new galaxy-k8s-boot image.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ecord

When two Galaxy VM apps share a project, they reused the same Leo cluster
record via `saveOrGetClusterForApp`. When the second VM started, it called
`updateAsyncFields` and overwrote the shared cluster's `loadBalancerIp`
with its own external IP. Since `kubernetesProxyHost` is keyed on cluster
ID, both apps produced the same proxy hostname, causing all requests to
be routed to the second user's VM (resulting in 404/401 for the first user).

Fix: Galaxy apps now always create a fresh Leo cluster record via
`saveNewClusterForApp`. Each VM gets a unique cluster ID → unique proxy
hostname → no IP collision in `hostToIpMapping`. GKE-based apps
(Cromwell, Allowed, Custom) continue to share a cluster per project.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…project

Drop IDX_KUBERNETES_CLUSTER_UNIQUE_V2 to permit more than one active
KubernetesCluster row per cloud context. Galaxy VM apps need their own
cluster record so each VM stores its external IP independently via
updateAsyncFields; without the constraint drop, saveNewClusterForApp
throws SQLIntegrityConstraintViolationException when a second Galaxy
app is created in the same project.

Also add an early disk-attachment check in createApp before the cluster
record is saved, so DiskAlreadyAttachedException is still raised (rather
than the constraint violation) when the same disk is reused across apps.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…cluster support

The IDX_KUBERNETES_CLUSTER_UNIQUE_V2 constraint was dropped to allow multiple
active cluster records per cloud context for Galaxy VM apps.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@LizBaldo
LizBaldo force-pushed the CTM-397-deploy-galaxy-on-GCE branch from 5236f89 to ed0aef1 Compare July 30, 2026 18:16
Liz Baldo and others added 6 commits July 31, 2026 12:33
…sible playbook

Supplies terra_workspace, terra_namespace, terra_drs_url, and terra_api_url
as ansible extra-vars so galaxy-k8s-boot can configure Galaxy's Terra
integration (required by cloudve/galaxy Helm chart 6.8.2).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… proxy hops

Leo terminates TLS and speaks plain HTTP to Galaxy VM ingress-nginx. Without this
header, ingress-nginx (use-forwarded-headers: false by default) stamps
X-Forwarded-Proto: http on requests to tusd. tusd runs with -behind-proxy and
trusts that header, so it generates Location: http://... for TUS upload sessions.
Browsers block the subsequent PATCH as mixed content, silently dropping uploads.

Adding X-Forwarded-Proto: https only for useHttp=true (Galaxy VM) backends; all
other backends use HTTPS natively and are unaffected. The Galaxy team also needs
to enable use-forwarded-headers: true in the ingress-nginx configmap so the header
reaches tusd, and to update anvil branch to galaxy Helm chart 6.8.2 which fixes
the tusd Ingress routing (6.8.1 had an annotation that caused ingress-nginx to
silently discard the tusd Ingress).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…r Galaxy VM

Leo terminates TLS and speaks plain HTTP to the Galaxy VM. ingress-nginx must be
told to trust Leo's X-Forwarded-Proto header (now set to "https" by ProxyService)
rather than deriving the scheme from its own plaintext connection, so tusd receives
X-Forwarded-Proto: https and generates Location: https://... for TUS upload sessions.

The galaxy-k8s-boot anvil branch exposes this as ingress_use_forwarded_headers
(default false for non-Leo deployments); we set it to true via ansible-pull extra-vars.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…laxy VM responses

Leo terminates TLS and speaks plain HTTP to the Galaxy VM, so tusd's
`-behind-proxy` flag generates `Location: http://...` URLs for TUS
uploads. The browser blocks the subsequent PATCH as mixed content from
an https page. Rewrite Location scheme to https for all useHttp=true
(Galaxy VM) backends in the proxy response path.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…Batch jobs

GCP Batch jobs were 403-ing because galaxy-k8s-boot values.yml hardcodes
project_id: anvil-and-terra-development. Pass the actual VM project
(already available as terra-namespace metadata) to ansible-pull as
gcp_project_id so galaxy-k8s-boot can use it to set the correct project.

The galaxy-k8s-boot anvil branch also needs to accept this variable and
use it to override project_id in the Helm values.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… file source label

Terra passes WORKSPACE_NAME in customEnvironmentVariables (already used for
disk restore validation). Forward it to the VM as terra-workspace-name
metadata and pass to ansible-pull as terra_workspace_name so galaxy-k8s-boot
can populate the workspace: field in the anvil file source config.

Without this the field is empty, causing Galaxy to show "Unlabeled Rfs File
Source" and fail to list workspace files. galaxy-k8s-boot also needs a
matching change to use terra_workspace_name for the workspace: field.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@LizBaldo
LizBaldo force-pushed the CTM-397-deploy-galaxy-on-GCE branch from 706dd5e to c8aaee9 Compare August 3, 2026 18:48
Liz Baldo and others added 9 commits August 3, 2026 15:01
ORCH_URL was already mapped to gke.galaxyApp.orchUrl but not to
galaxyVm.orchUrl, so the Galaxy VM Terra file source api_url always
defaulted to the dsde-dev hardcoded value in reference.conf. No helmfile
change needed; terra-helmfile already sets ORCH_URL per environment.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… no restore info

A Galaxy VM disk gets formattedBy=Galaxy when created but lastUsedBy is
only written after a successful install. If the previous app errored
during provisioning, the disk has no data and should be reusable for a
fresh install rather than returning a 500.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…project to Galaxy VM

terra-namespace was being set to the GCP project (e.g. terra-quality-50d1be3e)
instead of the Terra billing namespace (e.g. broad-dsp-liz). The startup script
was using terra-namespace for both gcp_project_id and terra_namespace in ansible,
conflating two distinct values.

Fix: terra-namespace now carries the Terra billing namespace (from WORKSPACE_NAMESPACE
custom env var). A new gcp-project-id metadata key carries the GCP project, and
the startup script reads it separately for gcp_project_id.

This fixes the "Problem listing file source path gxfiles://terra-launch-workspace/"
error in Galaxy.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rates https:// links

Without this, Galaxy infers http:// from its internal Leo connection and generates
http:// absolute URLs (e.g. history export links). Browsers refuse to send the
Secure-flagged LeoToken cookie on http:// requests, causing 401s.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…viz plugin requests

Galaxy's visualization plugin loader (analysis.bundled.js) fetches index.js and
index.css without a Referer header, causing Leo's checkReferer to 401 before the
LeoToken cookie is ever checked. The Origin header is present on these requests and
provides equivalent CSRF protection (browsers set it; HTML forms cannot forge it).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… GCP Batch

GCP Batch jobs fail with CODE_GCE_RESOURCE_NOT_FOUND because galaxy-k8s-boot
hardcodes network/subnet to 'default', which doesn't exist in Terra projects.
Read gcp-network and gcp-subnet from VM instance metadata (already set by Leo)
and forward as gcp_batch_network and gcp_batch_subnet extra-vars so galaxy-k8s-boot
can configure the Batch runner to use the correct VPC.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…account

Without `userinfo.email` and `userinfo.profile` scopes, the VM pet SA token
cannot be validated by Terra's Sam (Google's userinfo endpoint won't return
the caller's email), causing all `anvilfs` calls to Rawls/Orchestration to
fail with 401 Unauthorized.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nsible

GCP Batch was looking for the boot image by name in the job's project
(terra-quality-ff0f1af3), but the image lives in anvil-and-terra-development.
Passes the full image resource URL from Leo config as `gcp-batch-boot-image`
VM metadata so galaxy-k8s-boot can supply it to the Batch runner config.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Liz Baldo and others added 7 commits August 5, 2026 15:51
Galaxy team is fixing the image path regression in galaxy-k8s-boot directly;
passing it from Leo is not needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…-vars

Ansible splits --extra-vars key=value on whitespace, so workspace names
with spaces (e.g. "Galaxy Testing Party - 080426") were silently truncated
to the first word. JSON format preserves the full value.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… Origin

Galaxy visualization plugin assets (CSS, JS loaded by index.js) are
fetched via no-cors <link>/<script> tags from a page with a strict
referrer policy.  These requests strip Referer and, for no-cors GET
requests, also omit Origin — so neither of the existing fallbacks
helps.  Add a final Sec-Fetch-Site: same-origin check as a third-
layer fallback.  This header is set unconditionally by all modern
browsers and cannot be forged by page scripts (Sec-* is a forbidden
header prefix), so it provides the same CSRF guarantee as Referer
without requiring the page to expose its full URL.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…subnets; warn on pre-created subnets

GCP Batch VMs run without an external IP (org policy blocks them).
They need Private Google Access on the subnet to reach batch.googleapis.com.

- buildSubnetwork: add .setPrivateIpGoogleAccess(true) so all Leo-created
  subnets get PGA automatically
- setUpProjectNetworkAndFirewalls (high-security path): check the pre-created
  subnet and log an actionable warning with the gcloud command to fix it when
  PGA is off

For the current BEE (terra-quality-ff0f1af3), PGA must be enabled manually
since the subnet is pre-created:
  gcloud compute networks subnets update <subnet> \
    --region us-central1 --project terra-quality-ff0f1af3 \
    --enable-private-ip-google-access

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…created subnets; warn on pre-created subnets"

This reverts commit dd7c1a2.
…-runner SA

The Batch VM agent authenticates as the galaxy-batch-runner SA to phone home
to batch.googleapis.com. Without roles/batch.agentReporter the agent starts
(agent,start) but cannot report status, causing the 1080s timeout.

Also missing from the galaxy-k8s-boot README — needs a follow-up doc fix there.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…unner SA

Batch VMs need this role to write logs to Cloud Logging. Without it jobs
run successfully but no logs appear in the GCP Batch console.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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.

3 participants