boto3を使ってS3のサーバ側暗号化を試す


PythonのスクリプトでS3にオブジェクトをアップロードする際に
サーバ側で暗号化をする方法を調べました。

## S3とオブジェクト暗号化
[参考](http://docs.aws.amazon.com/ja_jp/AmazonS3/latest/dev/UsingEncryption.html): 大きく分けて2種類あります。

### Client-Side Encryption（CSE）

クライアント側がデータを暗号化してからサーバに送信して保存する。
-> サーバ（S3）との送受信の間もデータが保護されている。

### Server-Side Encryption（SSE）

クライアントはデータをそのまま送り、サーバが暗号化してから保存する。
-> サーバ（S3）に保管されている間はデータが保護されている。

後者はクライアント側で暗号化についてあれこれ考える手間が少なく
ダウンロード時にも透過的に復号化がされるため、扱いが楽です。
__※センシティブな情報はもちろん送受信前に暗号化すべきです__

## S3とServer-Side Encryption

[参考](http://docs.aws.amazon.com/ja_jp/AmazonS3/latest/dev/serv-side-encryption.html): S3におけるSSEの選択肢にも種類があります。

||SSE-S3| SSE-KMS | SSE-C |
|--------|--------|--------|--------|
|暗号化鍵|S3が裏側で管理している鍵|AWSのKMSサービスで管理している鍵|ユーザが管理している鍵|
|特徴|すべてAWSが管理するため、手間が少ない。ただし、鍵へのアクセス権限などが操作できず、その鍵はS3へのデータ保存にしか使用できない。|IAMユーザやロールを操作して鍵自体へのアクセス権限を柔軟に変更できる。SSE-S3より小回りが効くものの、逆に設定項目は増える。|使用する鍵や保存場所を選べる。もっとも自由度が高いが、鍵の安全な保管を自分で担保する必要がある。|

極力AWS側に役割を任せたいため、今回試したのはSSE-S3とSSE-KMSの2つです。

## Management Consoleでの事前確認
ManagementConsoleでオブジェクトをバケットに保存しようとすると
途中で暗号化の選択肢が出てきます。
![0.png](https://qiita-image-store.s3.amazonaws.com/0/43116/c7024e4a-afbf-ed9c-6abc-f7f10305c455.png "0.png")

"Amazon S3 master-key" はSSE-S3のことです。
選択しても特に選択肢は増えません。

![1.png](https://qiita-image-store.s3.amazonaws.com/0/43116/381b5afc-fcd6-f499-ff4b-a053f146b451.png "1.png")

"AWS KMS master-key"はAWS-KMSを意味します。
選択すると使用する鍵の選択肢が出てきます。

- "aws/s3"はKMSがS3サービス用に勝手に作成しようとする鍵
- "test_key"はKMSで作成した鍵（私が事前に作成したもの）
- Custom KMS ARNを選択するとARNの記述で鍵を指定可能

![2.png](https://qiita-image-store.s3.amazonaws.com/0/43116/48f3dbeb-10f6-3834-f019-3d732b3f0865.png "2.png")

試しにSSE-S3で保存してみると、オブジェクトの詳細画面でこのように表示されます。

![3.png](https://qiita-image-store.s3.amazonaws.com/0/43116/45a69b33-7549-f68b-71b3-1f29b5335f55.png "3.png")

この作業をスクリプト（boto3）から行います。

## スクリプトの記述

S3の[ドキュメント](http://docs.aws.amazon.com/ja_jp/AmazonS3/latest/dev/UsingServerSideEncryption.html)にはfor Pythonの記述がないため
他の言語のものや[boto3のドキュメント](http://boto3.readthedocs.io/en/latest/reference/services/s3.html)を参考に書いてみました。
実行環境はLambdaのPython3.6ですが、どこでも書き方や挙動はさほど変わらないと思います。

S3やKMSの鍵にアクセスできるように、ロールはいい感じに書き換えてください。

### SSE-S3

```python:SSE-S3
import boto3

def lambda_handler(event, context):
    
    file_name = 'test.txt'

    # 一時的に使える'/tmp'にファイルを作成
    with open('/tmp/' + file_name, 'w') as f:
        f.write('hoge')
    
    # ファイルのアップロード時、ExtraArgsに暗号化方式を指定
    response = boto3.client('s3').upload_file(
        Filename='/tmp/' + file_name,
        Bucket='nanakenashi-test',
        Key=file_name,
        ExtraArgs={'ServerSideEncryption': 'AES256'})

    return True
```

アップロードの際に静的な引数が増えるだけで非常に単純です。
なお、現時点（2017/09/16）で暗号化方式は `AES256` のみのようです。
実際に保存されたオブジェクトは、先程と同じかたちになっているのがわかります。

![4.png](https://qiita-image-store.s3.amazonaws.com/0/43116/955bb06b-d419-4b92-f3b4-5c972d371062.png "4.png")

### SSE-KMS

#### S3用のデフォルト鍵を使用する場合

前述のスクリプトの `ExtraArgs` のみを書き換えます。

```python:SSE-KMS（S3用デフォルト鍵を使用）
ExtraArgs={
    'ServerSideEncryption': 'aws:kms',
    }
```

保存されたオブジェクトはS3用のデフォルト鍵で暗号化されています。
（この鍵はこのタイミングで作成されたためSSE-S3用の鍵とは別物だと思います）

![5.png](https://qiita-image-store.s3.amazonaws.com/0/43116/5cf5a132-1059-cdd7-d84c-5722ac9ccc68.png "5.png")

ただしこの鍵は設定が変更できないため、SSE-S3と大差がありません。

#### KMSで作成した鍵を使用する場合

```python:SSE-KMS（KMSで作成した鍵を使用）
# 使用する鍵のIDを追加
ExtraArgs={
    'ServerSideEncryption': 'aws:kms',
    'SSEKMSKeyId': 'ea41458h-0c2o-496g-b92e-67441d771282'
    }
```

事前に作成したキーで暗号化されていることがわかります。

![6.png](https://qiita-image-store.s3.amazonaws.com/0/43116/51e659f2-b50d-b697-3189-bc645956e6a3.png "6.png")

## 補足

[SSE-S3](http://docs.aws.amazon.com/ja_jp/AmazonS3/latest/dev/UsingServerSideEncryption.html)と[SSE-KMS](http://docs.aws.amazon.com/ja_jp/AmazonS3/latest/dev/UsingKMSEncryption.html)いわく
> サーバー側の暗号化では、オブジェクトデータのみが暗号化されます。
オブジェクトメタデータは暗号化されません。

また
> バケットに保存するすべてのオブジェクトに対してサーバー側の暗号化を必要とする場合は
バケットポリシーを使用できます。

（難しい日本語ですが…）
つまり、暗号化されていないオブジェクトの保存を禁止することができるため
このバケットポリシーを設定しておけばセキュアな状態を保持しやすくなります。

## まとめ

S3でのデータ保管をよりセキュアにするために
サーバ側暗号化対応の選択肢をいくつか試してみました。

ちなみにSSE-Cについては[このあたり](http://boto3.readthedocs.io/en/latest/reference/services/s3.html#uploading-downloading-files-using-sse-customer-keys)を参考にすれば利用できそうです。