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:
CyberMind-FR 2026-02-06 06:08:28 +01:00
parent 0f4649c1e0
commit 714313633b
3 changed files with 196 additions and 185 deletions

View File

@ -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

View File

@ -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

View File

@ -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