Skip to content

fix(snap): require mTLS for the snap gateway - #3726

Merged
drew merged 11 commits into
mainfrom
fix/snap-gateway-mtls
Sep 26, 2026
Merged

drew merged 11 commits into
mainfrom
fix/snap-gateway-mtls

Conversation

@drew

@drew drew commented Sep 25, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Require mTLS on snap install

Related Issue

No issue required: localized hardening of the Snap packaging default.

Changes

  • Snap gateway uses mTLS. The wrapper no longer forces OPENSHELL_DISABLE_TLS=true, so the gateway serves TLS from the bundle it generates in $SNAP_COMMON/tls and requires a client certificate. The default config (snap/hooks/install) no longer sets allow_unauthenticated_users = true or pins a compute driver; with the local TLS bundle and an auto-detected local driver, mTLS user authentication turns on automatically.
  • Insecure configs are replaced on install and refresh. The install hook, also run by the new post-refresh hook, replaces any regular gateway config that explicitly sets allow_unauthenticated_users = true or disable_tls = true with the secure default, without keeping a copy. Secure custom configs, symlinks, and non-regular files are left alone.
  • Refresh restarts the gateway. refresh-mode changes from endure to restart, and the post-refresh hook runs snapctl restart on the gateway. Published revisions use endure, which snapd honors when refreshing away from them, so without the explicit restart the plaintext gateway kept running after an upgrade. Refreshes interrupt active sandbox sessions.
  • install.sh registers the snap over mTLS. It detects an mTLS snap revision by the presence of its post-refresh hook, waits for the gateway by probing HTTPS with the root-owned client bundle, copies the bundle into the target user's snap state (directories 0700, files 0600, written by the user), and registers https://127.0.0.1:17670 --local, replacing an existing registration. Older plaintext revisions still register over HTTP with a warning.
  • The snap is opt-in in install.sh. Linux installs default to the Debian or RPM package. OPENSHELL_INSTALL_METHOD=snap|deb|rpm selects the package explicitly; hosts that already have the OpenShell snap keep refreshing it. The release canary Snap jobs and the Snap repro script opt in explicitly.
  • Docs and checks. Updated the installation guide and snap store description (manual certificate steps for direct snap install), architecture/build.md, the cluster debugging skill, the release canary (asserts mTLS auth and an HTTPS registration), and the install-script, hook, wrapper, packaging, and release-formula tests.

Testing

Automated

  • mise run pre-commit passes on the final commit.
  • Focused tests pass: tasks/scripts/test-install-sh.sh (install-method selection, mTLS detection, HTTPS/HTTP registration, listener probe, client bundle permissions), test-snap-install-hook.sh (fresh default, insecure default and edited configs replaced with no copy kept, secure custom configs/symlinks/directories untouched, post-refresh migrates and runs snapctl restart), and test-packaging-assets.sh (includes the wrapper tests). release_formula_test.py passes.
  • mise run test passed on an earlier revision of this branch, before the later hook and installer changes.

VM (tmachine ubuntu-docker-rootful, real snapd and Docker)

Snaps: the published edge snap (rev 1605, plaintext) and the same snap repacked with this PR's wrapper, install and post-refresh hooks, and refresh-mode: restart. Gateway and CLI binaries are unchanged by this PR.

  • Baseline: on the published snap, another local user with no credentials got HTTP 200 from plaintext gRPC and listed sandboxes.
  • Refresh from the published snap (with an edited insecure config) and fresh install: config replaced with no copy kept; the gateway auto-detected Docker and logged mTLS user authentication; install.sh registered the installing user over HTTPS with Authenticated (mTLS transport); a sandbox was created and ran a command. A second local user without the bundle was rejected over plaintext gRPC (404), HTTPS without a certificate, and the plaintext CLI, and could not read either copy of the client key.
  • This branch's install.sh with OPENSHELL_INSTALL_METHOD=snap against the store latest/stable snap (rev 1606, plaintext): installed, warned about unauthenticated access, and registered over HTTP.
  • Refreshing that install to the patched snap with the HTTP registration in place: the old registration stopped working immediately (gateway restarted into mTLS by post-refresh); rerunning install.sh replaced the registration with HTTPS and authenticated with mTLS. This scenario found and verified the post-refresh restart fix. The store install/refresh inside install.sh was stubbed for this rerun because the patched snap only exists locally.

