Skip to content

Commit 49ce59e

Browse files
committed
fix(snap): migrate insecure gateway configs on refresh
Signed-off-by: Drew Newberry <anewberry@nvidia.com>
1 parent ed9ca18 commit 49ce59e

10 files changed

Lines changed: 124 additions & 38 deletions

File tree

‎architecture/build.md‎

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -336,12 +336,17 @@ artifact and verifies the release run instead of listing every repository
336336
artifact.
337337

338338
Snapd runs the gateway as a root-owned system service. Its generated client
339-
certificates reside in root-owned snap state and are unavailable to ordinary CLI
340-
users, so the Snap uses plaintext loopback transport and enables unauthenticated
341-
local users by default. Debian and RPM packages instead run systemd user services
342-
and use user-owned mTLS material. Bootstrap creates the default configuration
343-
only when it is missing. Sandbox-to-gateway sessions remain authenticated with
344-
gateway-minted JWTs.
339+
certificates reside in root-owned snap state. The installer copies the client
340+
bundle into the target user's private Snap state and registers the TLS endpoint;
341+
direct Snap installs require the same enrollment. The install and post-refresh
342+
hooks replace configs that explicitly enable plaintext or unauthenticated access
343+
with the secure Docker default, preserving one private backup. Snap refreshes
344+
restart the gateway so the migrated config takes effect immediately.
345+
346+
Debian and RPM packages instead run systemd user services with user-owned mTLS
347+
material. Sandbox-to-gateway sessions remain authenticated with gateway-minted
348+
JWTs.
349+
345350
The Debian qualification profile keeps candidate-image overrides outside the
346351
operator-owned gateway configuration: it writes a harness-owned file under
347352
`/var/lib/openshell-qualification` and selects it through the packaged systemd

‎docs/about/installation.mdx‎

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -112,18 +112,28 @@ sudo snap install openshell
112112

113113
The snap does not migrate existing Debian, RPM, or Homebrew installs. Remove any existing installation first, then rerun the script with `OPENSHELL_ACK_BREAKING_UPGRADE=1`.
114114

115-
The gateway runs as a system service at `http://127.0.0.1:17670` and reads `/var/snap/openshell/common/gateway.toml`.
116-
117-
<Warning>
118-
The snap gateway allows unauthenticated access from the local host. Any local user or process can operate it. Do not expose it beyond the local host.
119-
</Warning>
120-
121-
Snap refreshes do not restart the gateway, so active sandboxes keep running. Restart it to pick up a new version:
115+
The gateway runs as a system service at `https://127.0.0.1:17670` and reads `/var/snap/openshell/common/gateway.toml`. It requires a client certificate. The install script copies that certificate to the installing user's Snap state and registers the gateway automatically. If you installed with `sudo snap install openshell`, give each trusted user the certificate and register the gateway from that user's account:
122116

123117
```shell
124-
sudo systemctl restart snap.openshell.gateway
118+
d=~/snap/openshell/common/.local/state/openshell/tls
119+
mkdir -p -m 700 "$d/client"
120+
sudo cat /var/snap/openshell/common/tls/ca.crt > "$d/ca.crt"
121+
sudo cat /var/snap/openshell/common/tls/client/tls.crt > "$d/client/tls.crt"
122+
(umask 077; sudo cat /var/snap/openshell/common/tls/client/tls.key > "$d/client/tls.key")
123+
chmod 700 "$d" "$d/client"
124+
chmod 600 "$d/ca.crt" "$d/client/tls.crt" "$d/client/tls.key"
125+
openshell gateway add https://127.0.0.1:17670 --local --name openshell
126+
openshell status
125127
```
126128

129+
Keep the client key private. Anyone who can read it can authenticate to the local gateway.
130+
131+
On refresh, the Snap replaces any regular gateway config that explicitly enables unauthenticated access or disables TLS with the secure Docker default. Other settings in that file are not carried over. It saves the previous config at `/var/snap/openshell/common/gateway.toml.pre-mtls` with mode `0600`, or at a uniquely suffixed path if that backup already exists. The refresh log prints the actual path. Review the backup before restoring other settings; restoring its insecure settings reopens access.
132+
133+
Snap refreshes restart the gateway to apply the migrated config immediately. This interrupts active sandbox sessions.
134+
135+
If you previously registered the plaintext endpoint, run the certificate and HTTPS registration steps above after the refresh. Remove the old registration first with `openshell gateway remove openshell` if the name already exists. You can also rerun the install script to refresh the registration automatically.
136+
127137
To install a locally built snap, connect its interfaces manually:
128138

