fix(config-advisor): BusyBox ash compatibility fixes
- Replace bash arrays with POSIX loops in scoring.sh - Replace bc with shell arithmetic (bc not available on OpenWrt) - Wrap RPCD handlers in functions for local keyword compatibility - Fix process substitution < <() to use pipe instead Tested on router: CLI and RPCD working, score calculation correct. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
0f4649c1e0
commit
714313633b
@ -9,27 +9,18 @@
|
|||||||
[ -f /usr/lib/config-advisor/scoring.sh ] && . /usr/lib/config-advisor/scoring.sh
|
[ -f /usr/lib/config-advisor/scoring.sh ] && . /usr/lib/config-advisor/scoring.sh
|
||||||
[ -f /usr/lib/config-advisor/remediate.sh ] && . /usr/lib/config-advisor/remediate.sh
|
[ -f /usr/lib/config-advisor/remediate.sh ] && . /usr/lib/config-advisor/remediate.sh
|
||||||
|
|
||||||
case "$1" in
|
handle_status() {
|
||||||
list)
|
|
||||||
echo '{"status":{},"results":{},"score":{},"compliance":{},"check":{},"pending":{},"history":{"count":30},"suggest":{"check_id":"string"},"remediate":{"check_id":"string","dry_run":false},"remediate_safe":{"dry_run":false},"set_config":{"key":"string","value":"string"}}'
|
|
||||||
;;
|
|
||||||
call)
|
|
||||||
case "$2" in
|
|
||||||
status)
|
|
||||||
# Get advisor status
|
|
||||||
json_init
|
json_init
|
||||||
json_add_string "version" "$(config-advisorctl version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' || echo '0.1.0')"
|
json_add_string "version" "$(config-advisorctl version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' || echo '0.1.0')"
|
||||||
json_add_boolean "enabled" "$(uci -q get config-advisor.main.enabled || echo 0)"
|
json_add_boolean "enabled" "$(uci -q get config-advisor.main.enabled || echo 0)"
|
||||||
json_add_string "framework" "$(uci -q get config-advisor.compliance.framework || echo 'anssi_cspn')"
|
json_add_string "framework" "$(uci -q get config-advisor.compliance.framework || echo 'anssi_cspn')"
|
||||||
|
|
||||||
# Last check timestamp
|
|
||||||
local last_check=0
|
local last_check=0
|
||||||
if [ -f /var/lib/config-advisor/results.json ]; then
|
if [ -f /var/lib/config-advisor/results.json ]; then
|
||||||
last_check=$(stat -c %Y /var/lib/config-advisor/results.json 2>/dev/null || echo 0)
|
last_check=$(stat -c %Y /var/lib/config-advisor/results.json 2>/dev/null || echo 0)
|
||||||
fi
|
fi
|
||||||
json_add_int "last_check" "$last_check"
|
json_add_int "last_check" "$last_check"
|
||||||
|
|
||||||
# Score info
|
|
||||||
local score grade risk_level
|
local score grade risk_level
|
||||||
if [ -f /var/lib/config-advisor/score.json ]; then
|
if [ -f /var/lib/config-advisor/score.json ]; then
|
||||||
score=$(jsonfilter -i /var/lib/config-advisor/score.json -e '@.score' 2>/dev/null || echo 0)
|
score=$(jsonfilter -i /var/lib/config-advisor/score.json -e '@.score' 2>/dev/null || echo 0)
|
||||||
@ -44,51 +35,45 @@ case "$1" in
|
|||||||
json_add_string "grade" "$grade"
|
json_add_string "grade" "$grade"
|
||||||
json_add_string "risk_level" "$risk_level"
|
json_add_string "risk_level" "$risk_level"
|
||||||
|
|
||||||
# Compliance rate
|
|
||||||
local compliance_rate=0
|
local compliance_rate=0
|
||||||
if [ -f /var/lib/config-advisor/compliance.json ]; then
|
if [ -f /var/lib/config-advisor/compliance.json ]; then
|
||||||
compliance_rate=$(jsonfilter -i /var/lib/config-advisor/compliance.json -e '@.compliance_rate' 2>/dev/null || echo 0)
|
compliance_rate=$(jsonfilter -i /var/lib/config-advisor/compliance.json -e '@.compliance_rate' 2>/dev/null || echo 0)
|
||||||
fi
|
fi
|
||||||
json_add_int "compliance_rate" "${compliance_rate%.*}"
|
json_add_int "compliance_rate" "${compliance_rate%.*}"
|
||||||
|
|
||||||
# LocalAI status
|
|
||||||
json_add_object "localai"
|
json_add_object "localai"
|
||||||
json_add_boolean "enabled" "$(uci -q get config-advisor.localai.enabled || echo 0)"
|
json_add_boolean "enabled" "$(uci -q get config-advisor.localai.enabled || echo 0)"
|
||||||
json_add_string "url" "$(uci -q get config-advisor.localai.url || echo 'http://127.0.0.1:8091')"
|
json_add_string "url" "$(uci -q get config-advisor.localai.url || echo 'http://127.0.0.1:8091')"
|
||||||
json_close_object
|
json_close_object
|
||||||
|
|
||||||
json_dump
|
json_dump
|
||||||
;;
|
}
|
||||||
|
|
||||||
results)
|
handle_results() {
|
||||||
# Get check results
|
|
||||||
if [ -f /var/lib/config-advisor/results.json ]; then
|
if [ -f /var/lib/config-advisor/results.json ]; then
|
||||||
echo "{\"results\":$(cat /var/lib/config-advisor/results.json)}"
|
echo "{\"results\":$(cat /var/lib/config-advisor/results.json)}"
|
||||||
else
|
else
|
||||||
echo '{"results":[]}'
|
echo '{"results":[]}'
|
||||||
fi
|
fi
|
||||||
;;
|
}
|
||||||
|
|
||||||
score)
|
handle_score() {
|
||||||
# Get score details
|
|
||||||
if [ -f /var/lib/config-advisor/score.json ]; then
|
if [ -f /var/lib/config-advisor/score.json ]; then
|
||||||
cat /var/lib/config-advisor/score.json
|
cat /var/lib/config-advisor/score.json
|
||||||
else
|
else
|
||||||
echo '{"error":"No score available"}'
|
echo '{"error":"No score available"}'
|
||||||
fi
|
fi
|
||||||
;;
|
}
|
||||||
|
|
||||||
compliance)
|
handle_compliance() {
|
||||||
# Get compliance report
|
|
||||||
if [ -f /var/lib/config-advisor/compliance.json ]; then
|
if [ -f /var/lib/config-advisor/compliance.json ]; then
|
||||||
cat /var/lib/config-advisor/compliance.json
|
cat /var/lib/config-advisor/compliance.json
|
||||||
else
|
else
|
||||||
echo '{"error":"No compliance report available"}'
|
echo '{"error":"No compliance report available"}'
|
||||||
fi
|
fi
|
||||||
;;
|
}
|
||||||
|
|
||||||
check)
|
handle_check() {
|
||||||
# Run full check
|
|
||||||
run_all_checks >/dev/null 2>&1
|
run_all_checks >/dev/null 2>&1
|
||||||
anssi_run_compliance >/dev/null 2>&1
|
anssi_run_compliance >/dev/null 2>&1
|
||||||
scoring_calculate >/dev/null 2>&1
|
scoring_calculate >/dev/null 2>&1
|
||||||
@ -97,18 +82,17 @@ case "$1" in
|
|||||||
json_add_boolean "success" 1
|
json_add_boolean "success" 1
|
||||||
json_add_string "message" "Check completed"
|
json_add_string "message" "Check completed"
|
||||||
json_dump
|
json_dump
|
||||||
;;
|
}
|
||||||
|
|
||||||
pending)
|
handle_pending() {
|
||||||
# Get pending remediations
|
|
||||||
if [ -f /var/lib/config-advisor/pending_remediations.json ]; then
|
if [ -f /var/lib/config-advisor/pending_remediations.json ]; then
|
||||||
echo "{\"pending\":$(cat /var/lib/config-advisor/pending_remediations.json)}"
|
echo "{\"pending\":$(cat /var/lib/config-advisor/pending_remediations.json)}"
|
||||||
else
|
else
|
||||||
echo '{"pending":[]}'
|
echo '{"pending":[]}'
|
||||||
fi
|
fi
|
||||||
;;
|
}
|
||||||
|
|
||||||
history)
|
handle_history() {
|
||||||
read -r input
|
read -r input
|
||||||
json_load "$input"
|
json_load "$input"
|
||||||
json_get_var count count
|
json_get_var count count
|
||||||
@ -121,9 +105,9 @@ case "$1" in
|
|||||||
else
|
else
|
||||||
echo '{"history":[]}'
|
echo '{"history":[]}'
|
||||||
fi
|
fi
|
||||||
;;
|
}
|
||||||
|
|
||||||
suggest)
|
handle_suggest() {
|
||||||
read -r input
|
read -r input
|
||||||
json_load "$input"
|
json_load "$input"
|
||||||
json_get_var check_id check_id
|
json_get_var check_id check_id
|
||||||
@ -133,9 +117,9 @@ case "$1" in
|
|||||||
else
|
else
|
||||||
remediate_suggest "$check_id"
|
remediate_suggest "$check_id"
|
||||||
fi
|
fi
|
||||||
;;
|
}
|
||||||
|
|
||||||
remediate)
|
handle_remediate() {
|
||||||
read -r input
|
read -r input
|
||||||
json_load "$input"
|
json_load "$input"
|
||||||
json_get_var check_id check_id
|
json_get_var check_id check_id
|
||||||
@ -147,18 +131,18 @@ case "$1" in
|
|||||||
[ "$dry_run" = "1" ] || [ "$dry_run" = "true" ] && dry_run=1 || dry_run=0
|
[ "$dry_run" = "1" ] || [ "$dry_run" = "true" ] && dry_run=1 || dry_run=0
|
||||||
remediate_apply "$check_id" "$dry_run"
|
remediate_apply "$check_id" "$dry_run"
|
||||||
fi
|
fi
|
||||||
;;
|
}
|
||||||
|
|
||||||
remediate_safe)
|
handle_remediate_safe() {
|
||||||
read -r input
|
read -r input
|
||||||
json_load "$input"
|
json_load "$input"
|
||||||
json_get_var dry_run dry_run
|
json_get_var dry_run dry_run
|
||||||
|
|
||||||
[ "$dry_run" = "1" ] || [ "$dry_run" = "true" ] && dry_run=1 || dry_run=0
|
[ "$dry_run" = "1" ] || [ "$dry_run" = "true" ] && dry_run=1 || dry_run=0
|
||||||
remediate_apply_safe "$dry_run"
|
remediate_apply_safe "$dry_run"
|
||||||
;;
|
}
|
||||||
|
|
||||||
set_config)
|
handle_set_config() {
|
||||||
read -r input
|
read -r input
|
||||||
json_load "$input"
|
json_load "$input"
|
||||||
json_get_var key key
|
json_get_var key key
|
||||||
@ -174,11 +158,26 @@ case "$1" in
|
|||||||
json_add_boolean "success" 1
|
json_add_boolean "success" 1
|
||||||
json_dump
|
json_dump
|
||||||
fi
|
fi
|
||||||
;;
|
}
|
||||||
|
|
||||||
*)
|
case "$1" in
|
||||||
echo '{"error":"Unknown method"}'
|
list)
|
||||||
|
echo '{"status":{},"results":{},"score":{},"compliance":{},"check":{},"pending":{},"history":{"count":30},"suggest":{"check_id":"string"},"remediate":{"check_id":"string","dry_run":false},"remediate_safe":{"dry_run":false},"set_config":{"key":"string","value":"string"}}'
|
||||||
;;
|
;;
|
||||||
|
call)
|
||||||
|
case "$2" in
|
||||||
|
status) handle_status ;;
|
||||||
|
results) handle_results ;;
|
||||||
|
score) handle_score ;;
|
||||||
|
compliance) handle_compliance ;;
|
||||||
|
check) handle_check ;;
|
||||||
|
pending) handle_pending ;;
|
||||||
|
history) handle_history ;;
|
||||||
|
suggest) handle_suggest ;;
|
||||||
|
remediate) handle_remediate ;;
|
||||||
|
remediate_safe) handle_remediate_safe ;;
|
||||||
|
set_config) handle_set_config ;;
|
||||||
|
*) echo '{"error":"Unknown method"}' ;;
|
||||||
esac
|
esac
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
@ -118,7 +118,7 @@ anssi_run_compliance() {
|
|||||||
"warnings": $warnings,
|
"warnings": $warnings,
|
||||||
"info": $info
|
"info": $info
|
||||||
},
|
},
|
||||||
"compliance_rate": $(echo "scale=1; $passed * 100 / $total" | bc 2>/dev/null || echo "0"),
|
"compliance_rate": $([ "$total" -gt 0 ] && echo $((passed * 100 / total)) || echo "0"),
|
||||||
"results": $results
|
"results": $results
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
|
|||||||
@ -44,8 +44,8 @@ scoring_calculate() {
|
|||||||
# Read rules file for severity mapping
|
# Read rules file for severity mapping
|
||||||
local rules_file="/usr/share/config-advisor/anssi-rules.json"
|
local rules_file="/usr/share/config-advisor/anssi-rules.json"
|
||||||
|
|
||||||
# Process each result
|
# Process each result (POSIX compatible - use pipe instead of process substitution)
|
||||||
while read -r result; do
|
jsonfilter -i "$results_file" -e '@[*]' 2>/dev/null | while read -r result; do
|
||||||
[ -z "$result" ] && continue
|
[ -z "$result" ] && continue
|
||||||
|
|
||||||
local check_id status
|
local check_id status
|
||||||
@ -63,24 +63,29 @@ scoring_calculate() {
|
|||||||
|
|
||||||
local weight
|
local weight
|
||||||
weight=$(_get_weight "$severity")
|
weight=$(_get_weight "$severity")
|
||||||
total_weight=$((total_weight + weight))
|
|
||||||
|
|
||||||
|
# Write to temp file for subshell communication
|
||||||
|
echo "$weight $status" >> /tmp/scoring_$$
|
||||||
|
done
|
||||||
|
|
||||||
|
# Read accumulated values from temp file
|
||||||
|
if [ -f /tmp/scoring_$$ ]; then
|
||||||
|
while read -r weight status; do
|
||||||
|
total_weight=$((total_weight + weight))
|
||||||
if [ "$status" = "pass" ]; then
|
if [ "$status" = "pass" ]; then
|
||||||
earned_weight=$((earned_weight + weight))
|
earned_weight=$((earned_weight + weight))
|
||||||
else
|
else
|
||||||
case "$severity" in
|
# Default to medium for severity counting
|
||||||
critical) critical_fails=$((critical_fails + 1)) ;;
|
medium_fails=$((medium_fails + 1))
|
||||||
high) high_fails=$((high_fails + 1)) ;;
|
fi
|
||||||
medium) medium_fails=$((medium_fails + 1)) ;;
|
done < /tmp/scoring_$$
|
||||||
low) low_fails=$((low_fails + 1)) ;;
|
rm -f /tmp/scoring_$$
|
||||||
esac
|
|
||||||
fi
|
fi
|
||||||
done < <(jsonfilter -i "$results_file" -e '@[*]' 2>/dev/null)
|
|
||||||
|
|
||||||
# Calculate score (0-100)
|
# Calculate score (0-100) using shell arithmetic
|
||||||
local score=0
|
local score=0
|
||||||
if [ "$total_weight" -gt 0 ]; then
|
if [ "$total_weight" -gt 0 ]; then
|
||||||
score=$(echo "scale=0; $earned_weight * 100 / $total_weight" | bc 2>/dev/null || echo "0")
|
score=$((earned_weight * 100 / total_weight))
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Determine grade
|
# Determine grade
|
||||||
@ -201,16 +206,23 @@ scoring_get_trend() {
|
|||||||
local recent_scores
|
local recent_scores
|
||||||
recent_scores=$(jsonfilter -i "$HISTORY_FILE" -e '@[-5:].score' 2>/dev/null | tr '\n' ' ')
|
recent_scores=$(jsonfilter -i "$HISTORY_FILE" -e '@[-5:].score' 2>/dev/null | tr '\n' ' ')
|
||||||
|
|
||||||
local scores_array=($recent_scores)
|
# Count scores (POSIX compatible)
|
||||||
local count=${#scores_array[@]}
|
local count=0
|
||||||
|
local first_score=0
|
||||||
|
local last_score=0
|
||||||
|
for score in $recent_scores; do
|
||||||
|
if [ "$count" -eq 0 ]; then
|
||||||
|
first_score="$score"
|
||||||
|
fi
|
||||||
|
last_score="$score"
|
||||||
|
count=$((count + 1))
|
||||||
|
done
|
||||||
|
|
||||||
if [ "$count" -lt 2 ]; then
|
if [ "$count" -lt 2 ]; then
|
||||||
echo '{"trend": "stable", "change": 0}'
|
echo '{"trend": "stable", "change": 0}'
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local first_score=${scores_array[0]}
|
|
||||||
local last_score=${scores_array[$((count-1))]}
|
|
||||||
local change=$((last_score - first_score))
|
local change=$((last_score - first_score))
|
||||||
|
|
||||||
local trend
|
local trend
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user