概要
Google Home や Google Assistant から LAN 内部にある Raspberry Pi や ESP8266 を なるべく安全に 操作するための設定です.
特に注意したのは, トークンやパスワードを通信する際は必ず暗号化された通信経路を使う ということです. トークンやパスワードが第三者に知られてしまうと, 自宅内のデバイスが勝手に操作されてしまうかもしれません.
IFTTT や Beebotte との連携について丁寧に解説された記事がいくつかあります(各記事へのコメントはこのページ末尾の "参考" に書きました).
- IFTTTとBeebotteを使ってGoogleHomeからRaspberryPiを操作する
- Google AssistantとRaspberry Piで自宅の家電を操作する
- IFTTTのトリガーおよびアクションをESP8266で実行する
これらを参考にして, さらに通信経路を暗号化することで, Google Home から Raspberry Pi や ESP8266 をなるべく安全に操作できるようにします.
検討したこと
プッシュ通知をどう実現するか (プロトコル, 外部サービス)
- メッセージの方向が外部サーバ(IFTTT や Beebotte)から LAN 内部(ESP8266 や Raspberry Pi)への, いわゆる「プッシュ通知」
- でも ESP8266 や Raspberry Pi をサーバにしてポートを外部に公開したくない
- MQTT サービスを使えばポートを外部に公開せずに比較的簡単に「プッシュ通知」できるらしい
- Beebotte という MQTT ブローカーサービスを使った作例が多い
- 当面 Beebotte 経由で「プッシュ通知」を受け取ることにする
セキュリティをどう確保するか
- IFTTT の Webhooks は複雑な認証が使えないので, トークンを使った単純な認証にならざるをえない(トークンが第三者に知られると危険)
- トークンは定期的に変更することにする
- 最低限, 認証情報(トークンやパスワード)が流れる通信経路は暗号化したい
- でも ESP8266 はリソースが限られているためSSL/TLSで安定動作させることが難しいらしい
- HTTP ならば, Raspberry Pi などに nginx をインストールして SSL/TLS でリバースプロキシとして動作させて ESP8266 の負担を軽くするのがよいらしい
- MQTT で同様のことを実現するには, MQTT ブリッジ接続を SSL/TLS で設定すれば, ESP8266 の負担を軽くしつつ外部との通信経路を暗号化できるはず
- Raspberry Pi に MQTT ブローカーソフトウェアの Mosquitto をインストールして Beebotte とのブリッジ接続を SSL/TLS で設定して, ESP8266 と Beebotte を安全かつ低負荷で接続する
システム構成
想定している構成は以下の通りです. 秘密にしておかなければならないトークンが流れる通信経路は SSL/TLS で暗号化されています. Mosquitto と ESP8266 の間は暗号化なしの MQTT ですがトークンは流しません(LAN 内をメッセージが平文で流れますが).
Google Home -- IFTTT --(Webhooks over HTTPS)-- Beebotte --(MQTT over SSL/TLS)-- Raspberry Pi の Mosquitto --(MQTT)-- ESP8266
以下, 各要素をセットアップする方法について説明します.
Beebotte の設定
MQTT ブローカーサービスの Beebotte のセットアップを行います.
アカウントを作成してチャンネルとリソースを作成して, チャンネル名, リソース名, トークンをメモしておきます. 以下それぞれ channel
, resource
, token_XXXXXXXXXXXXXXXX
として説明します.
詳しい設定方法はこちらの記事やこちらの記事を参照してください.
また, Beebotte のドキュメント Beebotte MQTT Support と Token Based Authentication に目を通しておくとよいです.
なお, トークンが第三者に知られてしまったら何もかも操作されてしまうので, トークンは定期的に変更したほうが安全です.
IFTTT の設定
Web サービス同士を連携させる Web サービス IFTTT でアカウントを作成して以下のように新規アプレットを作成します.
注意: Webhooks の URL の設定で必ず https を指定してください. http だとトークンがインターネット上を平文で流れます.
項目 | 操作または値 |
---|---|
- | New Applet をクリック |
New Applet | this をクリック |
Choose a service | Google Assistant |
Choose trigger | Say a phrase with a text ingredient |
What do you want to say? | ラズパイで $ |
Language | Japanese |
- | Create Trigger をクリック |
- | that をクリック |
Choose action service | Webhooks |
Choose action | Make a web request |
URL | https://api.beebotte.com/v1/data/publish/channel/resource?token=token_XXXXXXXXXXXX |
Method | POST |
Content Type | application/json |
Body | {"data":"{{TextField}}"} |
- | Create action をクリック |
Review and finish | Finish をクリック |
- | Check now をクリック |
IFTTT と Beebotte の連携テスト
ここまで設定すると, Google Home に "ラズパイで..." と話しかけると "..." 以下が Beebotte にテキストメッセージとして送られるはずです.
Beebotte の console を開いて Secret Key
, Channel
, Resource
を入力して Subscribe
を押してしておきます.
Google Home に "ラズパイで..." と話しかけて Beebotte console の Messages に以下のようなメッセージがでれば成功です. ts
はタイムスタンプです.
{ "channel": "channel", "resource": "resource", "eid": "channel.resource", "data": "...", "ts": 1515487525775 }
同様に スマートフォンの Google Assistant からもテストするとよいかもしれません.
Python で Beebotte と MQTT over SSL/TLS で接続するスクリプト
Beebotte のドキュメントには MQTT over SSL/TLS で接続するサンプルがなかったのですが, 以下の Python スクリプトで接続できます.
あらかじめ paho-mqtt
というパッケージを pip
でインストールしておきます. また Beebotte の CA 証明書 mqtt.beebotte.com.pem を Beebotte からダウンロードしてローカルに保存しておきます.
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
import paho.mqtt.client as mqtt
HOST = 'mqtt.beebotte.com'
PORT = 8883
CA_CERTS = 'mqtt.beebotte.com.pem'
TOKEN = 'token_XXXXXXXXXXXX'
TOPIC = 'channel/resource'
def on_connect(client, userdata, flags, respons_code):
print('status {0}'.format(respons_code))
def on_message(client, userdata, msg):
print(msg.topic + ' ' + str(msg.payload))
print(msg.payload.decode("utf-8"))
if __name__ == '__main__':
client = mqtt.Client()
client.on_connect = on_connect
client.on_message = on_message
client.username_pw_set('token:%s' % TOKEN)
client.tls_set(CA_CERTS)
client.connect(HOST, PORT)
client.subscribe(TOPIC)
client.loop_forever()
ターミナルから mqtt_beebotte.py
を実行させておいて, Google Home に "ラズパイで..." と話しかけてターミナルに以下のようなメッセージがでれば成功です.
channel/resource b'{"data":"...","ispublic":true,"ts":1515490359831}'
{"data":"...","ispublic":true,"ts":1515490359831}
このスクリプトは MQTT の Python クライアントで TLS 接続を試す という記事を参考に Beebotte 用に修正しました.
Mosquitto の設定
MQTT ブローカーソフトウェアの Mosquitto を Raspberry Pi にインストールして設定します.
なお, ESP8266 のような非力なデバイスを使わない場合は Mosquitto の設定は不要です. 直接 MQTT over SSL/TLS で Beebotte に接続すれば OK です.
Mosquitto のインストール
Raspberry Pi のターミナルから
sudo apt-get install mosquitto mosquitto-clients
設定ファイルは /etc/mosquitto/
以下にあります.
Beebotte の CA 証明書をダウンロード
Beebotte と SSL/TLS 接続するために, あらかじめ Beebotte から CA 証明書をダウンロードして Mosquitto の証明書用ディレクトリに置きます.
curl -O https://beebotte.com/certs/mqtt.beebotte.com.pem
sudo mv mqtt.beebotte.com.pem /etc/mosquitto/certs/
詳しくは Beebotte の MQTT による接続方法を参照してください.
Mosquitto で Beebotte と SSL/TLS でブリッジ接続する設定
Mosquitto の README によると /etc/mosquitto/conf.d/
以下に .conf
ファイルを作って個別の設定を書けばよいとあるので, /etc/mosquitto/mosquitto.conf
を直接編集せずに /etc/mosquitto/conf.d/bridge-to-beebotte.conf
というファイルを新規作成することにします.
channel
, resource
, token_XXXXXXXXXXXXXXXX
は環境に合わせて書き換えてください.
connection bridge-to-beebotte
address mqtt.beebotte.com:8883
bridge_cafile /etc/mosquitto/certs/mqtt.beebotte.com.pem
cleansession true
try_private false
bridge_attempt_unsubscribe false
notifications false
remote_username token:token_XXXXXXXXXXXXXXXX
topic channel/resource out 0
topic channel/resource in 0
パラメータについては, 主にこのページの設定を参考に Beebotte 用に編集しました. 各パラメータの説明は Mosquitto のマニュアルを参照してください.
Mosquitto 再起動
restart
で再起動して status
でステータスをみて問題なく起動してるか確認します.
sudo service mosquitto restart
sudo service mosquitto status
Mosquitto のブリッジ接続のテスト
以下, channel
, resource
, token_XXXXXXXXXXXXXXXX
は環境に合わせて書き換えてください.
テストのためにターミナルを3つ使います.
1つめのターミナルで, 以下のように Raspberry Pi の MQTT を subscribe しておきます (ESP8266 から Raspberry Pi に MQTT 接続してメッセージを待つことを想定).
mosquitto_sub -h (Raspberry Pi の IP) -t "channel/resource"
2つめのターミナルで, 以下のように Beebotte の MQTT を subscribe しておきます (SSL/TLS が使えるデバイスから直接 Beebotte に MQTT over SSL/TLS 接続してメッセージを待つことを想定).
mosquitto_sub -h mqtt.beebotte.com -t "channel/resource" \
--cafile /etc/mosquitto/certs/mqtt.beebotte.com.pem \
-u "token:token_XXXXXXXXXXXX" -p 8883
3つめのターミナルで, 以下のような MQTT メッセージを publish します(ESP8266 から Raspberry Pi に MQTT メッセージを送ることを想定)
mosquitto_pub -h (Raspberry Pi の IP) -t "channel/resource" -m "Hello World"
1つめと2つめのターミナルに, それぞれ Hello World が表示されれば成功です. 1つめのターミナルには Hellow World が2回出力されるのですが理由がよくわかりません. どなたか教えてください :)
さらに, 3つめのターミナルで, 以下のような POST リクエストを Beebotte に送ります (IFTTT の Webhooks から Beebotte に HTTPS でメッセージを送ることを想定).
curl -d '{"data":"Hello World"}' -H "Content-Type: application/json" -X POST \
https://api.beebotte.com/v1/data/publish/channel/resource?token=token_XXXXXXXXXXXX
1つめと2つめのターミナルに, 以下のような json が表示されれば成功です.
{"data":"Hello World","ispublic":true,"ts":1515424078032}
詳しくは Beebotte の publish に関するドキュメントを参照してください.
さらに, Google Home に "ラズパイで..." と話しかけて, 1つめと2つめのターミナルに以下のような json が表示されれば成功です.
{"data":"...","ispublic":true,"ts":1515488512391}
ここまでできれば, Google Home から Raspberry Pi まで連携ができていることになります.
ESP8266 から MQTT ブローカーに接続するスケッチ
Raspberry Pi の MQTT ブローカー(Mosquitto) に接続してメッセージを待ち, メッセージが来たら LED を点灯・消灯するための回路とスケッチです.
回路図
PIN 13 -- 330 Ω -- LED -- GND
ESP8266 から Raspberry Pi に MQTT 接続するスケッチ
Arduino IDE のメニューから Sketch - Include Library - Manage Libraries... を選択して Library Manager を起動して, PubSubClient
と ArduinoJson
をあらかじめインストールしておきます.
SSID
等を環境に合わせて書き換えてください.
なお, このコード中にはトークンが含まれていません(必要ないです)
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#define SSID "SSID"
#define PASSWORD "password"
#define MQTT_SERVER "(Raspberry Pi の IP)"
#define MQTT_PORT 1883
#define CLIENT_ID "ESP8266Client"
#define TOPIC "channel/resource"
#define LED_PIN 13
#define MAX_BUF 256
WiFiClient espClient;
PubSubClient client(espClient);
void setup() {
pinMode(LED_PIN, OUTPUT);
Serial.begin(74880);
WiFi.begin(SSID, PASSWORD);
Serial.println(SSID);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println();
Serial.println(WiFi.localIP());
client.setServer(MQTT_SERVER, MQTT_PORT);
client.setCallback(callback);
if (client.connect(CLIENT_ID)) {
client.subscribe(TOPIC);
}
}
void loop() {
while (!client.connected()) {
if (client.connect(CLIENT_ID)) {
client.subscribe(TOPIC);
} else {
delay(5000);
}
}
client.loop();
}
void callback(char* topic, byte* payload, unsigned int len) {
// copy from payload to buf
char buf[MAX_BUF];
int end = len < MAX_BUF - 1 ? len : MAX_BUF - 1;
for (int i = 0; i < end; i++) {
buf[i] = (char)payload[i];
}
buf[end] = 0;
Serial.println(buf);
// convert buf to json
StaticJsonBuffer<MAX_BUF> jsonBuffer;
JsonObject& json = jsonBuffer.parseObject(buf);
String data = json["data"];
Serial.println(data);
// commands
if (-1 != data.indexOf("LED を つけ て")) {
digitalWrite(LED_PIN, HIGH);
} else if (-1 != data.indexOf("LED を 消し て")) {
digitalWrite(LED_PIN, LOW);
}
}
ESP8266 から Beebotte に MQTT over SSL/TLS で直接接続するスケッチ
ついでに ESP8266 から Beebotte に MQTT over SSL/TLS で直接接続するスケッチも書いておきます. 通常の MQTT に比べて負荷が高いため ESP8266 で長期間安定動作するかどうかはわかりません(要検証).
Beebotte に直接接続するのでトークンを送る必要がありますが, トークンを送る前に通信相手が本当に mqtt.beebotte.com
であるかどうかを検証(verify)する必要があります.
まず, ターミナルから以下のコマンドを実行して CA 証明書の fingerprint を取得しておきます.
openssl x509 -fingerprint -in mqtt.beebotte.com.pem
出力の 1 行目に以下のような文字列があるので, コロンを空白に置換してソース中の FINGERPRINT
に書いておきます.
SHA1 Fingerprint=XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
スケッチはこちらです. fingerprint の検証に失敗した場合は exit(1)
します. なお証明書には期限があるので, もし検証に失敗したら最新の CA 証明書を取得し直して fingerprint を計算し直してみてください.
SSID
等を環境に合わせて書き換えてください.
// https://github.com/esp8266/Arduino/blob/master/doc/esp8266wifi/client-secure-examples.rst
// https://github.com/esp8266/Arduino/blob/master/libraries/ESP8266WiFi/examples/HTTPSRequest/HTTPSRequest.ino
// https://github.com/knolleary/pubsubclient/issues/84#issuecomment-173677183
// https://gist.github.com/chaeplin/3223074601733fa46d4a
#include <ESP8266WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#define SSID "SSID"
#define PASSWORD "password"
#define MQTT_SERVER "mqtt.beebotte.com"
#define MQTT_PORT 8883
#define CLIENT_ID "ESP8266Client"
#define TOPIC "channel/resource"
#define TOKEN "token:token_XXXXXXXXXXXX
// openssl x509 -fingerprint -in mqtt.beebotte.com.pem
#define FINGERPRINT "XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX"
#define LED_PIN 13
#define MAX_BUF 256
WiFiClientSecure espClient;
PubSubClient client(espClient);
void setup() {
pinMode(LED_PIN, OUTPUT);
Serial.begin(74880);
WiFi.begin(SSID, PASSWORD);
Serial.println(SSID);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println();
Serial.println(WiFi.localIP());
// need to verify fingerprint BEFORE sending token
espClient.connect(MQTT_SERVER, MQTT_PORT);
if (espClient.verify(FINGERPRINT, MQTT_SERVER)) {
Serial.println("fingerprint matches");
} else {
Serial.println("fingerprint doesn't match");
exit(1);
}
espClient.stop();
client.setServer(MQTT_SERVER, MQTT_PORT);
client.setCallback(callback);
if (client.connect(CLIENT_ID, TOKEN, "")) {
client.subscribe(TOPIC);
}
}
void loop() {
while (!client.connected()) {
if (client.connect(CLIENT_ID, TOKEN, "")) {
client.subscribe(TOPIC);
} else {
delay(5000);
}
}
client.loop();
}
void callback(char* topic, byte* payload, unsigned int len) {
// copy from payload to buf
char buf[MAX_BUF];
int end = len < MAX_BUF - 1 ? len : MAX_BUF - 1;
for (int i = 0; i < end; i++) {
buf[i] = (char)payload[i];
}
buf[end] = 0;
Serial.println(buf);
// convert buf to json
StaticJsonBuffer<MAX_BUF> jsonBuffer;
JsonObject& json = jsonBuffer.parseObject(buf);
String data = json["data"];
Serial.println(data);
// commands
if (-1 != data.indexOf("LED を つけ て")) {
digitalWrite(LED_PIN, HIGH);
} else if (-1 != data.indexOf("LED を 消し て")) {
digitalWrite(LED_PIN, LOW);
}
}
テスト
Google Home から "ラズパイでLEDをつけて" と話しかけて ESP8266 の LED が点灯したら成功です. 同様に "ラズパイでLEDを消して" で消灯するはずです.
同様に スマートフォンの Google Assistant からもテストするとよいかもしれません.
"ラズパイで..." と話しかけて ESP8266 を操作するのはちょっと変な感じがするので IFTTT のアプレットを修正して別のメッセージにするとよいかもしれません.
以上で, Google Home から ESP8266 まで連携ができたことになります.
まとめ
IFTTT, Beebotte, Mosquitto, SSL/TLS を使って, なるべく安全に Google Home から Raspberry Pi や ESP8266 を操作することができました. ポイントは以下です.
- 秘密にしなければならないトークンを通信する際は必ず暗号化された通信経路を使う
- 低リソースなデバイスも操作可能 (HTTP より軽い MQTTを使う. 重い SSL/TLS は Raspberry Pi に任せる)
- トークンを定期的に変更するとより安全
参考
https://qiita.com/msquare33/items/9f0312585bb4707c686b
IFTTTとBeebotteを使ってGoogleHomeからRaspberryPiを操作する という記事です. とても参考になります. 通信経路が暗号化されているのでトークンが安全に送られます.https://qiita.com/104ki/items/9dcfe03246099d03d4dd
Google AssistantとRaspberry Piで自宅の家電を操作する という記事です. こちらもとても参考になります. ただし, IFTTT Webhooks の設定がhttp://api.beebotte.com/...
となっている(httpsになっていない)ので, IFTTT と Beebotte 間で秘密にするべきトークンが平文で流れることと,app.py
の設定でPORT = 1883
となっているので記事をそのまま実行すると Raspberry Pi と Beebotte 間でも同様にトークンが平文で流れてしまいます.https://qiita.com/mayfair/items/e761c788a9d8787bc610
IFTTTのトリガーおよびアクションをESP8266で実行する方法. ESP8266 を直接 Beebotte と接続する方法が書かれていてとても参考になります. ただし, この記事も同様に,IFTTT_Action_Beebotte
で ポート1883
が指定されているので, 記事をそのまま実行するとトークンが平文で流れてしまいます.https://www.mgo-tec.com/blog-entry-esp32-googlehome-ifttt-webhooks-blynk.html/3
Google Home Mini を使って、声で ESP32 の LED を光らせてみた という記事です. この記事では MQTT ではなく Blynk を使って IFTTT と ESP32 を中継しています. ただし, この記事も同様に, IFTTT の Webhooks で http を指定しているので認証が平文で流れてしまいます.https://qiita.com/voluntas/items/76f013acdc42fb492b0c
MQTT の Python クライアントで TLS 接続を試す という記事です. Beebotte との MQTT over SSL/TLS 接続についてはこのページを参考にしました.https://www.losant.com/blog/how-to-configure-mosquitto-bridge-to-losant
Mosquitto をブリッジ接続する方法. Mosquitto のブリッジ設定はここの設定ファイルが1番参考になりました.https://mosquitto.org/man/mosquitto-conf-5.html
Mosquitto のドキュメント. 設定ファイルのパラメータの説明.https://beebotte.com/docs/mqtt
Beebotte のドキュメント. MQTT で接続する際の認証方法やサンプルプログラムなど.http://owntracks.org/booklet/guide/bridge/
Mosquitto をブリッジ接続する際の設定ファイル.http://www.steves-internet-guide.com/mosquitto-bridge-configuration/
Mosquitto をブリッジ接続する方法. トピックのリマッピングについて詳しいです.http://www.steves-internet-guide.com/mosquitto-bridge-encryption/
Mosquitto を SSL/TLS でブリッジ接続する方法. 結局 Beebotte と接続するにはここに書かれている設定だけではダメでした.
以上