129139
```shell

‎install.sh‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1295,6 +1295,7 @@ copy_snap_client_bundle() {
12951295
for _file in ca.crt client/tls.crt client/tls.key; do
12961296
as_root cat "${_src}/${_file}" |
12971297
as_target_user sh -c 'umask 077; cat >"$1"' sh "${_dst}/${_file}"
1298+
as_target_user chmod 600 "${_dst}/${_file}"
12981299
done
12991300
}
13001301

‎python/openshell/release_formula_test.py‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,11 @@ def test_snap_wrapper_uses_optional_gateway_config_without_generating_toml() ->
155155
'export OPENSHELL_DB_URL="${OPENSHELL_DB_URL:-sqlite:${SNAP_COMMON}/gateway.db?mode=rwc}"'
156156
in wrapper
157157
)
158-
assert 'export OPENSHELL_DISABLE_TLS="${OPENSHELL_DISABLE_TLS:-true}"' in wrapper
158+
assert "OPENSHELL_DISABLE_TLS" not in wrapper
159+
assert (
160+
'export OPENSHELL_LOCAL_TLS_DIR="${OPENSHELL_LOCAL_TLS_DIR:-${SNAP_COMMON}/tls}"'
161+
in wrapper
162+
)
159163
assert (
160164
'exec "${SNAP}/bin/openshell-gateway" --config "$CANONICAL_CONFIG_FILE" "$@"'
161165
in wrapper

‎skills/debug-openshell-cluster/SKILL.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ Common findings:
7272
- `No active gateway`: register one with `openshell gateway add <endpoint>`.
7373
- Connection refused: gateway process is not running, service exposure is wrong, or a port-forward/proxy is not active.
7474
- TLS/certificate errors: the endpoint scheme or trust chain is wrong, a local mTLS bundle does not match the gateway CA, or TLS termination does not match the gateway listener.
75+
- A Snap refresh restarts the gateway with its migrated mTLS config. The secure Snap gateway uses `https://127.0.0.1:17670` and requires a client bundle in the user's Snap state. Check `/var/snap/openshell/common/gateway.toml.pre-mtls` for settings replaced during migration, then follow the published Snap installation steps to re-register an old HTTP client.
7576
- `Unauthenticated` from an edge or OIDC gateway: refresh stored credentials with `openshell gateway login [name]`, then retry. Use `gateway logout` only when intentionally clearing local credentials.
7677
- A direct development endpoint with a private or self-signed certificate can be isolated with `--gateway-endpoint <url> --gateway-insecure`; do not persist or recommend insecure verification for shared gateways.
7778

‎snap/hooks/install‎

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,20 @@
22
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
33
# SPDX-License-Identifier: Apache-2.0
44

5-
# Ensure the gateway is secured by default with mTLS.
5+
# Create the mTLS default and replace insecure configs on refresh.
66

77
set -eu
88

99
config_file="${SNAP_COMMON}/gateway.toml"
1010

11-
legacy_config='[openshell]
12-
version = 2
13-
14-
[openshell.gateway]
15-
16-
[openshell.gateway.auth]
17-
allow_unauthenticated_users = true'
18-
1911
replace=false
2012
if [ -L "$config_file" ]; then
2113
exit 0
2214
elif [ -f "$config_file" ]; then
23-
if [ "$(cat "$config_file")" != "$legacy_config" ]; then
15+
# Refresh every insecure default or edited config. Keep secure operator
16+
# configs intact, but do not preserve an explicit plaintext or anonymous
17+
# access setting merely because the file has other edits.
18+
if ! grep -Eq '^[[:space:]]*(allow_unauthenticated_users|disable_tls)[[:space:]]*=[[:space:]]*true([[:space:]#]|$)' "$config_file"; then
2419
exit 0
2520
fi
2621
replace=true
@@ -42,6 +37,17 @@ compute_driver = "docker"
4237
CONFIG
4338

