AWS × Pythonで、簡単、格安、専用のファイルアップローダを作ってみた。

ファイル共有ツールといえば、無償ツールがたくさんあるので、普通はそれを使えばいいとは思います。

でも、こんなこと考えてる人いませんか？

・セキュリティが心配（ブルートフォースアタック対策がされているのかとか） 
・そのツールの信頼性が心配（アップしたファイル紛失したりしないの？悪意持ってたりしない？）
・あとはダウンロード画面に広告が出てて汚い、見栄えが悪い（※あくまで個人の感想です）
・メール添付の時に、いちいちファイル容量気にするのがストレス
・アップローダの画面を開くのが面倒くさい
・自分はいいけど、お客さんがその不安を持ってしまわないか、とか

何より私はブラウザでアップローダのサイトのアップロード画面を開くまでのステップが<font color="red">メンドくさい</font>と思ってしまうのです。（何かの病気でしょうか。。。そのクセ、こういうコードを書くのはメンドくさく感じないという矛盾。）

しかし、専用のシステムを開発するまでもコストも時間もかけたくないし、でもそれなりにちゃんとしているものを作りたい、AWSにログインする権限なくその仕組みを共有したいという時、この記事の通りに作れば、小一時間で完成します。ただ、ターミナルを開くのにアレルギー反応がある人は、今回は対象外です。aws-cliの設定とかもありますし。

ちなみに、この方法はアクセス時点でパスワードをかける方式ではありません。パスワードをかける場合は、今回はアップファイルをパスワード付zip化して対応します。（つまり、　中身は簡単に見れないものの、ファイル自体は取得できますので、セキュリティ的にはMaxではありません。）
＊またMacユーザーならご存知かと思いますが、Macのzipは要注意です。windowsと共有する場合文字コードが異なるので、2バイト文字は使わないこと。（ファイル名は自動でユニークidに変換しますが、解凍後に化けます。）

### 完成の動作イメージ

<img width="592" alt="1__bash_と_S3_Management_Console.png" src="https://qiita-image-store.s3.amazonaws.com/0/258028/2f95c4c9-3ba8-1161-7179-a781343553d0.png">

このように、

```bash
upfile {ファイルパス}
```

と指定すると、

<img width="592" alt="1__bash_と_S3_Management_Console.png" src="https://qiita-image-store.s3.amazonaws.com/0/258028/727b6fe1-9154-6323-ceda-23d6726f7e1b.png">

こんな感じでパスを作成するだけ（ファイルをS3にアップして、一時公開し、送信URLを取得）。

・ 乱数を間に挟んでいるので、公開しても特に誰かに見つけられる心配もかなり低いと思います。（3600兆分の1以下。さらにファイル名も知っている必要もある）

パスワード付きzipオプションも！

<img width="591" alt="1__bash.png" src="https://qiita-image-store.s3.amazonaws.com/0/258028/952ed68e-4281-eec9-8910-e5c4847fd1d5.png">

第2引数に"zip" , 第３引数に"1day"とすると・・・

<img width="619" alt="1__bash.png" src="https://qiita-image-store.s3.amazonaws.com/0/258028/658a23fa-d071-1523-93f1-48baa2229fcf.png">

パスワード付きzip化完了！ 1dayディレクトリに保存。

＊補足：Macなら以下で簡単パス入力が可能なので、ものの3秒くらいでアップロード＆URL取得完了。
　・ファイルをターミナルにドロップする
　・FinderでCmmand+Option+"c"でコピー（ただし、空白などエスケープが必要な時は""でくくる）

便利ですね！

ダウンロードは、URLを知って入れば、誰でも可能（45日後（任意で設定）に失効）
<img width="674" alt="新しいタブ_と_新しいタブ.png" src="https://qiita-image-store.s3.amazonaws.com/0/258028/a2946121-763c-6276-ec21-9932094ee884.png">

