フューチャー技術ブログ

GKEでIdentity-Aware Proxyを利用したWebアプリケーション認証

初めに

明けましておめでとうございます! Future筋肉エンジニアの渡邉です。年も明けたことなので切り替えて減量に入りました。三月末までを目安に体を絞ろうと思っています。

私は現在Google Cloudを利用しているプロジェクトに所属しており、Google Cloudのスキルアップにいそしんでいます。今回はGKE (Google Kubernetes Engine)でCloud IAP (Identity-Aware Proxy)を利用したWebアプリケーションのGoogleアカウント認証について記事を書こうと思います。

Identity-Aware Proxyとは

以下、公式ドキュメント引用

IAP を使用すると、HTTPS によってアクセスされるアプリケーションの一元的な承認レイヤを確立できるため、ネットワーク レベルのファイアウォールに頼らずに、アプリケーション レベルのアクセス制御モデルを使用できます。

簡単に言うとGoogleアカウントとCloud IAMの仕組みを用いてWebアプリケーションの認証をできます。

認証・承認フロー

authenticate-flow.drawio.png

公式ドキュメントはこちら

  • Google Cloudリソースへのリクエスト(Cloud Load Balancing)します。
  • IAPが有効になっている場合は、IAP認証サーバへ情報を送信します(プロジェクト番号、リクエストURL、リクエストヘッダー、Cookie内のIAP認証情報など)
  • IAP認証サーバがブラウザの認証情報をチェックします。
  • 認証情報が存在しない場合は、OAuth2.0のGoogleアカウントログインフローにリダイレクトし、認証確認を実施する。認証トークンは今後のアクセスのためブラウザのCookieに保存されます。
  • 認証情報が有効な場合、認証サーバは認証情報からユーザのID(メールアドレスとユーザID)を取得します。
  • 認証サーバはこのIDからユーザのIAMロールをチェックし、ユーザがリソースにアクセスできる権限(IAP で保護されたウェブアプリ ユーザー)を持っているかをチェックします
  • 権限を持っていれば、アクセスOKになり、なければNGになります。

全体アーキテクチャ図

以下が全体アーキテクチャ図になります。
GKE/NetworkなどのGoogle Cloudのリソース構築に関しては慣れ親しんでいるTerraformを利用して作成しました。OAuth同意画面に関しては外部公開する場合は、APIから作成することはできない (公式ドキュメント記載)ので、コンソール画面から設定しました。

architecture.drawio.png

Bastion初期設定

Public Subnetに作成したGCEインスタンスからGKEのコントロールプレーンに対してkubectlコマンドを実行したいので、
kubectlコマンドや、google-cloud-sdk-gke-gcloud-auth-pluginなどをインストールします。
以下、Bashスクリプトです。

#!/bin/bash

########################################################
# Author: watanabe
# Initial Date: 2022/12/28
# History: Create
########################################################

# Variable Definition
project_name="xxxxxxxxxx"
gke_cluster_name="xxxxxxxxx"
region="asia-northeast1"

# Install Kubectl
curl -LO "https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl"
chmod +x ./kubectl
sudo mv ./kubectl /usr/local/bin/kubectl
kubectl version

# Install google-cloud-sdk-gke-gcloud-auth-plugin
sudo apt-get install apt-transport-https ca-certificates gnupg
echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" | sudo tee -a /etc/apt/sources.list.d/google-cloud-sdk.list
curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key --keyring /usr/share/keyrings/cloud.google.gpg add -
sudo apt-get update && sudo apt-get install google-cloud-cli
sudo apt-get install google-cloud-sdk-gke-gcloud-auth-plugin
gke-gcloud-auth-plugin --version
export USE_GKE_GCLOUD_AUTH_PLUGIN=True
source ~/.bashrc

# Get Credentials
gcloud container clusters get-credentials "${gke_cluster_name}" --region "${region}" --project "${project_name}"
kubectl config get-contexts
kubectl get node

manifestファイル

