AWS LambdaでEC2インスタンスに作成者を自動タグ付け

AWSのEC2インスタンスは、Descriptionに作成者の名前が残らないので、作った人の名前をタグ付けできないかなと調べてみました。

すると、クラスメソッドさんのDeveopers.IOがすぐにヒット。こちらを用いさせていただくことにしました。

http://dev.classmethod.jp/cloud/aws/lambda_create_tags/

この記事を元に、Node.jsの勉強がてら、Primiseを用いて作ってみました。

## 変更点
ロジックは変更していませんが、その他の変更点は下記。

* asyncの代わりにPromiseを使ってみました。
* ec2オブジェクトを作る際に、EC2インスタンスがあるリージョンの名前が必要になるのですが、これはS3のキーから取得できるので、これを用いてみました。    
例えば、下記のようになっているので、us-west-2を抜き出すようにしました。  
"AWSLogs/[account id]/CloudTrail/us-west-2/2016/..."
* ログ出力にlog4jsを使ってみました。
* ローカル実行用のコードを追加しました。

## 実際のコード

```js
var Log4js = require('log4js');
Log4js.configure('log-config.json');
var systemLogger = Log4js.getLogger('system');

var AWS = require('aws-sdk');
var s3 = new AWS.S3();

var zlib = require('zlib');
var Promise = require('bluebird');
var fs = require('fs');

exports.handler = function(event, context) {
    systemLogger.info('Received event:');
    systemLogger.info(JSON.stringify(event, null, "  "));
    var srcBucket = event.Records[0].s3.bucket.name;
    var srcKey = event.Records[0].s3.object.key;
    var region_name = srcKey.split('/')[3];
    systemLogger.debug('bucket.name:', srcBucket);
    systemLogger.debug('s3.object.key:', srcKey);
    systemLogger.debug('region:', region_name);

    // ec2はリージョンセット後に作成
    AWS.config.update({region: region_name});
    var ec2 = new AWS.EC2();

    var params = {
        Bucket: srcBucket,
        Key: srcKey
    };

    new Promise(function(resolve, reject){
        systemLogger.info('Fetching compressed log from S3...:');
        s3.getObject(params, function(err, data){
            if (err) {
                reject(err);
                return;
            }
            resolve(data);
        });
    }).then(function(response) {
        systemLogger.info("Uncompressing log...:");
        return new Promise(function(resolve, reject){
            zlib.gunzip(response.Body, function(err, data){
                if (err) {
                    reject(err);
                    return;
                }
                resolve(data);
            });
        });
    }).then(function(data) {
        var json = data.toString();
        systemLogger.info('CloudTrail JSON from S3:');
        var records;
        try {
            records = JSON.parse(json);
        } catch (err) {
            return Promise.reject('Unable to parse CloudTrail JSON: ' +  err);
        }
        var matchingRecords = records
                .Records
                .filter(function(record) {
                    return record.eventSource.match('ec2.amazonaws.com')
                        && record.eventName.match('RunInstances');
                });
        systemLogger.debug('Size:', matchingRecords.length);
        return Promise.all(matchingRecords.map(function(record){
            systemLogger.info('Filtered JSON:');
            systemLogger.debug(JSON.stringify(record));
            var createUserArn = record.userIdentity.arn;
            var items = record.responseElements.instancesSet.items;
            for(var i = 0; i < items.length; i++) {
                var instanceId = items[i].instanceId;
                systemLogger.info('CreateTags to EC2: ', instanceId);
                var params = {
                    Resources: [instanceId],
                    Tags: [{Key: 'createUserArn', Value: createUserArn}]
                };
                return new Promise(function(resolve, reject){
                    ec2.createTags(params, function(err, data){
                        if (err) {
                            reject(err);
                            return;
                        }
                        resolve(params);
                    });
                });
            }
        }));
    }).then(function(result) {
        systemLogger.info('Finish:', JSON.stringify(result, null, "  "));
        context.succeed();
    }).catch(function(err) {
        systemLogger.error('Reject:', err);
        context.fail();
    });
};

// ローカル実行用
if (!module.parent) {
    var file = './input.json';
    new Promise(function(resolve, reject){
        fs.readFile(file, function(err, data){
            if (err) {
                reject(err);
                return;
            }
            resolve(data);
        });
    }).then(function(data){
        var hoge = (function() {
            var hoge = function() {};
            var p = hoge.prototype;
            p.succeed = function() {};
            p.done = function() {};
            return hoge;
        })();

        var inputJson = JSON.parse(data);
        var mockedContext = new hoge();
        exports.handler(inputJson, mockedContext);
    }).catch(function(err) {
        systemLogger.error(err);
    });
}
```

GitHubにあげておきました。
https://github.com/khiraiwa/aws-ec2-created-usertag
以下、実行方法など。
## ローカルで試す
### 前準備
* AWSのCredentialsは設定済みとします。
* package.jsonのあるディレクトリで下記のコマンドで必要なモジュールをインストールします。  
`$ npm install`
* input.jsonにテスト用のパラメータを入力します。  
デフォルトだと"xxxx"のような値が入っています。

ちなみに、下記のようにcreateTagsに渡すパラメータでDryRunを有効化しておけば、実際にインスタンスの変更は行われません。(試すのに便利です。)

```js

var params = {
    Resources: [instanceId],
    Tags: [{Key: 'createUserArn', Value: createUserArn}],
    DryRun: true
};
```

### 実行
下記コマンドで実行できます。

```bash
$ node aws-ec2-created-usertag.js
```

## AWS Lambdaにインストール

続いて、Lambda上で実行する場合です。
### ポリシーの作成

名前は任意です。例えばLambdaCreatedUserTagToEC2のように設定してください。  
中身は下記のように設定しましました。(Developers.IOを参考)

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": "arn:aws:logs:*:*:*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetObject"
            ],
            "Resource": [
                "arn:aws:s3:::[CloudTrailのログを保存しているバケット]/*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:CreateTags"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}
```

## ロールの作成
続いてロールを作成します。

こちらも名前は任意です。例えばLambdaCreatedUserTagToEC2Roleのように設定します。
RoleTypeはAWS Lambdaを選択します。
中身は先ほど作成したロールをアタッチしてください。
例だとLambdaCreatedUserTagToEC2Role。

## Lamda Functionの作成

下記のコマンドを実行して作成します。

```bash
$ npm install
$ zip aws-ec2-created-usertag.zip -r aws-ec2-created-usertag.js log-config.json node_modules
$ aws --region [作成するリージョン名] lambda create-function --function-name EC2CreatedUserTag --zip-file fileb://[Zipファイルへのパス]/aws-ec2-created-usertag.zip --role [上記で作成したロールのRole ARN] --handler aws-ec2-created-usertag.handler --runtime nodejs --timeout 60 --memory-size 128
```

すると、指定したリージョンのAWS Lambda FuncitonにEC2CreatedUserTagというFunctionが追加されています。

## Event Sourceの設定

最後にLambda Functionを自動実行するためのEvent Sourceを設定しておきます。  
AWSのマネジメントコンソール上で、作成したEC2CreatedUserTagに対して、下記を設定しておきます。

* Event source type: S3
* Bucket: CloudTrailのログをためているバケット
* Event type: Put

これで完了です。
試したところ、実際にタグ付けされていました！
(クラスメソッドさん、ありがとうございます。)