12
4

More than 3 years have passed since last update.

Amazon S3 と AWS Lambda を使って PPAP を回避します。

Last updated at Posted at 2020-01-31

みなさん、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 への格納だけでいい!というラクチンな運用になります。

  1. S3 にファイルを格納
  2. S3 のファイルパスを確認(s3:///)
  3. EC2 へログイン(設定済みなら手元のパソコンでもOK)
  4. aws presign コマンドを実行する

レシピ

登場人物

  • Amazon S3
    • 送りたいファイルを格納する場所
    • ファイルが格納されたことを AWS Lambda に通知
  • Amazon SNS
    • 処理が完了したら指定メールアドレスに情報を送信。
    • その情報を社外へ送信するメールにコピペする。
  • AWS IAM
    • アクセスを制御するサービス。
    • 種類に応じて後述のとおり事前署名 URL の有効期限の最大長が決まる
  • AWS SSM
    • パラメータストア機能を使って、IAM のシークレットアクセスキーを保管
  • AWS Lambda
    • Amazon S3 にファイルが格納されたことを検知して、処理実施

構成図

image.png

Amazon S3 バケットを作る

image.png

必要に応じて、デフォルト暗号化など各種設定を有効に!

Amazon SNS の設定

SNS トピックの作成

Amazon SNS のトピックをまず作成します。
image.png
必要に応じて、暗号化やアクセスポリシーなどの設定をしてくださいね!

サブスクリプションの設定

  1. 作成した SNS トピックの詳細を開き、サブスクリプションの作成ボタンをクリック
    image.png

  2. プロトコルをEメール、エンドポイントに処理結果を伝えるメールアドレス(例:自分のアドレス)を指定
    image.png

  3. 指定したメールアドレスに以下のようなメールが着弾するので、Confirm subscriptionリンクをクリックする
    image.png

  4. ブラウザが開き以下のような画面が表示されれば設定完了
    image.png
    ※ 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 ユーザーの設定

  1. IAM ユーザーの作成画面を呼び出し、ユーザー名を指定するとともに、アクセスの種類プログラムによるアクセスにチェック
    image.png
    次のステップ:アクセス権限 ボタンをクリックして進む

  2. グループの作成ボタンをクリック
    image.png

  3. グループ名を任意の名称で指定し、ポリシーの作成ボタンをクリック
    image.png

  4. JSONタブを選択し、後述のJSON(バケット名:your-bucket-nameを適宜置き換え)をコピペし、ポリシーの確認ボタンをクリック
    image.png

policy-create-preSign
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::your-bucket-name/*"
        }
    ]
}

5.ポリシー名として名前に任意の名称を指定し、ポリシーの作成ボタンをクリック
image.png

6.IAMグループの作成画面(ブラウザタブなど)に戻り、更新ボタンをクリック
image.png

7.作成したポリシーを選択して、グループの作成ボタンをクリック
image.png

8.IAMユーザー作成の画面に戻ってくるので、次のステップ:タグボタンをクリック
9.タグの指定は任意。次のステップ:確認ボタンをクリック
10.内容を確認して、問題なければユーザーの作成ボタンをクリック
image.png

11.アクセスキーIDとシークレットアクセスキーを控える
image.png
シークレットアクセスキーは表示リンクをクリックすることで表示される。
保存しておきたい場合は.csvのダウンロードボタンをクリック

認証情報を AWS Systems Manager パラメータストアに格納する

  1. AWS Systems Manager のパラメータストアを開き、パラメータの作成ボタンをクリック
    image.png

  2. 名前、安全な文字列、値に控えておいたシークレットアクセスキーを指定
    image.png
    名前は IAM ユーザー名としました。アクセスキーIDでもよいかもしれません。お好みの方法で。。

  3. パラメータの作成ボタンをクリック

  4. 必要に応じて、ほかのユーザーがこのパラメータへアクセスできないように、 IAM ポリシーの見直しなどを行ってください。

AWS Lambda の設定

Lambda 関数の設定

以下のサンプルコードを参考に Lambda 関数を設定・保存する。
エラーハンドリングなどは適宜入れてください。

lambda_function.py
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 を指定

トリガー の設定

  1. Lambda 関数の Designer に表示辞されている+ トリガーを追加ボタンをクリック
    image.png

  2. トリガーの選択で S3 を選択する
    image.png

  3. バケットやイベントタイプといった設定を行う
    image.png

  4. トリガーの有効化にチェックが入っていることを確認して、追加ボタンをクリック

試してみよう

  1. S3 にファイル格納するファイルを作る
    image.png

  2. test.txt ファイルを S3 にドラッグアンドドロップして、アップロードボタンをクリック
    image.png

  3. ちょっと待つ

  4. メールボックスを確認!
    image.png

  5. 本文を確認!
    image.png

  6. リンクをクリック!
    image.png

  7. あとはこのURLを、ファイル送付したい先方宛てメールにコピペすればOK!

ちなみに、期限が切れると・・・

image.png

この後は

AWS Transfer for SFTP や Amazon API Gateway などと組み合わせて、 Amazon S3 へのファイルアップロードを簡略化していくとさらに使い勝手があがります!

ちなみに

こんな仕組みが使えるようになったらいいなぁ、と
妄想しながら作りました。お察しください。

参考資料

AWS Lambda を使ってS3の署名付きURLを自動的に発行する
https://qiita.com/aquaviter/items/ae32b24ccef491bf9e7d

12
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
4