kubell Creator's Note

ビジネスチャット「Chatwork」のエンジニアのブログです。

ビジネスチャット「Chatwork」のエンジニアのブログです。

読者になる

Hashi Corp Vaultを使ったDBユーザー管理

はじめに

みなさんは、DBユーザーの管理をどのように行っているでしょうか。
Create UserGrantなどの生のSQL文をそのまま管理していたり、何かしらのツールを使って管理しているのではないでしょうか。
弊社では今まではgratanというツールを使って管理してましたが、昨年にHashiCorp Vaultを使った管理に移行しました。

背景と課題

チャットサービスならではの要件

「Chatwork」ではチャットという特性上、秘匿情報を持ったDBテーブルが多く存在します。
そのため、各エンジニアは秘匿情報にアクセスできないようにカラムレベルで権限制御を行っています。
また、監査という観点からもAuditログをしっかりと記録する必要があるため、エンジニア単位でDBユーザーを作成する必要があります。
(ここまで細かくユーザーや権限を管理しているところは意外と珍しいのではないでしょうか)

既存の管理方法と課題

エンジニア単位での管理は、エンジニアの増加に伴い管理コストも上がっていくため、 先程も紹介したgratanというツールを使ってIaC化して管理をしていました。
ただし、ユーザー作成には強い権限が必要な関係から、CI上での実行などが実現できておらず、トイルとなっていました。
また、gratan自体にもいくつか課題がありました。

gratanの課題

  1. ユーザー作成(権限付与)時の問題点
    • 毎回ユーザー毎にFLUSH PRIVILEGESを実行していた
      • ユーザー数が多いとDBへの負荷が上昇してしまう
      • 本来はGrant文でのユーザー作成、権限付与の場合にはFLUSH PRIVILEGESは不要
    • 回避策として、dry-runで生成されたSQLからFLUSH PRIVILEGESを除いて実行する運用になっていた
  2. MySQL8.0以降への対応
    • MySQL8.0以降ではCreate Userでのユーザー作成が必須
    • gratanはアーカイブ化されており更新されていない
      • Aurora MySQL V3(MySQL8.0互換)への移行時に利用不可

これらの理由から、gratanをやめて別のツールや仕組みに移行することを決断しました。

HashiCorp Vaultを選定した理由

Vaultに決めた理由は実は最初からほぼ決まっていました。
というのも、過去にあるプロジェクトのチームでテスト的に運用していた実績があり、 問題なく利用できそうというフィードバックを得ていたからです。

そのフィードバックも踏まえて選定した理由は以下の通りです。

  1. AWS SSO(AWS IAM Identity Center)での認証による効率化
    • エンジニア単位ではなくグループ単位での管理が可能
    • 運用コストの大幅な削減
  2. gratanでの課題解決
    • 手動対応の削減
    • MySQL8.0への対応
  3. セキュリティの向上
    • 動的なDBユーザー作成によるパスワード漏えいリスクの軽減

全体構成

それでは具体的な構成や仕組みについて説明したいと思います。
まずは、全体構成と認証フローは以下の図のようになります。

全体構成
認証フロー

エンジニアがDBにログインする際の手順

各エンジニアは以下の手順でAurora(DB)にログインします。

  1. AWS SSOログイン
  2. AWS認証でVault Serverにログイン
  3. Vault ServerにアクセスしてDB Credential作成
  4. 事前にSSM Session Managerを使い踏み台サーバー(db-bastion)経由でAuroraとポートフォワードしておく
  5. 3で発行したDB Credentialを使ってDBにログイン

Vaultの仕組み

次にVaultの説明です。

Vaultの認証方法

Vaultで利用できる認証方法はいくかあるのですが、今回はAWS認証を選択しています。
https://developer.hashicorp.com/vault/docs/auth/aws

弊社ではOktaとAWS SSOを連携しているため、Vaultの認証をAWS SSO認証にすることでDBユーザーの管理もOktaで管理できるようになることからAWS認証を選択しました。

ハマりポイント

この部分で少しハマったポイントとしては、vaultコマンドがAWSプロファイルの指定(AWS_PROFLE)に対応しておらず、セッショントークンを渡す必要があったことです。
そのため、上に記載の図で示した通りsso loginしたあとにsso get-role-credentialsでセッショントークンを取得する必要があります。
この部分が煩雑なため、aws2-wrapというツールを使うことでこのステップを省略しています。

実際のコマンド例

aws sso login --profile db-read-access

export VAULT_ADDR=https://vault.example.com
export AWS_PROFILE=db-read-access
aws2-wrap  vault login -method=aws role=db-read

Vault Secrets Engine

今回はDatabase Secret Engineを使ってシークレットを管理します。
図で言うところのDB Credentialの発行をする部分です。
https://developer.hashicorp.com/vault/docs/secrets/databases

また、Aurora MySQLを利用しているため、プラグインとしてはmysql-aurora-database-pluginを利用します。
https://developer.hashicorp.com/vault/docs/secrets/databases/mysql-maria

権限の設計