どうでしょう？ファイル共有がサクサクできそうですよね。メール添付より速いですし、仮に間違えたらS3のファイルを消せばOKです。

zipパスワードはパスワード解析に備え、アルファベット＋数字の10桁ランダムで作ってますので、３６の１０乗で、、、

3,656,158,440,062,980 通り。(3656兆）

これに加え、パスにも同じ乱数を設定してますので、この２乗の見つかりにくさです。

数値にすると、

![image.png](https://qiita-image-store.s3.amazonaws.com/0/258028/1135e8fc-c30d-aa5d-3782-c1792278d2dc.png)

↑Excelだとオーバーフローしてますが、京（ケイ）とか垓（ガイ）とか通り越して、10の32乗の１つ下、0.13溝（コウ）です。。。

とはいえ、解析不可ではありませんので超機密情報（個人情報、カード情報を含むなど）などの共有はお控えください。営業資料とか、ちょっとしたメモファイルとかの共有なら十分でしょう。

## 環境＆準備するもの
- 環境
  Mac OSX

- 事前準備
 <必須>
 Python 3系のインストール(必要ライブラリのインストール boto3)
 AWS CLI のインストールとファイルの設定　（https://qiita.com/okhrn/items/8da6b217d3b1fce63371）

 <任意>
 独自ドメインの取得とRoute 53への設定（独自ドメインにしたい場合）
 ACMで独自ドメインのSSL証明書を発行

## 作成手順
1. S3にバケットを作成します
<img width="706" alt="S3_Management_Console.png" src="https://qiita-image-store.s3.amazonaws.com/0/258028/55d32723-fe4d-07ca-fdbe-a444a569b9b7.png">

<img width="705" alt="S3_Management_Console.png" src="https://qiita-image-store.s3.amazonaws.com/0/258028/f212dddb-5c7f-64d1-3274-fe6637b367c6.png">
ログは任意で残すと良いでしょう。
暗号化くらいは設定しておくと良いでしょう。

<img width="710" alt="S3_Management_Console.png" src="https://qiita-image-store.s3.amazonaws.com/0/258028/050ec7e0-f5d8-a952-1371-c1c54cde6d80.png">
そのまま次へ

<img width="705" alt="S3_Management_Console.png" src="https://qiita-image-store.s3.amazonaws.com/0/258028/63f1fcf8-169f-64fb-6370-a7f2f6ed53c3.png">
「作成」を押しましょう

- ファイルを一時有効にしたい場合は、ライフサイクルを設定します。（任意）
<img width="920" alt="S3_Management_Console.png" src="https://qiita-image-store.s3.amazonaws.com/0/258028/ecb2afc8-2a72-8f4f-166b-80de56fbe7ad.png">
管理 > ライフサイクル　からルールを追加します。

<img width="699" alt="S3_Management_Console.png" src="https://qiita-image-store.s3.amazonaws.com/0/258028/9bfbad07-e909-17a5-90ae-d1bb7c09ccdf.png">
名称を設定し、プレフィックス（ディレクトリ）を入力してください。

<img width="705" alt="S3_Management_Console.png" src="https://qiita-image-store.s3.amazonaws.com/0/258028/bff40636-999b-0c79-8d51-713b030f1e3e.png">
何もせず次へ

<img width="702" alt="S3_Management_Console.png" src="https://qiita-image-store.s3.amazonaws.com/0/258028/f1cf5361-9892-a44a-111c-57a20b803b22.png">
45日間で失効させます

<img width="702" alt="S3_Management_Console.png" src="https://qiita-image-store.s3.amazonaws.com/0/258028/573705a6-2a4a-4337-8f7f-67f6c0d18041.png">
保存してバケットの準備は完了です。

２. CloudFrontを設定します（任意）
S3直接でもファイルダウンロードはできるので、不要な方は飛ばしてください。

CloudFrontを通すメリットは、以下のようなところでしょうか
     - キャッシングによる高速化
     - 同一ファイルへの多数ユーザーのアクセスがある場合、効率的
     - SSL設定ができる
     - 独自ドメインが設定できる
     - 不正なサーバー攻撃に強い（特定地域を除外したり）

では手順です。
<img width="596" alt="AWS_CloudFront_Management_Console.png" src="https://qiita-image-store.s3.amazonaws.com/0/258028/4d38ad68-7d7b-2e10-8521-bd2c9585f181.png">
Create Distributionで作成開始します。

<img width="1037" alt="AWS_CloudFront_Management_Console.png" src="https://qiita-image-store.s3.amazonaws.com/0/258028/0faec558-64ad-0748-044b-29cb855abb15.png">

Webの方を、「Get Started」

<img width="988" alt="AWS_CloudFront_Management_Console.png" src="https://qiita-image-store.s3.amazonaws.com/0/258028/d8c222e2-3fb0-e55c-d27d-6082786e5205.png">

先ほど作成したバケットを指定します。

<img width="786" alt="AWS_CloudFront_Management_Console.png" src="https://qiita-image-store.s3.amazonaws.com/0/258028/bf028e4a-9964-0e21-fb35-60a599bc0d47.png">

次。HTTP to HTTPSとかにしておきましょう

<img width="964" alt="AWS_CloudFront_Management_Console.png" src="https://qiita-image-store.s3.amazonaws.com/0/258028/6f876178-6e97-4e7a-0b6f-7ce18d601f1b.png">

独自ドメインにする場合は、ここにドメインを入力し、
ACMで設定した証明書をセットしてください。

<img width="766" alt="AWS_CloudFront_Management_Console.png" src="https://qiita-image-store.s3.amazonaws.com/0/258028/9c34c2b7-5309-a722-cc4d-8a07daa8857b.png">

お金に余裕があれば下を（でも、それならもっとちゃんとしたものを作っても良いかも）、それ以外の人は上で我慢しましょう（通常ファイル共有用途では支障ないと思います）。

以上で、最下部の「Create Distribution」を押して作成完了です。

３. Route53から、CloudFrontに向けます（独自ドメインを使用する場合のみ）
　　　<img width="1163" alt="Route_53_Management_Console.png" src="https://qiita-image-store.s3.amazonaws.com/0/258028/e4764424-64c5-b630-6e9f-5f062bee884b.png">

これでAWSサイドは、準備完了です。

４. Pythonコードをコピーして、任意の場所に保存して、カスタマイズし、エイリアスを作成します

```Python
# -*- coding: utf-8 -*-
import sys
import requests
import boto3
import os
import string
import random
import urllib.parse
import unicodedata
import uuid

### こちらがカスタム設定です ###
filedomain = '独自ドメイン'
# 例
#    独自ドメインの場合 : 'https://mydomain.com/' 注意！最後のスラッシュは必要
#    S3直接の場合      : 'https://{s3のregion}.amazonaws.com/{バケット名}/'
bucket = 'バケット名'
s3prefix = 'temporary/' #任意のプレフィックス（ディレクトリ）
############################
zip_flg = False　　　# デフォルトではzip化なしに設定

try:
    filepath = sys.argv[1]
    if (len(sys.argv) >=3 and sys.argv[2] == 'zip'):
        zip_flg = True
    if (len(sys.argv) >=4 and sys.argv[3]):
        s3prefix += sys.argv[3] + '/'
    else:
        s3prefix += '45days/'
except:
    print("*** エラー: ファイルパスを入力してください")
    sys.exit()

def is_japanese(string):
    for ch in string:
        name = unicodedata.name(ch)
        if "CJK UNIFIED" in name \
        or "HIRAGANA" in name \
        or "KATAKANA" in name:
            return True
    return False

def rand_str(n):
    return ''.join([random.choice(string.ascii_letters + string.digits) for i in range(n)])

def upload_to_s3(filepath):
    if os.path.exists(filepath):
        random_str = rand_str(10)
        if (zip_flg):
            password = rand_str(10)
            unique_code=uuid.uuid4()
            to_path =  s3prefix + random_str + '/'+ str(unique_code) + '.zip'
            fp = filepath
            if (filepath[-1:]=='/'):    fp = filepath[:-1]  # 末尾が'/'なら削除
            dir = os.path.dirname(fp)
            zip_path = dir + '/'+ str(unique_code)  + '.zip'
            if os.path.isdir(filepath):
                print("エラー：現在、ディレクトリzip圧縮はできません。ファイルを指定してください。")
                return
                # cmd = ['zip','-p',password, '-r', zip_path,filepath,'-x','*.DS_Store'] # ディレクトリzip化やるとしたらこんな感じだけどうまく行かなかったので、中止！
            else:
                cmd = ['zip', '-jP', password,zip_path,filepath]
            output = subprocess.run(cmd, stdout=subprocess.PIPE)
            output = output.stdout
            print(output)
            print ('*PasssWord : '+password)
            filepath = zip_path
        elif is_japanese(os.path.basename(filepath)):
            extension = os.path.splitext(filepath)[1]
            unique_code=uuid.uuid4()
            to_path =  s3prefix + random_str + '/'+ str(unique_code) + extension
        else:
            to_path =  s3prefix + random_str + '/'+ urllib.parse.quote(os.path.basename(filepath))
        boto3.resource('s3').Bucket(bucket).upload_file(filepath,to_path)
        boto3.resource('s3').ObjectAcl(bucket,to_path).put(ACL='public-read')
        print("**ファイルをアップロードしました。パス =  " + filedomain + to_path)
    else:
        print("**ファイルが存在しません。パス =  " + filepath)

upload_to_s3(filepath)
```

GitHub : https://github.com/esikm/s3fileSender/blob/master/file_upload.py
* MITライセンス

- Python コードをカスタム設定してください

Python コードの10 ~ 14行目を修正します。後はお好きにカスタマイズするといいでしょう。
  ### こちらがカスタム部分です ###
  filedomain = '独自ドメイン'
  # 例）
  #    独自ドメインの場合 : 'https://mydomain.com/' 注意！最後のスラッシュは必要
  #    S3直接の場合      : 'https://{s3のregion}.amazonaws.com/{バケット名}/'
  #                       'https://ap-northeast-1.amazonaws.com/fileshare'
  bucket = 'バケット名'
  s3prefix = 'temporary/'
  ############################

- （任意）Python実行のエイリアスを./登録しておくと便利です
例えば、
/Applicastions/MAMP/htdocs/tool/uploader/file_upload.py  にプログラムコードがある場合、

```bash
alias upfile='python /Applications/MAMP/htdocs/tool/uploader/file_upload.py'
```

このように設定すると、ターミナルで

```bash

upfile ファイル名（ターミナルにドラッグ）
```

でアップ実行が可能で便利です。
エイリアス登録は、どこにソースを置いたか忘れてしまっても"alias"コマンドですぐ発見できますので、その意味でもおすすめです。

パスはFinder上で Command + Option + c でも取得可能ですが、パスにスペースが含まれる場合、スペースの前に"\"（エスケープ）を設定するか、ダブルクォテーションで囲むようにしなければならない手間があります。
例：　path/Google ドライブ/file.ext  -> path/Google\ ドライブ/file.ext


ファイルをzip化したり、有効期間別にアップしたり、自由にカスタマイズしてお使いください。
エンジニア以外に共有する場合は、同じような仕組みをイントラサーバーに立てて、webページ用意してドラッグ&ドロップであげるようにするとか、いいかもですね。

今回悩ましかった（時間切れで中止した）のが、ディレクトリzip化でした。少し悔いが残ります。
まぁ、今回は時間をかけないことが１つのテーマでもあるので、こんなところで十分、日頃使う用途では十分ではないでしょうか。