SORACOM Funnelを利用してKinesis FirehoseにJSONを送信してS3にPUTする(ついでにDynamoDBに書き込む)

はじめに

今頃Kinesis Firehoseとか今更感がありますがちゃんと使ってみたかったってのと、FunnelからFirehoseにJSONを投げたいという要望が舞い込んできたので、あんまりニーズはないと思いますが記事にしてみた

処理フロー

※点線部分はおまけ

funnel_firehose.PNG

Create S3

FirehoseがPUTするためのS3を作成しておきます。(作成する方法は省略します)

Create Kinesis Firehose

コンソールからFirehoseの画面へ移動して「Create delivery stream」からFirehoseを作成します
まだ東京リージョンで利用することはできないので、オレゴンリージョンで作成しました

  1. 「Delivery stream name」に任意の文字列を入力し、そのまま「next」をクリック
  2. FirehoseからLambdaをキックすることはしないのでそのまま「next」をクリック
  3. 「Destination」でS3を選択し(おそらくデフォルトで選択されています)、PUTするバケットを選択
  4. 「S3 buffer conditions」の「Buffer interval」を最小の「60」に設定。これで60秒待ってからS3にPUTします
  5. その他のデータを圧縮するか、暗号化するかなどのを任意に設定して、IAMroleを作成します
  6. 新しいIAMroleを作成すると今までの設定で必要な権限がついているIAMroleが作成されます
  7. 「Status」が「ACTIVE」になれば作成完了です

これでFirehoseにPUTRecordされたデータはS3にPUTObjectされます

※AWS-CLIでテストを行う

aws firehose put-record --delivery-stream-name deliveryStreamName --record Data="test"

SORACOM Funnelの設定

新しくSIMグループを作成し、Funnelの設定を行っていきます

  1. 「転送先サービス」から「Amazon Kinesis Firehose」を選択
  2. 「転送先URL」に「https://firehose.[region].amazonaws.com/[deliveryStreamName] 」を入力
  3. 「認証情報」には、Kinesis Firehoseの実行権限があるKey情報を持ったものを作成し、適用します
  4. 「送信データ形式」には「JSON」を選択します

これで、「http://funnel.soracom.io」にリクエストを送るとFirehoseにデータが送信されます

さぁテストの時間だ

先ほど設定したSIMグループのSIMが刺さったデバイスからPOSTメソッドを送信します

 curl -X POST --data @test.json -H "Content-Type: application/json" http://funnel.soracom.io

送信後S3にデータが保存されていれば成功です

おまけ

S3にPUTが行われたときにLambdaをキックしてDynamoDBにデータを保存する
おおまかなフロー
1. Funnel経由でFirehoseにJSONを送信
2. FirehoseがS3にPutRecord
3. S3がLambdaをキック
4. LambdaがS3にPutされたJSONを取得しDynamoDBにPut

Lambda

LambdaのロールにはDynamoDBへの権限を持たせておきます

index.js
const AWS = require("aws-sdk");
const co = require("co");

const dynamodb = new AWS.DynamoDB.DocumentClient({
  region: "us-west-2"
});
const s3 = new AWS.S3();

const dynamoPutData = require("./lib/dynamo_put_data");
const s3GetObject = require("./lib/s3_get_object");


exports.handler = (event, context, callback) => {
  console.log(JSON.stringify(event));
  co(function *() {
    // S3のBucketNameとPUTされたJSONのKey情報を取得
    const bucketName = event["Records"][0]["s3"]["bucket"]["name"];
    const objectKey = event["Records"][0]["s3"]["object"]["key"];
    // S3からJSONファイルを取得
    const s3GetData = yield s3GetObject.getObject(s3, bucketName, objectKey);
    const item = {
      id: "test",
      payloads: s3GetData
    };
    return yield dynamoPutData.putDynamoDB(dynamodb, item);
  }).then(() => {
    console.log("success");
  }).catch((err) => {
    console.log(err);
  });

};
s3_get_object.js
class s3GetObject {

