はじめに
こちらはSORACOM Advent Calendar 2021 13日目の記事です。
昨日は@ozk009さんによる「Androidスマホで簡易監視カメラをつくる」でした。
今はあまりコードを書かなくても、サービスの組み合わせでここまでできるようになっているのですね。とても勉強になります。
さて、私はSORACOM Arc関係の何かを書きます、と予告に書いていたので、SORACOM Arcの話です。
以前書いた記事「SORACOM Arcを使ってAWS LambdaからSORACOMサービスを利用する」にて、SORACOM Arcで利用しているVPN技術であるWireGuardはネットワークに関する特権(NET_ADMIN)が必要になるため、AWS Lambdaでは基本的には使えない、と書きました。
そして解決方法として、AWS LambdaをVPCの中に入れてSORACOM ArcのルーターとなるNATインスタンスを使う、という構成を提示したのですが、ぶっちゃけ不本意な構成なんですよね。NATインスタンスを動かすと、使っていない時にはコストがかからない、というLambdaの長所がなくなりますし、1つの仮想SIMに対して1つのNATインスタンスが必要になってしまうのも困ります。
これを解決する方法として、WireGuardの通信を単なるアプリケーションの通信として実装する、という方法が考えられます。SORACOM Arcに対する通信をパケットキャプチャしてみると、単なるUDPの通信にすぎないことがわかります。であれば、WireGuardのハンドシェイクやら暗号化を実装してパケットの中身を作れれば、特権なしでも普通に使えるはずです。
その記事を書いた時は(アプリケーションでWireGuardのVPNを実装すればいける可能性はあるけど現実的ではない)ということで断念しましたが、WireGuardのクライアントはさまざまな言語で実装されており、参考にできるコードもあるので、勝算は十分ある、ということで今回は特権なしでSORACOM Arcを使えるツール開発にチャレンジしてみました。参考にしたのはSORACOM公式のツールであるsoratun、およびsoratunから利用しているwireguard-goです。
作ったもの:wireguard-oneshot
今回開発するツールの仕様は以下のものとしました。
- SORACOM Arc(WireGuard サーバー)に接続し、UDPのパケットを1回送信し、その結果を受信して終了する
通常のWireGuardはVPNなので、基本的には常時接続し、UDPに限らずTCPやICMPなどさまざまな通信を利用できるものです。常時接続はLambdaのようなイベント処理と相性が悪いですし、今回は仕組み上パケット処理を自分で実装しないといけないため、まずは簡単なプロトコルに絞りたかったためです。
通常はUDPのパケットを1つだけ飛ばせる通信にはあまり需要ないと思いますが、SORACOM ARCおよびSORACOMのアプリケーションサービスの組み合わせの場合は十分価値があります。ここにUDPパケットを飛ばすと、SORACOM Beamなどによるプロトコル変換やSORACOM Orbitによるデータフォーマット変換により、さまざまなクラウドに対する連携が可能となるためです。素晴らしいですね。
ということでできたのがこちらです。
簡単にいうと以下のような処理をしています。
- 入力を受け取る
- WireGuardのハンドシェイクして、送受信に必要な共通鍵を作成する
- 送信に必要なIP、UDPパケットを作り、ペイロードと一緒に暗号化してUDPで送信する
- UDPで受信して復号し、ペイロードを取り出す
コードの詳細な説明は今回はしません。wireguard-goから利用したコードが多く、自分でもあまり理解できていないところが多いためです。コードが理解できたらそのうちWireGuardの解説記事を書くかもしれません。(WireGuard自体もですが、IPヘッダなどを自分で作るのも面倒だった。。なんかいい方法があればよいのですが)
例外処理なども甘いので現段階で実用レベルではないのですが、とりあえず動くところまではできました。
さっそく試してみましょう!
コマンドとしての利用
コマンドラインツールのダウンロード
まずはコマンドラインから利用してみましょう。プロジェクトをcloneしてビルドしてもよいのですが、ビルド済みのバイナリを用意したのでこちらを使うのが簡単です。
こちらのページから環境に合うものを選んでダウンロードしてください。
https://github.com/1stship/wireguard-oneshot/releases/tag/v0.0.1
この場合は、いったんcontrolを押しながら、アプリケーションを開いてください。
するとこのように実行された結果が表示され、以降普通に起動できるようになります。ついでにヘルプも出てきますので参考にしていただければと思います。
バーチャルSIMの作成
次にバーチャルSIMを作成し、privateKey、publicKey、clientIpAddressを入手します。
1.SORACOMにログインする(説明略)
2.「+SIM登録」をクリックする
3.バーチャルSIMを登録タブを選択し、登録ボタンをクリックする
費用が発生するため注意してください。(初期費用55円、基本料金88円/月、税込、2021/12/13現在)
4.画面に表示される情報を控える
PrivateKey、Address、PublicKey、Endpointの情報が必要になります。コピーしてテキストファイルに残しておくなどしておきましょう。
バーチャルSIMの作成は以上です。
コマンドの実行1
コマンドを実行してみましょう。以下のコマンドで、Unified EndpointにTESTというデータを送信できます。{PrivateKey}、{PublicKey}、{Endpoint}、{Address}には先ほど取得した情報を入力します。ただしAddressの/32の部分は入力しないようにしてください。(10.1.2.3/32であれば10.1.2.3と入力)
./wireguard-oneshot -privateKey "{PrivateKey}" -publicKey "{PublicKey}" -endpoint "{Endpoint}" -clientIpAddress "{Address}(/32は除く)" -destinationIpAddress "100.127.69.42" -destinationPort 23080 -payload "TEST"
うまくいけば応答が返ってきて終了します。うまくいかない場合はエラーが発生して終了するか、待ち状態になります。待ち状態のタイムアウトはかけていないため、終了しない場合はCtrl + Cなどで終了させてください。
ただしこの時点では、グループが設定されていません、というエラーになるはずです。
SIMグループの設定
とりあえず通信できてるっぽいことが確認できたところで、SORACOM Harvestへのデータ保存を試してみましょう。非常に簡単に試すことができます。
1.SIM管理画面にて、新しいSIM管理画面となっていることを確認。なっていなければ新しいSIM管理画面へ移動
2.対象のSIMにチェックを入れ、[操作] - [所属グループ変更]をクリックする
3.新しい所属グループを選択し、新しいグループを作成をクリックする
7.SORACOM Harvest Dataの設定を開き、ONにして保存する
料金について確認されるのでOKにします。
これでグループの設定及びHarvest Dataへの保存設定は終わりです。再度コマンドを実行してみましょう。
コマンドの実行2
同じコマンドを実行すると、今度は201が返ってくるはずです。
./wireguard-oneshot -privateKey "{PrivateKey}" -publicKey "{PublicKey}" -endpoint "{Endpoint}" -clientIpAddress "{Address}(/32は除く)" -destinationIpAddress "100.127.69.42" -destinationPort 23080 -payload "TEST"
念の為保存されているかを確認してみましょう。
SIM管理画面にて、対象のSIMにチェックを入れ、[操作] - [データを確認]をクリックします
一時処理済みデータに「TEST」が入っていればOKです。当然TESTだけではなく、JSONなどのデータを投入することも可能です。
特権なしでコマンドラインからSORACOM Arc経由でSORACOM Harvestおよびさまざまなアプリケーションサービスにデータを送信できることがわかりました。
WireGuardやsoratunなどでVPN接続すると、ネットワークインタフェースが増えたりルート設定が変わったりなど、ネットワーク環境に大きな影響を与えてしまいますが、この方法だと環境に対する影響はありません。より手軽にSORACOM Arcが使えるのではないかと思います。
一方、VPN接続すれば既存のアプリケーションがそのまま使える、という利点は失われます。特に携帯回線を使うSORACOM Airとインターネット回線を使うSORACOM Arcで同じアプリケーションが使える、というのは大きなメリットなので、それが使えなくなるのはもったいないですね。適材適所で使っていきましょう。
AWS Lambda + API Gatewayで利用してみる
次はAWS LambdaでSORACOM Arcを使ってみましょう。このプログラムであれば、特権が必要ないためLambdaでも使えるはずです。また、Webアプリなどに組み込めるようAPI Gatewayと連携してWebAPIとして呼び出せるようにします。
デプロイパッケージの作成
まずAPI Gatewayからの入力を受け取る部分を作ります。コマンドラインと同じパラメータを受け取り、処理に回すようにします。
package main
import (
"encoding/base64"
"encoding/json"
"github.com/1stship/wireguard-oneshot"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
)
type ArcGateway struct {
PrivateKey string `json:"privateKey"`
PublicKey string `json:"publicKey"`
Endpoint string `json:"endpoint"`
ClientIpAddress string `json:"clientIpAddress"`
DestinationIpAddress string `json:"destinationIpAddress"`
DestinationPort int `json:"destinationPort"`
Payload string `json:"payload"`
PayloadFormat string `json:"payloadFormat"`
}
func main() {
lambda.Start(handler)
}
func handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
body := request.Body
var bodyDecoded []byte
if request.IsBase64Encoded {
bodyDecoded, _ = base64.StdEncoding.DecodeString(body)
} else {
bodyDecoded = []byte(body)
}
var input ArcGateway
err := json.Unmarshal(bodyDecoded, &input)
if err != nil {
return events.APIGatewayProxyResponse{
Body: string(err.Error()),
StatusCode: 400,
}, err
}
config := wireguard.Configuration {
PrivateKey: input.PrivateKey,
PublicKey: input.PublicKey,
Endpoint: input.Endpoint,
ClientIpAddress: input.ClientIpAddress,
}
var payload []byte
if input.PayloadFormat == "base64" {
payload, err = base64.StdEncoding.DecodeString(input.Payload)
if err != nil {
return events.APIGatewayProxyResponse{
Body: string(err.Error()),
StatusCode: 400,
}, err
}
} else {
payload = []byte(input.Payload)
}
receivedBuffer, err := wireguard.UdpOneShot(payload, input.DestinationIpAddress, input.DestinationPort, config)
if err != nil {
return events.APIGatewayProxyResponse{
Body: string(err.Error()),
StatusCode: 400,
}, err
}
for i := 0; i < len(receivedBuffer); i++ {
if (receivedBuffer[i] == 0) {
receivedBuffer = receivedBuffer[0:i]
break
}
}
return events.APIGatewayProxyResponse{
Body: string(receivedBuffer),
StatusCode: 200,
}, nil
}
これをGOOS=linux、GOARCH=amd64としてビルドします。ビルドしたバイナリをzipで圧縮するとAWS Lambda用のデプロイパッケージとなります。
また、パッケージ済みのZIPファイルが、リリースページのarc-gateway_linux_amd64_v0.0.1.zipにあるので、こちらをダウンロードして利用しても良いです。
AWS Lambda関数の作成
AWS Lambdaの関数を作成しましょう。ランタイムはGo 1.xとします。
1.AWSにログインする(説明略)
2.AWS Lambdaのサービスページへ移動する(説明略)
3.関数の作成をクリックする
4.関数名を入力し、ランタイムをGo 1.xに変更して、関数を作成する
他の項目はデフォルトのままでよいです。
8.ハンドラを「arc-gateway」に変更する
ハンドラはzipファイルの中の実行ファイル名を指定します。リリースページのZIPファイルはarc-gatewayという名前で実行ファイルを作成しているので、それを指定します。
これでAWS Lambdaの準備は完了です。最後にAPI Gatewayの設定をします。
API Gatewayの設定
AWS Lambdaの関数をWeb APIから呼び出すのに、API Gatewayを利用します。以前のREST API設定に比べ、今はHTTP APIという比較的設定しやすい方法があるため、そちらを利用します。
1.API Gatewayのサービスページへ移動(説明略)
2.APIを作成する
5.POST /でLambdaが起動されるようルートを設定する
メソッドやパスは特にプログラム内ではみていませんが、一番簡単なPOST / で受け取れるように設定しておきます。
8.URLを確認する
$defaultステージのURLを確認しておきます。このURLで呼び出すことができます。
これでAPI Gatewayの準備も完了です。呼び出してみましょう!
WebAPIの呼び出し
コマンドラインの際のパラメータをそのままJSONにするだけです。
curl -H "Content-Type: application/json" -d '{"privateKey":"{PrivateKey}","publicKey":"{PublicKey}","endpoint":"{Endpoint}","clientIpAddress":"{Address}(/32は除く)","destinationIpAddress":"100.127.69.42","destinationPort":23080,"payload":"API Gateway"}' {API GatewayのURL}/
{PrivateKey}、{PublicKey}、{Endpoint}、{Address}は同じように入力、{API GatewayのURL}は先ほど取得したAPI GatwayのURLを指定し、末尾に/をつけましょう。
データを確認すると、API Gatewayというデータが入っています。
AWS Lambda + API Gatewayでの利用も問題ありません。当初の目的は達成です!
API GatewayにCORSの設定をすると、Webアプリから呼び出すこともできるようになります。
LINE Notifyに通知する
Harvestにデータを入れるだけではなく、LINE Notifyへの通知なんかも試してみました。
以前SORACOMのユーザーグループと、LINE Developers Community合同でLINE通知のハンズオンを実施した時のテキストが公開されていましたので、こちらの手順通りやりました。
https://soracomug.github.io/soracom-arc-button-simulator-handson/#0
バーチャルSIMの発行や、SIMグループの作成などは終わっており、WireGuardのインストールなどは必要ないため、以下の4点を実施しました。
-
- LINE Notify 用のトークン取得
-
- SORACOM SIMグループ セットアップのSORACOM Air for セルラー設定
-
- SORACOM Orbitセットアップ
-
- SORACOM Beamセットアップ
こちらはデータをバイナリで送る必要があるため、payloadFormat = base64を追加してWebAPIに送信します。(データは過去にSORACOM Peekでキャプチャしたデータを流用しています)
curl -H "Content-Type: application/json" -d '{"privateKey":"{PrivateKey}","publicKey":"{PublicKey}","endpoint":"{Endpoint}","clientIpAddress":"{Address}(/32は除く)","destinationIpAddress":"100.127.69.42","destinationPort":23080,"payload":"TQEDUQ==","payloadFormat":"base64"}' {API GatewayのURL}/
終わりに
今回は特権を使うことなしにSORACOM Arcを利用するツールを開発してみました。
例外処理やタイムアウト処理、接続の維持やデータの検証など、本来必要な処理をすっ飛ばしているので、実用的と言えるものではないですが、ちょっとしたログデータをSORACOMにアップロードするとか、メールやLINEへの送信などをちょっと足したい、というような場合には結構便利に使えるのではないかと思います。
また、実機がない状態でのバーチャルハンズオンに利用できますね。
SORACOMのユーザー会であるSORACOM UGでは、初心者歓迎のSORACOM UG ビギナーズや、LINE Developers Communityとの合同企画などでバーチャルハンズオンを実施しています。
IoTにご興味ある方は、直近では2021/12/18に企画している「SORACOM UG ビギナーズ #6〜パソコンがあればできるIoT体験〜」などにぜひご参加ください!
明日は@tacckさんです!よろしくお願いします!