RBAC
ロール (役割) に基づいてアクセス権限を管理する認可モデル
RBAC とは
RBAC (Role-Based Access Control) は、ロール (役割) に基づいてアクセス権限を管理する認可モデルである。ユーザーに直接権限を付与するのではなく、ロールに権限を付与し、ユーザーにロールを割り当てる。
このモデルは 1992 年に NIST の David Ferraiolo と Rick Kuhn が定式化したもので、2000 年に Sandhu らの枠組みと統合され、2004 年 2 月 11 日に米国規格 ANSI/INCITS 359-2004 として採択された。2012 年に INCITS 359-2012 として改訂されている。ロールの階層や相互排他のロール (同時に持てない役割の組) も、この規格が扱う要素に含まれる。
RBAC の構造
RBAC ではユーザーにロール (役割) を割り当て、ロールに権限を紐づける。ユーザーは直接権限を持たず、ロールを介して間接的に権限を取得する。新しいユーザーにはロールを割り当てるだけで適切な権限が付与され、権限の変更もロール単位で一括管理できる。
ユーザー → ロール → 権限
Alice → admin → [read, write, delete, manage-users]
Bob → editor → [read, write]
Carol → viewer → [read]
RBAC vs ABAC
RBAC が破綻するのは、条件の組み合わせをロール名で表現しようとしたときである。「営業部の編集者」「営業部の編集者だが金額 100 万円以上は不可」のように条件が増えるほどロールが掛け算で増え、名前だけでは何が許されるのか読めなくなる。この状態を避けるには、条件を権限の付与先ではなく判定の入力として扱う ABAC (属性ベースアクセス制御) へ寄せる。
逆に ABAC は、許可が実行時の属性の組み合わせで決まるため「今この人が何をできるか」を一覧で示しにくい。監査で権限の棚卸しを求められる環境では RBAC の見通しの良さが効く。実務では大枠を RBAC で切り、例外条件だけを属性で絞る形が多い。
| 観点 | RBAC | ABAC |
|---|---|---|
| 基準 | ロール (役割) | 属性 (部署、時間、IP) |
| 柔軟性 | 中程度 | 高い |
| 複雑さ | 低い | 高い |
| 例 | admin, editor, viewer | 部署=営業 AND 時間=営業時間 |
| AWS | IAM ロール | タグを条件に使う IAM ポリシー |
AWS における ABAC は、IAM の開発者ガイドが属性をタグと呼び、プリンシパルのタグと対象リソースのタグが一致したときに操作を許可する形を基本としている。ロールを増やす代わりにタグの値で切り分けるため、プロジェクトや環境が増えてもポリシーの本数を増やさずに済む。
アプリケーションレベルの RBAC
ロールと権限の対応をコードに持つ場合、判定関数の既定を拒否にしておくことが要点になる。TypeScript の型注釈はコンパイル時にしか効かないため、外部から届いたロール名をそのまま Role として扱うと、未知の値で対応表の参照が undefined になり、拒否ではなく実行時例外 (403 ではなく 500) になる。
type Role = 'admin' | 'editor' | 'viewer';
type Permission = 'read' | 'write' | 'delete' | 'manage-users';
const rolePermissions: Record<Role, Permission[]> = {
admin: ['read', 'write', 'delete', 'manage-users'],
editor: ['read', 'write'],
viewer: ['read'],
};
// 引数は Role ではなく string で受ける (実行時に検証されない型を信用しない)
function hasPermission(role: string, permission: Permission): boolean {
const granted = rolePermissions[role as Role];
return granted !== undefined && granted.includes(permission);
}
// Lambda ハンドラでの認可チェック
if (!hasPermission(user.role, 'write')) {
return { statusCode: 403, body: JSON.stringify({ error: 'Forbidden' }) };
}
認証基盤のグループを見て判定する場合は、値の形を確かめてから比較する。グループの一覧は配列で届く経路と、区切り文字で連結された 1 つの文字列で届く経路がある。文字列に対する includes は部分一致なので、admins や non-admin というグループしか持たない利用者でも includes('admin') が真になり、権限が漏れる。
function toGroups(claim: unknown): string[] {
if (Array.isArray(claim)) return claim.map(String);
// 連結文字列で届く経路 (区切りは空白・カンマ・角括弧のいずれもあり得る)
if (typeof claim === 'string') return claim.split(/[\s,[\]]+/).filter(Boolean);
return [];
}
const groups = toGroups(event.requestContext.authorizer?.claims?.['cognito:groups']);
const isAdmin = groups.includes('admin'); // 配列に対する includes は完全一致
配列に正規化してから includes を呼べば完全一致になる。同じ関数名でも文字列に対しては部分一致、配列に対しては完全一致という言語側の違いが、認可の判定では致命的な差になる。
RBAC の設計指針
最小権限の原則に従い、必要最小限の権限のみを付与する。ロールの粒度は細かすぎず粗すぎないバランスを取り、viewer < editor < admin のように権限の継承関係を明確にする。ただし継承は上の例のような対応表では表現されない。階層を入れるなら、権限を持つロールから継承元をたどって集める処理が必要になり、循環参照の検出も自分で用意することになる。階層を諦めて上位ロールの権限を書き並べる方が、規模が小さいうちは読みやすい。
不要なロールが蓄積しないよう、定期的に棚卸しを行う。棚卸しで見るのは「使われていないロール」だけではない。1 人しか割り当てられていないロールは、役割ではなく個人向けの例外が紛れ込んだ印である。逆に、ほぼ全員が持っているロールは既定の権限として扱った方が管理が減る。
落とし穴
ロールを増やす判断は戻しにくい。既に配ったロールを統合するには、誰の権限が減るかを洗い出して個別に合意を取る必要があり、増やすときの手軽さと釣り合わない。条件が 1 つ増えるたびにロールを分ける運用は、この非対称性を無視している。
権限のチェックを 1 か所に集めることも忘れやすい。画面の出し分けだけで済ませると、API を直接叩かれた時点で無効になる。表示の制御と認可の判定は別物で、後者はサーバー側で必ず通す経路に置く。
この記事は役に立ちましたか?
関連用語
IAM (概念)
Identity and Access Management の一般概念で、認証と認可を管理する仕組み
IAM
AWS のアクセス管理サービスで、誰が何のリソースにどのような操作を行えるかを制御する
OpenID Connect
OAuth 2.0 の上に構築された認証レイヤーで、ユーザーの身元情報を ID トークンとして提供する
最小権限の原則
ユーザーやプログラムに、タスクの遂行に必要な最小限の権限のみを付与するセキュリティ原則
脅威モデリング
システムに対する潜在的な脅威を体系的に特定・評価し、対策を設計するセキュリティ手法
ABAC
ユーザー、リソース、環境の属性に基づいてアクセス制御を動的に判断する認可モデル