feat(security): Add security stats and Gitea mirror commands

Security Stats:
- Add get_security_stats RPCD method for quick overview
- Track WAN drops, firewall rejects, CrowdSec bans
- Add secubox-stats CLI tool for quick stats check

Gitea Mirror Commands:
- Add mirror-sync to trigger mirror repository sync
- Add mirror-list to show all mirrored repos
- Add mirror-create to create new mirrors from GitHub URLs
- Add repo-list to list all repositories
- Requires API token: uci set gitea.main.api_token=<token>

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-01-25 15:15:50 +01:00
parent b7edc32695
commit 283f2567be
4 changed files with 326 additions and 3 deletions

View File

@ -22,6 +22,10 @@ define Package/luci-app-secubox-security-threats/conffiles
endef
define Package/luci-app-secubox-security-threats/install
# CLI tool
$(INSTALL_DIR) $(1)/usr/bin
$(INSTALL_BIN) ./root/usr/bin/secubox-stats $(1)/usr/bin/
# RPCD backend (MUST be 755 for ubus calls)
$(INSTALL_DIR) $(1)/usr/libexec/rpcd
$(INSTALL_BIN) ./root/usr/libexec/rpcd/luci.secubox-security-threats $(1)/usr/libexec/rpcd/

View File

@ -0,0 +1,5 @@
#!/bin/sh
# SecuBox Security Stats - Quick overview
# Copyright (C) 2026 CyberMind.fr
ubus call luci.secubox-security-threats get_security_stats 2>/dev/null | jsonfilter -e '@' 2>/dev/null || echo '{"error": "RPCD not available"}'

View File

@ -262,6 +262,72 @@ check_block_rules() {
config_foreach check_rule_match block_rule "$category" "$risks" "$score" "$ip"
}
# ==============================================================================
# SECURITY STATS (Quick Overview)
# ==============================================================================
# Get overall security statistics from all sources
get_security_stats() {
local wan_drops=0
local fw_rejects=0
local cs_bans=0
local cs_alerts_24h=0
local haproxy_conns=0
local invalid_conns=0
# WAN dropped packets (from kernel stats)
if [ -f /sys/class/net/br-wan/statistics/rx_dropped ]; then
wan_drops=$(cat /sys/class/net/br-wan/statistics/rx_dropped 2>/dev/null)
elif [ -f /sys/class/net/eth1/statistics/rx_dropped ]; then
wan_drops=$(cat /sys/class/net/eth1/statistics/rx_dropped 2>/dev/null)
fi
wan_drops=${wan_drops:-0}
# Firewall rejects from logs (last 24h)
fw_rejects=$(logread 2>/dev/null | grep -c "reject\|drop" || echo 0)
fw_rejects=$(echo "$fw_rejects" | tr -d '\n')
fw_rejects=${fw_rejects:-0}
# CrowdSec active bans
if [ -x "$CSCLI" ]; then
cs_bans=$($CSCLI decisions list -o json 2>/dev/null | grep -c '"id":' || echo 0)
cs_bans=$(echo "$cs_bans" | tr -d '\n')
cs_bans=${cs_bans:-0}
# CrowdSec alerts in last 24h
cs_alerts_24h=$($CSCLI alerts list -o json --since 24h 2>/dev/null | grep -c '"id":' || echo 0)
cs_alerts_24h=$(echo "$cs_alerts_24h" | tr -d '\n')
cs_alerts_24h=${cs_alerts_24h:-0}
fi
# Invalid connections (conntrack)
if [ -f /proc/net/nf_conntrack ]; then
invalid_conns=$(grep -c "INVALID\|UNREPLIED" /proc/net/nf_conntrack 2>/dev/null || echo 0)
fi
invalid_conns=$(echo "$invalid_conns" | tr -d '\n')
invalid_conns=${invalid_conns:-0}
# HAProxy connections (if running in LXC)
if lxc-info -n haproxy -s 2>/dev/null | grep -q "RUNNING"; then
haproxy_conns=$(lxc-attach -n haproxy -- sh -c 'echo "show stat" | socat stdio /var/run/haproxy/admin.sock 2>/dev/null | tail -n+2 | awk -F, "{sum+=\$8} END {print sum}"' 2>/dev/null || echo 0)
fi
haproxy_conns=$(echo "$haproxy_conns" | tr -d '\n')
haproxy_conns=${haproxy_conns:-0}
# Output JSON
cat << EOF
{
"wan_dropped": $wan_drops,
"firewall_rejects": $fw_rejects,
"crowdsec_bans": $cs_bans,
"crowdsec_alerts_24h": $cs_alerts_24h,
"invalid_connections": $invalid_conns,
"haproxy_connections": $haproxy_conns,
"timestamp": "$(date -Iseconds)"
}
EOF
}
# ==============================================================================
# STATISTICS
# ==============================================================================
@ -304,6 +370,8 @@ case "$1" in
list)
# List available methods
json_init
json_add_object "get_security_stats"
json_close_object
json_add_object "status"
json_close_object
json_add_object "get_active_threats"
@ -334,6 +402,10 @@ case "$1" in
call)
case "$2" in
get_security_stats)
get_security_stats
;;
status)
json_init
json_add_boolean "enabled" 1

