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

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

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

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

　　![ServerLess.png](https://qiita-image-store.s3.amazonaws.com/0/107056/56907db6-6744-d859-d7f6-a28007f69efb.png)

1. クライアントはAPI Gatewayにアクセスします。API Gatewayは外部からのアクセスに対して認証を行い、S3のファイルに対するアクセス権のチェックを行います。
2. 認証に通ったことを確認したうえで内部ではLambdaがS3の期限つきURLを取得して応答します。
3. クライアントはAPI Gatewayから応答されたS3の期限つきURLにアクセスします。

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

# GET Object
## S3
バケットを作成し、ファイルをアップロードしておきます。
静的ウェブサイトホスティングは、外部から直接アクセスできないように
　◎ ウェブサイトのホスティングを有効にしない
にセットします。

## Lambda
#### ■ IAMポリシー
Ｓ３バケット内のファイルに対する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](https://qiita-image-store.s3.amazonaws.com/0/107056/bb6637e5-5f7f-4106-79d5-274052d350f8.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](https://qiita-image-store.s3.amazonaws.com/0/107056/7e1672c7-e33b-72ad-0e83-faaa2acc590f.png)


## CURLで期限付きURLにアクセス
#### ■ 正常なアクセス
ローカルの `/path/to/file.jpg` にファイルを用意し、curlを実行します。

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

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

以上です。