5.7.5 日志脱敏与敏感信息保护

日志是脚本可观测性的核心,但也常成为敏感信息泄露的入口。本节聚焦在 Shell 脚本中对日志内容的脱敏与保护,确保在排错、审计的同时满足合规与安全要求,并给出可执行示例、排错与练习。

1. 识别敏感信息与风险边界#

常见敏感信息包括:
- 账号与密码:数据库/中间件账号、API Key、Token、证书私钥
- 个人信息:手机号、邮箱、身份证、姓名
- 业务密钥:支付密钥、内部服务鉴权串
- 系统信息:内网地址、主机名、版本细节

风险边界应明确:
- 日志可被哪些人访问
- 日志存储周期与归档位置
- 是否传输到外部系统(如 ELK、SLS、SIEM)

原理草图(脱敏与安全日志链路):

文章图片

2. 日志脱敏基本策略#

  • 最小化日志原则:只记录必要信息,不记录敏感字段
  • 部分保留:仅保留必要片段(如手机号前3后4)
  • 哈希替代:用于审计关联的字段用哈希表示
  • 替换标记:明确标识已脱敏字段 [REDACTED]

示例(仅展示必要字段):
- user=tom token=***
- phone=138****1234
- apikey=sha256:9c1d...

3. Shell 中的脱敏实现方式#

3.1 使用 sed 进行正则脱敏#

log='user=tom password=Passw0rd token=abcd1234 phone=13812341234'
echo "$log" | sed -E 's/(password|passwd|token)=([^ ]+)/\1=*** /g' \
               | sed -E 's/(phone=)([0-9]{3})[0-9]{4}([0-9]{4})/\1\2****\3/'

命令解释
- sed -E 启用扩展正则
- 第一条规则将 password/token 的值替换为 ***
- 第二条规则保留手机号前3后4

3.2 使用 awk 按字段脱敏#

log='user=tom token=abcd1234 ip=10.0.0.1'
echo "$log" | awk '{
  for(i=1;i<=NF;i++){
    if($i ~ /^token=/){ sub(/=.*/, "=***", $i) }
  }
  print
}'

命令解释
- NF 字段数量
- sub 仅替换当前字段

3.3 使用参数替换快速屏蔽#

secret="ABCD1234XYZ"
masked="${secret:0:2}****${secret: -2}"
echo "token=$masked"

命令解释
- ${secret:0:2} 取前2位
- ${secret: -2} 取后2位

3.4 统一脱敏函数(推荐)#

保存脚本:/opt/scripts/lib/log_secure.sh

#!/usr/bin/env bash
mask_kv() {
  echo "$1" | sed -E \
    -e 's/(password|passwd|token|apikey)=([^ ]+)/\1=*** /g' \
    -e 's/(phone=)([0-9]{3})[0-9]{4}([0-9]{4})/\1\2****\3/'
}

log_info() { echo "$(date +%F\ %T) [INFO] $(mask_kv "$*")"; }

调用示例:

source /opt/scripts/lib/log_secure.sh
log_info "user=tom token=abcd1234 phone=13812341234"

预期效果

2024-05-01 12:00:00 [INFO] user=tom token=***  phone=138****1234

4. 输出控制与日志级别#

  • 仅在 debug 环境输出详细日志
  • 通过环境变量控制日志级别:
LOG_LEVEL=${LOG_LEVEL:-INFO}
log_debug(){ [ "$LOG_LEVEL" = "DEBUG" ] && echo "$(date +%F\ %T) [DEBUG] $*"; }
  • 禁止在生产环境输出 set -x 中的参数(可能泄露)

示例:安全日志输出骨架

#!/usr/bin/env bash
source /opt/scripts/lib/log_secure.sh
LOG_LEVEL=${LOG_LEVEL:-INFO}

log_debug "request=full_body token=abcd1234"
log_info "user=tom action=login token=abcd1234"

5. 保护敏感日志的存储与传输#

  • 文件权限:日志文件权限设置为 600
  • 目录隔离:敏感日志单独目录隔离
  • 日志轮转:防止长期暴露
  • 传输加密:推送到集中日志时使用 HTTPS/TLS
  • 审计访问:记录谁访问/下载了日志

5.1 安全日志目录与权限#

sudo install -d -m 700 /var/log/app_secure
sudo install -m 600 /dev/null /var/log/app_secure/app.log

命令解释
- -d 创建目录
- -m 指定权限

5.2 安装并配置 logrotate(示例)#

安装:

# Debian/Ubuntu
sudo apt-get update && sudo apt-get install -y logrotate

# RHEL/CentOS
sudo yum install -y logrotate

配置文件:/etc/logrotate.d/app_secure

/var/log/app_secure/app.log {
  daily
  rotate 7
  missingok
  compress
  delaycompress
  notifempty
  create 600 root root
}

手动测试:

sudo logrotate -vf /etc/logrotate.d/app_secure

预期效果:生成 app.log.1 并压缩历史日志。

5.3 传输加密示例(curl + HTTPS)#

curl -sS -X POST https://log.example.com/ingest \
  -H "Content-Type: text/plain" \
  --data-binary @/var/log/app_secure/app.log

命令解释
- --data-binary 保持内容原样
- 使用 HTTPS 保护传输

6. 常见误区与规避#

  • 直接输出命令参数:如 mysqldump -pPASSWORD
  • 调试时开启 set -x:会输出所有变量与命令
  • 把密钥写入日志:排错完忘记清理
  • 把完整响应体打印:可能包含 token/PII

正确做法:
- 使用环境变量或文件传参
- 调试仅本地、短期、脱敏后再提交

示例(安全传参):

# 不在命令行暴露密码
export MYSQL_PWD='StrongPass'
mysqldump -h 127.0.0.1 -uroot dbname > /tmp/db.sql
unset MYSQL_PWD

7. 排错指南#

7.1 脱敏规则未生效#

排查命令:

log='password=123 token=abc'
echo "$log" | sed -E 's/(password|token)=([^ ]+)/\1=*** /g'

检查点
- 是否启用了 -E
- 是否有多余空格或字段格式不一致

7.2 日志权限被拒绝#

ls -l /var/log/app_secure/app.log
id

检查点
- 文件权限是否为 600
- 当前用户是否为 root 或日志所属用户

7.3 logrotate 不轮转#

sudo logrotate -d /etc/logrotate.d/app_secure

检查点
- 配置路径是否正确
- create 权限是否匹配

8. 练习#

  1. 编写 mask_kv,新增对 email= 的脱敏规则(仅保留域名)。
  2. 写一个脚本 secure_log_demo.sh:接收一行输入,脱敏后写入 /var/log/app_secure/app.log
  3. 配置 logrotate,将保留周期改为 14 天,并验证轮转效果。
  4. LOG_LEVEL=DEBUG 时输出详细日志,但保证脱敏依然生效。