AWSのEC2インスタンスは、Descriptionに作成者の名前が残らないので、作った人の名前をタグ付けできないかなと調べてみました。
すると、クラスメソッドさんのDeveopers.IOがすぐにヒット。こちらを用いさせていただくことにしました。
この記事を元に、Node.jsの勉強がてら、Primiseを用いて作ってみました。
変更点
ロジックは変更していませんが、その他の変更点は下記。
- asyncの代わりにPromiseを使ってみました。
- ec2オブジェクトを作る際に、EC2インスタンスがあるリージョンの名前が必要になるのですが、これはS3のキーから取得できるので、これを用いてみました。
例えば、下記のようになっているので、us-west-2を抜き出すようにしました。
"AWSLogs/[account id]/CloudTrail/us-west-2/2016/..." - ログ出力にlog4jsを使ってみました。
- ローカル実行用のコードを追加しました。
実際のコード
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を有効化しておけば、実際にインスタンスの変更は行われません。(試すのに便利です。)
var params = {
Resources: [instanceId],
Tags: [{Key: 'createUserArn', Value: createUserArn}],
DryRun: true
};
実行
下記コマンドで実行できます。
$ node aws-ec2-created-usertag.js
AWS Lambdaにインストール
続いて、Lambda上で実行する場合です。
ポリシーの作成
名前は任意です。例えばLambdaCreatedUserTagToEC2のように設定してください。
中身は下記のように設定しましました。(Developers.IOを参考)
{
"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の作成
下記のコマンドを実行して作成します。
$ 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
これで完了です。
試したところ、実際にタグ付けされていました!
(クラスメソッドさん、ありがとうございます。)