CI / Rust checks (push) Waiting to run
CI / Docs and registry checks (push) Waiting to run
CI / Smoke checks (push) Waiting to run
Coverage / Coverage baseline (push) Waiting to run
Security / Cargo audit (push) Waiting to run
Security / Cargo deny (push) Waiting to run
Security / Secret pattern check (push) Waiting to run
Security / Dependency review (push) Waiting to run
- default DetMir DLP runtime to core_only/disabled with load-guard protection - add fail-closed placeholder validation and runtime-scoped artifact checks - document operator re-enable flow for light profile and guard rollback - update prod docs, env examples, and Ansible DLP defaults
270 lines
7.8 KiB
Bash
270 lines
7.8 KiB
Bash
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
ACTION="${1:-status}"
|
|
PROFILE="${2:-${AW_DLP_PROFILE:-core_only}}"
|
|
AW_BASE="${AW_DLP_CONTROL_AW_BASE:-http://127.0.0.1:5600}"
|
|
HOSTNAME_FILTER="${AW_DLP_CONTROL_HOSTNAME:-${AW_LOGICAL_HOST_ID:-${AW_MONITORED_WINDOWS_HOSTNAME:-HOST-EXAMPLE}}}"
|
|
STATE_DIR="${AW_DLP_CONTROL_STATE_DIR:-/var/lib/activitywatch/health}"
|
|
STATE_FILE="${AW_DLP_CONTROL_STATE_FILE:-${STATE_DIR}/dlp-runtime-state.json}"
|
|
STATE_HISTORY_DIR="${AW_DLP_CONTROL_HISTORY_DIR:-${STATE_DIR}/dlp-runtime-history}"
|
|
ROLLBACK_FILE="${AW_DLP_CONTROL_ROLLBACK_FILE:-${STATE_DIR}/dlp-runtime-rollback.state}"
|
|
REASON="${AW_DLP_DISABLED_REASON:-dlp_runtime_profile_control}"
|
|
|
|
DLP_UNITS=(
|
|
aw-dlp-influx-exporter.timer
|
|
aw-dlp-influx-exporter.service
|
|
activitywatch-dlp-aggregator.timer
|
|
activitywatch-dlp-aggregator.service
|
|
aw-dlp-report-scheduler.timer
|
|
aw-dlp-report-scheduler.service
|
|
aw-dlp-syslog-forwarder.timer
|
|
aw-dlp-syslog-forwarder.service
|
|
aw-dlp-webhook-sender.timer
|
|
aw-dlp-webhook-sender.service
|
|
aw-dlp-cef-exporter.timer
|
|
aw-dlp-cef-exporter.service
|
|
aw-dlp-ioc-refresh.timer
|
|
aw-dlp-ioc-refresh.service
|
|
aw-dlp-policy-engine.service
|
|
aw-dlp-case-management.service
|
|
detmir-portal-evidence.service
|
|
)
|
|
|
|
DLP_BUCKET_PREFIXES=(
|
|
aw-dlp-endpoint-signals
|
|
aw-dlp-incidents
|
|
aw-dlp-review
|
|
aw-dlp-rules
|
|
)
|
|
|
|
DLP_LIGHT_UNITS=(
|
|
activitywatch-dlp-aggregator.timer
|
|
aw-dlp-ioc-refresh.timer
|
|
)
|
|
|
|
DLP_ON_DEMAND_UNITS=(
|
|
aw-dlp-ioc-refresh.timer
|
|
aw-dlp-policy-engine.service
|
|
aw-dlp-case-management.service
|
|
detmir-portal-evidence.service
|
|
)
|
|
|
|
json_escape() {
|
|
local value="$1"
|
|
python3 -c 'import json,sys; print(json.dumps(sys.argv[1], ensure_ascii=False))' "$value"
|
|
}
|
|
|
|
unit_json() {
|
|
local first=1 unit active enabled load
|
|
printf '['
|
|
for unit in "${DLP_UNITS[@]}"; do
|
|
load="$(systemctl show -p LoadState --value "$unit" 2>/dev/null || true)"
|
|
if [[ "$load" == "not-found" || -z "$load" ]]; then
|
|
active="not-found"
|
|
enabled="not-found"
|
|
else
|
|
active="$(systemctl is-active "$unit" 2>/dev/null || true)"
|
|
enabled="$(systemctl is-enabled "$unit" 2>/dev/null || true)"
|
|
fi
|
|
[[ "$first" -eq 1 ]] || printf ','
|
|
first=0
|
|
printf '{"unit":%s,"load":%s,"active":%s,"enabled":%s}' \
|
|
"$(json_escape "$unit")" \
|
|
"$(json_escape "${load:-not-found}")" \
|
|
"$(json_escape "${active:-unknown}")" \
|
|
"$(json_escape "${enabled:-unknown}")"
|
|
done
|
|
printf ']'
|
|
}
|
|
|
|
bucket_json() {
|
|
local first=1 prefix bucket url payload ts count
|
|
printf '['
|
|
for prefix in "${DLP_BUCKET_PREFIXES[@]}"; do
|
|
bucket="${prefix}_${HOSTNAME_FILTER}"
|
|
url="${AW_BASE%/}/api/0/buckets/${bucket}/events?limit=1"
|
|
payload="$(curl -sS --connect-timeout 3 --max-time 8 "$url" 2>/dev/null || true)"
|
|
ts="$(printf '%s' "$payload" | jq -r '.[0].timestamp // ""' 2>/dev/null || true)"
|
|
count="$(printf '%s' "$payload" | jq -r 'if type == "array" then length else 0 end' 2>/dev/null || printf '0')"
|
|
[[ "$first" -eq 1 ]] || printf ','
|
|
first=0
|
|
printf '{"bucket":%s,"sample_count":%s,"latest_timestamp":%s}' \
|
|
"$(json_escape "$bucket")" \
|
|
"${count:-0}" \
|
|
"$(json_escape "$ts")"
|
|
done
|
|
printf ']'
|
|
}
|
|
|
|
unit_exists() {
|
|
local unit="$1"
|
|
systemctl list-unit-files "$unit" --no-legend 2>/dev/null | grep -q . || systemctl status "$unit" >/dev/null 2>&1
|
|
}
|
|
|
|
stop_disable_all_dlp() {
|
|
local unit
|
|
for unit in "${DLP_UNITS[@]}"; do
|
|
if unit_exists "$unit"; then
|
|
systemctl stop "$unit" >/dev/null 2>&1 || true
|
|
systemctl disable "$unit" >/dev/null 2>&1 || true
|
|
systemctl reset-failed "$unit" >/dev/null 2>&1 || true
|
|
fi
|
|
done
|
|
}
|
|
|
|
enable_start_units() {
|
|
local unit
|
|
for unit in "$@"; do
|
|
if unit_exists "$unit"; then
|
|
systemctl enable --now "$unit" >/dev/null 2>&1 || true
|
|
fi
|
|
done
|
|
}
|
|
|
|
capture_rollback_state() {
|
|
local tmp unit load active enabled
|
|
mkdir -p "$STATE_DIR"
|
|
tmp="$(mktemp "${ROLLBACK_FILE}.tmp.XXXXXX")"
|
|
{
|
|
printf '# generated_at_utc=%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
printf '# reason=pre_profile_change\n'
|
|
for unit in "${DLP_UNITS[@]}"; do
|
|
load="$(systemctl show -p LoadState --value "$unit" 2>/dev/null || true)"
|
|
if [[ "$load" == "not-found" || -z "$load" ]]; then
|
|
active="not-found"
|
|
enabled="not-found"
|
|
else
|
|
active="$(systemctl is-active "$unit" 2>/dev/null || true)"
|
|
enabled="$(systemctl is-enabled "$unit" 2>/dev/null || true)"
|
|
fi
|
|
printf '%s|%s|%s|%s\n' "$unit" "${load:-not-found}" "$active" "$enabled"
|
|
done
|
|
} >"$tmp"
|
|
mv "$tmp" "$ROLLBACK_FILE"
|
|
}
|
|
|
|
write_stats() {
|
|
local mode="${1:-current}" now stamp tmp history_file
|
|
now="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
stamp="$(date -u +%Y%m%dT%H%M%SZ)"
|
|
mkdir -p "$STATE_DIR" "$STATE_HISTORY_DIR"
|
|
tmp="$(mktemp "${STATE_FILE}.tmp.XXXXXX")"
|
|
{
|
|
printf '{'
|
|
printf '"generated_at_utc":%s,' "$(json_escape "$now")"
|
|
printf '"mode":%s,' "$(json_escape "$mode")"
|
|
printf '"profile":%s,' "$(json_escape "${AW_DLP_PROFILE:-$PROFILE}")"
|
|
printf '"reason":%s,' "$(json_escape "$REASON")"
|
|
printf '"aw_base":%s,' "$(json_escape "$AW_BASE")"
|
|
printf '"hostname":%s,' "$(json_escape "$HOSTNAME_FILTER")"
|
|
printf '"units":'
|
|
unit_json
|
|
printf ',"buckets":'
|
|
bucket_json
|
|
printf '}\n'
|
|
} >"$tmp"
|
|
mv "$tmp" "$STATE_FILE"
|
|
history_file="${STATE_HISTORY_DIR}/dlp-runtime-${mode}-${stamp}.json"
|
|
cp -a "$STATE_FILE" "$history_file"
|
|
printf 'latest=%s\nhistory=%s\n' "$STATE_FILE" "$history_file"
|
|
}
|
|
|
|
apply_profile() {
|
|
local target_profile="$1"
|
|
capture_rollback_state
|
|
case "$target_profile" in
|
|
core_only|disabled|off)
|
|
PROFILE="core_only"
|
|
stop_disable_all_dlp
|
|
AW_DLP_PROFILE="core_only" write_stats "disabled"
|
|
;;
|
|
light)
|
|
PROFILE="light"
|
|
stop_disable_all_dlp
|
|
enable_start_units "${DLP_LIGHT_UNITS[@]}"
|
|
AW_DLP_PROFILE="light" write_stats "enabled_light"
|
|
;;
|
|
on_demand)
|
|
PROFILE="on_demand"
|
|
stop_disable_all_dlp
|
|
enable_start_units "${DLP_ON_DEMAND_UNITS[@]}"
|
|
AW_DLP_PROFILE="on_demand" write_stats "enabled_on_demand"
|
|
;;
|
|
full|enabled|on)
|
|
PROFILE="full"
|
|
stop_disable_all_dlp
|
|
enable_start_units "${DLP_LIGHT_UNITS[@]}"
|
|
enable_start_units \
|
|
aw-dlp-influx-exporter.timer \
|
|
activitywatch-dlp-aggregator.timer \
|
|
aw-dlp-report-scheduler.timer \
|
|
aw-dlp-syslog-forwarder.timer \
|
|
aw-dlp-webhook-sender.timer \
|
|
aw-dlp-cef-exporter.timer \
|
|
aw-dlp-policy-engine.service \
|
|
aw-dlp-case-management.service \
|
|
detmir-portal-evidence.service
|
|
AW_DLP_PROFILE="full" write_stats "enabled_full"
|
|
;;
|
|
*)
|
|
printf 'unsupported DLP profile: %s\n' "$target_profile" >&2
|
|
printf 'supported profiles: core_only, light, on_demand, full\n' >&2
|
|
exit 2
|
|
;;
|
|
esac
|
|
}
|
|
|
|
disable_dlp() {
|
|
apply_profile "core_only"
|
|
}
|
|
|
|
enable_dlp() {
|
|
apply_profile "full"
|
|
}
|
|
|
|
rollback_dlp() {
|
|
local unit load active enabled
|
|
if [[ ! -s "$ROLLBACK_FILE" ]]; then
|
|
printf 'rollback state not found: %s\n' "$ROLLBACK_FILE" >&2
|
|
exit 1
|
|
fi
|
|
stop_disable_all_dlp
|
|
while IFS='|' read -r unit load active enabled; do
|
|
[[ -n "${unit:-}" && "${unit:0:1}" != "#" ]] || continue
|
|
[[ "$load" != "not-found" ]] || continue
|
|
if [[ "$enabled" == "enabled" ]]; then
|
|
systemctl enable "$unit" >/dev/null 2>&1 || true
|
|
fi
|
|
if [[ "$active" == "active" ]]; then
|
|
systemctl start "$unit" >/dev/null 2>&1 || true
|
|
fi
|
|
done <"$ROLLBACK_FILE"
|
|
write_stats "rollback"
|
|
}
|
|
|
|
case "$ACTION" in
|
|
status|stats)
|
|
write_stats "current"
|
|
;;
|
|
profile)
|
|
printf '%s\n' "${AW_DLP_PROFILE:-$PROFILE}"
|
|
;;
|
|
set-profile)
|
|
apply_profile "$PROFILE"
|
|
;;
|
|
disable)
|
|
disable_dlp
|
|
;;
|
|
enable)
|
|
enable_dlp
|
|
;;
|
|
rollback)
|
|
rollback_dlp
|
|
;;
|
|
*)
|
|
printf 'Usage: %s [status|stats|profile|set-profile <core_only|light|on_demand|full>|disable|enable|rollback]\n' "$0" >&2
|
|
exit 2
|
|
;;
|
|
esac
|