Skip to content

Sync upstream authorization and object-store updates - #72

Merged
EItanya merged 21 commits into
mainfrom
sync/upstream-main-20261013
Oct 13, 2026
Merged

EItanya merged 21 commits into
mainfrom
sync/upstream-main-20261013

Conversation

@EItanya

@EItanya EItanya commented Oct 13, 2026 •

Copy link
Copy Markdown

Syncs upstream main at be3aa280f into the fork with one merge commit. Includes actor lifecycle/egress/worker authorization, private sandbox asset downloads, split filesystem checkpoints, the object-store plugin rename, and installer and benchmark updates.

Aligns the Helm charts and image build workflows with objectstore-plugin, including isolated asset staging, plugin-only storage settings, the gVisor runtime pin, and the SandboxConfig CRD. Preserves the fork's workflow deadlines, insecure development mode, and credential fixture endpoint alongside upstream request-log redaction and header echo support.

Pins agentgateway to 9557280b, a five-line client and test update on the previously pinned source. It sends name: "default" when fetching actor egress policy; the old image omitted it, causing both E2E jobs to fail API validation. The image is built for amd64 and arm64 by the image workflow.

Breaking change

  • Upgrade substrate-crds before the application chart, and upgrade gVisor SandboxConfigs with ateom-gvisor. The chart pins the 2026-10-10 nightly (v3) required for split filesystem checkpoints.
  • Rename snapshotPlugin Helm settings to objectStorePlugin. Move storage environment overrides from atelet.extraEnv to objectStorePlugin.extraEnv; atelet now rejects ATE_STORAGE_BACKEND. The chart selects the backend through atelet.storageBackend. Private S3 sandbox assets must use s3:// URLs.
  • Upgrade API clients with the server: singleton policy Get/Delete requests require name: "default", and DrainWorker returns a DrainWorkerResponse wrapper. Deployments enforcing OpenFGA authorization need the new actor lifecycle, egress policy, and worker permissions.

Upstream's ingress static-mtls mode supports Envoy only; the agentgateway chart retains its existing ingress configuration.

Validation: all 44 agentgateway egress integration tests pass. The full local Go race suite passes with env -u NO_COLOR go test -race -p 1 ./...; manifest rendering, CRD parity, and all three chart lints pass. Local repository verification passed code generation, module checks, formatting, and boilerplate, then stopped during linting because the shared 20 GB scratch filesystem filled. All four CI checks pass on 12118a988: unit tests and verification, plus agentgateway E2E, Helm E2E, and Helm verification.

Runtime results (passed/skipped): agentgateway gVisor 124/6 and microVM 128/2; Helm gVisor 120/5, microVM 30/1, egress credentials 9/0, and identity 1/0. Skips are the existing runtime-specific cases and the Helm-only credential test in manifest installs.

Merge with a merge commit to preserve upstream ancestry.

  • Tests pass
  • Appropriate changes to documentation are included in the PR

ericcurtin and others added 21 commits October 12, 2026 16:33
…trate#2452)

#### Breaking change

`GetActorEgressPolicy`, `DeleteActorEgressPolicy`,
`GetAtespaceAccessPolicy`, `DeleteAtespaceAccessPolicy` and
`GetGlobalAccessPolicy` now require `name` set to `default`. Please add
the `breaking-change` label.

Fixes agent-substrate#2363

Adds `name` to those requests, per the style guide. Any other value is
`INVALID_ARGUMENT`. `resources.SingletonName` replaces the repeated
`"default"` literals.

- [x] Tests pass
- [x] Appropriate changes to documentation are included in the PR
…ate#2509)

1) Make docs consistent for actor-templates wrt everything else.
2) Normalize all CLI help text on singular nouns with plurals as aliases
3) Don't accept plural commands where we can't handle more than 1 item

#### Breaking change

Some CLI usage of plurals breaks, but it wasn't DOING plural things.

- [X] Tests pass
- [X] Appropriate changes to documentation are included in the PR
agent-substrate#2513 


- [ ] Tests pass
- [ ] Appropriate changes to documentation are included in the PR
### Summary

