Google Cloud Storageに置いた静的ページにBasic認証をつけて公開する

# はじめに

本記事は [Google Cloud Platform その２ Advent Calendar 2018](https://qiita.com/advent-calendar/2018/gcp2) の記事です。


 **[追記]この記事はGAEで構築していましたが、Cloud Runへ移行していくつもりです。**
👉[Cloud Runバージョン](https://zenn.dev/quantum/articles/b670b07f2568df)


# 背景

Google Cloud Platformでは、**Google Cloud Storage**(以下、GCS)を使うことで、静的ページを公開できます。
ブラウザから簡単にファイル操作でき、サーバレスなのでメンテナンスもありません。
しかし、GCSではBasic認証をかけることができないため、事前のお客様確認がしにくく、断念されている人もいるのではないでしょうか。

それ以外に、**Google App Engine**(以下、GAE)もあります。
GAEは、フルマネージドなPaaS環境で、秒速でスケールできます。
PaaSなのでプログラムを使って認証を表示できます。
また、アクセスがあればすぐ起動するので、普段サーバを停止させておいて料金を抑えることもできます。
ただ、デプロイがちょっと変わっているので、デザイナさんには触ってもらいにくいです。

これを、組み合わせれば楽で面倒見なくてそこそこ安いサーバができるのではないでしょうか。


# やりたいこと

`GAEでBasic認証を表示し、認証したユーザにはGCSに置いているWebファイル(HTML/CSS/JS...etc)を表示するWebサーバ`を作ることで

* サーバレス環境
* 誰でも簡単にファイルの差し替えが可能
* アクセスがないときは格安で運用できる
* (サーバレスだから)一気に大量にアクセスが来ても落ちない  
を実現したい


![キャプチャ.PNG](https://qiita-image-store.s3.amazonaws.com/0/90529/6146e9a3-ef1f-5a93-9f63-67dd0ff467ca.png)

# 構築の流れ

[Basic認証を表示するプログラム](https://github.com/ToMGitHubN/gcs-basic-auth/releases)を書いてみました(ライブラリ組み合わせただけ)。
これに、必要な設定を付けてGAEにデプロイしてください。
また、環境によっては許可を追加する必要があります。

* GCSのバケット名は何でもOKです  
（GCSだけで公開できるように、ドメイン名にした方が後々よさそう)
* GAEは、スタンダードのNodeサーバを想定しています

GCS/GAEの建て方など基本的なことは、ここに書かないのでググってください(時間ができたら書くかも)


## とりあえず動かしたい人の設定

GAE用の[ファイル](https://github.com/ToMGitHubN/gcs-basic-auth/releases)を落として、設定ファイル(setting.yaml)を編集してください。  
最低限、3箇所編集すればOKです。

```yaml:setting.yaml
env_variables:
  # Google Cloud Storage Name
  BUCKET_NAME: 'hoge_bucket_name'#<-あなたが設定したGoogle Cloud Storageのバケット名
  #BASIC AUTH
  BASIC_AUTH_ENABLED: 'true'
  BASIC_AUTH_NAME: 'hoge_name'#<-Basic認証で入力させたいユーザ名
  BASIC_AUTH_PASSWORD: 'hoge_password'#<-Basic認証で入力させたいパスワード
```

## 設定ファイルについて

とりあえず、動かしたい人は飛ばしてください。

設定ファイル(setting.yaml) は、以下のようになっています。

```yaml:setting.yaml
env_variables:
  # Google Cloud Storage Name
  BUCKET_NAME: 'hoge_bucket_name'#<-あなたが設定したGoogle Cloud Storageのバケット名
  #BASIC AUTH
  BASIC_AUTH_ENABLED: 'true'#<-Basic認証の有効/無効切り替え (true:有効 false:無効)
  BASIC_AUTH_NAME: 'hoge_name'#<-Basic認証で入力させたいユーザ名
  BASIC_AUTH_PASSWORD: 'hoge_password'#<-Basic認証で入力させたいパスワード
  # Page Option
  DEFAULT_PAGE: 'index.html'#<-URLのファイルが存在しないときに表示する。ディレクトリ単位
  NOT_FOUND_PAGE: '404.html'#<-URLのファイルが存在せず、DEFAULT_PAGEも存在しないときに表示する。ルートに置く必要がある
  # Tranfer Option
  # TRANSFER_MODE List: ALL_DIRECT or ALLOW_DIRECT or ALLOW_REDIRECT
  # ALL_DIRECT : all file direct transfer
  # ALLOW_DIRECT : ALLOW_DIRECT_LIST extension direct transfer, other file redirect
  # ALLOW_REDIRECT : ALLOW_REDIRECT_LIST file redirect GCS, other file direct transfer
  TRANSFER_MODE: 'ALLOW_REDIRECT'#<-転送モード。ややこしいので、後述します
  # TRANSFER_MODE = ALLOW_DIRECT Only
  # extension of the target file
  ALLOW_DIRECT_LIST: '["html", "css", "js", "json"]'#<-後述
  # TRANSFER_MODE = ALLOW_REDIRECT Only
  # extension of the target file
  ALLOW_REDIRECT_LIST: '["mp4"]'#<-後述
  # GCP Option
  # transfer limit time
  # memo: 1 hour = 1000 * 60 * 60 = 3600000ms
  GCS_URL_LIFETIME: 3600000 #<-後述
  # DEBUG
  STACKDRIVER_DEBUGGER: 'false'#<-STACKDRIVERでデバッグしたいときに(false:デバッグしない)
```

### 転送モードについて

設定のTRANSFER_MODE(転送モード)について説明します。

転送の基本的な動きは、ユーザがアクセスすると、GAEが `GCS->GAE->ブラウザ/ユーザ` と流れます。
ただし、GAEのstandardは60秒しか動作しないため、巨大ファイルだと転送が間に合わない可能性があります。
そこで、GCSの署名付きURL(一定期間のみ使用可能なURL)を使い、ファイル転送をGCSから行います。
転送の流れが、 `GAE->(署名付きURL)->ブラウザ->(リダイレクト)->GCS->(ファイル転送)->ブラウザ` とすることで巨大ファイルの問題を解決できます。
また、GCS->GAEと経由するより直接転送したほうが安くなります。
ただし、HTMLを署名付きURLで取得すると、正常に機能しません。
URLがGCSになるため、HTMLはGCSを起点にファイルを取得しようとし、失敗します。
これは、CSSやJSにも当てはまります。

どのファイルをGCS署名付きURLを使うかの設定が転送モードです。

+ TRANSFER_MODE: 'ALL_DIRECT' => すべてのファイルはGAEを経由して転送
+ TRANSFER_MODE: 'ALLOW_DIRECT' => ALLOW_DIRECT_LISTに記載したファイルのみGAEを経由して転送
+ TRANSFER_MODE: 'ALLOW_REDIRECT' => ALLOW_REDIRECT_LISTに記載されていないファイルをGAEを経由して転送

ALLOW_DIRECT_LISTと、ALLOW_REDIRECT_LISTには、対象としたいファイルの拡張子を記載下さい。

また、署名付きURLの有効時間は、GCS_URL_LIFETIMEで設定できます。(初期値:1時間)

GAE転送する必要があり、60秒で間に合わない場合は、
`app.yaml`の`env: standard` => `env: flexible`に変更することで接続時間を伸ばせます。
ただし、サーバスケールが分速に遅くなるので、ある程度サーバ台数を確保しておく必要があります。

設定系は以上です。


## 設定が終わったら

**GAEをデプロイ下さい**


## APIの有効化と許可

GCSとGAEの連携の許可を一度だけ与える必要があります。

[こちらのページ](https://medium.com/@futa/getsignedurl-in-googleappengine-standard-nodejs-6a88d1445530)の以下の箇所を参考に権限を割り振ってください

* Signed URLを発行するために必要な権限
* 有効にしなければいけないAPI  

それでは、GAEでページが表示されるか確かめて下さい。
もし見れなければ、stackDrive LoggingページでGAEのログを確認てみましょう！

>UnhandledPromiseRejectionWarning: SigningError: A Forbidden error was returned while attempting to retrieve an access token for the Compute Engine built-in service account. This may be because the Compute Engine instance does not have the correct permission scopes specified. Identity and Access Management (IAM) API has not been used in project ####before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/iam.googleapis.com/overview?project=#### then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.

のような、IAMから警告があったら。リンクを開いて、許可を与えてください。


すべて終われば、`GAEでBasic認証を表示し、認証したユーザにはGCSに置いているWebファイル(HTML/CSS/JS...etc)を表示するWebサーバ`が出来上がります。
お疲れ様でした。


# おわりに

勉強も兼ねて作りましたが、きっとググれば誰かがやってるんでしょね。  
せっかく作ったのに使われなさそうなので鎮魂と誰かの参考のために、Qiitaに上げました。

余談ですが、ファイルが存在しなときにindex.htmlを見に行くところが適当で、/hoge.htmlでファイルが存在しないと/hoge.html/index.htmlのファイルがあるか確認したりしますが、実用上問題ないだろうと思ってます。