Goのredigoを使って堅牢なRedisとPub/Sub通信のPublishを行ってみる


# はじめに

この記事は**[and factory Advent Calendar 2020](https://qiita.com/advent-calendar/2020/andfactory)**の**18日目の記事**です。
昨日は [@ichi-nakashima](https://qiita.com/ichi-nakashima)さんの[【React Hooks】Component から stateを伴ったロジックを切り出す技術たち](https://qiita.com/ichi-nakashima/items/7cf3a0683f09aac77f68)でした。

# RedisにはPub/Sub機能がある

Redisと言えば**KVS（Key-Value Store）**として使われるのが一般的と思いますが、**Pub/Sub機能**も備わっているので、こいつを使って**Go**とやり取りをしてみたいと思います。

# RedisのPub/Sub機能とは

それぞれ**Publish（発行）**、**Subscrib（購読）**という意味で、Redisではある**Channel**を作成して、
その**Channel**に対してメッセージを**Publish（発行）**したり**Subscrib（購読）**したりする（＝メッセージの発信・受信みたいな）ことができる機能を指します。

- [公式ドキュメント](https://redis.io/topics/pubsub)
- [日本語翻訳ドキュメント（非公式）](https://redis-documentasion-japanese.readthedocs.io/ja/latest/topics/pubsub.html)

今回は**堅牢なRedis**を立ててそいつに対して**GoからPublish**を行ってみます。

# Redisサーバーを立てる

一応自分で試した環境は**Vagrant+VM上のCentOS7にRedisをインストール**しました。
以下はインストール時の一連（**EPELのリポジトリ追加→Redisインストール**）のログ

まずは**EPELのリポジトリの追加**
（※EPELについては [CentOSなどで使う、EPELってなんだ？ - Qiita](https://qiita.com/charon/items/f5732694515d174851b3) などを参照してください。）

```
[vagrant@localhost ~]$ sudo yum install epel-release
Loaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
 * base: ftp.tsukuba.wide.ad.jp
 * extras: ftp.tsukuba.wide.ad.jp
 * updates: download.nus.edu.sg
Resolving Dependencies
--> Running transaction check
---> Package epel-release.noarch 0:7-11 will be installed
--> Finished Dependency Resolution

Dependencies Resolved

===================================================================================================================================================================
 Package                                     Arch                                  Version                             Repository                             Size
===================================================================================================================================================================
Installing:
 epel-release                                noarch                                7-11                                extras                                 15 k

Transaction Summary
===================================================================================================================================================================
Install  1 Package

Total download size: 15 k
Installed size: 24 k
Is this ok [y/d/N]: y
Downloading packages:
epel-release-7-11.noarch.rpm                                                                                                                |  15 kB  00:00:00
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
  Installing : epel-release-7-11.noarch                                                                                                                        1/1
  Verifying  : epel-release-7-11.noarch                                                                                                                        1/1

Installed:
  epel-release.noarch 0:7-11

Complete!
```

次に**Redisのインストール**

```
[vagrant@localhost ~]$ sudo yum install redis -y
Loaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
epel/x86_64/metalink                                                                                                                        | 6.4 kB  00:00:00
 * base: ftp.tsukuba.wide.ad.jp
 * epel: download.nus.edu.sg
 * extras: ftp.tsukuba.wide.ad.jp
 * updates: download.nus.edu.sg
epel                                                                                                                                        | 4.7 kB  00:00:00
(1/3): epel/x86_64/group_gz                                                                                                                 |  95 kB  00:00:01
(2/3): epel/x86_64/updateinfo                                                                                                               | 1.0 MB  00:00:02
(3/3): epel/x86_64/primary_db                                                                                                               | 6.8 MB  00:00:01
Resolving Dependencies
--> Running transaction check
---> Package redis.x86_64 0:3.2.12-2.el7 will be installed
--> Processing Dependency: libjemalloc.so.1()(64bit) for package: redis-3.2.12-2.el7.x86_64
--> Running transaction check
---> Package jemalloc.x86_64 0:3.6.0-1.el7 will be installed
--> Finished Dependency Resolution

Dependencies Resolved

===================================================================================================================================================================
 Package                                Arch                                 Version                                      Repository                          Size
===================================================================================================================================================================
Installing:
 redis                                  x86_64                               3.2.12-2.el7                                 epel                               544 k
Installing for dependencies:
 jemalloc                               x86_64                               3.6.0-1.el7                                  epel                               105 k

Transaction Summary
===================================================================================================================================================================
Install  1 Package (+1 Dependent package)

Total download size: 648 k
Installed size: 1.7 M
Downloading packages:
warning: /var/cache/yum/x86_64/7/epel/packages/jemalloc-3.6.0-1.el7.x86_64.rpm: Header V3 RSA/SHA256 Signature, key ID 352c64e5: NOKEY0 B/s |    0 B  --:--:-- ETA
Public key for jemalloc-3.6.0-1.el7.x86_64.rpm is not installed
(1/2): jemalloc-3.6.0-1.el7.x86_64.rpm                                                                                                      | 105 kB  00:00:02

(2/2): redis-3.2.12-2.el7.x86_64.rpm                                                                                                        | 544 kB  00:00:02
-------------------------------------------------------------------------------------------------------------------------------------------------------------------
Total                                                                                                                              293 kB/s | 648 kB  00:00:02
Retrieving key from file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-7
Importing GPG key 0x352C64E5:
 Userid     : "Fedora EPEL (7) <epel@fedoraproject.org>"
 Fingerprint: 91e9 7d7c 4a5e 96f1 7f3e 888f 6a2f aea2 352c 64e5
 Package    : epel-release-7-11.noarch (@extras)
 From       : /etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-7
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
  Installing : jemalloc-3.6.0-1.el7.x86_64                                                                                                                     1/2
  Installing : redis-3.2.12-2.el7.x86_64                                                                                                                       2/2
  Verifying  : redis-3.2.12-2.el7.x86_64                                                                                                                       1/2
  Verifying  : jemalloc-3.6.0-1.el7.x86_64                                                                                                                     2/2

Installed:
  redis.x86_64 0:3.2.12-2.el7

Dependency Installed:
  jemalloc.x86_64 0:3.6.0-1.el7

Complete!
```

Redisのインストールが完了したので立ち上げる

```
[vagrant@localhost ~]$ sudo systemctl start redis.service
```

Redisの自動起動を有効にする

```
[vagrant@localhost ~]$ sudo systemctl enable redis
Created symlink from /etc/systemd/system/multi-user.target.wants/redis.service to /usr/lib/systemd/system/redis.service.
```

最後にRedisの状態を確認する

```
[vagrant@localhost ~]$ sudo systemctl status redis.service
● redis.service - Redis persistent key-value database
   Loaded: loaded (/usr/lib/systemd/system/redis.service; enabled; vendor preset: disabled)
  Drop-In: /etc/systemd/system/redis.service.d
           └─limit.conf
   Active: active (running) since Mon 2020-07-13 09:43:21 UTC; 31s ago
 Main PID: 22002 (redis-server)
   CGroup: /system.slice/redis.service
           └─22002 /usr/bin/redis-server 127.0.0.1:6379

Jul 13 09:43:21 localhost.localdomain systemd[1]: Starting Redis persistent key-value database...
Jul 13 09:43:21 localhost.localdomain systemd[1]: Started Redis persistent key-value database.
```

念のため`redis-cli`コマンドで`ping`を行い、`PONG`と返ってくるか確認する。
※この`ping`コマンドは疎通確認によく使用されるコマンドです。

```
[vagrant@localhost ~]$ redis-cli ping
PONG
```

あとは`/etc/redis.conf`を編集してIPアドレスなどを設定すればRedisのインストールは完了

```
[vagrant@localhost ~]$ sudo vi /etc/redis.conf
```


# Redisを堅牢にする（パスワードの設定）




```
[vagrant@localhost ~]$ echo "secretkey" | sha256sum
92d4e0a2ad853e81e6d4bf147618aae5a06f201e6e72a67794a542409c9d125a -
[vagrant@localhost ~]$ sudo vi /etc/redis.conf
```

`SECURITY`の項目があるので、その下に`requirepass`を追加し、パスワードを記述する。

```/etc/redis.conf

################################## SECURITY ###################################

# Require clients to issue AUTH <PASSWORD> before processing any other
# commands.  This might be useful in environments in which you do not trust
# others with access to the host running redis-server.
#
# This should stay commented out for backward compatibility and because most
# people do not need auth (e.g. they run their own servers).
#
# Warning: since Redis is pretty fast an outside user can try up to
# 150k passwords per second against a good box. This means that you should
# use a very strong password otherwise it will be very easy to break.
#
requirepass 92d4e0a2ad853e81e6d4bf147618aae5a06f201e6e72a67794a542409c9d125a

```

（※ここでは`sha256sum`を使って生成したハッシュ値をパスワードとして設定していますが、ある程度の長さがあればいいと思います。）


`/etc/redis.conf`編集後にRedisを再起動し、`redis-cli`から先ずはパスワードなしの状態でコマンドを実行する。

```
[vagrant@localhost ~]$ sudo systemctl restart redis.service
[vagrant@localhost ~]$ redis-cli
127.0.0.1:6379> set key 10
(error) NOAUTH Authentication required.
```

上記のように認証エラーがでることを確認。
次に`auth`コマンドを書き、先ほど設定したパスワードを入れて実行する。

```
127.0.0.1:6379> auth 92d4e0a2ad853e81e6d4bf147618aae5a06f201e6e72a67794a542409c9d125a
OK
```

OKが出れば認証された状態なので、コマンドを実行すれば以下のようにふつうに動くようになる。

```
127.0.0.1:6379> set key 10
OK
127.0.0.1:6379> get key
"10"
127.0.0.1:6379> quit
```




# Goのサンプルコード

サンプルコードは**パスワードを設定したRedisにパブリッシュする**処理を書いています。（※パスワード設定は必須ではないです。念のため）

```.go
package main

import (
    "fmt"
    "github.com/gomodule/redigo/redis"
)

// RedisPublish : コマンドをRedisへパブリッシュする
func RedisPublish(message string) (int, error) {
	conn, redisAuthPass, err := ConnectionRedis()
	if err != nil {
		fmt.Println(err)
		return 0, err
	}
	defer conn.Close()

	// パス認証
	if redisAuthPass != "" {
		_, err = conn.Do("AUTH", redisAuthPass)
		if err != nil {
			fmt.Println(err)
			return 0, err
		}
	}

	// パブリッシュ
	res, err := redis.Int(conn.Do("PUBLISH", "test_channel", message))
	if err != nil {
		fmt.Println(err)
		return 0, err
	}

	return res, err
}

// ConnectionRedis : Redisと接続し接続情報とAuthパスを返す
func ConnectionRedis() (redis.Conn, string, error) {
	// RedisのAUTHパス
	AuthPass := "92d4e0a2ad853e81e6d4bf147618aae5a06f201e6e72a67794a542409c9d125a"            
	// RedisのIPアドレス
	RedisIPAddress := "192.168.33.100"
	// Redisのポート番号
	RedisPort := "6379"

	// 接続
	conn, err := redis.Dial("tcp", RedisIPAddress+":"+RedisPort)
	if err != nil {
		fmt.Println(err)
		return nil, "", err
	}

	return conn, AuthPass, err
}

```

実際には**AUTHパス**とかは環境によって変わると思うので、ソースに書かずに`.env`とかに記述して読み込むのがいいと思います。
参考：[【Go】.envファイルをGolangでも使用するためのライブラリ「godotenv」 - Qiita](https://qiita.com/sola-msr/items/fb7d6889d7bd7a6705d0)

# Redisをさらに堅牢にする

個人的にはRedisに接続する際のAuthパスだけでもいいと思うんですが**念には念を入れて強化する**場合は以下のことが出来ます。

## ポート番号の変更

Redisのデフォルトポート番号は`6379`番ですが、これを変更することで多少なりともセキュリティが向上します。

```/etc/redis.conf
# Accept connections on the specified port, default is 6379 (IANA #815344).
# If port 0 is specified Redis will not listen on a TCP socket.
# port 6379
port 6597
```

変更後はRedisの再起動すること。

## 重要なコマンドの変更

例えば`CONFIG`や`SHUTDOWN`、`FLUSHDB`コマンドなど、実行されるとマズいコマンドをほかの文字列に置き換えることが出来ます。
代わりのコマンド名を以下の`redis.conf`に追記して再起動をすることで適用されます。

```/etc/redis.conf
# Command renaming.
#
# It is possible to change the name of dangerous commands in a shared
# environment. For instance the CONFIG command may be renamed into something
# hard to guess so that it will still be available for internal-use tools
# but not available for general clients.
#
# Example:
#
# rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52
#
# It is also possible to completely kill a command by renaming it into
# an empty string:
#
# rename-command CONFIG ""
#
# Please note that changing the name of commands that are logged into the
# AOF file or transmitted to slaves may cause problems.
rename-command CONFIG configconfig
rename-command FLUSHDB SF_FLUSHDB
rename-command SHUTDOWN RENAME_SD
```

以下コマンドがかわっているか確認の様子

```
[vagrant@localhost ~]$ sudo systemctl restart redis.service
[vagrant@localhost ~]$ redis-cli
127.0.0.1:6379> auth 92d4e0a2ad853e81e6d4bf147618aae5a06f201e6e72a67794a542409c9d125a // 上で設定したパス
OK
127.0.0.1:6379> config get requirepass
(error) ERR unknown command 'config' // configというコマンドは無い（上で変更したため）
127.0.0.1:6379> configconfig get requirepass
1) "requirepass"
2) "92d4e0a2ad853e81e6d4bf147618aae5a06f201e6e72a67794a542409c9d125a"
127.0.0.1:6379> exit
```

## Redisデータディレクトリの権限変更

RedisユーザーとRedisグループのみがアクセスできるように`770`に変更する。

```
[vagrant@localhost ~]$ ls -l /var/lib | grep redis
drwxr-x---. 2 redis   redis     22 Jul 13 13:31 redis

[vagrant@localhost ~]$ sudo chmod 770 /var/lib/redis

[vagrant@localhost ~]$ ls -l /var/lib | grep redis
drwxrwx---. 2 redis   redis     22 Jul 13 13:31 redis
```

# おわり
- なんか走り書きしたら雑なまとめになってしまった感がある・・・
- ちなみにSubscribは今度時間あれば書くかもしれません・・・（予定は未定です）
- あとまだRedisは**構成ファイルの権限変更**したりなどで強固にできますがここでは割愛します（[DigitalOceanさんの参考記事](https://www.digitalocean.com/community/tutorials/how-to-install-secure-redis-centos-7)をどうぞ）


# 参考URL
- [How to Install and Secure Redis in CentOS 7 | DigitalOcean](https://www.digitalocean.com/community/tutorials/how-to-install-secure-redis-centos-7)
- [Redigoを使う(6)　パブリッシュ／サブスクライブ - Qiita](https://qiita.com/riverplus/items/e31c6da6f5af28d3a3f7)
- [RedisのPub/Subがわからない人はRedisを使って理解しよう | アールエフェクト](https://reffect.co.jp/laravel/redis-pub-sub-dont-understand)