CasbinによるGoの権限管理:RBACから実用的なABACシステムまで

Development tutorial - IT technology blog
Development tutorial - IT technology blog

「スパゲッティ認可」という名の悪夢

最初のGoプロジェクトは、if-elseによる権限チェックの山で危うく失敗しそうになりました。最初は簡単でした。ユーザーが管理者(admin)なら記事の編集を許可するだけです。しかし、システムが10,000ユーザーに達した頃、クライアントから「厄介な」要求が届き始めました。

エディター、モデレーター、さらにはゲストといったロールの追加です。最も厄介だったのは、「エディターは自分の記事のみ編集可能」や「モデレーターは午前8時から午後5時までの間のみコメントを削除できる」といった条件でした。わずか2週間で、コードは権限チェックのロジックで溢れかえりました。小さなルール変更のたびに、数十のファイルを探し回り、コードを修正して再ビルドしなければなりませんでした。これこそが、多くの開発者が陥りがちな「ハードコードされた認可(Hardcoded Authorization)」の罠です。

なぜ自作の権限コードはスケール時に限界が来るのか?

1週間かけてリファクタリングした結果、自前で構築したシステムがすぐに限界を迎える3つの理由が見えてきました:

  • ロジックの密結合(Tight Coupling): 権限管理がビジネスロジックの中に散在してしまいます。ロール構造を変更したい場合、至る所のコードを修正しなければなりません。
  • ABACの実装が困難: RBAC(ロールベースアクセス制御)は比較的簡単です。しかし、ABAC(属性ベースアクセス制御 – 例:記事の所有者のみが編集可能)が必要になると、if-elseの山はまさに迷宮と化します。
  • セキュリティリスク: チームの各メンバーが独自のやり方で権限チェックを行うと、一瞬の油断がハッカーに悪用される脆弱性を生むことになります。

最適なソリューションを求めて

私は「正解」に辿り着く前に、3つの選択肢を検討しました:

  1. 自前でDBテーブルを作成: rolespermissionsテーブルを作成する方法。権限の継承(Role Hierarchy)の処理が非常に難しく、メンテナンスコストも高くなります。
  2. シンプルなミドルウェア: ルーターレベルでのブロックは可能ですが、リソースごとの詳細な(fine-grained)権限管理には対応できません。
  3. Casbinの利用: PERMモデル(Policy, Effect, Request, Matchers)をサポートする特化型ライブラリです。これが最もプロフェッショナルなアプローチです。

Casbin – 「ルール」と「データ」の分離

50以上のAPIエンドポイントを持つECサイトのプロダクション環境でCasbinを6ヶ月間運用した結果、これが正しい決断だったと確信しています。Casbinの最大の利点は、メカニズム(Mechanism)ポリシー(Policy)を分離できることです。

PERMモデル:Casbinの背骨

model.confファイルを一度定義するだけで済みます。これは以下の4つのコンポーネントを通じてシステムの動作を規定します:

  • Request (r): 誰が(sub)どのリソース(obj)に対して何(act)をしようとしているか?
  • Policy (p): 許可ルールのリスト。
  • Matchers (m): リクエストとポリシーを照合するための数式。
  • Effect (e): 最終的な決定(例:1つでも一致するルールがあれば許可する)。

Goでの実践的な実装

ライブラリのインストールは非常に簡単です:

go get github.com/casbin/casbin/v2

以下は、権限の継承をサポートするRBACシステムのmodel.confファイルです:

[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

[role_definition]
g = _, _

[policy_effect]
e = some(where (p.eft == allow))

[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act

Goのコードでアクセス権限をチェックする方法:

package main

import (
	"fmt"
	"github.com/casbin/casbin/v2"
)

func main() {
	e, _ := casbin.NewEnforcer("model.conf", "policy.csv")

	sub := "alice" // ユーザー
	obj := "data1" // リソース
	act := "read"  // アクション

	ok, err := e.Enforce(sub, obj, act)

	if err != nil {
		fmt.Println("システムエラー")
	} else if ok {
		fmt.Println("アクセス成功!")
	} else {
		fmt.Println("この権限がありません!")
	}
}

複雑なポリシー構造を扱う際、私はよく toolcraft.app を使ってJSONの整形やデータのクイックテストを行っています。これにより、VS Codeが重くなるような拡張機能を入れすぎるのを防げます。

ABACへのアップグレード:真のパワー

「記事の所有者のみが編集可能」というルールにしたい場合でも、Goのコードを修正する必要はありません。設定ファイルのMatcherを更新するだけです:

[matchers]
m = r.sub == r.obj.Owner || r.sub == "admin"

このカスタマイズ性により、ビジネスロジックを汚すことなく、クライアントからのどんな「特殊な」要求にも対応できます。

Casbinを使う際の血の滲むような教訓

半年間の運用の末、いくつかの実戦的な経験を得ました:

  • プロダクション環境でCSVは使わない: Gorm Adapterを使用して、ポリシーをPostgreSQLやMySQLに保存しましょう。CSVファイルはクイックテストやデモにのみ適しています。
  • キャッシュは不可欠: 権限チェックはすべてのリクエストで行われます。ポリシーが数千行に達する場合は、Redisを使用して結果をキャッシュしてください。レスポンス速度は1ms未満を目指すべきです。
  • コメントを惜しまない: model.confファイルには詳細な注釈を入れましょう。信じてください、コメントがないと3ヶ月後に戻ってきたとき、自分が書いたMatcherが何を意味しているのか分からなくなります。

終わりに

Casbinはすべての問題に対する万能薬ではありませんが、Goで権限管理を処理するための最も強力なツールです。セキュリティロジックを完全に分離することで、開発チームと運用チームがコードを読まずとも相互理解を深めることができます。プロジェクトが複雑になり始めたら、ぜひ今日からCasbinを試してみてください。

Share: