From d368f904fb4866ec644e6ba3d0ffe6b37118c01b Mon Sep 17 00:00:00 2001 From: dev-qwen Date: Tue, 21 Apr 2026 13:21:21 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20edge-control:=20install.sh=20seeds=20emp?= =?UTF-8?q?ty=20allowlist=20=E2=80=94=20every=20register=20breaks=20until?= =?UTF-8?q?=20admin=20populates=20it,=20with=20no=20install-time=20warning?= =?UTF-8?q?=20(#1110)=20(#1113)=20Co-authored-by:=20dev-qwen=20=20Co-committed-by:=20dev-qwen=20?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tools/edge-control/README.md | 17 ++++++---- tools/edge-control/install.sh | 61 +++++++++++++++++++++++++++++++---- 2 files changed, 65 insertions(+), 13 deletions(-) diff --git a/tools/edge-control/README.md b/tools/edge-control/README.md index 0c95dda..002507b 100644 --- a/tools/edge-control/README.md +++ b/tools/edge-control/README.md @@ -183,11 +183,16 @@ Shows all registered tunnels with their ports and FQDNs. ## Allowlist -The allowlist prevents project name squatting by requiring admin approval before a name can be registered. It is **opt-in**: when `allowlist.json` is empty (no project entries), registration works as before. Once the admin adds entries, only approved names are accepted. +The allowlist prevents project name squatting by requiring admin approval before a name can be registered. It is **opt-in**: when `allowlist.json` does not exist, registration is unrestricted. When the file exists, only project names listed in the `allowed` map can be registered. -### Setup +### Install-time behavior -Edit `/var/lib/disinto/allowlist.json` as root: +- **Fresh install**: `install.sh` seeds an empty allowlist (`{"version":1,"allowed":{}}`) and prints a warning that registration is now gated until entries are added. +- **Upgrade onto an existing box**: if `registry.json` has registered projects but `allowlist.json` does not exist, `install.sh` auto-populates the allowlist with each existing project name (unbound — `pubkey_fingerprint: ""`). This preserves current behavior so existing tunnels keep working. The operator can tighten pubkey bindings later. + +### Format + +`/var/lib/disinto/allowlist.json` (root-owned, `0644`): ```json { @@ -203,9 +208,9 @@ Edit `/var/lib/disinto/allowlist.json` as root: } ``` -- **With `pubkey_fingerprint`**: Only the specified SSH key can register this project name. The fingerprint is the SHA256 output of `ssh-keygen -lf `. -- **With empty `pubkey_fingerprint`**: Any caller may register this project name (name reservation without key binding). -- **Not listed**: Registration is refused with `{"error":"name not approved"}`. +- **With `pubkey_fingerprint`** (non-empty): only the SSH key with that exact SHA256 fingerprint can register this project name. +- **With empty `pubkey_fingerprint`**: any caller may register this project name (name reservation without key binding). +- **Not listed in `allowed`**: registration is refused with `{"error":"name not approved"}`. ### Workflow diff --git a/tools/edge-control/install.sh b/tools/edge-control/install.sh index 384e70b..b0f0182 100755 --- a/tools/edge-control/install.sh +++ b/tools/edge-control/install.sh @@ -8,9 +8,11 @@ # What it does: # 1. Creates users: disinto-register, disinto-tunnel # 2. Creates /var/lib/disinto/ with registry.json, registry.lock, allowlist.json -# 3. Installs Caddy with Gandi DNS plugin -# 4. Sets up SSH authorized_keys for both users -# 5. Installs control plane scripts to /opt/disinto-edge/ +# 3. On upgrade: auto-populates allowlist.json from existing registry entries +# 4. On fresh install: seeds empty allowlist with warning (registration gated) +# 5. Installs Caddy with Gandi DNS plugin +# 6. Sets up SSH authorized_keys for both users +# 7. Installs control plane scripts to /opt/disinto-edge/ # # Requirements: # - Fresh Debian 12 (Bookworm) @@ -158,12 +160,39 @@ LOCK_FILE="${REGISTRY_DIR}/registry.lock" touch "$LOCK_FILE" chmod 0644 "$LOCK_FILE" -# Initialize allowlist.json (empty = no restrictions until admin populates) +# Initialize allowlist.json ALLOWLIST_FILE="${REGISTRY_DIR}/allowlist.json" +_ALLOWLIST_MODE="" if [ ! -f "$ALLOWLIST_FILE" ]; then - echo '{"version":1,"allowed":{}}' > "$ALLOWLIST_FILE" - chmod 0644 "$ALLOWLIST_FILE" - chown root:root "$ALLOWLIST_FILE" + # Check whether the registry already has projects that need allowlisting + _EXISTING_PROJECTS="" + if [ -f "$REGISTRY_FILE" ]; then + _EXISTING_PROJECTS=$(jq -r '.projects // {} | keys[]' "$REGISTRY_FILE" 2>/dev/null) || _EXISTING_PROJECTS="" + fi + + if [ -n "$_EXISTING_PROJECTS" ]; then + # Upgrade path: auto-populate allowlist with existing projects (unbound). + # This preserves current behavior — existing tunnels keep working. + # Operator can tighten pubkey bindings later. + _ALLOWED='{}' + _PROJECT_COUNT=0 + while IFS= read -r _proj; do + _ALLOWED=$(echo "$_ALLOWED" | jq --arg p "$_proj" '. + {($p): {"pubkey_fingerprint": ""}}') + _PROJECT_COUNT=$((_PROJECT_COUNT + 1)) + done <<< "$_EXISTING_PROJECTS" + echo "{\"version\":1,\"allowed\":${_ALLOWED}}" | jq '.' > "$ALLOWLIST_FILE" + chmod 0644 "$ALLOWLIST_FILE" + chown root:root "$ALLOWLIST_FILE" + _ALLOWLIST_MODE="upgraded:${_PROJECT_COUNT}" + log_info "Initialized allowlist with ${_PROJECT_COUNT} existing project(s): ${ALLOWLIST_FILE}" + else + # Fresh install: seed empty allowlist and warn the operator. + echo '{"version":1,"allowed":{}}' > "$ALLOWLIST_FILE" + chmod 0644 "$ALLOWLIST_FILE" + chown root:root "$ALLOWLIST_FILE" + _ALLOWLIST_MODE="fresh-empty" + log_warn "Allowlist seeded empty — no project can register until you add entries to ${ALLOWLIST_FILE}." + fi log_info "Initialized allowlist: ${ALLOWLIST_FILE}" fi @@ -440,6 +469,7 @@ echo "" echo "Configuration:" echo " Install directory: ${INSTALL_DIR}" echo " Registry: ${REGISTRY_FILE}" +echo " Allowlist: ${ALLOWLIST_FILE}" echo " Caddy admin API: http://127.0.0.1:2019" echo " Operator site blocks: ${EXTRA_DIR}/ (import ${EXTRA_CADDYFILE})" echo "" @@ -447,6 +477,23 @@ echo "Users:" echo " disinto-register - SSH forced command (runs ${INSTALL_DIR}/register.sh)" echo " disinto-tunnel - Reverse tunnel receiver (no shell)" echo "" +echo "Allowlist:" +case "${_ALLOWLIST_MODE:-}" in + upgraded:*) + echo " Allowlist was auto-populated from existing registry entries." + echo " Existing projects can register without further action." + ;; + fresh-empty) + echo " Allowlist is empty — registration is GATED until you add entries." + echo " Edit ${ALLOWLIST_FILE} as root:" + echo ' {"version":1,"allowed":{"myproject":{"pubkey_fingerprint":""}}}' + echo " See ${INSTALL_DIR}/../README.md for the full workflow." + ;; + *) + echo " Allowlist already existed (no changes made)." + ;; +esac +echo "" echo "Next steps:" echo " 1. Verify Caddy is running: systemctl status caddy" echo " 2. Test SSH access: ssh disinto-register@localhost 'list'"