はじめに
先日書いたKubernetes (Azure AKS)上にVerneMQでMQTT over TLSクラスタを構成するで、「...Let’s Encrypt等のパブリックな認証局からサーバー証明書を発行してもらって利用することもできますが、...」という一言を書いていました。
が、実はLet's Encryptの証明書を用いるには、IdenTrustのルート証明書とLet's Encryptの中間証明書を結合して設定するという、一手間が必要です(2018/05/01時点)。
そこで今回は、Kubernetes上に構成したVerneMQクラスタへLet's Encryptのサーバ証明書を設定してみようと思います。
VerneMQクラスタの構成
ユーザー名とパスワードをKubernetes Secretに登録
以前紹介した手順を参考に、KubernetesのsecretsにVerneMQのパスワードを登録してください。
Let's Encryptからサーバ証明書を取得
次にLet's Encryptから、DNS-01 challengeを用いて mqtt.nmatsui.work
というFQDNのサーバ証明書を取得します。(ドメインレジストラへのドメイン登録やDNSサービスへのnameserver変更は、事前に実施しておいてください。)
Dockerコンテナ経由で certobot
コマンドを起動
-
certbotの公式Docker Imageを用いて、DNS-01 challengeを実行します。
$ mkdir -p secrets $ docker run -it -v $(PWD)/secrets:/etc/letsencrypt certbot/certbot certonly --manual --domain mqtt.nmatsui.work --email nobuyuki.matsui@gmail.com --agree-tos --manual-public-ip-logging-ok --preferred-challenges dns
-
TXTレコードに登録すべきvalueを確認します。
------------------------------------------------------------------------------- Please deploy a DNS TXT record under the name _acme-challenge.mqtt.nmatsui.work with the following value: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX Before continuing, verify the record is deployed. -------------------------------------------------------------------------------
-
このDockerコンテナのターミナルはそのままにしておき、別のターミナルを開きます。
DNSサービスへTXTレコードを登録
-
Dockerコンテナとは別のターミナルで、指定された
_acme-challenge.mqtt.nmatsui.work
のTXTレコードを作成します(今回はAzure DNSを用いています)。$ az network dns record-set txt add-record --resource-group nmatsui_dns --zone-name nmatsui.work --record-set-name "_acme-challenge.mqtt" --value "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
certbot
コマンドを継続してサーバ証明書を取得
-
TXTレコードを追加したら、Dockerコンテナを実行しているターミナルでEnterキーを押し、
certbot
に処理を継続させます。指定されたTXTレコードが正しく登録されていれば、./secrets
以下にmqtt.nmatsui.work
に対するサーバ証明書が取得されます。Press Enter to Continue Waiting for verification... Cleaning up challenges IMPORTANT NOTES: - Congratulations! Your certificate and chain have been saved at: /etc/letsencrypt/live/mqtt.nmatsui.work/fullchain.pem Your key file has been saved at: /etc/letsencrypt/live/mqtt.nmatsui.work/privkey.pem ...
TXTレコードを削除
-
サーバ証明書が取得できたので、登録したTXT recordは削除しておきます。
$ az network dns record-set txt remove-record --resource-group nmatsui_dns --zone-name nmatsui.work --record-set-name "_acme-challenge.mqtt" --value "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
ルート証明書と中間証明書を統合
Let's Encryptが発行するサーバ証明書は、IdenTrustのルート証明書からチェーンされるクロスルート証明書です。そのため、VerneMQやMQTTクライアントに設定する認証局の証明書としては、IdenTrustのルート証明書(DST Root CA X3)とLet's Encryptの中間証明書(Let's Encrypt Authority X3)の双方が必要となります。
IdenTrustのルート証明書(DST Root CA X3)の取得
IdenTrustの解説によれば、次のような手順が必要のようです。
-
公式ページに記載されているルート証明を
-----BEGIN CERTIFICATE-----
と-----END CERTIFICATE-----
の間にコピー&ペーストして証明書を作る
面倒ですし、ルート証明書は公開情報なので、DST_Root_CA_X3.pemというファイル名でgithubのリポジトリにpushしてあります。
Let's Encryptの中間証明書(Let's Encrypt Authority X3)と結合
Let's Encryptの中間証明書(Let's Encrypt Authority X3)は、Let's Encryptの公式ページから取得できます。ただしサーバ証明書取得時に同じファイルがダウンロードされていますので、それを用います。
$ cat secrets/DST_Root_CA_X3.pem secrets/archive/mqtt.nmatsui.work/chain1.pem > secrets/ca.crt
サーバ証明書と鍵をコピー
オレオレ証明書を生成した場合と同じyamlファイルを利用できるように、Let's Encryptで取得したサーバ証明書と秘密鍵を/secrets以下にコピーします。
$ cp secrets/archive/mqtt.nmatsui.work/fullchain1.pem secrets/server.crt
$ cp secrets/archive/mqtt.nmatsui.work/privkey1.pem secrets/server.key
- 上記以外のファイル名を用いる場合は、vernemq-cluster.yamlの
DOCKER_VERNEMQ_LISTENER__SSL__CAFILE
やDOCKER_VERNEMQ_LISTENER__SSL__CERTFILE
、DOCKER_VERNEMQ_LISTENER__SSL__KEYFILE
をそのファイル名に変更してください。
証明書類をKubernetes Secretに登録
結合したルート証明書、及びサーバー証明書とその秘密鍵をKubernetes Secretに登録します。
$ kubectl create secret generic vernemq-certifications --from-file=./secrets/ca.crt --from-file=./secrets/server.crt --from-file=./secrets/server.key
VerneMQクラスタの起動
-
以前と同様に、StatefulSetとしてVerneMQを起動します。
$ kubectl apply -f vernemq-cluster.yaml
-
VerneMQがクラスタを組んでいることを確認します。
$ kubectl exec vernemq-0 -- vmq-admin cluster show +---------------------------------------------------+-------+ | Node |Running| +---------------------------------------------------+-------+ |VerneMQ@vernemq-0.vernemq.default.svc.cluster.local| true | |VerneMQ@vernemq-1.vernemq.default.svc.cluster.local| true | |VerneMQ@vernemq-2.vernemq.default.svc.cluster.local| true | +---------------------------------------------------+-------+
VerneMQクラスタのExternalIPをDNSサービスに登録
-
起動したVerneMQのMQTTSサービスのExternalIPを確認します。
$ kubectl get services -l app=mqtts NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE mqtts LoadBalancer 10.0.124.40 WW.XXX.YYY.ZZZ 8883:30934/TCP 12m
-
このExternal IPに対して、
mqtt.nmatsui.work
をA recordとして登録します。$ az network dns record-set a add-record --resource-group nmatsui_dns --zone-name nmatsui.work --record-set-name "mqtt" --ipv4-address "WW.XXX.YYY.ZZZ"
接続確認
ここまでで、Let's Encryptから取得したサーバ証明書を用いて、VerneMQクラスタが起動できました。早速接続確認してみましょう。
subscriberを起動
Kubernetes内部
-
Kubernetes内部に検証用のpodを起動し、VerneMQクラスタをsubscribeします(経路の暗号化は行わない)。
$ kubectl run inner-sub --rm -it --image efrecon/mqtt-client /bin/ash # mosquitto_sub -h mqtt -p 1883 -t /foo/bar -d -u inner -P <password of 'inner'>
Internet経由
-
検証用端末からInternetを経由して、VerneMQクラスタをsubscribeします(TLSで経路を暗号化)。
$ mosquitto_sub -h mqtt.nmatsui.work -p 8883 --cafile ./secrets/ca.crt -t /foo/bar -d -u outer -P <password of 'outer'>
- ルート証明書として、ルート証明書と中間証明書を統合で結合したファイルを指定します。
メッセージをpublish
Kubernetes内部
-
Kubernetes内部に新たに起動した検証用PODから、VerneMQクラスタへメッセージをpublishします。
$ kubectl run inner-pub --rm -it --image efrecon/mqtt-client /bin/ash # mosquitto_pub -h mqtt -p 1883 -t /foo/bar -d -u inner -P <password of 'inner'> -m "Message from inner"
-
Kubernetes内部のsubscriberと検証端末のsubscriberの双方にメッセージがpushされることが確認できます。
... Client mosqsub|6-inner-sub-89b received PUBLISH (d0, q0, r0, m0, '/foo/bar', ... (6 bytes)) Message from inner ...
... Client mosqsub|35744-Nobuyukin received PUBLISH (d0, q0, r0, m0, '/foo/bar', ... (6 bytes)) Message from inner ...
Internet経由
-
検証用端末からInternetを経由して、VerneMQクラスタへメッセージをpublishします。
$ mosquitto_pub -h mqtt.nmatsui.work -p 8883 --cafile ./secrets/ca.crt -t /foo/bar -d -u outer -P <password of 'outer'> -m "Message from outer"
- subscribe時と同様、ルート証明書としてルート証明書と中間証明書を統合で結合したファイルを指定します。
-
Kubernetes内部のsubscriberと検証端末のsubscriberの双方にメッセージがpushされることが確認できます。
... Client mosqsub|6-inner-sub-89b received PUBLISH (d0, q0, r0, m0, '/foo/bar', ... (6 bytes)) Message from outer ...
... Client mosqsub|35744-Nobuyukin received PUBLISH (d0, q0, r0, m0, '/foo/bar', ... (6 bytes)) Message from outer ...
さいごに
ということで、Let's Encryptのサーバ証明書を用いて経路をTLS暗号化するVerneMQクラスタをKubernetes上に起動してみました。ブラウザやcurlコマンドとは異なり、MQTTクライアントはwell knownなルート証明書を内蔵していないため、クライアント側の手間は減らないのが微妙ではあります。が、ドメインの所有者であることは確認できるため、Internet上にMQTT Brokerを公開する場合にはオレオレ証明書よりも安心感があるのではないでしょうか。