このページは AI の支援を受けて英語から翻訳されています。内容が異なる場合は英語版が正となります。
StackQL を使用した Google の GitOps IAM バインディングフレームワーク
このガイドでは、StackQL を使用して GCP の IAM バインディング(権限付与)を管理する GitOps フレームワークを紹介します。StackQL は、SQL 構文でクラウドインフラストラクチャとリソースのクエリとデプロイを行える強力な開発ツールです。
動作確認済み 組み込み SQL バックエンド macos linux powershell
Google IAM の背景
Google Cloud Identity and Access Management(IAM)は、Google Cloud リソースへのアクセスを管理するためのフレームワークです。管理者は、誰がリソースに対して特定の操作を実行できるかを定義できます。IAM は ID(ユーザーやサービスアカウントなど)、ロール、権限を中心に機能し、ID に割り当てられたロールがそのアクセス権を決定します。このガイドでは、ロールと ID を結び付ける IAM バインディングを StackQL で管理する方法を詳しく説明します。StackQL は、SQL 構文でクラウドリソースの管理を簡素化するツールです。
IAM バインディングは、次のいずれのレベルでも作成できます:
- 組織レベル
- フォルダーレベル
- プロジェクトレベル
- リソースレベル(バケット、BigQuery データセットなど)
- 請求先アカウントレベル
組織レベルまたはフォルダーレベルで適用された IAM バインディングは、すべての子オブジェクトに継承されます。
宣言的なバインディング定義と GitOps
このガイドでは、IAM バインディングをソースコードリポジトリで一元管理し、リポジトリを状態と履歴の信頼できる唯一の情報源にする方法(GitOps と呼ばれる考え方)と、StackQL を使用して CI/CD ワークフローで変更を適用する方法を説明します。
この例では、ソース管理に保存された json マニフェストから、複数のメンバーまたはプリンシパルを複数のロールにマッピングする方法を示します。
GCP コンソール、
terraform、gcloudユーティリティでは、1 回の操作で 1 つのメンバーを 1 つ以上のロールにマッピングすることしかできません
仕組み
このガイドで説明するフレームワークは、GCP の階層内のすべてのノードに IAM バインディングをデプロイするとともに、カスタムロール(厳選した権限を持つもの)を作成します。これは、たとえば roles/bigquery.dataEditor のような事前定義ロールとは異なります。
プロジェクトのディレクトリ構造
このバインディングデプロイフレームワークでは、次のディレクトリツリーを使用します:
├── iql
│ ├── iam.iql
│ ├── ...
├── data
│ ├── iam.jsonnet
│ ├── iam
│ │ ├── bindings.json
│ │ ├── custom_roles.json
iql/iam.iql ファイル
これは stackql によって実行されるメインファイルです。data/iam.jsonnet を使用して、data/iam/bindings.json のデータから生成されたトランスパイル済みのポリシーバインディングをレンダリングし、適用します。
このファイルを変更する必要はありません
展開して iql/iam.iql ファイルを表示
/*
ROOT LEVEL (ORG AND FOLDER) IAM FOR SERVICE ACCOUNTS AND GROUPS
*/
{{ $root := . }}
/*** create custom roles ***/ {{ range .custom_roles }}
INSERT INTO google.iam.`organizations.roles`(
parent,
data__role,
data__roleId )
SELECT
'{{ $root.organization_id }}',
'{"title": "{{ .title }}", "description": "{{ .description }}", "stage": "{{ .stage }}", "includedPermissions": {{ .includedPermissions }}}',
'{{ .id }}'
;{{ end }}
/*** create org role bindings */
EXEC google.cloudresourcemanager.organizations.setIamPolicy
@resource = '{{ $root.organization_id }}'
@@json = '{
"policy": {
"bindings": {{ $root.bindings.org }}
}
}';
/*** create billing role bindings */
EXEC google.cloudbilling.billingAccounts.setIamPolicy
@resource = '{{ $root.billing_account_id }}'
@@json = '{
"policy": {
"bindings": {{ $root.bindings.billingacct }}
}
}';
/*** create nonprod folder role bindings */
EXEC google.cloudresourcemanager.folders.setIamPolicy
@resource = '{{ $root.nonprod_folder_id }}'
@@json = '{
"policy": {
"bindings": {{ $root.bindings.folders.nonprod }}
}
}';
/*** create prod folder role bindings */
EXEC google.cloudresourcemanager.folders.setIamPolicy
@resource = '{{ $root.prod_folder_id }}'
@@json = '{
"policy": {
"bindings": {{ $root.bindings.folders.prod }}
}
}';
/*** create datalabs folder role bindings */
EXEC google.cloudresourcemanager.folders.setIamPolicy
@resource = '{{ $root.datalabs_folder_id }}'
@@json = '{
"policy": {
"bindings": {{ $root.bindings.folders.datalabs }}
}
}';
/*** create stackql-audit project level role bindings */
EXEC google.cloudresourcemanager.folders.setIamPolicy
@resource = 'projects/stackql-audit'
@@json = '{
"policy": {
"bindings": {{ $root.bindings.projects.stackql_audit }}
}
}';
/*** create role bindings for buckets in stackql-terraform */ {{ range $root.bindings.buckets }}
-- creating policy bindings for {{ .name }}
EXEC google.storage.buckets.setIamPolicy
@bucket = '{{ .name }}'
@@json = '{
"bindings": {{ .data }}
}';
{{ end }}
/*** create role bindings for topics in stackql-audit */ {{ range $root.bindings.topics }}
-- creating policy bindings for {{ .name }}
EXEC google.pubsub.`projects.topics`.setIamPolicy
@resource = '{{ .resource }}'
@@json = '{
"bindings": {{ .data }}
}';
{{ end }}
data/iam.jsonnet
このファイルは iql/iam.iql によって前処理され、data/iam/bindings.json からデータを取得します。
このファイルを変更する必要はありません
展開して data/iam.jsonnet ファイルを表示
// update the following files only:
// data/iam/custom_roles.json
// data/iam/bindings.json
// variables
local custom_roles_data = import './data/iam/custom_roles.json';
local bindings_data = import './data/iam/bindings.json';
local organization_id = 'organizations/123466304837';
local billing_account_id = 'billingAccounts/123456-9DFD97-EE2B33';
local nonprod_folder_id = 'folders/1234016945998';
local prod_folder_id = 'folders/123431606453';
local datalabs_folder_id = 'folders/123464988355';
local environments = [{name: 'prod'}, {name: 'nonprod'}, {name: 'datalabs'}];
// DO NOT MODIFY BEYOND THIS POINT
local generate_binding(x) =
local members = [x for x in x.members];
std.map(function(r) {"role": r, "members": members} , [x for x in x.roles]);
local generate_conditional_binding(x) =
local members = [x for x in x.members];
local condition = x.condition;
std.map(function(r) {"role": r, "members": members, "condition": condition} , [x for x in x.roles]);
{
organization_id: organization_id,
billing_account_id: billing_account_id,
nonprod_folder_id: nonprod_folder_id,
prod_folder_id: prod_folder_id,
datalabs_folder_id: datalabs_folder_id,
custom_roles: custom_roles_data,
environments: environments,
bindings: {
org: std.flattenArrays(std.map(generate_binding, bindings_data.org)),
billingacct: std.flattenArrays(std.map(generate_binding, bindings_data.billingacct)),
folders: {
nonprod: std.flattenArrays(std.map(generate_binding, bindings_data.folders.nonprod)),
prod: std.flattenArrays(std.map(generate_binding, bindings_data.folders.prod)),
datalabs: std.flattenArrays(std.map(generate_binding, bindings_data.folders.datalabs)),
},
projects: {
stackql_audit: std.flattenArrays(std.map(generate_conditional_binding, bindings_data.projects.stackql_audit)),
},
buckets: [
{
name: "stackql-tf-prod",
data: std.flattenArrays(std.map(generate_binding, bindings_data.buckets.stackql_tf_prod)),
},
{
name: "stackql-tf-modules-prod",
data: std.flattenArrays(std.map(generate_binding, bindings_data.buckets.stackql_tf_modules_prod)),
},
{
name: "stackql-tf-nonprod",
data: std.flattenArrays(std.map(generate_binding, bindings_data.buckets.stackql_tf_nonprod)),
},
{
name: "stackql-tf-modules-nonprod",
data: std.flattenArrays(std.map(generate_binding, bindings_data.buckets.stackql_tf_modules_nonprod)),
},
{
name: "stackql-tf-datalabs",
data: std.flattenArrays(std.map(generate_binding, bindings_data.buckets.stackql_tf_datalabs)),
},
{
name: "stackql-tf-modules-datalabs",
data: std.flattenArrays(std.map(generate_binding, bindings_data.buckets.stackql_tf_modules_datalabs)),
},
],
topics: [
{
name: "stackql-np-log-topic",
resource: "projects/stackql-audit/topics/stackql-np-log-topic",
data: std.flattenArrays(std.map(generate_binding, bindings_data.topics.stackql_np_log_topic)),
},
{
name: "stackql-prod-log-topic",
resource: "projects/stackql-audit/topics/stackql-prod-log-topic",
data: std.flattenArrays(std.map(generate_binding, bindings_data.topics.stackql_prod_log_topic)),
},
],
},
}
data/iam/bindings.json
ポリシーバインディングを変更するには、このファイルを 変更する必要があります
これはポリシーとバインディングのデータのマスターソースです。次に示すように、このデータ構造は、特定のレベル(親)で 1 つ以上のプリンシパル(グループ、サービスアカウントなど)を 1 つ以上のロール(事前定義またはカスタム)にマッピングします:
"<< level >>": [
{
"members": [
"<< principal >>",
...
],
"roles": [
"<< role >>",
...
]
},
たとえば:
"folders": {
"nonprod": [
{
"members": [
"serviceAccount:terraform-nonprod@stackql-terraform.iam.gserviceaccount.com"
],
"roles": [
"roles/compute.networkAdmin",
"roles/resourcemanager.folderViewer",
"roles/resourcemanager.projectCreator",
"roles/storage.admin",
"roles/artifactregistry.admin",
"roles/container.serviceAgent",
"roles/iam.securityAdmin",
"roles/bigquery.admin",
"roles/serviceusage.serviceUsageAdmin",
"roles/cloudsql.admin",
"roles/iam.serviceAccountAdmin",
"roles/pubsub.admin",
"roles/cloudfunctions.developer"
]
}
],
デプロイ
変更の頻度が低く、またデプロイに必要なレベルの権限をサービスアカウントに付与したくないため、このプロセスは意図的に CI/CD パイプラインでは実行しません。このプロセスは、gcp-org-administrators Google グループのメンバーである特権管理者が実行します。
ユーザーは、サービスアカウントまたは対話型の認証のいずれかを使用して、GCP に認証されている必要があります。StackQL から Google への認証の詳細については、GCP の認証 を参照してください。
ドライランを実行するには、次のコマンドを実行します:
stackql exec -i ./iql/iam.iql \
--iqldata ./data/iam.jsonnet \
--outfile iam-TEMPLATED.iql \
--dryrun --output text --hideheaders
このファイルに出力されたデータは、stackql shell で個々の文として実行するか、次のコマンドでバッチとして実行できます:
stackql exec -i iam-TEMPLATED.iql \
または、このスクリプトで生成されたポリシー(JSON ペイロード)を取り出し、gcloud iam コマンドで実行することもできます。