CLOVER🍀

That was when it all began.

RedisのACLを試す

これは、なにをしたくて書いたもの?

Redisの認証設定ですが、パスワードを設定できることは知っていたのですが、Redis 6.0以降ではACLが使えるようになったことを
知らなかったので試してみようかなと。

Redisのセキュリティ

Redisのセキュリティに関するページはこちら。

Redis security | Redis

主に以下のことが書かれています。

  • ネットワークセキュリティ
    • Redisをバインドするネットワークインターフェースを制限する
  • プロテクションモード
    • Redis 3.2.0以降、すべてのネットワークインターフェースにバインドするデフォルト設定でRedisを起動すると、ローカル以外からのアクセスにはエラー応答する
  • 認証
    • Redis 6.0以降、ACL(アクセスコントロールリスト)が使用可能になり、細かく権限制御が可能に
    • パスワードのみで認証するrequirepass
  • TLS
  • 特定のコマンドの禁止や推測しにくい名前へのリネーム
  • 悪意のある入力に対処するための、実行ごとにハッシュ関数に疑似ランダムシードを使う
  • 文字列エスケープとNoSQLインジェクション(基本的に不可能)

今回は、このうち認証、というかACLに焦点を当てていきたいと思います。

ACLの概要

RedisのACLに関するページはこちら。

ACL | Redis

RedisのACL(Access Control List)は、実行できるコマンドとアクセスできるキーに対して特定の接続を制限できる機能です。

The Redis ACL, short for Access Control List, is the feature that allows certain connections to be limited in terms of the commands that can be executed and the keys that can be accessed.

Redisに接続した後にユーザー名とパスワードを指定する必要があるようです。

The way it works is that, after connecting, a client is required to provide a username and a valid password to authenticate.

認証が成功すると、その接続は特定のユーザーとその制限に関連付けられます。

If authentication succeeded, the connection is associated with a given user and the limits the user has.

Redisは新しい接続を「デフォルトのユーザー」として認証できるように設定でき、デフォルトユーザーを設定すると明示的に認証されていないユーザーには機能のサブセットを提供することもできます。これは副作用とされているようですが。

Redis can be configured so that new connections are already authenticated with a "default" user (this is the default configuration). Configuring the default user has, as a side effect, the ability to provide only a specific subset of functionalities to connections that are not explicitly authenticated.

デフォルトでは下位互換性のため、新しい接続はすべてのコマンドを実行でき、すべてのキーにアクセス可能です。

In the default configuration, Redis 6 (the first version to have ACLs) works exactly like older versions of Redis. Every new connection is capable of calling every possible command and accessing every key, so the ACL feature is backward compatible with old clients and applications.

requirepassでパスワードを設定する方法は従来の古い方法とされ、これはデフォルトユーザーのパスワードとして振る舞うようです。

Also the old way to configure a password, using the requirepass configuration directive, still works as expected. However, it now sets a password for the default user.

認証

Redisnの認証にはAUTHコマンドを使います。

AUTH | Redis

形式としてはユーザー名とパスワードを指定するのですが、

AUTH <username> <password>

古い形式ではパスワードのみとなります。

AUTH <password>

これは、デフォルトユーザーに対する認証を意味します。

ACLの設定方法

ACLの設定方法は、以下の3つがあります。

userディレクティブを使う場合、ACL SETUSERコマンドで指定する内容を記述することになるようです。またuserディレクティブと
aclfileディレクティブはどちらか片方のみが使用可能のようです。

ユーザーが少ないなど単純な場合はuserディレクティブを使い、複数のユーザーを使うなど複雑な場合はaclfileディレクティブを使うことに
なりそうです。

ここからは、実際のRedisを使いつつ設定方法や動作を見ていこうと思います。

環境

今回の環境は、こちら。

$ bin/redis-server --version
Redis server v=7.2.1 sha=00000000:0 malloc=jemalloc-5.3.0 bits=64 build=81a2b5148e5873e4

Redis 7.2.1でサーバーとクライアントを異なるホストで用意します。サーバー側は172.17.0.2とします。

OSはUbuntu Linux 22.04 LTSです。

