2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

TerraformでSnowflakeのRBACを構築:サービスユーザーとキーペア認証による自動化体制構築

2
Last updated at Posted at 2026-02-09

はじめに

本記事では、Snowflakeのベストプラクティスに基づいたRBAC(ロールベースアクセス制御)を、Terraformで完全自動化する手順を解説します。特に「MFA(多要素認証)の連打地獄」から脱出するためのサービスユーザー導入と、開発者が安全に作業できる権限設計について、実際のトラブルシューティングも含めて紹介します。

背景:なぜサービスユーザーが必要なのか

直面した課題

Terraformでインフラをプロビジョニングする際、個人アカウントで認証すると以下の問題が発生しました:

  1. MFA(Duo Security)の連打: リソース作成のたびにスマホに承認通知が飛ぶ
  2. タイムアウトによるアカウントロック: 承認が遅れると一時的にロックされる
  3. 自動化の妨げ: CI/CDパイプラインでの利用が困難

Snowflakeのベストプラクティス

Snowflakeの公式ドキュメントでは、以下が推奨されています:

  • 自動スクリプトにACCOUNTADMINを使用しない
  • サービスユーザー(TYPE = SERVICE)の活用
  • キーペア認証(JWT)による無人運用

アーキテクチャ設計

ロール階層の設計

Snowflakeのベストプラクティスに基づき、以下の階層構造を構築します:

[上位ロール:管理]
      ▲
      │ (継承)
  SYSADMIN(システム管理者:全ての「物」を把握する)
      ▲
      │ (継承)
  DEVELOPER_ROLE(機能ロール:開発者という「職種」)
      ▲
      │ (継承)
  DEV_FULL_ACCESS(アクセスロール:DB/Schemaへの「権限セット」)
      ▲
      │ (権限付与)
[オブジェクト:DB, Schema, Stage, Table]

設計の原則:

  • アクセスロール: 特定のオブジェクト(DB/Schema/Table)への権限を保持
  • 機能ロール: ビジネス上の役割に対応し、複数のアクセスロールを束ねる
  • SYSADMIN統合: すべてのカスタムロールを最終的にSYSADMINに付与し、管理を統合

ディレクトリ構成

infra/
├── main.tf
├── providers.tf
├── variables.tf
├── outputs.tf
├── terraform.tfvars      # .gitignoreに追加必須
└── modules/
    ├── aws_s3_iam/       # AWS側のリソース
    ├── snowflake_base/   # DB/Warehouse/基本リソース
    └── snowflake_iam/    # ユーザーとロール管理(今回新設)
        ├── main.tf
        ├── variables.tf
        └── providers.tf

実装手順

Step 1: サービスユーザーの作成

1-1. RSAキーペアの生成

ローカル環境で以下のコマンドを実行します:

cd ~/.ssh

# 秘密鍵の作成(暗号化なし)
openssl genrsa 2048 | openssl pkcs8 -topk8 -inform PEM -out snowflake_tf_snow_key.p8 -nocrypt

# 公開鍵の抽出
openssl rsa -in snowflake_tf_snow_key.p8 -pubout -out snowflake_tf_snow_key.pub

1-2. Snowflakeでサービスユーザーを作成

Snowflakeワークシートで以下のSQLを実行(ACCOUNTADMINロール):

USE ROLE ACCOUNTADMIN;

CREATE USER TERRAFORM_SVC
    TYPE = SERVICE
    COMMENT = "Service user for Terraforming Snowflake"
    RSA_PUBLIC_KEY = 'ここにsnowflake_tf_snow_key.pubの中身を貼り付け';

-- 必要な権限を付与
GRANT ROLE SYSADMIN TO USER TERRAFORM_SVC;
GRANT ROLE SECURITYADMIN TO USER TERRAFORM_SVC;
GRANT ROLE USERADMIN TO USER TERRAFORM_SVC;

重要なポイント:

  • SYSADMIN: データベース、ウェアハウスなどのオブジェクト作成
  • SECURITYADMIN: 権限の付与・取り消し
  • USERADMIN: ユーザーとロールの作成

Step 2: Terraformプロバイダーの設定

2-1. プロバイダー設定の更新

# infra/providers.tf
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
    snowflake = {
      source  = "snowflakedb/snowflake"
      version = "~> 2.12.0"
    }
  }
}

provider "aws" {
  region = var.aws_region
}

provider "snowflake" {
  organization_name = var.snow_organization
  account_name      = var.snow_account
  user              = "TERRAFORM_SVC"
  role              = "SECURITYADMIN"  # ユーザー/ロール作成のため
  authenticator     = "SNOWFLAKE_JWT"
  private_key       = file("~/.ssh/snowflake_tf_snow_key.p8")
}

2-2. 変数定義の整理

パスワード認証関連の変数を削除し、必要最小限に:

# infra/variables.tf
variable "snow_organization" {
  type        = string
  description = "Snowflakeの組織名"
}

