RBAC

ロール (役割) に基づいてアクセス権限を管理する認可モデル

セキュリティ認可
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 で切り、例外条件だけを属性で絞る形が多い。

観点RBACABAC
基準ロール (役割)属性 (部署、時間、IP)
柔軟性中程度高い
複雑さ低い高い
admin, editor, viewer部署=営業 AND 時間=営業時間
AWSIAM ロールタグを条件に使う 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 は部分一致なので、adminsnon-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 を直接叩かれた時点で無効になる。表示の制御と認可の判定は別物で、後者はサーバー側で必ず通す経路に置く。

この記事は役に立ちましたか?

関連用語

関連する記事