はじめに
RocketChatをk8sクラスターにhelmで導入したみたので、その作業のメモを残します。
今回のhelm用rocketchatパッケージのバージョンは2.0.10(Rocket.Chat 3.6.0)で、前提となる導入については、次のような作業を行なっています。
## Helm v3対応版に書き換え
$ helm repo update
$ helm fetch stable/rocketchat
$ tar xvzf rocketchat-2.0.2.tgz
$ vi rocketchat/values.yaml
## mongodb.mongodbRootPassword, mongodb.mongodbPassword, *.storageClass などの設定を行ない、helm --set は利用しない
$ ( cd rocketchat ; sudo helm install --namespace rocketchat myrocketchat . )
helm listの動作について
特定のnamespaceで稼動している場合には、-namespace
オプションを利用する必要があります。
$ sudo helm list --namespace rocketchat
最新版Rocket.Chatへの更新について
Helmで導入後に、values.yamlの利用するDockerコンテナ設定を変更し、現時点で最新版のv3.16.0に変更後、helm upgrade
しています。
$ ( cd rocketchat ; sudo helm upgrade myrocketchat . --namespace rocketchat)
Refernces
- 公式ガイド - Configuring SSL Reverse Proxy
- GitHub Issues - Mass delete users #8324
- 公式ガイド - Running in a sub folder with Apache
- 公式APIガイド - developer.rocket.chat/api/rest-api
- 公式ガイド - Two Factor Authentication
Nginx ReverseProxyの追加設定
Android Appから接続した際には、Websocketを有効にするようメッセージが表示されました。
通常のTLS設定を有効にしたProxy設定だけでは不十分で次のような設定を追加しています。
server{
listen 443 ssl;
server_name chat.example.com;
ssl_certificate /etc/ssl/certs/chat.example.com.pem;
ssl_certificate_key /etc/ssl/private/chat.example.com.nopass.key;
client_max_body_size 0;
tcp_nodelay on;
gzip on;
sendfile on;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
# rocket.chat websocket related setting
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header X-Nginx-Proxy true;
location / {
proxy_pass http://192.168.100.140/;
}
}
この他にTLS設定を有効にしておきます。
Rocket.ChatのLDAP設定
設定したReverseProxyサーバーに接続し、初回の登録処理を行ないます。
この時に登録する管理者IDに指定するe-mailアドレスはLDAPに登録されているユーザーとは異なるもの(GmailアドレスやLDAPに登録されているものの利用しないユーザーのアドレス等)にします。
LDAPに登録されている一般ユーザーでログインした際に、mail:フィールドが既に登録済みだとログインすることができません。
管理者IDでログインし、"Administration"→"LDAP"に進み、次のような設定を行なっている。
考慮点は次のとおり。
- ログから認証に失敗する原因として、LDAP検索でPaginationが有効に機能しない場合があるため、対応のため"Search Page Size"は0に設定していること (Referencesのリンクを参照)
- AuthenticationはBind ID/Passwordを利用するか、しないか、でOn/Offを切り替えること
- Sync/Importは(LDAPサーバーに負荷を与えないための)一括読み取りをするかどうかに関わらず、設定を行なう必要があること
- User Group Filterの設定は、ldapsearchコマンドなどで利用できることを確認した上で設定すること
- LDAP Group Channel MapはGroupを利用してデフォルトのチャネルを設定できるので便利
LDAP (General Settings)
利用するTLSの証明書が自己証明書でなければ、CA情報を入力する必要はありません。
Enable: On
Login Fallback: Off
Find user after login: Off
Host: ldap.example.com
Port: 636
Reconnect: On
Encryption: SSL/LDAPS
CA Cert:
Reject Unauthorized: On
Base DN: ou=proxy,dc=example,dc=com
Internal Log Level: Info
LDAP - Authentication
bindDN,bindPWを利用しない場合は、offに設定しておきます。
Enable: Off
LDAP - Sync/Import
User Group Filterでは、#{userdn}
という指定が利用できるので、以前のuid=#{username},ou=people,...
という指定から置き換えています。
Channel Adminはデフォルトの、rocket.chatボットを指定しています。
Username Field: uid
Unique Identifier Field: objectGUID,ibm-entryUUID,GUID,dominoUNID,nsuniqueId,uidNumber
Default Domain: example.com
Merge Existing Users: Off
Sync User Data: On
User Data Field Map: {"gecos":"name", "mail":"email"}
Sync LDAP Groups: On
User Group Filter: (&(cn=#{groupName})(member=#{userdn}))
LDAP Group BaseDN: ou=MailGroup,ou=Proxy,dc=example,dc=com
User Data Group Map: { }
Auto Sync LDAP Groups to Channels: On
Channel Admin: rocket.chat
LDAP Group Channel Map: { "all-group1": "general", "all-group2": "general", "techsupport": [ "helpdesk", "support" ] }
Auto Remove Users from Channels: Off
Sync User Avatar: On
Background Sync: Off
Background Sync Interval: every 24 hours
Background Sync Import New Users: Off
Background Sync Update Existing Users: On
LDAP Group Channel Map:
を利用する場合には、LDAPグループ(上の例では"rocket-admin","tech-support")に対応するチャネル・プライベートグループ等(上の例では"group1", "group2")が存在している必要があります。
存在していないチャネル等を指定している場合には、ログイン自体に失敗する点に注意してください。
LDAP - Timeouts
Timeout (ms): 60000
Connection Timeout (ms): 1000
Idle Timeout (ms): 1000
LDAP - User Search
考慮点にも挙げていますが、環境によってはSearch Page Size: 0
を設定する必要があります。
Filter: (objectclass=*)
Scope: sub
Search Field: uid
Search Page Size: 0
Search Size Limit: 2000
LDAP - User Search (Group Validation)
Group Nameを指定することで、特定のグループメンバーだけがログインするように変更できます。
ここでは利用していませんが
Enable: Off
ObjectClass: groupOfNames
Group ID Attribute: cn
Group Member Attribute: member
Group Member Format: uid=#{username},ou=people,ou=proxy,dc=example,dc=com
Group Name: group1
遭遇した問題
Background Syncで登録されてしまったユーザーの一括削除
Background Sync: Onにしていると、Background Sync Import New Users: Offでも、LDAPに登録されているユーザー全員をImportしてしまいます。一般的な利用者だけではなく、システムユーザーも登録されてしまったので、これらのユーザーを削除しようと思いました。以下のissuesで方法が説明されています。
Helmでインストールしているので、mongodbのPodに入って、mongoコマンドでサーバーに接続します。
$ kubectl exec -it myrocketchat-mongodb-primary-0 bash
I have no name!@myrocketchat-mongodb-primary-0:/$ mongo
rs0:PRIMARY> use rocketchat
rs@:PRIMARY> db.auth("rocketchat", "secret")
1
rs@:PRIMARY> db.users.remove({"ldap": true});
WriteResult({ "nRemoved" : 501 })
rs0:PRIMARY> db.users.find()
{ "_id" : "rocket.cat", "createdAt" ... }
{ "_id" : "a97Pm5DxbkHsW9MSq", "createdAt" : ....}
LDAP経由でログインした経験のあるユーザー情報を含めて全てが削除されますが、関連するデータは残っているため、Merge Existing UsersがOffである場合(当初の設定)、LDAPユーザーでログインした際にエラーが発生する可能性があります。
Merge Existing Users: On
きれいにデータベースを操作する方法が分からなかったので、この方法で対応しています。
'/'以外のsub folderで稼動させるための方法
Helmを利用すると、ROOT_URLを変更する方法が提供されていないため、'/'以外のpathでは稼動させることができません。
Ingressに対応する設定はあるように見えますが、実際には動作しません。
context-rootを変更するためには、ROOT_URL変数に手を加える必要があるので、次の2点の変更が必要です。
- values.yamlファイルの
host:
にサービス用(フロントエンドのReverseProxy)のホスト名を指定する - templates/deployment.yamlファイルのROOT_URLを修正
diff --git a/rocket.chat/rocketchat/templates/deployment.yaml b/rocket.chat/rocketchat/templates/deployment.yaml
index d1eecb7..56e5000 100644
--- a/rocket.chat/rocketchat/templates/deployment.yaml
+++ b/rocket.chat/rocketchat/templates/deployment.yaml
@@ -63,7 +63,7 @@ spec:
key: mongo-oplog-uri
{{- if .Values.host }}
- name: ROOT_URL
- value: https://{{ .Values.host }}
+ value: https://{{ .Values.host }}/chat
{{- end }}
{{- if .Values.smtp.enabled }}
- name: MAIL_URL
@@ -81,7 +81,7 @@ spec:
{{- if .Values.livenessProbe.enabled }}
livenessProbe:
httpGet:
- path: /api/info
+ path: /chat/api/info
port: http
initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }}
periodSeconds: {{ .Values.livenessProbe.periodSeconds }}
@@ -92,7 +92,7 @@ spec:
{{- if .Values.readinessProbe.enabled }}
readinessProbe:
httpGet:
- path: /api/info
+ path: /chat/api/info
port: http
initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }}
periodSeconds: {{ .Values.readinessProbe.periodSeconds }}
実際にはvalues.yamlに独自項目を追加し、pathを直接打ち込んでいる状態は避けた方が無難です。
この後、service/myrocketchat-rocketchat
への接続を行なう必要があります。type:LoadBalancerに変更し、EXTERNAL-IPを割り当てる方法もありますが、ここはkubesprayで導入したingressから接続させていきます。
namespace: ingress-nginx での作業
Ingressは ingress-nginx というnamespaceで稼動しているので、そのままでは別namespaceのrocketchatには接続できないので、Ingressオブジェクトを作成し、Service側でExternalNameを割り当てていきます。
既にIngressを利用していれば不要な設定もありますが、ここではIngress-ControllerのDaemonSet定義だけが行なわれていて、Podが各Nodeで稼動しているだけの状態から設定していきます。
まず目指すべき最終形態での、kubectl -n ingress-nginx get all
の出力を載せておきます。
NAME READY STATUS RESTARTS AGE
pod/ingress-nginx-controller-knrzd 1/1 Running 0 5d4h
pod/ingress-nginx-controller-mvgnp 1/1 Running 0 5d4h
pod/ingress-nginx-controller-vj5s5 1/1 Running 0 5d4h
pod/ingress-nginx-controller-zlgnr 1/1 Running 0 5d4h
NAME TYPE CLUSTER-IP EXTERNAL-IP
PORT(S) AGE
service/ingress-nginx-controller LoadBalancer 10.233.52.44 192.168.100.168
80:31345/TCP 7h53m
service/rocketchat-svc ExternalName <none> myrocketchat-rocketchat.rocketchat.svc.cluste
r.local <none> 7h30m
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR
AGE
daemonset.apps/ingress-nginx-controller 4 4 4 4 4 kubernetes.io/o
s=linux 5d4h
get allでは表示されないIngressオブジェクトは次のような設定になっています。
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-ingress
labels:
group: ingress-nginx
namespace: ingress-nginx
spec:
rules:
- http:
paths:
- backend:
service:
name: rocketchat-svc
port:
number: 80
path: /chat
pathType: Prefix
初期状態では、一切のserviceオブジェクトが定義されていないので、次のような設定を行なっています。
---
apiVersion: v1
kind: Service
metadata:
name: ingress-nginx-controller
namespace: ingress-nginx
spec:
type: LoadBalancer
loadBalancerIP: "10.1.200.168"
ports:
- name: http
port: 80
targetPort: http
selector:
app.kubernetes.io/name: ingress-nginx
続いて、rocetchatに接続するためのexternalNameを設定していきます。
---
apiVersion: v1
kind: Service
metadata:
name: rocketchat-svc
labels:
group: ingress-nginx
namespace: ingress-nginx
spec:
type: ExternalName
externalName: myrocketchat-rocketchat.rocketchat.svc.cluster.local
externalNameは、<service name>.<namespace>.svc.cluster.local
のように接続先のホスト名を指定します。
LDAP Groupに対応したチャネルを準備したい
ActiveDirectoryなどと違って、Userのディレクトリには所属グループの情報(memberOf)は存在していないため、グループのディレクトリ(objectclass=groupOfNames)に対して、member=${userDN}なフィルターをかけないと所属グループが分からないようになっています。
いろいろ調べてみましたが、デフォルトではLDAP Group Channel Map
に対応するためのグループをあらかじめ準備する良い方法はなさそうです。
REST APIを利用して、必要なチャネルを準備する方法について検討していきます。
単純にチャネルを1つ追加するだけであれば、curlを利用して次のようにできるとドキュメントに書かれています。
curl -H "X-Auth-Token: 9HqLlyZOugoStsXCUfD_0YdwnNnunAJF8V47U3QHXSq" \
-H "X-User-Id: aobEdbYhXfu5hkeqG" \
-H "Content-type: application/json" \
http://localhost:3000/api/v1/channels.create \
-d '{ "name": "channelname" }'
X-Auth-Token
とX-User-Id
に対応する値は、管理者権限でログインしたユーザーの設定画面から、2要素認証を無視するにチェックを入れた状態で取得することができます。
$ ./add-rocketchat-channel.sh | jq .
{
"channel": {
"_id": "....",
"fname": "channelname",
"customFields": {},
"name": "channelname",
"t": "c",
"msgs": 0,
"usersCount": 1,
"u": {
"_id": ".....",
"username": "admin"
},
"ts": "2021-06-30T02:02:45.487Z",
"ro": false,
"_updatedAt": "2021-06-30T02:02:45.494Z"
},
"success": true
}
あとはスクリプトを工夫すれば複数のチャネルを作成することはできそうです。
もう一つ必要な作業があって、LDAPグループChannelマップにLDAPグループと対応するチャネル等を指定する必要があります。今回はLDAPグループ名とチャネル名を同じにするので、次のような設定をAPIを通じて行なう必要があります。
{
"channelname": "channelname",
....
}
あまり変更頻度が低いのであれば手動でコンソールから追加するでも良さそうなのですが、3桁台のグループが存在しているので、間違いを防ぐためにも自動化したいところです。
問題は先ほどのリンク先をみても、LDAPに対応するREST APIのガイドは存在しません。
githubでcloneしたコードをみると、LDAP_Sync_User_Data_Groups_AutoChannelsMap
というIDが割り当てられて、内容はJSONフォーマットであることが分かるので、これを流し込めば良いという事は分かります。
そう思ってAPIを良くみていくと、/api/v1/settings/:_id
からPOSTメソッドによる設定内容の更新ができることが分かります。ガイドの中で、コードのthis.add()
メソッドの引数からIDを確認するという記述があるので、なんとかなりそうです。
これをもとに、テスト用のcurlのコマンドラインを作っていきます。
まず、どんな変数をPOSTすれば良いのか分からないので、GETメソッドによる値の確認を行ないます。
curl -H "X-Auth-Token: 9HqLlyZOugoStsXCUfD_0YdwnNnunAJF8V47U3QHXSq" \
-H "X-User-Id: aobEdbYhXfu5hkeqG" \
http://localhost:3000/api/v1/settings/LDAP_Sync_User_Data_Groups_AutoChannelsMap
これを実行すると次のような結果が返ってきました。
{
"_id": "LDAP_Sync_User_Data_Groups_AutoChannelsMap",
"value": "{\n \"employee\": \"general\" \n}",
"success": true
}
基本的には、{ "value": "...." } の形式で内容を指定して、POSTするようなので、これに合わせてコマンドラインを準備します。
$ curl -H "X-Auth-Token: 9HqLlyZOugoStsXCUfD_0YdwnNnunAJF8V47U3QHXSq" \
-H "X-User-Id: aobEdbYhXfu5hkeqG" \
-H "Content-type:application/json" \
http://localhost:3000/api/v1/settings/LDAP_Sync_User_Data_Groups_AutoChannelsMap \
-d '{ "value": "{ \"channelname\": \"channelname\" }" }'
これを実行した時の戻り値は次のようになりました。
{"success":true}
この状態でコンソールでは設定の反映(保存)が完了しています。自分のLDAPユーザーで改めてログアウト・ログインすることで、自動的に作成したグループに含まれました。
これで無事に自動化する目処が立ちました。
POST時に発生するTOTPエラーについて
v3.16.1を使用したところ、同一のToken/IDを利用していて、LDAP_Sync_User_Data_Groups_AutoChannelsMap へのPOSTを実行した時だけ、TOTPが必要だというエラーメッセージ(リターンコード:400)になる現象に遭遇しました。
管理者権限を持つユーザーのPersonal Access Tokenを利用していますが、これを生成する際に、"Ignore Two Factor Authentication"チェックボックスにチェックを入れる必要がありました。
ドキュメントをみると、この部分の機能はまだこれから実装が進みそうなので、エラーメッセージが変化するなど、バージョンによって挙動が異なる可能性があります。
以上