LambdaでEC2作成者をタグ付けする

2015.08.12

西澤です。 複数の担当者でAWSの検証環境を使っていると、誰が作ったのかわからないEC2インスタンスが並んでしまうことがよくあると思います。Lambdaどころかnode.jsもjavascriptも全く触ったことがなかったのですが、Lambdaを使って自動化できないかと考え、実際にやってみました。 主に参考にしたのは、下記のドキュメントです。

計画

CloudTrailとLambdaを連携して、下記のように動かしてみようと考えました。

  1. ユーザがEC2を起動
  2. CloudTrailがEC2起動を検知してS3バケットにログ記録
  3. Lambdaファンクション起動
  4. ログから実行ユーザを特定してEC2にタグ付け

LambdaCreateTags

事前準備

実装の前に以下の準備を行いました。

  • CloudTrail有効化
  • 実行契機となるCloudTrailログの意図的な出力
  • CloudTrailログ(JSON)の確認

JSONファイルには慣れてきたつもりでしたが、CloudTrailから記録されたJSONを読み解くのも大変でした。 今回JSONから抽出しようとした部位を抜粋しておきます。

"Records": [
	{
		:::
		"userIdentity": {
			:::
			"arn": "arn:aws:iam::123456789012:user/iamuser",
			:::
		},
		:::
		"eventSource": "ec2.amazonaws.com",
		"eventName": "RunInstances",
		:::
		"responseElements": {
			:::
			"instancesSet": {
				:::
				"items": [
					{
						"instanceId": "i-xxxxxxxx",
						:::
					},
					:::
				],
				:::
			},
			:::
		}
		:::
	},
	:::
]

IAMロールの作成

Lambdaに付与する権限を必要最低限とする為、IAMロールを事前に準備しました。

  • CloudWatchLogsへのログ書出
  • CloudTrailログ用S3バケットの読取
  • EC2へのタグ付与
{
    "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-ap-northeast-1-bucket/*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:CreateTags"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}

Lambda Function作成とテスト

node.jsの開発環境は下記手順にて準備しました。

ここからが大変でした。ほとんどの部分ははじめに紹介したページからの切り貼りなのですが、正常に動作させるまでに大いに手こずりました。 下記で紹介されているdriver.jsのような仕組みも無いとテストが辛いです。

ということで、↓こんな感じになりました。このスクリプトとasyncをzipファイルにして、CloudTrailログ用S3バケットへのObjectCreatedをトリガにLambda登録しました。

var aws  = require('aws-sdk');
var zlib = require('zlib');
var async = require('async');

//利用するRegionを指定
var DEFAULT_EC2_REGION  = 'ap-northeast-1';

var s3 = new aws.S3();
var ec2 = new aws.EC2({
    region: DEFAULT_EC2_REGION
});

exports.handler = function(event, context) {
    console.log('Received event:');
    console.log(JSON.stringify(event));
    var srcBucket = event.Records[0].s3.bucket.name;
    var srcKey = event.Records[0].s3.object.key;
    async.waterfall([
        function fetchLogFromS3(next){
            console.log('Fetching compressed log from S3...');
            s3.getObject({
               Bucket: srcBucket,
               Key: srcKey
            },
            next);
        },
        function uncompressLog(response, next){
            console.log("Uncompressing log...");
            zlib.gunzip(response.Body, next);
        },
        function createTags(jsonBuffer, next) {
            var json = jsonBuffer.toString();
            console.log('CloudTrail JSON from S3:');
            console.log(json);
            var records;
            try {
                records = JSON.parse(json);
            } catch (err) {
                next('Unable to parse CloudTrail JSON: ' + err);
                return;
            }
            var matchingRecords = records
                .Records
                .filter(function(record) {
                    return record.eventSource.match('ec2.amazonaws.com')
                        && record.eventName.match('RunInstances');
                });
            async.each(
                matchingRecords,
                function(record, createTagsComplete) {
                    console.log('Filtered JSON:');
                    console.log(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;
                        console.log('CreateTags to EC2: ', instanceId);
                        var params = {
                            Resources: [instanceId],
                            Tags: [{Key: 'createUserArn', Value: createUserArn}]
                        };
                        ec2.createTags(params, createTagsComplete);
                    }
                },
                next
            );
        }
    ], function (err) {
        if (err) {
            console.error('createTags failed: ', err);
        } else {
            console.log('createTags normally ended.');
        }
        context.done(err);
    });
};

動作確認

いよいよ実際のテストです。EC2を起動してCloudTrailログが出力されるまでしばらく待ちます。 場合によって5分程度待つので心配しましたが、無事にEC2インスタンスに作成者の情報をタグ設定することができました!

createUserArn

まとめ

取り立てて目新しい機能を用いた訳ではありませんでしたが、Lambda、CloudTrail等のサービスを上手に組み合わせることで、運用に生かせる余地がまだまだありそうです。個人的にはjavascriptの勉強が必要ですが、これからも役立つユースケースが無いか考えて行こうと思います。