247 lines
5.4 KiB
Bash
247 lines
5.4 KiB
Bash
#!/usr/bin/env bash
|
|
|
|
# ============================================
|
|
# Config
|
|
# ============================================
|
|
|
|
MONITOR_DIR="$(ctx::root)/daemon"
|
|
WATCHLIST_FILE="${MONITOR_DIR}/watchlist.json"
|
|
EVENTS_LOG="${MONITOR_DIR}/events.log"
|
|
MONITOR_SERVICE="wgctl-monitor"
|
|
|
|
# ============================================
|
|
# Lifecycle
|
|
# ============================================
|
|
|
|
function monitor::on_load() {
|
|
if [[ ! -f "$WATCHLIST_FILE" ]]; then
|
|
echo '{}' > "$WATCHLIST_FILE"
|
|
fi
|
|
if [[ ! -f "$EVENTS_LOG" ]]; then
|
|
touch "$EVENTS_LOG"
|
|
fi
|
|
}
|
|
|
|
# ============================================
|
|
# Watchlist
|
|
# ============================================
|
|
|
|
function monitor::watch() {
|
|
local ip="$1"
|
|
local client="$2"
|
|
|
|
python3 -c "
|
|
import json
|
|
with open('${WATCHLIST_FILE}') as f:
|
|
wl = json.load(f)
|
|
wl['${ip}'] = '${client}'
|
|
with open('${WATCHLIST_FILE}', 'w') as f:
|
|
json.dump(wl, f, indent=2)
|
|
"
|
|
log::wg "Watching: ${client} (${ip})"
|
|
}
|
|
|
|
function monitor::unwatch() {
|
|
local ip="$1"
|
|
|
|
python3 -c "
|
|
import json
|
|
with open('${WATCHLIST_FILE}') as f:
|
|
wl = json.load(f)
|
|
wl.pop('${ip}', None)
|
|
with open('${WATCHLIST_FILE}', 'w') as f:
|
|
json.dump(wl, f, indent=2)
|
|
"
|
|
log::wg "Unwatched: ${ip}"
|
|
}
|
|
|
|
function monitor::is_watched() {
|
|
local ip="$1"
|
|
python3 -c "
|
|
import json
|
|
with open('${WATCHLIST_FILE}') as f:
|
|
wl = json.load(f)
|
|
exit(0 if '${ip}' in wl else 1)
|
|
"
|
|
}
|
|
|
|
function monitor::unwatch_client() {
|
|
local name="$1"
|
|
python3 -c "
|
|
import json
|
|
with open('${WATCHLIST_FILE}') as f:
|
|
wl = json.load(f)
|
|
wl = {k: v for k, v in wl.items() if v != '${name}'}
|
|
with open('${WATCHLIST_FILE}', 'w') as f:
|
|
json.dump(wl, f, indent=2)
|
|
"
|
|
log::wg "Unwatched client: ${name}"
|
|
}
|
|
|
|
# ============================================
|
|
# Events
|
|
# ============================================
|
|
|
|
function monitor::last_attempt() {
|
|
local client="$1"
|
|
python3 -c "
|
|
import json
|
|
|
|
events = []
|
|
try:
|
|
with open('${EVENTS_LOG}') as f:
|
|
for line in f:
|
|
try:
|
|
e = json.loads(line.strip())
|
|
if e.get('client') == '${client}':
|
|
events.append(e)
|
|
except:
|
|
pass
|
|
except:
|
|
pass
|
|
|
|
if events:
|
|
print(events[-1].get('timestamp', ''))
|
|
"
|
|
}
|
|
|
|
function monitor::last_endpoint() {
|
|
local client="$1"
|
|
python3 -c "
|
|
import json
|
|
|
|
events = []
|
|
try:
|
|
with open('${EVENTS_LOG}') as f:
|
|
for line in f:
|
|
try:
|
|
e = json.loads(line.strip())
|
|
if e.get('client') == '${client}' and e.get('endpoint'):
|
|
events.append(e)
|
|
except:
|
|
pass
|
|
except:
|
|
pass
|
|
|
|
if events:
|
|
print(events[-1].get('endpoint', ''))
|
|
"
|
|
}
|
|
|
|
function monitor::events_for() {
|
|
local ip="$1"
|
|
local limit="${2:-50}"
|
|
python3 -c "
|
|
import json
|
|
from datetime import datetime, timezone
|
|
|
|
events = []
|
|
try:
|
|
with open('${EVENTS_LOG}') as f:
|
|
for line in f:
|
|
try:
|
|
e = json.loads(line.strip())
|
|
if e.get('ip') == '${ip}':
|
|
events.append(e)
|
|
except:
|
|
pass
|
|
except:
|
|
pass
|
|
|
|
for e in events[-${limit}:]:
|
|
ts = e.get('timestamp', '')
|
|
try:
|
|
dt = datetime.fromisoformat(ts)
|
|
ts = dt.strftime('%Y-%m-%d %H:%M:%S')
|
|
except:
|
|
pass
|
|
endpoint = e.get('endpoint', '—')
|
|
client = e.get('client', '—')
|
|
event = e.get('event', '—')
|
|
print(f' {ts} {client:<20} {endpoint:<20} {event}')
|
|
"
|
|
}
|
|
|
|
# ============================================
|
|
# Endpoint Cache (for blocked clients)
|
|
# ============================================
|
|
|
|
ENDPOINT_CACHE="${WGCTL_DIR}/daemon/endpoint_cache.json"
|
|
|
|
function monitor::cache_endpoint() {
|
|
local client="$1"
|
|
local endpoint="$2"
|
|
[[ -z "$endpoint" || "$endpoint" == "(none)" ]] && return 0
|
|
|
|
python3 -c "
|
|
import json
|
|
cache = {}
|
|
try:
|
|
with open('${ENDPOINT_CACHE}') as f:
|
|
cache = json.load(f)
|
|
except:
|
|
pass
|
|
cache['${client}'] = '${endpoint}'
|
|
with open('${ENDPOINT_CACHE}', 'w') as f:
|
|
json.dump(cache, f, indent=2)
|
|
"
|
|
}
|
|
|
|
function monitor::get_cached_endpoint() {
|
|
local client="$1"
|
|
python3 -c "
|
|
import json
|
|
try:
|
|
with open('${ENDPOINT_CACHE}') as f:
|
|
cache = json.load(f)
|
|
print(cache.get('${client}', ''))
|
|
except:
|
|
print('')
|
|
"
|
|
}
|
|
|
|
function monitor::update_endpoint_cache() {
|
|
while IFS=$'\t' read -r key endpoint; do
|
|
[[ "$endpoint" == "(none)" ]] && continue
|
|
local ip
|
|
ip=$(echo "$endpoint" | cut -d':' -f1)
|
|
local client
|
|
client=$(keys::find_by_public "$key") || continue
|
|
monitor::cache_endpoint "$client" "$ip"
|
|
done < <(wg show "$(config::interface)" endpoints 2>/dev/null)
|
|
}
|
|
|
|
# ============================================
|
|
# Endpoint (from wg show, for active clients)
|
|
# ============================================
|
|
|
|
function monitor::endpoint_for_key() {
|
|
local public_key="$1"
|
|
wg show "$(config::interface)" endpoints 2>/dev/null \
|
|
| grep "^${public_key}" \
|
|
| awk '{print $2}' \
|
|
| cut -d':' -f1
|
|
}
|
|
|
|
# ============================================
|
|
# Service
|
|
# ============================================
|
|
|
|
function monitor::start() {
|
|
systemctl start "$MONITOR_SERVICE"
|
|
log::wg_start "Monitor daemon started"
|
|
}
|
|
|
|
function monitor::stop() {
|
|
systemctl stop "$MONITOR_SERVICE"
|
|
log::wg_stop "Monitor daemon stopped"
|
|
}
|
|
|
|
function monitor::restart() {
|
|
systemctl restart "$MONITOR_SERVICE"
|
|
log::wg_start "Monitor daemon restarted"
|
|
}
|
|
|
|
function monitor::is_running() {
|
|
systemctl is-active --quiet "$MONITOR_SERVICE"
|
|
}
|