Skip to content

test(conformance): port the credential gating e2e tests - #5318

Draft
matthewgrossman wants to merge 8 commits into
mainfrom
test/5296-credential-gating-pilot/matthewgrossman
Draft

matthewgrossman wants to merge 8 commits into
mainfrom
test/5296-credential-gating-pilot/matthewgrossman

Conversation

@matthewgrossman

@matthewgrossman matthewgrossman commented Nov 5, 2026 •

Copy link
Copy Markdown
Member

Summary

Port the six credential gating e2e tests from #5264 into the conformance family, and remove the old e2e/rust test. This is the pilot port for #5296. The pieces the port needed that other tests will need too are shared: policy helpers and provider install in testkit, and a new tests/support/host-server crate for servers on the test host. The existing host-gateway feature test now uses them as well.

Related Issue

Closes #5317

Part of #5281. Pilot for #5296, which stays open. Ports the tests from #5264.

What the tests check, and what they needed

The #5264 tests check what the sandbox supervisor does with a provider credential when the sandbox sends traffic out:

  • The gateway rejects a policy that binds a credential to an endpoint it can't inspect (L4-only or tls: skip), unless the endpoint opts in.
  • A REST body sent upstream carries the credential placeholder, or the real secret when request_body_credential_rewrite is on.
  • A WebSocket binary frame, which can't be inspected for the credential, isn't forwarded.
  • Binding a provider with policy set adds its environment variable to a running sandbox, and unbinding removes it.

Porting them needed four things. Each was added as a generic helper, and host-gateway, an existing feature test that had its own copies, now uses the shared ones.

  1. An upstream server that records what arrived. Most of the assertions are about what reached upstream, so the test needs a server that the sandbox can reach and that records what it received. New crate tests/support/host-server (host-server-support):
    • HttpRecorder::start(body) answers every request with 200 and body. wait_for_requests(n) returns the raw requests (headers and body) once n have arrived.
    • WebSocketRecorder::start() completes the WebSocket handshake. wait_for_handshake() waits for one, and binary_seen() reports whether a binary frame followed.
    • Both listen on the test host, which sandboxes reach as host.openshell.internal, and stop when dropped. It lives in tests/support/, alongside the Keycloak fixture, because it's a service outside OpenShell that any suite can use. host-gateway replaces its hand-written server with HttpRecorder.
  2. Policies with credential settings on network endpoints. Each test needs a slightly different endpoint (protocol: rest or websocket, tls: skip, body rewriting, credential_binding, allow_uninspected_credentials). Before this PR, tests wrote out a whole policy as a YAML string. New testkit module testkit::policy:
    • base() reads base-policy.yaml: the filesystem and process settings every sandbox needs, and no network access.
    • allow(&mut policy, name, host, port, binaries) adds an endpoint. Its Endpoint setters are named after the YAML fields, e.g. .rest(Access::Full).credential_binding(&provider).
    • to_yaml() renders the policy for CLI commands such as policy set.
    • A unit test checks that each helper builds exactly what its YAML parses to.
    • PYTHON_BINARIES names the paths python3 runs from in testkit::python_image() images, which differ between the workload image and the pullable uv image.
    • The sandbox builder can now combine a policy with an image and providers. Before, cx.sandbox(..).policy(..) rejected both, and these tests need all three.
  3. A provider per test, cleaned up afterwards. Each test imports its own provider profile and creates a provider under a name unique to the run. New testkit::install_provider(cx, name, profile_yaml, credentials) registers cleanup for both before creating them.
  4. Small CLI and exec plumbing. testkit::YamlFile writes YAML to a temporary file for CLI commands that read one (profile import, policy set); host-gateway replaces its PolicyFile copy with it. ExecOutput::success() fails a test on a non-zero exec exit, like CliOutput::success().

Helpers for OpenShell itself (sandboxes, policies, providers, the CLI) go in testkit. Fixtures for services outside OpenShell go in tests/support/.