Expose the `WorkerPoolSyncer` worker goroutine count via
`--worker-sync-concurrency` (default `2`, preserving existing behavior)
instead of a hardcoded constant, so large-scale benchmarks can raise
worker-registration concurrency without patching Go sources at runtime.

### Testing

`go test ./cmd/atecontroller/...`

- [x] Tests pass
- [ ] Appropriate changes to documentation are included in the PR
)

Fixes agent-substrate#2373

ate-api-server no longer links the GCS and S3 SDKs. The plugin socket
helpers move to `internal/pluginsocket`, and
`hack/verify/ateapi-deps.sh` guards it.

- [x] Tests pass
- [x] Appropriate changes to documentation are included in the PR

---------

Co-authored-by: Haven Xia <haoyuxia@google.com>
Add tool for benchmarking Substrate egress.

- [x] Tests pass
- [x] Appropriate changes to documentation are included in the PR

---------

Signed-off-by: Yan Avlasov <yavlasov@google.com>
`DrainWorker` was the only custom method that returned its resource
directly; every other custom method returns a `{MethodName}Response`
wrapper. A wrapper keeps the API uniform and leaves room for
method-specific output in the future.

The API style guide now requires custom methods to return a
`{MethodName}Response` and no longer lets them return the resource
itself.

Part of agent-substrate#2411
…#2413)

Addresses agent-substrate#2377.

## What we saw
Under CPU contention (14 sandboxes per worker pod, each opening about a
thousand tunnels), 1 to 8% of gVisor sandboxes died mid-run. The actor
stayed `RUNNING`, `SuspendActor` succeeded, and the next resume failed
with `inconsistent private memory files on restore`. Sentry logs showed
`Stub thread no longer exists; killing syscall thread`. Across the
ladder runs on the nightly we lost 1/299, 17/968 and 79/999 actors, and
79 and 73 of 1000 in two seven-minute repros.