4439
if [ "$replace" = true ]; then
40+
# Keep one private copy for operators who need to restore other settings.
41+
backup_file="${config_file}.pre-mtls"
42+
backup_tmp=$(mktemp "${backup_file}.XXXXXX")
43+
cp "$config_file" "$backup_tmp"
44+
chmod 600 "$backup_tmp"
45+
if [ ! -e "$backup_file" ] && [ ! -L "$backup_file" ] && ln "$backup_tmp" "$backup_file"; then
46+
rm -f "$backup_tmp"
47+
else
48+
backup_file="$backup_tmp"
49+
fi
50+
echo "openshell: replacing insecure gateway config; previous config saved at $backup_file" >&2
4551
mv -f "$temporary_file" "$config_file"
4652
trap - 0 HUP INT TERM
4753
exit 0

‎snapcraft.yaml‎

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,19 @@ description: |
3939
sudo cat /var/snap/openshell/common/tls/ca.crt > "$d/ca.crt"
4040
sudo cat /var/snap/openshell/common/tls/client/tls.crt > "$d/client/tls.crt"
4141
(umask 077; sudo cat /var/snap/openshell/common/tls/client/tls.key > "$d/client/tls.key")
42+
chmod 700 "$d" "$d/client"
43+
chmod 600 "$d/ca.crt" "$d/client/tls.crt" "$d/client/tls.key"
4244
openshell gateway add https://127.0.0.1:17670 --local --name openshell
4345
openshell status
4446
45-
After a snap refresh, restart the gateway to pick up the new revision:
46-
47-
sudo snap restart openshell.gateway
48-
49-
Restarting the gateway will interrupt active sandbox sessions. The gateway
50-
is not restarted automatically to avoid disconnecting running sandboxes.
47+
On refresh, configs that explicitly enable plaintext or unauthenticated
48+
access are replaced with the secure default. The previous file is saved at
49+
/var/snap/openshell/common/gateway.toml.pre-mtls (mode 0600), or a uniquely
50+
suffixed path if that backup already exists. The gateway
51+
restarts automatically during refresh to apply the new revision and
52+
config. This interrupts active sandbox sessions. Users registered against
53+
the old HTTP endpoint must remove that gateway registration and repeat
54+
step 2, or rerun install.sh.
5155
5256
base: core24
5357
grade: stable
@@ -95,10 +99,9 @@ apps:
9599
gateway:
96100
command: bin/openshell-gateway-wrapper
97101
daemon: simple
98-
# refresh-mode: endure prevents snapd from restarting the gateway daemon
99-
# during snap refreshes, which would kill active sandbox sessions.
100-
# Operators must manually restart the service after a refresh if needed.
101-
refresh-mode: endure
102+
# Refresh must activate the migrated mTLS config immediately. This
103+
# interrupts active sandbox sessions.
104+
refresh-mode: restart
102105
# Snapd runs this daemon as root. The wrapper serves TLS from the bundle
103106
# generated in $SNAP_COMMON/tls, and the default config requires client
104107
# certificates; the installer copies the client bundle to the target

