GKEにデプロイしたアプリケーションをIAP(Identity-Aware Proxy)で保護するまで

# はじめに

業務都合で、GKEにデプロイしたアプリケーションを、 [Identity-Aware Proxy](https://cloud.google.com/iap) で保護（認証・認可）する方法を調べたので実際の環境構築方法をメモしてみました。

# Identity-Aware Proxyとは

ざっくりいうと、Googleアカウントによる認証と、ユーザに対する認可を一度にやってくれる機能です。
例えば許可のないユーザには、以下のような画面を出してアクセスをブロックしてくれます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317673/36a8ffbf-2cca-7a61-3e57-03f7361b9b33.png)


ただ本来自分たちがやりたいことをどこまで実現できるのかは調査中でして、それは次の記事で調べていこうと思います。
まずは環境構築までを書いてみました。

# IAPによりアプリケーションの認証・認可を行う場合の前提条件

GKEに対してIAPを適用するための手順が [こちら](https://cloud.google.com/iap/docs/enabling-kubernetes-howto) になります。
まず確認すべきはここに書かれている前提条件。

1. 課金が有効になっている Google Cloud Console プロジェクト
1. HTTPS ロードバランサで処理される 1 つ以上の GKE インスタンスを含むグループ
1. ロードバランサのアドレスに登録されたドメイン名
1. すべてのリクエストに ID があることを確認するアプリコード

4は作りの問題なのですが、問題は3ですね。FQDNがないとだめです。
要はINGRESSのGlobal IPだけではだめなので、何かしらそこに対する名前解決を定義しておく必要があります。
私は お名前.com と Cloud DNSのあわせ技で解決しました。（これは今回の趣旨とは直結しないため、末尾に参考情報として書きます）

# 環境構築手順

## サンプルアプリケーションの実装

何かしらのアプリケーションをGKEにデプロイしないといけないので、まずはサンプルのアプリケーションを作りました。
それが [こちら](https://github.com/keiichi-hikita/gke-iap) 。
ほぼNuxt.jsのテンプレです。

[このあたり](https://github.com/keiichi-hikita/gke-iap/blob/main/package.json#L6) で、起動ポートは `3333` に変更してあるのと、 `npm run dev` にも外部から接続可能なようHOSTを付与しています。

また、pages配下に `site-a.vue` , `site-b.vue` を作ってあります。
執筆時点では、これを使ってページごとに認可対象を分けることができるといいな・・・という希望的観測に基づくものです。さて出来るのやら。

## イメージ化、Google Cloud RegistryへのPUSH

Dockerfileは [こんな](https://github.com/keiichi-hikita/gke-iap/blob/main/Dockerfile) 感じ。
これでイメージをビルド。
イメージタグは `gke-iap:0.1` としてあります。

```bash
docker build -t gke-iap:0.1 --no-cache .
```

ローカルで動作確認したければ以下。

```bash
docker run -it -p 3333:3333 gke-iap:0.1
```

これで動作確認OKだったらイメージをCloud RegistryにPUSHします。

```bash
// dockerコマンドにgoogle authを付与
gcloud auth configure-docker 

// Google Cloud Registryに挙げられるようにタグを付与
docker tag [自分とこのDocker Image ID] gcr.io/[自身のGCP Project]/gke-iap
docker push gcr.io/[自身のGCP Project]/gke-iap
```

これでGoogle Cloud RegistryにイメージがPUSHされます。
どうやら今どきはArtifact Registryを使うほうがいいんですね。
それで検証しようとも思ったんですが、今回の本質からそれるのでやめました。

## クラスタ作ってServiceを公開する

GKEのクラスタを作ります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317673/6abdbfe0-287c-4504-8a8c-792cd74c776c.png)


実際には `cluster-1` という名前で作ってます。特段細かな設定は不要です。
強いて言うならノード数とか1でいい。（節約）

その後はCloud Shell等で以下を実施していきます。

```bash
// 認証
gcloud config set project [自身のGCP Project]
gcloud config set compute/zone asia-northeast3-a
gcloud container clusters get-credentials cluster-1

// イメージのデプロイ
kubectl create deployment --image=gcr.io/[自身のGCP Project]/gke-iap gke-iap

// Serviceの公開
kubectl expose deployment gke-iap --type=LoadBalancer --name=gke-iap --port=3333 --target-port=3333


$ kubectl get pods
NAME                       READY   STATUS    RESTARTS   AGE
gke-iap-545c4fa97f-9dw8x   1/1     Running   0          2m19s

$ kubectl get deployments
NAME      READY   UP-TO-DATE   AVAILABLE   AGE
gke-iap   1/1     1            1           2m25s

$ kubectl get services
NAME         TYPE           CLUSTER-IP   EXTERNAL-IP        PORT(S)          AGE
gke-iap      LoadBalancer   10.36.5.44   [Serviceの公開IP]   3333:30560/TCP   95s
kubernetes   ClusterIP      10.36.0.1    <none>             443/TCP          49m
```

こんな感じでServiceが公開されたら次に移ります。

## OAuth同意画面の作成

そもそもOAuth同意画面とは何か・・・については [このあたり](https://www.wantedly.com/companies/rakus/post_articles/140168) を見ると分かります。
認証時に表示される画面ですね。

[ここ](https://cloud.google.com/iap/docs/enabling-kubernetes-howto#oauth-credentials) に従って進めれば大きく困ることは有りませんでした。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317673/e3beec8a-314f-d395-97a3-45fb10c2c5bf.png)

まずこの画面で「作成」をクリック。あとは流れで作れます。

- アプリケーション名は gke-iap に
- 文書の任意項目の中で、デベロッパーの連絡先に自分のメールアドレスを入力
- あとは「保存して次へ」を繰り返した

## OAuth認証情報の作成

基本的に[こちら](https://cloud.google.com/iap/docs/enabling-kubernetes-howto#oauth-credentials) の流れに沿ってやればOKです。
何を入れるか一瞬悩んだり、入力欄どこ？となった画面だけエッセンスを書いておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317673/402adb59-b49a-0f7d-f34e-f8af4170acf7.png)

アプリケーションの種類と名前だけ入れて「作成」ボタンクリック。
名前は `gke-iap` とした。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317673/25cfdf27-8fed-9421-6bc1-3a4f4fabd6f4.png)


作成するとこの画面が出るが、この時点でJSONのダウンロードは不要。（後でやるので）
クライアントIDだけコピーしておく。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317673/788ddb5e-0cdd-6c37-5494-dc33400363b7.png)

> [承認済みのリダイレクト URI] フィールドに次の形式でユニバーサル リダイレクト URL を追加します。

については、この画面の下の方の「承認済みのリダイレクトURL」ボタンをクリックしてから行う。


承認済みのリダイレクトURLは手順書によると以下。

> https://iap.googleapis.com/v1/oauth/clientIds/CLIENT_ID:handleRedirect

ここにコピーしたCLIEND_IDを使う。

で、最後にJSONダウンロード。

## INGRESSを作る

INGRESSとはなにか・・・ですが、GCP Consoleには以下のように説明が書かれています。

> Ingress は、受信接続がクラスタ サービスに到達できるようにするルールの集まりです。

いまいちよくわからん表現ではあるので、 [こちら](https://cloud.google.com/kubernetes-engine/docs/concepts/ingress) を見るのが良いと思います。（自分もこれからちゃんと読もうとしてます）

今のところは、Serviceの前段で動作するロードバランサーくらいに思っておけば良いかなと思います。
区別しなくてはいけないのは、Deploymentをexposeするときに `--type=LoadBalancer` としたと思いますが、これとは関係ない別なLoadBalancerです。
実際に `Cloud Loadbalancer` を作りに行き、そのバックエンドにGKEのクラスタを指定することになります。


基本的な流れは [こちら](https://cloud.google.com/iap/docs/enabling-kubernetes-howto#iap-access) に従えばいいのですが、同じくポイントだけ書いていきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317673/b777543a-a58b-aa70-7456-f3531b7ac706.png)


まずGKEの `ServiceとIngress` のページで、`サービス` タブの下に出ているサービス名のチェックボックスをONにし、画面上部の `INGRESSを作成` をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317673/3cf4e50d-e4e4-527c-115d-431d5393a1d8.png)

INGRESS作成STEP1では、 `Ingressタイプ` と `名前` を入れます。
タイプは外部ロードバランサを選択しておけばOKです。

画面左下の `Host and path rules` をクリックしてSTEP2に行きます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317673/02df339b-e3d2-af50-af10-c04f0292b02f.png)



デフォルトルールだけ入れます。
`Hosts` , `Paths` はそのままでいいので、一番右の選択肢だけ `gke-iap` を選択します。（画面は選択前のものですね）
こんな感じ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317673/994f0255-28fa-a7d2-f8dd-50accbb16582.png)


STEP3に進みます。

STEP3は画面を取り忘れたのですが、フロントエンド構成にHTTPSを選びます。
IAP構成の前提にHTTPなロードバランサがないとダメと言っておきながら、作るのがここというのが非常に謎ですが、HTTPSにして下さい。

また、HTTPSにするとSSL証明書の指定が必要となりますので、Google Managedな証明書をこの画面から作ってしまいます。
ここで、このINGRESSに対して指定したいFQDNを入力します。
で、自分の場合はこの証明書完成(ACTIVE)までに色々悩みました。その話は後述。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317673/2f6b42e9-c2a8-a3fc-6ae8-f1891ac97b75.png)



STEP4はYAML確認なだけです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317673/ec392db7-0306-ea60-ff6b-54316939821c.png)