$ cat /etc/os-release
PRETTY_NAME="Ubuntu 22.04.3 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.3 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=jammy


$ uname -srvmpio
Linux 5.15.0-83-generic #92-Ubuntu SMP Mon Aug 14 09:30:42 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux

ACLの記法

ACLの記法は、以下に書かれています。

ACL / ACL rules

以下の記述ができるようです。

  • ユーザーの有効化/無効化
    • on、off
  • コマンドの許可または禁止
    • コマンド単位の許可(+<command>)、コマンド単位の禁止(-<command>)
    • カテゴリー単位でのコマンドの許可(+@<category>)、カテゴリー単位でのコマンドの禁止(-@<category>)
      • カテゴリーは@admin、@set、@sortedsetなどがあり、ACL CATで確認可能
      • @allは特殊なカテゴリーで、現在およびモジュールを通して将来ロードされるすべてのコマンドが含まれる
    • 禁止されているコマンドの特定の最初の引数の許可(+<command>|first-arg)
    • allcommands(+@allのエイリアス)
    • nocommands(-@allのエイリアス)
  • キーに対する許可または禁止
    • コマンドの一部として指定できるキーのパターン(~<pattern>)
    • 指定されたキーのパターンに対して読み込みを許可する(%R~<pattern>)
    • 指定されたキーのパターンに対して書き込みを許可する(%W~<pattern>)
    • allkeys(~*のエイリアス)
    • 許可されたキーパターンのリストをフラッシュする(resetkeys)
  • Pub/Subチャンネルに対する許可または禁止
    • ユーザーがアクセスできるPub/Subチャンネルをglobスタイルのパターンで追加(&<pattern>)
    • allchannels(ユーザーがすべてのPub/Subチャンネルにアクセスできるようにするエイリアス)
    • 許可されたチャンネルパターンのリストをフラッシュする(resetchannels)
  • ユーザーのパスワードを指定
    • 有効なパスワードを指定(><password>)
    • 有効なパスワードをのリストから削除する(<<password>)
    • SHA-256ハッシュ値を有効なパスワードとして指定(#<hash>)
    • 指定されたSHA-256ハッシュ値を有効なパスワードのリストから削除する(!<hash>)
    • ユーザーに設定されているすべてのパスワードが削除され、対象のユーザーはパスワード不要として扱われる(nopass)
    • 許可されたパスワードのリストをフラッシュする(resetpass)
  • ユーザーのセレクターを構成
  • ユーザーをリセット

ちょっと情報量が多すぎてよくわかりませんね…。具体的な例を見ていった方が良さそうです。

例としてはこんな感じみたいです。

ACL SETUSER antirez on +@all -@dangerous >42a979... ~*

また、以下を見ると

> ACL LIST
1) "user default on nopass ~* &* +@all"

最初はユーザー名で、その後にACL(ルール)が続くことになります。

The first two words in each line are "user" followed by the username. The next words are ACL rules that describe different things.

ACL / Configure ACLs with the ACL command

また、ACLは左から右に処理されるようです。設定ファイルに例が書かれています。

#
# Basically ACL rules are processed left-to-right.
#

https://raw.githubusercontent.com/redis/redis/7.2/redis.conf

たとえば、以下の例だとユーザーaliceは、DEBUGを除くすべてのコマンドを使用できることになります。

#   user alice on +@all -DEBUG ~* >somepassword
#
# This will allow "alice" to use all the commands with the exception of the
# DEBUG command, since +@all added all the commands to the set of the commands
# alice can use, and later DEBUG was removed. 

この順番を入れ替えると、-DEBUGがその後の+@allに打ち消されるのですべてのコマンドが使えることになります。

# However if we invert the order of two ACL rules the result will be different:
#
#   user alice on -DEBUG +@all ~* >somepassword
#
# Now DEBUG was removed when alice had yet no commands in the set of allowed
# commands, later all the commands are added, so the user will be able to
# execute everything.

試してみる

ここからは、実際に試してみましょう。

Redisサーバーを起動します。

$ bin/redis-server

他のホストからCLIから接続。

$ bin/redis-cli -h 172.17.0.2
172.17.0.2:6379>

