feat(pipeline): keycloak-session-store 25편·virtualization 59편을 S3→S5→S6 으로 돌린다
기록 84편을 계약 에이전트로 다시 썼다. 기존 71편(kss 25 · virt 46)과, 계약에만 있고 안 쓰여 있던 새 글감 13편이다. 원장 84개를 열어 단계마다 스킬 영수증과 관문 종료 코드를 적었고 verify-pipeline-run.py 가 error 0 으로 닫는다. SSOT 결함 둘을 고쳤다. - kss 의 `약 58일` 이 반입 중 `약 59일` 로 바뀌어 있었다. 원 증거 파일이 「남은 일수: 88일 … 실제 갱신까지 약 58일」로 산수를 직접 적는다. D-4a 쪽 `약 59일` 은 강제 갱신 뒤(`VALID: 89 days`)라 맞는 값이라 그대로 뒀다. - virt §198 의 `11.6GB` 는 §178 의 원 측정 `Mem: 11648`(MiB)과 어긋나는데 원 가이드의 표기 그대로라 고치지 않고 쓰이는 자리에 대조를 적었다. 기록의 수치 오류 셋을 고쳤다 — CASE 요약의 「게스트 셋에 8240MB」(5120+3120 은 둘이다), k3s 편이 같은 것을 여섯·일곱·여덟로 세던 것, no-docker 편의 「셋을 더 든다」(§281 의 표는 네 행이고 디스크 행이 빠져 있었다). 계약을 셋 고쳤다. - kss 의 sourceRepository 리비전이 cdac9b8 이었는데 그 커밋에는 docs/guides/** 28개가 아예 없다. 9465582b 로 바꾸고, 반입한 바이트가 어느 커밋과도 같지 않다는 것을 측정값과 함께 적었다 — 반입은 커밋이 아니라 그 시점의 작업 트리에서 떠 온 것이다(kss 297/306 · virt 12/14 가 작업 트리와 같고, 200 커밋을 거슬러 전수 대조했을 때 가장 가까운 커밋도 28개가 어긋났다). - virt 계약이 「2026-09-11 재배분」이라고 적는데 SSOT 는 재배분 날짜를 적지 않고 재배분 뒤 값은 이미 2026-09-10 측정에 찍혀 있다. - kss 후보 대장이 지나친 절 아홉에 처분을 적었다(warn 9 → 0). 새 글감은 0건이고 넷은 앵커가 h3 슬러그의 접두가 아니라 중간 토막이라 검사기가 못 본 것이었다. style_profile.mjs 의 결함 둘을 고쳤다 — frontmatter 가 문장으로 세어져 (실측 398자짜리 「문장」 하나) 평균 길이를 기준 안으로 밀어 올리고 있었고, engPerSent 의 분자는 목록을 포함한 글에서, 분모는 목록을 걷어낸 글에서 세고 있었다(Question 기록에서 11.94 → 3.86). verify-pipeline.py 전 항목 PASS · error 0 · unittest 334건 OK. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
d473609e0a
commit
2109f726fe
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,289 @@
|
||||
{
|
||||
"version": "1.1",
|
||||
"id": "nftables-forward-hook-chain-order",
|
||||
"title": "같은 forward 훅에 붙은 base 체인 둘이 우선순위 순으로 이어서 평가되고, 앞 체인의 accept 는 뒤 체인의 reject 를 막지 못한다",
|
||||
"question": "밖에서 온 packet 하나가 forward 훅에서 어느 규칙을 어떤 순서로 지나 어디서 끝났고, insert 로 넣은 구멍은 그 순서의 어디에 들어가는가",
|
||||
"type": "network",
|
||||
"direction": "LR",
|
||||
"audience": [
|
||||
"iptables 감각으로 nftables 규칙을 쓰다 밖에서만 막히는 것을 보는 사람",
|
||||
"libvirt NAT 뒤의 게스트에 밖에서 들어오는 경로를 여는 사람"
|
||||
],
|
||||
"summary": "밖에서 온 packet 은 priority filter - 10 인 forward 체인의 ct state new accept 를 지나고도 평가가 끝나지 않아 libvirt_network guest_input 으로 이어지고, 거기서 체인 끝 reject 에 닿아 connection refused 가 되며, insert 로 넣은 구멍은 같은 체인의 맨 앞에 서서 같은 packet 을 엣지 nginx 로 보낸다.",
|
||||
"alt": "밖에서 온 packet 이 forward 훅에 붙은 base 체인 둘을 우선순위 순으로 지나는 경로도. 앞 체인의 accept 뒤에 libvirt guest_input 이 이어지고, 구멍을 넣기 전 경로는 그 체인 끝 reject 로, 넣은 뒤 경로는 체인 맨 앞의 구멍을 지나 엣지 nginx 로 갈라진다.",
|
||||
"long_description": "왼쪽에서 오른쪽으로 읽는다. 왼쪽 끝이 밖에서 친 curl 이 만든 inbound packet 이고 forward 훅으로 들어간다. 첫 점선 상자가 DNAT 파일에 둔 base 체인이다. priority filter - 10 이라 먼저 돌고, 그 안의 ct state new accept 가 이 packet 을 통과시킨다. 다음 점선 상자가 libvirt 가 만든 ip libvirt_network 테이블의 guest_input 체인이다. 앞 체인의 accept 는 평가를 끝내지 않으므로 같은 packet 이 이 체인으로 이어진다. 그 안의 상자 둘은 같은 체인의 맨 앞과 맨 끝이다. 구멍을 넣기 전에는 맨 앞이 비어 있어 packet 이 ct state established,related accept 에 걸리지 못한 채 체인 끝 reject 에 닿았고 connection refused 로 끝났다. 그 reject 규칙의 카운터가 4 패킷 240 바이트다. insert 로 구멍을 넣으면 그 규칙이 맨 앞에 서서 같은 packet 을 먼저 받아 192.168.122.10 의 엣지 nginx 로 보낸다. 실선이 구멍을 넣은 뒤의 경로이고 점선이 넣기 전의 경로다. 이 그림은 그 규칙이 libvirt 네트워크를 다시 세우면 사라진다는 것은 말하지 않는다.",
|
||||
"source_context": {
|
||||
"document": "docs/virtualization/final/document.md",
|
||||
"document_sha256": "60d902c7aed218ba637b48603a3bd0e6b193fea59c9de97ffdb8e05d5087aedd",
|
||||
"anchor": {
|
||||
"kind": "heading",
|
||||
"value": "180. nftables 는 앞 체인의 `accept` 로 뒤 체인의 `reject` 를 막지 못한다",
|
||||
"line": 7805
|
||||
}
|
||||
},
|
||||
"composition": {
|
||||
"profile": "component-flow",
|
||||
"diagram_only": true,
|
||||
"reference_ids": [
|
||||
"payment-event-flow"
|
||||
],
|
||||
"rationale": "이 절이 세운 것은 방향이 있는 경로 하나다 — 밖에서 온 packet 이 base 체인 둘을 우선순위 순으로 지나 두 결말 가운데 하나에 닿는다. 두 체인은 소유가 다른 실제 containment 라 groups 로 둘렀다. comparison 은 관계선을 지워도 뜻이 남는 자리의 문법인데 여기서는 지우면 「앞 체인의 accept 를 지나고도 뒤 체인에 닿는다」가 통째로 사라져 규칙 목록만 남는다. sequence 는 주고받는 참가자가 둘 이상일 때의 문법이고 여기서 움직이는 것은 packet 하나뿐이다.",
|
||||
"focus_node": "tail-reject"
|
||||
},
|
||||
"groups": [
|
||||
{
|
||||
"id": "our-forward-chain",
|
||||
"label": "forward · priority filter - 10",
|
||||
"kind": "system",
|
||||
"role": "zone",
|
||||
"description": "DNAT 를 정의한 파일에 둔 base 체인. 같은 훅에서 libvirt 체인보다 먼저 돈다.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 7829,
|
||||
"end_line": 7830
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "libvirt-guest-input",
|
||||
"label": "ip libvirt_network · guest_input",
|
||||
"kind": "system",
|
||||
"role": "zone",
|
||||
"description": "libvirt 가 자기 테이블 안에 만드는 base 체인. 같은 훅에서 뒤에 돈다.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 7818,
|
||||
"end_line": 7824
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
}
|
||||
],
|
||||
"nodes": [
|
||||
{
|
||||
"id": "inbound-packet",
|
||||
"label": "inbound packet",
|
||||
"kind": "packet",
|
||||
"role": "source",
|
||||
"shape": "box",
|
||||
"details": [
|
||||
"curl http://100.83.212.4"
|
||||
],
|
||||
"description": "밖에서 친 curl 이 만든 packet. 호스트가 직접 듣는 리스너가 없어 FORWARD 로 간다.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 7814,
|
||||
"end_line": 7814
|
||||
},
|
||||
{
|
||||
"start_line": 7809,
|
||||
"end_line": 7809
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "our-accept",
|
||||
"label": "ct state new accept",
|
||||
"kind": "rule",
|
||||
"role": "service",
|
||||
"shape": "box",
|
||||
"group": "our-forward-chain",
|
||||
"description": "우리가 먼저 돌게 해 둔 체인의 규칙. 이 체인은 통과시키지만 평가를 끝내지는 않는다.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 7829,
|
||||
"end_line": 7833
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "inserted-accept",
|
||||
"label": "inserted accept",
|
||||
"kind": "rule",
|
||||
"role": "service",
|
||||
"shape": "box",
|
||||
"group": "libvirt-guest-input",
|
||||
"emphasis": "primary",
|
||||
"details": [
|
||||
"nft insert rule",
|
||||
"daddr 192.168.122.10",
|
||||
"tcp dport {80,443}"
|
||||
],
|
||||
"description": "insert 로 guest_input 맨 앞에 넣은 구멍. add 로 넣으면 맨 뒤라 reject 뒤에 선다.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 7835,
|
||||
"end_line": 7840
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "tail-reject",
|
||||
"label": "reject",
|
||||
"kind": "rule",
|
||||
"role": "service",
|
||||
"shape": "box",
|
||||
"group": "libvirt-guest-input",
|
||||
"emphasis": "primary",
|
||||
"details": [
|
||||
"chain tail",
|
||||
"counter packets 4",
|
||||
"bytes 240"
|
||||
],
|
||||
"description": "guest_input 체인을 끝내는 규칙. 앞의 ct state established,related accept 에 걸리지 못한 packet 이 여기 닿는다. 이 카운터가 밖에서 친 curl 횟수와 일치해 범인 확정에 쓰였다.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 7821,
|
||||
"end_line": 7823
|
||||
},
|
||||
{
|
||||
"start_line": 7826,
|
||||
"end_line": 7827
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "edge-nginx",
|
||||
"label": "edge nginx",
|
||||
"kind": "service",
|
||||
"role": "sink",
|
||||
"shape": "box",
|
||||
"details": [
|
||||
"192.168.122.10"
|
||||
],
|
||||
"description": "게스트에서 도는 엣지. 호스트에서 친 요청에는 이미 404 로 응답하고 있었다.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 7813,
|
||||
"end_line": 7813
|
||||
},
|
||||
{
|
||||
"start_line": 7839,
|
||||
"end_line": 7840
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "connection-refused",
|
||||
"label": "connection refused",
|
||||
"kind": "result",
|
||||
"role": "sink",
|
||||
"shape": "box",
|
||||
"description": "drop 이 아니라 reject 라 기다리지 않고 즉시 돌아온 결과.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 7814,
|
||||
"end_line": 7814
|
||||
},
|
||||
{
|
||||
"start_line": 7816,
|
||||
"end_line": 7816
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "packet-to-our-chain",
|
||||
"from": "inbound-packet",
|
||||
"to": "our-accept",
|
||||
"label": "forward hook",
|
||||
"kind": "data",
|
||||
"style": "solid",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 7814,
|
||||
"end_line": 7814
|
||||
},
|
||||
{
|
||||
"start_line": 7829,
|
||||
"end_line": 7830
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "our-chain-after-insert",
|
||||
"from": "our-accept",
|
||||
"to": "inserted-accept",
|
||||
"label": "after insert",
|
||||
"kind": "data",
|
||||
"style": "solid",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 7831,
|
||||
"end_line": 7833
|
||||
},
|
||||
{
|
||||
"start_line": 7835,
|
||||
"end_line": 7836
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "our-chain-before-insert",
|
||||
"from": "our-accept",
|
||||
"to": "tail-reject",
|
||||
"label": "before insert",
|
||||
"kind": "data",
|
||||
"style": "dashed",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 7831,
|
||||
"end_line": 7833
|
||||
},
|
||||
{
|
||||
"start_line": 7821,
|
||||
"end_line": 7823
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "inserted-to-edge",
|
||||
"from": "inserted-accept",
|
||||
"to": "edge-nginx",
|
||||
"label": "accept",
|
||||
"kind": "result",
|
||||
"style": "solid",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 7835,
|
||||
"end_line": 7840
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "reject-to-refused",
|
||||
"from": "tail-reject",
|
||||
"to": "connection-refused",
|
||||
"label": "reject",
|
||||
"kind": "result",
|
||||
"style": "dashed",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 7823,
|
||||
"end_line": 7823
|
||||
},
|
||||
{
|
||||
"start_line": 7814,
|
||||
"end_line": 7816
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
}
|
||||
],
|
||||
"legend": [],
|
||||
"metadata": {
|
||||
"rationale": "network 으로 고른 이유는 이 절이 답하는 물음이 「packet 하나가 어느 규칙을 어떤 순서로 지나는가」이기 때문이다. 상자는 체인이 아니라 규칙 하나씩이다 — 체인을 상자로 두면 「앞 체인 accept 다음에 뒤 체인」이라는 자리 관계가 안 보이고, 그 자리가 이 사건의 전부다. 실선은 구멍을 넣은 뒤의 경로이고 점선은 넣기 전의 경로다. 색이 아니라 선 모양으로 갈랐다. guest_input 의 ct state established,related accept 는 상자로 세우지 않았다 — 세우면 그 체인 그룹이 두 칸을 차지하고 bbox 가 그룹 밖인 엣지 nginx 를 삼켜 엣지가 libvirt 체인 안에 있는 것처럼 보였다(렌더 확인). 그 규칙의 원문은 기록 본문 코드블록에 그대로 있다. ExecStartPost 로 규칙을 되살리는 수명은 시간 축이라 본문에 두었다."
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
# 같은 forward 훅에 붙은 base 체인 둘이 우선순위 순으로 이어서 평가되고, 앞 체인의 accept 는 뒤 체인의 reject 를 막지 못한다
|
||||
|
||||
## Alternative text
|
||||
|
||||
밖에서 온 packet 이 forward 훅에 붙은 base 체인 둘을 우선순위 순으로 지나는 경로도. 앞 체인의 accept 뒤에 libvirt guest_input 이 이어지고, 구멍을 넣기 전 경로는 그 체인 끝 reject 로, 넣은 뒤 경로는 체인 맨 앞의 구멍을 지나 엣지 nginx 로 갈라진다.
|
||||
|
||||
## Long description
|
||||
|
||||
왼쪽에서 오른쪽으로 읽는다. 왼쪽 끝이 밖에서 친 curl 이 만든 inbound packet 이고 forward 훅으로 들어간다. 첫 점선 상자가 DNAT 파일에 둔 base 체인이다. priority filter - 10 이라 먼저 돌고, 그 안의 ct state new accept 가 이 packet 을 통과시킨다. 다음 점선 상자가 libvirt 가 만든 ip libvirt_network 테이블의 guest_input 체인이다. 앞 체인의 accept 는 평가를 끝내지 않으므로 같은 packet 이 이 체인으로 이어진다. 그 안의 상자 둘은 같은 체인의 맨 앞과 맨 끝이다. 구멍을 넣기 전에는 맨 앞이 비어 있어 packet 이 ct state established,related accept 에 걸리지 못한 채 체인 끝 reject 에 닿았고 connection refused 로 끝났다. 그 reject 규칙의 카운터가 4 패킷 240 바이트다. insert 로 구멍을 넣으면 그 규칙이 맨 앞에 서서 같은 packet 을 먼저 받아 192.168.122.10 의 엣지 nginx 로 보낸다. 실선이 구멍을 넣은 뒤의 경로이고 점선이 넣기 전의 경로다. 이 그림은 그 규칙이 libvirt 네트워크를 다시 세우면 사라진다는 것은 말하지 않는다.
|
||||
|
||||
## Elements and evidence
|
||||
|
||||
- **Boundary: forward · priority filter - 10** (system): DNAT 를 정의한 파일에 둔 base 체인. 같은 훅에서 libvirt 체인보다 먼저 돈다. Evidence: L7829–L7830.
|
||||
- **Boundary: ip libvirt_network · guest_input** (system): libvirt 가 자기 테이블 안에 만드는 base 체인. 같은 훅에서 뒤에 돈다. Evidence: L7818–L7824.
|
||||
- **inbound packet** (packet): 밖에서 친 curl 이 만든 packet. 호스트가 직접 듣는 리스너가 없어 FORWARD 로 간다. Evidence: L7814–L7814, L7809–L7809.
|
||||
- **ct state new accept** (rule): 우리가 먼저 돌게 해 둔 체인의 규칙. 이 체인은 통과시키지만 평가를 끝내지는 않는다. Evidence: L7829–L7833.
|
||||
- **inserted accept** (rule): insert 로 guest_input 맨 앞에 넣은 구멍. add 로 넣으면 맨 뒤라 reject 뒤에 선다. Evidence: L7835–L7840.
|
||||
- **reject** (rule): guest_input 체인을 끝내는 규칙. 앞의 ct state established,related accept 에 걸리지 못한 packet 이 여기 닿는다. 이 카운터가 밖에서 친 curl 횟수와 일치해 범인 확정에 쓰였다. Evidence: L7821–L7823, L7826–L7827.
|
||||
- **edge nginx** (service): 게스트에서 도는 엣지. 호스트에서 친 요청에는 이미 404 로 응답하고 있었다. Evidence: L7813–L7813, L7839–L7840.
|
||||
- **connection refused** (result): drop 이 아니라 reject 라 기다리지 않고 즉시 돌아온 결과. Evidence: L7814–L7814, L7816–L7816.
|
||||
|
||||
## Relationships
|
||||
|
||||
- **inserted accept → edge nginx:** accept. Evidence: L7835–L7840.
|
||||
- **ct state new accept → inserted accept:** after insert. Evidence: L7831–L7833, L7835–L7836.
|
||||
- **ct state new accept → reject:** before insert. Evidence: L7831–L7833, L7821–L7823.
|
||||
- **inbound packet → ct state new accept:** forward hook. Evidence: L7814–L7814, L7829–L7830.
|
||||
- **reject → connection refused:** reject. Evidence: L7823–L7823, L7814–L7816.
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
# 같은 forward 훅에 붙은 base 체인 둘이 우선순위 순으로 이어서 평가되고, 앞 체인의 accept 는 뒤 체인의 reject 를 막지 못한다
|
||||
# Question: 밖에서 온 packet 하나가 forward 훅에서 어느 규칙을 어떤 순서로 지나 어디서 끝났고, insert 로 넣은 구멍은 그 순서의 어디에 들어가는가
|
||||
direction: right
|
||||
g0: "forward · priority filter - 10" {
|
||||
n1: "ct state new accept" {
|
||||
shape: rectangle
|
||||
}
|
||||
}
|
||||
g1: "ip libvirt_network · guest_input" {
|
||||
n2: "inserted accept" {
|
||||
shape: rectangle
|
||||
}
|
||||
n3: "reject" {
|
||||
shape: rectangle
|
||||
}
|
||||
}
|
||||
n0: "inbound packet" {
|
||||
shape: rectangle
|
||||
}
|
||||
n4: "edge nginx" {
|
||||
shape: rectangle
|
||||
}
|
||||
n5: "connection refused" {
|
||||
shape: rectangle
|
||||
}
|
||||
n0 -> g0.n1: "forward hook"
|
||||
g0.n1 -> g1.n2: "after insert"
|
||||
g0.n1 -> g1.n3: "before insert"
|
||||
g1.n2 -> n4: "accept"
|
||||
g1.n3 -> n5: "reject"
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<mxfile host="app.diagrams.net" modified="2026-07-23T00:00:00.000Z" agent="techviz-harness" version="24.7.17" type="device">
|
||||
<diagram id="nftables-forward-hook-chain-order" name="같은 forward 훅에 붙은 base 체인 둘이 우선순위 순으로 이어서 평가되고, 앞 체인의 accept 는 뒤 체인의 reject 를 막지 못한다">
|
||||
<mxGraphModel dx="1298" dy="434" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1298" pageHeight="1169" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0"/>
|
||||
<mxCell id="1" parent="0"/>
|
||||
<mxCell id="g_our-forward-chain" value="forward · priority filter - 10" style="swimlane;html=1;rounded=1;startSize=30;horizontal=1;dashed=1;strokeWidth=1.5;fillColor=#f7f9fb;strokeColor=#66788a;fontStyle=1;fontSize=13;" vertex="1" parent="1">
|
||||
<mxGeometry x="402.0" y="144.0" width="227.0" height="136.0" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="g_libvirt-guest-input" value="ip libvirt_network · guest_input" style="swimlane;html=1;rounded=1;startSize=30;horizontal=1;dashed=1;strokeWidth=1.5;fillColor=#f7f9fb;strokeColor=#66788a;fontStyle=1;fontSize=13;" vertex="1" parent="1">
|
||||
<mxGeometry x="729.0" y="35.0" width="234.0" height="354.0" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="n_inbound-packet" value="inbound packet<br/>curl http://100.83.212.4" tooltip="밖에서 친 curl 이 만든 packet. 호스트가 직접 듣는 리스너가 없어 FORWARD 로 간다. | Evidence: L7814-L7814, L7809-L7809" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="70.0" y="186.5" width="202.0" height="71.0" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="n_our-accept" value="ct state new accept" tooltip="우리가 먼저 돌게 해 둔 체인의 규칙. 이 체인은 통과시키지만 평가를 끝내지는 않는다. | Evidence: L7829-L7833" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="432.0" y="190.0" width="167.0" height="64.0" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="n_inserted-accept" value="inserted accept<br/>nft insert rule<br/>daddr 192.168.122.10<br/>tcp dport {80,443}" tooltip="insert 로 guest_input 맨 앞에 넣은 구멍. add 로 넣으면 맨 뒤라 reject 뒤에 선다. | Evidence: L7835-L7840" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;strokeColor=#2563eb;strokeWidth=2;" vertex="1" parent="1">
|
||||
<mxGeometry x="759.0" y="81.0" width="174.0" height="105.0" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="n_tail-reject" value="reject<br/>chain tail<br/>counter packets 4<br/>bytes 240" tooltip="guest_input 체인을 끝내는 규칙. 앞의 ct state established,related accept 에 걸리지 못한 packet 이 여기 닿는다. 이 카운터가 밖에서 친 curl 횟수와 일치해 범인 확정에 쓰였다. | Evidence: L7821-L7823, L7826-L7827" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;strokeColor=#2563eb;strokeWidth=2;" vertex="1" parent="1">
|
||||
<mxGeometry x="769.5" y="258.0" width="153.0" height="105.0" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="n_edge-nginx" value="edge nginx<br/>192.168.122.10" tooltip="게스트에서 도는 엣지. 호스트에서 친 요청에는 이미 404 로 응답하고 있었다. | Evidence: L7813-L7813, L7839-L7840" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="1098.0" y="118.5" width="150.0" height="71.0" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="n_connection-refused" value="connection refused" tooltip="drop 이 아니라 reject 라 기다리지 않고 즉시 돌아온 결과. | Evidence: L7814-L7814, L7816-L7816" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="1093.0" y="261.5" width="160.0" height="64.0" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="e_inserted-to-edge" value="accept" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_inserted-accept" target="n_edge-nginx">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="1039.5" y="143.8" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="e_our-chain-after-insert" value="after insert" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_our-accept" target="n_inserted-accept">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="703.0" y="173.2" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="e_our-chain-before-insert" value="before insert" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_our-accept" target="n_tail-reject">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="708.2" y="270.8" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="e_packet-to-our-chain" value="forward hook" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_inbound-packet" target="n_our-accept">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="352.0" y="194.0" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="e_reject-to-refused" value="reject" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_tail-reject" target="n_connection-refused">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="1031.8" y="302.0" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
+1060
File diff suppressed because it is too large
Load Diff
+31
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"harness_version": "0.2.0",
|
||||
"spec_id": "nftables-forward-hook-chain-order",
|
||||
"spec_version": "1.1",
|
||||
"spec_sha256": "7c412e3a6278b75551ce56f35693dd236d0b3bf0962c2788a87703123f009f9f",
|
||||
"source_context": {
|
||||
"document": "docs/virtualization/final/document.md",
|
||||
"document_sha256": "60d902c7aed218ba637b48603a3bd0e6b193fea59c9de97ffdb8e05d5087aedd",
|
||||
"anchor": {
|
||||
"kind": "heading",
|
||||
"value": "180. nftables 는 앞 체인의 `accept` 로 뒤 체인의 `reject` 를 막지 못한다",
|
||||
"line": 7805
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
"nftables-forward-hook-chain-order.svg",
|
||||
"nftables-forward-hook-chain-order.drawio",
|
||||
"nftables-forward-hook-chain-order.mmd",
|
||||
"nftables-forward-hook-chain-order.d2",
|
||||
"nftables-forward-hook-chain-order.excalidraw",
|
||||
"nftables-forward-hook-chain-order.alt.md"
|
||||
],
|
||||
"lint_issue_count": 0,
|
||||
"assumption_count": 0,
|
||||
"assumptions_allowed": false,
|
||||
"composition_profile": "component-flow",
|
||||
"reference_ids": [
|
||||
"payment-event-flow"
|
||||
],
|
||||
"diagram_only": true
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
%% 같은 forward 훅에 붙은 base 체인 둘이 우선순위 순으로 이어서 평가되고, 앞 체인의 accept 는 뒤 체인의 reject 를 막지 못한다
|
||||
%% question: 밖에서 온 packet 하나가 forward 훅에서 어느 규칙을 어떤 순서로 지나 어디서 끝났고, insert 로 넣은 구멍은 그 순서의 어디에 들어가는가
|
||||
flowchart LR
|
||||
subgraph g_our_forward_chain["forward · priority filter - 10"]
|
||||
n1["ct state new accept"]
|
||||
end
|
||||
subgraph g_libvirt_guest_input["ip libvirt_network · guest_input"]
|
||||
n2["inserted accept"]
|
||||
n3["reject"]
|
||||
end
|
||||
n0["inbound packet"]
|
||||
n4["edge nginx"]
|
||||
n5["connection refused"]
|
||||
n0 -->|"forward hook"| n1
|
||||
n1 -->|"after insert"| n2
|
||||
n1 -->|"before insert"| n3
|
||||
n2 -->|"accept"| n4
|
||||
n3 -->|"reject"| n5
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1298" height="434" viewBox="0 0 1298 434" role="img" aria-labelledby="diagram-title diagram-description">
|
||||
<title id="diagram-title">같은 forward 훅에 붙은 base 체인 둘이 우선순위 순으로 이어서 평가되고, 앞 체인의 accept 는 뒤 체인의 reject 를 막지 못한다</title>
|
||||
<desc id="diagram-description">왼쪽에서 오른쪽으로 읽는다. 왼쪽 끝이 밖에서 친 curl 이 만든 inbound packet 이고 forward 훅으로 들어간다. 첫 점선 상자가 DNAT 파일에 둔 base 체인이다. priority filter - 10 이라 먼저 돌고, 그 안의 ct state new accept 가 이 packet 을 통과시킨다. 다음 점선 상자가 libvirt 가 만든 ip libvirt_network 테이블의 guest_input 체인이다. 앞 체인의 accept 는 평가를 끝내지 않으므로 같은 packet 이 이 체인으로 이어진다. 그 안의 상자 둘은 같은 체인의 맨 앞과 맨 끝이다. 구멍을 넣기 전에는 맨 앞이 비어 있어 packet 이 ct state established,related accept 에 걸리지 못한 채 체인 끝 reject 에 닿았고 connection refused 로 끝났다. 그 reject 규칙의 카운터가 4 패킷 240 바이트다. insert 로 구멍을 넣으면 그 규칙이 맨 앞에 서서 같은 packet 을 먼저 받아 192.168.122.10 의 엣지 nginx 로 보낸다. 실선이 구멍을 넣은 뒤의 경로이고 점선이 넣기 전의 경로다. 이 그림은 그 규칙이 libvirt 네트워크를 다시 세우면 사라진다는 것은 말하지 않는다.</desc>
|
||||
<metadata>{"techviz":{"spec_version":"1.1","id":"nftables-forward-hook-chain-order","profile":"component-flow"},"source_context":{"document":"docs/virtualization/final/document.md","document_sha256":"60d902c7aed218ba637b48603a3bd0e6b193fea59c9de97ffdb8e05d5087aedd","anchor":{"kind":"heading","value":"180. nftables 는 앞 체인의 `accept` 로 뒤 체인의 `reject` 를 막지 못한다","line":7805}},"evidence_policy":"Each factual element cites source lines or is marked assumption.","diagram_only":true}</metadata>
|
||||
<defs>
|
||||
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" />
|
||||
</marker>
|
||||
<style>
|
||||
:root { color-scheme: light; }
|
||||
text { font-family: Inter, Pretendard, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #111827; }
|
||||
.canvas { fill: #ffffff; }
|
||||
.group-box { fill: #ffffff; stroke: #9ca3af; stroke-width: 1.4; stroke-dasharray: 7 5; }
|
||||
.group-label-bg { fill: #ffffff; }
|
||||
.group-label { font-size: 13px; font-weight: 650; fill: #374151; }
|
||||
.edge { fill: none; stroke: #374151; stroke-width: 1.8; stroke-linejoin: round; stroke-linecap: round; marker-end: url(#arrow); }
|
||||
.edge.style-dashed, .edge.semantic-dashed, .edge.assumption { stroke-dasharray: 7 5; }
|
||||
.edge.style-dotted { stroke-dasharray: 2 5; }
|
||||
.edge.emphasis-primary { stroke: #2563eb; stroke-width: 2.2; }
|
||||
.edge.emphasis-muted { stroke: #9ca3af; }
|
||||
.edge.emphasis-warning, .edge.kind-failure, .edge.kind-error { stroke: #dc2626; stroke-width: 2.2; }
|
||||
.edge-label-bg { fill: #ffffff; }
|
||||
.edge-label { font-size: 12px; font-weight: 560; text-anchor: middle; }
|
||||
.node-shape { fill: #ffffff; stroke: #4b5563; stroke-width: 1.7; }
|
||||
.node-shape.emphasis-primary { stroke: #2563eb; stroke-width: 2.2; }
|
||||
.node-shape.emphasis-muted { stroke: #9ca3af; fill: #f9fafb; }
|
||||
.node-shape.emphasis-warning { stroke: #d97706; stroke-width: 2; fill: #fffdf5; }
|
||||
.node-shape.kind-database, .node-shape.kind-datastore, .node-shape.kind-storage { fill: #f8fafc; }
|
||||
.node-shape.kind-queue, .node-shape.kind-event, .node-shape.kind-topic { fill: #fafafa; }
|
||||
.node-shape.assumption { stroke-dasharray: 4 4; }
|
||||
.storage-bottom, .controller-divider { fill: none; stroke: #4b5563; stroke-width: 1.4; }
|
||||
.controller-led { fill: #4b5563; }
|
||||
.actor-symbol { fill: none; stroke: #4b5563; stroke-width: 1.8; stroke-linecap: round; }
|
||||
.actor-symbol.emphasis-primary { stroke: #2563eb; stroke-width: 2.2; }
|
||||
.node-label { font-size: 14px; font-weight: 650; text-anchor: middle; }
|
||||
.node-role { font-size: 10px; letter-spacing: 0.04em; text-anchor: middle; fill: #6b7280; }
|
||||
.node-detail-divider { stroke: #d1d5db; stroke-width: 1; }
|
||||
.node-detail { font-size: 11px; fill: #374151; }
|
||||
.assumption-badge { font-size: 9px; font-weight: 700; fill: #92400e; }
|
||||
.failure-mark { stroke: #dc2626; stroke-width: 4; stroke-linecap: round; }
|
||||
.lifeline { stroke: #9ca3af; stroke-width: 1.2; stroke-dasharray: 5 5; }
|
||||
.timeline-axis { stroke: #374151; stroke-width: 1.8; marker-end: url(#arrow); }
|
||||
.timeline-stem { stroke: #6b7280; stroke-width: 1.3; }
|
||||
.timeline-marker { fill: #ffffff; stroke: #374151; stroke-width: 1.7; }
|
||||
.timeline-marker.primary { fill: #2563eb; stroke: #2563eb; }
|
||||
.timeline-marker.warning { fill: #dc2626; stroke: #dc2626; }
|
||||
.timeline-label { font-size: 13px; font-weight: 650; text-anchor: middle; }
|
||||
.timeline-detail { font-size: 11px; fill: #4b5563; text-anchor: middle; }
|
||||
</style>
|
||||
</defs>
|
||||
<rect class="canvas" width="1298" height="434" />
|
||||
<rect class="group-box" x="402.0" y="144.0" width="227.0" height="136.0" rx="8" />
|
||||
<rect class="group-label-bg" x="416.0" y="134.0" width="232.0" height="22" />
|
||||
<text class="group-label" x="426.0" y="149.0">forward · priority filter - 10</text>
|
||||
<rect class="group-box" x="729.0" y="35.0" width="234.0" height="354.0" rx="8" />
|
||||
<rect class="group-label-bg" x="743.0" y="25.0" width="246.0" height="22" />
|
||||
<text class="group-label" x="753.0" y="40.0">ip libvirt_network · guest_input</text>
|
||||
<polyline class="edge kind-result style-solid emphasis-normal" points="933.0,133.5 1015.5,133.5 1015.5,154.0 1098.0,154.0" data-evidence="7835-7840" />
|
||||
<rect class="edge-label-bg" x="1010.4" y="129.8" width="58.2" height="22" rx="3" />
|
||||
<text class="edge-label" x="1039.5" y="144.8">accept</text>
|
||||
<polyline class="edge kind-data style-solid emphasis-normal" points="599.0,213.0 679.0,213.0 679.0,133.5 759.0,133.5" data-evidence="7831-7833,7835-7836" />
|
||||
<rect class="edge-label-bg" x="653.8" y="159.2" width="98.4" height="22" rx="3" />
|
||||
<text class="edge-label" x="703.0" y="174.2">after insert</text>
|
||||
<polyline class="edge kind-data style-dashed emphasis-normal" points="599.0,231.0 684.2,231.0 684.2,310.5 769.5,310.5" data-evidence="7831-7833,7821-7823" />
|
||||
<rect class="edge-label-bg" x="655.7" y="256.8" width="105.1" height="22" rx="3" />
|
||||
<text class="edge-label" x="708.2" y="271.8">before insert</text>
|
||||
<polyline class="edge kind-data style-solid emphasis-normal" points="272.0,222.0 352.0,222.0 352.0,222.0 432.0,222.0" data-evidence="7814-7814,7829-7830" />
|
||||
<rect class="edge-label-bg" x="302.8" y="180.0" width="98.4" height="22" rx="3" />
|
||||
<text class="edge-label" x="352.0" y="195.0">forward hook</text>
|
||||
<polyline class="edge kind-result style-dashed emphasis-normal" points="922.5,310.5 1007.8,310.5 1007.8,293.5 1093.0,293.5" data-evidence="7823-7823,7814-7816" />
|
||||
<rect class="edge-label-bg" x="1002.6" y="288.0" width="58.2" height="22" rx="3" />
|
||||
<text class="edge-label" x="1031.8" y="303.0">reject</text>
|
||||
<g id="node-inbound-packet">
|
||||
<rect class="node-shape kind-packet emphasis-normal role-source" data-evidence="7814-7814,7809-7809" x="70.0" y="186.5" width="202.0" height="71.0" rx="7" />
|
||||
<text class="node-label" x="171.0" y="213.5">inbound packet</text>
|
||||
<line class="node-detail-divider" x1="84.0" y1="234.5" x2="258.0" y2="234.5" />
|
||||
<text class="node-detail" x="86.0" y="251.5">curl http://100.83.212.4</text>
|
||||
</g>
|
||||
<g id="node-our-accept">
|
||||
<rect class="node-shape kind-rule emphasis-normal role-service" data-evidence="7829-7833" x="432.0" y="190.0" width="167.0" height="64.0" rx="7" />
|
||||
<text class="node-label" x="515.5" y="220.0">ct state new accept</text>
|
||||
</g>
|
||||
<g id="node-inserted-accept">
|
||||
<rect class="node-shape kind-rule emphasis-primary role-service" data-evidence="7835-7840" x="759.0" y="81.0" width="174.0" height="105.0" rx="7" />
|
||||
<text class="node-label" x="846.0" y="108.0">inserted accept</text>
|
||||
<line class="node-detail-divider" x1="773.0" y1="129.0" x2="919.0" y2="129.0" />
|
||||
<text class="node-detail" x="775.0" y="146.0">nft insert rule</text>
|
||||
<text class="node-detail" x="775.0" y="162.0">daddr 192.168.122.10</text>
|
||||
<text class="node-detail" x="775.0" y="178.0">tcp dport {80,443}</text>
|
||||
</g>
|
||||
<g id="node-tail-reject">
|
||||
<rect class="node-shape kind-rule emphasis-primary role-service" data-evidence="7821-7823,7826-7827" x="769.5" y="258.0" width="153.0" height="105.0" rx="7" />
|
||||
<text class="node-label" x="846.0" y="285.0">reject</text>
|
||||
<line class="node-detail-divider" x1="783.5" y1="306.0" x2="908.5" y2="306.0" />
|
||||
<text class="node-detail" x="785.5" y="323.0">chain tail</text>
|
||||
<text class="node-detail" x="785.5" y="339.0">counter packets 4</text>
|
||||
<text class="node-detail" x="785.5" y="355.0">bytes 240</text>
|
||||
</g>
|
||||
<g id="node-edge-nginx">
|
||||
<rect class="node-shape kind-service emphasis-normal role-sink" data-evidence="7813-7813,7839-7840" x="1098.0" y="118.5" width="150.0" height="71.0" rx="7" />
|
||||
<text class="node-label" x="1173.0" y="145.5">edge nginx</text>
|
||||
<line class="node-detail-divider" x1="1112.0" y1="166.5" x2="1234.0" y2="166.5" />
|
||||
<text class="node-detail" x="1114.0" y="183.5">192.168.122.10</text>
|
||||
</g>
|
||||
<g id="node-connection-refused">
|
||||
<rect class="node-shape kind-result emphasis-normal role-sink" data-evidence="7814-7814,7816-7816" x="1093.0" y="261.5" width="160.0" height="64.0" rx="7" />
|
||||
<text class="node-label" x="1173.0" y="291.5">connection refused</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 9.7 KiB |
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user