```yaml
apiVersion: "extensions/v1beta1"
kind: "Ingress"
metadata:
  name: "gke-iap-ingress"
  namespace: "default"
spec: {}
```

「CREATE」をクリックします。

で、作成が完了するとこうなります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317673/db51b9fd-ca19-cd0e-7f23-247cf3951769.png)

画面ではまだIPアドレスが確定していませんが、暫く待つとIPが確定します。
確定後、そのIPで疎通が可能かを確認してみて下さい。
ただしSSL証明書がACTIVEになるまでには時間がかかります。
以下のコマンドで確認しておくと良いです。

```
gcloud compute ssl-certificates list

NAME: mcrt-ae120651-4787-425b-a6d1-7b0c802e0163
TYPE: MANAGED
CREATION_TIMESTAMP: 2021-10-05T01:28:19.604-07:00
EXPIRE_TIME: 2022-01-02T23:28:21.000-08:00
MANAGED_STATUS: ACTIVE

iap.gcp.example.com: ACTIVE <-- ACTIVEになれば作成成功
```

ただし、当然この時点では誰であってもリーチが出来てしまいます。
そこでIAPが出てきます。

## IAPの有効化

手順に従うと [このような](https://cloud.google.com/iap/docs/enabling-kubernetes-howto#iap-access) 画面になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317673/dc582090-841a-9594-5f03-fd78da53751f.png)

ここで一覧にあるトグルをONに倒せば有効化されます。
ただその前に右ペインでアクセスを許可したいユーザーを指定しておけます。

「プリンシパルを追加」ボタンをクリックすると以下のような画面になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317673/7063efc2-0b6b-84e1-4182-3b94cf4a0a1a.png)

