S3の署名付き URLを利用する際に注意する3つのポイント #aws #s3 #lambda #python #serverless

f:id:uchimanajet7:20190329093949p:plain

今回は

みんな大好きAWS S3ですが、署名付き URL(Pre-Signed URL)という便利で素敵な機能が利用できます。

非常によく利用される機能なので、ここで稚拙な 説明を書くようなことは割愛しますが、公式ドキュメントだと以下 Amazon CloudFront のドキュメントに詳細が書かれています。

docs.aws.amazon.com

今回はこのS3の機能をPython AWS Lambda で利用する際にハマったポイントを共有します。

Boto3 ドキュメントの確認

今回利用したAWS SDK Python(Boto3)のドキュメントを確認してみると

boto3.amazonaws.com

generate_presigned_url(ClientMethod, Params=None, ExpiresIn=3600, HttpMethod=None) となっており ClientMethod に get_object や put_object と言った署名付き URLにて操作を許可したいメソッドを指定して、Paramsbucket namekey name を指定するだけで戻り値として署名付きのURLが得られる完結な作りになっています。

通常は特に気にせず利用すればハマることもないかと思います。 今回は短時間ではなく1週間程度の期限を想定したことと、AWS Lambdaを利用したことでハマるポイントを作ってしまった形になります。

また、これに加えて Amazon S3 における AWS 署名バージョン についても考慮する必要があったため解決まで色々試行錯誤することになりました。

Point.1 実行権限について

AWS Lambdaを利用する場合 IAM role を利用して実行時に必要最低限の権限を付与した IAM policy を利用する形になるかと思います。

今回も個人的に好きな Chalice (Python Serverless Microframework for AWS) を利用しています。詳細は以下をご確認ください。

github.com

uchimanajet7.hatenablog.com

Chalice のメリットである IAM policy 自動生成は当然ながら利用するので generate_presigned_url(ClientMethod, Params=None, ExpiresIn=3600, HttpMethod=None) 関数するだけで問題ないと思っていました。

しかし、実際には自動生成で作成された IAM policy はClientMethod に記載したメソッドを 加味しない 状態で作成されていました。権限的には許可されていない状態となります。

この状態で generate_presigned_url を実行すると 権限不足で実行が失敗する と思っていましたが、実際には実行は問題なく終了して署名付きのURLが取得できます。

この取得したURLにアクセスすると、この段階で権限がないというエラーが表示され何も操作することができません。

だとしたらURL発行の段階で教えて欲しいのですが・・・ちょっと残念な仕様ですね。

以上を踏まえて generate_presigned_urlを利用して署名付きのURLを取得した場合で、アクセス権限が無い といったエラーでアクセスが行い時は、そもそも署名付きのURLを発行している IAM policy が必要な権限を持っているか確認することをお勧めします。

今回はIAM policyの自動生成を利用していたため、この部分に気がつくのが遅くなりだいぶ遠回りをして時間を無駄にした感が強いです・・・単純なところのなので見逃しがちですが、皆様はお気をつけください。

Point.2 有効期限について

IAM policy を追加して無事にアクセスはできるようになりました。 しかし今度は署名付きのURLが指定した有効期限より前に失効してしまう現象に遭遇しました。

この段階で指定していたのはExpiresIn=864000(10日間)でドキュメントには特に制限は書かれていないので原因が分からず困っていました。

boto3.amazonaws.com

しかしWebを検索してみると問い合わせが多い内容のようで以下の公式ドキュメントが展開されていました。

aws.amazon.com

AWS Identity and Access Management (IAM) インスタンスプロファイル: 最大 6 時間有効

AWS Security Token Service (STS): 最大 36 時間有効 (AWS アカウントユーザーや IAM ユーザーの認証情報など、永続的認証情報を使用して署名した場合)

・IAM ユーザー: 最大 7 日間有効 (AWS 署名バージョン 4 を使用した場合)

という記述があることから最大 7 日間なのかーという部分しか確認せずにExpiresIn=604800を指定してみましたが、残念ながら有効期限より前に失効する状況は改善しませんでした。

