AWS LambdaからIAM RoleのCredential情報を取得し、RedshiftのCOPY処理に利用する
小ネタです。
先日投稿した以下AWS LambdaのエントリにてRedshiftへのCOPY処理を行う際のコード記述を行いましたが、この時はCredential情報(AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,AWS_SESSION_TOKEN)を別途用意したIAM Roleの情報を直打ちして処理していました。
何か方法があるはずよね、でもどうやるんだろう?とこの時は保留事項としていましたが、先日ブログのコメント欄に、片山 暁雄 a.k.a. #ヤマンさんから『認証情報、変数から取れますよ』とのコメントを頂き、取得方法及び取得した情報を用いてのRedshiftへのCOPY処理も確認出来たので、備忘録として残しておこうと思います。
目次
情報取得はAWS.Credentialsから
パッケージaws-sdkを取り込む事で利用出来る情報AWS.credentialから処理に必要な情報3つ(accessKeyId, secretAccessKey, sessionToken)を得ることが出来ます。(認証情報を用いるだけであれば、パッケージング処理は必要無く、Lambdaの管理コンソール上で試す事が出来ます)
var aws = require('aws-sdk'); : console.log('[access_key_id]:' + aws.config.credentials.accessKeyId); console.log('[secret_access_key]:' + aws.config.credentials.secretAccessKey); console.log('[session_token]:' + aws.config.credentials.sessionToken);
実行結果は以下の様になります。
2014-12-22 00:37:44 UTC+9 2014-12-21T15:37:44.594Z 794efeac-8926-11e4-affd-bd21d15d47b9 [access_key_id]:ASIAXXXXXXXXXXXXXXX 2014-12-22 00:37:44 UTC+9 2014-12-21T15:37:44.594Z 794efeac-8926-11e4-affd-bd21d15d47b9 [secret_access_key]:xeGOXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 2014-12-22 00:37:44 UTC+9 2014-12-21T15:37:44.594Z 794efeac-8926-11e4-affd-bd21d15d47b9 [session_token]:AQoD(中略)BQ==
利用するIAM Policyへの権限付与
後述するNode.jsプログラムをパッケージングし、実行(任意のS3バケット上にCSVファイルをアップロード)してみた結果、処理は実行されたものの途中でログの情報が途絶えてしまっていました。試しにそこで生成されたCOPY文をローカルで実行してみると、以下の様なログが表示されてしまいました。実行権限が足りなかったようです。
# COPY public.table_bbb \ # FROM 's3://xxxxxxxxxx/table_bbb/2014/12/21/query-earthquake-20141221_223919.csv' \ # CREDENTIALS 'aws_access_key_id=XXXXXXXXXX;aws_secret_access_key=YYYYYYYYYY;token=ZZZZZZZZZZ' \ # CSV IGNOREHEADER 1 TIMEFORMAT 'auto' DELIMITER ','; ERROR: S3ServiceException:Access Denied,Status 403,Error AccessDenied,Rid XXXXXXXXXXXXXXXX,ExtRid XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,CanRetry 1 DETAIL: ----------------------------------------------- error: S3ServiceException:Access Denied,Status 403,Error AccessDenied,Rid XXXXXXXXXXX,ExtRid XXXXXXXXX,CanRetry 1 code: 8001 context: Listing bucket=xxxxxxxxxx prefix=table_bbb/2014/12/21/query-earthquake-20141221_223919.csv query: 341932 location: s3_utility.cpp:529 process: padbmaster [pid=28089] -----------------------------------------------
取り敢えず、必要そうな権限として『S3フルアクセス』及び『Redshiftフルアクセス』の権限を別途追加したIAM Policyを作成、Lambda Function実行の際のPolicy Nameに指定してみました。(※実利用・本番環境で利用する際にはこの辺り権限を絞って定める必要があると思われます。今回はサンプル実行と言う事でざっくりな設定としてみました)
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "logs:*" ], "Resource": "arn:aws:logs:*:*:*" }, { "Effect": "Allow", "Action": [ "s3:GetObject", "s3:PutObject" ], "Resource": [ "arn:aws:s3:::*" ] }, { "Action": [ "redshift:*", "ec2:DescribeAccountAttributes", "ec2:DescribeAddresses", "ec2:DescribeAvailabilityZones", "ec2:DescribeSecurityGroups", "ec2:DescribeSubnets", "ec2:DescribeVpcs", "ec2:DescribeInternetGateways", "sns:CreateTopic", "sns:Get*", "sns:List*", "cloudwatch:Describe*", "cloudwatch:Get*", "cloudwatch:List*", "cloudwatch:PutMetricAlarm", "cloudwatch:EnableAlarmActions", "cloudwatch:DisableAlarmActions" ], "Effect": "Allow", "Resource": "*" } ] }
サンプルコードとコード実行(確認)
以下が実行時に利用したサンプルコードとなります。(Node.jsとしてのコードとして美しくない点についてはご了承ください...m(_ _;)m
利用した環境は先日エントリで用いたものを流用しました。指定バケットのtable_bbb配下日付フォルダにファイルをアップロードすると、指定のテーブル(ここではtable_bbb)にデータをCOPYするという流れです。
フォルダを作成した際にもイベントとして検知してしまうようなので、サンプルではファイル名を取れなかった場合はCOPY処理を実行しない様にしています。この辺りも実際の利用環境に応じて必要な制御を行う必要が出てくるでしょう。
console.log('Loading event'); var aws = require('aws-sdk'); var pg = require('pg'); var s3 = new aws.S3({apiVersion: '2006-03-01'}); // 接続文字列 var rsConnectionString = "tcp://<接続ユーザー名>:<接続パスワード>@<接続サーバ名>:<接続ポート番号>/<接続DB名>"; var rollback = function(client) { console.log("ERROR!"); client.query('ROLLBACK', function() { client.end(); }); }; exports.handler = function(event, context) { console.log('Received event:'); console.log(JSON.stringify(event, null, ' ')); // Get the object from the event and show its content type var bucket = event.Records[0].s3.bucket.name; console.log(bucket); var key = event.Records[0].s3.object.key; console.log(key); console.log(aws.config.credentials); s3.getObject({Bucket:bucket, Key:key}, function(err,data) { if (err) { console.log('error getting object ' + key + ' from bucket ' + bucket + '. Make sure they exist and your bucket is in the same region as this function.'); context.done('error','error getting file'+err); } else { console.log('CONTENT TYPE:',data.ContentType); console.log('[access_key_id]:' + aws.config.credentials.accessKeyId); console.log('[secret_access_key]:' + aws.config.credentials.secretAccessKey); console.log('[session_token]:' + aws.config.credentials.sessionToken); var paths = key.split("/"); console.log("-------------------------"); console.log("tablename:" + paths[0]); console.log("year :" + paths[1]); console.log("month :" + paths[2]); console.log("day :" + paths[3]); console.log("filename :" + paths[4]); console.log("-------------------------"); var queryString = ""; queryString += "COPY public." + paths[0] + " "; queryString += "FROM \'s3://" + bucket + "/" + key + "\' "; queryString += "CREDENTIALS \'aws_access_key_id=" + aws.config.credentials.accessKeyId + ";aws_secret_access_key=" + aws.config.credentials.secretAccessKey + ";token=" + aws.config.credentials.sessionToken + "\' " queryString += "CSV "; queryString += "IGNOREHEADER 1 "; queryString += "TIMEFORMAT \'auto\' "; queryString += "DELIMITER \',\'; "; console.log(queryString); if (paths[4] == "") { console.log("not csv files."); } else { console.log("it is csv files."); // 接続開始 var client = new pg.Client(rsConnectionString); console.log(rsConnectionString); client.connect(); console.log("connect go."); // クエリ実行(1).COPY client.query(queryString, function(err, result) { if(err) { console.log(err); return rollback(client); } console.log("COPY Operation Done."); // クエリ実行(2).COMMIT client.query("COMMIT;", client.end.bind(client)); console.log("transaction commit."); // トランザクション終了 client.end(); console.log("transaction end."); }); context.done(null,''); } } } ); };
実行前のデータベースを確認してみます。件数は0件です。
# SELECT COUNT(*) FROM public.table_bbb; count ------- 0 (1 row)
ファイルを所定のバケットにアップロードし、実行ログを確認してみます。何度か処理が走ってますが、実際に接続に向かったのは1度だけであり、時間差で処理が進んでいる事が確認出来ています。
$ aws logs get-log-events \ --log-group-name /aws/lambda/copyS3CsvToRedshift \ --log-stream-name 8af25dcf008948b2ba0b13093358426b | jq -r '.events[].message' | grep connect 2014-12-21T16:25:41.598Z 00376a08-892e-11e4-b02a-3bac3e086988 connect go.
$ aws logs get-log-events \ --log-group-name /aws/lambda/copyS3CsvToRedshift \ --log-stream-name 8af25dcf008948b2ba0b13093358426b | jq -r '.events[].message' 2014-12-21T16:25:23.675Z 2ab9hi0ns7vkgexw Loading event START RequestId: f6291af9-892d-11e4-a022-dd2e8d0539fa 2014-12-21T16:25:25.675Z f6291af9-892d-11e4-a022-dd2e8d0539fa Received event: 2014-12-21T16:25:25.676Z f6291af9-892d-11e4-a022-dd2e8d0539fa {} 2014-12-21T16:25:25.676Z f6291af9-892d-11e4-a022-dd2e8d0539fa xxxxxx-xxxx-xxxxxx 2014-12-21T16:25:25.676Z f6291af9-892d-11e4-a022-dd2e8d0539fa table_bbb/2014/12/22/ 2014-12-21T16:25:25.676Z f6291af9-892d-11e4-a022-dd2e8d0539fa { expired: false, 2014-12-21T16:25:26.377Z f6291af9-892d-11e4-a022-dd2e8d0539fa CONTENT TYPE: binary/octet-stream 2014-12-21T16:25:26.377Z f6291af9-892d-11e4-a022-dd2e8d0539fa [access_key_id]:ASIAXXXXXXXXXXXXX 2014-12-21T16:25:26.377Z f6291af9-892d-11e4-a022-dd2e8d0539fa [session_token]:AQoXXXXXXXXXXXXXXXXX 2014-12-21T16:25:26.377Z f6291af9-892d-11e4-a022-dd2e8d0539fa ------------------------- 2014-12-21T16:25:26.377Z f6291af9-892d-11e4-a022-dd2e8d0539fa tablename:table_bbb 2014-12-21T16:25:26.377Z f6291af9-892d-11e4-a022-dd2e8d0539fa year :2014 2014-12-21T16:25:26.377Z f6291af9-892d-11e4-a022-dd2e8d0539fa month :12 2014-12-21T16:25:26.377Z f6291af9-892d-11e4-a022-dd2e8d0539fa day :22 2014-12-21T16:25:26.377Z f6291af9-892d-11e4-a022-dd2e8d0539fa filename : 2014-12-21T16:25:26.377Z f6291af9-892d-11e4-a022-dd2e8d0539fa ------------------------- 2014-12-21T16:25:26.377Z f6291af9-892d-11e4-a022-dd2e8d0539fa COPY public.table_bbb FROM 's3://xxxxxx-xxxx-xxxxxx/table_bbb/2014/12/22/' CREDENTIALS 2014-12-21T16:25:26.377Z f6291af9-892d-11e4-a022-dd2e8d0539fa not csv files. 2014-12-21T16:25:39.855Z 00376a08-892e-11e4-b02a-3bac3e086988 { : 2014-12-21T16:25:41.518Z 00376a08-892e-11e4-b02a-3bac3e086988 filename :query-earthquake-20141222_012502.csv 2014-12-21T16:25:41.518Z 00376a08-892e-11e4-b02a-3bac3e086988 ------------------------- 2014-12-21T16:25:41.518Z 00376a08-892e-11e4-b02a-3bac3e086988 COPY public.table_bbb FROM 's3://xxxxxx-xxxx-xxxxxx/table_bbb/2014/12/22/query- 2014-12-21T16:25:41.518Z 00376a08-892e-11e4-b02a-3bac3e086988 it is csv files. 2014-12-21T16:25:41.596Z 00376a08-892e-11e4-b02a-3bac3e086988 tcp://xxxxx:XXXXXXXXXX@xxxxx.xxxxx.com:xxxx/XXXXXXXX 2014-12-21T16:25:41.598Z 00376a08-892e-11e4-b02a-3bac3e086988 connect go. END RequestId: 00376a08-892e-11e4-b02a-3bac3e086988 REPORT RequestId: 00376a08-892e-11e4-b02a-3bac3e086988 Duration: 4005.84 ms Billed Duration: 4100 ms Memory Size: 128 MB Max Memory Used: 25 MB START RequestId: f6291af9-892d-11e4-a022-dd2e8d0539fa 2014-12-21T16:28:22.257Z f6291af9-892d-11e4-a022-dd2e8d0539fa Received event: : 2014-12-21T16:28:22.485Z f6291af9-892d-11e4-a022-dd2e8d0539fa filename : 2014-12-21T16:28:22.485Z f6291af9-892d-11e4-a022-dd2e8d0539fa ------------------------- 2014-12-21T16:28:22.485Z f6291af9-892d-11e4-a022-dd2e8d0539fa not csv files. : 2014-12-21T16:28:23.775Z f6291af9-892d-11e4-a022-dd2e8d0539fa COPY Operation Done. 2014-12-21T16:28:23.775Z f6291af9-892d-11e4-a022-dd2e8d0539fa transaction commit. 2014-12-21T16:28:23.775Z f6291af9-892d-11e4-a022-dd2e8d0539fa transaction end. Process exited before completing request END RequestId: f6291af9-892d-11e4-a022-dd2e8d0539fa REPORT RequestId: f6291af9-892d-11e4-a022-dd2e8d0539fa Duration: 1641.45 ms Billed Duration: 1700 ms Memory Size: 128 MB Max Memory Used: 27 MB START RequestId: f6291af9-892d-11e4-a022-dd2e8d0539fa : 2014-12-21T16:31:25.197Z f6291af9-892d-11e4-a022-dd2e8d0539fa filename : 2014-12-21T16:31:25.197Z f6291af9-892d-11e4-a022-dd2e8d0539fa ------------------------- 2014-12-21T16:31:25.197Z f6291af9-892d-11e4-a022-dd2e8d0539fa not csv files. Process exited before completing request END RequestId: f6291af9-892d-11e4-a022-dd2e8d0539fa REPORT RequestId: f6291af9-892d-11e4-a022-dd2e8d0539fa Duration: 3058.05 ms Billed Duration: 3100 ms Memory Size: 128 MB Max Memory Used: 14 MB
ログファイルが出力されている事を確認後、改めて件数を確認してみます。所定の件数分、データがCOPYされているのが確認出来ました。
# SELECT COUNT(*) FROM public.table_bbb; count ------- 19337 (1 row)
まとめ
以上、AWSのCredential情報を直書き設定では無く、IAM Role経由で取得し、その情報を以ってAmazon RedshiftへのCOPY処理を行う実行サンプルの御紹介でした。細かい設定や制御は内容を詰めて行く必要がありますが、これでセキュリティの面でも不安要素が取り除けますね。こちらからは以上です。