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、企业微信/钉钉通知。

关键检查项示例#

系统基线#

  • sysctlnet.ipv4.ip_forward=0vm.swappiness=10
  • 时区与NTP:timedatectlchronyc sources
  • SSH:PermitRootLogin noPasswordAuthentication no
  • 文件权限:/etc/shadow 0000/var/log/secure属主属组
  • 资源限制:/etc/security/limits.confulimit -n

中间件基线#

  • MySQLinnodb_buffer_pool_sizesql_modeskip_name_resolve
  • Nginxworker_processesserver_tokens off
  • Redisrequirepassprotected-mode yesappendonly yes
  • Kafkanum.partitionslog.retention.hours
  • ZKtickTimesyncLimitmaxClientCnxns
  • Keepalived/HAProxyvrrp_instance配置一致、健康检查脚本路径
  • ProxySQLmysql_serversmysql_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

排错与定位#

  1. 采集值为空
    - 排查命令是否有权限或路径错误:
    bash sudo -u root sysctl -n net.ipv4.ip_forward
    - 检查配置文件路径:
    bash ls -l /etc/ssh/sshd_config

  2. 正则匹配不到
    - 查看实际配置行:
    bash grep -n "PermitRootLogin" /etc/ssh/sshd_config
    - 调整正则并验证:
    bash echo "PermitRootLogin no" | sed -E "s/^PermitRootLogin\s+(\w+)/\1/"

  3. 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

练习#

  1. baseline.yml 中新增一个 ulimit -n 检查项,要求值为 1024,并在脚本中验证生效。
  2. nginx 增加 worker_processes 检查,正则匹配并输出实际值。
  3. 将报告输出改为 HTML(可使用 jq -r 转表格),并保存到 /var/log/inspect/report.html
  4. 增加白名单机制:对于允许范围内的值(如 vm.swappiness 10~20),实现范围匹配。