それもそのはずで、よく読めば利用している認証情報で有効期限の最大値が決まり、その最大値で有効期限が失効するということになります。

AWS LambdaだとIAM role を利用しているので、AssumeRole アクションにてAWS STS から一時トークンを取得し、その一時トークンで認証を行なっていることになります。

そのため、上記ドキュメントに記載のSTS 利用時に合致し指定している7日を待たずに失効する形になっていました。

ちなみに AssumeRole は

docs.aws.amazon.com

By default, the temporary security credentials created by AssumeRole last for one hour. However, you can use the optional DurationSeconds parameter to specify the duration of your session. You can provide a value from 900 seconds (15 minutes) up to the maximum session duration setting for the role. This setting can have a value from 1 hour to 12 hours. To learn how to view the maximum value for your role, see View the Maximum Session Duration Setting for a Role in the IAM User Guide. The maximum session duration limit applies when you use the AssumeRole API operations or the assume-role CLI commands. However the limit does not apply when you use those operations to create a console URL. For more information, see Using IAM Roles in the IAM User Guide.

の記載があるので、有効期限が最大でも12時間となるので今回はこの制限でURLも同時に失効していたことになります。

で、修正するにはどうすればいいのかと言うと・・・

最大 7 日間有効な署名付き URL を作成するには、まず、使用する SDK への IAM ユーザー認証情報 (アクセスキーとシークレットアクセスキー) を指定します。次に、AWS 署名バージョン 4 を使用して署名付き URL を生成します。

の記載にあるように、IAM user を利用する必要があるのでアクセスキーとシークレットアクセスキーをAWS Lambda中で指定して署名付き URLを生成する必要があります。

AWS Lambdaのソースコード中にアクセスキーとシークレットアクセスキーを持たせるわけにはいかないので、今回は定番のAWS Systems Manager パラメータストアを利用しました。

docs.aws.amazon.com

使う側からするとSTSの有効期限を7日までに伸ばす方法があるか、URL文字列にこの情報を含めずにURLを作ってくれる方が嬉しいんですが・・・そこは将来に期待しております。

あとは、以下のページにあるスニペットをそのまま使えば 問題なく7日間有効な署名付き URLが生成できます。

aws.amazon.com

import boto3
from botocore.client import Config

# Get the service client with sigv4 configured
s3 = boto3.client('s3', config=Config(signature_version='s3v4'))

# Generate the URL to get 'key-name' from 'bucket-name'
# URL expires in 604800 seconds (seven days)
url = s3.generate_presigned_url(
    ClientMethod='get_object',
    Params={
        'Bucket': 'bucket-name',
        'Key': 'key-name'
    },
    ExpiresIn=604800
)

以上を踏まえて 署名付きのURLが指定した有効期限より前に失効する場合は、利用している認証情報一時トークンを使用していないかを確認することをお勧めします。

また、合わせて署名バージョンについても SigV4AWS 署名バージョン 4)を利用する必要があるため、確認することをお勧めします。

今回はIAM roleで取得した一時トークンを利用していたことと、署名がSigV2で行われていたことの2点が問題となっていました。 SigV4を利用していないだけかと思い、もう一つの認証情報の件にたどり着くのが遅くなりました。皆様はお気をつけください。 今回は最終的にAWSサポートに確認して作業を行いましたが、AWSサポートは対応が素晴らしいのでもっと早めに確認するべきだったと。

Point.3 署名バージョンについて

前述のSigV4AWS 署名バージョン 4)を利用する件については、ここ最近のSigV2 廃止の話題でご存知かもしれません。

dev.classmethod.jp

docs.aws.amazon.com

Amazon S3 における AWS 署名バージョン 2 の廃止

Amazon S3 における署名バージョン 2 は廃止され、署名バージョン 2 の最終サポートは 2019 年 6 月 24 日に終了します。2019 年 6 月 24 日以降、Amazon S3 は署名バージョン 4 を使用して署名された API リクエストのみを受けつけます。

ということで、上記のページを読んでみると利用しているBoto3(Python)については

