メドピア開発者ブログ

集合知により医療を再発明しようと邁進しているヘルステックカンパニーのエンジニアブログです。読者に有用な情報発信ができるよう心がけたいので応援のほどよろしくお願いします。

YAML + Rakeタスクで実現する「権限漏れゼロ」なBI運用

こんにちは、サーバーサイドエンジニアの長谷川(@hasehiro25)です。

今回の『ClinPeerアプリ開発の裏側』連載では、BIツール運用の実践的なTipsをご紹介します。 tech.medpeer.co.jp

BIツールの運用における課題として、「テーブルやカラムが追加された際のメンテナンスに手間がかかる」があります。

参照できるカラムをリストで管理する「許可リスト方式」では更新漏れが起きやすく、逆に参照できないカラムを管理する「拒否リスト方式」では、意図せず個人情報などのカラムが参照可能になってしまうリスクがあります。

そこでClinPeerでは、テーブルやカラムが変更された際にCIでチェックを行い、BIで参照できるデータを安全に更新する仕組みを導入しています。これにより、権限設定の抜け漏れを防いでいます。

本格的な分析用BIツールと、エラー調査用の簡易BIツールとしてBlazerをClinPeerでは導入しており、今回は後者のBlazerをメインにご紹介します。なお、両ツールとも共通でBI用スキーマファイルを使用しています。

github.com

specによるschemaチェック

ClinPeerでは、schema_for_bi.ymlというファイルで、Blazerから参照できるスキーマ情報を管理しています。

ファイルの中身は以下のようなイメージです。

articles:
  - id
  - created_at
  - updated_at
  - title
  - body

users:
  - id
  - created_at
  - updated_at
  - nick_name
  # - email

YAMLファイルの構造は、articles:のようにまずテーブル名を記述し、その配下にカラムをリスト形式で記述するシンプルなものです。

ここで重要なのが、リストの中でも#でコメントアウトされているカラムの扱いです。これらは意図的に参照を許可しないカラムとして扱われます。

コメントアウトを用いる目的は、カラムの存在を把握しつつ、「これは意図的に許可してないよ」という状態を誰が見ても分かるようにするためです。 もし「許可リスト方式」の場合、それが意図的に書いてないのか、単なる追加忘れなのかを区別できません。コメントアウトによって、その曖昧さをなくしています。

このYAMLファイルがデータベースの現状と一致していることを担保するために、CIで以下のようなspecを実行しています。

it "テーブル定義順がABC順であること" do
  tables = YAML.load_file("db/schema_for_bi.yml").keys
  expect(tables).to eq(tables.sort)
end

it "全てのスキーマ情報が記載されていること", aggregate_failures: false do
  expect_tokens = []
  ActiveRecord::Base.connection.tables.sort_by(&:itself).each do |table|
    expect_tokens << table
    columns = ActiveRecord::Base.connection.columns(table).map(&:name)
    columns.each { |column| expect_tokens << column }
  end

  yml_lines = File.readlines("db/schema_for_bi.yml")
  yml_lines.fill("", yml_lines.size..expect_tokens.size - 1).zip(expect_tokens).each do |actual_line, expect_token|
    expect(actual_line).to include(expect_token)
  end
end

内容はシンプルで、ActiveRecord::Base.connection.tables でテーブルとカラムの情報を全て取得し、File.readlinesで読み込んだYAMLファイルの内容と一致するかを一行ずつ確認します。(コメントアウト部分含む)

このspecがパスすれば、YAMLファイルがデータベースの現状を正しく反映していることが保証され、安全にBlazerの権限設定に進めます。

Rakeタスクによる権限更新

specのチェックを通過したYAMLファイルを使い、実際に権限を更新するためのRakeタスクがこちらです。

namespace :bi do
  task initialize_user: :environment do
    exec = ->(sql) { ActiveRecord::Base.connection.execute(sql) }
    exec["DROP USER IF EXISTS #{Setting::Blazer.db_user_name};"] # 権限リセット
    exec["CREATE USER #{Setting::Blazer.db_user_name} IDENTIFIED BY '#{Setting::Blazer.db_user_password}';"]
    exec["GRANT SHOW VIEW ON * TO #{Setting::Blazer.db_user_name};"]
  end

  task grant_select_columns: :initialize_user do
    YAML.load_file("db/schema_for_bi.yml").each do |table, columns|
      # 「read」など、MySQLの予約語に定義されたカラム名を、テーブル登録するカラム名として認識されるように、バッククォートで囲う
      columns = columns.map { |s| "`#{s}`" }.join(",")
      ActiveRecord::Base.connection.execute("GRANT SELECT (#{columns}) ON #{table} TO #{Setting::Blazer.db_user_name};")
    end
  end
end

initialize_userで古い権限設定を無効化して、新たな権限を適用する初期化処理を実行します。

そのあとgrant_select_columnsではYAML.load_file を使ってYAMLファイルを読み込み、それぞれのテーブルとカラムに対して権限設定を行なっています。コメントアウト部分は無視されるため、結果として参照を許可したいカラムにのみ SELECT 権限を付与する GRANT 文が実行される仕組みになっています。

grant_select_columnsの実行タイミングですが、他のデプロイ用タスクと合わせて追加しており、デプロイ時に自動で実行されるように設定しています。

namespace :deploy do
  task pre_hook: %i[setting:validate db:create db:migrate db:seed bi:grant_select_columns]
end

まとめ

BIツールの権限設定は後回しにされがちで、「いざデータを見たい!」という時に参照できない、といったことが起こりがちです。

今回ご紹介したように、テーブル構造の変更と同時に権限設定もチェックする仕組みをCIに組み込むことで、最新かつ安全な状態でデータを参照できる環境を維持できます。

「いつかやろう」と溜まりがちな作業は、CIを活用して日々コツコツと対応していくことで、少しずつ快適な開発環境に繋がっていくと思いますので、ぜひ参考にしてみてください。


是非読者になってください!


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら medpeer.co.jp

■エンジニア紹介ページはこちら engineer.medpeer.co.jp

■メドピア公式YouTube  www.youtube.com

■メドピア公式note
style.medpeer.co.jp