障害時のアクセス制御と更新処理
判定できない状態を許可と混同せず、障害時に保つべき状態を説明します。
説明
認可サービスへ問い合わせられないとき、許可した場合と拒否した場合では保護する性質が変わります。また、更新途中で監査記録に失敗したときも、途中の状態を残すか戻すか決める必要があります。
残高を10減らす固定操作で、許可・拒否・タイムアウトを比較します。認可障害時の方針と、業務更新・監査記録のトランザクションを変え、保存状態まで確かめます。
判定できない状態を許可と混同せず、障害時に保つべき状態を説明します。
1残高と監査記録の初期状態
残高は100、監査記録は0件です。許可された操作だけ10減らす規則にします。
from sc12 import *
db = failure_db()独立した残高と監査表を作る
print(db.execute("SELECT amount FROM balance").fetchone())
print(db.execute("SELECT COUNT(*) FROM audit").fetchone())操作の成否を文字列だけでなく、残高と監査記録の両方で確認します。
2正常な許可で処理する
認可結果allowを得てから、残高と監査を同じトランザクションで更新します。
print(operation(db,"allow","secure")許可された操作を確定する)
print(db.execute("SELECT amount FROM balance").fetchone(),db.execute("SELECT COUNT(*) FROM audit").fetchone())committed、残高90、監査1件です。許可結果に対応する更新が残っています。
3明示的な拒否を受ける
認可サービスがdenyを返します。
print(operation(db,"deny","open")明示的な拒否と障害を分ける)
print(db.execute("SELECT amount FROM balance").fetchone(),db.execute("SELECT COUNT(*) FROM audit").fetchone())残高90、監査1件を保ちます。障害時にopenを選ぶ設定でも、明示的な拒否まで許可へ変える設計ではありません。
4認可障害を許可として扱う
downはTimeoutErrorを発生させます。openの方針では、この例の処理を続けます。
print(operation(db,"down","open")タイムアウト時にも処理を続ける)
print(db.execute("SELECT amount FROM balance").fetchone(),db.execute("SELECT COUNT(*) FROM audit").fetchone())許可を確認できていないのに残高80、監査2件へ進みました。サービス継続と引換えに、認可できない主体の操作も通る可能性があります。
5認可障害を拒否として扱う
同じタイムアウトでsecureを指定します。
print(operation(db,"down","secure")判定不能時に操作を拒否する)
print(db.execute("SELECT amount FROM balance").fetchone(),db.execute("SELECT COUNT(*) FROM audit").fetchone())残高80、監査2件を保ちます。保護状態は保てますが、正当な利用者もこの操作を続けられません。対象操作ごとに必要な継続性を検討します。
6更新後の監査書込みで失敗する
認可は成功しますが、残高更新の後で監査の障害を発生させます。
print(operation(db,"allow","secure",fail_audit=True業務更新後に監査障害を発生させる))
print(db.execute("SELECT amount FROM balance").fetchone(),db.execute("SELECT COUNT(*) FROM audit").fetchone())rolled_backで残高80、監査2件です。トランザクションにより、減額だけが残る状態を防ぎました。外部の監査サービスと自動で同じ原子性になるわけではありません。
7復旧後に一度確定する
認可と監査が正常になった条件で実行します。
print(operation(db,"allow","secure")復旧後の処理を確定する)
print(db.execute("SELECT amount FROM balance").fetchone())
print(db.execute("SELECT id,operation FROM audit確定した監査記録を照合する ORDER BY id").fetchall())残高70、監査3件です。前の失敗分は残っていません。応答喪失などで成功済み操作を再試行するケースには、別途要求IDによる重複対策も必要です。
まとめ
明示的な拒否と判定不能を別の状態として扱います。
障害時の許可・拒否が保護と継続性へ与える影響を確認します。
業務更新と付随記録の途中失敗でも、一貫した保存状態を保ちます。
操作履歴と解説を振り返れます。
Python 3.15 / cryptography 50.0.2 / 公開試料と固定時刻
from sc12 import *
db = failure_db()
print(db.execute("SELECT amount FROM balance").fetchone())
print(db.execute("SELECT COUNT(*) FROM audit").fetchone())(100,) (0,)
print(operation(db,"allow","secure"))
print(db.execute("SELECT amount FROM balance").fetchone(),db.execute("SELECT COUNT(*) FROM audit").fetchone())committed (90,) (1,)
print(operation(db,"deny","open"))
print(db.execute("SELECT amount FROM balance").fetchone(),db.execute("SELECT COUNT(*) FROM audit").fetchone())denied (90,) (1,)
print(operation(db,"down","open"))
print(db.execute("SELECT amount FROM balance").fetchone(),db.execute("SELECT COUNT(*) FROM audit").fetchone())committed (80,) (2,)
print(operation(db,"down","secure"))
print(db.execute("SELECT amount FROM balance").fetchone(),db.execute("SELECT COUNT(*) FROM audit").fetchone())denied (80,) (2,)
print(operation(db,"allow","secure",fail_audit=True))
print(db.execute("SELECT amount FROM balance").fetchone(),db.execute("SELECT COUNT(*) FROM audit").fetchone())rolled_back (80,) (2,)
print(operation(db,"allow","secure"))
print(db.execute("SELECT amount FROM balance").fetchone())
print(db.execute("SELECT id,operation FROM audit ORDER BY id").fetchall())committed (70,) [(1, 'debit-10'), (2, 'debit-10'), (3, 'debit-10')]
演習完了