さくらのIoT Platformの署名をAWS Lambdaで検証する
ども、大瀧です。
さくらのIoT Platformのαテストに参加しています。ドキュメントにてOutgoing Webhookのメッセージ署名アルゴリズムが公開されたので、今回はメッセージ署名をAWS Lambdaで検証するコードサンプルをご紹介します。
AWSの構成
さくらのIoT PlatformをAWSに連携するのエントリーと同じく、さくらのIoT PlatformのOutgoing WebhookでAmazon API GatewayのエンドポイントにPOSTリクエストを送出します。今回はAPI GatewayからAWS Lambdaを呼び出し、Lambdaでのリクエスト処理の中でメッセージ署名を検証することにしました *1。
AWS Lambdaの構成
今回はコードをランタイムNode.js 4.3向けに記述しました。古いNode.jsバージョンのランタイムでは動かないコードなので、注意してください。Outgoing Webhookのメッセージ署名はHMAC-SHA1で署名されているので、共有鍵(secret)を決めておき、後述のIoT Platformの設定で同じ値をセットします。署名自体はCryptoライブラリの一般的な検証フローでカバーできるので、特に難しい記述はありません。メッセージボディーをevent.body
、メッセージ署名をevent.signature
で参照しているので、API GatewayでLambdaに渡す値をこのあとの手順で調整します。
var crypto = require('crypto'); var secret = 'testkey'; # さくらのIoT PlatformのOutgoing WebhookのSecretに合わせる exports.handler = function(event, context, callback) { var signature = crypto.createHmac('sha1', secret).update(event.body).digest('hex'); if (event.signature == signature) { console.log('The request was authorized.'); # 認証成功時の処理 } else { # 認証失敗時の処理 callback(Error('The request was not authorized.')); } callback(null, event); }
あとは、認証成功時の処理としてデータを登録したり、ログを残したりと任意の処理をアレンジしてください。Amazon ESにデータを投げてKibanaで可視化する例を次のブログで書きたいと思っています。
API Gatewayの構成
API Gatewayでは、IoT PlatformのOutgoing Webhookに合わせてリクエストヘッダとLambdaに渡すデータのマッピングを設定します。ポイントは2点あって、メッセージ署名が含まれるX-Sakura-Signature
ヘッダを拾うこととリクエストボディをパースせずにLambdaに渡すことです。では、実際の設定例を見ていきます。
まずは、API GatewayのAPIを作成し、適当なリソース(今回は/
)を作成、Outgoing Webhookの仕様に合わせてPOST
メソッドを定義します。[メソッドリクエスト]の[HTTPリクエストヘッダー]にX-Sakura-Signature
を設定しましょう。
続いて[統合リクエスト]でリクエストを処理するLambda関数を選択します。
[本文マッピングテンプレート]にContent-Type application/json
を追加し、以下のコードを記述します。
{ "body" : "$util.escapeJavaScript($input.body)", "signature" : "$input.params().header.get('X-Sakura-Signature')" }
ここで指定する要素がLambdaのevent
オブジェクトに対応します。body
要素はそのまま$input.body
を渡そうとするとJSONとしてパースされてしまうため、エスケーブ処理をかけています。signature
要素はなんとなく読めると思いますが、リクエストヘッダを取得して渡す感じです。
あと必須ではありませんが、認証に失敗したときのLambdaのエラーメッセージに合わせてAPI Gatewayから4XXレスポンスを返すようにするとAPIぽくなって良いと思います。
設定後は、ステージのデプロイを忘れずに実行しましょう。
さくらのIoT Platformの構成
以前の記事と同じく、デバイスはArduino UNOに接続しサンプルのスケッチを実行します。コントロールパネルでOutgoing Webhookサービスを追加し、API Gatewayのエンドポイントを指定します。
これでOKです。
動作確認
デバイスから1〜2秒間隔でカウントアップするデータが送信されるので、Lambdaのログを有効化してCloudWatch Logsで確認してみます。
認証が成功しているThe request was authorized.
というメッセージが出力されている様子がわかりますね!
手元のマシンからダミーのリクエストを投げてみると。。。
$ curl -s -v -X POST https://XXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/prod/ | jq '.' * Trying 54.192.234.60... * Connected to XXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com (54.192.234.60) port 443 (#0) * TLS 1.2 connection using TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 * Server certificate: *.execute-api.ap-northeast-1.amazonaws.com * Server certificate: Symantec Class 3 Secure Server CA - G4 * Server certificate: VeriSign Class 3 Public Primary Certification Authority - G5 > POST /prod/ HTTP/1.1 > Host: XXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com > User-Agent: curl/7.43.0 > Accept: */* > < HTTP/1.1 403 Forbidden < Content-Type: application/json < Content-Length: 147 < Connection: keep-alive < Date: Tue, 14 Jun 2016 07:54:43 GMT < x-amzn-RequestId: 418d4000-3205-11e6-bc9a-2de3c94b9ce4 < X-Cache: Error from cloudfront < Via: 1.1 df145b7fed9c16526e432395da27a563.cloudfront.net (CloudFront) < X-Amz-Cf-Id: -4kalFNmTpFpr8Se-yBiBnkOmSuKfhWcC4WiJPyjXD91vObbLVqycg== < { [147 bytes data] * Connection #0 to host XXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com left intact { "errorMessage": "The request was not authorized.", "errorType": "Error", "stackTrace": [ "Error (native)", "exports.handler (/var/task/index.js:23:18)" ] }
エラーメッセージを含む403エラーが返ってきました。想定通りの動作ですね。
まとめ
さくらのIoT PlatformのOutgoing Webhookサービスのメッセージ署名を検証する処理を、AWS Lambdaで実装してみました。関係のないデバイスやホストからのデータ送出を遮断し、信頼性の高いデータ収集、分析を行う下地ができました。そろそろデータの可視化や具体的なセンサーデバイスのデータを取ってみたくなってきましたw