View File

@ -91,6 +91,15 @@ Commands:
--password <pass>
--email <email>
mirror-sync <repo> Sync a mirrored repository
mirror-list List all mirrored repositories
mirror-create Create a new mirror from URL
--name <name>
--url <github-url>
--owner <user> (default: first admin user)
repo-list List all repositories
service-run Start service (used by init)
service-stop Stop service (used by init)
@ -718,6 +727,235 @@ cmd_admin_create_user() {
fi
}
# Get Gitea API token (from admin user or config)
get_api_token() {
local token
token="$(uci_get main.api_token)"
if [ -n "$token" ]; then
echo "$token"
return 0
fi
# Try to get token from container
if lxc_running; then
token=$(lxc-attach -n "$LXC_NAME" -- cat /data/api_token 2>/dev/null)
if [ -n "$token" ]; then
echo "$token"
return 0
fi
fi
return 1
}
# Get Gitea API URL
get_api_url() {
load_config
echo "http://127.0.0.1:${http_port}/api/v1"
}
# Make Gitea API call
gitea_api() {
local method="$1"
local endpoint="$2"
local data="$3"
local token
token=$(get_api_token) || {
log_error "No API token configured. Set with: uci set gitea.main.api_token=<token>"
log_error "Generate token in Gitea: Settings → Applications → Generate Token"
return 1
}
local api_url=$(get_api_url)
local url="${api_url}${endpoint}"
if [ "$method" = "GET" ]; then
wget -q -O- --header="Authorization: token $token" "$url" 2>/dev/null
elif [ "$method" = "POST" ]; then
if [ -n "$data" ]; then
wget -q -O- --header="Authorization: token $token" \
--header="Content-Type: application/json" \
--post-data="$data" "$url" 2>/dev/null
else
wget -q -O- --header="Authorization: token $token" \
--post-data="" "$url" 2>/dev/null
fi
fi
}
cmd_mirror_sync() {
load_config
local repo_name="$1"
if [ -z "$repo_name" ]; then
log_error "Usage: giteactl mirror-sync <owner/repo> or <repo>"
return 1
fi
if ! lxc_running; then
log_error "Gitea container is not running"
return 1
fi
# If no owner specified, try to find the repo
if ! echo "$repo_name" | grep -q "/"; then
# Search for repo in all users
local found_owner
found_owner=$(gitea_api GET "/repos/search?q=$repo_name" 2>/dev/null | \
jsonfilter -e '@.data[0].owner.login' 2>/dev/null)
if [ -n "$found_owner" ]; then
repo_name="${found_owner}/${repo_name}"
else
log_error "Repository not found: $repo_name"
log_error "Specify full path: owner/repo"
return 1
fi
fi
log_info "Syncing mirror: $repo_name"
# Trigger mirror sync via API
local result
result=$(gitea_api POST "/repos/${repo_name}/mirror-sync" 2>&1)
if [ $? -eq 0 ]; then
log_info "Mirror sync triggered for $repo_name"
log_info "Check progress in Gitea web UI"
else
log_error "Failed to sync mirror: $result"
# Try alternative: use gitea command directly in container
log_info "Trying direct sync via container..."
lxc-attach -n "$LXC_NAME" -- su-exec git /usr/local/bin/gitea admin repo-sync-releases \
--config /data/custom/conf/app.ini 2>/dev/null || true
return 1
fi
}
cmd_mirror_list() {
load_config
if ! lxc_running; then
log_error "Gitea container is not running"
return 1
fi
log_info "Fetching mirror repositories..."
local repos
repos=$(gitea_api GET "/repos/search?mirror=true&limit=50" 2>/dev/null)
if [ -z "$repos" ]; then
echo "No mirrored repositories found (or API token not set)"
return 1
fi
echo ""
echo "Mirrored Repositories:"
echo "======================"
echo "$repos" | jsonfilter -e '@.data[*]' 2>/dev/null | while read repo; do
local name=$(echo "$repo" | jsonfilter -e '@.full_name' 2>/dev/null)
local url=$(echo "$repo" | jsonfilter -e '@.original_url' 2>/dev/null)
local updated=$(echo "$repo" | jsonfilter -e '@.updated_at' 2>/dev/null)
echo " $name"
echo " Source: $url"
echo " Updated: $updated"
echo ""
done
}
cmd_mirror_create() {
load_config
local name=""
local url=""
local owner=""
# Parse arguments
while [ $# -gt 0 ]; do
case "$1" in
--name) name="$2"; shift 2 ;;
--url) url="$2"; shift 2 ;;
--owner) owner="$2"; shift 2 ;;
*) shift ;;
esac
done
if [ -z "$name" ] || [ -z "$url" ]; then
log_error "Usage: giteactl mirror-create --name <repo-name> --url <source-url> [--owner <user>]"
return 1
fi
if ! lxc_running; then
log_error "Gitea container is not running"
return 1
fi
# Get default owner if not specified
if [ -z "$owner" ]; then
owner=$(gitea_api GET "/user" 2>/dev/null | jsonfilter -e '@.login' 2>/dev/null)
if [ -z "$owner" ]; then
log_error "Could not determine owner. Specify with --owner"
return 1
fi
fi
log_info "Creating mirror repository: $owner/$name from $url"
local data=$(cat <<EOF
{
"clone_addr": "$url",
"repo_name": "$name",
"mirror": true,
"private": false,
"description": "Mirror of $url"
}
EOF
)
local result
result=$(gitea_api POST "/repos/migrate" "$data" 2>&1)
if echo "$result" | grep -q '"id":'; then
log_info "Mirror created successfully: $owner/$name"
log_info "First sync in progress..."
else
log_error "Failed to create mirror: $result"
return 1
fi
}
cmd_repo_list() {
load_config
if ! lxc_running; then
log_error "Gitea container is not running"
return 1
fi
local repos
repos=$(gitea_api GET "/repos/search?limit=100" 2>/dev/null)
if [ -z "$repos" ]; then
echo "No repositories found (or API token not set)"
return 1
fi
echo ""
echo "Repositories:"
echo "============="
echo "$repos" | jsonfilter -e '@.data[*].full_name' 2>/dev/null | while read name; do
local is_mirror=$(echo "$repos" | jsonfilter -e "@.data[?(@.full_name=='$name')].mirror" 2>/dev/null)
if [ "$is_mirror" = "true" ]; then
echo " [mirror] $name"
else
echo " $name"
fi
done
}
cmd_service_run() {
require_root
load_config
@ -751,7 +989,11 @@ case "${1:-}" in
*) echo "Usage: giteactl admin create-user --username <name> --password <pass> --email <email>"; exit 1 ;;
esac
;;
service-run) shift; cmd_service_run "$@" ;;
service-stop) shift; cmd_service_stop "$@" ;;
*) usage ;;
mirror-sync) shift; cmd_mirror_sync "$@" ;;
mirror-list) shift; cmd_mirror_list "$@" ;;
mirror-create) shift; cmd_mirror_create "$@" ;;
repo-list) shift; cmd_repo_list "$@" ;;
service-run) shift; cmd_service_run "$@" ;;
service-stop) shift; cmd_service_stop "$@" ;;
*) usage ;;
esac