Merge pull request 'fix: edge-control: append-only audit log for register/deregister operations (#1095)' (#1099) from fix/issue-1095 into main
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
This commit is contained in:
commit
47fb08524c
2 changed files with 90 additions and 7 deletions
|
|
@ -162,7 +162,43 @@ if [ ! -f "$ALLOWLIST_FILE" ]; then
|
||||||
fi
|
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" <<EOF
|
||||||
|
${LOG_FILE} {
|
||||||
|
daily
|
||||||
|
rotate 30
|
||||||
|
compress
|
||||||
|
delaycompress
|
||||||
|
missingok
|
||||||
|
notifempty
|
||||||
|
create 0640 root disinto-register
|
||||||
|
copytruncate
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
chmod 0644 "$LOGROTATE_CONF"
|
||||||
|
|
||||||
|
log_info "Audit log: ${LOG_FILE}"
|
||||||
|
log_info "Logrotate config: ${LOGROTATE_CONF}"
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Step 4: Install Caddy with Gandi DNS plugin
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
log_info "Installing Caddy ${CADDY_VERSION} with Gandi DNS plugin..."
|
log_info "Installing Caddy ${CADDY_VERSION} with Gandi DNS plugin..."
|
||||||
|
|
||||||
|
|
@ -293,7 +329,7 @@ systemctl restart caddy 2>/dev/null || {
|
||||||
log_info "Caddy configured with admin API on 127.0.0.1:2019"
|
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}..."
|
log_info "Installing control plane scripts to ${INSTALL_DIR}..."
|
||||||
|
|
||||||
|
|
@ -315,7 +351,7 @@ chmod 750 "${INSTALL_DIR}/lib"
|
||||||
log_info "Control plane scripts installed"
|
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..."
|
log_info "Setting up SSH authorized_keys..."
|
||||||
|
|
||||||
|
|
@ -357,7 +393,7 @@ source "${INSTALL_DIR}/lib/authorized_keys.sh"
|
||||||
rebuild_authorized_keys
|
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..."
|
log_info "Configuring forced command for disinto-register..."
|
||||||
|
|
||||||
|
|
@ -380,7 +416,7 @@ if [ -n "$ADMIN_PUBKEY" ]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Step 7: Final configuration
|
# Step 8: Final configuration
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
log_info "Configuring domain suffix: ${DOMAIN_SUFFIX}"
|
log_info "Configuring domain suffix: ${DOMAIN_SUFFIX}"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 path (root-owned, never mutated by this script)
|
||||||
ALLOWLIST_FILE="${ALLOWLIST_FILE:-/var/lib/disinto/allowlist.json}"
|
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)
|
# Captured error from check_allowlist (used for JSON response)
|
||||||
_ALLOWLIST_ERROR=""
|
_ALLOWLIST_ERROR=""
|
||||||
|
|
||||||
|
# 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
|
||||||
|
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
|
# Print usage
|
||||||
usage() {
|
usage() {
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
|
|
@ -164,6 +196,11 @@ do_register() {
|
||||||
# Reload Caddy
|
# Reload Caddy
|
||||||
reload_caddy
|
reload_caddy
|
||||||
|
|
||||||
|
# Audit log
|
||||||
|
local pubkey_fp
|
||||||
|
pubkey_fp=$(ssh-keygen -lf /dev/stdin <<<"$full_pubkey" 2>/dev/null | awk '{print $2}') || pubkey_fp="unknown"
|
||||||
|
audit_log "register" "$project" "$port" "$pubkey_fp"
|
||||||
|
|
||||||
# Build JSON response
|
# Build JSON response
|
||||||
local response="{\"port\":${port},\"fqdn\":\"${project}.${DOMAIN_SUFFIX}\""
|
local response="{\"port\":${port},\"fqdn\":\"${project}.${DOMAIN_SUFFIX}\""
|
||||||
if [ "$routing_mode" = "subdomain" ]; then
|
if [ "$routing_mode" = "subdomain" ]; then
|
||||||
|
|
@ -179,8 +216,8 @@ do_register() {
|
||||||
do_deregister() {
|
do_deregister() {
|
||||||
local project="$1"
|
local project="$1"
|
||||||
|
|
||||||
# Get current port before removing
|
# Get current port and pubkey before removing
|
||||||
local port
|
local port pubkey_fp
|
||||||
port=$(get_port "$project")
|
port=$(get_port "$project")
|
||||||
|
|
||||||
if [ -z "$port" ]; then
|
if [ -z "$port" ]; then
|
||||||
|
|
@ -188,6 +225,13 @@ do_deregister() {
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
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
|
# Remove from registry
|
||||||
free_port "$project" >/dev/null
|
free_port "$project" >/dev/null
|
||||||
|
|
||||||
|
|
@ -209,6 +253,9 @@ do_deregister() {
|
||||||
# Reload Caddy
|
# Reload Caddy
|
||||||
reload_caddy
|
reload_caddy
|
||||||
|
|
||||||
|
# Audit log
|
||||||
|
audit_log "deregister" "$project" "$port" "$pubkey_fp"
|
||||||
|
|
||||||
# Return JSON response
|
# Return JSON response
|
||||||
echo "{\"removed\":true,\"port\":${port},\"fqdn\":\"${project}.${DOMAIN_SUFFIX}\"}"
|
echo "{\"removed\":true,\"port\":${port},\"fqdn\":\"${project}.${DOMAIN_SUFFIX}\"}"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue