デバイス証明書を使って、S3をダウンロード制限付きのファイル転送サービスとして使う方法

# この記事の目的

メールで数GBのファイルを送りたいが、サイズ制限で送れない。

送るデータにはセキュリティをかけて、第三者にダウンロードさせたくない。

ファイル転送サービスやクラウドストレージを使えば簡単だが、社内的にまずい。

そんなときに、S3＋デバイス証明書だけでファイル転送サービスを自作する方法があります。

![image_kousei_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233180/1f9b01d4-7bbc-d6c3-a371-8748980bd2e4.png)

# 構成図

AWSの構成図は以下の通りです。LambdaやCloudFrontは不要です。

![image_kousei_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233180/3258bed1-98f0-59ae-8c8d-aad8a95e6366.png)

デバイス証明書からアクセス権限を貸与することで、図の赤線部分の認証が通るようになります。

# 利用フロー

利用の流れは以下の通りです。

![flow.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233180/e066b2c9-b378-a8f8-c078-c4ea020c3b92.png)

受け取る人のPCに、特別なアプリケーションやソフトをインストールする必要はありません。
（※Windowsの標準機能だけで動きます）

# セキュリティ

証明書にはパスワード付きpfxを使います（※AWS IoTの「1-Click 証明書作成」で作成した証明書と秘密鍵とルート証明書を1つのファイルにまとめて、パスワードをかけたものです）

S3のセキュリティにpfx証明書を使うことで、以下の状態になります。

![image-cases.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233180/571769b9-da86-338a-e57b-d8b2d25ae31b.png)

また、1つの証明書が許可する範囲は、S3にある1つのファイルパスだけです。

![policy_allow.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233180/c25260b1-bf10-eef7-6d44-64e30c643eed.png)

# 設定の手順

手順が複雑なため、GithubにCloudFormationとバッチファイルを用意しました。
https://github.com/ShotaOki/s3FileDeriver

先に簡単な手順を説明して、後でAWS純正で作業するときの手順を説明します。

## サクッと簡単に作る

前提条件

- AWSアカウントがあること
- メールの送信側と受信側、どちらもWindows10であること

送信側に必要なもの

- AWS アカウント
- Python（3.8で動作検証しています。PyOpenSSLを使います）
- AWS CLI

受信側に必要なもの

- なし（PowerShellが動けばOKです）

#### 事前準備

GithubからバッチファイルをCloneしてきます。

```bash:ソースコードをCloneする
git clone https://github.com/ShotaOki/s3FileDeriver.git
```

Cloneしたら、フォルダの直下を開きます。

![root.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233180/476d9937-6a59-c7e3-5e0f-aeb96684fe58.png)

pythonに依存ライブラリをインストールします。

```bash:OpenSSLをインストールする
pip install -r .\requirements.txt
```

#### リソースを作ります

目的：デバイス証明書、ロール、ポリシーを作ります。
手順：フォルダにある`create.bat`をダブルクリックします。

![image-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233180/8adbe607-0c2c-ee52-60e0-d2631f8349b1.png)

ダブルクリックすると対話型コンソールが出てきて、

- ダウンロードを許可するS3のバケット名
- ダウンロードを許可するS3のファイル名
- AWSアカウントのプロファイル名

を入力します。CloudFormationのリソース作成が2分くらいかかるので、気長に待ちます。

完了すると、`outputs`の下にファイルが作られます。

#### 各証明書をpfx形式にまとめます

目的：「秘密鍵 ＋ PEM ＋ ルート証明書」をpfx形式にまとめます。
手順：フォルダにある`convert.bat`を実行します。

ダブルクリックすると対話型コンソールが出てきて、

- `create.bat`で作成したデプロイ情報ファイル（deploy-info.json）のパス
- 証明書の保護パスワード（好きな文字列）

を入力します。

#### 送信する

`convert.bat`が完了すると、`exports`の下に3つのファイルが作られます。

この`exports`フォルダを圧縮して、メールで渡して、保護パスワードを伝えれば、他のPCからダウンロードすることができます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233180/049b65bd-184b-c795-e856-9c0d8484c995.png" width="400" />

