obsidian: 정본에서 변경 이력 분리, history/ 도입

- history/README.md: 변경 이력/인시던트 기록 규약
- history/2026-04-10-edge-cleanup.md: 오늘의 엣지 정리·인시던트·복구 전체 연대기
- infra/cloudflare.md: 연대기 주석/strikethrough/인시던트 서사 제거, 현재 사실만
- infra/crowdsec-safeline.md: 인시던트 bullet 제거, 과거 이력은 history/ 참조
- services/bunnycdn-security.md: sitekey 이력표 제거, 현재 위젯 정보만
- infra/nas-storage.md: reverse proxy 섹션의 날짜 주석 제거
This commit is contained in:
kappa
2026-04-10 11:38:36 +09:00
parent d16090a33d
commit 72750cfc9d
6 changed files with 407 additions and 209 deletions

View File

@@ -0,0 +1,225 @@
---
date: 2026-04-10
topic: Edge layer cleanup (BunnyCDN+Cloudflare 전수 감사·정리·인시던트·복구)
areas:
- infra/cloudflare.md
- infra/crowdsec-safeline.md
- infra/nas-storage.md
- services/bunnycdn-security.md
tags: [history, edge, bunnycdn, cloudflare, crowdsec, incident]
---
하루 동안 여러 건의 엣지 레이어 정리 작업이 연쇄적으로 일어남. 감사 → 1차 정리 → nas 복원 → cleanup-2 시도 → 3-incident chain → 복구 → cron 영구 제거 → iron-kr IgnoreQS 통일 순서.
## 1. BunnyCDN+CF 도메인 전수 감사
### 배경
Obsidian 정본의 CF/BunnyCDN 정보가 오래되어 현재 상태와 불일치. 전체 도메인 인벤토리 재구축 필요.
### 조치
Syn 가 BunnyCDN MCP + CF MCP 로 실측 조회.
결과: BunnyCDN 풀존 5개 / 호스트 17개 (사용자 12 + 시스템 5). CF zone 6개 / DNS 60건 / Turnstile 8개 / Workers 9개. 상세는 Outline doc `51b963c3-b251-48b5-a57a-a2305959c470`.
Obsidian 정본 (`infra/cloudflare.md`, `services/bunnycdn-security.md`) 을 현재 상태로 전면 재작성.
### 발견된 조치 필요 항목
- `servidor.it.com` — CF zone 이지만 DNS 0건 (orphan zone). cert-manager wildcard + CrowdSec bouncer + Turnstile 위젯 설정은 살아있음. 죽은 도메인이 아니라 "배포 준비만 된 상태" 로 판단, 유지 결정.
- `nas.inouter.com` CNAME → `actions.b-cdn.net` — dead 풀존 참조
- `ironclad.jp.inouter.com` A `10.19.228.193`, `k8s.jp.inouter.com` A `10.253.103.124` — 사설 IP 공개 DNS 노출
- `*.actions.it.com` CNAME → `actions.b-cdn.net` — dead 풀존 참조
- `inouter.com` / `anvil.it.com` CF Worker bouncer 라우트가 DNS `proxied: false` 라서 실제 enforcement 안 됨 — BunnyCDN 미들웨어 64811 단독 방어
- `vultr.actions.it.com/*`, `linode.actions.it.com/*` Worker 라우트 `script: null` (orphan)
- Turnstile `inouter` (`0x4AAAAAACbmaudAjITah7y7`, 2026-02-13) — 이름·도메인 불일치 (이름은 inouter 인데 도메인은 anvil.it.com). legacy 후보
- iron-kr `IgnoreQueryStrings: true` vs iron-jp `false` — 같은 미들웨어인데 캐시 키 정책 불일치
- BunnyCDN 미들웨어 `TURNSTILE_SITE_KEY``inouter.com` zone 단일 키 → actions / anvil / `*.b-cdn.net` 에서 ban 발동 시 캡차 로드 실패 가능
## 2. CF DNS 정리 1차 (`cf-audit-cleanup-1`)
### 조치
Syn 가 CF API 로 아래 레코드 삭제:
- `nas.inouter.com` CNAME → `actions.b-cdn.net`
- `ironclad.jp.inouter.com` A `10.19.228.193`
- `k8s.jp.inouter.com` A `10.253.103.124`
검증: `dig @1.1.1.1` 로 3건 모두 삭제 확인.
### 참고
`nas.inouter.com``*.inouter.com` 와일드카드 (`→ k3s.inouter.com → 192.168.9.53`) 로 폴백되지만 LAN 전용 MetalLB VIP 라서 외부 접근 불가 — 실질적으로 의도한 결과.
## 3. `nas.inouter.com` Traefik 리버스 프록시 복원
### 배경
kappa 가 nas.inouter.com 을 Synology NAS (`192.168.9.100`) 의 DSM 접근 경로로 쓰고 싶어함.
### 조치
Heimdall 이 K3s 에 resources 생성:
- Namespace `lan-proxies` (LAN 장비 reverse proxy 전용)
- Selector-less Service `nas` (ClusterIP 5000/TCP) + 수동 EndpointSlice `192.168.9.100:5000`
- Traefik IngressRoute `nas` (web) + `nas-tls` (websecure), Middleware `redirect-https`
- TLS: 기존 `wildcard-inouter-tls` 재사용
### 기술 노트
`ExternalName` Service 는 IP 를 받지 않고 DNS name 만 받음. LAN IP reverse proxy 에는 **selector-less Service + 수동 EndpointSlice** 패턴이 정석.
### 검증
`curl -sk --resolve nas.inouter.com:443:192.168.9.53 https://nas.inouter.com/` → 200, `<title>NAS - Synology DiskStation</title>`. HTTP 301 redirect 정상.
## 4. CF cleanup-2 3-incident chain
### 배경
감사에서 발견한 "inouter/anvil 의 CF Worker bouncer 라우트가 dead" 항목 정리. BunnyCDN 미들웨어가 단독 방어하고 있으니 CF 측 라우트와 위젯을 제거해 관리 면적 축소 목적.
### 인시던트 #1 — destructive widget cleanup
Syn 가 `cfb-manager` API `DELETE /domains/inouter.com` 호출. cfb-manager 는 bouncer 호스트의 yaml 을 편집하고 `systemctl restart` 호출. 재기동된 bouncer 가 **destructive cleanup** 으로 모든 zone 의 Turnstile 위젯을 새로 rotate 하면서 기존 위젯을 삭제. 이 과정에서 **BunnyCDN 미들웨어 64811 이 baked-in 으로 사용 중이던 `inouter.com` zone 위젯 `0x4AAAAAAC2cntUlRC3KKMKG` 가 함께 삭제**. 잠재 버그 (현재 ban 없으면 미발현).
잘못된 가정: "zone 을 bouncer config 에서 빼면 위젯은 freeze 된 채 남는다" — 실제로는 bouncer restart 가 force-rotation 사이클.
### 인시던트 #2 — 복구 중 토큰 권한 공백
복구 시도. 새 Turnstile 위젯 생성 필요. Obsidian 에 "현 CF API 토큰은 Turnstile read-only" 기록 → kappa 가 CF 대시보드에서 수동 생성 경로로 먼저 제안. 이후 "API 가 있는데 왜 대시보드?" 피드백 → jp1 `cs-cf-worker-bouncer` 의 CF 토큰 (`seUKZID4...`) 을 SSH 경유로 확인, 이 토큰이 **Turnstile read+write** 권한 보유 확인. 해당 토큰을 일시 차용해 API 로 새 위젯 생성:
- sitekey: `0x4AAAAAAC3otPWhldI96Aks`
- secret: `0x4AAAAAAC3otP9NIYkJUVYdMjNLZsbWgpM`
- name: `inouter-bunny-middleware` (bouncer auto 이름 패턴 `crowdsec-cloudflare-worker-bouncer-widget` 과 다름 → bouncer rotation 에서 freeze 보장)
- domains: `[inouter.com]`, mode: managed
Vault `secret/cloud/cloudflare/turnstile-inouter-bunny``sitekey` / `secret` / `name` 저장. Syn 가 kappa 게이트웨이 ASK 프로토콜로 값을 받아 BunnyCDN 미들웨어 64811 env 변수 `TURNSTILE_SITE_KEY` / `TURNSTILE_SECRET_KEY` 갱신 후 publish (deployment `3816dbc5-...`, HTTP 204).
### 인시던트 #3 — cron-induced crashloop
Syn 가 `DONE: cf-audit-cleanup-2-recovery` 회신. kappa 검증 결과:
- `bouncer-managed` 위젯 4개 (keepanker / actions / ironclad / servidor) **사라짐** → CF Worker enforcement 4개 zone 다운
- bouncer service **48회 crashloop** (`yaml: line 57: mapping values are not allowed`)
- 4개 zone 의 CF Worker 라우트 부재
- `cfb-manager /status` 도 YAML 파싱 에러로 500
Syn 의 narrative ("cfb-manager /status 200, bouncer_running") 는 **false positive** — cfb-manager 의 in-memory 상태나 일시적 normal window 만 보고 disk + systemd status 직접 검증 생략.
### 근본 원인 발견
`/etc/cron.d/cf-zone-sync``/usr/local/bin/auto-add-cf-zones.sh` (매 10분):
1. CF API 에서 account 의 zone 목록 fetch
2. yaml 에 없는 zone 발견 → **12-space indent 로 자동 추가** (script bug, YAML 파괴)
3. `systemctl restart crowdsec-cloudflare-worker-bouncer`
inouter / anvil 을 yaml 에서 뺐더니 10분 뒤 cron 이 다시 넣으면서 indent bug 로 YAML 깨뜨림 → bouncer 재기동 실패 → crashloop. 매 10분 루프.
Syn 가 yaml 을 수동 fix 해 disk 에 push 했다고 한 것도 `~10분 후` cron 이 다시 깨뜨려 무효화. Syn 는 push 직후 validation 에 `cfb-manager /status` 만 쓰고 disk 내용을 재확인하지 않아 detect 못 함.
### 복구 (kappa 직접, Syn 우회)
| 단계 | 조치 | 결과 |
|---|---|---|
| 1 | journalctl 로 crashloop 확인, cron 발견 | `auto-add-cf-zones.sh` root cause 식별 |
| 2 | `mv /etc/cron.d/cf-zone-sync /etc/cron.d/cf-zone-sync.disabled.kappa-incident-20260410` | cron 비활성화 |
| 3 | `cp yaml.bak.20260410004004 yaml` | 79 lines clean 복원 |
| 4 | `crowdsec-cloudflare-worker-bouncer -c yaml -t` | `config is valid` |
| 5 | `systemctl reset-failed && systemctl start` | active (running), PID 371770 |
| 6 | 15 초 대기 후 CF API 검증 | 위젯 7개 (4개 bouncer-managed 자동 재생성 + `inouter-bunny-middleware` 생존 + `crowdsec-captcha` + `inouter` legacy), 라우트 4개 zone 재생성, inouter/anvil 은 0 routes |
재생성된 bouncer-managed 위젯:
- servidor: `0x4AAAAAAC3rgjWkt-40fI7d`
- actions: `0x4AAAAAAC3rg3VTXEllaMhY`
- keepanker: `0x4AAAAAAC3rhJWcBgQ5aPym`
- ironclad: `0x4AAAAAAC3rhaCWqBq5Qgza`
`inouter-bunny-middleware` 가 rotation 에서 건드려지지 않음을 확인 — freeze 가설 증명 (이름 패턴 분리).
### 교훈
1. **DONE 회신 전에 ground truth 직접 검증**. API 응답·in-memory 상태가 아닌 disk + systemd + 외부 API 직접 조회.
2. **destructive cron 은 recovery 윈도우의 적**. 복구 작업 전 모든 자동화 (cron, systemd timer, scheduled job) 를 먼저 멈추고 수동 mode 로 진입.
3. **같은 시각 두 다른 크기의 백업 파일은 scheduled writer 의 흔적**. 이걸 일찍 감지했으면 근본 원인 파악이 빨랐음.
4. **에이전트의 false-positive 보고는 신뢰 철회 신호**. 복구를 다시 같은 에이전트에 맡기지 않고 kappa 가 직접 수행한 결정은 옳음.
### 참조
- Outline incident report: `8f5c43f8-0b46-4032-b3f6-08106aa1e5e2`
- OpenMemory: cf-audit-cleanup-2 3-incident chain
- Vault: `secret/cloud/cloudflare/turnstile-inouter-bunny`
## 5. `cf-zone-sync` cron 영구 제거
### 배경
인시던트 #3 의 근본 원인이자 3 writer 구조 (`cfb-manager`, bouncer read, cron) 중 조정 없이 동작하는 writer. 재발 방지.
### 조치
- `/etc/cron.d/cf-zone-sync.disabled.kappa-incident-20260410` **완전 삭제**
- `/usr/local/bin/auto-add-cf-zones.sh``.disabled.20260410-incident` 로 rename, `chmod 600` (실행 불가, post-mortem 용 보존)
- `/usr/local/bin/sync-cf-zones.sh` (older detect-only 버전, cron 미참조, yaml 편집 없음) — 건드리지 않고 dormant 유지
- `/var/log/cf-zone-sync.log` (274 KB) — history 로 유지
### 결정 근거
Exclusion list 지원 재작성보다 **자동 쓰기 자체를 제거** 가 옳음. yaml 에 쓰는 주체가 하나일 때만 coordination 문제 해소. 신규 zone 추가 시 cfb-manager `POST /domains` 또는 수동 yaml edit + systemctl restart.
### 영향
`cfb-manager` protected_domains 4, bouncer active (PID 371770, 유지). 추가 자동화 없음.
## 6. iron-kr `IgnoreQueryStrings` true → false
### 배경
iron-kr 과 iron-jp 의 설정 불일치. iron-kr `true` (query string 을 캐시 키에서 무시) 는 privilege 교차 / 배포 cache-bust 버그의 time bomb. 안전한 기본값은 `false`.
### 실측
BunnyCDN statistics 조회: iron-kr 의 월 전체 평균 cache hit rate `0.22973383260803562` (≈ 0.23%). 실질적으로 캐시 미사용 상태라 변경 영향 무시 수준.
### 조치
`bunny_update_pullzone` pullzone_id `5555227``{"IgnoreQueryStrings": false}`. API 응답에서 `"IgnoreQueryStrings": false` 확인.
### 결과
iron-kr / iron-jp 둘 다 `false` 로 통일. 안전한 기본값 확보.
## 종합 — 오늘의 end state
- BunnyCDN 풀존 5개 (iron-kr / iron-jp / iron-kr-waf / iron-git / i-gate), 호스트 17개 (유지)
- BunnyCDN 미들웨어 64811 — `TURNSTILE_SITE_KEY` = `0x4AAAAAAC3otPWhldI96Aks` (`inouter-bunny-middleware`, freeze)
- iron-kr `IgnoreQueryStrings: false` (iron-jp 와 통일)
- CF zone 6개, DNS 레코드 57건 (60 - 3 삭제)
- CF Worker bouncer 보호 zone 4개 (keepanker / actions / ironclad / servidor)
- K3s `lan-proxies` ns 신설, `nas.inouter.com` reverse proxy 동작
- `cf-zone-sync` cron 영구 제거, yaml writer = `cfb-manager` 단일
- 미해결 별건: `cfb-manager` DELETE destructive 동작 문서화, Worker 라우트 null orphan (vultr/linode.actions.it.com) 정리, Turnstile `inouter` legacy 위젯 결정
### 관련 OpenMemory 엔트리
- 2026-04-10 BunnyCDN+Cloudflare 도메인 전수 감사
- 2026-04-10 CF DNS 정리 1차 (cf-audit-cleanup-1)
- 2026-04-10 nas.inouter.com LAN-only Traefik 리버스 프록시 복원
- 2026-04-10 cf-audit-cleanup-2 3-incident chain
- 2026-04-10 cf-zone-sync cron 영구 제거
- 2026-04-10 iron-kr IgnoreQueryStrings true→false
### 관련 git commits
- `639c9b5` edge: 2026-04-10 BunnyCDN+CF 전수 감사 결과 정본 반영
- `f5264bb` edge: CF inouter.com zone 정리 1차 (cf-audit-cleanup-1)
- `9d71167` infra: nas.inouter.com Traefik 리버스 프록시 (LAN-only) 복원
- `218c323` edge: cf-audit-cleanup-2 + recovery 정본 반영
- `7a8230d` edge: cf-audit-cleanup-2 3차 인시던트 (cron-induced crashloop) + kappa 직접 복구 반영
- `8fc6a34` edge: cf-zone-sync cron 영구 제거 (cleanup-2 인시던트 근본 원인)
- `d16090a` edge: iron-kr IgnoreQueryStrings true→false (iron-jp 와 통일)