5.8.6 配置巡检与合规性检查脚本
配置巡检与合规性检查脚本用于验证系统与中间件配置是否符合基线标准,输出差异与整改建议。本节围绕检查项设计、数据采集、结果比对、报告生成与通知闭环,给出可落地的脚本思路与实践要点,并提供完整示例、命令解释、排错与练习。
巡检目标与范围#
- 系统层:内核参数、时区与时间同步、主机名解析、用户与权限、SSH与审计、sudo策略、文件权限与日志轮转。
- 网络层:防火墙策略、端口暴露、监听服务、路由与DNS配置。
- 中间件层:MySQL/Nginx/Redis/Nacos/Kafka/ZooKeeper/Keepalived/HAProxy/ProxySQL关键配置项一致性。
- 容器与编排:Docker daemon参数、镜像源、K8s组件配置与安全策略。
- 监控与发布:Prometheus与Jenkins基础配置、凭据权限与访问控制。
巡检脚本结构建议#
- 配置基线:以YAML/INI定义检查项、期望值、允许范围、来源文件与命令。
- 采集层:统一封装读取文件、执行命令、解析输出与获取版本。
- 比对层:支持精确匹配、正则匹配、范围匹配与列表包含。
- 报告层:生成文本/JSON/HTML,标注风险级别与差异详情。
- 通知层:邮件、Webhook、企业微信/钉钉通知。
关键检查项示例#
系统基线#
sysctl:net.ipv4.ip_forward=0、vm.swappiness=10- 时区与NTP:
timedatectl与chronyc sources - SSH:
PermitRootLogin no、PasswordAuthentication no - 文件权限:
/etc/shadow 0000、/var/log/secure属主属组 - 资源限制:
/etc/security/limits.conf、ulimit -n
中间件基线#
- MySQL:
innodb_buffer_pool_size、sql_mode、skip_name_resolve - Nginx:
worker_processes、server_tokens off - Redis:
requirepass、protected-mode yes、appendonly yes - Kafka:
num.partitions、log.retention.hours - ZK:
tickTime、syncLimit、maxClientCnxns - Keepalived/HAProxy:
vrrp_instance配置一致、健康检查脚本路径 - ProxySQL:
mysql_servers、mysql_users权限校验
配置巡检脚本实现要点#
- 统一规范:返回值0/1表示通过与失败,输出
key: actual -> expected - 版本适配:不同版本配置项名称差异需纳入映射表
- 权限控制:读取配置与执行命令需最小权限
- 可扩展:新增检查项只改配置基线文件
- 审计追踪:每次巡检记录时间、主机、版本与结果摘要
差异检测与合规评分#
- 差异分类:致命/高危/一般/建议
- 评分模型:按权重累计扣分,设定合格线
- 整改建议:输出具体修改命令或配置片段
输出与集成#
- 本地输出:
/var/log/inspect/report_YYYYmmdd.json - 远程汇总:通过HTTP POST提交到运维平台
- 报警触发:高危差异触发即时通知
常见问题与优化#
- 误报:对动态值(如
/proc)采用范围匹配或白名单 - 性能影响:并发执行与缓存上次结果,避免频繁读取大文件
- 可维护性:将规则、采集与比对解耦,便于持续迭代
基线配置示例(YAML)#
文件路径:
/opt/inspect/baseline.yml
system:
sysctl:
net.ipv4.ip_forward:
expect: "0"
source: "cmd"
cmd: "sysctl -n net.ipv4.ip_forward"
level: "high"
vm.swappiness:
expect: "10"
source: "cmd"
cmd: "sysctl -n vm.swappiness"
level: "medium"
ssh:
PermitRootLogin:
expect: "no"
source: "file"
file: "/etc/ssh/sshd_config"
regex: "^PermitRootLogin\\s+(\\w+)"
level: "high"
PasswordAuthentication:
expect: "no"
source: "file"
file: "/etc/ssh/sshd_config"
regex: "^PasswordAuthentication\\s+(\\w+)"
level: "high"
middleware:
mysql:
skip_name_resolve:
expect: "ON"
source: "cmd"
cmd: "mysql -Nse \"show variables like 'skip_name_resolve';\" | awk '{print $2}'"
level: "medium"
nginx:
server_tokens:
expect: "off"
source: "file"
file: "/etc/nginx/nginx.conf"
regex: "^\\s*server_tokens\\s+(\\w+);"
level: "medium"
redis:
protected-mode:
expect: "yes"
source: "file"
file: "/etc/redis/redis.conf"
regex: "^protected-mode\\s+(\\w+)"
level: "high"
巡检脚本示例(可直接运行)#
文件路径:
/opt/inspect/inspect.sh
依赖:bash,yq(用于解析YAML)
#!/usr/bin/env bash
# 检查脚本示例:读取 baseline.yml,输出 JSON 报告
set -euo pipefail
BASELINE="/opt/inspect/baseline.yml"
OUTDIR="/var/log/inspect"
OUTFILE="${OUTDIR}/report_$(date +%Y%m%d_%H%M%S).json"
mkdir -p "$OUTDIR"
# 依赖检查
if ! command -v yq >/dev/null 2>&1; then
echo "ERROR: yq not found. Install: yum install -y yq 或 apt install -y yq"
exit 2
fi
# 统一比对函数
compare_value() {
local key="$1" actual="$2" expect="$3" level="$4"
if [[ "$actual" == "$expect" ]]; then
echo "{\"key\":\"$key\",\"status\":\"PASS\",\"actual\":\"$actual\",\"expect\":\"$expect\",\"level\":\"$level\"}"
else
echo "{\"key\":\"$key\",\"status\":\"FAIL\",\"actual\":\"$actual\",\"expect\":\"$expect\",\"level\":\"$level\"}"
fi
}
# 采集单项
collect_item() {
local key="$1" source="$2" cmd="$3" file="$4" regex="$5"
local actual=""
if [[ "$source" == "cmd" ]]; then
actual="$(eval "$cmd" 2>/dev/null | tr -d '\r')"
else
actual="$(grep -E "$regex" "$file" 2>/dev/null | head -n1 | sed -E "s/$regex/\\1/")"
fi
echo "$actual"
}
# 生成 JSON
echo "[" > "$OUTFILE"
first=1
while IFS= read -r path; do
key="$(yq e "$path | path | .[-1]" -r "$BASELINE")"
expect="$(yq e "$path.expect" -r "$BASELINE")"
source="$(yq e "$path.source" -r "$BASELINE")"
cmd="$(yq e "$path.cmd" -r "$BASELINE")"
file="$(yq e "$path.file" -r "$BASELINE")"
regex="$(yq e "$path.regex" -r "$BASELINE")"
level="$(yq e "$path.level" -r "$BASELINE")"
actual="$(collect_item "$key" "$source" "$cmd" "$file" "$regex")"
result="$(compare_value "$path" "$actual" "$expect" "$level")"
if [[ $first -eq 1 ]]; then
first=0
else
echo "," >> "$OUTFILE"
fi
echo "$result" >> "$OUTFILE"
done < <(yq e '.. | select(has("expect")) | path | "." + join(".")' -r "$BASELINE")
echo "]" >> "$OUTFILE"
echo "Report saved to: $OUTFILE"
命令解释#
yq e '.. | select(has("expect")) | path | "." + join(".")':列出所有含expect的检查项路径。eval "$cmd":执行基线中定义的命令采集值。grep -E "$regex":按正则提取配置项的实际值。- 输出 JSON 便于运维平台与可视化解析。
示例:一次巡检与预期输出#
bash /opt/inspect/inspect.sh
预期效果:
- 生成 /var/log/inspect/report_YYYYmmdd_HHMMSS.json
- 结果中包含 PASS/FAIL、实际值与期望值
示例报告片段:
[
{"key":".system.sysctl.net.ipv4.ip_forward","status":"PASS","actual":"0","expect":"0","level":"high"},
{"key":".system.ssh.PermitRootLogin","status":"FAIL","actual":"yes","expect":"no","level":"high"}
]
安装与依赖(工具层)#
若系统未安装 yq,可按发行版安装:
# RHEL/CentOS
yum install -y epel-release
yum install -y yq
# Debian/Ubuntu
apt update
apt install -y yq
验证:
yq --version
排错与定位#
-
采集值为空
- 排查命令是否有权限或路径错误:
bash sudo -u root sysctl -n net.ipv4.ip_forward
- 检查配置文件路径:
bash ls -l /etc/ssh/sshd_config -
正则匹配不到
- 查看实际配置行:
bash grep -n "PermitRootLogin" /etc/ssh/sshd_config
- 调整正则并验证:
bash echo "PermitRootLogin no" | sed -E "s/^PermitRootLogin\s+(\w+)/\1/" -
JSON 格式错误
- 使用jq校验:
bash jq . /var/log/inspect/report_*.json
扩展示例:合规评分与通知#
# 统计失败数并输出评分(满分100,每个FAIL扣10)
fails=$(jq '[.[] | select(.status=="FAIL")] | length' /var/log/inspect/report_*.json)
score=$((100 - fails*10))
echo "Score: $score"
# Webhook 通知示例
curl -X POST -H "Content-Type: application/json" \
-d "{\"score\":$score,\"fails\":$fails}" \
http://ops.example.com/webhook/inspect
练习#
- 在
baseline.yml中新增一个ulimit -n检查项,要求值为1024,并在脚本中验证生效。 - 为
nginx增加worker_processes检查,正则匹配并输出实际值。 - 将报告输出改为 HTML(可使用
jq -r转表格),并保存到/var/log/inspect/report.html。 - 增加白名单机制:对于允许范围内的值(如
vm.swappiness10~20),实现范围匹配。