Skip to content

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

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

matthewgrossman wants to merge 17 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 requests, head and body apart, once n have arrived. It reads Content-Length bodies, not chunked ones.
    • WebSocketRecorder::start() completes the WebSocket handshake. It then reads frames until a close frame, the end of the connection, or a 10s timeout. wait_for_handshake() waits for the handshake, and wait_until_finished() waits for the connection to end and reports whether a binary frame arrived.
    • 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::import_profile(cx, profile_yaml) imports a profile and returns its id, read from the YAML, and testkit::create_provider(cx, name, type, credentials) creates a provider. Each registers its cleanup before creating. testkit::install_provider(cx, name, profile_yaml, credentials) does both.
  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. The original client counted a read timeout as "denied" too, so the check passed whether or not the supervisor closed the connection. The test now requires that the handshake reaches upstream, waits until upstream's connection has ended, and requires that no binary frame arrived on it.
    • The REST tests compare the body upstream exactly (the secret, or the placeholder between the client's prefix and suffix), and check that the secret appears nowhere in the request. Failure messages print the request with the secret redacted.
    • Dropped the "secret reached the sandbox" check, which could never fail.
    • The gateway's rejections are checked through the builder's create() error: the gRPC code must be FailedPrecondition, and the message keeps test(e2e): split credential_gating into independent parallel tests #5264's wording checks. testkit's Error now keeps the gateway's status code (grpc_code(), testkit::GrpcCode). The CLI is used for profile import, provider create and policy set --wait.
  • test image: testkit::python_image() still defaults to openshell/e2e-python:dev. The Docker and Podman runners now build it for conformance, as the tmachine installers already load it. The Kubernetes and VM runners set OPENSHELL_TEST_PYTHON_IMAGE to a pinned public uv Python image unless it is already set. A gateway outside these lanes has to set OPENSHELL_TEST_PYTHON_IMAGE; running conformance against any gateway without an override is a follow-up. host_gateway runs python3 from PATH and allows PYTHON_BINARIES, so it works with either image, and no longer retries its connection for 20s: the credential tests never did, and the server listens before the sandbox is created.
  • 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. The renderer fails if the lane set the IP but the chart rendered no host.openshell.internal alias.
  • 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 state that conformance requires the machine running it to be reachable from sandboxes as 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

Conformance has to run against any gateway, and the unpublished
openshell/e2e-python:dev image only exists where a lane built or loaded
it. Default to the pinned uv Python image, which any gateway can pull,
and keep OPENSHELL_TEST_PYTHON_IMAGE as the override.

Host lanes no longer build the workload image for conformance. The base
test policy names no workload user, so drivers use a default non-root
user on images without one, and host_gateway runs python3 from PATH.

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

Copy link
Copy Markdown
Member Author

/ok to test 61504a3

- The WebSocket recorder reads frames until the connection ends, and the
  test waits for that before checking that no binary frame arrived.
- The HTTP recorder keeps the request head and body apart. The REST tests
  compare the body exactly and check the secret appears nowhere, and
  failure messages redact it.
- testkit errors keep the gateway's gRPC status code, and the admission
  test checks for FailedPrecondition.
- The policy create request destructures the SDK spec, so a new field
  fails to compile instead of being dropped.
- host_gateway no longer retries its connection.
- Docs state that conformance requires sandboxes to reach the test
  machine as host.openshell.internal.

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

Copy link
Copy Markdown
Member Author

/ok to test 3a31220

@elezar elezar left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the port. The recorder-based assertions are a clear improvement on the old verdict strings, and keeping all six scenarios while moving them onto shared helpers is the right shape for the pilot.

I'd like two things changed before this merges, plus some smaller suggestions inline:

  1. Drop 61504a338 (the default Python image change) and handle "conformance runs on any gateway" in a follow-up.
  2. Give the WebSocket test a positive control, so the denial assertion can fail.

Remote-cluster behaviour (inline comment on TESTING.md) is fine as a tracked follow-up.

Comment thread tests/testkit/testkit/src/image.rs Outdated
/// The image for tests that run `python3` in a sandbox. It is public and
/// pinned by digest so any gateway can pull it, and has the Python standard
/// library only: no curl, git or openssl.
const DEFAULT_PYTHON_IMAGE: &str = "ghcr.io/astral-sh/uv:0.12.17-python3.12-trixie-slim@sha256:9a59bb7206905ccaae4f7dab222fbac47c125a21e5fc16f43f427cd6c940ade3";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requested change (introduced in 61504a338)

Could we drop this commit and do the "runs on any gateway" goal as a follow-up?

Before it, python_image() defaults to openshell/e2e-python:dev. The tmachine Docker, Podman and k3s installers load it, and the Docker and Podman runners build it, so no registry pull happens. After it, the default is the uv image from ghcr.io. Nothing pre-pulls it, so each host or node pulls it on its first sandbox, inside that test's timeout. Every lane then needs outbound registry access, and the default loses curl, git and openssl.

15fd553c8 limits the damage: Docker and Podman build the local image, but the Kubernetes and VM runners point the tests at the uv image, so those lanes still pull it. With this commit dropped, the tmachine lanes keep using the loaded image.

The "any gateway" goal is a good one, and the follow-up can pick the mechanism deliberately: pre-load the pinned image in every installer, publish our own test image, or add a capability check. Dropping the commit also restores run_as_user: sandbox in base-policy.yaml. Please check that nothing else depends on the Podman workaround, and keep the OPENSHELL_TEST_PYTHON_IMAGE override.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ya this was one I went back and forth on, I think it's something worth striving for but I think it's fair if it doesn't work quite yet. I think a followup ticket to make sure that we can eventually do this would be nice, which I'll do

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up for the "runs on any gateway" goal is tracked in #5352, under #5281. It records why the public-image default was reverted here and lists the open choices: a fuller public image or our own published one, pre-loading it so tests don't pull inside their timeouts, and an up-front pull check.

This PR is now back to openshell/e2e-python:dev as the default, with the Kubernetes and VM runners passing the pinned uv image through OPENSHELL_TEST_PYTHON_IMAGE, and run_as_user: sandbox restored in the base policy.

Comment on lines +91 to +107
async fn profile_websocket_endpoint_denies_binary_frames(cx: &mut Context) -> testkit::Result {
let server = HttpRecorder::start("ok").await?;
let websocket = WebSocketRecorder::start().await?;
let provider = install_gating_provider(cx, Some((server.port, websocket.port))).await?;

let mut policy = policy::base();
allow(&mut policy, websocket.port).websocket(Access::ReadWrite);
let sandbox = create_sandbox(cx, "ws", policy, &provider).await?;
run_python(&sandbox, WEBSOCKET_CLIENT, websocket.port).await?;
websocket
.wait_for_handshake()
.await
.context("upstream should receive the WebSocket handshake")?;
let binary_seen = websocket.wait_until_finished().await?;
testkit::ensure!(
!binary_seen,
"credentialed WebSocket binary frame reached upstream"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requested change (introduced in 566b39d81)

This test passes whenever no binary frame reaches upstream, whatever the reason. The supervisor forwards binary frames by default and denies them only when deny_uninspected_credentials is set (l7/websocket.rs:866). A supervisor that dropped every frame, or a close before the frame was read, would pass here.

Could you add a control: the same client and endpoint in a sandbox without the provider attached, expecting wait_until_finished() to return true? That shows the recorder can see a binary frame and makes the denial meaningful. A small unit test in host-server that sends a binary frame directly to WebSocketRecorder would also guard read_frames.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added both. The test now first runs the same client and endpoint in a sandbox without the provider and requires wait_until_finished() to return true, then runs the credentialed case and requires false. host-server has unit tests that send frames straight to WebSocketRecorder: a binary frame after a 300-byte text frame (the 126 extended-length path) is seen, and text frames alone are not. They also check the accept key against the RFC 6455 sample.

Comment thread TESTING.md
@@ -656,6 +657,8 @@ explains the isolation backend separation behind the capability-free runtime.
On a **remote** cluster, drop the `e2e-host-gateway` feature. Those tests rely

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking, fine as a follow-up

Before this PR, e2e:kubernetes with e2e,e2e-kubernetes excluded the host-dependent credential tests. With this PR, openshell-conformance runs them unconditionally, so a remote Kubernetes or OpenShift gateway fails with connection errors even when it meets the contract. The compatibility bot flagged the same thing.

This is acceptable for the pilot if it is tracked. Please link a follow-up issue and say here that these tests need the gateway to reach host.openshell.internal. The options for the follow-up are to preserve the host-gateway opt-in, move these tests to a feature suite, or add an up-front reachability probe that skips with a clear message instead of a 10s poll timeout.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tracked in #5409, under #5281, with the three options you listed (keep an opt-in, move the tests to a feature suite, or check reachability up front and skip with a clear message). TESTING.md and the conformance README now say these tests need sandboxes to reach the test machine as host.openshell.internal and link the issue.

Comment thread tests/support/host-server/src/lib.rs Outdated
};
// The supervisor checks Sec-WebSocket-Accept, so compute it properly.
// SHA-1 is what RFC 6455 specifies for it; it protects nothing here.
let mut hasher = Sha1::new();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion (introduced in f0d528712; also applies to Cargo.toml:17)

sha1 and base64 are added only for the accept key. tungstenite::handshake::derive_accept_key does the same computation. It's public in 0.26.2, the version already in all the tests lockfiles, and it passes the RFC 6455 sample. Using it would drop both dependencies and avoid a new sha1 user that CRYPTO.md:116 doesn't list (only openshell-supervisor-network is). The tests workspaces aren't scanned by cargo deny, so CI won't flag it either way.

Separately, at about 300 lines with two independent servers, splitting into http.rs and websocket.rs would keep the handshake code in one file. Optional.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done: the accept key now comes from tungstenite::handshake::derive_accept_key, and sha1 and base64 are gone from host-server. tungstenite was already in the test workspaces' lockfiles. host-server is split into http.rs and websocket.rs, with the shared bind and header helpers in lib.rs. The recorder unit tests still check the accept value against the RFC 6455 sample.

# server.hostGatewayIP renders the gateway pod's only hostAliases entry
# (deploy/helm/openshell/templates/_gateway-workload.tpl), which the
# host-gateway component copies from hostAliases.0.ip.
if grep -q '^ *hostAliases:' "${work_dir}/rendered.yaml"; then

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion (introduced in 99bbed46c)

The hostAliases: grep matches any workload, and when it misses, sandboxes fail later on name resolution (the symptom this fixes). Two small changes: match the - host.openshell.internal line, and fail when the lane set an IP but no alias rendered. The second needs the lane to export HOST_GATEWAY_IP, which is a plain shell variable in with-kube-gateway.sh today. I checked both against helm template output with and without the IP, and with an unrelated workload that has hostAliases.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done: the post-renderer now matches the - host.openshell.internal line instead of any hostAliases:. The kube gateway script exports OPENSHELL_E2E_EXPECTED_HOST_GATEWAY_IP for the external-driver lane, and the renderer fails with a clear error if that is set but no alias rendered. Checked with helm template: IP set copies it into the driver env, no IP adds nothing, and a chart with the alias renamed fails.

Comment thread tests/testkit/testkit/src/provider.rs Outdated
crate::ensure!(
profile
.lines()
.any(|line| line.trim_end() == format!("id: {name}")),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion (introduced in 7a70c1283)

The id: {name} check is a line match, so id: "x" or a different layout fails or passes wrongly. Parsing the profile would be sturdier. Also, tying the provider name to the profile id rules out several providers from one profile and the built-in provider types. Splitting this into import_profile and create_provider, with install_provider built from them, would cover both.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done: testkit::import_profile(cx, profile) parses the YAML for id (with noyalib, the workspace's YAML crate, already in testkit's lockfile) and returns it, and testkit::create_provider(cx, name, type, credentials) takes the name and type separately. Each registers its own cleanup. install_provider is now built from the two, so the provider name no longer has to match the profile id.

/// Let `binaries` connect to `host:port` under the network policy `name`,
/// adding the policy if it doesn't exist. Returns the new endpoint, which
/// [`Endpoint`] configures further.
pub fn allow<'a>(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion, follow-up (introduced in 7a70c1283; also applies to the Endpoint trait at line 75)

Two notes for the next policy tests. allow() merges binaries across a rule name and doesn't dedupe endpoints, so a second call widens the first endpoint's binaries. Worth documenting or scoping. Endpoint also lacks enforcement, L7 rules and allowed_ips; keycloak-provider-refresh already uses enforcement: enforce and still hand-rolls its tempfile instead of YamlFile. The trait shape is fine for adding these.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Documented on policy::allow() and in the testkit README: binaries apply to every endpoint under a rule name, so use a different name to keep them apart. I left Endpoint as is and will add fields when a ported test needs them.

/// `..`, so a field added to the SDK's spec fails to compile here instead of
/// being dropped. [`SandboxBuilder::create`] rejects a GPU or service
/// exposures before calling this.
fn policy_create_request(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note, follow-up (introduced in 3a3122064)

policy_create_request rebuilds the SDK's create request because the SDK spec has no policy field. The exhaustive destructure is a good guard. The better fix is a policy field on the SDK builder, which would also lift the GPU and service-exposure restriction.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Filed #5410 under #5281 for a policy field on the Rust SDK's SandboxSpec, including the open question of which type it takes, since the curated spec avoids wire types. The doc comment on policy_create_request now points to it.

/// The paths `python3` runs from in [`python_image`] images, as network
/// policy binaries: the system Python, the pullable uv image's Python, and the
/// workload image's uv-installed Python behind its virtualenv.
pub const PYTHON_BINARIES: &[&str] = &[

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note (introduced in 7a70c1283)

PYTHON_BINARIES is valid for the known images but separate from python_image(). With OPENSHELL_TEST_PYTHON_IMAGE set to an image with a different layout, policy rules silently don't match. Either return the binaries with the image or document the override contract.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Documented on OPENSHELL_TEST_PYTHON_IMAGE in image.rs and in the testkit README: an override image must run python3 from one of PYTHON_BINARIES, or policies won't match it. The PYTHON_BINARIES comment says to add a path for an image that installs Python elsewhere.

@matthewgrossman
matthewgrossman marked this pull request as ready for review November 9, 2026 16:29
…mage

The tmachine installers load it and the Docker and Podman lanes build it,
so those lanes don't pull an image inside a test's timeout or lose curl,
git and openssl. The Kubernetes and VM lanes keep using the pinned public
image through OPENSHELL_TEST_PYTHON_IMAGE, and the base test policy names
the sandbox user again. Running conformance against any gateway without
an override is left to a follow-up.

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

Copy link
Copy Markdown
Member Author

/ok to test 9ab1be1

@github-actions

github-actions Bot commented Nov 9, 2026

Copy link
Copy Markdown

Compatibility review (advisory)

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

1. cli — Risk explicitly accepted

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 run --junit &lt;path&gt;.

  • Impact: Remote Kubernetes/OpenShift operators and driver authors now encounter failed connections to host.openshell.internal and conformance exit 1 despite satisfying the previously supported gateway contract.

  • Action: The earlier host-gateway opt-in or feature-suite remediation remains necessary for v0.1.8; the documented restriction and accepted follow-up do not resolve this patch-train violation.

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

  • Acceptance: comment

Review details
  • Execution: complete
  • Candidate: PR #5318 (328cde47374d56cd8cbd425adc3dd8a2ad2d210c)
  • 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

The test passed whenever no binary frame reached upstream, whatever the
reason. It now first sends the same frame from a sandbox without the
provider and requires that it arrives, so the denial check can fail.
Unit tests check that WebSocketRecorder sees a binary frame after a long
text frame, and none among text frames.

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

Copy link
Copy Markdown
Member Author

/ok to test 4ff621b

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

Copy link
Copy Markdown
Member Author

/ok to test e5e72bf

Use tungstenite's derive_accept_key instead of hashing with sha1 and
base64 directly, and split host-server into http.rs and websocket.rs.

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
… is missing

The post-renderer copied the host gateway IP whenever the chart rendered
any hostAliases. It now matches the host.openshell.internal entry, and
fails if the lane set server.hostGatewayIP but no such entry rendered.

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
…r create

import_profile reads the profile's id from its YAML and returns it, and
create_provider takes the provider name and type separately, so the
provider name no longer has to match the profile id. install_provider
does both. Document that policy::allow merges binaries under one name
and that an override Python image must use a PYTHON_BINARIES path.

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

Copy link
Copy Markdown
Member Author

/ok to test e22087f

…gating-pilot/matthewgrossman

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

Copy link
Copy Markdown
Member Author

/ok to test aeec397

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

2 participants