variable "snow_account" {
  type        = string
  description = "Snowflakeのアカウント名"
}

variable "dev_user_password" {
  type      = string
  sensitive = true
  description = "開発者ユーザーの初期パスワード"
}

# その他の変数...

Step 3: RBAC モジュールの実装

3-1. ユーザーとロールの作成

# infra/modules/snowflake_iam/main.tf

# 1. 開発者ユーザーの作成
resource "snowflake_user" "developer_user" {
  name                 = "DEV_USER"
  login_name           = "DEV_USER"
  password             = var.dev_user_password
  default_role         = "DEVELOPER_ROLE"
  must_change_password = true  # 初回ログイン時に変更を強制
}

# 2. アクセスロール(権限の箱)
resource "snowflake_account_role" "dev_access_role" {
  name = "DEV_FULL_ACCESS"
}

# 3. 機能ロール(業務上の役割)
resource "snowflake_account_role" "developer_role" {
  name = "DEVELOPER_ROLE"
}

3-2. 権限の付与

# データベースへのUSAGE権限
resource "snowflake_grant_privileges_to_account_role" "db_grant" {
  privileges        = ["USAGE"]
  account_role_name = snowflake_account_role.dev_access_role.name
  on_account_object {
    object_type = "DATABASE"
    object_name = var.database_name
  }
}

# スキーマへの権限(開発に必要なもの)
resource "snowflake_grant_privileges_to_account_role" "schema_grant" {
  privileges        = ["USAGE", "CREATE TABLE", "CREATE STAGE", "CREATE PIPE"]
  account_role_name = snowflake_account_role.dev_access_role.name
  on_schema {
    schema_name = "${var.database_name}.${var.schema_name}"
  }
}

# ウェアハウスへのアクセス権
resource "snowflake_grant_privileges_to_account_role" "wh_grant" {
  privileges        = ["USAGE", "OPERATE"]
  account_role_name = snowflake_account_role.dev_access_role.name
  on_account_object {
    object_type = "WAREHOUSE"
    object_name = var.warehouse_name
  }
}

3-3. ロール階層の構築

# アクセスロール → 機能ロール
resource "snowflake_grant_account_role" "access_to_functional" {
  role_name        = snowflake_account_role.dev_access_role.name
  parent_role_name = snowflake_account_role.developer_role.name
}

# 機能ロール → SYSADMIN(管理の統合)
resource "snowflake_grant_account_role" "functional_to_sysadmin" {
  role_name        = snowflake_account_role.developer_role.name
  parent_role_name = "SYSADMIN"
}

# 機能ロール → ユーザー
resource "snowflake_grant_account_role" "functional_to_user" {
  role_name = snowflake_account_role.developer_role.name
  user_name = snowflake_user.developer_user.name
}

3-4. 既存オブジェクトと将来のオブジェクトへの権限

開発者が既存のステージを参照でき、今後作成されるオブジェクトも自動で見えるように:

# 既存の全ステージへの権限
resource "snowflake_grant_privileges_to_account_role" "all_stage_grant" {
  privileges        = ["USAGE", "READ"]
  account_role_name = snowflake_account_role.dev_access_role.name
  on_schema_object {
    all {
      object_type_plural = "STAGES"
      in_schema          = "${var.database_name}.${var.schema_name}"
    }
  }
}

# 将来作成されるテーブルへの自動権限付与
resource "snowflake_grant_privileges_to_account_role" "future_table_grant" {
  privileges        = ["SELECT", "INSERT", "UPDATE", "DELETE"]
  account_role_name = snowflake_account_role.dev_access_role.name
  on_schema_object {
    future {
      object_type_plural = "TABLES"
      in_schema          = "${var.database_name}.${var.schema_name}"
    }
  }
}

Step 4: メインモジュールでの呼び出し

# infra/main.tf
module "snowflake_iam" {
  source            = "./modules/snowflake_iam"
  database_name     = "GOD_PROJECT_DB"
  schema_name       = "RAW"
  warehouse_name    = "GOD_PROJECT_WH"
  dev_user_password = var.dev_user_password
}

トラブルシューティング

Issue 1: "password conflicts with private_key"

症状: キーペア認証に切り替えたのに、パスワード認証と競合するエラーが出る

原因: 環境変数にSNOWFLAKE_PASSWORDが残っている

解決策:

# Windows PowerShell
$env:SNOWFLAKE_PASSWORD=""

# Mac/Linux
unset SNOWFLAKE_PASSWORD

その後、Terraformを再初期化:

terraform init -upgrade

Issue 2: "Insufficient privileges to operate on account"

症状: サービスユーザーでユーザーやロールを作成しようとするとエラー

原因: SYSADMINロールはオブジェクト作成権限を持つが、ユーザー/ロール作成権限は持たない

解決策: サービスユーザーに適切なロールを付与