  /**
   * S3からObjectを取得する
   * @param s3
   * @param bucket
   * @param key
   * @returns {Promise}
   */
  static getObject(s3, bucket, key) {
    return new Promise((resolve, reject) => {
      const params = {
        Bucket: bucket,
        Key: key
      };
      s3.getObject(params, (err, data) => {
        if(err) {
          return reject(err);
        } else {
          const getData = JSON.parse(data.Body.toString());
          return resolve(getData["payloads"]);
        }
      });
    });
  }
}

module.exports = s3GetObject;
dynamo_put_data.js
class dynamoPutData {

   /**
   * DynamoDBにデータを保存する(Put)
   * @param {DocumentClient} dynamoDB
   * @param item
   * @returns {Promise}
   */
  static putDynamoDB(dynamoDB, item) {
    return new Promise((resolve, reject) => {
      const params = {
        TableName: "tableName",
        Item: item
      };
      return dynamoDB.put(params, (err, data) => {
        if (err) {
          console.log(err);
          return reject(err);
        } else {
          console.log(data);
          return resolve(data);
        }
      });
    });
  }
}

module.exports = dynamoPutData;

S3

  1. S3のコンソール画面からプロパティ⇒Eventsを選択
  2. 「Add notification」を選択
  3. 「Name」には任意の文字列、「Events」はPut、「Send to」は「Lambda Function」を選択し、先ほど作成したLambdaを選択します

これで作業完了です。
Funnel経由でJSONを送信し、S3やDynamoDBにデータが保存されているか確認してください

まとめ

とりあえずFirehoseがどうしても1分データを保持してしまうのでStreamに比べたら処理時間がかかってしまうようです
というかリアルタイム性がほしければStream使おうかってなりますよねー
あと早く東京に上陸してくれないだろうかって感じですね

ではまた!

続きを読む

SORACOM BeamでバイナリデータをAPIGateway経由でS3にアップロードする

はじめに

あるお客様からLTEモジュールからS3に画像ファイルをアップロードしたとの要望があったので、いろいろな方法を模索したのですが、AWS-CLIなんて積めないし、HTTPリクエストのヘッダーに必要なAWS認証情報を作成できないし、といった感じでどうやって実現しようかと悩んでいました。(AWS-CLIやAWS-SDKのすばらしさに感動したw)
そんなとき「SORACOM Beamを使ってAPIGateway経由で何とかならんかね?バイナリデータのサポートもされたことだし」という神のお告げが降ってきたので実装してみた

どんな感じ?

apigateway_post.PNG

SORACOM Beamって?

下記公式ページから抜粋

SORACOM Beam(以下、Beam)は、IoT デバイスにかかる暗号化等の高負荷処理や接続先の設定を、クラウドにオフロードできるサービスです。
Beam を利用することによって、クラウドを介していつでも、どこからでも、簡単に IoT デバイスを管理することができます。
大量のデバイスを直接設定する必要はありません。

(参考:https://soracom.jp/services/beam/)

S3バケットの作成

まず画像ファイルをアップロードするバケットを作成しましょう
詳しい手順は割愛しますが、バケットポリシーを設定しないとPutObjectできないのでこんな感じで設定しておきます

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AddPerm",
            "Effect": "Allow",
            "Principal": "*",
            "Action": [
                "s3:PutObject"
            ],
            "Resource": "arn:aws:s3:::[Bucket Name]/*"
        }
    ]
}

APIGatewayの作成

