はじめに
前回はプラットフォームエンジニアリングの概要を学びました。今回は、実際にAPIプラットフォームを構築してみましょう。PC1台で完結するシンプルな構成なので、ぜひ皆さんも試してみてください。
今回、構築するプラットフォームの機能は以下の通りです。
- マルチテナント: 複数のチームが同じプラットフォームを利用しても、互いに影響を与えないようにする
- APIゲートウェイ: Gateway APIを利用してルーティングを行う
- 簡単なAPI認可: 「Kuadrant」と「Keycloak」でAPIへのアクセスを認証されたクライアントのみに制限する
構築する構成の説明
プラットフォームエンジニアリングでは、プラットフォームの構築・運用をプラットフォームチームが行い、そのプラットフォームを使ってプロダクトチームがアプリケーションを開発・運用します。そのため、今回の構成もプラットフォームチームが構築する部分と、プロダクトチームが構築する部分に分けています。
今回の構成は以下の通りです。
【補足】実際の通信経路はこの通りにはなりませんが、まずはこのようなイメージで理解していただくと分かりやすいです。通信経路に関して違和感を覚えた方は、実際に構築して確認してみましょう。
構成要素は以下の通りです。
- Kubernetes: コンテナのデプロイや管理を自動化するプラットフォーム。Namespaceによるマルチテナントを実現する。また、Operatorによりプラットフォームの機能追加を容易にする
- Gateway API: 柔軟なAPIゲートウェイ機能をKubernetes上で提供する
- Gateway: Gateway APIで定義されるリソース。APIゲートウェイの本体となり、外部からの通信を受け付ける
- HTTPRoute: Gateway APIのリソース。Gatewayへのリクエストを各Serviceへルーティングするルールを定義する
- Kuadrant: Gateway APIに認証認可や流量制御などの機能を追加するプロダクト
- Authorino: Kuadrantの認証認可エンジン。APIリクエストに対する認可ポリシーの適用などを行う
- AuthPolicy: Kuadrantで利用する認証認可ポリシー定義用のリソース
- Keycloak: オープンソースのIDアクセス管理システム。Kuadrantと連携し、APIの認可を提供する
今回はKubernetes上にAPIプラットフォームを構築します。主なNamespaceの構成は以下の通りです。
- shared-gateway: テナント間で共有されるGatewayリソースをデプロイする。プラットフォーム上で提供される機能へのアクセスは全てこのGatewayを通るよう構成する
- kuadrant-system: Kuadrantのコンポーネントをデプロイする
- keycloak: 認証認可機能を提供するKeycloakをデプロイする
- echo(テナント): プロダクトチームのアプリケーションをデプロイする
shared-gateway Namespaceには、テナント間で共有して利用するGatewayリソースがあります。プラットフォームへの通信はすべてこのGatewayを経由させます。プラットフォームチームはこのGatewayに対して、デフォルトのセキュリティ設定や流量制御などを集中管理できます。
プロダクトチーム用のNamespaceには、各チームのアプリケーションとHTTPRouteリソースをデプロイします。HTTPRouteリソースでは、アプリケーションへのルーティングを定義します。
さらに、認証・認可機能を提供するために、keycloak NamespaceにKeycloakをデプロイします。
今回、プロダクトチームのアプリケーションへの通信は認証されたクライアントのみに制限します。クライアントが認証済みかどうかはトークンイントロスペクションで確認します。トークンイントロスペクションとはアクセストークンの有効性を確認する手段の1つで、トークンの失効や状態変化を即時反映できる仕組みです。GatewayからAuthorinoを経由してKeycloakのイントロスペクションエンドポイントを利用し、アクセストークンの有効性を確認します。
手順の前提
プラットフォームそのものの構築方法とプラットフォームの利用方法を区別するため、以降の手順では「プラットフォームチームの作業」と「プロダクトチームの作業」を分けて説明します。
なお、手順で使用する各種マニフェストはGitHubリポジトリにもまとめて掲載しています。本記事にもマニフェストの内容を記載していますが、コピー&ペーストの手間を省きたい場合はリポジトリをクローンして利用すると便利です。
Kubernetesクラスタの準備
(プラットフォームチームの作業)
今回はAWSのEC2上でKubernetesクラスタを構築したため、ロードバランサーの関係で「K3s」を利用します。ローカルのPCで構築する場合は「kind」や「Docker Desktop」を利用しても問題ありません。
K3sは軽量なKubernetesディストリビューションで、特にリソースが限られた環境や開発環境での利用に適しています。また、デフォルトで「ServiceLB」というロードバランサーがインストールされているため、Gateway APIの検証にも便利です。
K3sのインストール
K3sのインストールは以下のコマンドで行います。今回はGateway APIを使いたいので、ノードのIPアドレスを使ってしまうTraefik(Ingress Controller)を無効化しておきます。
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="server" sh -s - --disable traefik
Docker Hubのプル制限対策
コンテナイメージを取得する際には「Docker Hub」を使うことが多いですが、Docker Hubにはイメージのプル制限があります。プル制限が気になる場合はミラーレジストリを設定することをおすすめします。
K3sのミラーレジストリの設定は/etc/rancher/k3s/registries.yamlで行います。
cat << EOF | sudo tee /etc/rancher/k3s/registries.yaml > /dev/null
mirrors:
docker.io:
endpoint:
- "https://mirror.gcr.io"
設定したらK3sを再起動しましょう。
sudo systemctl restart k3s
kubeconfigの設定
K3sのkubeconfigは/etc/rancher/k3s/k3s.yamlに生成されるので、それを〜/.kube/配下にコピーします。
sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/
このままだとk3s.yamlの所有者はrootユーザーになっており、自分の操作ユーザーでは読み取れません。そのため、k3s.yamlの所有者を自分のユーザーに変更します。
sudo chown $(whoami) ~/.kube/k3s.yaml
環境変数KUBECONFIGに~/.kube/k3s.yamlを設定します。
export KUBECONFIG=~/.kube/k3s.yaml
既存のkubeconfigと共存させたい場合は、以下のように「:」でつなぐこともできます。
export KUBECONFIG=~/.kube/config:~/.kube/k3s.yaml
Gateway APIのインストール
(プラットフォームチームの作業)
Gateway APIはあくまでKubernetesが定義したフレームワークであり、実際のゲートウェイ実装は「NGINX Gateway Fabric」や「Envoy Gateway」「Istio」などのプロジェクトが提供します。今回はEnvoy Gatewayを利用します。
Envoy Gatewayをhelmでインストールします。また、Kuadrantを利用するため、EnvoyPatchPolicyを有効にします。
helm upgrade --install eg oci://docker.io/envoyproxy/gateway-helm \
--version v1.4.1 \
--set config.envoyGateway.extensionApis.enableEnvoyPatchPolicy=true \
-n envoy-gateway-system --create-namespace
以下のように、Envoy Gatewayがデプロイされていることが確認できます。
$ kubectl get po -n envoy-gateway-system
NAME READY STATUS RESTARTS AGE
eg-gateway-helm-certgen-l7s9d 0/1 Completed 0 114s
envoy-gateway-7c88d4fff4-lg4dd 1/1 Running 0 23s
Gateway Classのマニフェストを作成します。Gateway Classは、Gateway APIの実装(Envoy GatewayやNGINX Fabric Gatewayなど)を指定するためのリソースです。Gatewayを作成する際にGateway Classを指定することで、どの実装を使うかを決定します。 今回はEnvoy Gatewayを指定します。
# envoy-gateway/gateway-class.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: envoy-gateway
spec:
controllerName: gateway.envoyproxy.io/gatewayclass-controller
作成したGateway Classをデプロイします。
kubectl apply -f envoy-gateway/gateway-class.yaml
共有Gatewayの作成
(プラットフォームチームの作業)
次に、共有Gatewayを作成します。共有Gatewayは、複数のプロダクトチームが共通で利用するAPIゲートウェイです。これにより、各プロダクトチームは自分たちのNamespace内でHTTPRouteを定義し、共有Gatewayを通じて外部と通信できるようになります。
shared-gateway Namespaceを作成します。
kubectl create namespace shared-gateway
共有Gatewayのマニフェストを作成します。
# shared-gateway/shared-gateway.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: shared-gateway
namespace: shared-gateway
spec:
gatewayClassName: envoy-gateway
listeners:
# Keycloak用リスナー
- name: keycloak
protocol: HTTP
port: 80
# 172.32.4.127 はEC2インスタンスのIPアドレスです。実際の環境に合わせて変更してください。
hostname: keycloak.172.32.4.127.nip.io
allowedRoutes:
namespaces:
from: Selector
selector:
matchLabels:
kubernetes.io/metadata.name: keycloak
kinds:
- kind: HTTPRoute
# echo API用リスナー(テナント)
- name: echo
protocol: HTTP
port: 80
hostname: "echo.172.32.4.127.nip.io"
allowedRoutes:
namespaces:
from: Selector
selector:
matchLabels:
kubernetes.io/metadata.name: echo
kinds:
- kind: HTTPRoute
このマニフェストでは、2つのリスナーを定義しています。
- Keycloak用リスナー: KeycloakのAPIにアクセスするためのリスナー。KeycloakのNamespaceにデプロイされたHTTPRouteのみを許可する
- echo API用リスナー: プロダクトチームが管理するecho APIにアクセスするためのリスナー。echo APIのNamespaceにデプロイされたHTTPRouteのみを許可する
【補足】nip.ioはワイルドカードDNSで、例えばホスト名がecho.172.32.4.127.nip.ioだと172.32.4.127が返ってきます。これにより、/etc/hostsやDNSサーバーを使わずともホスト名を使ったルーティングが可能になります。
共有Gatewayをデプロイします。
kubectl apply -f shared-gateway.yaml
以下のように、共有Gatewayが作成されていることが確認できます。172.32.4.127はEC2インスタンスのIPアドレスです。K3sのServiceLBはノードのIPアドレスをServiceに割り当てます。
$ kubectl get gateway -n shared-gateway
NAME CLASS ADDRESS PROGRAMMED AGE
shared-gateway envoy-gateway 172.32.4.127 True 5d22h
プロダクトチームのアプリケーションデプロイ
(プロダクトチームの作業)
ここではプロダクトチームは「echo API」というアプリケーションを作っているとしましょう。echo APIの実装はecho-basicです。echo-basicはHTTPリクエストを受け取り、レスポンスを返すだけのシンプルなアプリケーションです。プロダクトチームは自分たちのNamespace echoにアプリケーションをデプロイします。
【補足】今回はKubernetesに対するすべての操作をcluster-admin権限(最も強い権限)で行っていますが、実際にプラットフォームを構築する場合は、プロダクトチームの各開発者に対してNamespace内に絞った権限を付与する必要があります。
プロダクトチーム用のNamespaceを作成します(プラットフォームチームがNamespaceを作成することもあります)。
kubectl create namespace echo
echo APIのマニフェストを作成します。
# echo/echo-basic.yaml
apiVersion: v1
kind: Service
metadata:
name: echo-svc
namespace: echo
labels:
example: http-routing
spec:
ports:
- name: http
port: 8080
targetPort: 3000
selector:
app: echo-backend
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: echo-backend
namespace: echo
labels:
app: echo-backend
example: http-routing
spec:
replicas: 1
selector:
matchLabels:
app: echo-backend
template:
metadata:
labels:
app: echo-backend
spec:
containers:
- name: echo-backend
image: gcr.io/k8s-staging-gateway-api/echo-basic:v20231214-v1.0.0-140-gf544a46e
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
resources:
requests:
cpu: 10m
echo APIをデプロイします。
kubectl apply -f echo/echo-basic.yaml
HTTPRouteを作成して、共有Gatewayを通じてecho APIにアクセスできるようにします。
# echo/httproute.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: echo-route
namespace: echo
spec:
hostnames:
- "echo.172.32.4.127.nip.io"
parentRefs:
# 共有Gatewayを指定
- name: shared-gateway
namespace: shared-gateway
sectionName: echo
rules:
- name: root
matches:
- path:
type: PathPrefix
value: /
backendRefs:
# echo APIのServiceを指定
- name: echo-svc
port: 8080
kind: Service
weight: 1
動作確認を行います。
$ curl http://echo.172.32.4.127.nip.io
{
"path": "/",
"host": "echo.172.32.4.127.nip.io",
"method": "GET",
"proto": "HTTP/1.1",
"headers": {
"Accept": [
"*/*"
],
"User-Agent": [
"curl/8.5.0"
],
"X-Envoy-External-Address": [
"10.42.0.1"
],
"X-Forwarded-For": [
"10.42.0.1"
],
"X-Forwarded-Proto": [
"http"
],
"X-Request-Id": [
"eb2f3a73-bb37-4ddc-8686-cf267ecf8f03"
]
},
"namespace": "echo",
"ingress": "",
"service": "",
"pod": "echo-backend-7564b6f58c-z454r"
}
API認可の設定
ここまで設定すると、プロダクトチームが自分たちのアプリケーションとHTTPRouteをデプロイすることで、共有Gatewayを通じて外部からアクセスできるようになります。しかし、現状では誰でもアクセスできる状態なので、KuadrantとKeycloakを使ってAPIの認可を設定していきます。
Kuadrantインストール(プラットフォームチームの作業)
Kuadrantは、Kubernetes環境におけるAPIのセキュリティ設定や流量制御、トラフィック管理を可能とする拡張機能を提供するプロダクトです。Kuadrantの中で認証認可を行うコンポーネントが「Authorino」です。今回はAPIの認可をKeycloakと連携して行います。
Kuadrantをインストールします。
Kuadrantの前提であるcert-managerをインストールします。
helm upgrade --install \
cert-manager cert-manager \
--version v1.15.3 \
--repo https://charts.jetstack.io \
--namespace cert-manager \
--create-namespace \
--set crds.enabled=true
次に「Kuadrant Operator」をインストールします。Kuadrant Operatorは、Kuadrantのリソースを管理するためのコントローラーです。
helm upgrade --install \
kuadrant-operator kuadrant-operator \
--version 1.2.0 \
--repo https://kuadrant.io/helm-charts/ \
--create-namespace \
--namespace kuadrant-system
Kuadrantのマニフェストを作成します。
# kuadrant/kuadrant.yaml
apiVersion: kuadrant.io/v1beta1
kind: Kuadrant
metadata:
name: kuadrant
namespace: kuadrant-system
Kuadrantをデプロイします。ここでデプロイされるのは、実際の通信に認証認可や流量制御などの処理を行うKuadrantコンポーネントです。
kubectl apply -f kuadrant/kuadrant.yaml
Kuadrant OperatorとKuadrantのコンポーネントがデプロイされていることを確認します。
$ kubectl get po -n kuadrant-system
NAME READY STATUS RESTARTS AGE
authorino-744569f65-h7sds 1/1 Running 0 13m
authorino-operator-55c46d7d78-vbxt8 1/1 Running 0 51m
dns-operator-controller-manager-6dcd48c697-dqltr 1/1 Running 0 51m
kuadrant-operator-controller-manager-5d5f9bc775-zxtsj 1/1 Running 0 51m
limitador-limitador-84bdfb4747-tj7wv 1/1 Running 0 13m
limitador-operator-controller-manager-5d5c4c478f-l6k6n 1/1 Running 0 51m
xxx-operatorはKuadrantのOperatorコンポーネントで、Kuadrantのリソースが作成されたときに対応する処理を行います。authorinoは認証認可を行うコンポーネントです。limitadorは流量制御を行います。
共有Gatewayにデフォルトで全トラフィックを拒否させる
(プラットフォームチームの作業)
Kuadrantの設定が終わり、各プロダクトチームが自分たちのAPIに認可をかけられるようになりました。あとは各プロダクトチームがAuthPolicyを作成し、HTTPRouteに認可設定を行うだけです。
しかし、認可設定をすべて各プロダクトチームに任せてしまって良いのでしょうか。もちろん、本記事を読むような意識の高い方々はきちんと認可設定を行うと思いますが、すべての開発者が高いセキュリティ意識を持っているわけではありません。そこで、各プロダクトチームが認可設定を忘れた場合にトラフィックが拒否されるよう、共有Gatewayにデフォルトで全てのトラフィックを拒否するポリシーを適用します。
認証認可のポリシーはKuadrantのAuthPolicyというリソースで定義します。AuthPolicyは、GatewayもしくはHTTPRouteに認証認可のポリシーを適用するためのリソースです。
共有Gatewayに対して全てのトラフィックを拒否するAuthPolicyを作成します。
まず、マニフェストを作成します。
# shared-gateway/authpolicy-deny-all.yaml
apiVersion: kuadrant.io/v1
kind: AuthPolicy
metadata:
name: shared-gateway-auth-policy-deny-all
namespace: shared-gateway
spec:
targetRef:
# ポリシーの適用対象を共有Gatewayにする
group: gateway.networking.k8s.io
kind: Gateway
name: shared-gateway
defaults:
strategy: atomic # HTTPRoute側のポリシーで上書きできるようにする
rules:
authorization:
deny-all:
opa:
# 全てのトラフィックを拒否するポリシー
rego: |
allow = false
response:
# レスポンスを設定
unauthorized:
headers:
"content-type":
value: application/json
body:
value: |
{
"error": "Forbidden",
"message": "403 Forbidden: All traffic is denied by the AuthPolicy configuration for the Gateway."
}
AuthPolicyをデプロイします。
kubectl apply -f shared-gateway/authpolicy-deny-all.yaml
これで、今までアクセスできていたecho APIに対して、全てのトラフィックが拒否されるようになります。実際にアクセスしてみましょう。
$ curl http://echo.172.32.4.127.nip.io
{
"error": "Forbidden",
"message": "403 Forbidden: All traffic is denied by the AuthPolicy configuration for the Gateway."
}
この設定を各HTTPRouteに対するAuthPolicyで上書きすることで、初めてアクセスが許可されるように設計します。
Keycloakのインストール(プラットフォームチームの作業)
KeycloakをインストールしてAPIを認可します。KeycloakはオープンソースのIDアクセス管理プロダクトで、APIプラットフォームに認証認可機能を提供します。
まずは、KeycloakのNamespaceを作成します。
kubectl create namespace keycloak
Keycloakをデプロイします。
kubectl apply -n keycloak -f https://raw.githubusercontent.com/Hitachi/oss-assets/refs/heads/main/article/thinkit-smallplatform/02-api-gateway/keycloak/keycloak.yaml
今回は管理者ユーザーのパスワードをadminに設定しています。実際の運用で同じことをすると非常に大きなセキュリティホールになるため、実運用では複雑なパスワードを設定するようにしてください。
続いて、KeycloakのHTTPRouteのマニフェストを作成します。
# keycloak/httproute.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: keycloak-route
namespace: keycloak
spec:
hostnames:
- keycloak.172.32.4.127.nip.io
parentRefs:
- name: shared-gateway
namespace: shared-gateway
sectionName: keycloak
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: keycloak
port: 8080
kind: Service
weight: 1
KeycloakのHTTPRouteをデプロイします。
kubectl apply -f keycloak/httproute.yaml
このままでは、共有Gatewayに設定したAuthPolicyにより、Keycloakへのトラフィックも拒否されてしまいます。Keycloakは自身で認証認可機能を持っているので、Keycloakへのアクセスを制御する必要はありません。そこで、Keycloakへの全てのトラフィックを許可するAuthPolicyを作成します。まず、マニフェストを作成します。
# keycloak/authpolicy-allow-all.yaml
apiVersion: kuadrant.io/v1
kind: AuthPolicy
metadata:
name: keycloak-authpolicy-allow-all
namespace: keycloak
spec:
targetRef:
group: gateway.networking.k8s.io
kind: HTTPRoute
name: keycloak-route
defaults:
rules:
authentication:
"anonymous":
anonymous: {}
マニフェストができたらデプロイします。
kubectl apply -f keycloak/authpolicy-allow-all.yaml
Keycloakへアクセスしてみましょう。ホスト名はHTTPRouteを参照してください。
$ kubectl get httproute -n keycloak keycloak-route
NAME HOSTNAMES AGE
keycloak-route ["keycloak.172.32.4.127.nip.io"] 29m
Webブラウザでhttp://keycloak.172.32.4.127.nip.ioにアクセスします。ユーザー名/パスワードは、Keycloakデプロイ時に設定したadmin/adminです。
KeycloakのRealm作成(プラットフォームチームの作業)
KeycloakのRealmはKeycloak内でのテナントのようなもので、各Realmごとにユーザーやクライアントを管理できます。今回はecho APIを呼び出すアプリケーション用のRealmを作成します。Realmの名前はechoとします。
まず、Manage realms(下図①)からCreate realm(下図②)をクリックします。
Realm name(下図①)にechoと入力し、Create(下図②)をクリックします。
Realmを作成したら、自動的にechoに切り替わっているのがKeycloakの画面から確認できます。Keycloakのバージョンによって画面のどこに現在のRealmが表示されるのか異なりますが、v26.3.2では画面左上とManage realmsメニューから確認できます。切り替わってない場合は、Manage realmsメニューからechoを選択してください。
【補足】今回はecho APIを呼び出すアプリケーションを認証するためのRealmのみを作成しました。しかし、実際にプラットフォームを構築する場合はプラットフォームを利用する開発者を認証するためのRealmも作成し、Kubernetesをはじめとする各種プラットフォーム機能への認証認可も行う必要があります。
Keycloakのクライアント作成(プロダクトチームの作業)
Keycloakのクライアントとは、Keycloakが認証認可を提供するアプリケーションやサービスのことを指します。今回作成するクライアントは2つです。1つはAuthorino用のクライアントtoken-introspection、もう1つはecho APIを呼び出すアプリケーション用のクライアントechoです。
まず、Authorino用のクライアントtoken-introspectionを作成します。このクライアントはAuthorinoがKeycloakのイントロスペクションエンドポイントを呼び出すために使用します。 Keycloakの管理画面のClientsメニュー(下図①)からCreate client(下図②)をクリックします。
Client ID(下図①)にtoken-introspectionと入力し、Next(下図②)をクリックします。
トークンイントロスペクションではクライアント認証を行うため、Client authenticationを有効にし(下図①)、Authentication flowは全てオフにします。Standard flow(下図①)がデフォルトでオンになっているので、チェックを外します。
Login settingsはデフォルトの設定で問題ありません。
token-introspectionクライアント設定のCredentials(下図①)からClient Secret(下図②)をコピーしてメモします。
ここで、Authorino用のクライアントシークレットをKubernetesのSecretとして保存します。Kuadrantは、このSecretを使ってKeycloakのイントロスペクションエンドポイントを呼び出します。ただし、Secretはkuadrant-system Namespaceに作成する必要があります。プラットフォームチームのNamespaceに作成する必要があるため、kuadrant-systemの特定の名前のシークレットのみに権限を付与するなどの工夫が必要です。
kubectl create secret generic keycloak-echo-token-introspection-client-secret \
-n kuadrant-system \
--from-literal=clientID=token-introspection \
--from-literal=clientSecret=<your client secret>
次に、echo API用のクライアントechoを作成します。こちらは、実際にAPIを呼び出す側を認証するためのクライアントです。先ほどと同様に、Clientsメニュー(下図①)からクライアントを作成(下図②)します。
Client ID(下図①)にechoと入力し、Next(下図②)をクリックします。
今回は単純に「クライアントクレデンシャルズグラント」を使ってトークンを発行します。これは、クライアントIDとクライアントシークレットを使ってアクセストークンを取得するOAuth2の認可フローです。ユーザーの介在なしにアプリケーションが自身を認証し、トークンを取得するために使用されます。
Client authenticationtを有効(下図①)にし、Authentication flowのService accounts roles(下図②)を有効にします。こちらもStandard flow(下図③)がデフォルトでオンになっているので、チェックを外してください。
Login settingsはデフォルトの設定で問題ありません。
echoクライアント設定のCredentials(下図①)からClient Secret(下図②)をコピーしてメモしておいてください。APIを呼び出す際に必要です。
echo APIの認可設定(プロダクトチームの作業)
echo APIの認可設定をします。KuadrantのAuthPolicyを使って、Keycloakのトークンイントロスペクションを利用した認可を設定します。
HTTPRouteにAuthPolicyを設定すると、Gatewayに届いたトラフィックのうちecho APIへ向かうリクエストだけに、そのAuthPolicyが適用されます。今回の例では、GatewayはAuthorinoを通じてKeycloakにトークンイントロスペクションを行い、トークンの有効性を確認します。
AuthPolicyのマニフェストを作成します。
# echo/authpolicy.yaml
apiVersion: kuadrant.io/v1
kind: AuthPolicy
metadata:
name: echo-authpolicy
namespace: echo
spec:
targetRef:
group: gateway.networking.k8s.io
kind: HTTPRoute
name: echo-route
defaults:
rules:
authentication:
"keycloak-introspection":
oauth2Introspection:
# Keycloakのイントロスペクションエンドポイントを指定
endpoint: http://keycloak.172.32.4.127.nip.io/realms/echo/protocol/openid-connect/token/introspect
credentialsRef:
# Keycloakから取得したクライアントIDとクライアントシークレットを格納したSecretを指定
name: keycloak-echo-token-introspection-client-secret
response:
unauthenticated:
headers:
"content-type":
value: application/json
body:
value: |
{
"error": "Unauthorized",
"message": "401 Unauthorized: token introspection failed."
}
unauthorized:
headers:
"content-type":
value: application/json
body:
value: |
{
"error": "Forbidden",
"message": "403 Forbidden: policy check failed."
}
AuthPolicyをデプロイします。
kubectl apply -f echo/authpolicy.yaml
動作確認
クライアントクレデンシャルズグラントを使って、echo APIにアクセスできることを確認します。まず、クライアント認証でKeycloakからアクセストークンを取得してください。
CLIENT_ID=echo
CLIENT_SECRET=<Keycloakから取得したechoクライアントのクライアントシークレット>
ACCESS_TOKEN=$(curl -X POST "http://keycloak.172.32.4.127.nip.io/realms/echo/protocol/openid-connect/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=$CLIENT_ID" \
-d "client_secret=$CLIENT_SECRET" | jq -r '.access_token')
Keycloakから取得したアクセストークンを使ってecho APIにアクセスします。以下のコマンドを実行し、アクセストークンをAuthorizationヘッダーに設定してリクエストを送ってください。
curl -H "Authorization: Bearer $ACCESS_TOKEN" "http://echo.172.32.4.127.nip.io"
以下のようなレスポンスが返ってくれば、認可が成功しています。
{
"path": "/",
"host": "echo.172.32.4.127.nip.io",
"method": "GET",
"proto": "HTTP/1.1",
"headers": {
"Accept": [
"*/*"
],
"Authorization": [
"Bearer <Keycloakから取得したechoクライアントのアクセストークン>"
],
"User-Agent": [
"curl/8.5.0"
],
"X-Envoy-External-Address": [
"10.42.0.1"
],
"X-Forwarded-For": [
"10.42.0.1"
],
"X-Forwarded-Proto": [
"http"
],
"X-Request-Id": [
"62bc0256-f27c-47e8-a16c-2229681b82e4"
]
},
"namespace": "echo",
"ingress": "",
"service": "",
"pod": "echo-backend-7564b6f58c-z454r"
}
ソフトウェアのバージョン
今回利用したソフトウェアのバージョンは下表の通りです。
| ソフトウェア名 | バージョン |
|---|---|
| K3s | v1.32.6+k3s1 (eb603acd) |
| Envoy Gateway | v1.4.1 |
| Kuadrant | 1.2.0 |
| cert-manager | v1.15.3 |
| Keycloak | 26.3.2 |
まとめ
今回は、Kubernetes上にAPIプラットフォームのベーシックな機能セットを構築する手順を解説しました。
- チームごとにNamespaceを分離し、マルチテナントを実現
- Gateway APIでAPIのルーティングを実現
- Kuadrant(Authorino)+KeycloakでAPIの認可を実現
今回の構成で、プロダクトチームはコンテナのデプロイ、ルーティング、簡単なAPI認可をKubernetesのインターフェースを通じて行うことができるようになりました。
しかし、ドキュメントは整備されていないので、プロダクトチームはプラットフォームチームのサポートなしでは各種リソースを作成することが難しいかもしれません。そのためプラットフォームエンジニアリングの成熟度モデルに当てはめると、インターフェースモデルのレベル1「暫定的である - 機能毎に独自の手順」からレベル2「戦略的である - 標準ツール」に相当します。インターフェースは一貫していますが、ドキュメントやテンプレートが整備されていない状態です。
また、今回作成したプラットフォームの導入前後での開発プロセスの変化を下表にまとめます。変わっていない箇所は今後の連載で改善していきます。
| 大プロセス | 小プロセス | プラットフォーム導入前 | 今回のプラットフォーム導入後 |
|---|---|---|---|
| セキュリティ要件定義 | 認証要件の設計 | 手動 | Keycloakで標準化 |
| 認可要件の設計 | 手動 | Authorinoで部分自動化 | |
| OpenAPI仕様作成 | OpenAPIの設計 | 手書き | 手書き |
| セキュリティスキーマの定義 | 手書き | Keycloak定義 | |
| API仕様の公開 | メール送付など | メール送付など | |
| 実装 | APIの実装 | 手動実装 | 手動実装 |
| 認証・認可の実装 | コード実装 | Authorinoで自動化 | |
| ルーティング設定 | 手動設定 | Gateway APIで宣言管理 | |
| デプロイと運用 | デプロイ | 手動 | Kubernetesで自動化 |
| 監視・可観測性の導入 | ログ出力と手動確認 | ログ出力と手動確認 | |
| ポリシー準拠チェック | 手動レビュー | 手動レビュー | |
| 認可ポリシーの連携 | 手動実装 | 手動実装 |
次回以降では、この構成をベースにオブザーバビリティやCI/CDなど、さらに多くのプラットフォームの機能を追加していきます。まずは本記事の内容を実際に手を動かして試し、APIプラットフォームの基礎を体験してみてください。
- この記事のキーワード