‎tasks/scripts/test-install-sh.sh‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,9 @@ printf 'ca\n' >"${snap_tls_src}/ca.crt"
530530
printf 'cert\n' >"${snap_tls_src}/client/tls.crt"
531531
printf 'key\n' >"${snap_tls_src}/client/tls.key"
532532
snap_user_home="${tmpdir}/snap-user-home"
533+
mkdir -p "${snap_user_home}/snap/openshell/common/.local/state/openshell/tls/client"
534+
printf 'old key\n' >"${snap_user_home}/snap/openshell/common/.local/state/openshell/tls/client/tls.key"
535+
chmod 644 "${snap_user_home}/snap/openshell/common/.local/state/openshell/tls/client/tls.key"
533536
(
534537
as_root() { "$@"; }
535538
as_target_user() { "$@"; }

‎tasks/scripts/test-packaging-assets.sh‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ if [[ ! -x "$snap_install_hook" ]]; then
124124
exit 1
125125
fi
126126
assert_contains "$snap_install_hook" 'compute_driver = "docker"'
127+
assert_contains "$snapcraft" 'refresh-mode: restart'
127128
if [[ ! -x "$(dirname "$snap_install_hook")/post-refresh" ]]; then
128129
echo "FAIL: Snap post-refresh hook must be executable" >&2
129130
exit 1

‎tasks/scripts/test-snap-install-hook.sh‎

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,17 +63,69 @@ cp "$legacy" "$common/gateway.toml"
6363
printf '\n# operator note\n' >>"$common/gateway.toml"
6464
cp "$common/gateway.toml" "${work}/legacy-edited-before"
6565
SNAP_COMMON="$common" "$hook"
66-
cmp -s "${work}/legacy-edited-before" "$common/gateway.toml"
66+
cmp -s "$expected" "$common/gateway.toml"
67+
cmp -s "${work}/legacy-edited-before" "$common/gateway.toml.pre-mtls"
68+
if [[ -z $(find "$common/gateway.toml.pre-mtls" -perm 600) ]]; then
69+
echo "FAIL: migrated config backup must be mode 0600" >&2
70+
exit 1
71+
fi
72+
SNAP_COMMON="$common" "$hook"
73+
cmp -s "${work}/legacy-edited-before" "$common/gateway.toml.pre-mtls"
74+
cp "$legacy" "$common/gateway.toml"
75+
SNAP_COMMON="$common" "$hook"
76+
cmp -s "$expected" "$common/gateway.toml"
77+
cmp -s "${work}/legacy-edited-before" "$common/gateway.toml.pre-mtls"
78+
if [[ $(find "$common" -maxdepth 1 -name 'gateway.toml.pre-mtls.*' -type f | wc -l) -ne 1 ]]; then
79+
echo "FAIL: repeated migration must preserve the existing backup" >&2
80+
exit 1
81+
fi
82+
83+
common="${work}/custom-insecure"
84+
mkdir -p "$common"
85+
cat >"$common/gateway.toml" <<'EOF'
86+
[openshell]
87+
version = 2
88+
89+
[openshell.gateway]
90+
compute_driver = "docker"
91+
disable_tls = true # old local override
92+
93+
[openshell.gateway.auth]
94+
allow_unauthenticated_users = true # old local override
95+
EOF
96+
cp "$common/gateway.toml" "${work}/custom-insecure-before"
97+
SNAP_COMMON="$common" "$hook"
98+
cmp -s "$expected" "$common/gateway.toml"
99+
cmp -s "${work}/custom-insecure-before" "$common/gateway.toml.pre-mtls"
100+
101+
common="${work}/custom-secure"
102+
mkdir -p "$common"
103+
cat >"$common/gateway.toml" <<'EOF'
104+
[openshell]
105+
version = 2
106+
107+
[openshell.gateway]
108+
compute_driver = "docker"
109+
# allow_unauthenticated_users = true
110+
EOF
111+
cp "$common/gateway.toml" "${work}/custom-secure-before"
112+
SNAP_COMMON="$common" "$hook"
113+
cmp -s "${work}/custom-secure-before" "$common/gateway.toml"
114+
if [[ -e "$common/gateway.toml.pre-mtls" ]]; then
115+
echo "FAIL: secure operator config should not be backed up or replaced" >&2
116+
exit 1
117+
fi
67118

68119
common="${work}/post-refresh"
69120
mkdir -p "$common" "${work}/snap/meta/hooks"
70121
cp "$hook" "${work}/snap/meta/hooks/install"
71-
cp "$legacy" "$common/gateway.toml"
122+
cp "${work}/legacy-edited-before" "$common/gateway.toml"
72123
SNAP="${work}/snap" SNAP_COMMON="$common" "${hook_dir}/post-refresh"
73124
if ! cmp -s "$expected" "$common/gateway.toml"; then
74-
echo "FAIL: post-refresh hook must migrate the legacy unauthenticated config" >&2
125+
echo "FAIL: post-refresh hook must migrate an edited insecure config" >&2
75126
exit 1
76127
fi
128+
cmp -s "${work}/legacy-edited-before" "$common/gateway.toml.pre-mtls"
77129

78130
common="${work}/broken-link"
79131
mkdir -p "$common"

0 commit comments

Comments
 (0)