/note/tech

Goの型安全性で実現する、複数プロダクトを横断する権限管理

要約:

■ 1. 概要と背景

  • バクラクは複数プロダクト(申請・経費精算、ビジネスカード、請求書発行、勤怠など)が1つのアカウント基盤上で動作している
  • Goの型システム(Defined Type)とコード生成を活用した権限管理の仕組みを紹介する記事
  • Protocol Buffersの1ファイルをSingle Source of Truthとして、900個近い権限定数を自動生成する

■ 2. 権限管理の課題

  • 管理対象は「複数プロダクト × プロダクトごとの複数ロール × ロールが持つ複数の権限」という3階層の掛け算
  • プロダクト増加に伴い、ロールと権限の組み合わせも爆発的に増加する
  • 権限名を文字列で管理すると、タイポや存在しない権限名がコンパイルを通り抜け、実行時まで検知できない
  • AIコーディングエージェントによる実装速度の向上に伴い、ハルシネーションによるミスがレビューをすり抜けるリスクも存在する

■ 3. GoのDefined Typeによる型安全

  • GoのDefined Typeは既存の型をもとに新しい型を定義する仕組み
  • PermissionとProductRoleはどちらもint32が実体だが、Go型システム上では互換性のない別の型として扱われる
  • Permissionを入れるべき場所にProductRoleを渡すとコンパイルエラーになる
  • 生のint32をそのまま渡してもコンパイルエラーになる
  • ビルド段階で間違いを検知できる

■ 4. Protocol BuffersをSingle Source of Truthとする仕組み

  • 権限管理に関わる定義をすべて1つのpermission.protoに集約する
  • 人間が手で書くのはこのファイルのみで、あとはコード生成に委ねる
  • permission.protoの構成:
    • enum ProductRole(ユーザーのロール)
    • enum Permission(個々の操作権限)
    • ロール→権限の対応(カスタムオプションで宣言)
    • RPC→必要な権限の対応(カスタムオプションで宣言)
  • プロダクトごとに番号帯を割り当てることで、新規プロダクト追加時の番号衝突を回避する

■ 5. コード生成の仕組み

  • 標準プラグイン(protoc-gen-go): Defined Typeと定数(permission.pb.go)を生成する
  • 自社製プラグインの生成物:
    • 逆引きマップ(ロール→権限の正引きから、権限→ロールの逆引きマップを生成)
    • RPC→権限マップ(Interceptor用)
  • ロールと権限の対応はprotoのカスタムオプション(attached_permissions)に宣言的に記述する
  • 生成される逆引きマップのキーはPermission型、値のキーはProductRole型であり、Defined Typeの型安全性が生成コード全体に適用される

■ 6. 権限チェックの実装

  • ロールの特定:
    • クライアントからのリクエストの認証情報をもとに内部通信用トークンを生成する
    • トークン内にユーザーのProductRole一覧を格納し、サービス間通信で伝播させる
  • Interceptorによる権限チェック:
    • Connect RPCのInterceptorにより、すべてのRPCを自動的に保護する
    • RPCのフルパスをキーにpermissionByRpcFullNameを参照し権限を検証する
    • 開発者はprotoに宣言を書くだけで、個々のハンドラに権限チェックコードを書く必要がない
  • HasPermission関数による照合:
    • 認証トークンのロール一覧と逆引きマップを突き合わせる
    • 引数・マップ・トークンがすべて同じDefined Typeで統一されているため、型の取り違えはコンパイルエラーで検知する
    • InterceptorだけでなくビジネスロジックやHTTPハンドラでも同じ型・同じ関数を使用できる

■ 7. 型安全性がもたらす効果

  • 存在しない権限の参照: 実行時エラー(サイレントに通過する場合もある)→ ビルド失敗(コンパイル時に検知)
  • タイポ: 実行時まで検知不能 → コンパイルエラー
  • ロールと権限の対応変更: 複数ファイルを手動修正 → protoを1行修正して再生成で全ファイルに反映
  • 新規プロダクト追加: 対応表を複数箇所に追加 → protoに番号帯を割り当ててenumを追加するだけ
  • AIによるハルシネーション(実在しない権限名の生成)も同じ仕組みでコンパイルエラーとして防止できる

■ 8. まとめ

  • Defined Type × コード生成により、組み合わせ爆発する権限管理を型安全に管理できる
  • Single Source of TruthはPermission.protoの1ファイル
  • 認証トークン、生成マップ、ビジネスロジックを同じDefined Typeで統一することで、一貫した権限チェックが実現する
  • 同様のアプローチは権限管理に限らず、似た構造を持つ様々な領域に応用可能