## Why the bump helps
The 2026-09-02 nightly predates gVisor commit `80bb741691be` ("Close
exposed race window in stub ThreadID setting"): a sentry that interrupts
a stub thread mid context switch can read an invalid thread id and kill
the whole subprocess. Release 20260907.0 is the first tagged release
containing it (GitHub compare: 25 commits ahead of the fix, 0 behind).
The same repro on 20260907.0 lost 0 of 1000 actors, as did the 34-minute
1000 x 1000 rung of the egress ladder.

## Change
`manifests/ate-install/sandboxconfig-gvisor.yaml` gains version `v2`
with release 20260907.0 as `defaultVersion`; the nightly entry is
removed. `docs/api-guide.md` mirrors it and now states that a resume
resolves the default version at restore time, so after this rolls out
recut golden snapshots and expect snapshots captured under the nightly
to fail on their next resume.

## Test signals
- `go test ./pkg/api/v1alpha1/` applies the edited manifest against the
CRD and the admission policy in envtest; `./cmd/ate-setup/...`,
`./cmd/atelet/...` and the ateapi sandbox tests pass.
- CI `e2e-test` installs this manifest through `ate-setup` and boots,
suspends and restores gVisor actors, so a green run exercises the
release.
- Both sha256 values were recomputed from the downloaded tarballs and
match the published `.sha512` files.
- The loss counts above come from the issue's gist and the ladder run,
not re-verified in this PR.

Prepared with help from an AI coding agent; reviewed and verified by the
author.
…strate published (agent-substrate#2418)

ClusterTrustBundles are cluster-scoped, so deleting the ate-system and
podcertificate-controller namespaces left Substrate's four bundles
behind.

- Once both namespaces are gone (so the controllers can't republish),
teardown deletes exactly these bundles, each only when its signer also
matches:
- `podidentity.podcert.ate.dev:identity:primary-bundle` (signer
`podidentity.podcert.ate.dev/identity`)
- `servicedns.podcert.ate.dev:identity:primary-bundle` (signer
`servicedns.podcert.ate.dev/identity`)
- `postgres.podcert.ate.dev:identity:primary-bundle` (signer
`postgres.podcert.ate.dev/identity`)
- `egress-mitm.ate.dev:mitm:primary-bundle` (signer
`egress-mitm.ate.dev/mitm`)
- A foreign bundle under one of these signers with another name is kept.

Tested with unit tests and a kind install plus teardown: the four
bundles were deleted; the kube-apiserver bundle and foreign bundles,
including two under our signers, were kept.
…durable dirs (agent-substrate#2455)

Use gVisor's split filesystem checkpointing from the 2026-10-10 nightly
release instead of host-side tar archiving for durable-dir volumes:

- Mount durable-dir volumes as container-shared tmpfs inside the gVisor
sandbox in ShapeGVisor.
- On MEMORY checkpoints, pass -fs-checkpoint-paths to runsc checkpoint
so durable-dir volumes are checkpointed into the fs/ subdirectory
alongside the process and rootfs checkpoint.
- On VOLUMES checkpoints, run runsc fscheckpoint with -image-path
<checkpointDir>/fs and -path <container>:<mount_path> for each
durable-dir mount.
- On RestoreWorkload, pass --fs-restore-image-path <checkpointDir>/fs to
runsc create on the pause container when fs/fscheckpoint.pb is present,
then either restore the full sandbox (MEMORY) or start fresh containers
with only durable-dir volumes restored (VOLUMES).
- Allow fs/<file> relative paths in snapshot file validation, local
pause staging, and object store plugin transfers.

Fixes #<issue_number_goes_here>

> It's a good idea to open an issue first for discussion.

- [ ] Tests pass
- [ ] Appropriate changes to documentation are included in the PR
…nt-substrate#2371)

The digest that turns an environment change into a rollout was patched
onto the running Deployment before the deploy applied the new manifest,
and a fresh install skipped it.

So the first `deploy apiserver` after an install actually **rolled
twice** whenever it brought a new image: once for the new annotation on
the old image, then again for the new image. When the environment did
change, the old image came up on the new values before the new image
landed.

A deploy now stamps the digest on the Deployment it applies, so a new
image and a new environment are one rollout and a fresh install starts
out stamped. A deploy whose manifest has no such Deployment now fails
rather than applying it without the digest. `create
api-server-env-vars`, which applies no Deployment, still patches the
running one.

Made with help from Claude Code

Part of agent-substrate#1887 and it
unblocks agent-substrate#1686
agent-substrate#2535)

agent-substrate#2513 

### Changes
- Re-enable the credential injection e2e which was skipped in agent-substrate#2518
- `testserver http` gains `/echoheaders`, which returns the request
headers it received as `{"headers": {...}}`.
- The suite deploys two echo origins in its own namespace:
- `echo-https` on 443, serving a certificate from the cluster's
service-DNS signer through a projected `podCertificate` volume. The
egress gateway trusts that signer once
`hack/setup-e2e-egress-tls-kind.sh` has run, and CI already runs it.
  - `echo-http` on 80.
- The fail-closed hosts are `.invalid` names that exist only in the
policy.
- Coverage is unchanged: injection and no-header checks over HTTPS and
plain HTTP, plus the three fail-closed cases (unfetchable secret,
unserved provider, unauthorized namespace) on both schemes.

### Testing


- [x] Tests pass
- [ ] Appropriate changes to documentation are included in the PR
…ubstrate#2439)

atelet held its own storage credential to download sandbox assets, and
private micro-VM assets only worked on GCS or S3. Assets now go through
the node's object-store plugin, which already has the credentials.

- Add `AssetProvider.FetchAsset` to the plugin API.
- The plugin refuses snapshot locations, stages downloads where atelet
can't read them, and replaces the target only once the sha256 matches;
`pkg/assetfetch` shares these checks with other plugins.
- atelet asks the plugin first for every asset. Only for a `gs://` URL,
and only when the plugin returns NotFound, InvalidArgument,
Unimplemented or Unavailable, does it fall back to an anonymous read
(which serves public buckets such as `gs://gvisor`). It never falls back
when the plugin refuses a snapshot location or the size or digest is
wrong.
- atelet re-checks the size and sha256 of whatever it caches, since the
plugin is a separate, possibly third-party process and atelet executes
these bytes.

Tested with the kind gVisor and micro-VM lanes before the plugin-first
change; only the NFS external-volume tests failed, same as on main. Unit
tests cover each fallback and non-fallback code.

Related agent-substrate#2063: assets in the cluster's own store no longer wait on an
anonymous GCS lookup first.

#### Breaking change

Plugins must implement `AssetProvider` to serve sandbox assets; public
`gs://` assets still load anonymously if the plugin can't serve them.
atelet exits if `ATE_STORAGE_BACKEND` is set on it; set storage settings
on the snapshot-plugin sidecar. Please add the `breaking-change` label.

Fixes agent-substrate#2385

- [x] Tests pass
- [x] Appropriate changes to documentation are included in the PR
…ngress router (agent-substrate#2387)

The ingress router can now be started up in two modes, controlled by the
`--ingress-auth-mode` flag: `deprecated-insecure` (the default), and
`static-mtls`.

Both modes require you to specify the new `--ingress-client-ca-file` and
`--ingress-allowed-spiffe-ids` flags. By default, the validation CA is
set to the pod identity CA.

In deprecated-insecure mode, the router listens on both plaintext and
HTTPS ports.
* If a client connects on the plaintext port, they are allowed to
connect to any actor without authentication and authorization.
* If a client connects on the HTTPS port, but does not send a client
certificate in the TLS handshake, then they are allowed to connect to
any actor without authentication and authorization.
* If a client connects on the HTTPS port, and provides a client
certificate, then the certificate will be checked against
`--ingress-client-ca-file` and `--ingress-allowed-spiffe-ids`.

In static-mtls mode, the router only listens on the HTTPS port:
* Clients are required to send a client certificate, and it is always
checked against `--ingress-client-ca-file` and
`--ingress-allowed-spiffe-ids`.

These two modes allow a smooth migration:
* Land this PR, which keeps the default at deprecated-insecure.
* The next PR will allow ate-setup to configure the cluster into
static-mtls mode.
* The next PR(s) will cut over end-to-end tests and demos so that they
send a client certificate.
* The final PR will remove the deprecated-insecure mode.
… RPCs with OpenFGA (agent-substrate#2185)

Working on agent-substrate#1563

Fixes agent-substrate#1700

Adds OpenFGA checks for the remaining actor RPCs (except the identity
ones) and for all worker RPCs. These checks are enforced when
`--experimental-enable-authz` is set.

### Checks

| RPC | Check | Who passes |
|---|---|---|
| `PauseActor` / `ResumeActor` / `SuspendActor` / `RevertActor` |
`can_pause` / `can_resume` / `can_suspend` / `can_revert` on `actor` |
atespace editor and above |
| `GetActorEgressPolicy` | `can_get_egress_policy` on `actor` | atespace
viewer and above |
| `Create/Update/DeleteActorEgressPolicy` |
`can_create/update/delete_egress_policy` on `actor` | atespace editor
and above |
| `CreateWorker` / `ListWorkers` | `can_create_worker` /
`can_list_workers` on `global:root` | global owner / global viewer |
| `GetWorker` / `ListWorkerActorAssignments` | `can_get` /
`can_list_actor_assignments` on `worker` | global viewer |
| `Update/Delete/DrainWorker` | `can_update` / `can_delete` /
`can_drain` on `worker` | global owner |

### Not in this PR

- **System component roles (follow-up PR).** With enforcement on, the
ateapi callers inside Substrate need grants:
- `atenet-router`: `ResumeActor`, plus `ListActors` for its health
check;
  - `atenet-egress`: `GetActor`, `GetActorEgressPolicy`;
  - `ate-controller`: the worker RPCs.

Today the only way to grant them is global owner or AccessPolicy roles.
A follow-up will add system roles granted from ateapi's configuration to
the components' mTLS SPIFFE IDs, the same way bootstrap owners are
granted. Until then, don't turn on `--experimental-enable-authz` in a
cluster that relies on these components.
- **Identity RPCs** (`MintActorJWT`, `MintActorCertificate`) are handled
separately (see agent-substrate#2026).

### Testing

- Registry tests for each new RPC, plus fail-closed cases for missing or
invalid identifiers.
- Interceptor tests against Postgres-backed OpenFGA covering editors,
viewers, outsiders, global viewers, global owners and bootstrap owners.
Removing the worker → `global:root` contextual link fails the worker
cases.
- OpenFGA model tests for the new relations (`fga model test`: 6/6
tests, 185/185 checks).
…i-server can use it (agent-substrate#2519)

Part of agent-substrate#777 

This is the first, mechanical step: later PRs add a `MintAteomActorJWT`
RPC on `WorkerService`, have the snapshot plugin's control sidecar
exchange that JWT for a downscoped GCS token scoped to the actor's own
prefix plus its source tag, and have the node plugin transfer with that
token. The design, including why downscoped tokens rather than the
signed URLs the issue suggests, is in the design doc that accompanies
those PRs.

The snapshot plugin's control sidecar is about to verify actor JWTs
before exchanging them for downscoped object-store credentials. The
verifier in `oidcjwt` lived under `cmd/ateapi/internal`, where only
ate-api-server can import it. This moves it to `internal/oidcjwt` so
other binaries in the module can share it, keeping JWT verification on
the one standard-library implementation.

Pure move: the four files are unchanged (`git` records them as renames),
ate-api-server's import path is updated, and the pointer in `AGENTS.md`
follows the package. No behavior change.

- [x] Tests pass (`go test -race ./internal/oidcjwt/...
./cmd/ateapi/...`; the `cmd/ateapi` postgres password-logging test only
fails locally with `PGPASSWORD` set in the shell, on `main` as well)
- [x] Appropriate changes to documentation are included in the PR
(`AGENTS.md`)
…gin (agent-substrate#2540)

The plugin now serves sandbox assets as well as snapshots, and its API
is already `objectstore.v1`, so the name `snapshot-plugin` no longer
describes it. This renames the binary, sidecar, sockets and flag to
match. The `objectstore.v1` proto package and RPC names are unchanged.

- `cmd/snapshot-plugin` moves to `cmd/objectstore-plugin`; the atelet
and ate-api-server manifests and the kind overlays follow.
- `--snapshot-plugin-socket` becomes `--objectstore-plugin-socket` on
atelet and ate-api-server, with no alias.
- Go identifiers, log and error text, `docs/architecture.md` and
`hack/verify/ateapi-deps.sh` now say "object-store plugin".

#### Breaking change

The `cmd/snapshot-plugin` image is now `cmd/objectstore-plugin`, the
`snapshot-plugin` sidecar container and volume in atelet and
ate-api-server are now `objectstore-plugin`, the sockets move from
`/run/snapshot-plugin` to `/run/objectstore-plugin`, and the
`--snapshot-plugin-socket` flag on atelet and ate-api-server is now
`--objectstore-plugin-socket`. The sidecars' trace `service.name`
changes from `snapshot-plugin-<mode>` to `objectstore-plugin-<mode>`.
Overlays that patch the `snapshot-plugin` container, pass the old flag
or filter on the old service name must be updated.

Needs the `breaking-change` label.

Tested with `go build ./...`, `go test -race` on cmd/ateapi, cmd/atelet,
cmd/objectstore-plugin, cmd/ate-setup, internal/objectstoreplugin and
internal/pluginsocket, and `hack/verify/ateapi-deps.sh`. The base, kind,
kind-agentgateway and agentgateway kustomize renders differ from before
only by the rename.

- [x] Tests pass
- [x] Appropriate changes to documentation are included in the PR
…agent-substrate#2403)

On a new workstation, a Kind demo install reports only a missing
kubeconfig context. On a cluster without the control plane, it can
create the demo Namespace and WorkerPool before failing while waiting
for a Deployment that no controller can create.

Check Docker and the resolved kubeconfig context before Kind
deployments, with instructions to restore kubeconfig or create the
cluster and local registry. Demo and benchmark commands check for
`deployment/ate-controller` before creating resources. Permission and
connection errors retain their original cause. The checks live in
`ate-setup`, so both native commands and the shell shims use them,
including when a demo flag precedes `--deploy-ate-system`.

The controller check establishes that it is installed; it does not
replace workload rollout checks. Help and delete commands do not require
the new deployment checks. Cluster recovery guidance states that the
creation script replaces an existing cluster with the same name.

Fixes agent-substrate#2400.

Validation:

- Regression tests first failed on the original implementation. Tests
cover native commands and Kind shims, missing context and Docker, demo
and benchmark prerequisites, action order, installed controllers,
Forbidden responses, resolved configuration, malformed kubeconfig, and
help.
- `go test -race ./cmd/ate-setup/...` passed on macOS arm64.
- The original isolated reproduction passes with the fix; the
missing-controller scenario makes no resource writes.
- `make verify` passed on Linux amd64 with Go 1.27.1: all 145 test
packages passed with the race detector, and all 14 verification scripts
passed.
- The Linux `ate-setup` binary built successfully from the final commit.
Generated files and the Git working tree remained clean.
…ate#2446)

Return the reader's close error when the download succeeds, and preserve
the original download error when both fail. Add regression tests with
valid and truncated zstd streams.

Refs agent-substrate#2445

Tested with `go test -race ./pkg/objectstorage -count=1`.
Rebases agent-substrate#1074 (Add benchmarking for storage, by @elijahrb) onto main and
runs it with the DurDir load. Replaces agent-substrate#1074.

- Adds the `glutton-storage` ActorTemplate, which mounts an external CSI
volume as glutton's data dir. `workloads/deploy.sh --storage-class-name
NAME` deploys it, and `deploy_locust.sh` passes the flag through.
Without a StorageClass it is not deployed, so clusters with no CSI
driver don't wait on its golden snapshot.
- The DurDir load drives it with `--durdir-template glutton-storage`.
DurDir already resumes an actor, writes a file, checks the file's
checksum after every resume, and suspends the actor again, so the
storage benchmark needs no load of its own. DurDir does not count
crashed actors under `CrashCount` yet.
- glutton gets two opt-in flags. Only `glutton-storage` sets them, so
the baseline and DurDir numbers don't change. `--sync-writes` fsyncs
each `WriteDisk` file. `--readyz-require-writable-data-dir` fails
`/readyz` until the data dir is writable, so a resume waits for the
volume mount.
- `csi-nfs-sc` is now defined in one file,
`hack/third_party/csi-driver-nfs/deploy/example/storageclass-nfs.yaml`.
`ate-setup setup csi nfs` and `hack/setup-csi-nfs-kind.sh` both apply it
instead of each carrying a copy, so the e2e tests, demos and benchmarks
use the same class. A test checks this. To benchmark on NFS, install
with `hack/install-ate.sh --setup-csi=nfs` and pass
`--storage-class-name csi-nfs-sc`.
- The Locust scripts take `LOCUST_IMAGE` or `KO_DOCKER_REPO`, so they
work on Kind with a local registry, and `LOCUST_IMAGE_PULL_POLICY`
(default `Always`).

The orchestrator and `tests.yaml` don't change. Letting a test set a
StorageClass or apply extra manifests moves to a follow-up PR, where the
test API can be worked out.

Testing: unit tests for the glutton flags and the shared StorageClass.
`go test -race` on the changed packages, golangci-lint, shellcheck and
the boilerplate check pass. Not run end to end on a cluster.

- [x] Tests pass

---------

Co-authored-by: Elijah Rodriguez-Beltran <elijahrb@google.com>
Integrate singleton policy requests, actor lifecycle authorization, sandbox asset staging, and split filesystem checkpoints. Align the Helm charts and image builds with the objectstore-plugin rename while preserving fork workflow deadlines and development transport settings.

Signed-off-by: Eitan Yarmush <eitan.yarmush@solo.io>
@EItanya
EItanya force-pushed the sync/upstream-main-20261013 branch from 592a0bd to 12118a9 Compare October 13, 2026 10:24
@EItanya EItanya changed the title Sync upstream authorization and sandbox asset updates Sync upstream authorization and object-store updates Oct 13, 2026
@EItanya
EItanya marked this pull request as ready for review October 13, 2026 10:42
@EItanya
EItanya merged commit f499a14 into main Oct 13, 2026
4 checks passed
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.