今回は自分の個人ユーザを追加していますが、公式手順に従うと以下のものが追加可能と書かれています。

- Google アカウント: user@gmail.com
- Google グループ: admins@googlegroups.com
- サービス アカウント: server@example.gserviceaccount.com
- Google Workspace ドメイン: example.com

また、今回ロールとしては、同じく公式に従い `IAP-secured Web App User` を選択していますが、これ以外のRoleだと何が起きるのかがよくわかっていないので要検証です。

これでIAPをonにします。

## Backend Configの構成

公式手順を見ると、こうしろと書かれています。

```bash
kubectl create secret generic my-secret --from-literal=client_id=client_id_key \
--from-literal=client_secret=client_secret_key
```

でkeyは以前の認証情報作成でダウンロードしたJSONから引っ張ってくればいいようです。

`my-secret` という名前もなんかあれなので名前を変えて以下のようにしました。

```bash
kubectl create secret generic gke-iap-secret --from-literal=[CLIEND_ID] --from-literal=client_secret=[CLIEND_SECRET]

secret/gke-iap-secret created
```

次に「BackendConfigへのIAPブロックの追加」にかかれている手順に進みます。
kubectlにてyamlをapplyするしか無いっぽいのですかね。

手順書だと2つのyamlが出てきますが、これらをマージしてapplyせよということのようで、結局以下のようにすればいいのだと思います。
私は順番にapplyしていきましたが。（metadata > annotationsがない状態でapplyして、annotations足してapplyして・・・）
一括でいいのだと思うので、別途検証してみようと思います。


