feat(helm): cert-manager external issuer + OpenShift passthrough Route - #2468
feat(helm): cert-manager external issuer + OpenShift passthrough Route#2468jhjaggars wants to merge 9 commits into
Conversation
87527a1 to
cf02477
Compare
|
/ok to test cf02477 |
Blocking1. Native roots are controlled by the sandbox image
The supervisor now calls both If an attacker can also influence DNS or routing for the gateway connection, they can install their own CA, present a certificate for the gateway hostname, and receive the sandbox JWT that the supervisor sends on its RPCs. That can expose sandbox-scoped data and let the fake gateway return attacker-selected policy during supervisor startup. There is also a compatibility regression: tonic 0.14 returns Please remove 2.
|
…ssuer Addresses all five blocking review items from NVIDIA#2468: 1. Remove .with_native_roots() from supervisor gRPC client -- the supervisor runs inside the user-selected sandbox image, so the image CA bundle is not operator-controlled. Keep .with_webpki_roots() (compiled-in, not user-controlled) alongside the configured CA. 2. Fail at render time when serverIssuerRef.name is set but clientCaFromServerTlsSecret is still true. Add negative Helm test. 3. Remove clientIssuerRef -- changing only clientIssuerRef breaks both directions because trust bundles are not modeled separately. Change serverIssuerRef.kind default from ClusterIssuer to Issuer. 4. Add server.oidc.issuer and server.oidc.audience to the documented OpenShift production Helm command. Add Access Control prerequisite. 5. Fail at render time when openshiftRoute.enabled and disableTls are both true. Add negative Helm test. Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>
cf02477 to
0276980
Compare
Follow-up review: all five blocking items addressedRe-reviewed at
Remaining1. The env-scrubbing half of item 1 is still open
Now that the WebPKI roots are trusted, a sandbox user who can also redirect the gateway hostname inside the container can satisfy verification with a publicly valid certificate for a name they control and receive the sandbox JWT. Before this change, only the configured CA was trusted, so redirection alone was not enough. Please strip the variable in the Docker and Podman drivers to match what the Kubernetes driver already does. 2. Dangling
|
|
/ok to test 0276980 |
… default clientCaSecretName, remove stale clientIssuerRef doc ref Address remaining review feedback from NVIDIA#2468: 1. Strip OPENSHELL_GATEWAY_TLS_SERVER_NAME from Docker and Podman driver supervisor environments to match the K8s driver. With WebPKI roots trusted, leaving this user-controllable would let an attacker inside the sandbox redirect TLS verification to a hostname they control. 2. Remove dangling 'clientIssuerRef' reference from docs/kubernetes/managing-certificates.mdx — the value was removed from the chart in the prior fix commit. 3. Add a fail guard in cert-manager-pki.yaml when clientCaFromServerTlsSecret=false but clientCaSecretName is still the default (openshell-server-client-ca), which nothing creates under cert-manager. Includes a negative Helm unittest. Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>
cc11d20 to
876959a
Compare
…ssuer Addresses all five blocking review items from NVIDIA#2468: 1. Remove .with_native_roots() from supervisor gRPC client -- the supervisor runs inside the user-selected sandbox image, so the image CA bundle is not operator-controlled. Keep .with_webpki_roots() (compiled-in, not user-controlled) alongside the configured CA. 2. Fail at render time when serverIssuerRef.name is set but clientCaFromServerTlsSecret is still true. Add negative Helm test. 3. Remove clientIssuerRef -- changing only clientIssuerRef breaks both directions because trust bundles are not modeled separately. Change serverIssuerRef.kind default from ClusterIssuer to Issuer. 4. Add server.oidc.issuer and server.oidc.audience to the documented OpenShift production Helm command. Add Access Control prerequisite. 5. Fail at render time when openshiftRoute.enabled and disableTls are both true. Add negative Helm test. Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>
… default clientCaSecretName, remove stale clientIssuerRef doc ref Address remaining review feedback from NVIDIA#2468: 1. Strip OPENSHELL_GATEWAY_TLS_SERVER_NAME from Docker and Podman driver supervisor environments to match the K8s driver. With WebPKI roots trusted, leaving this user-controllable would let an attacker inside the sandbox redirect TLS verification to a hostname they control. 2. Remove dangling 'clientIssuerRef' reference from docs/kubernetes/managing-certificates.mdx — the value was removed from the chart in the prior fix commit. 3. Add a fail guard in cert-manager-pki.yaml when clientCaFromServerTlsSecret=false but clientCaSecretName is still the default (openshell-server-client-ca), which nothing creates under cert-manager. Includes a negative Helm unittest. Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>
Follow-up review at
|
The supervisor gRPC client only trusted the CA configured via OPENSHELL_TLS_CA, since tonic ClientTlsConfig starts with an empty root store unless with_native_roots()/with_webpki_roots() is also enabled. Deployments where the gateway server certificate is issued by a public CA (e.g. cert-manager against an ACME issuer) caused every supervisor connection to fail the TLS handshake with "UnknownCA", since the sandbox mTLS CA and the server cert issuer were no longer the same. Enable both native and webpki roots in addition to the configured CA. tonic root store is a union of all configured sources, so this does not weaken verification for existing self-signed deployments. webpki-roots (compiled in) is enabled alongside native-roots since the supervisor binary may run in minimal sandbox images without a populated system CA bundle. Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>
… passthrough Add certManager.serverIssuerRef/clientIssuerRef so the gateway and mTLS client certificates can be issued by a real Issuer/ClusterIssuer (e.g. ACME) instead of only the chart built-in self-signed CA. Add openshiftRoute template for exposing the gateway via a TLS passthrough Route so the gateway keeps terminating its own TLS/mTLS. The server Certificate excludes internal-only SANs (cluster-local, localhost, loopback) when an external issuer is configured, since ACME issuers reject those per CA/Browser Forum baseline requirements. A template-time fail guard catches the misconfiguration at helm install time rather than asynchronously at cert-manager issuance time. Includes Helm unittest coverage for both issuerRef overrides and Route rendering, plus a CI values overlay for lint coverage. Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>
Update managing-certificates.mdx with the serverIssuerRef workflow and install-time validation behavior. Add a production section to the OpenShift guide covering passthrough Route with a real certificate. Regenerate Helm README for new certManager and openshiftRoute values. Sync debug-openshell-cluster skill with new troubleshooting steps for ACME issuance failures and supervisor UnknownCA from mismatched CAs. Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>
…ssuer Addresses all five blocking review items from NVIDIA#2468: 1. Remove .with_native_roots() from supervisor gRPC client -- the supervisor runs inside the user-selected sandbox image, so the image CA bundle is not operator-controlled. Keep .with_webpki_roots() (compiled-in, not user-controlled) alongside the configured CA. 2. Fail at render time when serverIssuerRef.name is set but clientCaFromServerTlsSecret is still true. Add negative Helm test. 3. Remove clientIssuerRef -- changing only clientIssuerRef breaks both directions because trust bundles are not modeled separately. Change serverIssuerRef.kind default from ClusterIssuer to Issuer. 4. Add server.oidc.issuer and server.oidc.audience to the documented OpenShift production Helm command. Add Access Control prerequisite. 5. Fail at render time when openshiftRoute.enabled and disableTls are both true. Add negative Helm test. Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>
Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>
876959a to
aac65a9
Compare
Follow-up review at
|
… tests Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>
|
/ok to test 85ec214 |
…r TLS Split the gateway server certificate into two: an internal cert issued by the chart's own CA (for supervisor connections via cluster-local SANs) and an external cert issued by an operator-configured Issuer such as ACME/Let's Encrypt (for CLI and Route access via public SANs). The gateway uses SNI-based certificate selection: connections whose SNI hostname matches external_server_names receive the external cert; all others (including those with no SNI) receive the internal cert. Security improvement: remove .with_webpki_roots() from the supervisor gRPC client so supervisors trust only the chart CA, closing a MITM vector via publicly-trusted certificates in user-supplied container images. Key changes: - Add DualCertResolver with SNI-based cert selection and full test coverage - Add external_cert_path, external_key_path, external_server_names to TlsConfig - Validate partial external cert config (error on cert-without-key or vice versa) - Validate empty external_server_names when external cert is configured - Split cert-manager templates into internal + external Certificate resources - Add Helm guards for misconfigured external issuer (empty serverDnsNames, internal-only SANs with external issuer, conflicting clientCaFromServerTlsSecret) - Update gateway-config.mdx, managing-certificates.mdx, openshift.mdx docs - Update debug-openshell-cluster skill for dual-cert troubleshooting Signed-off-by: Pi Agent <agent@openshell.local>
Head branch was pushed to by a user without write access
|
All contributors have signed the DCO ✍️ ✅ |
…comments Add the same GATEWAY_TLS_SERVER_NAME environment stripping to the VM compute driver that Docker, Podman, and Kubernetes drivers already perform. Without this, a sandbox user on the VM driver could override the TLS server name the supervisor verifies. Fix stale comments in Docker and Podman drivers that referenced 'with WebPKI roots trusted' — WebPKI roots are explicitly not trusted after the tls-webpki-roots removal. Use tls-ring instead of bare channel for tonic in openshell-core so the TLS API (ClientTlsConfig, Endpoint::tls_config) is available without pulling in any root certificate store. Signed-off-by: Pi Agent <agent@openshell.local>
Add RFC 6125 single-level wildcard matching to DualCertResolver so external_server_names entries like *.example.com correctly match SNI hostnames like gw.example.com. Previously only exact matches worked, silently falling back to the internal cert for wildcard configurations. Add a Helm fail guard in route.yaml that rejects openshiftRoute.host values not listed in certManager.serverDnsNames when an external issuer is configured — catches cert/route hostname mismatches at install time instead of at TLS connect time. Quote the host field in route.yaml for robustness. Signed-off-by: Pi Agent <agent@openshell.local>
4e5ec46 to
1362c65
Compare
Summary
Lets cert-manager issue the gateway's server TLS certificate from a real Issuer/ClusterIssuer (for example an ACME issuer) instead of only the chart's built-in self-signed CA, and adds an OpenShift Route template with TLS passthrough so the gateway can be exposed externally with a publicly-trusted certificate while it keeps terminating its own TLS/mTLS.
Closes #2466
Changes
Dual-certificate architecture (SNI-based)
The gateway server certificate is split into two: an internal cert issued by the chart's own CA (for supervisor connections via cluster-local SANs) and an external cert issued by an operator-configured Issuer such as ACME/Let's Encrypt (for CLI and Route access via public SANs). The gateway uses SNI-based certificate selection (
DualCertResolver): connections whose SNI hostname matchesexternal_server_namesreceive the external cert; all others receive the internal cert. Wildcard patterns (e.g.*.example.com) are supported per RFC 6125.Security: supervisor trust pinned to chart CA
The sandbox supervisor's gRPC client previously trusted WebPKI roots (
tls-webpki-roots). This PR removes that trust and keeps supervisors pinned exclusively to the chart CA configured viaOPENSHELL_TLS_CA. TheUnknownCAproblem (supervisor connecting to a gateway serving an ACME cert) is solved server-side: supervisors connect via internal service names, the SNI resolver presents the internal (chart CA) cert, and the TLS handshake succeeds without needing public root trust.This is a deliberate security improvement — a user-supplied container image that ships its own root store can no longer influence the supervisor's trust decisions, closing a MITM vector where a publicly-trusted cert for an attacker-controlled name could intercept the sandbox JWT.
All four compute drivers (Docker, Podman, VM, Kubernetes) now strip
OPENSHELL_GATEWAY_TLS_SERVER_NAMEfrom the sandbox environment to prevent user-supplied overrides of the TLS server name the supervisor verifies.Helm templates
certManager.serverIssuerRef(new value): creates a second serverCertificatefrom the operator's Issuer/ClusterIssuer with only externally-resolvable SANs. The internal server cert always uses the chart's own CA issuer.certManager.serverDnsNamesentries are validated: internal-only names are rejected when an external issuer is configured.templates/route.yaml: OpenShiftRoutewithtls.termination: passthrough, gated byopenshiftRoute.enabled. Includes fail guards for TLS disabled and Route host not covered byserverDnsNames.serverDnsNameswith an external issuer is rejected at install time.Docs
docs/kubernetes/openshift.mdxwith full helm install command.docs/kubernetes/managing-certificates.mdxdocumenting the dual-cert architecture.docs/reference/gateway-config.mdxupdated withexternal_cert_path,external_key_path,external_server_namesTOML fields..agents/skills/debug-openshell-cluster/SKILL.mdupdated with dual-cert troubleshooting.Testing
mise run pre-commitpassesDualCertResolverSNI selection (exact + wildcard),build_cert_resolvervalidation (partial config, empty names), and full TCP+TLS integration testGATEWAY_TLS_SERVER_NAMEstripping across Docker, Podman, and VM driversValidated end-to-end against a live OpenShift (ROSA) cluster: passthrough Route serving a real Let's Encrypt certificate issued via a Route53 DNS-01 ClusterIssuer, OIDC-authenticated CLI access via Keycloak, sandbox created and exec'd successfully with the supervisor connecting back to the gateway over the internal (chart CA) TLS path.
Checklist
mise run pre-commitpasses