次にAPIGatewayを作成します

  1. APIGatewayのコンソール画面を開き、「Create API」をクリックします
  2. 「API name」に任意の文字列を入力し「Create API」で作成します
  3. リソースを作成します。「Actions」から「Create Resource」を選択し「upload」というリソースを作成します
  4. 「/upload」の子階層に「/{key}」といリソースを作成します。「/{key}」はURLパスパラメータとなり、今回はS3にPutする際のKey情報となります
  5. 「/{key}」を選択した状態で「Actions」から「Create method」を選択し「POST」メソッドを作成します。画面はこのような感じになっていると思います。この時点で「/upload/{key}」に対してPOSTメソッドを受け付けるように設定されています
    apigateway_post_test.PNG
  6. 「Integration type:HTTP」「HTTP method:PUT」「Endpoint URL:S3のエンドポイント(ex:https://s3-ap-northeast-1.amazonaws.com/[Bucket Name]/{key})」を選択・入力し「save」をクリックします。そうするとPOSTメソッドが作成されます
    apigateway_post_method.PNG
    「Method Request」を選択して「API Key Required」を「ture」に変更します
  7. 「Actions」から「Deploy API」を選択し、APIをデプロイします。初めての場合は[New Stage]を選択して新しいステージを作成してください。「Invoke URL」を控えておいてください
  8. バイナリデータを受け取る設定をします。「Binary Support」を選択し、「Edit」をクリックして「application/octet-stream」と「image/png」を追加します
  9. 次にSORACOM BeamからAPIGatewayを利用するにあたってAPIキーが必要になるので、APIキーを作成してAPIGatewayと紐付けます。
  10. 「API Keys」を選択し、「Actions」から「Create API Key」をクリック。「Name」に任意の文字列を入力し、「Save」をクリックします。これでAPIキーが作成されました。「API Key」の文字列をどこかに控えておいてください
  11. 「Usage Plans」を選択し、「Create」をクリック。各必須項目に任意の値を入力して「next」をクリック
  12. 「Add API Stage」をクリックして、先ほどデプロイしたAPIGatewayを選択し「Next」をクリック
  13. 「Add API Key to Usage Plan」を選択し、先ほど作成したAPIキーを入力し「Done」をクリック
  14. これで、APIGatewayとAPIキーが結びつきました

SORACOM Beamの設定

最後にSORACOM Beamの設定をします
1. SORACOMのコンソール画面を開きグループを作成します
2. 作成したグループのBeam設定を開き、プラスのアイコンから「HTTPエントリポイント」を選択します
3. 下記内容を入力・設定していきます

設定名:任意の文字列
エントリポイント
  パス:/
転送先
  ホスト名:APIGatewayのInvoke URL
  パス:/upload/test.png
ヘッダ操作
  IMSIヘッダ付与:ON
  署名ヘッダ付与:ON
  事前共有鍵:作成しておいたものを選択。新たに作成する場合は後ほど記述します
カスタムヘッダ:「アクション:置換」「ヘッダ名:X-API-KEY」「値:APIキーの文字列」

※事前共有鍵の作成
「認証情報ID」と「事前共有鍵」に任意の文字列を入力し「登録」をクリックしてください
soracom_key.PNG

これで「http://beam.soracom.io:8888/ 」に対してリクエストを送信すると、APIGatewayにPOSTメソッドが送信されます

送信テスト

SIMのグループをBeamが設定されているグループに変更し、デバイスにSSHでログインします
テスト送信用のpngファイル(下記コマンドでは「test.png」)があるディレクトリに移動し、下記コマンドを入力しS3に画像がアップロードされているかを確認してください

curl -X POST --data-binary "@test.png" -H  "Content-Type: application/octet-stream" http://beam.soracom.io:8888/

成功していれば、S3の対象バケット内に「test.png」がアップロードされていると思います

まとめ

最近よくIoT案件に関わらせていただいているのですが、そもそもセキュアな通信を前提としていないデバイスをクラウドなどと接続したいという要望が増えてきているように感じてきました
SORACOMさんのようなSIMに認証情報を持たせてセキュアな通信を担保してくれるようなサービスを最大限に活用してそういった要望にこたえていきたいと思う今日この頃でした
ただクラウド側での処理が複雑になればなるほどどこかで破綻するんじゃないかなーとも感じていたりいなかったり

あと、SORACOMBeamでAPIGatewayのパスを変数的に持たせることってできないんだろうか
APIGateway側でURLパスパラメータを使ってS3へアップロードするキー名を決めているので、今回のように固定にしてしまうと、アップロードされる画像がずっと上書きされてしまうので、あまりよろしくない・・・
S3のイベントで画像がアップロードされたタイミングでLambdaキックしてリネームしてアップロードしなおすという手もあるけど・・・
Beamの仕様を漁るか・・・
もし、何かよい方法があればコメント等で教えてください!

ではまた!

続きを読む

AWSからS3のセキュリティについて警告メールが来た時の対処方法

AWSからS3の警告メールが来たら

S3バケットのポリシーに関するTIPSです。
SORACOMの松井さんに教えていただいて大変助かったので情報共有として記事にします。

いきなりメールが来る

AWSからある日こんなメールが届きました。どうも特定の条件の人に一斉にメール送ってるようです。

Subject: Securing Amazon S3 Buckets [AWS Account: ******]

Hello,

We’re writing to remind you that one or more of your Amazon S3 bucket access control lists (ACLs) are currently configured to allow access from any user on the Internet. The list of buckets with this configuration is below.

By default, S3 bucket ACLs allow only the account owner to read contents from the bucket; however, these ACLs can be configured to permit world access. While there are reasons to configure buckets with world read access, including public websites or publicly downloadable content, recently, there have been public disclosures by third parties of S3 bucket contents that were inadvertently configured to allow world read access but were not intended to be publicly available.

We encourage you to promptly review your S3 buckets and their contents to ensure that you are not inadvertently making objects available to users that you don’t intend. Bucket ACLs can be reviewed in the AWS Management Console (http://console.aws.amazon.com ), or using the AWS CLI tools. ACLs permitting access to either “All Users” or “Any Authenticated AWS User” (which includes any AWS account) are effectively granting world access to the related content.

For more information on configuring your bucket ACLs, please visit: https://docs.aws.amazon.com/AmazonS3/latest/dev/S3_ACLs_UsingACLs.html

For additional assistance reviewing your bucket ACLs, please visit http://aws.amazon.com/support to create a case with AWS Developer Support.

Your list of buckets configured to allow access from anyone on the Internet are:

バケットの名前

要約すると「おめー、S3のバケットを世界中から見られるように設定してるけどまずいんじゃね?ちゃんと制限しろよ」ということのようです。

とはいえ

指摘されたバケットはいずれもstatic web hostingで使っているバケットです。その性質上、世界中から見えないとあまり意味がないです。
公式ドキュメントでも以下のように「該当バケット以下のすべてのオブジェクトに対するGetObjectメソッドを、全てのアクセスに対して許可しろ」と書いてあります。

http://docs.aws.amazon.com/ja_jp/AmazonS3/latest/dev/WebsiteAccessPermissionsReqd.html

これは困った・・。

そこに救いの手が

Facebookで相談したところ、元・中の人の松井さんが教えてくれました。

Bucket policy でアクセス元IPアドレスを 0.0.0.0/1 と 128.0.0.0/1 からのみ許可するというライフハック

これはどういうことかというと

  • 0.0.0.0/0を開放するとこれは全開放になっちゃうのでこれまでと変わらないから多分また警告が来る
  • ネットマスクを最小値の1として0.0.0.0/1とするとこれは0.0.0.1~127.255.255.255.254のレンジになる
  • 追加で128.0.0.0/1を許可すると、これが128.0.0.1~255.255.255.254になる
  • 結局、2つ合わせると0.0.0.0/0とするのとほぼ等価になる(厳密にいうとブロードキャストアドレスの分があるけど、そんなアドレスからのアクセスはないだろう)

ということになります。ネットマスクの計算については割愛します。

というわけで

以下のように書き換えたら無事終了。

{
    "Version": "2008-10-17",
    "Statement": [
        {
            "Sid": "PublicReadForGetBucketObjects",
            "Effect": "Allow",
            "Principal": {
                "AWS": "*"
            },
            "Action": "s3:GetObject",
            "Resource": "バケットのARN/*",
            "Condition": {
                "IpAddress": {
                    "aws:SourceIp": [
                        "0.0.0.0/1",
                        "128.0.0.0/1"
                    ]
                }
            }
        }
    ]
}

将来的なことを考えるとv6アドレスも足すべきでしょうけどとりあえずはこれでよさそうです。

しかし、そもそもstatic web hostingがONになってる人は対象から外すとかしてくれればいいんですけどねぇ・・。

あと、これで本当に警告が来なくなるのかは未検証です(しばらく待ってみないとわからないので)のでご了承ください。
また届いたら記事をアップデートします。

続きを読む