また、manifestファイルは以下を用意してkubectlコマンドを実行しk8sリソースをGKEに対して作成しました。

ここまでの設定で事前準備は完了です。

Deployment

nginxのPodを用意するため、Deploymentのmanifestを作成しました。

deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
selector:
matchLabels:
app: nginx
replicas: 3
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.22
ports:
- containerPort: 80

Service

IngressにはNodePortが必要になるので、Serviceのmanifestを作成しました。

service.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
type: NodePort
selector:
app: nginx
ports:
- port: 80
targetPort: 80
protocol: TCP

ManagedCertificate

クライアントとIngressで構築するHTTP(S)ロードバランサ間をHTTPSでアクセスするようにしたいので、Googleマネージド証明書のmanifestを作成しました。
domainsには、terraformで用意したHTTP(S)ロードバランサに設定したい外部IPアドレスにフリーなワイルドカードDNSサービスのnip.ioを利用したものを設定します。

managed-certificate.yaml
apiVersion: networking.gke.io/v1
kind: ManagedCertificate
metadata:
name: nginx
spec:
domains:
- 34.xxx.xxx.xxx.nip.io

Ingress

インターネット上にnginxを公開するためにIngressを構築するmanifestを作成しました。

ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx-ingress
annotations:
# 外部ロードバランサの作成
kubernetes.io/ingress.class: "gce"
# クライアントとHTTP(S)ロードバランサ間のすべての通信をHTTPSに強制
kubernetes.io/ingress.allow-http: "false"
# 事前に用意していた静的外部IPアドレスを設定する
kubernetes.io/ingress.global-static-ip-name: "loadbalancer-external-ip-address"
# Googleマネージド証明書をIngressに適用する
networking.gke.io/managed-certificates: "nginx"
spec:
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nginx-service
port:
number: 80

Cloud IAPなしでのアクセス確認

まず、Cloud IAPなしでのアクセス確認を行います。
Load Balancerに設定したドメインに対してアクセスを行うと、特に認証画面を経由することもなくアクセスできます。
1-IAPなしでのアクセス確認.png

Cloud IAPの設定を追加

上記の状態ではだれでもアクセスすることが可能なため、セキュアな状態ではありません。
ここでCloud IAPの設定を追加してみましょう。

OAuth同意画面の作成

OAuth同意画面はUser Typeを「外部」で作成します。
2-OAuth同意画面①.png

アプリ情報として、必須項目の以下を設定して「保存して次へ」をクリックします。
ほかの情報は任意のため設定しませんでした。

  • アプリ名:GKE Application
  • ユーザサポートメール:自身のメールアドレス
  • デベロッパーの連絡先情報:自身のメールアドレス
2-OAuth同意画面②.png 2-OAuth同意画面③.png

スコープとテストユーザは任意情報のため設定しませんでした。
以下が設定完了したOAuth同意画面になります。

2-OAuth同意画面④.png

OAuth認証情報の作成

APIとサービスタブの「認証情報」をクリックします。
認証情報の作成プルダウンリストからOAuthクライアントIDをクリックします。

3-OAuth認証情報①.png
  • アプリケーションの種類:ウェブアプリケーション
  • OAuthクライアントIDの名前:GKE Application
    を入力し、作成ボタンをクリックします。
3-OAuth認証情報②.png

作成ボタンをクリックするとOAuthクライアントIDとクライアントシークレットが生成されるので、JSONをダウンロードします。

3-OAuth認証情報③.png

作成したOAuthクライアントを再度クリックし、承認済みリダイレクトURIをダウンロードしたOAuthクライアントID(CLIENT_ID)に修正して保存します。

https://iap.googleapis.com/v1/oauth/clientIds/CLIENT_ID:handleRedirect
3-OAuth認証情報④.png

IAPアクセス権の設定

Google Cloud ConsoleのIdentity-Aware Proxyにアクセスします。
アクセス権を付与するリソースの横にあるチェックボックスをオンにします。

