Snowflake の Role を Terraform で作成しユーザーにテーブルへのアクセス権限を与える

snowflaketerraform

Snowflake でオブジェクトの OWNERSHIP 権限を持たない他のユーザーでもアクセスできるようにするにはロールで権限を与える必要がある。システムロールも例外ではなく、ACCOUNTADMIN であっても権限がないと何もできないので階層の最上位のカスタムロールを SYSADMIN に GRANT することが推奨されている

キーペア認証で Terraform を実行したり Snowflake CLI や gosnowflake でクエリを実行する - sambaiz-net

# CREATE ROLE ...
resource "snowflake_account_role" "test" {
  name = "test"
}

# GRANT USAGE ON SCHEMA ... TO ROLE ...
resource "snowflake_grant_privileges_to_account_role" "grant_testschema_role" {
  privileges        = ["USAGE", "MONITOR"]
  account_role_name = snowflake_account_role.test.name
  on_schema {
    schema_name = snowflake_schema.test.fully_qualified_name
  }
}

# GRANT ROLE ... TO ROLE ...
resource "snowflake_grant_account_role" "test" {
  role_name        = snowflake_account_role.test.name
  parent_role_name = "SYSADMIN"
}

ロールをユーザーに GRANT すると USE ROLE してその権限でクエリを実行したりできるようになる。default_role であっても GRANT は必要。

locals {
  users = {
    "user1" = { email = "[email protected]", default_role = "ROLE1" }
    "user2" = { email = "[email protected]", default_role = "ROLE2", active = false }
  }
}

resource "snowflake_user" "users" {
  for_each = { for key, user in local.users : key => user if lookup(user, "active", true) }

  name         = each.key
  email        = each.value.email
  default_role = each.value.default_role
}

# GRANT ROLE ... TO USER ...
resource "snowflake_grant_account_role" "default_role" {
  for_each = snowflake_user.users
  
  user_name = each.value.name
  role_name = each.value.default_role
}

ちなみに新規ユーザーのパスワード設定をどうするかという問題がある。password フィールドで初期パスワードを渡し must_change_password = true で初回ログイン時に変更させるという方法があるが、想定より長く変更されていないパスワードが tfstate に残ってしまうことを懸念している。そのため ALTER USER <user_name> RESET PASSWORD; で発行できるパスワード再発行リンクを SYSTEM$SEND_EMAIL() で自動的に送付することを試みたが、ユーザー作成時にメールアドレスの確認が行われず、確認されていないアドレスには送れないため諦めてしまった。そもそも確認済かどうかすら確認する術が現状なかったりと色々足りていないので今後に期待している。なお SAML認証はサポートしている。

provider "snowflake" {
  ...
  preview_features_enabled = ["snowflake_email_notification_integration_resource"]
}

resource "snowflake_email_notification_integration" "email_integration" {
  name     = "EMAIL_INTEGRATION"
  enabled  = true
}

resource "null_resource" "run_procedure_once" {
  for_each = snowflake_user.users

  triggers = {
    email = each.value.email
  }

  provisioner "local-exec" {
    command = <<EOF
    snow sql -q "CALL SYSTEM\$SEND_EMAIL(
      'EMAIL_INTEGRATION',
      '${each.value.email}',
      'title'
      'content'
    );"
    EOF
  }
}

管理しやすさのためデータベースに紐づくデータベースロールというのもあるが、他のデータベースロールに GRANT する場合に同じ DATABASE である制約があること以外は通常のロールと変わらない。

# CREATE DATABASE ROLE ...
resource "snowflake_database_role" "testdb" {
  database = snowflake_database.test.name
  name     = "TESTDB_ROLE"
}

# GRANT DATABASE ROLE ... TO ROLE ...
resource "snowflake_grant_database_role" "grant_testdb_role" {
  database_role_name = snowflake_database_role.testdb.fully_qualified_name
  parent_role_name   = snowflake_account_role.test.name
}

SCHEMA の権限だけではテーブルにアクセスはできず個別にテーブルの権限を与える必要があり、FUTURE で将来作成されるオブジェクトの権限を自動的に付与することができる。なお object_type_plural は通常の TABLES に加え、DYNAMIC, EVENT, EXTERNAL, HYBRID, ICEBERG TABLES を受け取り、異なるタイプのテーブルには権限が付かないことに注意が必要。

# GRANT SELECT ON FUTURE TABLES IN DATABASE ... TO DATABASE ROLE ...
resource "snowflake_grant_privileges_to_database_role" "grant_select_testdb_future_tables" {
  privileges         = ["SELECT"]
  database_role_name = snowflake_database_role.testdb.fully_qualified_name
  on_schema_object {
    future {
      object_type_plural = "TABLES"
      in_database        = snowflake_database_role.testdb.database
    }
  }
}