みなさん、PPAP をご存知ですか?
以前、流行した、ペンパイナポーアッポーペン ではなく...
PasswordつきZIP暗号化ファイルを送ります
Passwordを送ります
Aん号化(暗号化)
Protocol
のことです。
https://www.jaipa.or.jp/event/isp_mtg/asahikawa_190912-13/190913-3.pdf
つまり、社外に添付ファイルを送る際に使われるプロトコルです。
このプロトコルにはいろいろと問題があるといわれています。
例えば、パスワードを送り忘れたら受信者はいつまでたっても開けません。
受信者はパスワード入りメールがくるまで待ってないといけません。
そして、そもそも、メールでパスワードを送ってよいのかしら。。。
そんな PPAP を使わないで済む仕組みを Amazon S3 と AWS Lambda で作りました。
予備知識
Amazon S3 には事前署名 URL というものを生成する機能があります。
これを使うことで、期間を指定して(執筆時点で最大7日)、URLを知ってる人だけが HTTPS でファイルを取得できます。
https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/dev/ShareObjectPreSignedURL.html
本記事のようなものを作ると嬉しい点
事前署名 URL を作るには、aws cli の presign コマンドを使うことが多いです。
その場合以下のステップになりますが、最初の S3 への格納だけでいい!というラクチンな運用になります。
- S3 にファイルを格納
- S3 のファイルパスを確認(s3:///)
- EC2 へログイン(設定済みなら手元のパソコンでもOK)
- aws presign コマンドを実行する
レシピ
登場人物
- Amazon S3
- 送りたいファイルを格納する場所
- ファイルが格納されたことを AWS Lambda に通知
- Amazon SNS
- 処理が完了したら指定メールアドレスに情報を送信。
- その情報を社外へ送信するメールにコピペする。
- AWS IAM
- アクセスを制御するサービス。
- 種類に応じて後述のとおり事前署名 URL の有効期限の最大長が決まる
- AWS SSM
- パラメータストア機能を使って、IAM のシークレットアクセスキーを保管
- AWS Lambda
- Amazon S3 にファイルが格納されたことを検知して、処理実施
構成図
Amazon S3 バケットを作る
必要に応じて、デフォルト暗号化など各種設定を有効に!
Amazon SNS の設定
SNS トピックの作成
Amazon SNS のトピックをまず作成します。
必要に応じて、暗号化やアクセスポリシーなどの設定をしてくださいね!
サブスクリプションの設定
-
ブラウザが開き以下のような画面が表示されれば設定完了
※ click here to unsubscribe と記載されているリンクはクリックしないでください。せっかく登録したメールアドレスの設定が解除されます。
認証情報ごとの事前署名 URL の最大有効期限
事前署名 URL は作成時に有効期限が決められます。この期限は、作成時に使った認証情報によって最大値が決まります。
- AWS Identity and Access Management (IAM) インスタンスプロファイル: 最大 6 時間有効
- AWS Security Token Service (STS): 最大 36 時間有効 (AWS アカウントユーザーや IAM ユーザーの認証情報など、永続的認証情報を使用して署名した場合)
- IAM ユーザー: 最大 7 日間有効 (AWS 署名バージョン 4 を使用した場合)
お好みの情報を使えばよいですが、使い勝手を考えると IAM ユーザー の情報を使うのがよろしいかと存じます。
※例えば、STS で36時間とした場合でも1日半しかもたないため、土日を挟む恐れがあるとか、受信側が開くのが遅れたりすると取得できないといったことになります。
というわけで、本記事では IAM ユーザー を利用することとします。
IAM ユーザーの設定
-
IAM ユーザーの作成画面を呼び出し、ユーザー名を指定するとともに、アクセスの種類でプログラムによるアクセスにチェック
次のステップ:アクセス権限 ボタンをクリックして進む -
JSONタブを選択し、後述のJSON(バケット名:your-bucket-nameを適宜置き換え)をコピペし、ポリシーの確認ボタンをクリック
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::your-bucket-name/*"
}
]
}
5.ポリシー名として名前に任意の名称を指定し、ポリシーの作成ボタンをクリック
6.IAMグループの作成画面(ブラウザタブなど)に戻り、更新ボタンをクリック
7.作成したポリシーを選択して、グループの作成ボタンをクリック
8.IAMユーザー作成の画面に戻ってくるので、次のステップ:タグボタンをクリック
9.タグの指定は任意。次のステップ:確認ボタンをクリック
10.内容を確認して、問題なければユーザーの作成ボタンをクリック
11.アクセスキーIDとシークレットアクセスキーを控える
シークレットアクセスキーは表示リンクをクリックすることで表示される。
保存しておきたい場合は**.csvのダウンロード**ボタンをクリック
認証情報を AWS Systems Manager パラメータストアに格納する
-
名前、安全な文字列、値に控えておいたシークレットアクセスキーを指定
名前は IAM ユーザー名としました。アクセスキーIDでもよいかもしれません。お好みの方法で。。 -
パラメータの作成ボタンをクリック
-
必要に応じて、ほかのユーザーがこのパラメータへアクセスできないように、 IAM ポリシーの見直しなどを行ってください。
AWS Lambda の設定
Lambda 関数の設定
以下のサンプルコードを参考に Lambda 関数を設定・保存する。
エラーハンドリングなどは適宜入れてください。
import json
import boto3
import os
from datetime import datetime, timezone, timedelta
import urllib.parse
JST = timezone(timedelta(hours=+9), 'JST')
iam=boto3.client('iam')
sns=boto3.client('sns')
ssm=boto3.client('ssm')
userName=os.environ['USERNAME']
# 指定した値*1日の秒数(60*60*24)
expire=int(os.environ['EXPIRE'])*60*60*24
topic_arn=os.environ['TOPIC_ARN']
def lambda_handler(event, context):
# PUTされたファイルがゼロバイトの場合は処理終了
size=event['Records'][0]['s3']['object']['size']
if size==0:
print("対象外のイベント")
return
# S3 Put イベントの中身を取得
bucketName=event['Records'][0]['s3']['bucket']['name']
objectKey=urllib.parse.unquote_plus(event['Records'][0]['s3']['object']['key'])
accessKeyId = get_access_key(userName)
if accessKeyId == 'nothing':
print('AccessKeyがありません')
msg('指定しているIAM ユーザーにAccessKeyが存在しません。')
send_email(msg)
return
secretAccessKey = get_parameter(userName)
s3 = boto3.client('s3', aws_access_key_id=accessKeyId, aws_secret_access_key=secretAccessKey)
request = s3.generate_presigned_url(
ClientMethod = 'get_object',
Params = {
'Bucket' : bucketName,
'Key' : objectKey
},
ExpiresIn = expire,
HttpMethod = 'GET'
)
expireEpoch = request[-10:]
expireTime = datetime.fromtimestamp(int(expireEpoch), JST)
msg = []
msg.append("★事前署名URL対象ファイル:\r\n"+objectKey)
msg.append("\r\n★有効期限(日本時間):\r\n"+str(expireTime))
msg.append("\r\n★事前署名URL:\r\n"+request)
send_email(msg)
return
def get_access_key(userName):
# Status が Active で作成日時が一番新しいアクセスキーを返す
request = iam.list_access_keys(
UserName=userName,
)
createDate = datetime(2000, 1, 1, 0, 0, 0)
accessKeyId = "nothing"
for i in request['AccessKeyMetadata']:
if i['Status'] != 'Active':
return 'noAccessKey'
date = str(i['CreateDate'])[0:19]
date = datetime.strptime(date, "%Y-%m-%d %H:%M:%S")
if createDate <= date:
createDate = date
accessKeyId = i['AccessKeyId']
return accessKeyId
def send_email(msg):
# SNS 経由でメールを送信する。
request = {
'TopicArn': topic_arn,
'Message': '\r\n'.join(msg),
'Subject': 'S3 PreSignUrl for you'
}
response = sns.publish(**request)
return response
def get_parameter(userName):
response = ssm.get_parameter(
Name=userName,
WithDecryption=True
)
return response['Parameter']['Value']
環境変数
- USERNAME
- 作成した IAM ユーザー名を指定
- EXPIRE
- 有効期限を日数で指定(MAX 7日)
- TOPIC_ARN
- 作成した SNS トピックの ARN を指定
トリガー の設定
-
トリガーの有効化にチェックが入っていることを確認して、追加ボタンをクリック
試してみよう
-
ちょっと待つ
-
あとはこのURLを、ファイル送付したい先方宛てメールにコピペすればOK!
ちなみに、期限が切れると・・・
この後は
AWS Transfer for SFTP や Amazon API Gateway などと組み合わせて、 Amazon S3 へのファイルアップロードを簡略化していくとさらに使い勝手があがります!
ちなみに
こんな仕組みが使えるようになったらいいなぁ、と
妄想しながら作りました。お察しください。
参考資料
AWS Lambda を使ってS3の署名付きURLを自動的に発行する
https://qiita.com/aquaviter/items/ae32b24ccef491bf9e7d