一覧SD-06-3 · 認可とテナント分離

認可キャッシュと代理アクセス

古い許可が残る時間と、代理操作で使える権限の上限を説明します。

説明

認可結果をキャッシュすると、権限を消しても古い許可が残る場合があります。またサービスが利用者に代わって動く場合、サービス自身の権限をそのまま利用者へ渡さないよう、委譲範囲も確認します。

固定の許可を60秒キャッシュし、権限失効後の違いを調べます。版番号を含むキャッシュへ切り替えた後、代理サービス・利用者・委譲の三つの条件をSQLで照合します。

古い許可が残る時間と、代理操作で使える権限の上限を説明します。

1同じ許可を二種類のキャッシュへ入れる

plainは期限だけ、versionedは主体の権限版番号もキーに含めます。

from sd06 import * db = database() plain = Cached(db) versioned = Cached(db) print(plain.allowed期限だけのキャッシュへ入れる("alice",7,"update",0)) print(versioned.allowed版番号付きのキャッシュへ入れる("alice",7,"update",0,True))

どちらもTrueを時刻60まで保持します。今は元の権限表にupdateが存在します。

2権限と版番号を同じ確定処理で変更する

aliceのupdateを取り消し、版番号を1から2へ進めます。

with db: db.execute("DELETE FROM grants更新権限を失効させる WHERE subject='alice' AND action='update'") db.execute("UPDATE epochs SET version=version+1同じ処理で権限版を進める WHERE subject='alice'") print(db.execute("SELECT * FROM epochs WHERE subject='alice'").fetchone())

版番号は2です。権限だけを変更して版を更新し忘れると、版付きキャッシュでも古い許可を使い続ける原因になります。

3失効直後の判定を比較する

時刻1では旧キャッシュの期限前です。

print(plain.allowed古い許可が残る判定を確認する("alice",7,"update",1)) print(versioned.allowed現在版のキーで再評価する("alice",7,"update",1,True)) print(edit(db,"alice",7更新時の現在権限を照合する,"forbidden"))

期限だけのキャッシュはTrue、版付きはFalseです。DBの現在権限を含む更新も0行で拒否します。

4旧キャッシュの期限境界を確認する

時刻60で期限だけのキャッシュも再評価します。

print(plain.allowed("alice",7,"update",60旧キャッシュの期限で評価する)) print(db.execute("SELECT body FROM docs WHERE id=7").fetchone())

Falseへ変わり、文書はred-secretのままです。キャッシュTTLは失効反映の最大遅延にも関わります。

5代理するサービスと利用者を照合する

serviceにはread/updateがありますが、aliceからの委譲は文書7のreadだけです。

print(db.execute("SELECT * FROM delegations").fetchall()) print(delegated(db,"service","alice",7,"read")サービス・利用者・委譲の共通範囲を検査する) print(delegated(db,"service","alice",7,"update")委譲していない操作を比較する)

readはTrue、updateはFalseです。サービス自身が持つ強い権限を、代理操作でそのまま使っていません。

6別主体への代理に流用しない

同じserviceがbobの代理を名乗る場合と、別資源へ使う場合です。

print(delegated(db,"service","bob"委譲元の主体を変える,7,"read")) print(delegated(db,"service","alice",8対象資源を変える,"read"))

どちらもFalseです。委譲には誰の代理か、対象資源、操作を含めて結び付けます。

7委譲を残しても本人の権限失効を反映する

alice自身の文書7のreadを消します。委譲の行は残しています。

db.execute("DELETE FROM grants本人の読取り権限を消す WHERE subject='alice' AND doc=7 AND action='read'") print(delegated(db,"service","alice",7,"read")残存する委譲を現在権限と照合する) print(db.execute("SELECT * FROM delegations").fetchall())

Falseです。委譲の記録だけでなく、本人とサービスの現在権限を併せて評価する規則になっています。

まとめ

キャッシュの期限と失効反映の遅延を対応付けます。

版番号を使う場合は権限変更と一体で更新します。

代理操作はサービス・本人・委譲範囲のすべてを確認します。

操作履歴と解説を振り返れます。

Python 3.15 / SQLite / 固定の主体・資源・時刻

1
from sd06 import * db = database() plain = Cached(db) versioned = Cached(db) print(plain.allowed("alice",7,"update",0)) print(versioned.allowed("alice",7,"update",0,True))
True
True
2
with db: db.execute("DELETE FROM grants WHERE subject='alice' AND action='update'") db.execute("UPDATE epochs SET version=version+1 WHERE subject='alice'") print(db.execute("SELECT * FROM epochs WHERE subject='alice'").fetchone())
('alice', 2)
3
print(plain.allowed("alice",7,"update",1)) print(versioned.allowed("alice",7,"update",1,True)) print(edit(db,"alice",7,"forbidden"))
True
False
0
4
print(plain.allowed("alice",7,"update",60)) print(db.execute("SELECT body FROM docs WHERE id=7").fetchone())
False
('red-secret',)
5
print(db.execute("SELECT * FROM delegations").fetchall()) print(delegated(db,"service","alice",7,"read")) print(delegated(db,"service","alice",7,"update"))
[('service', 'alice', 7, 'read')]
True
False
6
print(delegated(db,"service","bob",7,"read")) print(delegated(db,"service","alice",8,"read"))
False
False
7
db.execute("DELETE FROM grants WHERE subject='alice' AND doc=7 AND action='read'") print(delegated(db,"service","alice",7,"read")) print(db.execute("SELECT * FROM delegations").fetchall())
False
[('service', 'alice', 7, 'read')]

演習完了