SlackクローンのRocket.ChatをRaspberry Pi 4で運用する

Posted by 雅楽斎 on Sunday, January 24, 2021

TOC

苦労の連続しかなかった…

Rocket.ChatというSlackと似た使い勝手のFLOSSがあります。これをUbuntu 20.10を動かしているRPi4でローカルネットワーク用にオンプレミスで運用しようと決めてから、実際に運用できるようになるまで膨大な時間が掛かったので、その手順のまとめ記事です。

引っかかったポイント

様々なソフトウェアのインストールやデプロイは、Dockerによるコンテナ運用が一般的になるにつれてどんどん簡単になっています。今回紹介するRocket.Chatもその例に漏れず、snapを使用した配布をしているので、とても簡単に構築できる…はずでした。

  • Android版Rocket.ChatアプリはTLS接続が必須
  • Androidは名前解決に(アプリ側で特別な実装をしない限り)mDNSを使うことができない
  • 外部に公開しない運用をした場合、Let’s EncryptやZeroSSLにTLS証明書を発行してもらうことができない
  • Ubuntu 20.04以降ではsnapによる構築ができなかった(18.04はできるらしい)

なので、上記にひっかからないRocket.Chatの運用はとても手間いらずで簡単にできます。

ARM版のsnapパッケージも準備されているので、RPi4でも簡単に構築できる…はずでした。

手順のサマリー

結局、作業が膨大になったので、やるべきことを先にまとめます。

  • Rocket.Chatのsnapパッケージをインストール
  • Androidアプリをインストール
  • snap set rocketchat-server caddy-url=https://rpi4.internal
  • snap set rocketchat-server caddy=enable
  • (TLS接続するので)DNSで名前を引けるようにする
  • TLS証明書を準備
  • サーバー側に秘密鍵とCRTを配備
  • /var/snap/rocketchat-server/current/Caddyfileを作る
  • クライアント側にCAの公開鍵をインポート
  • クライアントからアクセス

Rocket.Chatのインストール

Install Rocket.Chat Server on Linux | Snap Store

snapでインストールします。今回はRpi4で実行しているのでARM版がインストールされます。

# snap install rocketchat-server
rocketchat-server (3.x/stable) 3.7.4 from Rocket.Chat (rocketchat✓) installed

Rocket.Chatの起動

インストールが終わったら、そのまま起動します。

# snap start rocketchat-server
Started.

デフォルトではTCPの3000番ポートを使うので、ブラウザから http://192.168.1.210:3000/ などして接続すれば初期設定から使い始められます。

ここからが長いです。

Androidアプリの接続先は暗号化されていないといけない

Rocket.Chat - Google Play のアプリ

the Android app Rocketchat returns an error CLEARTEXT · Issue #2662 · RocketChat/Rocket.Chat.ReactNative · GitHub

This is the correct behavior. We’re following Google recommendation of disallowing insecure connections.

去年の10月リリースの4.11.0からGoogleのススメに従っているようです。

Release Version: 4.11.0 · RocketChat/Rocket.Chat.ReactNative · GitHub

ブラウザから接続すれば問題ないので、妥協できるポイントではあるんですが、使い勝手はネイティブアプリの方が良いだろうと見立ててなんとか回避していくことに。

オフィシャルの手順をなぞるとどのくらい引っかかるのか試してみる

Deploy with Snaps

最短手はsnap用の変数を3つ設定してsnap run rocketchat-server.initcaddyを実行することになっています。

# snap set rocketchat-server caddy-url=https://rpi4.internal
# snap set rocketchat-server caddy=enable
# snap set rocketchat-server https=enable
error: cannot perform the following tasks:
- Run configure hook of "rocketchat-server" snap (run hook "configure": 
-----
dig: error while loading shared libraries: libdns.so.162: cannot open shared object file: No such file or directory
Error: Can't resove DNS query for rpi4.internal, check your DNS configuration, disabling https ...
-----)

Caddy Error When Enabling HTTPS · Issue #41 · RocketChat/Rocket.Chat.RaspberryPi · GitHub

digでDNSを拾っている時に共有ライブラリがないという結構根本的におかしいエラーが出ていて、結果Ubuntu20.04以降では現時点で64bit ARMのMongoDBではサポートされないので、64bit ARMでsnapで使う場合は18.04を使ってくれという本末転倒感満載のレスがあったため、今回この3手でのTLS対応を諦めました。

以降、Caddyを使わずに普通にTLS証明書を使って暗号化、ついでにTLSではIPアドレスを使うことはまずなく、ドメインで名前解決する必要があるだろうという判断でDNSサーバーも構築しました。

DNSサーバーをLAN内に構築

ローカルネットワーク用にdnsmasqでDNSサーバーを構築する

RPi4にDnsmasqをインストールして名前で引けるようにしました。この結果、LAN内からRPi4は rpi4.internal というドメインで引けるようになっています。

TLS証明書を準備する

easy-rsaでプライベートCA(認証局)を作ってオレオレ証明書ではないローカル用TLS証明書の管理をする

CAは構築したので、

  • 秘密鍵の生成
  • 秘密鍵からのCSRの生成
  • CAでのCSRへの署名
  • 署名結果のCRTの配置

