背景と目的
データエンジニアを生業としているのですが、業務でSnowflakeを利用した基盤構築案件が増えてきているように感じます。
そこで今回はSnowflake×Terraformをテーマに、個人開発した内容について備忘録的に整理したいと思います。
Snowflakeについて独り言
DWHサービスに限らず、Snowflakeというプラットフォーム上でかなり多くのことが実現できるようになってきており、そしてそのサービス拡充のスピードに驚かされます。
私的に今後、Snowflakeのサービスを使って取り組んでみたいことをまとめた記事があります。ぜひ併せて読んでみてください。2024下半期 これから取り組んでいきたいこと
何をつくるか
公式Docでも言及されている、Functional Role + Access Roleのロール設計を実現するTerraformの構成を本記事でまとめてみます。
本書の構成はBeforeとAfterとし、BeforeではTerraformでのみロールを実装してみます。Afterでは同じ構成をTerraformとYamlを利用し実装します。
- 変数の設定や作成するリソースの定義等をYamlで管理することで、コードの読みやすさや開発の柔軟性の良さを感じたい
Functional Role + Access Roleのロール設計
図は下記のリンク先からの引用です。Functional Role + Access Roleというレイヤを持たせることで、ユーザに割り当てるロールを微細に制御することが可能になります。
ただし、ロールの数がかなり増えるためその管理の仕方が、難しさの一つだと感じています。
A Functional Approach For Snowflake’s Role-Based Access Controls
動作環境
$ cat /etc/os-release
NAME="Ubuntu"
VERSION="20.04.6 LTS (Focal Fossa)"
$ cat provider.tf
terraform {
required_version = "~> 1.6.0"
required_providers {
snowflake = {
source = "Snowflake-Labs/snowflake"
version = "0.64.0"
}
}
}
ディレクトリ構成
beforeとafterのディレクトリ構成の違いは、common(Roleの組み合わせを定義するyamlを格納)の有無と、yamlの中身を変数として受け取るlocals.tfの有無だけです。
$ tree
.
├── after
│ ├── common
│ │ └── yaml
│ │ ├── access_roles.yml
│ │ ├── access_roles_to_functional_roles.yml
│ │ └── functional_roles.yml
│ ├── locals.tf
│ ├── main.tf
│ ├── provider.tf
│ └── variables.tf
│ └── module
│ ├── grant_access_role
│ │ ├── main.tf
│ │ ├── provider.tf
│ │ └── variables.tf
│ ├── grant_dunctional_role_to_user
│ │ ├── main.tf
│ │ ├── provider.tf
│ │ └── variables.tf
│ ├── grant_functional_role
│ │ ├── main.tf
│ │ ├── provider.tf
│ │ └── variables.tf
│ ├── roles
│ │ ├── main.tf
│ │ ├── provider.tf
│ │ └── variables.tf
│ └── users
│ ├── main.tf
│ ├── provider.tf
│ └── variables.tf
├── before省略
before
苦労・大変だった点
ソースにコメントアウトしてある内容と同じですが、以下の点で苦労しました。
- Access Roleに対するgrantについて
- Access Role毎にprivilegeが異なる場合、オブジェクトに対してgrantするresourceを何度も定義する必要がある点。冗長な記述になってしまう。
# Read付与:AccessRoleの_R, _RW両方
# roleごとに、どのDB,schema, tableに対するPrivilageを付与するか、を外部ファイルで管理できれば楽そう
resource "snowflake_table_grant" "on_table_grant_read" {
provider = snowflake.sysadmin
for_each = {for k,v in local.filtered_map_access_role: k=>v}
database_name = "SNOWFLAKE_FOR_TF"
schema_name = "SCHEMA_A"
privilege = "SELECT"
table_name = "T_SNOWFLAKE_FOR_TF"
roles = [each.key]
}
# ReadWrite付与:AccessRoleの_RWのみ
resource "snowflake_table_grant" "on_table_grant_read_write" {
provider = snowflake.sysadmin
for_each = {for k in local.read_write_keys: k=> local.filtered_map_access_role[k]}
database_name = "SNOWFLAKE_FOR_TF"
schema_name = "SCHEMA_A"
privilege = "INSERT"
table_name = "T_SNOWFLAKE_FOR_TF"
roles = [each.key]
}
- Access RoleとFunctional Roleの紐づけ
- 複数あるAccess RoleをどのFunctional Roleにぶら下げるのか、という紐づけの管理が複雑になる点。ここが一番苦労した。。。
locals {
filtered_map_access_role = {for key,val in var.roles_name_descript_dict : key=>val if startswith(key, "access_role_")}
filtered_map_functional_role = {for key,val in var.roles_name_descript_dict : key=>val if startswith(key, "func_role_")}
read_write_keys = [for k in keys(var.roles_name_descript_dict) : k if can(regex("_RW", k))][0]
read_keys = [for k in keys(var.roles_name_descript_dict) : k if can(regex("_R", k))][0]
# Access role を Functional role に grant する
# develop: RW, analyst: R, consul: R
combine_functional_and_access_role = {
for key in keys(var.roles_name_descript_dict) :
key => (
key == "func_role_div_developer" ? local.read_write_keys:
key == "func_role_div_analyst" ? local.read_keys:
key == "func_role_div_consultant" ? local.read_keys:
null
)
if can(regex("func_role_div_", key))
}
}
after
beforeとの比較
- Access Roleに対するgrantについて
- beforeでは、ロールごとにテーブルに対するprivilegeを定義する必要があり、コードが冗長だった"grant_access_role"は"snowflake_table_grant"リソース1つで記述できるようになりました。
# table
resource "snowflake_table_grant" "on_table" {
provider = snowflake.sysadmin
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 = grant.parameter.table_name
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
}
- Access RoleとFunctional Roleの紐づけ
- 同様に、Functional RoleとAccess Roleの組み合わせを実装するコード部分は下記のようにシンプルに記述できるようになりました。Role同士の組み合わせをYAMLで定義しておくことで、main.tfファイルの中で変数として受け取りForLoopするだけのコードになりました。
# Access role を Functional role に grant する
resource "snowflake_role_grants" "access_role_to_functional_role_grants" {
provider = snowflake.securityadmin
for_each = {
for grant in var.grant_access_role_to_functional_role : grant.access_role => {
access_role = grant.access_role
functional_roles = grant.functional_roles
}
}
role_name = each.value.access_role
roles = each.value.functional_roles
}
作り終えて
冗長なコードの削減、それによって読みやすさの向上、メンテナス性・再利用性がかなり向上する結果となったと思います。
スペシャルサンクス
参考にさせていただいたリンク集です。ありがとうございました!