Skip to content

Commit 5619687

Browse files
authored
deploy: ship the vault dependency, keep it opt-in (#106)
Declaring cryptography nowhere meant the credential vault was impossible to enable in the bundled Docker image and systemd unit even with WEBSH_VAULT_ENABLE=1 set — the HAS_CRYPTOGRAPHY gate kept it off. Ship the dependency (requirements.txt; pip layer in the image, apt/pip step in the install docs) and pre-provision a writable creds path (Docker /data volume, systemd StateDirectory=websh — the standard writable-state mechanism under the unit's ProtectSystem=strict hardening) so enabling the vault is a one-line opt-in. WEBSH_VAULT_ENABLE stays off by default: turning persistent server-side credential storage on is an operator's decision, not a packaging side effect. Also fixes the manual-install dependency step for PEP 668 (apt install python3-cryptography) and documents Docker named-volume persistence (-v websh-data:/data).
1 parent 330a970 commit 5619687

6 files changed

Lines changed: 71 additions & 8 deletions

File tree

Dockerfile

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,27 @@ RUN apt-get update && apt-get install -y --no-install-recommends openssh-client
88
RUN useradd -r -s /bin/false websh
99

1010
WORKDIR /app
11+
COPY requirements.txt ./
12+
RUN pip install --no-cache-dir -r requirements.txt
1113
COPY server.py index.html websh.js ./
1214
COPY assets/ ./assets/
1315

16+
# Writable home for the encrypted credential vault (websh.creds.json).
17+
# WORKDIR is root-owned, so point the vault at a dir the websh user owns
18+
# and expose it as a volume so saved credentials survive a container
19+
# replacement.
20+
RUN mkdir -p /data && chown websh:websh /data
21+
VOLUME /data
22+
1423
USER websh
1524

16-
ENV PORT=8765 HOST=0.0.0.0 SESSION_TIMEOUT=300 MAX_SESSIONS=50
25+
# The bundled cryptography wheel makes the encrypted credential vault
26+
# available, but it stays OFF by default. Opt in at run time with
27+
# `-e WEBSH_VAULT_ENABLE=1` (add `-v websh-data:/data` to persist the
28+
# store across container replacement). WEBSH_CREDS_PATH points the store
29+
# at the writable /data volume — the default cwd path is not writable here.
30+
ENV PORT=8765 HOST=0.0.0.0 SESSION_TIMEOUT=300 MAX_SESSIONS=50 \
31+
WEBSH_CREDS_PATH=/data/websh.creds.json
1732

1833
EXPOSE 8765
1934

docs/configuration.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Environment variables for `server.py`:
1010
| `MAX_SESSIONS` | `50` | Max concurrent SSH sessions |
1111
| `MAX_SESSIONS_PER_IP` | `0` | Max concurrent sessions per source IP (`0` disables; counts foreground + background together) |
1212
| `WEBSH_CONFIG` | *(unset)* | Path to `websh.json`. `server.py` loads a config only when this is set; the PHP proxy computes a default (`../../websh.json`). |
13-
| `WEBSH_VAULT_ENABLE` | `0` | Set to `1` to enable the encrypted credential vault endpoints and saved-credential UI when `cryptography` is installed. See [`encryption.md`](encryption.md). |
13+
| `WEBSH_VAULT_ENABLE` | `0` | Set to `1` to enable the encrypted credential vault endpoints and saved-credential UI (requires `cryptography`). The bundled `websh.service` and Docker image ship the dependency and a writable creds path, so enabling the vault there is a one-line opt-in. See [`encryption.md`](encryption.md). |
1414
| `WEBSH_CREDS_PATH` | *(sibling of `WEBSH_CONFIG`)* | Path to the encrypted credential store `websh.creds.json`. See [`encryption.md`](encryption.md). Created lazily on first user save with mode `0600`. |
1515
| `WEBSH_REQUIRE_VAULT` | `0` | Set to `1` to make legacy plaintext credentials in `websh.json` a fatal startup error (forces migration to the vault) instead of a warning. See [`encryption.md`](encryption.md). |
1616
| `TRUSTED_PROXIES` | `127.0.0.1` | Comma-separated IPs to trust `X-Forwarded-For` from |

docs/deployment.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,15 @@ docker build -t websh .
8080
docker run -d -p 127.0.0.1:8765:8765 websh
8181
```
8282

83+
The image ships the vault dependency but leaves it **off** by default. To
84+
enable it, add `-e WEBSH_VAULT_ENABLE=1`; the encrypted store lives under
85+
the `/data` volume. Mount a named volume to persist it across container
86+
replacement:
87+
88+
```bash
89+
docker run -d -p 127.0.0.1:8765:8765 -e WEBSH_VAULT_ENABLE=1 -v websh-data:/data websh
90+
```
91+
8392
Open `http://localhost:8765/` — the backend serves the frontend directly.
8493
The container still listens on `0.0.0.0` internally so Docker port
8594
publishing works, but the command above binds the published host port to
@@ -96,6 +105,14 @@ mkdir -p /opt/websh
96105
# Copy the backend, the frontend, AND the assets/ dir (the logo lives
97106
# there; without it index.html 404s on assets/websh-logo.svg).
98107
cp -r server.py index.html websh.js assets/ /opt/websh/
108+
109+
# Install the one optional dependency so the encrypted credential vault
110+
# can be enabled (it ships off by default). On Debian/Ubuntu the system
111+
# Python is externally managed (PEP 668), so use the distro package rather
112+
# than a system-wide pip; skipping this is non-fatal — the server still
113+
# runs and the saved-credential UI just stays hidden.
114+
apt install python3-cryptography # or: pip install --break-system-packages -r requirements.txt
115+
99116
cp websh.service /etc/systemd/system/
100117
systemctl enable --now websh
101118
```
@@ -117,6 +134,14 @@ systemctl restart websh
117134
The bundled unit also pins `PORT`/`HOST`; change them there (or via
118135
`systemctl edit`) rather than relying on the in-code defaults.
119136

137+
The unit pre-provisions a writable `/var/lib/websh` via
138+
`StateDirectory=websh`, but leaves the encrypted credential vault **off**
139+
by default. With `cryptography` installed (the optional step above),
140+
enabling it is one line — add `Environment=WEBSH_VAULT_ENABLE=1` in the
141+
same `systemctl edit` override; saved credentials then persist (encrypted)
142+
under `/var/lib/websh/websh.creds.json`. See
143+
[`encryption.md`](encryption.md).
144+
120145
## HTTPS via reverse proxy
121146

122147
Put nginx or Caddy in front for TLS termination:

docs/encryption.md

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
# Encrypted credential vault
22

3-
> **Status:** implemented behind an operator opt-in. Install
4-
> `cryptography` and set `WEBSH_VAULT_ENABLE=1` to enable the encrypted
5-
> save UI and vault endpoints. Deployments without that optional
6-
> dependency or flag continue to run, but saved-credential UI stays
7-
> hidden and legacy plaintext entries are not migrated automatically.
3+
> **Status:** implemented, **off by default**. The bundled `websh.service`
4+
> and Docker image ship the `cryptography` dependency (`requirements.txt`)
5+
> and a writable creds path, so enabling the vault there is a one-line
6+
> opt-in (`WEBSH_VAULT_ENABLE=1`); a bare `python3 server.py` additionally
7+
> needs `cryptography` installed. Either way a deployment missing the
8+
> dependency or flag still runs — the saved-credential UI just stays
9+
> hidden, and legacy plaintext entries are not migrated automatically.
810
911
Saved SSH credentials are stored as **opaque encrypted blobs** on the
1012
server. The decryption key lives in the browser's IndexedDB, generated
@@ -27,7 +29,9 @@ the ~50 ms it takes to type it into the SSH PTY.
2729
Without it, websh keeps working — the saved-credential UI is just
2830
hidden. With it, the browser's "Save" checkbox enables encrypted
2931
storage end-to-end.
30-
2. Opt the vault on explicitly:
32+
2. Opt the vault on explicitly (the bundled `websh.service` and Docker
33+
image already ship the dependency, so this flag is all that's needed
34+
there):
3135
```bash
3236
WEBSH_VAULT_ENABLE=1 python3 server.py
3337
```

requirements.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# websh runs on the Python 3 standard library alone. The single optional
2+
# dependency below ships in the bundled Docker image and systemd unit so
3+
# the encrypted credential vault can be turned on without chasing a missing
4+
# wheel. The vault stays OFF by default; opt in with WEBSH_VAULT_ENABLE=1.
5+
# Without this dependency the server still runs — the saved-credential UI
6+
# just stays hidden. See docs/encryption.md.
7+
# 3.4.8 is the documented minimum; modern wheels (>=42) are recommended.
8+
cryptography>=3.4.8

websh.service

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,17 @@ Environment=HOST=127.0.0.1
1313
Environment=SESSION_TIMEOUT=300
1414
Environment=MAX_SESSIONS=50
1515

16+
# The encrypted credential vault is available but OFF by default. To turn
17+
# it on, `systemctl edit websh` and add `Environment=WEBSH_VAULT_ENABLE=1`
18+
# (the cryptography wheel from requirements.txt must be installed; without
19+
# it the server still runs and the saved-credential UI just stays hidden).
20+
# StateDirectory pre-creates /var/lib/websh owned by the service user and
21+
# keeps it writable despite ProtectSystem=strict / ProtectHome=read-only,
22+
# so the lazily created websh.creds.json (mode 0600) has somewhere to live
23+
# the moment the vault is enabled.
24+
Environment=WEBSH_CREDS_PATH=/var/lib/websh/websh.creds.json
25+
StateDirectory=websh
26+
1627
User=websh
1728
Group=websh
1829

0 commit comments

Comments
 (0)