をします。

秘密鍵の生成・CSRの生成

opensslコマンドで秘密鍵とCSRを生成します。指定している引数、入力内容は以下の通りです。

  • key 秘密鍵のファイル名(rocketchat.privkey)
  • addext SANに指定するドメイン(subjectAltName = DNS:rpi4.internal)
  • Common Name ドメイン(rpi4.internal)
  • out CSRのファイル名(rocketchat.csr)

    $ openssl req -new -key rocketchat.privkey -out rocketchat.csr -addext 'subjectAltName = DNS:rpi4.internal'
    You are about to be asked to enter information that will be incorporated
    into your certificate request.
    What you are about to enter is what is called a Distinguished Name or a DN.
    There are quite a few fields but you can leave some blank
    For some fields there will be a default value,
    If you enter '.', the field will be left blank.
    -----
    Country Name (2 letter code) [AU]:
    State or Province Name (full name) [Some-State]:
    Locality Name (eg, city) []:
    Organization Name (eg, company) [Internet Widgits Pty Ltd]:
    Organizational Unit Name (eg, section) []:
    Common Name (e.g. server FQDN or YOUR name) []:rpi4.internal
    Email Address []:
    
    Please enter the following 'extra' attributes
    to be sent with your certificate request
    A challenge password []:
    An optional company name []:
    

補足:Chrome58以降ではドメインとCN(Common Name)の一致ではなくドメインとSAN(Subject Alternative Name)が一致する必要がある

以前はCNがドメインと一致していることをChromeが判定していましたが、Chrome58以降ではドメインとSANが一致している必要があります。

Rocket.ChatのAndroidアプリでもCNの一致のみでは信頼されない挙動に見えます。

CAでのCSRへの署名

生成したCSRをCAで署名してCRTを生成します。

まず、CSRをrocketchatという名前でインポートします。/tmp/rocketchat.csrがインポートするCSRのファイル名です。

$ cd ~/easy-rsa
$ ./easyrsa import-req /tmp/rocketchat.csr rocketchat

Note: using Easy-RSA configuration from: ./vars

Using SSL: openssl OpenSSL 1.1.1f  31 Mar 2020

The request has been successfully imported with a short name of: rocketchat
You may now use this name to perform signing operations on this request.

内容を確認してrocketchatに署名します。

$ ./easyrsa sign-req server rocketchat

Note: using Easy-RSA configuration from: ./vars

Using SSL: openssl OpenSSL 1.1.1f  31 Mar 2020


You are about to sign the following certificate.
Please check over the details shown below for accuracy. Note that this request
has not been cryptographically verified. Please be sure it came from a trusted
source or that you have verified the request checksum with the sender.

Request subject, to be signed as a server certificate for 1080 days:

subject=
    countryName               = AU
    stateOrProvinceName       = Some-State
    organizationName          = Internet Widgits Pty Ltd
    commonName                = rpi4.internal


Type the word 'yes' to continue, or any other input to abort.
  Confirm request details: yes
Using configuration from /home/ca/easy-rsa/pki/safessl-easyrsa.cnf
Enter pass phrase for /home/ca/easy-rsa/pki/private/ca.key:
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
countryName           :PRINTABLE:'AU'
stateOrProvinceName   :ASN.1 12:'Some-State'
organizationName      :ASN.1 12:'Internet Widgits Pty Ltd'
commonName            :ASN.1 12:'rpi4.internal'
Certificate is to be certified until Jan  5 16:08:27 2024 GMT (1080 days)

Write out database with 1 new entries
Data Base Updated

Certificate created at: /home/ca/easy-rsa/pki/issued/rocketchat.crt

最終行にあるように、/home/ca/easy-rsa/pki/issued/rocketchat.crtがCRTなので、これをRocket.Chat側にコピーします。

秘密鍵とCRTの配備

秘密鍵のファイルであるrocketchat.privkeyとCAが署名したrocketchat.crtをRocket.Chatが見える場所にコピーします。ここでは/var/snap/rocketchat-server/common/key/を作って、その下に2ファイルをコピーします。

# mkdir /var/snap/rocketchat-server/common/key/

/var/snap/rocketchat-server/current/Caddyfile の作成

このファイル自体snapパッケージをインストールした時点では存在しませんが、作成するとネットワークの設定を読み込むようになります。

https://rpi4.internal
tls /var/snap/rocketchat-server/common/key/rocketchat.crt /var/snap/rocketchat-server/common/key/rocketchat.privkey
proxy / localhost:3000 {
  websocket
  transparent
}

2行目のtlsの後にCRTファイル、秘密鍵ファイルを記載したら、caddyを再起動します。

# systemctl restart snap.rocketchat-server.rocketchat-caddy

これでRocket.Chat側の準備は完了です。なお、上記の内容だと443番ポートを掴んでしまうため、後々の運用に響かないよう、HTTPSで接続するポートを8443に変更しています。

https://rpi4.internal:8443
tls /var/snap/rocketchat-server/common/key/rocketchat.crt /var/snap/rocketchat-server/common/key/rocketchat.privkey
proxy / localhost:3000 {
  websocket
  transparent
}

