fix: edge-control: install.sh seeds empty allowlist — every register breaks until admin populates it, with no install-time warning (#1110) #1113

Merged
disinto-admin merged 1 commit from fix/issue-1110 into main 2026-04-21 13:21:23 +00:00
2 changed files with 65 additions and 13 deletions

View file

@ -183,11 +183,16 @@ Shows all registered tunnels with their ports and FQDNs.
## Allowlist ## 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 ```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 <keyfile>`. - **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). - **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"}`. - **Not listed in `allowed`**: registration is refused with `{"error":"name not approved"}`.
### Workflow ### Workflow

View file

@ -8,9 +8,11 @@
# What it does: # What it does:
# 1. Creates users: disinto-register, disinto-tunnel # 1. Creates users: disinto-register, disinto-tunnel
# 2. Creates /var/lib/disinto/ with registry.json, registry.lock, allowlist.json # 2. Creates /var/lib/disinto/ with registry.json, registry.lock, allowlist.json
# 3. Installs Caddy with Gandi DNS plugin # 3. On upgrade: auto-populates allowlist.json from existing registry entries
# 4. Sets up SSH authorized_keys for both users # 4. On fresh install: seeds empty allowlist with warning (registration gated)
# 5. Installs control plane scripts to /opt/disinto-edge/ # 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: # Requirements:
# - Fresh Debian 12 (Bookworm) # - Fresh Debian 12 (Bookworm)
@ -158,12 +160,39 @@ LOCK_FILE="${REGISTRY_DIR}/registry.lock"
touch "$LOCK_FILE" touch "$LOCK_FILE"
chmod 0644 "$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_FILE="${REGISTRY_DIR}/allowlist.json"
_ALLOWLIST_MODE=""
if [ ! -f "$ALLOWLIST_FILE" ]; then if [ ! -f "$ALLOWLIST_FILE" ]; then
# 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" echo '{"version":1,"allowed":{}}' > "$ALLOWLIST_FILE"
chmod 0644 "$ALLOWLIST_FILE" chmod 0644 "$ALLOWLIST_FILE"
chown root:root "$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}" log_info "Initialized allowlist: ${ALLOWLIST_FILE}"
fi fi
@ -440,6 +469,7 @@ echo ""
echo "Configuration:" echo "Configuration:"
echo " Install directory: ${INSTALL_DIR}" echo " Install directory: ${INSTALL_DIR}"
echo " Registry: ${REGISTRY_FILE}" echo " Registry: ${REGISTRY_FILE}"
echo " Allowlist: ${ALLOWLIST_FILE}"
echo " Caddy admin API: http://127.0.0.1:2019" echo " Caddy admin API: http://127.0.0.1:2019"
echo " Operator site blocks: ${EXTRA_DIR}/ (import ${EXTRA_CADDYFILE})" echo " Operator site blocks: ${EXTRA_DIR}/ (import ${EXTRA_CADDYFILE})"
echo "" echo ""
@ -447,6 +477,23 @@ echo "Users:"
echo " disinto-register - SSH forced command (runs ${INSTALL_DIR}/register.sh)" echo " disinto-register - SSH forced command (runs ${INSTALL_DIR}/register.sh)"
echo " disinto-tunnel - Reverse tunnel receiver (no shell)" echo " disinto-tunnel - Reverse tunnel receiver (no shell)"
echo "" 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 "Next steps:"
echo " 1. Verify Caddy is running: systemctl status caddy" echo " 1. Verify Caddy is running: systemctl status caddy"
echo " 2. Test SSH access: ssh disinto-register@localhost 'list'" echo " 2. Test SSH access: ssh disinto-register@localhost 'list'"