はじめに
こんにちは、新卒一年目のpopopopmanです。今回は練習のためにタイトル通りの機能をAWS上に構築してみました。Amazon S3に置かれたファイルを比較的安全に共有します。
TerraformとPythonを使って実装しました。
背景
ニフティでは実弾演習場と呼ばれるAWSの練習用アカウントが存在します。これを使ってAWSに慣れてみようということで本機能を作成しました。
構成
Amazon S3上にファイルが置かれたら、Amazon EventBridgeがそれを検知し、AWS Lambdaを発火させます。
アーキテクチャ図は以下のようになっています。
ディレクトリ構成は以下のようになりました。
1 2 3 4 |
. ├── main.tf ├── send_url.py ├── send_url.zip |
以下main.tfのコードです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
provider "aws" { region = "ap-northeast-1" # 東京リージョン profile = "send-s3-url" # ~/.aws/credentials } resource "aws_s3_bucket" "my_bucket" { bucket = "任意のユニークなバケット名" # S3バケットの名前 } resource "aws_lambda_function" "send_url" { function_name = "SendUrl" # Lambda関数の名前 handler = "send_url.lambda_handler" # ハンドラーメソッド runtime = "python3.8" # ランタイム role = aws_iam_role.lambda_role.arn # Lambda実行ロールのARN timeout = 5 # タイムアウト時間(秒) filename = "send_url.zip" # ZIPファイルに圧縮されたLambda関数のコード } resource "aws_iam_role" "lambda_role" { name = "LambdaS3ExecutionRole" # Lambda実行ロールの名前 assume_role_policy = jsonencode({ Version = "2012-10-17", Statement = [ { Action = "sts:AssumeRole", Principal = { Service = "lambda.amazonaws.com" }, Effect = "Allow", Sid = "" } ] }) } # Lambda関数に関連付けるIAMロールポリシーの作成 resource "aws_iam_role_policy" "lambda_policy" { name = "LambdaS3Policy" # ロールポリシーの名前 role = aws_iam_role.lambda_role.id policy = jsonencode({ Version = "2012-10-17", Statement = [ { Action = [ "s3:GetObject", # S3オブジェクトの取得アクション ], Effect = "Allow", Resource = [ "arn:aws:s3:::任意のユニークなバケット名/*" # 特定のS3バケットへのアクセス許可 ] }, { Action = [ "logs:CreateLogGroup", # CloudWatch Logsのロググループ作成 "logs:CreateLogStream", # CloudWatch Logsのログストリーム作成 "logs:PutLogEvents", # CloudWatch Logsへのログイベントの送信 "ssm:GetParameter" # Systems Managerのパラメータの取得 ], Effect = "Allow", Resource = "*" # リソースの許可(全てのリソースに対する許可) } ] }) } # S3バケット通知の設定 resource "aws_s3_bucket_notification" "bucket_notification" { bucket = aws_s3_bucket.my_bucket.bucket # 通知を設定するS3バケットの指定 # Lambda関数のトリガーイベントを設定 lambda_function { lambda_function_arn = aws_lambda_function.send_url.arn # トリガーとして使用するLambda関数のARN events = ["s3:ObjectCreated:*"] # トリガーとなるイベント(S3オブジェクト作成時) } } resource "aws_lambda_permission" "allow_bucket" { statement_id = "AllowS3BucketNotification" action = "lambda:InvokeFunction" function_name = aws_lambda_function.send_url.function_name # アクセス許可を付与するLambda関数の名前 principal = "s3.amazonaws.com" source_arn = aws_s3_bucket.my_bucket.arn # アクセス許可を付与するS3バケットのARN } |
send_url.pyのコードです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
<code>import boto3 import json from urllib.request import Request, urlopen def lambda_handler(event, context): # SSMクライアントを作成 ssm_client = boto3.client("ssm") # SSM Parameter StoreからSlack Webhook URLを取得 response = ssm_client.get_parameter( Name="/webhook/url/slack/dm", WithDecryption=True ) webhook_url = response["Parameter"]["Value"] # イベントからS3バケットとオブジェクトキーを取得 bucket_name = event["Records"][0]["s3"]["bucket"]["name"] object_key = event["Records"][0]["s3"]["object"]["key"] # S3クライアントを作成 s3_client = boto3.client("s3") # 署名付きURLを生成 signed_url = generate_signed_url(s3_client, bucket_name, object_key) # slackに通知 post_slack(signed_url, webhook_url, object_key) return {"statusCode": 200, "body": "Notification sent to Slack"} def generate_signed_url(s3_client, bucket_name, object_key, expiration=3600): url = s3_client.generate_presigned_url( "get_object", Params={"Bucket": bucket_name, "Key": object_key}, ExpiresIn=expiration, ) return url def post_slack(message: str, webhook_url: str, object_key: str): headers = { "Content-Type": "application/json", "Content-Disposition": f'attachment; filename="{object_key}"', } data = {"text": message} request = Request( webhook_url, data=json.dumps(data).encode(), headers=headers, ) urlopen(request)</code> |
下準備
.gitignoreの追加
githubでコードを管理する場合、.gitignoreファイルを他のファイルと同じ階層に作成し、
1 |
**/.terraform/* |
を記入。
Pythonファイルを圧縮
Pythonのファイルをzip圧縮。windowsの場合、zipコマンドをインストールするかファイルを右クリックして圧縮を行なってください。
1 |
zip send_url.zip send_url.py |
AWSアクセスキーとシークレットアクセスキーの取得
AWSアクセスキーとシークレットアクセスキー外部に流出しないように大切に保管してください。
- AWS Management Consoleにサインイン。
- サービスメニューから「IAM(Identity and Access Management)」を選択。
- 左側のメニューから「ユーザー」を選択し、「ユーザーの追加」をクリック。
- ユーザーに適切なアクセス権を付与し、アクセスキーとシークレットアクセスキーを生成。
Terraformで読み込むクレデンシャルの設定
~/.aws/credentialsに以下のように記述。
1 2 3 |
[send-s3-url] aws_access_key_id = アクセスキーID aws_secret_access_key = シークレットアクセスキー |
AWSのパラメータストアにSlackのwebhook URLを格納
Slackのwebhook URLを取得したら、
- AWS Management Consoleにサインイン。
- Systems Managerパラメータストアに移動。
- 左側のメニューから「パラメータストア」を選択し、「パラメータの作成」をクリック。
- Nameを”/webhook/url/slack/dm”とし、Valueにwebhook URLを入力してパラメータを作成。
構築
1 2 3 |
terraform plan terraform apply |
を実行してデプロイ。
動作確認
コンソールからAmazon S3にtest.txtをアップロード。
するとSlackに長い署名付きURLの通知が来ました。
アクセスすると、
アップロードしたファイルを見ることができました。
めでたしめでたし。
おわりに
今回は実弾演習場を使ってAWS LambdaからAmazon S3に置かれたファイルの署名付きURLをslackに通知しました。自分で作ると理解が深まりますね。
改良すれば運用を自動化したりなど夢が広がります。