Redis 认证密码设置 – wiki基地


Redis 认证密码设置:保障数据安全的基石

引言

Redis 是一款高性能的开源内存数据库,常被用作缓存、消息队列或数据库。它的速度得益于数据存储在内存中,但这也带来了潜在的安全风险。默认情况下,Redis 的设计哲学是假设运行在一个受信任的环境中,例如通过防火墙保护的内部网络。然而,在现实世界中,将 Redis 暴露在不可信的网络环境(如公共互联网)中而不采取任何安全措施,无异于将数据门户大开。

最基本也是最重要的 Redis 安全措施之一就是设置认证密码。通过设置密码,可以确保只有知道密码的客户端才能连接并操作 Redis 实例,从而阻止未经授权的访问。本文将详细介绍如何在 Redis 中设置和使用认证密码,包括其工作原理、配置步骤、最佳实践以及相关的安全考虑。我们将深入探讨 requirepass 配置指令,并触及更高级的安全性话题,如 ACLs(访问控制列表)和 TLS/SSL 加密,以提供一个全面的安全视野。

理解 Redis 的默认安全状态与风险

在深入探讨密码设置之前,理解 Redis 的默认安全状态及其带来的风险至关重要。

默认绑定地址

早期的 Redis 版本默认绑定到所有网络接口 (bind 0.0.0.0),这意味着如果服务器有公网 IP,那么 Redis 实例就可能直接暴露在互联网上。较新的 Redis 版本(通常是 3.2 版本及以后)为了提高安全性,默认绑定到本地环回地址 (bind 127.0.0.1bind ::1)。

  • bind 127.0.0.1 (或 ::1): 这是更安全的默认设置。在这种情况下,Redis 实例只能被运行在同一台服务器上的客户端访问。外部机器无法直接连接到 Redis。这大大降低了风险,但如果你的应用运行在另一台服务器上需要访问 Redis,这种设置将无法满足需求,你需要更改绑定地址并采取其他安全措施。
  • bind 0.0.0.0: 这种设置允许 Redis 接受来自任何网络接口的连接。如果你想让局域网内的其他机器或外部应用访问 Redis,你需要使用这种设置。但正如前文所述,如果服务器有公网 IP 且没有防火墙保护,或者防火墙配置不当,那么 Redis 实例就完全暴露了。

未经认证的访问风险

如果 Redis 实例绑定到非本地地址且未设置密码,那么任何能够到达 Redis 端口(默认为 6379)的客户端都可以执行任意命令,包括:

  • 读取敏感数据: KEYS * 可以列出所有键,然后可以使用 GET, HGETALL, LRANGE, SMEMBERS, ZSCAN 等命令读取存储的所有数据。
  • 修改或删除数据: SET, DEL, FLUSHALL, FLUSHDB 等命令可以轻松地修改或擦除整个数据库。
  • 植入恶意数据: 攻击者可以写入恶意数据,用于后续攻击或破坏应用。
  • 利用 Redis 作为跳板: 攻击者可能利用 Redis 的一些特性(如 MASTEROFSLAVEOF)将你的 Redis 实例连接到攻击者的 Redis 实例,窃取数据。更危险的是,如果 Redis 以 root 权限运行(强烈不推荐)且具有某些模块加载能力,攻击者可能通过写入.so文件然后加载模块的方式实现远程代码执行(RCE)。即使没有 root 权限,如果 Redis 能够写入文件系统,攻击者也可能通过 CONFIG SET dirCONFIG SET dbfilename 将恶意脚本写入到服务器上,然后通过其他方式触发执行(例如写入 SSH 公钥以获得 SSH 访问)。
  • DDoS 攻击的放大器: 在旧版本或特定配置下,Redis 可能被利用进行 DDoS 放大攻击。

这些风险都强调了设置认证密码的重要性。它是防止未经授权访问的第一道防线。

requirepass 指令:设置认证密码的基础

Redis 提供了一个简单的指令 requirepass 来设置认证密码。当设置了 requirepass 后,客户端连接到 Redis 后,在执行大多数命令之前,必须先使用 AUTH <password> 命令进行认证。

