GCS上のコンテンツを自分のGAEアプリケーションからしかアクセスできないようにする

#2017/01/20追記
コメントにて
>App EngineはService Accountはデフォルトで、ProjectのEditorを持っている

というご指摘をいただきました。やり直してみたらたしかにその通りでした。
おそらくいろいろいじってるうちに設定が消えるなどしてしまったのかも…。

この記事はACLを再設定する方法ということにします。

ただ、それにしても最後のGCSから画像をロードするサンプルコードで、GAEのメモリめっちゃ食う実装をしてたので改善策を新たな記事に書きます。テキストなどの小さいファイルのものはこれでも特に問題なさそうなのでこれで問題ないかと思います。

#元記事
GoogleAppEngineからGCS上のコンテンツをアクセスするには、コードでGCSのライブラリを使う必要があります。
その方法は後で紹介するとして、まずはGCS上のコンテンツのアクセス制御を設定しなければなりません。


何も考えずにGAEからGCS上のコンテンツにアクセスできるようにすると
![CropperCapture[243].png](https://qiita-image-store.s3.amazonaws.com/0/88657/bd9cb5f2-638a-c26d-2b57-d24036a312d9.png)
こんな感じで公開リンクとして設定してやれば、公開用のURLが発行されるので楽にできます。

ただ、セキュリティの事を考えるとこれはあまり良くないですね。
セキュリティ以外にも、GCSはネットワーク出力に金がかかるので知らんうちに課金が発生してしまうかも…なんて恐怖に怯えることになります。

というわけで今回は公開リンクを設定せず、自分のGAEアプリケーションからのみコンテンツにアクセス出来る方法を紹介します。

#はじめに
前提として
・GAEアプリケーションはデプロイ済み
・GCSにはBucketが存在してなんらかのファイルがあるものとします。
・gsutilが使える状態

この前提の中でわからないことがある人は、以下の記事を見ると良いと思います。
（参照：http://qiita.com/sinmetal/items/20fc9ae601161291b16e ）


#自分のGAEアプリケーションのサービスアカウントを確認する
IAMと管理を開きます。↓
https://console.cloud.google.com/iam-admin

さらに横のタブにあるIAMを開きます。
![CropperCapture[244].png](https://qiita-image-store.s3.amazonaws.com/0/88657/3326a1e6-0985-6b2e-5a21-0c78c65a6708.png)

この中で「App Engine default service account」を探します
別にDefaultでなくてもいいと思いますが今回はわかりやすいので。

おそらく
**{{projectID}}@appspot.gserviceaccount.com**という文字列になっているのでそれをメモしておいて下さい。

#GCSのバケットとファイルにアクセス制御を設定する
GCSのアクセス制御はACLを設定することによって成ります。
>アクセス制御リスト（ACL）とは、オブジェクトを他のユーザーと共有したり、自分のバケットとオブジェクトへのアクセスを他のユーザーに許可したりするためのメカニズムです。
https://cloud.google.com/storage/docs/access-control/lists

このACLはGCSのコンソールで1つずつ権限を付与できますが、それはあまりにも面倒だし僕の場合は1万個ほどのファイルに権限を付与したいのでやってられませんね。

なのでそこら辺を上手いことやってくれるgsutilを使います。

アクセス制御には具体的には以下のようなコマンドを使います。

```
#バケットに対してアクセス制御をする時
gsutil acl ch -u {{projectID}}@appspot.gserviceaccount.com:R gs://{{ACLを編集したいbucket名}}

#バケットの中のファイルに対してアクセス制御をする時
gsutil -m acl ch -u {{projectID}}@appspot.gserviceaccount.com:R gs://{{ACLを編集したいbucket名}}/foo/bar.jpg

```

-mオプション：
マルチスレッドで権限付与をしてくれます。多数のファイルが有る時にかなり有効

acl ch：
Linuxのchmodのような機能。ACLを変更する。

-uオプション：
[ここ](https://cloud.google.com/storage/docs/gsutil/commands/acl#ch-examples)にある通り、ユーザをIDやEmailで定めるようにコマンドのフォーマットを指定するオプション

{{projectID}}@appspot.gserviceaccount.com:R：
AppEngineのサービスアカウントに対してR(Read=読み取り)を許可している。書き込みを許可する時はW。

gs://{{ACLを編集したいbucket名}}/foo/bar.jpg：
バケットもしくはファイルを指定する。
gs://{{ACLを編集したいbucket名}}/*.jpg
とやればバケット直下の全てのJPGファイルを対象とできる。

----
コマンドが完了すれば下のような権限が設定されているはず。
![CropperCapture[247].png](https://qiita-image-store.s3.amazonaws.com/0/88657/8bcd4eed-13e7-8613-136b-ba808ab593c8.png)


まぁ困ったときは公式のドキュメントを見るのが良いですね。
https://cloud.google.com/storage/docs/gsutil/commands/acl


#注意点
既に公開リンクに設定してあるファイルに↑のコマンドを設定しても勝手に非公開になるわけではありません。設定が追加されるだけなので。
なので一度非公開化してからgsutilでGAEアプリケーションのみを許可するようにしたほうが良いかなと思います。

```
#例
gsutil acl set private gs://{{bucket名}}/*.jpg
```


#GAE/Goからファイルにアクセスする方法
おまけなのでざっくりと書きます。

```FileManager.go
func GetImageData(g *gin.Context) (imageBytes []byte, err error) {
	c := appengine.NewContext(g.Request)
	bucketName, err := file.DefaultBucketName(c)
	if err != nil {
		return imageBytes, err
	}
	client, err := storage.NewClient(c)
	if err != nil {
		return imageBytes, err
	}

	reader, err := client.Bucket(bucketName).Object("test/ds.PNG").NewReader(c)
	if err != nil {
		return imageBytes, err
	}
	defer reader.Close()
	imageBytes, error := ioutil.ReadAll(reader)
	return imageBytes, error

}
```

```main.go
func GetImage(g *gin.Context) {

	imageBytes, err := FileManager.GetImageData(g)
	if err != nil {
		g.String(http.StatusBadRequest, err.Error())
	}
	g.Data(200, "image/jpeg", imageBytes)
}
```

これでGCS上のコンテンツは自分とGAEからのみしかアクセスを受付ないので脆弱性が少し減ったのではないかと思います。
次はGAEのRESTfulAPIの設計とか学習したらそこら辺の話を書きます。

以上です。