```yaml
apiVersion: cloud.google.com/v1
kind: BackendConfig
metadata:
  name: config-default
  namespace: default
  annotations:
    beta.cloud.google.com/backend-config: '{"default": "config-default"}'
spec:
  iap:
    enabled: true
    oauthclientCredentials:
      secretName: dke-iap-secret
```

これをbackendconfig.yamlとして保存し、

```bash
kubectl apply -f backendconfig.yaml
```

これでBackendConfigの作成が終わり。

## 接続確認

ここまで終わったら該当のドメインにHTTPSで接続します。
IPでの接続ではダメです。FQDN必須。

すると以下のように認証画面に飛ばされます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317673/9e6d0987-3504-7225-70ea-ed8eb8e8e6df.png)

プリンシパルに追加していないユーザーの場合、 `You don't have access` なページに飛ばされログインが出来ないはずです。
一方追加したユーザーの場合は以下のように画面が表示されるかと思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317673/cd90f14e-db43-8fb3-3607-3a333dfcd76f.png)


これで基本的なIAPの構築は終わりました。
が、色々実用途に向けては調べなくてはならないことがあるので、それは次の記事で書きます。

# まとめ

以上IAP構築の流れでしたが、基本的には [公式の手順](https://cloud.google.com/iap/docs/enabling-kubernetes-howto) に従って行えば問題ないです。
自分の場合は自分で好きに使えるFQDNがないという問題が有りましたが、通常の開発用途ならその問題も引かないでしょうし。
あと個々のオブジェクトの意味が正しく理解できていないのでそれはこれから学習しようと思います。

ということで次回に続きます。
以下、自分がはまったFQDN無い問題をどう解いたかです。参考に書いておきます。


# 自分が悩んだ証明書作成

この問題は、

- 署名書を作りたい
- でも自分が取得したドメインについて、使ったレジストラでDNSSECがonになっていない
  - やろうとすると110円かかる()
- DNSSECがoffだと、SSL証明書作成に失敗する（ように見える）

というどうでもいい問題をいかに解いたかです。
110円されど100円。

要するにお名前.comでドメインとったんですが、 `.net` だと1円じゃないですか。
その100倍かかるってのはなんか1円にした意味ないなぁって思っちゃうんですよね。
会社の業務のための検証だしねぇ・・・。
別に固定ドメインとか自分いらないんでね・・・。

あと上で「DNSSEC offだとだめなように見える」と書いた根拠はこのあたり。

https://qiita.com/Yukina_28/items/5f3f73714eaf0ef42dc9
https://github.com/GoogleCloudPlatform/gke-managed-certs/issues/13


ということで何をしたか。単純に `サブドメインを定義してCloud DNSに飛ばして、そっちでDNSSEC onにした` です。

まずCloud DNSで、取得したドメインに対するゾーンを切ります。

今回はゾーン名を `gcp` にしました。
また、このときにゾーンのDNSSECをonにします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317673/0aa947a1-8afa-b161-feb6-2bd066f08385.png)

ゾーンが作成されたら、その詳細の、さらにNSレコードのリンクをたどると、以下のようにNSレコードとして登録すべきものが一覧出でてきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317673/70765a26-70f8-1797-ec23-d964fde76509.png)

この4行をコピーするなどして、お名前.com側に以下のようにレコードを登録します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317673/a5e00323-656d-c9ae-20f5-35169ccb6e5a.png)

これでしばらくするとドメイン情報が伝搬され、名前解決が出来るようになるはずです。

以上余談でした。