さくらのIoT Platformの署名をAWS Lambdaで検証する

2016.06.14

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

ども、大瀧です。
さくらのIoT Platformのαテストに参加しています。ドキュメントにてOutgoing Webhookのメッセージ署名アルゴリズムが公開されたので、今回はメッセージ署名をAWS Lambdaで検証するコードサンプルをご紹介します。

AWSの構成

さくらのIoT PlatformをAWSに連携するのエントリーと同じく、さくらのIoT PlatformのOutgoing WebhookでAmazon API GatewayのエンドポイントにPOSTリクエストを送出します。今回はAPI GatewayからAWS Lambdaを呼び出し、Lambdaでのリクエスト処理の中でメッセージ署名を検証することにしました *1

sakura-iot21

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を設定しましょう。

sakura-iot32

続いて[統合リクエスト]でリクエストを処理するLambda関数を選択します。

sakura-iot33

[本文マッピングテンプレート]にContent-Type application/jsonを追加し、以下のコードを記述します。

sakura-iot34

{
  "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ぽくなって良いと思います。

sakura-iot37

設定後は、ステージのデプロイを忘れずに実行しましょう。

さくらのIoT Platformの構成

以前の記事と同じく、デバイスはArduino UNOに接続しサンプルのスケッチを実行します。コントロールパネルでOutgoing Webhookサービスを追加し、API Gatewayのエンドポイントを指定します。

sakura-iot04

これでOKです。

動作確認

デバイスから1〜2秒間隔でカウントアップするデータが送信されるので、Lambdaのログを有効化してCloudWatch Logsで確認してみます。

sakura-iot36

認証が成功している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

参考URL

脚注

  1. API Gatewayには認証・認可ハンドラを定義するCustom Authorizerという機能(参考記事)があるのですが、Custom AuthorizerはBearer Tokenの検証を目的とするため、リクエストボディをハンドラに渡す手段がありません。JWTなどトークンのみで検証できる仕組みであれば良いのですが、今回のWebhookではヘッダ、ボディともに固定されているためCustom Authorizerは適用できないと判断しました。