Terraform による Snowflake ロール作成
~ Functional role + Access role モデル ~
こんにちは
DATUM STUDIOの梶谷です。
Snowflake
では、Functional role と Access role を組み合わせたロール構成
が推奨されています。推奨されてはいるものの、組織やデータの規模が大きくなっていくと、クエリでロールをメンテナンスしていくのは、ちょっと大変ではないでしょうか。
そんな時こそ IaC ツールの出番!ということで、今回の記事では Terraform で Functional role と Access role を実装する例をご紹介したいと思います。
目次
Snowflake でのアクセス制御
Snowflake でのアクセス制御について、基本的な考え方はSnowflake ドキュメント をご参照ください。
Functional role + Access role モデル
現実世界で人に与えられる役割や組織でのポジションに基づくロール(Functional Role)と、スキーマやテーブル等のオブジェクトに対する読み書き・使用可否に基づくロール(Access Role)をそれぞれ作成し、役割ごとに必要な権限を紐づけていくモデルです。 Functional Role が上位ロールに、Access Role が下位のロールになります。ユーザーには Functional Role 経由で各オブジェクトへの権限が割り当てられます。
(https://docs.snowflake.com/ja/user-guide/security-access-control-considerations.html
より引用)
詳しくは、Snowflake ドキュメント
や、こちらの記事にて解説されておりますので、ご参照ください。
Terraform による実装
それでは、Terraform で実装していきたいと思います。Let’s Terraforming!
環境について
本記事の実装については、次の環境で動作を確認しております。
- ・Terraform v1.2.6
- ・Terraform Snowflake Provider :snowflake-labs/snowflake v0.40.0
ディレクトリ構成
次の構成になっています。
terraform ├─environments # 環境別作業ディレクトリ │ ├─prd # 本番環境用(テーブル等の定義がありますが、今回は割愛) │ ├─dev # 開発環境用(上と同じく今回は割愛) │ └─common # アカウントレベルのオブジェクトなど、共通リソース用 │ ├─yaml # Functional role と Access role の設定ファイル │ │ ├─access_roles_to_functional_roles.yml │ │ ├─access_roles.yml │ │ └─functional_roles.yml │ ├─backend.tf │ ├─locals.tf │ ├─main.tf │ ├─provider.tf │ └─variable.tf └─modules # モジュール置き場 └─snowflake └─functional_and_access_roles ├─grant_access_roles.tf ├─grant_access_roles_to_functional_roles.tf ├─grant_functional_roles.tf ├─output.tf ├─provider.tf ├─roles.tf └─variable.tf
作業ディレクトリ
まずは、作業ディレクトリにあるソースコードから見ていきましょう。
ロールに関する設定は全て yaml ファイルに記載しており、local でファイルから読み込んでモジュールに渡していきます。
access_roles.yml には、Access role の名前とコメント、付与する権限を記載します。
# Access Role 一覧 # 書式: access_roles: - name: <Access Role 1> comment: <comment for Access Role 1> - name: <Access Role 2> comment: <comment for Access Role 2> : # オブジェクトに対する権限を付与する Access Role 一覧 # 書式: grant_on_object_to_access_role: - name: <object1>_<parameter1_1>_<parameter1_2>_... # 一意な名前にする type: <object1> roles: - <Access Role 1_1> - <Access Role 1_2> : - name: <object2>_<parameter2_1>_<parameter2_2>_... type: <object2> roles: - <Access Role 2_1> - <Access Role 2_2> : :
functional_roles.yml には、 Functional role の名前とコメント、ロールを付与するユーザーを記載します。
# Functional Role一覧 # 書式: functional_roles: - name: <User1> comment: <Comment for User1> - name: <User2> comment: <Comment for User2> : # Functional role を付与するユーザー一覧 # 書式: grant_functional_roles_to_user: - role_name: <Functional Role 1> users: - <User 1_1> - <User 2_1> : - role_name: <Functional RolE 2> users: - <User 1_2> - <User 2_2> : :
access_roles_to_functional_roles.yml には、Access role をどの Functional role に付与するかを記載します。
# Access role を付与する Functional role 一覧 # 書式: grant_access_roles_to_functional_roles: - access_role: <Access Role 1> functional_roles: - <Functional Role 1_1> - <Functional Role 1_2> : - access_role: <Access Role 2> functional_roles: - <Functional Role 2_1> - <Functional Role 2_2> : :
locals.tf で yaml ファイルに記載したロール情報を読み込みます。
locals { # 設定ファイルをロード access_roles_yml = yamldecode( file("${path.root}/yaml/access_roles.yml") ) functional_roles_yml = yamldecode( file("${path.root}/yaml/functional_roles.yml") ) access_roles_to_functional_roles_yml = yamldecode( file("${path.root}/yaml/access_roles_to_functional_roles.yml") ) # Access role のリスト access_roles = flatten(local.access_roles_yml["access_roles"]) # grant ... on objects to Access role のリスト grant_on_object_to_access_role = flatten(local.access_roles_yml["grant_on_object_to_access_role"]) # Functional roleのリスト functional_roles = local.functional_roles_yml["functional_roles"] # grant Functional role to ユーザーのリスト grant_functional_roles_to_user = flatten(local.functional_roles_yml["grant_functional_roles_to_user"]) # grant Access role to Functional role のリスト grant_access_role_to_functional_role = flatten(local.access_roles_to_functional_roles_yml["grant_access_roles_to_functional_roles"]) }
main.tf はモジュールを呼び出して locals を渡すだけのものです。
なお 、snowflake.snowflake_securityadmin は SECURITYADMIN ロールを設定した snowflake プロバイダです。
# Functional roleとAccess roleを作成 module "functional_and_access_roles" { source = "../../modules/snowflake/functional_and_access_roles_refactor" providers = { snowflake = snowflake.snowflake_securityadmin } access_roles = local.access_roles grant_on_object_to_access_role = local.grant_on_object_to_access_role functional_roles = local.functional_roles grant_access_roles_to_functional_roles = local.grant_access_role_to_functional_role grant_functional_roles_to_user = local.grant_functional_roles_to_user }
モジュール
variables.tf の各 variable に渡されたロール情報を展開して、ロールを作ったり権限を付与したりしていきます。
variable "functional_roles" { type = list(any) description = "Functional roleリスト。[ {name: <role_name>, comment: <comment>},... ]" } variable "access_roles" { type = list(any) description = "Access roleリスト。[ {name: <role_name>, comment: <comment>},... ]" } variable "grant_on_object_to_access_role" { type = list(any) description = "grant on ○○ を付与する Access role のリスト。[ {name: <name>, roles: [<role_name>], type: SCHEMA/FUTURE_TABLE/WAREHOUSE/etc., parameter: <parameter>},... ]" } variable "grant_access_roles_to_functional_roles" { type = list(any) description = "Access role を付与する Functional role のリスト。[ {functional_roles: [<role_a>, <role_b>, ...], access_role: <role_name>},... ]" } variable "grant_functional_roles_to_user" { type = list(any) description = "Functional role を付与するユーザーのリスト。[ {users: [<user_1>, <user_2, ...], role_name: <role_name>},... ]" }
roles.tf で Functional role と Access role を作成し、SYSADMIN に紐づけます。
# Roleを作成 resource "snowflake_role" "roles" { for_each = { for role in concat(var.functional_roles, var.access_roles) : role.name => role.comment } name = each.key comment = each.value } # SYSADMIN にぶら下げる resource "snowflake_role_grants" "role_grants" { for_each = toset([for role in snowflake_role.roles : role.name]) role_name = each.key roles = ["SYSADMIN"] }
grant_access_roles.tf で、Access role への権限付与を行います。
access_roles.yml で type に指定したオブジェクトに従って、呼び出すリソースを分けています。
# on Database resource "snowflake_database_grant" "on_database_grant" { for_each = { for grant in var.grant_on_object_to_access_role : grant.name => { database_name = grant.parameter.database_name privilege = grant.parameter.privilege roles = grant.roles } if grant.type == "DATABASE" } database_name = each.value.database_name privilege = each.value.privilege roles = each.value.roles # 先に存在しなければならないが、参照していないので依存関係を付ける depends_on = [snowflake_role.roles] } # on Schema resource "snowflake_schema_grant" "on_schema_grant" { for_each = { for grant in var.grant_on_object_to_access_role : grant.name => { schema_name = grant.parameter.schema_name database_name = grant.parameter.database_name privilege = grant.parameter.privilege roles = grant.roles } if grant.type == "SCHEMA" } database_name = each.value.database_name schema_name = each.value.schema_name privilege = each.value.privilege roles = each.value.roles # 先に存在しなければならないが、参照していないので依存関係を付ける depends_on = [snowflake_role.roles] } # on (future) table resource "snowflake_table_grant" "on_table" { for_each = { for grant in var.grant_on_object_to_access_role : grant.name => { database_name = grant.parameter.database_name schema_name = grant.parameter.schema_name table_name = lookup(grant.parameter, "table_name", null) privilege = grant.parameter.privilege on_future = lookup(grant.parameter, "on_future", null) roles = grant.roles } if grant.type == "TABLE" } database_name = each.value.database_name schema_name = each.value.schema_name table_name = each.value.table_name privilege = each.value.privilege on_future = each.value.on_future roles = each.value.roles # 先に存在しなければならないが、参照していないので依存関係を付ける depends_on = [snowflake_role.roles] } # on warehouse resource "snowflake_warehouse_grant" "on_warehouse_grant" { for_each = { for grant in var.grant_on_object_to_access_role : grant.name => { warehouse_name = grant.parameter.warehouse_name privilege = grant.parameter.privilege roles = grant.roles } if grant.type == "WAREHOUSE" } warehouse_name = each.value.warehouse_name privilege = each.value.privilege roles = each.value.roles # 先に存在しなければならないが、参照していないので依存関係を付ける depends_on = [snowflake_role.roles] }
grant_access_roles_to_functional_roles.tf で Access role を Functional role に付与します。
# Access role を Functional role に grant する resource "snowflake_role_grants" "access_role_to_functional_role_grants" { for_each = { for grant in var.grant_access_roles_to_functional_roles : grant.access_role => { access_role = grant.access_role functional_roles = grant.functional_roles } } role_name = snowflake_role.roles[each.value.access_role].name roles = each.value.functional_roles }
grant_functional_roles.tf で、Functional role をユーザーに付与します。
# Access role を Functional role に grant する resource "snowflake_role_grants" "access_role_to_functional_role_grants" { for_each = { for grant in var.grant_access_roles_to_functional_roles : grant.access_role => { access_role = grant.access_role functional_roles = grant.functional_roles } } role_name = snowflake_role.roles[each.value.access_role].name roles = each.value.functional_roles }
これで、Functional role と Access role を作成する準備が整いました。
Terraform で作ってみよう
今回は、次の状況を仮定して、ロールを作ってみます。
- ・Snowflake 環境を利用するユーザーは「HOGE社」または「PIYO社」のいずれかに所属しています。
- ・HOGE社 所属のユーザーは開発担当者または分析担当者です。PIYO社 所属のユーザーは分析担当者のみです。
- ・分析担当者はテーブルをSELECTできればOKで、開発担当者は必要に応じてデータを更新・修正するために書き込み権限が必要です。
ビューやステージも作りたくなりそうですが、簡単のために省略させてください。 - ・SCHEMA_A は、PIYO社 からは閲覧不可です。
以上の前提で、ロールを Functional role と Access role で設計してみましょう。 こんな感じにします。
Functional role
Access role
それではさっそく作ってみましょう!上のロールを設定ファイルに記載していきます。
access_roles.yml
access_roles: - name: PRD_SCHEMA_A_READWRITE comment: 本番環境データベースで SCHEMA_A スキーマに対して読み書き可能 - name: PRD_SCHEMA_A_READONLY comment: 本番環境データベースで SCHEMA_A スキーマに対して読み取り可能 - name: PRD_SCHEMA_B_READWRITE comment: 本番環境データベースで SCHEMA_B スキーマに対して読み書き可能 - name: PRD_SCHEMA_B_READONLY comment: 本番環境データベースで SCHEMA_B スキーマに対して読み取り可能 - name: PRD_WAREHOUSE_USAGE comment: 本番環境用ウェアハウスを使用可能 grant_on_object_to_access_role: - name: DATABASE_PRD_DB_USAGE type: DATABASE parameter: database_name: PRD_DB privilege: USAGE roles: - PRD_SCHEMA_A_READWRITE - PRD_SCHEMA_A_READONLY - PRD_SCHEMA_B_READWRITE - PRD_SCHEMA_B_READONLY - name: SCHEMA_PRD_DB_SCHEMA_A_USAGE type: SCHEMA parameter: database_name: PRD_DB schema_name: SCHEMA_A privilege: USAGE roles: - PRD_SCHEMA_A_READWRITE - PRD_SCHEMA_A_READONLY - name: SCHEMA_PRD_DB_SCHEMA_B_USAGE type: SCHEMA parameter: database_name: PRD_DB schema_name: SCHEMA_B privilege: USAGE roles: - PRD_SCHEMA_B_READWRITE - PRD_SCHEMA_B_READONLY - name: TABLE_PRD_DB_SCHEMA_A_SELECT_ON_FUTURE type: TABLE parameter: database_name: PRD_DB schema_name: SCHEMA_A privilege: SELECT on_future: true roles: - PRD_SCHEMA_A_READWRITE - PRD_SCHEMA_A_READONLY - name: TABLE_PRD_DB_SCHEMA_A_INSERT_ON_FUTURE type: TABLE parameter: database_name: PRD_DB schema_name: SCHEMA_A privilege: INSERT on_future: true roles: - PRD_SCHEMA_A_READWRITE - name: TABLE_PRD_DB_SCHEMA_A_UPDATE_ON_FUTURE type: TABLE parameter: database_name: PRD_DB schema_name: SCHEMA_A privilege: UPDATE on_future: true roles: - PRD_SCHEMA_A_READWRITE - name: TABLE_PRD_DB_SCHEMA_A_DELETE_ON_FUTURE type: TABLE parameter: database_name: PRD_DB schema_name: SCHEMA_A privilege: DELETE on_future: true roles: - PRD_SCHEMA_A_READWRITE
access_roles_to_functional_roles.yml
grant_access_roles_to_functional_roles: - access_role: PRD_SCHEMA_A_READWRITE functional_roles: - DEVELOPER_HOGE - access_role: PRD_SCHEMA_A_READONLY functional_roles: - ANALYST_HOGE - access_role: PRD_SCHEMA_B_READWRITE functional_roles: - DEVELOPER_HOGE - access_role: PRD_SCHEMA_B_READONLY functional_roles: - ANALYST_HOGE - ANALYST_PIYO - access_role: PRD_WAREHOUSE_USAGE functional_roles: - DEVELOPER_HOGE - ANALYST_HOGE - ANALYST_PIYO
あとは terraform apply すれば、あっという間に Functional role と Access role が作成されます。
$ terraform apply Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: :(中略) Plan: 36 to add, 0 to change, 0 to destroy. :(中略) Apply complete! Resources: 36 added, 0 changed, 0 destroyed.
terraform コマンドの裏側では、ひたすら grant plivilage と create role が実行されています。これを手打ちするのは、ちょっと大変じゃないですか?
作成されたロールを Snowsight で確認してみましょう。
DEVELOPER_HOGE
ANALYST_HOGE
ANAPYST_PIYO
先に記載した図と同じ階層関係になっていますね。
では、アクセス制御ができているか、確認しましょう。SCHEMA_A と SCHEMA_B にテーブルを作成します。
create table "PRD_DB"."SCHEMA_A"."A_TABLE_1" ( id int, name string ); create table "PRD_DB"."SCHEMA_B"."B_TABLE_1" ( id int, name string );
DEVELOPER_HOGE では、SCHEMA_A も SCHEMA_B も書き込み可能です。
ANALYST_HOGE では、SCHEMA_A も SCHEMA_B も閲覧可能です。
書き込みはできません。
ANALYST_PIYO では、 SCHEMA_A を閲覧することはできません。SCHEMA_B は閲覧可能です。
修正もしてみよう
これで HOGE社 と PIYO社 のためのロールは作成できました。しかし、現実に Snowflake 環境の運用をしていると、ロールの追加や修正が必要な場面が出てくるのではないでしょうか。
例えば「FUGA社」から分析者が新たに参加し、SCHEMA_A と SCHEMA_B の両方を閲覧する必要があるとします。この場合、FUGA社 分析者用の Functional role を追加し、既存の Access role を
FUGA社 用ロールに紐づければOKです。
FUGA社 分析者用ロールは ANALYST_FUGA としましょう。
Functional role
設定ファイルを次のように修正します。
functional_roles.yml
functional_roles: - name: DEVELOPER_HOGE comment: HOGE社に所属する開発者が使用するロール。全ての環境でテーブル作成・データ処理フロー作成などを行う - name: ANALYST_HOGE comment: HOGE社に所属する分析者が使用するロール。本番環境で集計などを行う - name: ANALYST_PIYO comment: PIYO社に所属する分析者が使用するロール。本番環境で集計などを行う + - name: ANALYST_FUGA + comment: FUGA社に所属する分析者が使用するロール。本番環境で集計などを行う grant_functional_roles_to_user: - role_name: DEVELOPER_HOGE users: - HOGE_USER_1 - role_name: ANALYST_HOGE users: - HOGE_USER_2 - role_name: ANALYST_PIYO users: - PIYO_USER_1 + - role_name: ANALYST_FUGA + users: + - FUGA_USER_1
access_roles_to_functional_roles.yml
grant_access_roles_to_functional_roles: - access_role: PRD_SCHEMA_A_READWRITE functional_roles: - DEVELOPER_HOGE - access_role: PRD_SCHEMA_A_READONLY functional_roles: - ANALYST_HOGE + - ANALYST_FUGA - access_role: PRD_SCHEMA_B_READWRITE functional_roles: - DEVELOPER_HOGE - access_role: PRD_SCHEMA_B_READONLY functional_roles: - ANALYST_HOGE - ANALYST_PIYO + - ANALYST_FUGA - access_role: PRD_WAREHOUSE_USAGE functional_roles: - DEVELOPER_HOGE - ANALYST_HOGE - ANALYST_PIYO + - ANALYST_FUGA
あとは terraform apply すれば、ANALYST_FUGA の追加は完了です。
SCHEMA_A と SCHEMA_B 、両方とも閲覧できていますね。
おわりに
Functional role + Access role モデルの Terraform 実装例についてご紹介しました。
ロールの追加・修正を行う際、設定ファイルの修正で済むため、クエリでロールを追加・修正するよりも作業量が少なくなり、そこが良いなと感じています。最初に作成するコード量が少々多くなってしまうのが弱点ですが、後々、メンテナンス作業が楽になる事を考えれば、十分にメリットがあるのではないかと思います。
Terraform で Snowflake ともっと仲良くなりましょう!
DATUM STUDIOは、クライアントの事業成長と経営課題解決を最適な形でサポートする、データ・ビジネスパートナーです。
データ分析の分野でお客様に最適なソリューションをご提供します。まずはご相談ください。
Contact
Explore Jobs
関連記事