Changes

  • conformance: add credential_gating, one file with the same six scenarios and unique names as test(e2e): split credential_gating into independent parallel tests #5264, built on the helpers above.
    • The tests assert on the request that reached the host server, not on verdict strings printed by the sandbox client. The REST client is a short urllib POST.
    • Dropped a client-side check: the WebSocket test no longer requires the client to see close code 1008. It still requires that the handshake reaches upstream and the binary frame does not.
    • Dropped the "secret reached the sandbox" check, which could never fail.
    • The gateway's rejections are checked through the builder's create() error. The CLI is used for profile import, provider create and policy set --wait.
  • lanes: the Docker and Podman scripts now build openshell/e2e-python:dev on every lane, including e2e-conformance-only. The Kubernetes and VM scripts default OPENSHELL_TEST_PYTHON_IMAGE to the pinned uv image in e2e/support/conformance.sh, and e2e:testkit depends on e2e:workload:build. This keeps credential coverage on the lanes that still run conformance from these scripts (VM, GPU, Agent Sandbox, external driver) until a host provisioner replaces them.
  • external Kubernetes driver lane: the Helm post-renderer now copies the chart's server.hostGatewayIP into the standalone driver's OPENSHELL_HOST_GATEWAY_IP. Before, only the in-process driver got it, so sandboxes on that lane couldn't resolve host.openshell.internal. The lane hadn't run a host-reachability test before.
  • removal: delete e2e/rust/tests/credential_gating.rs, its [[test]] entry, and its PODMAN_CI_TESTS entry.
  • docs: the testkit README lists the new helpers. The conformance README and TESTING.md list the suite and note that it needs host.openshell.internal.

