fix: edge-control: per-caller attribution for register/deregister (#1094)
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
ci/woodpecker/pr/edge-subpath Pipeline was successful

- register.sh parses --as <tag> from forced-command argv, stores as
  registered_by in registry entries (defaults to "unknown")
- allocate_port() accepts optional registered_by parameter
- list output includes registered_by for each tunnel
- deregister response includes deregistered_by
- install.sh accepts --admin-tag <name> (defaults to "admin") and wires
  it into the forced-command entry as --as <tag>

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude 2026-04-20 19:29:15 +00:00
parent 47fb08524c
commit 1835750b0d
3 changed files with 41 additions and 10 deletions

View file

@ -5,6 +5,10 @@
# This script runs as a forced command for the disinto-register SSH user.
# It parses SSH_ORIGINAL_COMMAND and dispatches to register|deregister|list.
#
# Per-caller attribution: each admin key's forced-command passes --as <tag>,
# which is stored as registered_by in the registry. Missing --as defaults to
# "unknown" for backwards compatibility.
#
# Usage (via SSH):
# ssh disinto-register@edge "register <project> <pubkey>"
# ssh disinto-register@edge "deregister <project>"
@ -37,16 +41,31 @@ AUDIT_LOG="${AUDIT_LOG:-/var/log/disinto/edge-register.log}"
# Captured error from check_allowlist (used for JSON response)
_ALLOWLIST_ERROR=""
# Caller tag (set via --as <tag> in forced command)
CALLER="unknown"
# Parse script arguments (from forced command, not SSH_ORIGINAL_COMMAND)
while [[ $# -gt 0 ]]; do
case $1 in
--as)
CALLER="$2"
shift 2
;;
*)
shift
;;
esac
done
# Append one line to the audit log.
# Usage: audit_log <action> <project> <port> <pubkey_fp>
# Fails silently — write errors are warned but never abort.
audit_log() {
local action="$1" project="$2" port="$3" pubkey_fp="$4"
local timestamp caller
local timestamp
timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
caller="${SSH_USERNAME:-${SUDO_USER:-${USER:-unknown}}}"
local line="${timestamp} ${action} project=${project} port=${port} pubkey_fp=${pubkey_fp} caller=${caller}"
local line="${timestamp} ${action} project=${project} port=${port} pubkey_fp=${pubkey_fp} caller=${CALLER}"
# Ensure log directory exists
local log_dir
@ -176,7 +195,7 @@ do_register() {
# Allocate port (idempotent - returns existing if already registered)
local port
port=$(allocate_port "$project" "$full_pubkey" "${project}.${DOMAIN_SUFFIX}")
port=$(allocate_port "$project" "$full_pubkey" "${project}.${DOMAIN_SUFFIX}" "$CALLER")
# Add Caddy route for main project domain
add_route "$project" "$port"
@ -216,6 +235,9 @@ do_register() {
do_deregister() {
local project="$1"
# Record who is deregistering before removal
local deregistered_by="$CALLER"
# Get current port and pubkey before removing
local port pubkey_fp
port=$(get_port "$project")
@ -257,7 +279,7 @@ do_deregister() {
audit_log "deregister" "$project" "$port" "$pubkey_fp"
# Return JSON response
echo "{\"removed\":true,\"port\":${port},\"fqdn\":\"${project}.${DOMAIN_SUFFIX}\"}"
echo "{\"removed\":true,\"port\":${port},\"fqdn\":\"${project}.${DOMAIN_SUFFIX}\",\"deregistered_by\":\"${deregistered_by}\"}"
}
# List all registered tunnels