Not covered before merge

  • A snap built by the release pipeline and a store refresh to it. The release canary covers this on the edge snap after merge (it now asserts mTLS auth and an HTTPS registration).

Checklist

  • Follows Conventional Commits.
  • Commits are signed off (DCO).
  • Published and architecture docs updated.
  • Unit and packaging tests updated.
  • Snap refresh and fresh install tested end to end in a VM (repacked snap).
  • Release canary passes on the published edge snap (after merge).

Replace the installer opt-in with an authenticated snap gateway. The wrapper
no longer forces plaintext, so the gateway serves TLS from the bundle it
already generates in $SNAP_COMMON/tls. The install hook writes a config that
enables mTLS user auth instead of unauthenticated access, and a new
post-refresh hook migrates the exact legacy default on existing installs.

install.sh waits for the gateway, detects whether it serves TLS, copies the
client bundle into the target user's snap state directory, and registers the
gateway over HTTPS. Older plaintext snap revisions still register over HTTP
with a warning. The release canary asserts mTLS auth and HTTPS registration.

Signed-off-by: Drew Newberry <anewberry@nvidia.com>
An explicit [openshell.gateway.mtls_auth] table fails config preflight,
which validates mTLS auth before the local TLS bundle supplies the client
CA. Write a default that pins the Docker driver instead; with the wrapper's
TLS bundle the gateway requires client certificates and enables mTLS user
auth automatically, as the native packages do.

The mTLS gateway rejects TLS handshakes without a client certificate, and
it still answers plaintext loopback HTTP for sandbox service routing, so the
installer could misdetect it as a legacy plaintext gateway. Probe HTTPS with
the root-owned client bundle, and treat a gateway as legacy only when a
plaintext gRPC Health call succeeds.

Signed-off-by: Drew Newberry <anewberry@nvidia.com>
Signed-off-by: Drew Newberry <anewberry@nvidia.com>
Signed-off-by: Drew Newberry <anewberry@nvidia.com>
@github-actions

Copy link
Copy Markdown

Detect the mTLS snap from the installed revision's post-refresh hook instead
of probing plaintext gRPC, and drop the scheme global. Remove the installer's
pre-hook config fallback, which is dead now that every channel ships the
install hook and which wrote the insecure default. Give the install hook a
single write path with a simple backup name, and shorten the manual client
certificate steps.

Signed-off-by: Drew Newberry <anewberry@nvidia.com>
Signed-off-by: Drew Newberry <anewberry@nvidia.com>
Stop selecting the OpenShell snap just because the snap command exists. Linux
installs default to the Debian or RPM package; OPENSHELL_INSTALL_METHOD=snap
(or deb, rpm) selects the package explicitly. Hosts that already have the
OpenShell snap keep refreshing it rather than gaining a second gateway on the
same port. The release canary and snap repro script opt in explicitly.

Signed-off-by: Drew Newberry <anewberry@nvidia.com>
Signed-off-by: Drew Newberry <anewberry@nvidia.com>
Published revisions use refresh-mode: endure, and snapd honors the old
revision's setting during a refresh, so the plaintext gateway kept running
with the migrated config unused until a manual restart. Restart the gateway
from the post-refresh hook so the mTLS config takes effect immediately.

Signed-off-by: Drew Newberry <anewberry@nvidia.com>
Signed-off-by: Drew Newberry <anewberry@nvidia.com>
Signed-off-by: Drew Newberry <anewberry@nvidia.com>
@drew
drew added this pull request to the merge queue Sep 26, 2026
Merged via the queue into main with commit a67567e Sep 26, 2026
73 checks passed
@drew
drew deleted the fix/snap-gateway-mtls branch September 26, 2026 01:59
@drew drew added this to the OpenShell 0.1.1 milestone Sep 28, 2026
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.

2 participants