requirepass 的工作原理

  1. 服务器端配置: 在 Redis 的配置文件 redis.conf 中设置 requirepass 指令,指定一个密码。
  2. 客户端连接: 客户端使用标准的 Redis 协议连接到 Redis 服务器。
  3. 认证要求: Redis 服务器接收到客户端连接后,会记住这个连接需要认证。
  4. 命令执行: 客户端发送命令(例如 PING, GET mykey)。
  5. 认证检查: Redis 在处理命令之前,会检查当前连接是否已经通过认证。如果未认证且客户端发送的不是 AUTHHELLO (Redis 6.0+) 命令,服务器将返回一个错误,通常是 (error) NOAUTH Authentication required.(error) DENIED (取决于版本和具体错误)。
  6. 认证命令: 客户端必须发送 AUTH <password> 命令,其中 <password> 是在服务器端配置的密码。
  7. 密码验证: Redis 服务器验证客户端提供的密码是否与 requirepass 设置的密码一致。
  8. 认证成功: 如果密码正确,服务器返回 OK,并将该连接标记为已认证。此后,该客户端可以在此连接上执行任何允许的命令。
  9. 认证失败: 如果密码不正确,服务器返回错误,通常是 (error) invalid password。连接保持未认证状态,客户端需要再次尝试 AUTH 命令或断开连接。

需要注意的是,AUTH 命令必须在执行其他命令之前发送。在 Redis 6.0 及更高版本中,HELLO 命令也可以用于同时进行认证和协议握手。

如何设置 requirepass

设置 Redis 认证密码主要通过修改 Redis 的配置文件 redis.conf 来完成。

步骤 1: 找到 Redis 配置文件

redis.conf 文件的位置取决于你的 Redis 安装方式和操作系统。常见的位置包括:

  • 使用包管理器安装 (如 apt, yum, brew):/etc/redis/redis.conf (Linux), /usr/local/etc/redis.conf (macOS 使用 Homebrew)
  • 从源码编译安装:通常在源码目录的顶层。
  • 通过 Docker 运行:配置文件通常需要通过数据卷挂载到容器内部。

如果你不确定配置文件在哪里,可以通过以下方法查找:

  1. 查找正在运行的 Redis 进程:ps aux | grep redis-server。查看进程启动命令中是否有指定配置文件的路径(例如 redis-server /path/to/redis.conf)。
  2. 如果 Redis 正在运行且允许未经认证的访问(临时或在安全环境中),可以通过 redis-cli 连接,然后执行 CONFIG GET dirCONFIG GET dbfilename 来查找 Redis 持久化文件的位置,配置文件可能就在附近。或者直接尝试 CONFIG GET requirepass (如果没设置,会返回空)。在某些情况下,CONFIG GET * 可以显示所有配置项,包括配置文件的路径。
  3. 如果 Redis 尚未运行,可以尝试使用 find / -name redis.conf 命令在整个文件系统中搜索(可能需要 root 权限)。

步骤 2: 编辑 redis.conf 文件

使用一个文本编辑器打开 redis.conf 文件。常见的编辑器有 nano, vim, emacs 等。

“`bash

例如,使用 nano 编辑器

sudo nano /etc/redis/redis.conf
“`

