AWS Lambdaを使って、S3のデータ利用量を制限する

# はじめに
たまに「S3のデータ量に上限をつけたい」という話を聞きますが、今回はタイトル通り、Lambdaでこれが実現できないかどうか試してみました。

# 仕組み
S3へデータがアップロードされた時にLambda Functionが呼び出されるので、その呼び出しを使って、Putされたデータ量をDynamoDBに記録していきます。
ある一定以上のデータ量が加算されたところで、LambdaからS3バケットに対してバケットポリシーの変更を行い、以降はPutできなくします。仕組みは以下の通りです。
![2014-12-03_172210.png](https://qiita-image-store.s3.amazonaws.com/0/43668/f9ba929f-dfbf-04fd-f948-8ae9ac43b7f6.png)

# 実装
はじめに、DynamoDBにテーブルを作成します。キーは文字列型のBucketとしておきます。
適当な名前で、S3バケットを作ります。S3バケットとDynamoDBは同じリージョンで作成します。
以下のコードをLambda Functionとして登録して、最後にLambda FunctionとS3を関連づけます。

```js
var aws = require('aws-sdk');

var dynamoTableName = "S3バケット名";
var limitDataSize = 5000;//最大データサイズ

exports.handler = function(event, context) {
  var record = event.Records[0];
  var size = record.s3.object.size;
  var region = record.awsRegion;
  var bucket = record.s3.bucket.name;

  var dynamo = new aws.DynamoDB({
    region : region
  });
  var params = {
    TableName : dynamoTableName,
    Key : {
      bucket : {
        'S' : bucket
      }
    },
    AttributeUpdates : {
      size : {
        Action : 'ADD',
        Value : {
          'N' : size
        }
      }
    },
    ReturnValues : "UPDATED_NEW"
  };
  dynamo.updateItem(params, function(err, data) {
    if (err) {
      context.done(err, "error");
    } else {
      console.log("data updated," + JSON.stringify(data));
      var updatedSize = data.Attributes.size.N;
      checkLimit(context, updatedSize, region, bucket);
    }
  });
};
function checkLimit(context, updatedSize, region, bucket) {
  if (updatedSize < limitDataSize) {
    context.done(null, "size=" + updatedSize + " limit=" + limitDataSize);
    return;
  }
  console.log("limit exceeded size=" + updatedSize + " limit="
      + limitDataSize);
  // change S3 bucket policy
  var s3 = new aws.S3({
    params : {
      region : region
    }
  });
  var policy = {
    "Version" : "2012-10-17",
    "Statement" : [ {
      "Sid" : "ignorePut",
      "Effect" : "Deny",
      "Principal" : {
        "AWS" : "*"
      },
      "NotAction" : [ "s3:Get*", "s3:List*", "s3:Delete*" ],
      "Resource" : [ "arn:aws:s3:::" + bucket + "/*" ]
    } ]
  };
  var param = {
    Bucket : bucket,
    Policy : JSON.stringify(policy)
  };

  s3.putBucketPolicy(param, function(err, data) {
    if (err) {
      context.done(err, "error");
    } else {
      context.done(null, "done");
    }
  });
}
```

PutされるごとにDynamoDBにデータサイズが加算され、一定以上になったときにS3バケットにpolicyが適用されます。

# ほかの使い道
Lambdaからポリシーを変えるというのが今回のキモになっていますので、たとえば指定IPから一定量のアクセスがあったらDenyするとか、CloudTrailと連携して、一定回数マネジメントコンソールにログインに失敗したら、翌日までログインできなくする、というようなこともできると思います（これは次回トライしたいです）

# オチ
実は、S3からのPut通知は、新規にオブジェクトを作成したときだけでなく、ファイルを上書きしたり、消したときにも通知され、知る限り見分ける方法がありません。ですので実は今の段階では、保存されているデータ量の制限ではなく、操作したデータの総量での制限になってしまいます。今後S3 notificationがより細かく情報を取れるようになれば、削除や更新時に適切にデータ量を足し引きすることで、保存データ量による制限ができるようになると思います。

# 他のLambda関係Post(外部blog)
[Lambdaを使って、CloudTrailログをCloudSearchに入れて検索する](http://d.hatena.ne.jp/c9katayama/20141124/1416789876)
[Lambdaで顔認識アプリケーションを作る](http://d.hatena.ne.jp/c9katayama/20141125/1416936774)
[S3からJavaを起動するLambda Function](http://d.hatena.ne.jp/c9katayama/20141127/1417045793)
[Lambdaで画像に描画する](http://d.hatena.ne.jp/c9katayama/20141127/1417045793)
またこの投稿は会社とは関係ありません。