Harden deployment: DB ownership and config secrecy

- Restrict the deployed /etc/esim-provisioning/config.yaml to 600 owned by the
  esim service user; it holds the OP key and was previously world-readable.
- Chown data/ AFTER init_db runs (as root) so the SQLite file is owned by the
  esim service user; the service runs with ReadWritePaths=.../data and could
  not write a root-owned database before.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
A ashkan@beigi.net 2026-07-01 21:59:27 +00:00
parent c6423b1bad
commit 035bc59460
2 changed files with 14 additions and 1 deletions

View File

@ -33,6 +33,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- The in-memory profile cache is guarded by a lock (thread-safe under a - The in-memory profile cache is guarded by a lock (thread-safe under a
threaded server), and the captive portal disables the activate button while threaded server), and the captive portal disables the activate button while
a request is in flight to prevent double provisioning. a request is in flight to prevent double provisioning.
- `setup_wifi_ap.sh`: the deployed `config.yaml` (holds the OP key) is now
`chmod 600` and owned by the service user instead of world-readable, and
`data/` is chowned *after* `init_db` so the root-created SQLite file is
writable by the `esim` service (previously it was root-owned and the service
could not write to it).
- Added `test_app.py` (API-level: provision, rate-limit, token download, - Added `test_app.py` (API-level: provision, rate-limit, token download,
404 on unknown/enumerated ids, collision retry/give-up), `test_utils.py` 404 on unknown/enumerated ids, collision retry/give-up), `test_utils.py`
(MAC/IMSI/op_key/config/ensure_parent_dir), and adapter APN coverage. (MAC/IMSI/op_key/config/ensure_parent_dir), and adapter APN coverage.

View File

@ -143,8 +143,12 @@ python3 -m venv "$INSTALL_DIR/venv"
# Create system user # Create system user
id esim &>/dev/null || useradd --system --no-create-home --shell /usr/sbin/nologin esim id esim &>/dev/null || useradd --system --no-create-home --shell /usr/sbin/nologin esim
# Restrict config — it holds the OP key and other secrets. Readable only by
# the service user, not world-readable.
chown esim:esim /etc/esim-provisioning/config.yaml
chmod 600 /etc/esim-provisioning/config.yaml
mkdir -p "$INSTALL_DIR/data" mkdir -p "$INSTALL_DIR/data"
chown -R esim:esim "$INSTALL_DIR/data"
# Install systemd services # Install systemd services
cp "$REPO_DIR/systemd/esim-provisioning.service" /etc/systemd/system/ cp "$REPO_DIR/systemd/esim-provisioning.service" /etc/systemd/system/
@ -155,6 +159,10 @@ systemctl daemon-reload
CONFIG_PATH=/etc/esim-provisioning/config.yaml \ CONFIG_PATH=/etc/esim-provisioning/config.yaml \
"$INSTALL_DIR/venv/bin/python" "$INSTALL_DIR/scripts/init_db.py" "$INSTALL_DIR/venv/bin/python" "$INSTALL_DIR/scripts/init_db.py"
# Own data (incl. the DB file just created by root) as the service user, which
# runs with ReadWritePaths=.../data — otherwise the service cannot write it.
chown -R esim:esim "$INSTALL_DIR/data"
# Enable everything # Enable everything
systemctl unmask hostapd systemctl unmask hostapd
systemctl enable hostapd dnsmasq nginx esim-provisioning captive-portal systemctl enable hostapd dnsmasq nginx esim-provisioning captive-portal