Vaultにはポリシー、ロールという概念があり、 それぞれ、Vaultが管理するシークレットのどこにアクセスできるか、認証したユーザーはどのポリシーを割り当てるかを設定します。
そのため、誰にどの権限を付与するかを考える必要があります。
以下が考えた各権限の組み合わせの一例です。

権限 AWS SSO ロール名 ポリシー名 Vault ロール名
管理 DbAdminAccess database-admin-access db-admin
書き込み DbWriteAccess database-write-access db-write
読み込み DbReadAccess database-read-access db-read

また、このロール以外にもDatabase Secret Engineにもロールと言う概念があり、こちらは実際に作成するユーザーにどういった権限をもたせるかを設定するものです。
具体的にはCreate UserGrantを設定することになります。
これを先程の表に付け加えると以下の表になります。

権限 AWS SSO ロール名 ポリシー名 Vault ロール名 DB ロール名
管理 DbAdminAccess database-admin-access db-admin - admin-db
- write-db
- read-db
書き込み DbWriteAccess database-write-access db-write - write-db
- read-db
読み込み DbReadAccess database-read-access db-read - read-db

上から権限が強いものとして下の権限にもなれるような設計にしました。
これは管理者であっても行うオペレーションによっては読み込み権限だけで作業したい場合にVaultのログインをし直さずに済むように手間を省きたかったからです。

これを踏まえて実際にエンジニアがDB Credentialを作成するためには、先程の認証方法の際に記載したコマンドを実行後に以下を実行することで作成されます。

vault read database/creds/read-db

実行結果としては以下のような出力になります。

Key                Value
---                -----
lease_id           database/creds/read-db/O1k6orQQ8aVIV521swotBrVD
lease_duration     1h
lease_renewable    true
password           4ipA0osb744Kcf85-Awp
username           xxxx-r-xqWc7

運用

最後に実際にどういう運用をしているかを説明します。

Vaultの管理

Vaultの設定はterraformを使って管理することにしました。
また、Vault ServerはEKS上で稼働させました。
今までは新規テーブルが追加されたり、削除されたりする度に全エンジニアのDBユーザーの権限を更新する必要があり、かなり大変だったのですがコードの修正一つでできるようになりました。

ちなみに、先ほどの権限の設計のところで紹介したポリシーやロールは以下のようなコードになります。

# policy
resource "vault_policy" "database_read_access" {
  name = "database-read-access"

  policy = <<EOT
path "database/roles" {
  capabilities = ["list"]
}
path "database/creds/read-*" {
  capabilities = ["read"]
}
EOT
}

# role
resource "vault_aws_auth_backend_role" "db_read" {
  backend   = vault_auth_backend.aws.path
  role      = "db-read"
  auth_type = "iam"
  bound_iam_principal_arns = [
    data.aws_iam_role.db-read-access.arn,
  ]
  token_ttl      = var.database_role_default_ttl
  token_max_ttl  = var.database_role_max_ttl
  token_policies = [vault_policy.database_read_access.name]
}

# DB role
# 実際にはテーブル単位でカラムレベルの制限を入れています
resource "vault_database_secret_backend_role" "read_db" {
  backend     = vault_mount.database.path
  name        = "read-db"
  db_name     = vault_database_secret_backend_connection.db.name
  default_ttl = var.database_admin_role_default_ttl
  max_ttl     = var.database_admin_role_max_ttl
  creation_statements = [
    "CREATE USER '{{name}}'@'%' IDENTIFIED BY '{{password}}';",
    "GRANT SELECT ON *.* TO '{{name}}'@'%';",
  ]
}

エンジニアのアクセス管理

エンジニアの権限管理は前述したように大元はOktaでの管理となるためOktaにAWS SSOと紐付くグループを作り、そのグループに該当するエンジニア個別や組織のグループを追加することでエンジニアはDBへアクセスできるようになります。

今までは一人のエンジニアを追加するためには、毎回すべてのデータベース、テーブルの権限を付与する必要があるため、何個もGrant文を手動で実行する必要がありましたが既に権限は準備されているため、Oktaグループへのメンバー追加だけでできるようになりこの部分の作業の軽減が一番大きかったです。

まとめ

Vaultへの移行の経緯や仕組み、運用などについて簡単に説明しました。
これによって大幅に作業が軽減することができました。
今までは依頼が来た際に手順書を作成、レビューしてペアオペという作業をしていましたが、Vaultにしてからは毎日の朝会の少しの時間で作業してしまえるほどに短縮できました。

エンジニア用のDBユーザーの管理はこれで改善できましたが、アプリケーション用のDBユーザーの管理は未だにGratanを利用しています。
実は課題があったMySQL8.0への対応もして利用しているのですがこの話はまたの機会にしたいと思います。

お知らせ

最後にお知らせです。
7月11日、12日に開催される「SRE NEXT 2025」にプラチナムスポンサーとしてブース出展します🎉
二日目の12日にはスポンサーセッションも行いますのでぜひご参加ください!

SRE NEXT 2025 詳細