3
2

SnowflakeのFunctional Role + Access Roleのロール設計をTerraformで実装してみる

Last updated at Posted at 2024-08-08

背景と目的

データエンジニアを生業としているのですが、業務で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
image.png

動作環境

$ 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を何度も定義する必要がある点。冗長な記述になってしまう。
grant_access_role
# 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にぶら下げるのか、という紐づけの管理が複雑になる点。ここが一番苦労した。。。
grant_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つで記述できるようになりました。
grant_access_role
# 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するだけのコードになりました。
grant_functional_role
# 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
}

作り終えて

冗長なコードの削減、それによって読みやすさの向上、メンテナス性・再利用性がかなり向上する結果となったと思います。

スペシャルサンクス

参考にさせていただいたリンク集です。ありがとうございました!

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2