#### ダウンロードする

Zipファイルを受け取った人は、展開してフォルダを開きます。

![created.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233180/6127f2ec-c2b4-38d4-7902-0a50841ae7d2.png)

ここにある`download.bat`をダブルクリックすると、パスワードの入力画面が出ます。

![image-7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233180/65c46375-99ac-76d6-1654-3dfd7e5c2fa7.png)

送信側が`convert.bat`で設定したパスワードと一致すると、S3からのダウンロードが始まります。

#### ダウンロード後の後始末

目的：不要になったリソースを削除します
手順：`delete.bat`を実行します

バッチを実行すると、証明書の失効、AWS上のリソースの削除、ローカルファイルの削除をします。

## 解説：AWSコンソールで作る場合の手順

自動化せず、AWSコンソールでやる場合の手順について、概要だけ説明します。

### どんな仕組みで、何をやっているのか

#### 制限されたS3に、デバイス証明書でアクセスする

AWSのサービスの認証には`ACCESS_KEY_ID`と`SECRET_ACCESS_KEY`を使った署名が必要です。
AWS IoTにだけは特例があり、デバイス証明書（X.509）で認証することができます。

> デバイスは、TLS 相互認証プロトコルを使用して AWS IoT Core に接続するために X.509 証明書を使用することができます。他の（※IoT以外の） AWS サービスは証明書ベースの認証をサポートしていません

デバイス証明書の認証はIoTでしか使えないのですが、IoTには以下の機能があります。

> X.509 証明書を使用して発信者を認証し、一時的で制限された権限のセキュリティトークンを発行します。このトークンは、すべての AWS リクエストに署名して認証するために使用できます。

つまり、デバイス証明書から`ACCESS_KEY_ID`と`SECRET_ACCESS_KEY`を発行できます。
ここで発行した一時的なトークンは、ポリシーでアクセス範囲を制限されます。

※ポリシーで許可したAWSのサービスを実行できる、Lambdaと同じ状態です。

参考：AWS のサービスの直接呼び出しの認証
https://docs.aws.amazon.com/ja_jp/iot/latest/developerguide/authorizing-direct-aws.html

#### SDKなしで、証明書の認証とAWSへの接続をする

実行するために、以下のことをします。

- デバイス証明書と秘密鍵を使ったIoTエンドポイントとのやり取りをする。
- デバイス証明書から`ACCESS_KEY_ID`と`SECRET_ACCESS_KEY`を発行（AssumeRole）する。
- 受け取った`ACCESS_KEY_ID`と`SECRET_ACCESS_KEY`を使って、S3からファイルをダウンロードする。

普通はboto3みたいな`AWS SDK`でやり取りをするのですが、「`AWS SDK`は必ずインストールしないといけないほど特別な処理をしているのか？」というとそうでもなく、ほとんどのAPIは署名の計算をしてURLを投げているだけです。署名の仕様も公開されていますので、BashやPowerShellで同じ処理を書くことができます。

今回のGithubのソースでは、PowerShellで上の3つを実施しています。

Github：上記の処理をしているPowerShellのバッチ
https://github.com/ShotaOki/s3FileDeriver/blob/main/powershell/download_with_device_cert.ps1

参考：署名バージョン 4 署名プロセス
https://docs.aws.amazon.com/ja_jp/general/latest/gr/signature-version-4.html

参考：AWS-SDKをbashで自作してみる（過去記事。Bashでv4署名する）
https://qiita.com/ShotaOki/items/0487828b19319bb90411

#### PEMをpfxに変換する理由

PowerShellのInvoke-RestMethodは「PEM ＋ 秘密鍵 ＋ ルート証明書」のリクエストに対応していないため、pfxに変換します。

Bashで実施する場合は、以下のコマンドを実行します。

```bash
openssl pkcs12 -export -in abcdef-certificate.pem.crt -inkey abcdef-private.pem.key -certfile AmazonRootCA1.pem -out abcdef.pfx
```

#### 記事中の画像リソースについて

記事中の画像は規約の範囲内で利用しています。

ファンキット利用規約
https://arknights.jp/fankit/precautions