在配置文件中查找 requirepass 指令。默认情况下,这一行通常被注释掉了(以 # 开头),并且可能包含示例密码 foobared

“`ini

requirepass foobared

“`

步骤 3: 取消注释并设置密码

取消 requirepass 行的注释(删除开头的 #),并将默认的示例密码 foobared 替换为你选择的强密码。

ini
requirepass your_very_strong_and_secret_password_123!

重要提示: 选择一个强密码至关重要。一个强密码应该:
* 长度足够长(建议至少 12 个字符,最好更长)。
* 包含大小写字母、数字和符号。
* 不包含容易被猜测的信息,如用户名、生日、常用词汇等。
* 不与其他服务的密码重复。

将密码明文存储在 redis.conf 文件中是 Redis 设置密码的标准方式。因此,务必确保 redis.conf 文件的访问权限得到严格控制,只有 Redis 运行用户和系统管理员才能读取。

步骤 4: 保存并关闭配置文件

根据你使用的编辑器,保存对文件的修改。例如,在 nano 中按 Ctrl+X,然后按 Y 确认保存,最后按 Enter 确认文件名。

步骤 5: 重启或重新加载 Redis 服务器

为了使配置文件的更改生效,你需要重启 Redis 服务器。

使用 systemd (现代 Linux 发行版):

bash
sudo systemctl restart redis

使用 SysVinit (较旧的 Linux 发行版):

bash
sudo service redis restart

如果你是通过其他方式运行 Redis (例如直接执行二进制文件),你需要停止当前的 Redis 进程并重新启动它。

注意: 在某些情况下,Redis 支持通过 CONFIG REWRITE 命令将当前运行配置写回配置文件,并通过 CONFIG SET 命令临时修改某些配置。然而,requirepass 通常是一个需要重启才能完全生效的关键安全配置项,尤其是在从无密码到有密码的转变时。推荐使用重启的方式来确保密码设置生效。

步骤 6: 验证密码设置是否成功

重启后,尝试使用 redis-cli 连接到 Redis 服务器进行验证。

  1. 不带密码连接:
    bash
    redis-cli

    连接后,尝试执行一个命令,例如 PING
    bash
    PING

    如果密码设置成功,你会收到类似 (error) NOAUTH Authentication required.(error) DENIED 的错误。

  2. 带密码连接并认证:

    • 方法 A (推荐): 在 redis-cli 启动时直接提供密码
      bash
      redis-cli -a your_very_strong_and_secret_password_123!

      连接后,尝试执行 PING 命令。
      bash
      PING

      如果认证成功,你会收到 PONG 回复。

    • 方法 B: 连接后再使用 AUTH 命令认证
      bash
      redis-cli

      连接后,立即执行 AUTH 命令:
      bash
      AUTH your_very_strong_and_secret_password_123!

      如果密码正确,服务器返回 OK
      现在你可以执行其他命令了,例如 PING
      bash
      PING

      你会收到 PONG 回复。
      如果密码错误,会返回 (error) invalid password

如果验证成功,恭喜你,你已经为 Redis 实例设置了认证密码!

客户端如何使用密码进行认证

设置了服务器端的 requirepass 后,所有需要连接此 Redis 实例的应用客户端也必须进行相应的修改,以提供正确的密码。不同的客户端库有不同的认证方式,但核心思想都是在建立连接后或连接过程中发送 AUTH 命令。

以下是几种常见编程语言客户端的认证示例:

Python (使用 redis-py 库)

“`python
import redis

直接在连接时指定密码

如果 Redis 运行在本地默认端口

r = redis.Redis(host=’localhost’, port=6379, password=’your_very_strong_and_secret_password_123!’)

可以在创建连接池时指定密码

pool = redis.ConnectionPool(host=’localhost’, port=6379, password=’your_very_strong_and_secret_password_123!’, db=0)
r = redis.Redis(connection_pool=pool)

测试连接

try:
r.ping()
print(“Successfully connected to Redis with authentication.”)
# 执行其他 Redis 命令
r.set(‘mykey’, ‘myvalue’)
value = r.get(‘mykey’)
print(f”Value of mykey: {value}”)
except redis.exceptions.AuthenticationError:
print(“Redis authentication failed. Invalid password.”)
except redis.exceptions.ConnectionError as e:
print(f”Could not connect to Redis: {e}”)

“`

Node.js (使用 ioredis 库)

“`javascript
const Redis = require(‘ioredis’);

// 在创建 Redis 客户端实例时指定密码
const redis = new Redis({
port: 6379, // Redis port
host: ‘localhost’, // Redis host
password: ‘your_very_strong_and_secret_password_123!’, // Password
db: 0, // Redis database index
});

// 监听连接和错误事件
redis.on(‘connect’, () => {
console.log(‘Successfully connected to Redis with authentication.’);
// 执行其他 Redis 命令
redis.set(‘mykey’, ‘myvalue’, (err, result) => {
if (err) {
console.error(‘Error setting key:’, err);
} else {
console.log(‘Set key mykey:’, result);
redis.get(‘mykey’, (err, value) => {
if (err) {
console.error(‘Error getting key:’, err);
} else {
console.log(‘Value of mykey:’, value);
redis.quit(); // Close the connection when done
}
});
}
});
});

redis.on(‘error’, (err) => {
console.error(‘Redis connection error:’, err);
if (err.message.includes(‘Authentication required’) || err.message.includes(‘invalid password’)) {
console.error(‘Redis authentication failed. Check your password.’);
}
});
“`

Java (使用 Jedis 库)

“`java
import redis.clients.jedis.Jedis;
import redis.clients.jedis.exceptions.JedisConnectionException;
import redis.clients.jedis.exceptions.JedisAuthenticationException;

public class RedisAuthExample {
public static void main(String[] args) {
Jedis jedis = null;
try {
// 连接到 Redis 服务器
jedis = new Jedis(“localhost”, 6379);

        // 进行认证
        // 在连接后立即调用 auth() 方法
        jedis.auth("your_very_strong_and_secret_password_123!");

        // 测试连接
        String pong = jedis.ping();
        System.out.println("Successfully connected to Redis with authentication. PING result: " + pong);

        // 执行其他 Redis 命令
        jedis.set("mykey", "myvalue");
        String value = jedis.get("mykey");
        System.out.println("Value of mykey: " + value);

    } catch (JedisAuthenticationException e) {
        System.err.println("Redis authentication failed. Invalid password.");
        e.printStackTrace();
    } catch (JedisConnectionException e) {
        System.err.println("Could not connect to Redis:");
        e.printStackTrace();
    } finally {
        // 关闭连接
        if (jedis != null) {
            jedis.close();
        }
    }
}

}
“`

重要提示:

  • 不要将密码硬编码在源代码中: 将密码直接写在代码里是安全大忌。应该将密码存储在安全的地方,例如环境变量、配置文件、密钥管理服务(如 HashiCorp Vault, AWS Secrets Manager, Azure Key Vault)等,并在应用启动时读取。
  • 确保配置文件/环境变量的安全: 存储密码的配置文件或环境变量也需要严格的安全控制,防止泄露。

requirepass 的局限性与高级安全性

requirepass 提供了一个基本的、全局的认证机制。一旦客户端通过密码认证,它就可以执行所有允许的 Redis 命令(除非使用了 rename-command 禁用了某些命令)。这意味着 requirepass 提供的是all-or-nothing的权限控制。这对于简单的应用场景可能足够,但在以下情况下可能不够灵活或安全:

  1. 多个应用/用户访问同一个 Redis 实例: 如果你有多个应用或用户需要访问同一个 Redis 实例,但它们需要不同的访问权限(例如,一个应用只能读,另一个应用可以读写,某个用户只能访问特定前缀的键),requirepass 无法满足这种细粒度的权限控制需求。
  2. 需要区分只读和读写客户端: 你可能希望某些客户端只能读取数据,而其他客户端可以修改数据。requirepass 无法区分这一点。
  3. 密码在网络上传输: 使用 requirepass 时,认证密码是以明文形式在客户端和服务器之间传输的。尽管认证成功后后续数据传输没有密码,但首次认证时密码会暴露。在不可信的网络环境中,这可能带来风险,可以通过网络抓包获取密码。

为了解决这些局限性,Redis 提供了更高级的安全特性:访问控制列表 (ACLs)TLS/SSL 加密

ACLs (Access Control Lists)

从 Redis 6.0 版本开始引入的 ACLs 提供了比 requirepass 更强大、更灵活的访问控制机制。ACLs 允许你创建不同的用户,为每个用户设置独立的密码或根本不使用密码(通过其他方式认证,如客户端证书),并精确控制每个用户可以执行哪些命令以及可以访问哪些键、发布/订阅频道。

使用 ACLs,你可以:

  • 创建多个用户,每个用户有自己的用户名和密码。
  • 为每个用户定义允许执行的命令集合(例如,只读命令,或只允许执行特定命令)。
  • 为每个用户定义允许访问的键的模式(例如,只能访问以 user: 开头的键)。
  • 为每个用户定义允许发布或订阅的频道。
  • 禁用 requirepass,完全依赖 ACLs 进行认证和授权。

ACLs 如何取代 requirepass:

当你开始使用 ACLs 并创建第一个用户时(除了默认用户),requirepass 指令通常会被忽略。认证将通过 AUTH <username> <password> 命令完成。

示例 (在 redis-cli 中设置 ACLs):

“`bash

1. 启用 ACL 日志记录 (可选但推荐)

CONFIG SET acllog-max-len 128 # 将未认证/未授权尝试记录到 ACL 日志

2. 创建一个新用户 ‘myuser’,设置密码 ‘mypassword’,只允许执行 ‘get’, ‘set’, ‘del’ 命令,并只能操作以 ‘user:’ 开头的键。

语法: ACL SETUSER [<规则> …]

规则:

on/off: 启用/禁用用户

>password: 设置密码 (使用 ‘>’ 前缀表示明文密码,也可以使用 ‘#’ 加密密码或 ‘!‘ 指定哈希)

~: 允许访问匹配模式的键

+/-: 允许/禁止执行指定命令 (+@ 允许某个类别的命令,如 +@read)

&: 允许访问匹配模式的 pub/sub 频道

ACL SETUSER myuser >mypassword +get +set +del ~user:* on

3. 保存 ACL 配置到 aclfile (推荐配置 aclfile)

首先在 redis.conf 中设置 aclfile /path/to/acl.conf

然后执行:

ACL SAVE

4. 客户端连接时使用新用户和密码认证

redis-cli -u myuser -a mypassword

或者在连接后:

AUTH myuser mypassword

注意: 默认用户 (没有用户名,只有 requirepass 密码) 仍然存在,可以使用 AUTH 认证,但最好禁用或删除 requirepass 并完全迁移到带用户名的 ACLs。

可以禁用默认用户: ACL SETUSER default off

“`

ACLs 提供了更精细的安全控制,对于复杂的部署环境,强烈建议使用 ACLs 替代或结合 requirepass

TLS/SSL 加密

requirepass 解决了认证问题,但密码和数据在网络上仍然是明文传输的(除非你运行在完全信任的内部网络)。为了保护数据在传输过程中的安全,可以使用 TLS/SSL 加密。

  • Stunnel: 对于 Redis 6.0 之前的版本,可以通过 Stunnel 等工具在 Redis 外部添加一层 TLS 加密代理。客户端连接到 Stunnel 监听的加密端口,Stunnel 解密后转发给 Redis 监听的非加密端口,Redis 的响应则反向通过 Stunnel 加密后返回给客户端。
  • Native TLS: Redis 6.0 及以上版本原生支持 TLS。你可以在 redis.conf 中配置证书、私钥和 CA 证书,启用 TLS 端口。客户端需要使用支持 TLS 的客户端库连接到 TLS 端口。

通过 TLS 加密,即使数据包被截获,攻击者也无法轻易读取其中的内容,包括认证密码。

密码管理的最佳实践

设置 requirepass 只是第一步,有效的密码管理同样重要。

  1. 选择强密码: 再次强调,密码必须复杂且难以猜测。
  2. 定期轮换密码: 像其他重要服务的密码一样,考虑定期(例如每隔几个月)更改 Redis 密码。
  3. 安全存储密码:
    • 服务器上的 redis.conf 文件必须具有严格的权限,只有 Redis 运行用户和必要的管理员才能读取。
    • 客户端应用不应将密码硬编码在代码中。使用环境变量、独立的配置文件或密钥管理系统来存储和加载密码。
    • 如果使用配置文件存储密码,确保这些配置文件不被意外地提交到版本控制系统(如 Git),并对文件权限进行限制。
  4. 限制访问 Redis 端口: 即使设置了密码,也应该使用防火墙(如 iptables, ufw, 安全组)限制只有需要访问 Redis 的特定 IP 地址或 IP 范围才能连接到 Redis 端口。这是纵深防御的关键一环。
  5. 绑定到特定 IP: 如果可能,使用 bind 指令将 Redis 绑定到内部网络接口的 IP 地址,而不是 0.0.0.0。结合防火墙,这可以进一步减少攻击面。
  6. 监控认证失败尝试: 配置日志系统监控 Redis 的认证失败尝试。频繁的失败尝试可能表明有人试图进行暴力破解,可以触发告警或自动采取措施(如暂时阻止来源 IP)。
  7. 考虑 ACLs: 如果需要更细粒度的权限控制,尽快迁移到 ACLs。ACLs 的密码管理也更灵活,可以为不同用户设置不同密码,甚至结合其他认证方式。

总结

为 Redis 实例设置认证密码 (requirepass) 是保障数据安全最基本、最关键的步骤之一。它通过要求客户端在执行操作前进行身份验证,有效地防止了未经授权的访问。本文详细介绍了 requirepass 的工作原理、如何通过修改 redis.conf 配置文件进行设置,以及如何在不同的客户端应用中进行认证。

虽然 requirepass 提供了一个全局的认证机制,但它存在一些局限性,如无法实现细粒度的权限控制和密码明文传输的潜在风险。为了构建更健壮的 Redis 安全体系,建议结合其他安全措施:

  • 绑定到特定 IP 地址 (bind):限制 Redis 监听的网络接口。
  • 配置防火墙: 限制只有可信来源的 IP 才能访问 Redis 端口。
  • 使用 ACLs (Redis 6.0+): 实现用户级的细粒度权限控制,包括命令、键和频道限制。
  • 启用 TLS/SSL 加密: 保护数据和密码在传输过程中的安全。
  • 安全地管理密码: 选择强密码,定期轮换,并使用安全的方式存储和分发密码,避免硬编码。
  • 监控和审计: 记录和审查访问日志,特别是认证失败的尝试。

总之,设置 requirepass 是 Redis 安全的起点。对于任何非本地或暴露在不可信网络环境中的 Redis 实例,都必须设置认证密码。同时,理解并应用更高级的安全特性和最佳实践,才能为你的 Redis 数据提供全面的保护。安全是一个持续的过程,需要结合实际应用场景和风险评估,采取多层次、立体化的防护措施。


发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部