外部から接続して、かつ認証していないのでなにもできません。プロテクションモードですね。

172.17.0.2:6379> set key1 value1
(error) DENIED Redis is running in protected mode because protected mode is enabled and no password is set for the default user. In this mode connections are only accepted from the loopback interface. If you want to connect from external computers to Redis you may adopt one of the following solutions: 1) Just disable protected mode sending the command 'CONFIG SET protected-mode no' from the loopback interface by connecting to Redis from the same host the server is running, however MAKE SURE Redis is not publicly accessible from internet if you do so. Use CONFIG REWRITE to make this change permanent. 2) Alternatively you can just disable the protected mode by editing the Redis configuration file, and setting the protected mode option to 'no', and then restarting the server. 3) If you started the server manually just for testing, restart it with the '--protected-mode no' option. 4) Set up an authentication password for the default user. NOTE: You only need to do one of the above things in order for the server to start accepting connections from the outside.

このままだと操作できないので、1度サーバー側のローカルで接続。

$ bin/redis-cli
127.0.0.1:6379>

ACL LISTで現在のユーザーを確認できます。

127.0.0.1:6379> acl list
1) "user default on nopass sanitize-payload ~* &* +@all"

ACL LIST | Redis

デフォルトユーザーのみがいますね。有効なユーザーですべてのキー、チャンネルにアクセスでき、すべてのコマンドが使えますが
パスワードが設定されていません。

ユーザーを追加してみましょう。ACL SETUSERで追加します。

管理ユーザー的なものを追加。

127.0.0.1:6379> acl setuser redis-admin on >admin-password ~* &* +@all
OK

ユーザー名はredis-admin、パスワードはadmin-passwordです。

確認。

127.0.0.1:6379> acl list
1) "user default on nopass sanitize-payload ~* &* +@all"
2) "user redis-admin on sanitize-payload #8e70fdbd0400b7a21539fd15fb4ab86c129f7cbd99261dbb0d95c18df8dec177 ~* &* +@all"

別のサーバーから接続してみましょう。

172.17.0.2:6379> auth redis-admin admin-password
(error) DENIED Redis is running in protected mode because protected mode is enabled and no password is set for the default user. In this mode connections are only accepted from the loopback interface. If you want to connect from external computers to Redis you may adopt one of the following solutions: 1) Just disable protected mode sending the command 'CONFIG SET protected-mode no' from the loopback interface by connecting to Redis from the same host the server is running, however MAKE SURE Redis is not publicly accessible from internet if you do so. Use CONFIG REWRITE to make this change permanent. 2) Alternatively you can just disable the protected mode by editing the Redis configuration file, and setting the protected mode option to 'no', and then restarting the server. 3) If you started the server manually just for testing, restart it with the '--protected-mode no' option. 4) Set up an authentication password for the default user. NOTE: You only need to do one of the above things in order for the server to start accepting connections from the outside.

なんと怒られてしまいました。

メッセージを読むと、プロテクションモードを無効化するか、デフォルトユーザーのパスワードを設定するように言われています。

では、デフォルトユーザーのパスワードを設定してみます。

127.0.0.1:6379> acl setuser default >default-password ~* &* +@all
OK

今度は、別サーバーからログインできるようになりました。

172.17.0.2:6379> auth redis-admin admin-password
OK

パスワードを誤ると、こうなりました。

172.17.0.2:6379> auth redis-admin wrong
(error) WRONGPASS invalid username-password pair or user is disabled.

もう少し試してみましょう。

読み書きできるユーザー、読み取り専用のユーザーを追加。

127.0.0.1:6379> acl setuser read-write-user on >password +@read +@write ~*
OK


127.0.0.1:6379> acl setuser read-only-user on >password +@read ~*
OK

Pub/Subチャンネルは今回は外しました。

確認。

127.0.0.1:6379> acl list
1) "user default on sanitize-payload #dd9038e72e23e8c6375f050b606ac31ee596443015d385dc8f25f15516464919 ~* &* +@all"
2) "user read-only-user on sanitize-payload #5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8 ~* &* -@all +@read"
3) "user read-write-user on sanitize-payload #5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8 ~* &* -@all +@read +@write"
4) "user redis-admin on sanitize-payload #8e70fdbd0400b7a21539fd15fb4ab86c129f7cbd99261dbb0d95c18df8dec177 ~* &* +@all"

