diff --git a/tools/edge-control/install.sh b/tools/edge-control/install.sh index c7af075..636741e 100755 --- a/tools/edge-control/install.sh +++ b/tools/edge-control/install.sh @@ -162,7 +162,43 @@ if [ ! -f "$ALLOWLIST_FILE" ]; then fi # ============================================================================= -# Step 3: Install Caddy with Gandi DNS plugin +# Step 3: Create audit log directory and logrotate config +# ============================================================================= +log_info "Setting up audit log..." + +LOG_DIR="/var/log/disinto" +LOG_FILE="${LOG_DIR}/edge-register.log" + +mkdir -p "$LOG_DIR" +chown root:disinto-register "$LOG_DIR" +chmod 0750 "$LOG_DIR" + +# Touch the log file so it exists from day one +touch "$LOG_FILE" +chmod 0640 "$LOG_FILE" +chown root:disinto-register "$LOG_FILE" + +# Install logrotate config (daily rotation, 30 days retention) +LOGROTATE_CONF="/etc/logrotate.d/disinto-edge" +cat > "$LOGROTATE_CONF" </dev/null || { log_info "Caddy configured with admin API on 127.0.0.1:2019" # ============================================================================= -# Step 4: Install control plane scripts +# Step 5: Install control plane scripts # ============================================================================= log_info "Installing control plane scripts to ${INSTALL_DIR}..." @@ -315,7 +351,7 @@ chmod 750 "${INSTALL_DIR}/lib" log_info "Control plane scripts installed" # ============================================================================= -# Step 5: Set up SSH authorized_keys +# Step 6: Set up SSH authorized_keys # ============================================================================= log_info "Setting up SSH authorized_keys..." @@ -357,7 +393,7 @@ source "${INSTALL_DIR}/lib/authorized_keys.sh" rebuild_authorized_keys # ============================================================================= -# Step 6: Configure forced command for disinto-register +# Step 7: Configure forced command for disinto-register # ============================================================================= log_info "Configuring forced command for disinto-register..." @@ -380,7 +416,7 @@ if [ -n "$ADMIN_PUBKEY" ]; then fi # ============================================================================= -# Step 7: Final configuration +# Step 8: Final configuration # ============================================================================= log_info "Configuring domain suffix: ${DOMAIN_SUFFIX}" diff --git a/tools/edge-control/register.sh b/tools/edge-control/register.sh index bef83e9..b104ebd 100755 --- a/tools/edge-control/register.sh +++ b/tools/edge-control/register.sh @@ -31,9 +31,41 @@ RESERVED_NAMES=(www api admin root mail chat forge ci edge caddy disinto registe # Allowlist path (root-owned, never mutated by this script) ALLOWLIST_FILE="${ALLOWLIST_FILE:-/var/lib/disinto/allowlist.json}" +# Audit log path +AUDIT_LOG="${AUDIT_LOG:-/var/log/disinto/edge-register.log}" + # Captured error from check_allowlist (used for JSON response) _ALLOWLIST_ERROR="" +# Append one line to the audit log. +# Usage: audit_log +# Fails silently — write errors are warned but never abort. +audit_log() { + local action="$1" project="$2" port="$3" pubkey_fp="$4" + local timestamp caller + 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}" + + # Ensure log directory exists + local log_dir + log_dir=$(dirname "$AUDIT_LOG") + if [ ! -d "$log_dir" ]; then + mkdir -p "$log_dir" 2>/dev/null || { + echo "[WARN] audit log: cannot create ${log_dir}" >&2 + return 0 + } + chown root:disinto-register "$log_dir" 2>/dev/null || true + chmod 0750 "$log_dir" + fi + + # Append — write failure is non-fatal + if ! printf '%s\n' "$line" >> "$AUDIT_LOG" 2>/dev/null; then + echo "[WARN] audit log: failed to write to ${AUDIT_LOG}" >&2 + fi +} + # Print usage usage() { cat </dev/null | awk '{print $2}') || pubkey_fp="unknown" + audit_log "register" "$project" "$port" "$pubkey_fp" + # Build JSON response local response="{\"port\":${port},\"fqdn\":\"${project}.${DOMAIN_SUFFIX}\"" if [ "$routing_mode" = "subdomain" ]; then @@ -179,8 +216,8 @@ do_register() { do_deregister() { local project="$1" - # Get current port before removing - local port + # Get current port and pubkey before removing + local port pubkey_fp port=$(get_port "$project") if [ -z "$port" ]; then @@ -188,6 +225,13 @@ do_deregister() { exit 1 fi + pubkey_fp=$(get_project_info "$project" | jq -r '.pubkey // empty' 2>/dev/null) || pubkey_fp="" + if [ -n "$pubkey_fp" ]; then + pubkey_fp=$(ssh-keygen -lf /dev/stdin <<<"$pubkey_fp" 2>/dev/null | awk '{print $2}') || pubkey_fp="unknown" + else + pubkey_fp="unknown" + fi + # Remove from registry free_port "$project" >/dev/null @@ -209,6 +253,9 @@ do_deregister() { # Reload Caddy reload_caddy + # Audit log + audit_log "deregister" "$project" "$port" "$pubkey_fp" + # Return JSON response echo "{\"removed\":true,\"port\":${port},\"fqdn\":\"${project}.${DOMAIN_SUFFIX}\"}" }