DNSフィルタリングとWebプロキシ
名前解決、プロキシ、復号、配送のどこで処理したかを記録から説明します。
説明
DNSフィルタリングは名前解決の応答を制御し、SWGはWeb通信の経路上で宛先や要求を評価します。それぞれの通信が、設定した制御点を通るかを確認します。
URLのホストとパス、DNS応答の状態、明示プロキシへの要求を順に開きます。最後に固定の経路記録で、管理外の経路を探します。
名前解決、プロキシ、復号、配送のどこで処理したかを記録から説明します。
1URLをホストとパスへ分解する
同じホストの異なるパスを比較します。
from sp05 import *
from sp02 import show
db = connect()
for u in ('https://docs.example.test/manual','https://docs.example.test/private'):
p=urlsplitURLをホストとパスへ分解する(u);print(p.hostname,p.path)DNSへ問い合わせるホスト名は同じです。DNSの名前単位の制御だけで/manualと/privateを選び分けることはできません。
2DNSヘッダーの応答コードを取り出す
固定ヘッダーのflags下位4ビットがRCODEです。
header = bytes.fromhex('123481850001000000000000')
ident,flags,qd,an,ns,ar = struct.unpackDNSヘッダーの応答コードを取り出す('!6H',header)
print(hex(ident),flags&15,qd,an)RCODE 5はREFUSED、回答数は0です。これは拒否を示す固定例で、フィルタリング製品によってはNXDOMAINやブロックページのアドレスを返す方式もあります。
3HTTPプロキシへ渡す要求を開く
暗号化しないHTTPを明示プロキシへ送る場合の要求例です。
proxy_request = b'GET http://docs.example.test/manual HTTP/1.1\r\nHost: docs.example.test\r\n\r\n'
print(proxy_request.decodeHTTPプロキシへ渡す要求を開く(),end='')要求行に絶対URIが含まれます。プロキシはホストとパスの両方を受け取れます。
4HTTPSのトンネル要求と比較する
HTTPSではまずCONNECTで接続先を指定する形があります。
connect_request = b'CONNECT docs.example.test:443 HTTP/1.1\r\nHost: docs.example.test:443\r\n\r\n'
print(connect_request.decodeHTTPSのトンネル要求と比較する(),end='')この要求には/manualがありません。トンネル内のTLSを終端しなければ、このCONNECT行から内部のHTTPパスは分かりません。
5経路別の結果を表示する
resolverとproxyを結果と一緒に読みます。
show経路別の結果を表示する(db,"SELECT * FROM route ORDER BY id")r2は社内DNSで拒否されています。一方r3は外部resolverと直接接続を使い、同じ制限対象ホストへ到達した記録です。
6制御点を通らない通信を抽出する
社内DNSまたはSWGを使わなかった行です。
show制御点を通らない通信を抽出する(db,"SELECT id,resolver,proxy,result FROM route WHERE resolver<>'corp' OR proxy<>'swg'")r3が該当します。分類表にrestrictedと書かれているだけでは、経路外の通信を制御できません。
7カテゴリと実際の結果を集計する
制限カテゴリでも結果が一つでないことを確認します。
showカテゴリと実際の結果を集計する(db,"SELECT category,result,COUNT(*) AS requests FROM route GROUP BY category,result ORDER BY category,result")restrictedに配送とDNS拒否の両方があります。DoHも名前解決の別経路になり得るため、利用するresolverと通信経路を合わせて調べます。
まとめ
DNSは名前、HTTPプロキシは要求の情報を扱います。
CONNECTだけでは内部のパスは分かりません。
ポリシーだけでなく制御点を通る経路を確認します。
操作履歴と解説を振り返れます。
Python 3.15 / TLS MemoryBIO / 固定の経路・配送記録
from sp05 import *
from sp02 import show
db = connect()
for u in ('https://docs.example.test/manual','https://docs.example.test/private'):
p=urlsplit(u);print(p.hostname,p.path)docs.example.test /manual docs.example.test /private
header = bytes.fromhex('123481850001000000000000')
ident,flags,qd,an,ns,ar = struct.unpack('!6H',header)
print(hex(ident),flags&15,qd,an)0x1234 5 1 0
proxy_request = b'GET http://docs.example.test/manual HTTP/1.1\r\nHost: docs.example.test\r\n\r\n'
print(proxy_request.decode(),end='')GET http://docs.example.test/manual HTTP/1.1 Host: docs.example.test
connect_request = b'CONNECT docs.example.test:443 HTTP/1.1\r\nHost: docs.example.test:443\r\n\r\n'
print(connect_request.decode(),end='')CONNECT docs.example.test:443 HTTP/1.1 Host: docs.example.test:443
show(db,"SELECT * FROM route ORDER BY id")id | host | resolver | proxy | category | result r1 | docs.example.test | corp | swg | reference | delivered r2 | other.example.test | corp | swg | restricted | dns_refused r3 | other.example.test | external | direct | restricted | delivered
show(db,"SELECT id,resolver,proxy,result FROM route WHERE resolver<>'corp' OR proxy<>'swg'")id | resolver | proxy | result r3 | external | direct | delivered
show(db,"SELECT category,result,COUNT(*) AS requests FROM route GROUP BY category,result ORDER BY category,result")category | result | requests reference | delivered | 1 restricted | delivered | 1 restricted | dns_refused | 1
演習完了