他のサーバーから接続して、確認してみましょう。

読み書き。

172.17.0.2:6379> auth read-write-user password
OK
172.17.0.2:6379> set key1 value1
OK
172.17.0.2:6379> get key1
"value1"

読み取り専用。

172.17.0.2:6379> auth read-only-user password
OK
172.17.0.2:6379> get key1
"value1"
172.17.0.2:6379> set key1 value1
(error) NOPERM User read-only-user has no permissions to run the 'set' command

OKですね。

次は、キーに対して制限をかけてみましょう。

127.0.0.1:6379> acl setuser a-prefix-user on >password +@read +@write ~a-*
OK


127.0.0.1:6379> acl setuser b-prefix-user on >password +@read +@write ~b-*
OK

a-、b-で始まるキーのみにアクセスできるユーザーをそれぞれ追加。

a-で始まるキーのみにアクセスできるユーザーで確認。

172.17.0.2:6379> auth a-prefix-user password
OK
172.17.0.2:6379> set a-key1 value1
OK
172.17.0.2:6379> get a-key1
"value1"
172.17.0.2:6379> set b-key1 value1
(error) NOPERM No permissions to access a key
172.17.0.2:6379> get b-key1
(error) NOPERM No permissions to access a key

b-で始まるキーのみにアクセスできるユーザーで確認。

172.17.0.2:6379> auth b-prefix-user password
OK
172.17.0.2:6379> set b-key1 value1
OK
172.17.0.2:6379> get b-key1
"value1"
172.17.0.2:6379> set a-key1 value1
(error) NOPERM No permissions to access a key
172.17.0.2:6379> get a-key1
(error) NOPERM No permissions to access a key

OKですね。雰囲気はだいたいわかりました。

設定ファイルに書いてみる

次は、設定ファイルに書いてみましょう。

こんなファイルを作成。

conf/redis.conf

user default off
user redis-admin on >admin-password ~* &* +@all
user read-write-user on >password +@read +@write ~*
user read-only-user on >password +@read ~*

defaultユーザーを設定しないと、他のサーバーから接続できない(プロテクションモードが有効なので)のは変わらずです。
特に使わないのなら、ユーザーとして無効にしておいてもいい気がします。

この設定ファイルを指定してRedisサーバーを起動。

$ bin/redis-server conf/redis.conf

他のサーバーからアクセス。

$ bin/redis-cli -h 172.17.0.2
172.17.0.2:6379> auth redis-admin admin-password
OK
172.17.0.2:6379> set key1 value1
OK
172.17.0.2:6379> get key1
"value1"

OKですね。これで、だいたい使い方がわかった気がします。

なお、今回はセレクターは除外しています。

ACLのカテゴリー

今回はコマンド単位ではなく、カテゴリーを指定してACLを設定しました。

カテゴリーの一覧は、ACL CATコマンドで確認できます。

127.0.0.1:6379> acl cat
 1) "keyspace"
 2) "read"
 3) "write"
 4) "set"
 5) "sortedset"
 6) "list"
 7) "hash"
 8) "string"
 9) "bitmap"
10) "hyperloglog"
11) "geo"
12) "stream"
13) "pubsub"
14) "admin"
15) "fast"
16) "slow"
17) "blocking"
18) "dangerous"
19) "connection"
20) "transaction"
21) "scripting"

ACL CAT | Redis

カテゴリーの意味や含まれるコマンドのイメージは、以下に書かれています。

ACL / Command categories

なのですが、各コマンドのドキュメントにどのカテゴリーに含まれるのかが書かれているので、こちらを見てもよいかもですね。

たとえば、SETコマンドならこのように書かれています。

SET | Redis

おわりに

RedisのACLを試してみました。

今までデフォルトユーザーにパスワードを設定することしか知らなかったので、いつの間にかいろいろ進んでいたんだなという気分に
なりました。

実際に使う時にはちゃんと設定しないといけない内容だと思うので、覚えておきましょう。