Edited at

S3の期限付きURLをLambdaで取得する

More than 1 year has passed since last update.


概要

 S3には、ウェブサイトホスティングを有効にしないまま、期限付きのURLを発行して外部からアクセスできるようにする機能があります。今回は、Lambda(Node.js)からS3の期限付きURLを取得し、CURLで期限付きURLにアクセスしてみます。


用途

 Lambdaから期限付きURLを取得する処理は、サーバレス アーキテクチャでよく登場します。

■ サーバーレス アーキテクチャの認証の流れ

  ServerLess.png


  1. クライアントはAPI Gatewayにアクセスします。API Gatewayは外部からのアクセスに対して認証を行い、S3のファイルに対するアクセス権のチェックを行います。

  2. 認証に通ったことを確認したうえで内部ではLambdaがS3の期限つきURLを取得して応答します。

  3. クライアントはAPI Gatewayから応答されたS3の期限つきURLにアクセスします。

今回の「S3の期限付きURLをLambdaから取得する」は②の部分の処理になります。


GET Object


S3

バケットを作成し、ファイルをアップロードしておきます。

静的ウェブサイトホスティングは、外部から直接アクセスできないように

 ◎ ウェブサイトのホスティングを有効にしない

にセットします。


Lambda


■ IAMポリシー

S3バケット内のファイルに対するGET権限を与えます。


s3-getObject.policy

{

"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject"
],
"Resource": "arn:aws:s3:::<バケット>/*"
}
]
}


■ コード


node.js

exports.handler = (event, context, callback) => {

let AWS = require('aws-sdk');
let s3 = new AWS.S3();
let params = {
Bucket: '<バケット>',
Key: '<オブジェクトキー>',
Expires: <秒数>
};
s3.getSignedUrl('getObject', params, function (err, url) {
callback(null, url);
});
};


■ Lambdaのテスト

Lambdaのテストボタンを押してみます。

Execution result: succeeded に期限付きURLが表示されます。

ExecutionResult.png


CURLで期限付きURLにアクセス


■ 正常なアクセス

Lambdaのテストで表示された期限付きURLにCURLでアクセスしてみます。

クエリパラメータが複数あるのでURLを「""」で囲む必要があります。

$ curl -o <ファイル名> -X GET "<期限付きURL>"

期限内にURLにアクセスすると、<ファイル名>をダウンロードすることができました。


■ 期限切れ

期限が過ぎてからアクセスしてみます。

  期限(<Expires>)<サーバー時刻(<ServerTime>)

のエラーが返ってきてファイルが取得できなくなります。

<?xml version="1.0" encoding="UTF-8"?>

<Error>
<Code>AccessDenied</Code>
<Message>Request has expired</Message>
<Expires>2017-02-04T12:55:14Z</Expires>
<ServerTime>2017-02-04T12:55:36Z</ServerTime>
<RequestId>137B27F263A55608</RequestId>
<HostId>ZrQWyt7Rf0sPwQPfUbYUy34wSFdWZxSBC3JFU8YGU6w0NT+18ibIdAHE5suoC6MoZttTwAI9dyQ=</HostId>
</Error>


■ URLを改竄

URLを改竄してみます。<エポック秒>を改竄してアクセスしてみます。

不正が検知されエラーが返ってきます。

<Error>

<Code>InvalidToken</Code>
<Message>The provided token is malformed or otherwise invalid.</Message>
<Token-0>FQoDYXdzEOrFFFFFFFFFFwEaDDORFHT0g0nL4wg7oSLlAUy2ILzR33Szq98fHk8fOEKj89RBlkVLAsZITfBaKBndNj8MMS0HtKilZ3utMx8krPwFNBSBBBGwzBSAvBUutX4hRQR3qW0jvS4uL6VtTFPmxNyzSQBvdvHhWIZFnXeTp1cd1xxDunPVEaRiJNqwvegcmvVIiVfW1DLCIA1FNHSAp8FF9kKVvjTbtB5nv2BrnlTOcfphLtTyhAxMs1aeMATRc2cJ6fs068fxfJFIifTpfLLYcCQ4kbGhFBmjYB7Kr4U10rmdGY4rZeOenhc9YPeZShgv0QP5IabFBzFB6cFEFa5EQD8o5LLWxAUD</Token-0>
<RequestId>0A2B6F8EC80D0A8F</RequestId>
<HostId>P6VveqnTd9twOSxV94D7vtRvwmiS/k9LYqJh+9iLKtNJcLdNAeHtCrVEUws+Ftfb2bS9gQOItY8=</HostId>
</Error>


■ セキュリティトークンを改竄

セキュリティトークン(x-amz-security-token)を改竄してアクセスしてみます。

シグネチャが一致しないエラーになります。

<?xml version="1.0" encoding="UTF-8"?>

<Error>
<Code>SignatureDoesNotMatch</Code>
<Message>The request signature we calculated does not match the signature you provided. Check your key and signing method.</Message>
<AWSAccessKeyId>XXXXXXXX</AWSAccessKeyId>
<StringToSign>
GET
1486269940
x-amz-security-token:YYYYYYYY
</StringToSign>
<SignatureProvided>rTfGRqWMWAKLixOm/92tqt6WVlA=</SignatureProvided>
<StringToSignBytes>00 01 02 03 ...</StringToSignBytes><RequestId>A5962F972E4E3D14</RequestId>
<HostId>do4mKLzPZZpVmH7eCDhF6cmsPaY53oPF5LYsoLuu65qKUaF2QcTpJXbpXVOEx42xQ/TflrL6cgw=</HostId>
</Error>


■ ファイルが存在しない

期限付きURL発効後にファイルが削除された場合は、「AccessDenied」になります。

<?xml version="1.0" encoding="UTF-8"?>

<Error>
<Code>AccessDenied</Code>
<Message>Access Denied</Message>
<RequestId>9F6816DF55ACC3B4</RequestId>
<HostId>vEh9mzdRPUHi8D3UAyzOn/lc3aV/Bz5aRZNRnbYTU8Vdrc+92Dpy65Kpb+yrnzw9fGHTbtpfsRQ=</HostId>
</Error>


PUT Object


S3

バケットを作成しておきます。

静的ウェブサイトホスティングは、外部から直接アクセスできないように

 ◎ ウェブサイトのホスティングを有効にしない

にセットします。


Lambda


■ IAMポリシー


s3-putObject.policy

{

"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:PutObject"
],
"Resource": "arn:aws:s3:::<バケット>/*"
}
]
}


■ コード


Lambda.js

exports.handler = function (event, context, callback) {

"use strict";

const AWS = require('aws-sdk');
let s3 = new AWS.S3();
const BUCKET = '<バケット>';
const KEY = '<オブジェクトキー>';
const CONTENT_TYPE = 'image/jpeg'; // image/png, image/tiff, etc...

var params = {
Bucket: BUCKET,
Key: KEY,
Body: '',
ContentType: CONTENT_TYPE,
Expires: 1800
};

s3.getSignedUrl('putObject', params, function (err, url) {
if (err) {
callback(err);
return;
}

callback(null, url);
});
};



■ Lambdaのテスト

Lambdaのテストボタンを押してみます。

Execution result: succeeded に期限付きURLが表示されます。

ExecutionResult_put.png


CURLで期限付きURLにアクセス


■ 正常なアクセス

ローカルの /path/to/file.jpg にファイルを用意し、curlを実行します。

curl -H "Content-Type: image/jpeg" --data-binary "@/path/to/file.jpg" -X PUT "<期限付きURL>"

期限内にアクセスすると、ファイルをS3にアップロードすることができました。

以上です。