DLPの送信先制御と監査ログ
識別条件、読める範囲、宛先別の制御と監査記録を分けて評価します。
説明
同じ機微情報でも、社内共有と外部公開では許可条件が異なります。警告時の上書き承認、配送を止める条件、ログへ残す情報の範囲を合わせて設計します。
固定の配送待ちDBへ文書を送る処理を使い、許可、警告、理由付き上書き、遮断を試します。監査記録をマスクしつつ、内容の同一性を照合する値も確認します。
識別条件、読める範囲、宛先別の制御と監査記録を分けて評価します。
1宛先と処理の規則を読む
team、partner、publicの3宛先です。
from sp10 import *
from sp02 import show宛先と処理の規則を読む
db=connect()
show(db,"SELECT * FROM policy ORDER BY destination")teamはallow、partnerはwarn、publicはblockです。この試料では登録値を含む文書に適用します。
2社内向けの配送待ちを作る
同じ本文をteamへ渡します。
print(send社内向けの配送待ちを作る(db,'team',TEXT))
show(db,"SELECT id,destination FROM outbox ORDER BY id")allowとなりoutboxへ1件入ります。これは配送待ちへの登録であり、受信先への配送完了とは別の段階です。
3警告だけでは配送待ちへ追加しない
partnerへ同じ文書を渡します。
print(send警告だけでは配送待ちへ追加しない(db,'partner',TEXT))
show(db,"SELECT COUNT(*) AS queued FROM outbox")warnで件数は1のままです。警告を表示しただけで処理を通すのか、確認まで止めるのかは規則として明確にします。
4理由を必須にした上書きを試す
空の理由と、確認内容を添えた理由を比較します。
print(send(db,'partner',TEXT,override=True理由を必須にした上書きを試す))
print(send(db,'partner',TEXT,override=True,reason='approved sample transfer'))
show(db,"SELECT id,destination FROM outbox ORDER BY id")空の理由ではwarn、理由ありでallow_overrideとなり2件目が追加されます。実運用では承認者や共有契約などの条件も合わせて確認します。
5公開宛先の遮断を上書きできないことを確認する
publicへoverrideを指定してもblock条件を優先します。
print(send公開宛先の遮断を上書きできないことを確認する(db,'public',TEXT,override=True,reason='requested'))
print(send(db,'unknown',TEXT))
show(db,"SELECT COUNT(*) AS queued FROM outbox")どちらもblockで配送待ちは2件のままです。未登録宛先も既定で拒否しています。
6監査ログで値をマスクする
処理結果、理由、マスク済み本文を表示します。
show監査ログで値をマスクする(db,"SELECT destination,action,reason,masked FROM audit ORDER BY id")独立したIDはEMP-****に置換されます。ただし境界条件で検査対象外だった埋込み文字列は残ります。検知用の範囲とログ秘匿用の範囲を同じにすると、このような漏れが起こり得ます。
7ログ用のマスク範囲を広げる
保存済みマスク欄の埋込みIDも消します。
for ident,masked in db.execute("SELECT id,masked FROM audit").fetchall():
db.execute("UPDATE audit SET masked=? WHERE id=?",(re.subログ用のマスク範囲を広げる(r'EMP-[0-9]{4}','EMP-****',masked),ident))
show(db,"SELECT DISTINCT masked FROM audit")XEMP-****Zとなり、生の4桁も残らなくなります。ログの秘匿は検知の判定と別に点検します。
8本文を表示せず同じ内容の試行を結ぶ
公開試験鍵のHMACで一致を照合します。
show(db,"SELECT COUNT(*) AS attempts,COUNT(DISTINCT digest) AS contents FROM audit")
print(hmac.compare_digest本文を表示せず同じ内容の試行を結ぶ(hmac.new(PUBLIC_KEY,TEXT.encode(),hashlib.sha256).hexdigest(),db.execute("SELECT digest FROM audit LIMIT 1").fetchone()[0]))6試行とも同じ値です。HMAC値を使って記録を関連付けられるため、この値もアクセス管理の対象にします。
まとめ
警告、上書き、遮断の適用条件を確認します。
配送待ちと配送完了を分けて追います。
ログのマスク範囲は検知条件とは別に点検します。
操作履歴と解説を振り返れます。
Python 3.15 / AES-GCM / SQLiteの配送待ちモデル
from sp10 import *
from sp02 import show
db=connect()
show(db,"SELECT * FROM policy ORDER BY destination")destination | action partner | warn public | block team | allow
print(send(db,'team',TEXT))
show(db,"SELECT id,destination FROM outbox ORDER BY id")allow id | destination 1 | team
print(send(db,'partner',TEXT))
show(db,"SELECT COUNT(*) AS queued FROM outbox")warn queued 1
print(send(db,'partner',TEXT,override=True))
print(send(db,'partner',TEXT,override=True,reason='approved sample transfer'))
show(db,"SELECT id,destination FROM outbox ORDER BY id")warn allow_override id | destination 1 | team 2 | partner
print(send(db,'public',TEXT,override=True,reason='requested'))
print(send(db,'unknown',TEXT))
show(db,"SELECT COUNT(*) AS queued FROM outbox")block block queued 2
show(db,"SELECT destination,action,reason,masked FROM audit ORDER BY id")destination | action | reason | masked team | allow | | owner=EMP-****; sample=EMP-****; embedded=XEMP-0042Z partner | warn | | owner=EMP-****; sample=EMP-****; embedded=XEMP-0042Z partner | warn | | owner=EMP-****; sample=EMP-****; embedded=XEMP-0042Z partner | allow_override | approved sample transfer | owner=EMP-****; sample=EMP-****; embedded=XEMP-0042Z public | block | requested | owner=EMP-****; sample=EMP-****; embedded=XEMP-0042Z unknown | block | | owner=EMP-****; sample=EMP-****; embedded=XEMP-0042Z
for ident,masked in db.execute("SELECT id,masked FROM audit").fetchall():
db.execute("UPDATE audit SET masked=? WHERE id=?",(re.sub(r'EMP-[0-9]{4}','EMP-****',masked),ident))
show(db,"SELECT DISTINCT masked FROM audit")masked owner=EMP-****; sample=EMP-****; embedded=XEMP-****Z
show(db,"SELECT COUNT(*) AS attempts,COUNT(DISTINCT digest) AS contents FROM audit")
print(hmac.compare_digest(hmac.new(PUBLIC_KEY,TEXT.encode(),hashlib.sha256).hexdigest(),db.execute("SELECT digest FROM audit LIMIT 1").fetchone()[0]))attempts | contents 6 | 1 True
演習完了