Files
linux-scripts/freeradius-exporter.sh
T
chiefgeek a1a17e81a1 Sync all scripts from website downloads — 352 scripts total
Includes updated JS challenge scripts with Claude-User whitelist,
same-site referer bypass, Blackbox-Exporter allowed bot, and all
new exporters, cheat sheets, and automation scripts.
2026-05-25 03:31:08 +02:00

396 lines
15 KiB
Bash

#!/usr/bin/env bash
#########################################################################################
#### freeradius-exporter.sh — Prometheus metrics exporter for FreeRADIUS ####
#### Exports authentication, accounting, and proxy statistics from the ####
#### FreeRADIUS status server as Prometheus metrics ####
#### Requires: bash 4+, radclient ####
#### ####
#### Author: Phil Connor ####
#### Contact: contact@mylinux.work ####
#### License: MIT ####
#### Version 1.00 ####
#### ####
#### Usage: ####
#### ./freeradius-exporter.sh --http --port 9620 ####
#### ####
#### See --help for all options. ####
#########################################################################################
set -uo pipefail
# ============================================================================
# CONFIGURATION VARIABLES
# ============================================================================
RADIUS_HOST="${RADIUS_HOST:-localhost}"
RADIUS_STATUS_PORT="${RADIUS_STATUS_PORT:-18121}"
RADIUS_SECRET="${RADIUS_SECRET:-adminsecret}"
TEXTFILE_DIR="/var/lib/node_exporter"
OUTPUT_FILE=""
HTTP_MODE=false
HTTP_PORT=9620
EXPORTER_VERSION="1.00"
# ============================================================================
# HELPER FUNCTIONS
# ============================================================================
show_usage() {
cat <<EOF
Usage: $0 [OPTIONS]
Export FreeRADIUS statistics as Prometheus metrics via the status server.
MODES:
--textfile Write to node_exporter textfile collector
--http Run HTTP server on port $HTTP_PORT
OPTIONS:
-p, --port HTTP port (default: 9620)
-o, --output Output file path
-H, --host RADIUS host (default: localhost)
-s, --secret RADIUS status secret (default: adminsecret)
--status-port RADIUS status port (default: 18121)
ENVIRONMENT VARIABLES:
RADIUS_HOST RADIUS server host (default: localhost)
RADIUS_STATUS_PORT Status server port (default: 18121)
RADIUS_SECRET Status server secret (default: adminsecret)
EXAMPLES:
$0 --textfile # Write to textfile collector
$0 --http --port 9620 # Run HTTP server
$0 -o /tmp/freeradius.prom # Write to custom file
RADIUS_SECRET=mysecret $0 --http # Custom secret via env
NOTE:
FreeRADIUS must have the status server enabled. See:
/etc/freeradius/3.0/sites-enabled/status (Debian/Ubuntu)
/etc/raddb/sites-enabled/status (RHEL/CentOS)
EOF
exit 0
}
parse_args() {
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help) show_usage ;;
--textfile) OUTPUT_FILE="$TEXTFILE_DIR/freeradius.prom"; shift ;;
--http) HTTP_MODE=true; shift ;;
-p|--port) HTTP_PORT="$2"; shift 2 ;;
-o|--output) OUTPUT_FILE="$2"; shift 2 ;;
-H|--host) RADIUS_HOST="$2"; shift 2 ;;
-s|--secret) RADIUS_SECRET="$2"; shift 2 ;;
--status-port) RADIUS_STATUS_PORT="$2"; shift 2 ;;
*) echo "Unknown option: $1" >&2; exit 1 ;;
esac
done
}
# Query the FreeRADIUS status server via radclient
# Returns: raw attribute-value pair output, or empty on failure
query_status_server() {
echo "Message-Authenticator = 0x00, FreeRADIUS-Statistics-Type = All" \
| radclient "${RADIUS_HOST}:${RADIUS_STATUS_PORT}" status "${RADIUS_SECRET}" 2>/dev/null
}
# Extract a numeric value from radclient output
# Args: $1 - attribute name, $2 - radclient output
# Returns: numeric value or 0 if not found
extract_value() {
local attr="$1"
local data="$2"
local val
val=$(echo "$data" | grep -F "$attr" | awk -F'= ' '{print $2}' | tr -d '[:space:]')
echo "${val:-0}"
}
# ============================================================================
# METRIC GENERATION
# ============================================================================
# Generate all Prometheus metrics
# Returns: Prometheus text format metrics on stdout
generate_metrics() {
local script_start
script_start=$(date +%s)
# Check radclient is available
if ! command -v radclient >/dev/null 2>&1; then
echo "ERROR: radclient command not found" >&2
cat <<EOF
# HELP freeradius_up FreeRADIUS status server reachable
# TYPE freeradius_up gauge
freeradius_up 0
EOF
return
fi
# Query the status server
local raw_output
raw_output=$(query_status_server)
if [[ -z "$raw_output" ]]; then
cat <<EOF
# HELP freeradius_up FreeRADIUS status server reachable
# TYPE freeradius_up gauge
freeradius_up 0
EOF
return
fi
cat <<EOF
# HELP freeradius_up FreeRADIUS status server reachable
# TYPE freeradius_up gauge
freeradius_up 1
EOF
echo ""
# ========================================================================
# ACCESS / AUTHENTICATION METRICS
# ========================================================================
local access_requests access_accepts access_rejects access_challenges
access_requests=$(extract_value "FreeRADIUS-Total-Access-Requests" "$raw_output")
access_accepts=$(extract_value "FreeRADIUS-Total-Access-Accepts" "$raw_output")
access_rejects=$(extract_value "FreeRADIUS-Total-Access-Rejects" "$raw_output")
access_challenges=$(extract_value "FreeRADIUS-Total-Access-Challenges" "$raw_output")
cat <<EOF
# HELP freeradius_access_requests_total Total Access-Request packets received
# TYPE freeradius_access_requests_total counter
freeradius_access_requests_total $access_requests
# HELP freeradius_access_accepts_total Total Access-Accept packets sent
# TYPE freeradius_access_accepts_total counter
freeradius_access_accepts_total $access_accepts
# HELP freeradius_access_rejects_total Total Access-Reject packets sent
# TYPE freeradius_access_rejects_total counter
freeradius_access_rejects_total $access_rejects
# HELP freeradius_access_challenges_total Total Access-Challenge packets sent
# TYPE freeradius_access_challenges_total counter
freeradius_access_challenges_total $access_challenges
EOF
echo ""
local auth_responses auth_duplicate auth_malformed auth_invalid auth_dropped auth_unknown
auth_responses=$(extract_value "FreeRADIUS-Total-Auth-Responses" "$raw_output")
auth_duplicate=$(extract_value "FreeRADIUS-Total-Auth-Duplicate-Requests" "$raw_output")
auth_malformed=$(extract_value "FreeRADIUS-Total-Auth-Malformed-Requests" "$raw_output")
auth_invalid=$(extract_value "FreeRADIUS-Total-Auth-Invalid-Requests" "$raw_output")
auth_dropped=$(extract_value "FreeRADIUS-Total-Auth-Dropped-Requests" "$raw_output")
auth_unknown=$(extract_value "FreeRADIUS-Total-Auth-Unknown-Types" "$raw_output")
cat <<EOF
# HELP freeradius_auth_responses_total Total authentication responses sent
# TYPE freeradius_auth_responses_total counter
freeradius_auth_responses_total $auth_responses
# HELP freeradius_auth_duplicate_requests_total Total duplicate authentication requests
# TYPE freeradius_auth_duplicate_requests_total counter
freeradius_auth_duplicate_requests_total $auth_duplicate
# HELP freeradius_auth_malformed_requests_total Total malformed authentication requests
# TYPE freeradius_auth_malformed_requests_total counter
freeradius_auth_malformed_requests_total $auth_malformed
# HELP freeradius_auth_invalid_requests_total Total invalid authentication requests
# TYPE freeradius_auth_invalid_requests_total counter
freeradius_auth_invalid_requests_total $auth_invalid
# HELP freeradius_auth_dropped_requests_total Total dropped authentication requests
# TYPE freeradius_auth_dropped_requests_total counter
freeradius_auth_dropped_requests_total $auth_dropped
# HELP freeradius_auth_unknown_types_total Total unknown authentication packet types
# TYPE freeradius_auth_unknown_types_total counter
freeradius_auth_unknown_types_total $auth_unknown
EOF
echo ""
# ========================================================================
# ACCOUNTING METRICS
# ========================================================================
local acct_requests acct_responses acct_duplicate acct_malformed acct_invalid acct_dropped
acct_requests=$(extract_value "FreeRADIUS-Total-Accounting-Requests" "$raw_output")
acct_responses=$(extract_value "FreeRADIUS-Total-Accounting-Responses" "$raw_output")
acct_duplicate=$(extract_value "FreeRADIUS-Total-Acct-Duplicate-Requests" "$raw_output")
acct_malformed=$(extract_value "FreeRADIUS-Total-Acct-Malformed-Requests" "$raw_output")
acct_invalid=$(extract_value "FreeRADIUS-Total-Acct-Invalid-Requests" "$raw_output")
acct_dropped=$(extract_value "FreeRADIUS-Total-Acct-Dropped-Requests" "$raw_output")
cat <<EOF
# HELP freeradius_accounting_requests_total Total Accounting-Request packets received
# TYPE freeradius_accounting_requests_total counter
freeradius_accounting_requests_total $acct_requests
# HELP freeradius_accounting_responses_total Total Accounting-Response packets sent
# TYPE freeradius_accounting_responses_total counter
freeradius_accounting_responses_total $acct_responses
# HELP freeradius_accounting_duplicate_requests_total Total duplicate accounting requests
# TYPE freeradius_accounting_duplicate_requests_total counter
freeradius_accounting_duplicate_requests_total $acct_duplicate
# HELP freeradius_accounting_malformed_requests_total Total malformed accounting requests
# TYPE freeradius_accounting_malformed_requests_total counter
freeradius_accounting_malformed_requests_total $acct_malformed
# HELP freeradius_accounting_invalid_requests_total Total invalid accounting requests
# TYPE freeradius_accounting_invalid_requests_total counter
freeradius_accounting_invalid_requests_total $acct_invalid
# HELP freeradius_accounting_dropped_requests_total Total dropped accounting requests
# TYPE freeradius_accounting_dropped_requests_total counter
freeradius_accounting_dropped_requests_total $acct_dropped
EOF
echo ""
# ========================================================================
# PROXY METRICS
# ========================================================================
local proxy_access_requests proxy_access_accepts proxy_access_rejects
local proxy_acct_requests proxy_acct_responses
proxy_access_requests=$(extract_value "FreeRADIUS-Total-Proxy-Access-Requests" "$raw_output")
proxy_access_accepts=$(extract_value "FreeRADIUS-Total-Proxy-Access-Accepts" "$raw_output")
proxy_access_rejects=$(extract_value "FreeRADIUS-Total-Proxy-Access-Rejects" "$raw_output")
proxy_acct_requests=$(extract_value "FreeRADIUS-Total-Proxy-Accounting-Requests" "$raw_output")
proxy_acct_responses=$(extract_value "FreeRADIUS-Total-Proxy-Accounting-Responses" "$raw_output")
cat <<EOF
# HELP freeradius_proxy_access_requests_total Total proxied Access-Request packets
# TYPE freeradius_proxy_access_requests_total counter
freeradius_proxy_access_requests_total $proxy_access_requests
# HELP freeradius_proxy_access_accepts_total Total proxied Access-Accept packets
# TYPE freeradius_proxy_access_accepts_total counter
freeradius_proxy_access_accepts_total $proxy_access_accepts
# HELP freeradius_proxy_access_rejects_total Total proxied Access-Reject packets
# TYPE freeradius_proxy_access_rejects_total counter
freeradius_proxy_access_rejects_total $proxy_access_rejects
# HELP freeradius_proxy_accounting_requests_total Total proxied Accounting-Request packets
# TYPE freeradius_proxy_accounting_requests_total counter
freeradius_proxy_accounting_requests_total $proxy_acct_requests
# HELP freeradius_proxy_accounting_responses_total Total proxied Accounting-Response packets
# TYPE freeradius_proxy_accounting_responses_total counter
freeradius_proxy_accounting_responses_total $proxy_acct_responses
EOF
echo ""
# ========================================================================
# EXPORTER RUNTIME
# ========================================================================
local script_end script_duration
script_end=$(date +%s)
script_duration=$((script_end - script_start))
cat <<EOF
# HELP freeradius_exporter_duration_seconds Time to generate all metrics
# TYPE freeradius_exporter_duration_seconds gauge
freeradius_exporter_duration_seconds $script_duration
# HELP freeradius_exporter_last_run_timestamp Unix timestamp of last successful run
# TYPE freeradius_exporter_last_run_timestamp gauge
freeradius_exporter_last_run_timestamp $script_end
EOF
echo ""
}
# ============================================================================
# HTTP SERVER MODE
# ============================================================================
# Run simple HTTP server using netcat
# Serves metrics on /metrics endpoint
run_http_server() {
echo "Starting FreeRADIUS exporter on port $HTTP_PORT..." >&2
if ! command -v nc >/dev/null 2>&1; then
echo "ERROR: netcat (nc) required for HTTP mode" >&2
exit 1
fi
while true; do
{
read -r request
if [[ "$request" =~ ^GET\ /metrics ]]; then
echo -e "HTTP/1.1 200 OK\r\nContent-Type: text/plain; version=0.0.4\r\n\r"
generate_metrics
else
echo -e "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r"
cat <<EOF
<!DOCTYPE html>
<html>
<head><title>FreeRADIUS Exporter v${EXPORTER_VERSION}</title></head>
<body>
<h1>FreeRADIUS Prometheus Exporter v${EXPORTER_VERSION}</h1>
<p><a href="/metrics">Metrics</a></p>
<p>Authentication, accounting, and proxy statistics from the FreeRADIUS status server.</p>
</body>
</html>
EOF
fi
} | nc -l -p "$HTTP_PORT" -q 1 2>/dev/null
done
}
# ============================================================================
# MAIN EXECUTION
# ============================================================================
main() {
parse_args "$@"
if [[ "$HTTP_MODE" = true ]]; then
run_http_server
elif [[ -n "$OUTPUT_FILE" ]]; then
local output_dir
output_dir="$(dirname "$OUTPUT_FILE")"
mkdir -p "$output_dir"
local temp_file
temp_file=$(mktemp "${output_dir}/.freeradius_metrics.XXXXXX")
if ! generate_metrics > "$temp_file" 2>/dev/null; then
rm -f "$temp_file"
echo "ERROR: Failed to generate metrics" >&2
exit 1
fi
local file_lines
file_lines=$(wc -l < "$temp_file" 2>/dev/null || echo 0)
if [[ "$file_lines" -lt 5 ]]; then
rm -f "$temp_file"
echo "ERROR: Metrics file too small ($file_lines lines), keeping previous" >&2
exit 1
fi
chmod 644 "$temp_file"
mv -f "$temp_file" "$OUTPUT_FILE"
echo "Metrics written to $OUTPUT_FILE ($file_lines lines)" >&2
else
generate_metrics
fi
}
main "$@"