この SDK または製品を使用する場合 この SDK バージョンにアップグレード Sigv4 を使用するために、クライアントでコード変更が必要ですか。
Boto3 1.5.71 (Botocore)、1.4.6 (Boto3) にアップグレード。 はい

との記載がありました。Boto3のバージョンアップだけではなくクライアントでコード変更が必要 という記載があります。 これはPoint.2で紹介したコードスニペットをみると分かりますが

aws.amazon.com

import boto3
from botocore.client import Config

# Get the service client with sigv4 configured
s3 = boto3.client('s3', config=Config(signature_version='s3v4'))

S3のクライアントconfig=Config(signature_version='s3v4'))作成時に明示的に SigV4の利用を宣言する必要があるためとなります。

コードとしては以下のあたりが該当するかなーと。

github.com

github.com

署名バージョンの確認については、S3のログを利用して確認しました。

docs.aws.amazon.com

最近のアップデートでログに署名バージョンが出力されるようになったようです。

dev.classmethod.jp

docs.aws.amazon.com

Change Description Date
Added new fields to the server access logs Amazon S3 added the following new fields to the server access logs: Host Id, Signature Version, Cipher Suite, Authentication Type, and Host Header. For more information, see Amazon S3 Server Access Log Format. March 5, 2019

S3 サーバーアクセスのログはベストエフォートでの配信となるので 1時間程度経過したのちに配信されることになるかと思います。

docs.aws.amazon.com

ベストエフォート型のサーバーログ配信

サーバーアクセスログレコードの配信は、ベストエフォートで行われます。ログ記録用に適切にバケットを設定した場合、そのバケットへのほとんどのリクエストについてログレコードが配信されます。ほとんどのログレコードは、記録された時間から数時間以内に配信されますが、配信間隔は短くなる場合もあります。

サーバーログの完全性や適時性は保証されません。リクエストのログレコードが、リクエストが実際に処理されてからかなり後に配信されたり、配信すらされないこともあり得ます。サーバーログの目的は、バケットに対するトラフィックの特性を理解することです。ログレコードが失われることはまれですが、すべてのリクエストが完全に報告されるとは限りません。

サーバーのログ作成機能はベストエフォート型であるため、AWS ポータルで利用できる使用状況レポート (AWS マネジメントコンソール でレポートされる請求およびコスト管理レポート) には、サーバーログに記録されていないアクセスリクエストが含まれる場合があります。

さらに今回はログを確認する手段としてDatadogを利用しました。 ログインテグレーションを行なっていれば検索も簡単に行えますし 他のAWS関連メトリクスと1箇所で確認できるのは非常に便利ですね。

docs.datadoghq.com

以上を踏まえて 署名バージョンについては、Boto3(Python)に関してはSigV4を使うために明示的にプログラム中でバージョン指定が必要であり、これを行わないとSigV2で通信されていることがわかりました。

単純にSDKのアップデートでは対応できないことになるので、S3に対してアクセスを行なっているプログラムを確認する必要があります。

今後はSDK側のデフォルトがSigV4に変更というような方向で対応していただけると簡単で良いですが、影響範囲が大きそうなので無しの方向なのかなぁ

まとめ

  • Amazon Simple Storage Service (Amazon S3)

    aws.amazon.com

  • Simple って難しいなと思いました

  • AWSサポートは対応が相変わらず素晴らしいのでちゃんと利用しましょう
  • SigV2 がデフォルトなのは知らなかったです
  • STSの期限についてはworkaroundでもいいので延長できる方法が欲しいです
  • 権限がないのにURLが発行できるのもエラーで終わってもらえると良いきがします
  • Boto3側のドキュメントにSigV4の話とかリンクでいいので追加されていると親切だと思いました
  • Datadogはログインテグレーションして検索も簡単に!
  • 他のAWSメトリクスも合わせて1箇所で状態が確認できるので便利でした
  • 相変わらずドキュメントの読み込みが甘くて余計な手間が増えているなーと
  • ドキュメントはちゃんと読みましょう!

以上になります。