職務分離と最小権限
役割を持つだけでなく、操作の相手や承認対象との関係、委譲する権限の範囲を確認します。
説明
職務分離は、単独で申請から承認まで完結できないように、相反する操作を別の主体へ分ける設計です。ケイパビリティは、特定の資源と操作に使える権限を持つトークンです。広い権限を丸ごと渡す代わりに範囲を限定できます。
申請者aliceと承認者bobの支払い記録を使い、自分の申請を承認できない条件を確認します。次に公開試料鍵で文書7のread・期限60という権限を署名し、対象・操作・時刻・改変を変えて検証します。
役割を持つだけでなく、操作の相手や承認対象との関係、委譲する権限の範囲を確認します。
1申請と承認可能な主体
aliceもbobも承認者表にいますが、申請7の作成者はaliceです。役割を持つだけでは自己承認を防げません。
from sc03 import approvals, sign, permits
db = approvals()申請と承認者の固定資料を用意する
print(db.execute("SELECT * FROM payments申請者と状態を読む").fetchall())
print(db.execute("SELECT * FROM approvers承認役割を持つ主体を読む").fetchall())100の支払い申請はpendingです。これからcreatorと承認操作の主体を比較します。
2自分の申請を承認しようとする
承認役割の存在に加えてcreatorと異なることをUPDATE条件へ入れます。
approve = "UPDATE payments SET status='approved',approver=? WHERE id=? AND status='pending' AND creator<>?申請者と承認主体が異なることを要求する AND EXISTS(SELECT 1 FROM approvers WHERE name=?)"
print(db.execute(approvealiceとして承認する, ("alice",7,"alice","alice")).rowcount)
print(db.execute("SELECT status,approver FROM payments拒否後の保存状態を読む").fetchone())更新0件でpendingのままです。aliceが承認役割を持っていても、自分の申請を承認する条件は満たしません。
3別の承認者が確定する
bobの承認は申請者と異なり、承認者表にも存在します。
print(db.execute(approvebobとして承認する, ("bob",7,"bob","bob")).rowcount)
print(db.execute("SELECT * FROM payments承認主体と確定状態を読む").fetchall())更新1件でapproved、approver=bobになります。実行主体だけでなく、申請者との関係も検査できました。
4限定した権限を作る
文書7のreadを時刻60より前だけ許可する権限を署名します。公開試料鍵なので実運用の秘密には使いません。
cap =対象・操作・期限を限定する {"resource":7,"action":"read","expires":60}
tag = sign(cap)限定した内容へ認証タグを付ける
print(cap)委譲する内容を読む
print(tag)内容の認証タグを読むトークンが表すのは文書7のreadだけです。この例は保有者へ使える権限を渡すbearer型であり、利用者IDへの拘束は含めていません。
5範囲内と範囲外の操作
時刻10で、文書7のread、文書8のread、文書7のdeleteを比較します。
print(permits(cap, tag, "read", 7, 10)範囲内の読取りを確認する)
print(permits(cap, tag, "read", 8, 10)対象外の文書を要求する)
print(permits(cap, tag, "delete", 7, 10)操作範囲外の削除を要求する)True、False、Falseです。一つの権限を保有しても、対象や操作を自由に広げられるわけではありません。
6期限ちょうどでは使えない
expires=60は、now<60の場合だけ有効という条件です。
print(permits(cap, tag, "read", 7, 59)期限直前を確認する)
print(permits(cap, tag, "read", 7, 60)期限ちょうどを確認する)59ではTrue、60ではFalseです。境界の等号を含め、期限条件を明示しました。
7内容の書換えを検出する
resourceを8へ変えても、元のタグでは署名対象が一致しません。
changed =権限の対象を文書8へ書き換える {**cap,"resource":8}
print(permits(changed, tag, "read", 8, 10)元のタグで改変後の権限を検証する)Falseです。対象の限定と改変検知は別の条件として検査しています。紛失・漏えい後の失効を即時反映したい場合は、この期限だけの例に追加の仕組みが必要です。
まとめ
承認役割だけでなく、申請者と承認者の関係を検査すると自己承認を防げます。
委譲する権限は資源・操作・期限などで限定できます。
トークンの署名、権限範囲、期限はそれぞれ別の受入条件です。
操作履歴と解説を振り返れます。
Python 3.15 / SQLite / 明示した読取り認可モデル
from sc03 import approvals, sign, permits
db = approvals()
print(db.execute("SELECT * FROM payments").fetchall())
print(db.execute("SELECT * FROM approvers").fetchall())[(7, 'alice', 100, 'pending', None)]
[('alice',), ('bob',)]approve = "UPDATE payments SET status='approved',approver=? WHERE id=? AND status='pending' AND creator<>? AND EXISTS(SELECT 1 FROM approvers WHERE name=?)"
print(db.execute(approve, ("alice",7,"alice","alice")).rowcount)
print(db.execute("SELECT status,approver FROM payments").fetchone())0
('pending', None)print(db.execute(approve, ("bob",7,"bob","bob")).rowcount)
print(db.execute("SELECT * FROM payments").fetchall())1 [(7, 'alice', 100, 'approved', 'bob')]
cap = {"resource":7,"action":"read","expires":60}
tag = sign(cap)
print(cap)
print(tag){'resource': 7, 'action': 'read', 'expires': 60}
76e383bf7186ab60f871c8735e2ec84ee02510a5e37253a068c63e06d219dfc2print(permits(cap, tag, "read", 7, 10))
print(permits(cap, tag, "read", 8, 10))
print(permits(cap, tag, "delete", 7, 10))True False False
print(permits(cap, tag, "read", 7, 59))
print(permits(cap, tag, "read", 7, 60))True False
changed = {**cap,"resource":8}
print(permits(changed, tag, "read", 8, 10))False
演習完了