Files
Cloud-book/数据库/Redis_2025/10_Redis安全管理.md
2025-08-27 17:10:05 +08:00

785 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Redis 安全管理
## 访问控制
### 密码认证
Redis 提供了基本的密码认证机制来保护数据安全。
**配置密码认证**
```shell
# 在配置文件中设置密码
# /etc/redis/redis.conf
requirepass your_strong_password
# 动态设置密码
redis-cli CONFIG SET requirepass "your_strong_password"
# 使用密码连接
redis-cli -a your_strong_password
# 或者连接后认证
redis-cli
127.0.0.1:6379> AUTH your_strong_password
OK
```
**密码安全最佳实践**
```shell
# 生成强密码
openssl rand -base64 32
# 密码复杂度要求:
# - 长度至少16位
# - 包含大小写字母、数字、特殊字符
# - 避免使用字典词汇
# - 定期更换密码
# 示例强密码
requirepass "Rd!s@2024#Str0ng&P@ssw0rd"
```
### 用户管理 (ACL)
Redis 6.0+ 引入了 ACLAccess Control List功能提供更细粒度的权限控制。
**ACL 基本概念**
```shell
# 查看当前用户
ACL WHOAMI
# 列出所有用户
ACL LIST
# 查看用户详细信息
ACL GETUSER username
# 查看当前用户权限
ACL GETUSER default
```
**创建和管理用户**
```shell
# 创建只读用户
ACL SETUSER readonly on >readonly_password ~* &* -@all +@read
# 创建读写用户(限制特定键模式)
ACL SETUSER readwrite on >readwrite_password ~app:* &* -@all +@read +@write
# 创建管理员用户
ACL SETUSER admin on >admin_password ~* &* +@all
# 创建应用用户(限制命令)
ACL SETUSER appuser on >app_password ~app:* &* -@all +get +set +del +exists +expire
# 删除用户
ACL DELUSER username
```
**ACL 规则详解**
```shell
# ACL 规则语法:
# on/off启用/禁用用户
# >password设置密码
# ~pattern允许访问的键模式
# &pattern允许访问的发布订阅频道模式
# +command允许的命令
# -command禁止的命令
# +@category允许的命令分类
# -@category禁止的命令分类
# 常用命令分类:
# @read读命令
# @write写命令
# @admin管理命令
# @dangerous危险命令
# @keyspace键空间命令
# @string字符串命令
# @list列表命令
# @set集合命令
# @hash哈希命令
# @sortedset有序集合命令
```
### 权限控制
**细粒度权限配置**
```shell
# 数据库管理员
ACL SETUSER dba on >dba_password ~* &* +@all
# 应用开发者
ACL SETUSER developer on >dev_password ~dev:* &dev:* -@all +@read +@write -flushdb -flushall -shutdown
# 监控用户
ACL SETUSER monitor on >monitor_password ~* &* -@all +info +ping +client +config|get
# 备份用户
ACL SETUSER backup on >backup_password ~* &* -@all +@read +bgsave +lastsave
# 只读分析用户
ACL SETUSER analyst on >analyst_password ~analytics:* &* -@all +@read +scan +keys
```
**权限验证测试**
```shell
# 测试用户权限
redis-cli --user readonly --pass readonly_password
127.0.0.1:6379> GET some_key # 应该成功
127.0.0.1:6379> SET some_key value # 应该失败
# 测试键模式限制
redis-cli --user developer --pass dev_password
127.0.0.1:6379> GET dev:config # 应该成功
127.0.0.1:6379> GET prod:config # 应该失败
```
### IP 白名单
**网络访问控制**
```shell
# 绑定特定IP地址
# /etc/redis/redis.conf
bind 127.0.0.1 192.168.1.100 10.0.0.50
# 禁用保护模式(仅在安全网络环境中)
protected-mode no
# 使用防火墙限制访问
sudo firewall-cmd --permanent --add-rich-rule="rule family='ipv4' source address='192.168.1.0/24' port protocol='tcp' port='6379' accept"
sudo firewall-cmd --reload
```
## 网络安全
### 端口安全
**端口配置和保护**
```shell
# 更改默认端口
# /etc/redis/redis.conf
port 16379 # 使用非标准端口
# 禁用端口仅使用Unix套接字
port 0
unixsocket /var/run/redis/redis.sock
unixsocketperm 700
# 连接Unix套接字
redis-cli -s /var/run/redis/redis.sock
```
**网络接口绑定**
```shell
# 仅绑定内网接口
bind 127.0.0.1 192.168.1.100
# 绑定多个接口
bind 127.0.0.1 10.0.0.100 172.16.0.100
# 监听所有接口(不推荐)
# bind 0.0.0.0
```
### SSL/TLS 加密
Redis 6.0+ 支持 SSL/TLS 加密传输。
**生成SSL证书**
```shell
# 创建证书目录
sudo mkdir -p /etc/redis/ssl
cd /etc/redis/ssl
# 生成私钥
sudo openssl genrsa -out redis.key 2048
# 生成证书签名请求
sudo openssl req -new -key redis.key -out redis.csr -subj "/C=CN/ST=Beijing/L=Beijing/O=Company/CN=redis.example.com"
# 生成自签名证书
sudo openssl x509 -req -days 365 -in redis.csr -signkey redis.key -out redis.crt
# 生成DH参数文件
sudo openssl dhparam -out redis.dh 2048
# 设置权限
sudo chown redis:redis /etc/redis/ssl/*
sudo chmod 600 /etc/redis/ssl/redis.key
sudo chmod 644 /etc/redis/ssl/redis.crt
sudo chmod 644 /etc/redis/ssl/redis.dh
```
**配置SSL/TLS**
```shell
# Redis 配置文件
# /etc/redis/redis.conf
# 启用TLS端口
port 0
tls-port 6380
# 证书文件路径
tls-cert-file /etc/redis/ssl/redis.crt
tls-key-file /etc/redis/ssl/redis.key
tls-dh-params-file /etc/redis/ssl/redis.dh
# CA证书如果使用
# tls-ca-cert-file /etc/redis/ssl/ca.crt
# 客户端证书验证
tls-auth-clients yes
# TLS协议版本
tls-protocols "TLSv1.2 TLSv1.3"
# 密码套件
tls-ciphersuites TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
tls-ciphers ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256
# 会话缓存
tls-session-caching no
tls-session-cache-size 5000
tls-session-cache-timeout 60
```
**SSL客户端连接**
```shell
# 使用SSL连接
redis-cli --tls --cert /etc/redis/ssl/client.crt --key /etc/redis/ssl/client.key --cacert /etc/redis/ssl/ca.crt -p 6380
# 跳过证书验证(仅测试环境)
redis-cli --tls --insecure -p 6380
```
### 防火墙配置
**iptables 配置**
```shell
# 允许特定IP访问Redis
sudo iptables -A INPUT -p tcp -s 192.168.1.0/24 --dport 6379 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 6379 -j DROP
# 限制连接频率
sudo iptables -A INPUT -p tcp --dport 6379 -m connlimit --connlimit-above 10 -j DROP
sudo iptables -A INPUT -p tcp --dport 6379 -m recent --set --name redis
sudo iptables -A INPUT -p tcp --dport 6379 -m recent --update --seconds 60 --hitcount 20 --name redis -j DROP
# 保存规则
sudo iptables-save > /etc/iptables/rules.v4
```
**UFW 配置**
```shell
# 启用UFW
sudo ufw enable
# 允许SSH
sudo ufw allow ssh
# 允许特定网络访问Redis
sudo ufw allow from 192.168.1.0/24 to any port 6379
# 拒绝其他Redis连接
sudo ufw deny 6379
# 查看规则
sudo ufw status numbered
```
### VPN 访问
**OpenVPN 配置示例**
```shell
# 安装OpenVPN
sudo apt update
sudo apt install openvpn easy-rsa
# 配置VPN服务器
sudo make-cadir /etc/openvpn/easy-rsa
cd /etc/openvpn/easy-rsa
# 初始化PKI
./easyrsa init-pki
./easyrsa build-ca
./easyrsa gen-req server nopass
./easyrsa sign-req server server
./easyrsa gen-dh
# 生成客户端证书
./easyrsa gen-req client1 nopass
./easyrsa sign-req client client1
```
## 数据安全
### 数据加密
**应用层加密**
```python
# Python 数据加密示例
import redis
import json
from cryptography.fernet import Fernet
class EncryptedRedis:
def __init__(self, host='localhost', port=6379, password=None, key=None):
self.redis = redis.Redis(host=host, port=port, password=password)
self.cipher = Fernet(key or Fernet.generate_key())
def set(self, name, value, ex=None):
"""加密存储数据"""
if isinstance(value, dict):
value = json.dumps(value)
encrypted_value = self.cipher.encrypt(value.encode())
return self.redis.set(name, encrypted_value, ex=ex)
def get(self, name):
"""解密获取数据"""
encrypted_value = self.redis.get(name)
if encrypted_value:
decrypted_value = self.cipher.decrypt(encrypted_value)
return decrypted_value.decode()
return None
def hset(self, name, key, value):
"""加密存储哈希字段"""
if isinstance(value, dict):
value = json.dumps(value)
encrypted_value = self.cipher.encrypt(value.encode())
return self.redis.hset(name, key, encrypted_value)
def hget(self, name, key):
"""解密获取哈希字段"""
encrypted_value = self.redis.hget(name, key)
if encrypted_value:
decrypted_value = self.cipher.decrypt(encrypted_value)
return decrypted_value.decode()
return None
# 使用示例
key = Fernet.generate_key()
encrypted_redis = EncryptedRedis(password='your_password', key=key)
# 存储加密数据
user_data = {'name': 'John', 'email': 'john@example.com', 'phone': '123-456-7890'}
encrypted_redis.set('user:1', json.dumps(user_data))
# 获取解密数据
data = encrypted_redis.get('user:1')
user_info = json.loads(data)
print(user_info)
```
### 敏感数据处理
**敏感数据脱敏**
```python
# 数据脱敏工具
import re
import hashlib
class DataMasking:
@staticmethod
def mask_phone(phone):
"""手机号脱敏"""
if len(phone) == 11:
return phone[:3] + '****' + phone[7:]
return phone
@staticmethod
def mask_email(email):
"""邮箱脱敏"""
if '@' in email:
local, domain = email.split('@')
if len(local) > 2:
masked_local = local[0] + '*' * (len(local) - 2) + local[-1]
else:
masked_local = '*' * len(local)
return f"{masked_local}@{domain}"
return email
@staticmethod
def mask_id_card(id_card):
"""身份证脱敏"""
if len(id_card) == 18:
return id_card[:6] + '********' + id_card[14:]
return id_card
@staticmethod
def hash_sensitive_data(data, salt=''):
"""敏感数据哈希"""
return hashlib.sha256((str(data) + salt).encode()).hexdigest()
# 使用示例
masker = DataMasking()
# 存储脱敏数据
user_data = {
'name': 'John Doe',
'phone': masker.mask_phone('13812345678'),
'email': masker.mask_email('john.doe@example.com'),
'id_card': masker.mask_id_card('110101199001011234')
}
redis_client = redis.Redis(password='your_password')
redis_client.hset('user:masked:1', mapping=user_data)
```
### 备份安全
**安全备份策略**
```shell
#!/bin/bash
# 安全备份脚本
BACKUP_DIR="/secure/backup/redis"
ENCRYPTION_KEY="/secure/keys/backup.key"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="redis_backup_$DATE.rdb"
ENCRYPTED_FILE="$BACKUP_FILE.enc"
# 创建备份目录
mkdir -p $BACKUP_DIR
# 生成备份
redis-cli --rdb $BACKUP_DIR/$BACKUP_FILE
# 加密备份文件
openssl enc -aes-256-cbc -salt -in $BACKUP_DIR/$BACKUP_FILE -out $BACKUP_DIR/$ENCRYPTED_FILE -pass file:$ENCRYPTION_KEY
# 删除未加密文件
rm $BACKUP_DIR/$BACKUP_FILE
# 计算校验和
sha256sum $BACKUP_DIR/$ENCRYPTED_FILE > $BACKUP_DIR/$ENCRYPTED_FILE.sha256
# 设置权限
chmod 600 $BACKUP_DIR/$ENCRYPTED_FILE
chown backup:backup $BACKUP_DIR/$ENCRYPTED_FILE
echo "备份完成:$BACKUP_DIR/$ENCRYPTED_FILE"
# 清理旧备份保留30天
find $BACKUP_DIR -name "*.enc" -mtime +30 -delete
find $BACKUP_DIR -name "*.sha256" -mtime +30 -delete
```
**备份恢复脚本**
```shell
#!/bin/bash
# 安全恢复脚本
BACKUP_FILE="$1"
ENCRYPTION_KEY="/secure/keys/backup.key"
TEMP_DIR="/tmp/redis_restore"
if [ -z "$BACKUP_FILE" ]; then
echo "用法: $0 <encrypted_backup_file>"
exit 1
fi
# 验证文件存在
if [ ! -f "$BACKUP_FILE" ]; then
echo "备份文件不存在: $BACKUP_FILE"
exit 1
fi
# 验证校验和
if [ -f "$BACKUP_FILE.sha256" ]; then
echo "验证文件完整性..."
sha256sum -c "$BACKUP_FILE.sha256"
if [ $? -ne 0 ]; then
echo "文件完整性验证失败"
exit 1
fi
fi
# 创建临时目录
mkdir -p $TEMP_DIR
# 解密备份文件
echo "解密备份文件..."
DECRYPTED_FILE="$TEMP_DIR/$(basename $BACKUP_FILE .enc)"
openssl enc -aes-256-cbc -d -in "$BACKUP_FILE" -out "$DECRYPTED_FILE" -pass file:$ENCRYPTION_KEY
if [ $? -eq 0 ]; then
echo "备份文件已解密到: $DECRYPTED_FILE"
echo "请手动将文件复制到Redis数据目录并重启服务"
else
echo "解密失败"
rm -rf $TEMP_DIR
exit 1
fi
```
### 审计日志
**审计日志配置**
```shell
# Redis 配置文件
# /etc/redis/redis.conf
# 启用命令日志
logfile /var/log/redis/redis-server.log
loglevel notice
# 启用慢查询日志
slowlog-log-slower-than 10000
slowlog-max-len 128
# 客户端连接日志
# 通过监控脚本实现
```
**审计日志脚本**
```shell
#!/bin/bash
# Redis 审计日志脚本
LOG_FILE="/var/log/redis/audit.log"
REDIS_LOG="/var/log/redis/redis-server.log"
# 监控Redis连接
tail -f $REDIS_LOG | while read line; do
if echo "$line" | grep -q "Accepted\|Client closed connection"; then
echo "$(date '+%Y-%m-%d %H:%M:%S') $line" >> $LOG_FILE
fi
done &
# 监控命令执行需要启用monitor
redis-cli monitor | while read line; do
# 过滤敏感命令
if echo "$line" | grep -qE "AUTH|CONFIG|EVAL|FLUSHDB|FLUSHALL|SHUTDOWN"; then
echo "$(date '+%Y-%m-%d %H:%M:%S') SENSITIVE: $line" >> $LOG_FILE
fi
done &
echo "审计日志监控已启动"
```
## 安全最佳实践
### 安全配置检查
**安全配置检查清单**
```shell
#!/bin/bash
# Redis 安全配置检查脚本
echo "=== Redis 安全配置检查 ==="
# 检查密码配置
echo "1. 检查密码配置"
if redis-cli CONFIG GET requirepass | grep -q "requirepass"; then
echo "✓ 已配置密码认证"
else
echo "✗ 未配置密码认证"
fi
# 检查绑定地址
echo "2. 检查绑定地址"
BIND_ADDR=$(redis-cli CONFIG GET bind | tail -1)
if [ "$BIND_ADDR" != "" ] && [ "$BIND_ADDR" != "0.0.0.0" ]; then
echo "✓ 绑定地址配置安全: $BIND_ADDR"
else
echo "✗ 绑定地址不安全: $BIND_ADDR"
fi
# 检查保护模式
echo "3. 检查保护模式"
PROTECTED_MODE=$(redis-cli CONFIG GET protected-mode | tail -1)
if [ "$PROTECTED_MODE" = "yes" ]; then
echo "✓ 保护模式已启用"
else
echo "✗ 保护模式未启用"
fi
# 检查危险命令
echo "4. 检查危险命令"
DANGEROUS_COMMANDS=("FLUSHDB" "FLUSHALL" "CONFIG" "EVAL" "SHUTDOWN" "DEBUG")
for cmd in "${DANGEROUS_COMMANDS[@]}"; do
if redis-cli CONFIG GET "rename-command" | grep -q "$cmd"; then
echo "✓ 危险命令 $cmd 已重命名或禁用"
else
echo "✗ 危险命令 $cmd 未处理"
fi
done
# 检查文件权限
echo "5. 检查文件权限"
REDIS_CONF="/etc/redis/redis.conf"
if [ -f "$REDIS_CONF" ]; then
PERM=$(stat -c "%a" "$REDIS_CONF")
if [ "$PERM" = "640" ] || [ "$PERM" = "600" ]; then
echo "✓ 配置文件权限安全: $PERM"
else
echo "✗ 配置文件权限不安全: $PERM"
fi
fi
# 检查日志配置
echo "6. 检查日志配置"
LOGFILE=$(redis-cli CONFIG GET logfile | tail -1)
if [ "$LOGFILE" != "" ]; then
echo "✓ 已配置日志文件: $LOGFILE"
else
echo "✗ 未配置日志文件"
fi
echo "=== 检查完成 ==="
```
### 漏洞防护
**常见漏洞防护措施**
```shell
# 1. 禁用或重命名危险命令
# /etc/redis/redis.conf
rename-command FLUSHDB ""
rename-command FLUSHALL ""
rename-command CONFIG "CONFIG_b840fc02d524045429941cc15f59e41cb7be6c52"
rename-command EVAL ""
rename-command DEBUG ""
rename-command SHUTDOWN "SHUTDOWN_b840fc02d524045429941cc15f59e41cb7be6c52"
# 2. 限制客户端连接
maxclients 1000
timeout 300
tcp-keepalive 300
# 3. 禁用Lua脚本调试
lua-replicate-commands yes
# 4. 设置内存限制
maxmemory 2gb
maxmemory-policy allkeys-lru
```
### 安全更新
**安全更新策略**
```shell
#!/bin/bash
# Redis 安全更新脚本
echo "检查Redis版本和安全更新"
# 获取当前版本
CURRENT_VERSION=$(redis-server --version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
echo "当前Redis版本: $CURRENT_VERSION"
# 检查是否有安全更新
echo "检查安全公告..."
echo "请访问以下链接查看最新安全公告:"
echo "- https://redis.io/topics/security"
echo "- https://github.com/redis/redis/security/advisories"
# 备份当前配置
echo "备份当前配置..."
cp /etc/redis/redis.conf /etc/redis/redis.conf.backup.$(date +%Y%m%d)
# 更新前检查
echo "更新前安全检查:"
redis-cli CONFIG GET '*' > /tmp/redis_config_before_update.txt
echo "请手动执行以下步骤:"
echo "1. 下载最新稳定版本"
echo "2. 测试环境验证"
echo "3. 制定回滚计划"
echo "4. 执行更新"
echo "5. 验证功能和安全配置"
```
### 应急响应
**安全事件应急响应**
```shell
#!/bin/bash
# Redis 安全事件应急响应脚本
INCIDENT_LOG="/var/log/redis/security_incident.log"
DATE=$(date '+%Y-%m-%d %H:%M:%S')
echo "=== Redis 安全事件应急响应 ==="
echo "事件时间: $DATE" | tee -a $INCIDENT_LOG
# 1. 立即隔离
echo "1. 立即隔离Redis服务"
echo "停止Redis服务? (y/n)"
read -r response
if [ "$response" = "y" ]; then
sudo systemctl stop redis
echo "$DATE: Redis服务已停止" | tee -a $INCIDENT_LOG
fi
# 2. 收集证据
echo "2. 收集安全证据"
EVIDENCE_DIR="/tmp/redis_incident_$(date +%Y%m%d_%H%M%S)"
mkdir -p $EVIDENCE_DIR
# 收集日志
cp /var/log/redis/* $EVIDENCE_DIR/ 2>/dev/null
# 收集配置
cp /etc/redis/redis.conf $EVIDENCE_DIR/
# 收集进程信息
ps aux | grep redis > $EVIDENCE_DIR/processes.txt
# 收集网络连接
netstat -tulpn | grep :6379 > $EVIDENCE_DIR/connections.txt
# 收集系统信息
uname -a > $EVIDENCE_DIR/system_info.txt
whoami > $EVIDENCE_DIR/current_user.txt
echo "证据已收集到: $EVIDENCE_DIR"
echo "$DATE: 证据收集完成 - $EVIDENCE_DIR" | tee -a $INCIDENT_LOG
# 3. 分析威胁
echo "3. 分析潜在威胁"
echo "检查可疑连接..."
redis-cli CLIENT LIST > $EVIDENCE_DIR/client_list.txt 2>/dev/null
echo "检查慢查询日志..."
redis-cli SLOWLOG GET 100 > $EVIDENCE_DIR/slowlog.txt 2>/dev/null
# 4. 修复建议
echo "4. 安全修复建议:"
echo "- 更改Redis密码"
echo "- 检查ACL配置"
echo "- 更新防火墙规则"
echo "- 检查系统用户账户"
echo "- 扫描恶意软件"
echo "- 更新Redis到最新版本"
echo "应急响应完成,详细日志: $INCIDENT_LOG"
```