クライアント側の準備

公開鍵のインポート

プライベートCAの構築エントリに記載している手順を再掲します。対象はUbuntu 20.04とAndroidです。

easy-rsaでプライベートCA(認証局)を作ってオレオレ証明書ではないローカル用TLS証明書の管理をする

システムにインポート

update-ca-certificates

ca.crtをscpでコピーするか中身のbase64の文字列をコピーして~/ca.crtというファイルに保存したら、/usr/local/share/ca-certificates/ディレクトリにコピーします。

$ sudo mv ca.crt /usr/local/share/ca-certificates/

端末上にCAの公開鍵を反映します。

# update-ca-certificates
Updating certificates in /etc/ssl/certs...
1 added, 0 removed; done.
Running hooks in /etc/ca-certificates/update.d...

Adding debian:ca.pem
done.
Updating Mono key store
Mono Certificate Store Sync - version 6.12.0.107
Populate Mono certificate store from a concatenated list of certificates.
Copyright 2002, 2003 Motus Technologies. Copyright 2004-2008 Novell. BSD licensed.

Importing into legacy system store:
I already trust 138, your new list has 139
Import process completed.

Importing into BTLS system store:
I already trust 138, your new list has 139
Import process completed.
Done
done.

これで、この端末はCAが署名した証明書を信頼します。

$  ls -l /etc/ssl/certs/ | grep ca.pem
lrwxrwxrwx 1 root root      6  1月 20 22:57 2352eff5.0 -> ca.pem
lrwxrwxrwx 1 root root     39  1月 20 22:57 ca.pem -> /usr/local/share/ca-certificates/ca.crt

但し、ブラウザのキャッシュが関係しているのかわかりませんがこの状態で接続できるようになったのは手元ではEpiphanyのみでした。(後で確認したRocket.Chatの画面)

certutilコマンドで$HOME/.pki/nssdbにインポート

update-ca-certificatesコマンドで反映した後もChromium Edge(Dev)では状態が変わらなかった(かつ、後述するChromiumと違って証明書のインポート設定を開けなかった)ので、他にシステム的に反映させられそうなものを検索した結果、$HOME/.pki/nssdbがユーザー単位で操作できそうだったので、ここにインポートします。

libnss3-toolsをインストールします。

# apt-get install libnss3-tools 

現在格納されている証明書を見てみます。

$ certutil -d sql:$HOME/.pki/nssdb -L

Certificate Nickname                                         Trust Attributes
                                                             SSL,S/MIME,JAR/XPI

1つもありませんでした。/home/hogehoge/privateca.crtというファイル名の証明書をインポートします。-tで指定する属性はよくわかりませんでしたがとりあえずこれで動くことを確認しています。

$ certutil -d sql:$HOME/.pki/nssdb -A -t "CT,c," -n privateca -i /home/hogehoge/privateca.crt 

$ certutil -d sql:$HOME/.pki/nssdb -L

Certificate Nickname                                         Trust Attributes
                                                             SSL,S/MIME,JAR/XPI

privateca                                                    CT,c,

この後、Chromium Edge(Dev)を起動し直すかリロードすることで信頼されるようになることを確認できました。

Chromiumにインポート

Chromiumの場合どこからルート証明書を読み取っているのかわからなかったので設定画面から直接インストールします。Chromiumの場合の手順です。

システムではインポートしたはずですが、相変わらず信頼されていない状態が続いています。

Chromiumの設定から「セキュリティ」を選択します。

「詳細設定」にある「証明書の管理」を選びます。

「認証局」タブの「インポート」ボタンを押します。

CAの公開鍵ファイルを開きます。

信頼する種別を選択する画面になるので、今回はとりあえずウェブサイトの識別で信頼します。

インポートが終わると、internalというCAがインポートされたことがわかります。

信頼されていなかったタブに戻ってリロードすると、証明書が有効扱いに変更されたことがわかります。

Androidにインポート

CA公開鍵(privateca.crt)をAndroidにコピーして、「設定」→「セキュリティ」→「暗号化と認証情報」→「ストレージからのインストール」を選択します。

CAの公開鍵ファイルを選択して認証をすると、証明書の名前を指定する画面になります。

名前を指定してOKをタップすると、信頼できる認証情報に追加されます。

これで設定が問題ない場合、Android上のRocket.Chatアプリからもログインできるようになります。

Mattermostは?

SlackのクローンといえばRocket.Chat以外にも代表的なものの一つにMattermostがあります。Goで実装されていることもあり消費リソースの観点からも魅力的な選択肢でした。Dockerイメージでも配布されているのでインストールもある程度簡単にできそうに見えたというのもあります。

しかし、ARM版については執筆時点でもオフィシャルでは消極的で、セットアップ手順も纏まっているものがなさそうでDockerで運用するのも難しそうだったので、今回はスルーしました。

Raspberry Pi 4, Mattermost inside Docker? - Troubleshooting - Mattermost Discussion Forums

ELF: not found · Issue #387 · mattermost/mattermost-docker · GitHub

スポンサーリンク


comments powered by Disqus