4-CloudIAPアクセス権設定①.png

IAPの有効化で「構成要件」を参照し、問題なければ「有効にする」をクリックします。
4-CloudIAPアクセス権設定②.png

チェックボックスが「オン」になりました
右側のパネルから、「プリンシパルの追加」をクリックします。
4-CloudIAPアクセス権設定③.png

IAPアクセスを許可したいGoogleアカウント(メールアドレス)または、Googleグループなどを指定して、IAMロール(IAP-secured Web App User)を付与してください。

4-CloudIAPアクセス権設定④.png

ここまででOAuthの設定は完了です。

Kubernetes Secretの作成

GKEでCloud IAPを適用するためには、Kubernetes Secretを作成してBackendConfigに適用する必要があります。
先ほど作成してダウンロードしたOAuth認証情報のClient IDとClient Secretを指定してKubernetes Secretを作成します。

kubectl create secret generic oauth-secret --from-literal=client_id=xxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com \
--from-literal=client_secret=xxxxxxxxxxxxxxxxxxxxxxxxx

Kubernetes Secretが作成されていることを確認します。

xxxxxxxxxxxxx@tky-bastion:~$ kubectl describe secret oauth-secret
Name: oauth-secret
Namespace: default
Labels: <none>
Annotations: <none>

Type: Opaque

Data
====
client_secret: 35 bytes
client_id: 73 bytes

BackendConfigの作成

Kubernetes Secretで作成したSecretをBackendConfigに設定することでCloud IAPを適用できます。
以下のmanifestファイルを用意します。

backendconfig.yaml
apiVersion: cloud.google.com/v1
kind: BackendConfig
metadata:
name: iap-conifg
namespace: default
spec:
iap:
enabled: true
oauthclientCredentials:
secretName: oauth-secret

kubectlコマンドでBackendConfigを作成します。

kubectl apply -f backendconfig.yaml

BackendConfigが作成されていることを確認します。

xxxxxxxxxxxxx@tky-bastion:~/manifest$ kubectl get backendconfig
NAME AGE
iap-conifg 3m42s

サービスポートを BackendConfig に関連付けて、IAP の有効化をトリガーする必要があります。既存のService リソースにアノテーションを追加し、サービスのすべてのポートをデフォルトで BackendConfig にします。

service.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-service
# 追記
annotations:
beta.cloud.google.com/backend-config: '{"default": "config-default"}'
spec:
type: NodePort
selector:
app: nginx
ports:
- port: 80
targetPort: 80
protocol: TCP
kubectl apply -f service.yaml

以上で、Cloud IAPの設定は完了です。

Cloud IAPありでのアクセス確認

Cloud IAPの設定が完了したので、画面にアクセスしてCloud IAPが適用されているかを確認します。

Cloud IAP認証対象外アカウントでのアクセス確認

Load Balancerに設定したドメインに対してアクセスを行うと、Cloud IAPによるGoogleアカウントログイン画面にリダイレクトされます。

5-IAPアクセスなし①.png

本GoogleアカウントはCloud IAPのアクセスできる権限(IAP で保護されたウェブアプリ ユーザー)を持っていないため、画面にアクセスすることはできません。
5-IAPアクセスなし②.png

Cloud IAP認証対象アカウントでのアクセス確認

Load Balancerに設定したドメインに対してアクセスを行うと、Cloud IAPによるGoogleアカウントログイン画面にリダイレクトされます。

6-IAPアクセスあり①.png

本GoogleアカウントはCloud IAPのアクセスできる権限(IAP で保護されたウェブアプリ ユーザー)を持っているため、画面にアクセスできました。
6-IAPアクセスあり②.png

最後に

今回はGKE (Google Kubernetes Engine)でCloud IAP (Identity-Aware Proxy)を利用したGoogleアカウント認証について記事を書きました。
Google Cloudを利用していて、特定のGoogleアカウントにのみアクセスを許可したいケースはあるかと思いますので、その時にでも参考にしていただければ幸いです。