GKE で oauth2_proxy を使って任意のサービスに Google アカウント認証を追加する

# はじめに

* GKE で Google アカウント認証を導入する場合 [Cloud IAP](https://cloud.google.com/iap/) を使うのが簡単。
* Cloud IAP は手軽ではあるが、アクセスを許可するメールアドレスなどの設定がプロジェクト単位になるため場合によっては要件とマッチしないことがある。
* そこで [oauth2_proxy](https://github.com/bitly/oauth2_proxy#google-auth-provider) を使って Google アカウント認証プロキシを立てることで、任意のサービスで任意のアカウントやグループを対象にアクセスを制御することができるようになる。
* この記事では Nginx サーバーの前に oauth2_proxy を立ち上げてアクセスコントロールを行う設定手順を記載する。


# 基本的な流れ

  * GCP コンソールで OAuth クライアントを作成する
  * （必要であれば）サービスアカウントを作成し設定する
  * Google アカウント認証を行う Nginx サービスを起動する

# GKE クラスタの作成

* 任意のプロジェクトで任意の設定で GKE クラスタを作成し、クラスタに接続しておく。


# OAuth クライアントの作成

* https://console.cloud.google.com にログイン
* プロジェクトの作成または選択
* "API とサービス" > "認証情報"
* "認証情報を作成" > "OAuth クライアント ID"
  * "ウェブアプリケーション" を選択
  * "承認済みのリダイレクト URI" にリダイレクト先の URL を入力（例: https://www.example.com/oauth2/callback）
  * "作成"
* "クライアント ID" と "クライアントシークレット" をシークレットに登録する

```
echo -n "クライアント ID" > clientid
echo -n "クライアントシークレット" > clientsecret
kubectl create secret generic oauth-setting --from-file=./clientid --from-file=./clientsecret
```

# アクセスを許可するメールアドレスやグループを指定するための設定

* oauth2_proxy では通常の OAuth 認証のあとにホワイトリストに記載されたメールアドレスやグループに所属するユーザーだけアクセスを許可する機能がある。
* この検証のために内部ではサービスアカウントを利用しているので、メールアドレスやグループを指定してアクセスコントロールを行いたい場合はこの節の設定が必要になる。

## サービスアカウントの作成

* https://console.cloud.google.com にログイン
* "IAM と管理" > "サービスアカウント"
* "サービスアカウントの作成"
* "新しい秘密鍵の提供" にチェックをして "JSON" でダウンロード

## API の有効化

* GCP コンソールメニューで "API とサービス" > "ライブラリ"
* "Admin SDK" を検索して有効化する

## サービスアカウントの設定

* admin.google.com にログイン
* "セキュリティ" > "設定"
* "設定詳細" > "認証" > "API クライアント アクセスを管理する"
* "クライアント名" にサービスアカウント ID を入力
* "API スコープ" に "https://www.googleapis.com/auth/admin.directory.group.readonly, https://www.googleapis.com/auth/admin.directory.user.readonly" と入力

## サービスアカウントの鍵ファイルをシークレットに入れる

* サービスアカウント作成時にダウンロードした鍵の入った JSON ファイルを Kubernetes の Secret に追加します。

```
kubectl create secret generic sa-setting --from-file=./KEY_FILE.json
```

# SSL 証明書の作成

* OAuth 認証のためのページでは SSL を利用する必要がある。
* 検証のために Ingress で自己認証証明書を使って SSL 通信を提供する。 

```
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=nginxsvc/O=nginxsvc"
kubectl create secret tls tls-secret --key tls.key --cert tls.crt
```

# Docker image の作成

* oauth2_proxy を実行するための Docker image を作成し Google Container Registry にアップロードする

```Dockerfile
FROM golang:1.10

ADD https://github.com/bitly/oauth2_proxy/releases/download/v2.2/oauth2_proxy-2.2.0.linux-amd64.go1.8.1.tar.gz /tmp
RUN tar -xf /tmp/oauth2_proxy-2.2.0.linux-amd64.go1.8.1.tar.gz -C ./bin --strip-components=1 && rm /tmp/*.tar.gz
```

```
docker build -t oauth2-proxy:v1.0 .
docker tag oauth2-proxy:v1.0 gcr.io/PROJECT_NAME/oauth2-proxy
gcloud docker -- push gcr.io/PROJECT_NAME/oauth2-proxy
```

# Google 認証をする Nginx コンテナを立ち上げる

## Deployment の作成

* Nginx コンテナと oauth2_proxy コンテナを持つ Pod を Deployment から立ち上げる。
* oauth2_proxy の args の `-google-group` や `-google-service-account-json` はメールアドレスやグループ単位でアクセス制御を行いたい場合のみ指定する。
  * サービスアカウントの鍵ファイルのマウントも同様に必要であれば行う。

```deployment.yml
apiVersion: apps/v1beta2
kind: Deployment
metadata:
  name: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 1
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      # Nginx コンテナ
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80
      # oauth2_proxy コンテナ
      - name: oauth2-proxy
        image: gcr.io/PROJECT_NAME/oauth2-proxy
        ports:
        - containerPort: 4180
        command: ["/go/bin/oauth2_proxy"]
        args:
        - "-upstream=http://0.0.0.0:80/" # 認証成功時に提供するコンテンツの URL
        - "-http-address=0.0.0.0:4180" # oauth2_proxy が可動する URL
        - "-email-domain=*" # アクセスを許可するドメイン名
        - "-google-service-account-json=/etc/sa-setting/projectname-random.json" # サービスアカウントの鍵ファイルのパス
        - "-google-group=groupname@yourcompany.com" # アクセスを許可するグループのメールアドレス（複数回指定可）（サービスアカウント設定もする必要がある）
        - "-cookie-secret=5E3899D7-969C-449F-9EA7-41A21E690387" # クッキー名の seed 文字列
        - "-google-admin-email=chiba@kakapoapps.com" # Google の管理者アカウントのメールアドレス. サービスアカウントでのリクエスト送信時に利用する
        env:
        - name: OAUTH2_PROXY_CLIENT_ID
          valueFrom:
            secretKeyRef:
              name: oauth-setting
              key: clientid
        - name: OAUTH2_PROXY_CLIENT_SECRET
          valueFrom:
            secretKeyRef:
              name: oauth-setting
              key: clientsecret
        volumeMounts:
        # サービスアカウントの鍵ファイルをマウントする
        - name: sa-setting
          mountPath: "/etc/sa-setting"
          readOnly: true
        # Ingress のヘルスチェック用に設定. oauth2_proxy では /ping で疎通確認を行うことができる.
        readinessProbe:
          httpGet:
            path: /ping
            port: 4180
          initialDelaySeconds: 5
          periodSeconds: 5
      volumes:
      # サービスアカウントの鍵ファイルの Secret をマウントできるようにボリュームとして設定する
      - name: sa-setting
        secret:
          secretName: sa-setting
```

## Service の作成

* TCP/80 で受けて oauth2_proxy の TCP/4180 に転送するサービスを作成する.

```service.yml
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
  labels:
    app: nginx-service
spec:
  type: NodePort
  ports:
  - port: 80
    targetPort: 4180
    protocol: TCP
    name: http
  selector:
    app: nginx
```

## Ingress の作成

```ingress.yml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: nginx-ingress
spec:
  tls:
    - hosts:
      - www.example.com # 任意のドメイン名を指定
      secretName: tls-secret # 作成した SSL 証明書の Secret を指定
  rules:
    - host: www.example.com
      http:
        paths:
        - path: /*
          backend:
            serviceName: nginx-service
            servicePort: 80
```

# 動作確認

* `kubectl get ingress` で Ingress の IP アドレスを確認したら `/etc/hosts` などに指定したドメイン名と IP アドレスの設定を行いブラウザで動作確認を行う。