USE ROLE ACCOUNTADMIN;
GRANT ROLE USERADMIN TO USER TERRAFORM_SVC;
GRANT ROLE SECURITYADMIN TO USER TERRAFORM_SVC;

そして、プロバイダー設定で適切なロールを指定:

provider "snowflake" {
  # ...
  role = "SECURITYADMIN"  # ユーザー/ロール作成とGRANT管理
}

Issue 3: "Stage does not exist or not authorized"

症状: 管理者が作成した既存のStageが、開発者ロールから見えない

原因: 既存オブジェクトへの権限が明示的に付与されていない

解決策: GRANT ... ON ALL STAGESを使用

resource "snowflake_grant_privileges_to_account_role" "all_stage_grant" {
  privileges        = ["USAGE", "READ"]
  account_role_name = snowflake_account_role.dev_access_role.name
  on_schema_object {
    all {
      object_type_plural = "STAGES"
      in_schema          = "${var.database_name}.${var.schema_name}"
    }
  }
}

Issue 4: カタログ(UI)にデータベースが表示されない

症状: SQLコマンドは成功するが、Snowsight UIでデータベースが見えない

原因: 以下画像のユーザー名の下にあるロールが間違っていた(画像の DEVELOPER_ROLE に表示されていた部分のロール権限のないロールだった)
image.png

解決策: 正しいロールを選択する

image.png

検証方法

1. ロール階層の確認

-- 作成したロールの確認
SHOW ROLES;

-- ロールの権限確認
SHOW GRANTS TO ROLE DEVELOPER_ROLE;
SHOW GRANTS ON ROLE DEVELOPER_ROLE;

2. 開発者ユーザーでのログイン

  1. DEV_USERでSnowflakeにログイン
  2. 初回ログイン時にパスワード変更を求められることを確認
  3. ロールをDEVELOPER_ROLEに切り替え

3. 権限のテスト

USE ROLE DEVELOPER_ROLE;
USE WAREHOUSE GOD_PROJECT_WH;

-- テーブル作成権限の確認
CREATE TABLE GOD_PROJECT_DB.RAW.TEST_TABLE (ID INT);

-- 既存のステージへのアクセス確認
LS @GOD_PROJECT_DB.RAW.MY_S3_STAGE;

-- 自分で作成したステージの確認
CREATE STAGE GOD_PROJECT_DB.RAW.DEV_TEST_STAGE;
LS @GOD_PROJECT_DB.RAW.DEV_TEST_STAGE;

ベストプラクティス

セキュリティ

  1. 秘密鍵の管理

    • .p8ファイルは絶対にGitにコミットしない
    • 本番環境ではHashiCorp VaultやAWS Secrets Managerを使用
  2. 最小権限の原則

    • 必要な権限のみを付与
    • 本番環境ではAmazonS3FullAccessではなくカスタムポリシーを使用
  3. MFAの適切な使用

    • 人間用アカウントには必ずMFAを有効化
    • サービスユーザーはキーペア認証のみ

運用

  1. ロールの命名規則

    • アクセスロール: <対象>_<権限レベル> (例: DEV_FULL_ACCESS)
    • 機能ロール: <業務役割>_ROLE (例: DEVELOPER_ROLE)
  2. 将来の付与(Future Grants)の活用

    • 新しく作成されるオブジェクトへの権限を自動化
    • 権限管理の手間を大幅に削減
  3. 管理アクセススキーマの検討

    • より厳密な権限管理が必要な場合は、Managed Access Schemaの使用を検討

コスト最適化

Warehouseの自動停止設定

resource "snowflake_warehouse" "this" {
  name           = "GOD_PROJECT_WH"
  warehouse_size = "XSMALL"
  auto_suspend   = 60     # 60秒で自動停止
  auto_resume    = false  # 勝手に起動させない
}

重要: auto_resume = trueは特に個人開発・ラボにおいて意図しないクエリで勝手に起動し、想定外の課金が発生します。そのため今回はauto_resume = falseとしましたが必要な時は明示的にウェアハウスをresumeする必要があり多少の手間はあります。

まとめ

本記事では、以下を実現しました:

  1. MFA地獄からの脱出: キーペア認証により、承認通知なしで高速デプロイ
  2. 適切なRBAC設計: ベストプラクティスに基づいた階層的な権限管理
  3. インフラ部分のTerraform化: ほとんど変化しないインフラのコード化により再現性を確保
  4. 開発者体験の向上: 適切な権限により、ストレスなく開発・テストが可能

次のステップ

この基盤の上に、以下を構築してみたいと考えています:

  • Snowpipe: S3からの自動データ取り込み
  • RDS連携: 上流DB役のRDSからのデータ連携
  • dbt統合: データ変換の自動化
  • Terraformの適切な分割: インフラ、ユーザー作成、権限設定を分割して影響範囲を狭める&それぞれの領域に必要な権限を付与

参考資料

2
1
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
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?