カスタム組織ポリシーで GKE クラスタの安全な設定を強制する
この記事は Google Cloud Japan Advent Calendar 2022 の 2 日目の記事です。Preview で利用可能になった組織ポリシーのカスタム制約を使って、GKE クラスタに安全な設定を強制するよう構成してみます。
tl;dr
- 組織ポリシーのカスタム制約機能により、利用者による独自の制約を設定することができるようになりました (2022.12 現在 Preview です)
- カスタム制約を活用し、GKE のクラスタ構成に関する柔軟かつ強固な制約を組織全体や特定フォルダ配下のプロジェクトに適用することができます
組織ポリシーとは
組織ポリシーは Google Cloud の組織やフォルダ配下、特定プロジェクトに対して制約を設定することができる機能です。組織全体や特定フォルダに対してポリシーを設定すると、その配下のプロジェクトに自動的にポリシーが継承されるため、ガバナンスを効かせることができます。本機能の利用にあたり追加コストは発生しません (無料です)。
元々組織ポリシーは事前に定義された制約の中から自組織に合うものを選択し適用するものでしたが、本記事で紹介するカスタム制約の登場により利用者側でも独自にポリシーを作成し柔軟な制約を設定できるようになりました。
Google Cloud リソースに対する柔軟な制約は Terraform (Conftest 等の利用) や Config Controller のレイヤーでかけることもできますが、組織ポリシーを使うことで gcloud や Cloud Console で直接触られた場合など CI を通さない変更に対しても強制力を効かせることができます。
ただし 2022.12 現在、カスタム制約は Preview ステータスであり、また GKE と Dataproc しかサポートしていないのでご注意ください。
カスタム制約
カスタム制約のフォーマットは以下のようになっています。
name: organizations/ORGANIZATION_ID/customConstraints/custom.CONSTRAINT_NAME
resourceTypes:
- container.googleapis.com/RESOURCE_NAME
methodTypes:
- METHOD1
- METHOD2
condition: resource.OBJECT_NAME.FIELD_NAME == VALUE
actionType: ACTION
displayName: DISPLAY_NAME
description: DESCRIPTION
-
name
でカスタム制約の名前を設定 (接頭辞を抜いて最大 100 文字) -
resourceTypes
で制約をかける対象のリソースを指定 (Cluster
やNodePool
) -
methodTypes
で制約をかける対象のメソッドを指定 (CREATE
単体もしくはCREATE
とUPDATE
を両方指定) -
condition
で制約の条件を CEL (Common Expression Language) で設定 -
actionType
で制約が条件に合致した際のアクションを定義 (ALLOW
またはDENY
)
actionType
が DENY
の場合は condition
に合致したもののみ拒否しそれ以外は許可するという挙動になり、ALLOW
の場合は condition
に合致したもののみ許可しそれ以外は拒否する (Default Deny) 挙動となります。ちなみに ALLOW
と DENY
が競合した場合は DENY
が優先されます。
ALLOW
と DENY
どちらの制約を書くべきか、については condition
の複雑さや強制したい制約の意図等をベースに設定するのが良いかと思います。
GKE クラスタに対するカスタム制約は Google Kubernetes Engine API v1 の Cluster
または NodePool
リソースの任意フィールドに対する CREATE
メソッドもしくは CREATE
と UPDATE
メソッド両方に対して設定することができます (UPDATE
単体に対する制約はサポートされていません)。各種 API の仕様については以下ドキュメントをご参照ください。
実際に試してみる
では以下 3 パターンを例に実際に試してみようと思います。
① 本番環境ではアルファクラスタのデプロイを禁止する
アルファクラスタ は Kubernetes のアルファ版機能をお試しいただけるクラスタです。アルファクラスタは検証用途のクラスタとなっており SLA 無しかつアップグレード不可、またクラスタ自体の有効期限も 30 日となっていますので本番環境での利用は非推奨となっています。
開発環境でアルファクラスタを使ってもらう分にはいいけど、本番環境に間違ってデプロイしてほしくないというケースもあるかと思いますのでカスタム制約を作って防いでみます。
② 本番環境ではセキュアな構成の GKE クラスタを強制する
本番環境ではなるべくセキュアな構成のクラスタしか動かしたくないということもあると思います。今回は一例として以下のような構成のクラスタのみデプロイさせるような制約を設定してみます。
本記事では上記の各オプション・機能の説明は割愛しますが、気になる方は以下のブログ記事も読んでみてください。
③ 開発環境では Spot VM のみ許可する
(安全な構成という趣旨から逸れますが)コスト削減のために開発環境は Spot VM のみ利用を許可したいというケースもあるかもしれないので、そういうパターンも試してみます。
これからご紹介する制約はあくまでデモ用に用意したものなので、もし試す場合は検証用の組織など本番ワークロードに影響がでない環境でお試しください。(あとツッコミどころなどあれば教えてください)
① 本番環境ではアルファクラスタのデプロイを禁止する
まずは本番環境ではアルファクラスタが作成できないようにカスタム制約を設定していきます。
1. カスタム制約の作成
はじめに、設定したい内容の制約を書いていきます。Kubernetes Engine API の仕様を確認したところ、今回防ぎたいアルファクラスタの有効化
は Cluster リソースの enableKubernetesAlpha
フィールドの boolean により設定されるようです。なのでこのフィールドに対する制約を書いてみます。
まず resourceTypes
は container.googleapis.com/Cluster
を指定します。
アルファクラスタの新規作成を防ぎたい (かつアルファクラスタのパラメータは immutable である) ため、methodTypes
には CREATE
を指定します。
制約の条件を設定する condition
には resource.enableKubernetesAlpha == true
を設定し、条件に合致すると拒否するように actionType
を DENY
に設定します。
name: organizations/ORGANIZATION_ID/customConstraints/custom.disableAlphaClusters
resourceTypes:
- container.googleapis.com/Cluster
methodTypes:
- CREATE
condition: resource.enableKubernetesAlpha == true
actionType: DENY
displayName: Disable Alpha Clusters
description: All new GKE clusters must have Alpha Clusters disabled
続いて、カスタム制約を作成をします。 gcloud org-policies set-custom-constraint
コマンドにより、先ほど用意した yaml ファイルを使って組織内にカスタム制約を作成します。作成したカスタム制約は gcloud org-policies list-custom-constraints
や Cloud Console 上から確認することができます。
$ export ORGANIZATION_ID=<ORGANIZATION ID>
$ gcloud org-policies set-custom-constraint constraint-disableAlphaClusters.yaml
Created custom constraint [organizations/ORGANIZATION_ID/customConstraints/custom.disableAlphaClusters].
$ gcloud org-policies list-custom-constraints --organization=$ORGANIZATION_ID
CUSTOM_CONSTRAINT ACTION_TYPE METHOD_TYPES RESOURCE_TYPES DISPLAY_NAME
custom.disableAlphaClusters DENY CREATE container.googleapis.com/Cluster Disable Alpha Clusters
2. カスタム制約の適用
この制約は本番環境だけに適用したいので、作成したカスタム制約 disableAlphaClusters
を prod
フォルダに適用します。これにより、 prod
フォルダ配下に作成されるプロジェクト全てに対して disableAlphaClusters
が適用されるようになります。
3. アルファクラスタを作成してみる
では挙動を確認していきます。アルファクラスタでは自動アップグレードができないため Release Channel からは外し、また自動アップグレード・自動修復を無効にした上で --enable-kubernetes-alpha
を付けてクラスタを作成してみます。
$ export PROJECT_ID=<PROD PROJECT ID>
$ gcloud config set project ${PROJECT_ID}
$ export CLUSTER_LOCATION=asia-northeast1-b
$ export CLUSTER_NAME=alpha-prod
$ gcloud container clusters create ${CLUSTER_NAME} \
--zone ${CLUSTER_LOCATION} \
--num-nodes=2 \
--cluster-version "1.23.12-gke.100" \
--release-channel "None" \
--no-enable-autoupgrade \
--no-enable-autorepair \
--enable-kubernetes-alpha
ERROR: (gcloud.container.clusters.create) ResponseError: code=400, message=Operation denied by custom org policy: ["customConstraints/custom.disableAlphaClusters": "All new GKE clusters must have Alpha Clusters disabled"].
想定通りエラーが出力しクラスタ作成に失敗しました。これで本番環境ではカスタム制約によりアルファクラスタが有効になっているクラスタは作れないようになりました。ただ、dev
フォルダ配下にある開発環境ではアルファクラスタの作成はできるはずです。念の為そちらも試してみます。
$ export PROJECT_ID=<DEV PROJECT ID>
$ gcloud config set project ${PROJECT_ID}
$ export CLUSTER_NAME=alpha-dev
$ gcloud container clusters create ${CLUSTER_NAME} \
--zone ${CLUSTER_LOCATION} \
--num-nodes=2 \
--cluster-version "1.23.12-gke.100" \
--release-channel "None" \
--no-enable-autoupgrade \
--no-enable-autorepair \
--enable-kubernetes-alpha
Creating cluster alpha-dev in asia-northeast1-b... Cluster is being health-checked (master is healthy)...done.
Created [https://container.googleapis.com/v1/projects/kuchima-adventcal2022-dev/zones/asia-northeast1-b/clusters/alpha-dev].
無事クラスタが作成できました。これでフォルダを使った組織ポリシー適用の挙動が確認できました。アルファクラスタを無効にしたクラスタが prod
配下で作成できるかは、次のパターン② 本番環境ではセキュアな構成の GKE クラスタを強制する
であわせて確認していきます。
② 本番環境ではセキュアな構成の GKE クラスタを強制する
本番環境ではなるべくセキュアな構成のクラスタしか動かしたくないということで、一例として以下の構成のクラスタのみデプロイさせるような制約を設定してみます。
- プライベートクラスタ (Private Endpoint)
- Workload Identity
- Shielded GKE Node
- COS Node Pool
上記は割とよくある設定なので、制約を書き始める前にまず既存の組織ポリシーでカバーができないか考えてみることにします。既存のポリシーは公式ドキュメントや Cloud Console 上から確認ができるので見てみましょう。
確認したところ、プライベートクラスタに関しては constraints/compute.vmExternalIpAccess
という既存のポリシーで Node への外部 IP アドレスの付与を防いでくれそうなので活用できそうです。ただし Privtate Endpoint を強制してくれるわけでは無いのでそこはカスタムで作成が必要そうです。
また、Shielded GKE Node については constraints/compute.requireShieldedVm
という既存のポリシーで事足りそうです。
その他の Workload Identity や COS Node の利用を強制する既存ポリシーはなさそうなので、以下3種類のカスタム制約を作成してみます。
- Privtate Endpoint を強制する制約
- Workload Identity を強制する制約
- COS Node の利用を強制する制約
1. カスタム制約の作成
まずは Private Endpoint の有効化を強制する制約を作成します。Private Endpoint は Cluster
リソースで設定しています。今回は resource.privateClusterConfig.enablePrivateEndpoint
が true
になっているかをチェックするような制約を書きます。
name: organizations/ORGANIZATION_ID/customConstraints/custom.enablePrivateEndpoint
resource_types: container.googleapis.com/Cluster
method_types:
- CREATE
- UPDATE
condition: resource.privateClusterConfig.enablePrivateEndpoint == true
action_type: ALLOW
display_name: Enable GKE Private Endpoint
description: Only allow GKE Cluster resource create or update if Private Endpoint is enabled
続いて Workload Identity の有効化を強制する制約です。Workload Identity の有効・無効を判断するために resource.workloadIdentityConfig.workloadPool
の有無をチェックしています。
name: organizations/ORGANIZATION_ID/customConstraints/custom.enableWorkloadIdentity
resourceTypes:
- container.googleapis.com/Cluster
methodTypes:
- CREATE
condition: has(resource.workloadIdentityConfig.workloadPool) || resource.workloadIdentityConfig.workloadPool.size() > 0
actionType: ALLOW
displayName: Enable Workload Identity on new clusters
description: All new clusters must use Workload Identity.
最後に COS Node のチェックです。Node の設定になるため、NodePool リソースの制約を設定します。
Node のイメージタイプは NodePool リソースの NodeConfig、具体的には resource.config.imageType
フィールドで指定されます。COS (containerd) が利用されているかどうかはこちらを確認するのが良さそうです。
name: organizations/ORGANIZATION_ID/customConstraints/custom.requireCOSNode
resourceTypes:
- container.googleapis.com/NodePool
methodTypes:
- CREATE
condition: resource.config.imageType == "COS_CONTAINERD"
actionType: ALLOW
displayName: Require COS Nodes
description: All cluster nodes must be COS.
マニフェストが作成できたら gcloud org-policies set-custom-constraint
でカスタム制約を組織内に作成します。
$ gcloud org-policies set-custom-constraint constraint-enablePrivateEndpoint.yaml
Created custom constraint [organizations/ORGANIZATION_ID/customConstraints/custom.enablePrivateEndpoint].
$ gcloud org-policies set-custom-constraint constraint-enableWorkloadIdentity.yaml
Created custom constraint [organizations/ORGANIZATION_ID/customConstraints/custom.enableWorkloadIdentity].
$ gcloud org-policies set-custom-constraint constraint-requireCOSNode.yaml
Created custom constraint [organizations/ORGANIZATION_ID/customConstraints/custom.useCOSNode].
$ gcloud org-policies list-custom-constraints --organization=$ORGANIZATION_ID
CUSTOM_CONSTRAINT ACTION_TYPE METHOD_TYPES RESOURCE_TYPES DISPLAY_NAME
custom.disableAlphaClusters DENY CREATE container.googleapis.com/Cluster Disable Alpha Clusters
custom.enablePrivateEndpoint ALLOW CREATE,UPDATE container.googleapis.com/Cluster Enable GKE Private Endpoint
custom.enableWorkloadIdentity ALLOW CREATE container.googleapis.com/Cluster Enable Workload Identity on new clusters
custom.requireCOSNode ALLOW CREATE container.googleapis.com/NodePool Require COS Nodes
2. カスタム制約の適用
作成したカスタム制約を Prod フォルダに適用します。先ほどのように Console からポチポチ変更しても良いのですが、せっかくなので gcloud で適用します。
以下のようにポリシー用のファイルを用意し、name
に適用対象のフォルダ ID と制約を指定し、enforce
を true
に設定します。
name: folders/PROD_FOLDER_ID/policies/custom.enablePrivateEndpoint
spec:
rules:
- enforce: true
gcloud org-policies set-policy
コマンドで先ほどのポリシーファイルを指定し、ポリシーを適用します。ちゃんとポリシーが設定されたかどうかは gcloud org-policies list
で確認可能です。
$ export FOLDER_ID=<PROD FOLDER ID>
$ gcloud org-policies set-policy policy-enablePrivateEndpoint.yaml
Created policy [folders/PROD_FOLDER_ID/policies/custom.enablePrivateEndpoint].
$ gcloud org-policies list --folder=$FOLDER_ID
CONSTRAINT LIST_POLICY BOOLEAN_POLICY ETAG
custom.disableAlphaClusters - SET CI/yn5wGEKiulp8C
custom.enablePrivateEndpoint - SET COf0oZwGEMiyte4B
残りの制約も同様に適用します。既存のポリシー (compute.requireShieldedVm
, compute.vmExternalIpAccess
) と今回新規に作成した制約の適用が完了すると以下のような状態になっているかと思います。
$ gcloud org-policies list --folder=$FOLDER_ID
CONSTRAINT LIST_POLICY BOOLEAN_POLICY ETAG
compute.requireShieldedVm - SET CKT7oZwGELicwPIB
compute.vmExternalIpAccess SET - CIb7oZwGEMiQstQC
custom.disableAlphaClusters - SET CI/yn5wGEKiulp8C
custom.enablePrivateEndpoint - SET COf0oZwGEMiyte4B
custom.enableWorkloadIdentity - SET CMn7oZwGEID0pO8B
custom.requireCOSNode - SET COT7oZwGEIidtMsC
3. 制約の挙動を確認する
それでは実際に設定した制約が想定通りに動いているか確認してみます。とはいえ今回は時間(文字数)の都合上、網羅的な確認ではなくピンポイントで挙動を確認していきます。
まずは失敗するであろうパターンとして プライベートクラスタだけどパブリックエンドポイント
かつ Workload Identity が設定されていない
クラスタをデプロイしてみます。
$ export PROJECT_ID=<PROD PROJECT ID>
$ gcloud config set project ${PROJECT_ID}
$ export CLUSTER_NAME=dame-cluster
$ gcloud container clusters create ${CLUSTER_NAME} \
--zone ${CLUSTER_LOCATION} \
--num-nodes=2 \
--enable-shielded-nodes \
--shielded-secure-boot \
--shielded-integrity-monitoring \
--enable-ip-alias \
--enable-private-nodes \
--master-ipv4-cidr "172.16.0.0/28" \
--release-channel=regular
ERROR: (gcloud.container.clusters.create) ResponseError: code=400, message=Operation denied by custom org policy: ["customConstraints/custom.enablePrivateEndpoint": "Only allow GKE Cluster resource create or update if Private Endpoint is enabled" "customConstraints/custom.enableWorkloadIdentity": "All new clusters must use Workload Identity."].
想定通り enablePrivateEndpoint
と enableWorkloadIdentity
に引っかかってエラーが出力されました。
続いて成功パターンで試してみます。先ほど怒られた内容を踏まえて --workload-pool
と --enable-private-endpoint
をちゃんと設定して試してみます。
$ export WORKLOAD_POOL=${PROJECT_ID}.svc.id.goog
$ export CLUSTER_NAME=ok-cluster
$ gcloud container clusters create ${CLUSTER_NAME} \
--zone ${CLUSTER_LOCATION} \
--num-nodes=2 \
--workload-pool=${WORKLOAD_POOL} \
--enable-shielded-nodes \
--shielded-secure-boot \
--shielded-integrity-monitoring \
--enable-ip-alias \
--enable-private-nodes \
--master-ipv4-cidr "172.16.0.0/28" \
--enable-private-endpoint \
--release-channel=regular
Creating cluster ok-cluster in asia-northeast1-b... Cluster is being health-checked (master is healthy)...done.
Created [https://container.googleapis.com/v1/projects/kuchima-adventcal2022-prod/zones/asia-northeast1-b/clusters/ok-cluster].
無事作成できました。続いて COS 以外のノード (Ubuntu) を追加したときの挙動も確認してみます。
$ gcloud container node-pools create ubuntu-pool \
--cluster ${CLUSTER_NAME} \
--zone ${CLUSTER_LOCATION} \
--num-nodes=2 \
--shielded-secure-boot \
--shielded-integrity-monitoring \
--image-type "UBUNTU_CONTAINERD"
ERROR: (gcloud.container.node-pools.create) ResponseError: code=400, message=Operation denied by custom org policy: ["customConstraints/custom.requireCOSNode": "All cluster nodes must be COS."].
こちらも想定通り requireCOSNode
によりノードの作成がブロックされました。これで本番環境ではある程度安全な GKE クラスタの作成が強制されるようになりました。
③ 開発環境では Spot VM のみ許可する
最後にコスト節約のために開発環境では Spot VM のみ許可するような制約を作ります。
1. カスタム制約の作成
まずマニフェストを作成します。Spot VM は NodePool
の resource.config.spot
で設定するので、これが true
になっているかを確認します。
name: organizations/ORGANIZATION_ID/customConstraints/custom.enableSpotVM
resourceTypes:
- container.googleapis.com/NodePool
methodTypes:
- CREATE
condition: resource.config.spot == true
actionType: ALLOW
displayName: Enable Spot VMs
description: All cluster nodes must be Spot VMs.
組織内にカスタム制約を作成します。
$ gcloud org-policies set-custom-constraint constraint-enableSpotVM.yaml
Created custom constraint [organizations/ORGANIZATION_ID/customConstraints/custom.enableSpotVM].
2. カスタム制約の適用
ポリシーのマニフェストを作成し、カスタム制約を dev
フォルダに適用します。
name: folders/DEV_FOLDER_ID/policies/custom.enableSpotVM
spec:
rules:
- enforce: true
$ gcloud org-policies set-policy policy-enableSpotVM.yaml
Created policy [folders/DEV_FOLDER_ID/policies/custom.enableSpotVM].
3. 制約の挙動を確認する
では実際に試してみます。② 本番環境ではセキュアな構成の GKE クラスタを強制する
でデプロイに成功したセキュアな構成のクラスタをそのまま開発環境にもデプロイしてみます。
$ export PROJECT_ID=<DEV PROJECT ID>
$ gcloud config set project ${PROJECT_ID}
$ export WORKLOAD_POOL=${PROJECT_ID}.svc.id.goog
$ export CLUSTER_NAME=secured-cluster
$ gcloud container clusters create ${CLUSTER_NAME} \
--zone ${CLUSTER_LOCATION} \
--num-nodes=2 \
--workload-pool=${WORKLOAD_POOL} \
--enable-shielded-nodes \
--shielded-secure-boot \
--shielded-integrity-monitoring \
--enable-ip-alias \
--enable-private-nodes \
--master-ipv4-cidr "172.16.0.0/28" \
--enable-private-endpoint \
--release-channel=regular
ERROR: (gcloud.container.clusters.create) ResponseError: code=400, message=Operation denied by custom org policy: ["customConstraints/custom.enableSpotVM": "All cluster nodes must be Spot VMs."].
そうすると想定通り enableSpotVM
によってクラスタの作成が阻止されました。
続いて --spot
を追加して、Spot VM としてクラスタを作成してみます。
$ export CLUSTER_NAME=spot-cluster
$ gcloud container clusters create ${CLUSTER_NAME} \
--zone ${CLUSTER_LOCATION} \
--num-nodes=2 \
--workload-pool=${WORKLOAD_POOL} \
--enable-shielded-nodes \
--shielded-secure-boot \
--shielded-integrity-monitoring \
--enable-ip-alias \
--enable-private-nodes \
--master-ipv4-cidr "172.16.0.0/28" \
--enable-private-endpoint \
--release-channel=regular \
--spot
Creating cluster spot-cluster in asia-northeast1-b... Cluster is being health-checked (master is healthy)...done.
Created [https://container.googleapis.com/v1/projects/kuchima-adventcal2022-dev/zones/asia-northeast1-b/clusters/spot-cluster].
クラスタの作成に成功しました。これで動作確認は終了です。
お片付け
このまま制約を残しておくと他の作業に影響が出る可能性もあるので、gcloud org-policies delete-custom-constraint
で作成した制約を削除しましょう。
$ gcloud org-policies delete-custom-constraint custom.CONSTRAINT_NAME --organization=ORGANIZATION_ID
まとめ
組織ポリシーのカスタム制約により、柔軟かつ強固な制約を組織全体や特定フォルダ配下のプロジェクトに適用しガバナンスを効かせることができるようになりました。
まだ Preview の機能なので対応しているサービスが少なかったり色々と足りていない部分はありますが、今後成熟してくると便利に使えそうで個人的にはかなり期待しています。気になった方はぜひ試してみてください。
明日 3 日目は Google Workspace に関する記事です。お楽しみに〜
参考資料
Discussion