Gaps found by the pilot (for #5296)

  • Step logging: there is no per-step progress output. A slow step shows up only as the test's total time.
  • Sandbox names: Podman caps sandbox names at 19 characters, which leaves 5 for cx.sandbox(suffix). testkit doesn't check this before sending the request.
  • Error-text assertions: the admission test matches phrases in the gateway's error ("credentialed endpoint", "L4-only", "tls:"/"skip"), as test(e2e): split credential_gating into independent parallel tests #5264 does. That is stricter than the conformance README's guidance on asserting behavior over wording.
  • Remote clusters: host servers run on the test host, so remote Kubernetes or OpenShift gateways can't reach them.
  • Host lanes on tmachine: the Docker and Podman host lanes still run through e2e/rust/e2e-*.sh scripts, not tmachine.

Testing

  • cargo fmt and cargo clippy --locked --all-targets -D warnings on testkit, support, features and conformance.

  • testkit unit tests: 43 passed.

  • check_cargo_lockfile_alignment.py and check-conformance-program.sh (22 tests) pass.

  • Local Podman (macOS, e2e/with-podman-gateway.sh): openshell-conformance run credential_gating passed 6/6 in 33.7s. host_gateway passed.

    Test Time
    policy_binding_requires_inspection_or_opt_in 2.1s
    policy_binding_rest_body_keeps_placeholder_without_rewrite 2.7s
    policy_binding_rest_body_rewrite_sends_secret_upstream 1.9s
    policy_binding_updates_provider_environment_live 22.4s
    profile_rest_endpoint_keeps_body_placeholder 2.8s
    profile_websocket_endpoint_denies_binary_frames 1.8s
  • Not run locally: Docker (Docker Desktop requires sign-in), k3s, k3s-ha. CI covers these rows.

CI: copy-pr-bot doesn't sync draft PRs, so E2E runs only after a maintainer comments /ok to test <sha>. In #5264, the WebSocket test took 60–120s on Kubernetes. If k3s or k3s-ha hit the per-test timeout, I'll raise it here rather than change .config/nextest.toml (see #5295).

  • Checks appropriate to the affected code and behavior pass
  • Unit tests added/updated (if applicable)
  • E2E tests added/updated (if applicable)

Checklist

  • Follows Conventional Commits
  • Commits are signed off (DCO)
  • Architecture docs updated (if applicable)

@matthewgrossman matthewgrossman added the test:e2e Requires end-to-end coverage label Nov 5, 2026
@copy-pr-bot

copy-pr-bot Bot commented Nov 5, 2026

Copy link
Copy Markdown

Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

@github-actions

github-actions Bot commented Nov 5, 2026

Copy link
Copy Markdown

Label test:e2e applied, but pull-request/5318 does not exist yet. A maintainer needs to comment /ok to test 1472ee7b123f994b562336888e2bb2fea87bb91d to mirror this PR. Once the mirror exists, re-apply the label or re-run Branch E2E Checks from the Actions tab.

@github-actions

github-actions Bot commented Nov 5, 2026

Copy link
Copy Markdown

Base automatically changed from test/5275-migration-prep/matthewgrossman to main November 6, 2026 13:41
Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
Port the six credential gating scenarios from e2e/rust/tests/credential_gating.rs
into a conformance suite. Each test imports its own provider profile and
creates a provider under a run-unique name, creates sandboxes from the
Python test image through the CLI, and reaches probe servers on the host
through host.openshell.internal.

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
The conformance suite now creates sandboxes from testkit::python_image().
Docker and Podman runners build openshell/e2e-python:dev also in
e2e-conformance-only mode, and the Kubernetes and VM runners default
OPENSHELL_TEST_PYTHON_IMAGE to the pullable uv Python image.

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
Add testkit::policy (a base policy from base-policy.yaml, allow(), and
Endpoint settings), install_provider(), YamlFile, ExecOutput::success(),
and PYTHON_BINARIES, so suites build policies and providers the same way.

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
Add host-server-support with HttpRecorder and WebSocketRecorder, servers on
the test host that sandboxes reach through host.openshell.internal. Move the
host-gateway feature test onto them and onto testkit's base policy.

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
Use testkit's policy helpers and install_provider and host-server-support's
recorders, and assert on the requests that reached the host server instead
of verdicts the sandbox clients print.

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
@matthewgrossman
matthewgrossman force-pushed the test/5296-credential-gating-pilot/matthewgrossman branch from 8e5c8d7 to 566b39d Compare November 6, 2026 18:59
@matthewgrossman

Copy link
Copy Markdown
Member Author

/ok to test 566b39d

The external-driver lane renders server.hostGatewayIP, but only the
in-process driver reads it from the gateway TOML. The standalone driver
takes OPENSHELL_HOST_GATEWAY_IP, which the post-renderer never set, so
sandbox policy DNS refused host.openshell.internal on that lane.

Copy the chart's rendered value into the driver container when present.

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
@matthewgrossman

Copy link
Copy Markdown
Member Author

/ok to test 99bbed4

@github-actions

github-actions Bot commented Nov 6, 2026

Copy link
Copy Markdown

Compatibility review (advisory)

New or updated breaking changes; see the full report for all findings.

1. cli — No explicit risk acceptance found

OPENSHELL_E2E_KUBERNETES_FEATURES=e2e,e2e-kubernetes mise run e2e:kubernetes previously excluded host-dependent credential tests but now runs them unconditionally through openshell-conformance.

  • Impact: Remote Kubernetes/OpenShift operators and driver authors encounter failed connections to host.openshell.internal and conformance exit 1 even when their gateway satisfies the existing contract.
  • Action: For v0.1.8, preserve the existing host-gateway opt-in or move these tests to a feature suite; the new warning does not address this patch-train break.
  • PR: test(conformance): port the credential gating e2e tests #5318
Review details
  • Execution: complete
  • Candidate: PR #5318 (c29689d502b2329fb012721ec158f81b7e83c8e1)
  • Baseline: cc4acdcfdeda9fdeeb0fe94a537d47ad8414eb3d (cc4acdcfdeda9fdeeb0fe94a537d47ad8414eb3d)
  • Reviewed: rust-sdk, python-sdk, go-sdk, typescript-sdk, api-behavior, cli, configuration, policy, helm, state

Advisory review; publication gates are unchanged.

Branch check run

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

test:e2e Requires end-to-end coverage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

test(conformance): port the credential gating e2e tests

1 participant