RKE2 Cilium Hubble 운영 경계

이 변경은 RKE2에 포함된 Cilium 1.19.3에서 Hubble Agent와 내부 Relay를 활성화하기 위한 제한된 계약이다. 기본값은 Hubble UI, Ingress, Gateway API, Cilium Gateway, kube-proxy replacement, WireGuard, 외부 Relay Service를 모두 끈다.

내부 시험에 한해 Hubble UI와 단일 Ingress host hubble.cysec.bloodbag.internal.tirosh.ai만 아래 예외로 허용한다. 이 예외는 제품 E3와 분리되며, E3 증거는 계속 Relay JSON-lines와 hubble-evidence export만 사용한다. UI 화면은 E3 증거가 아니다.

기본 상태

sites/tirosh-home/rke2/hubble.vars.yml은 다음 입력이 없으면 항상 차단된다.

  • 승인된 내부 OCI host
  • Hubble Relay image repository와 SHA-256 digest
  • certgen image repository와 SHA-256 digest
  • rke2_hubble_enabled: true

저장소의 빈 값은 아직 정책이 결정되지 않았음을 나타내며 자동 기본값으로 외부 registry를 사용하지 않는다. UI/Ingress 예외가 문서에 있어도 이 네 입력이 채워지기 전에는 rke2/hubble/applyBLOCK이다. digest를 발명하거나 빈 값으로 apply하지 않는다.

공개 Make Interface

make rke2/hubble/verify SITE=tirosh-home
make rke2/hubble/preflight SITE=tirosh-home
make rke2/hubble/plan SITE=tirosh-home
make rke2/hubble/apply SITE=tirosh-home
make rke2/hubble/rollback \
  SITE=tirosh-home \
  RKE2_HUBBLE_ROLLBACK_CONFIRMATION=<bootstrap-server-inventory-name>
  • verify: Ansible syntax, Hubble 증거 요약기와 정적 계약을 로컬에서 검증
  • preflight: rke2_hubble_evidence_namespace의 NetworkPolicy가 선언된 rke2_hubble_evidence_ports를 모두 커버하는지 확인. RKE2_HUBBLE_PREFLIGHT_INPUT으로 클러스터에서 수집한 NetworkPolicy YAML을 지정한다. PASS 시 site profile과 입력 내용에 결속된 receipt(RKE2_HUBBLE_PREFLIGHT_RECEIPT)를 기록한다.
  • plan: 동일 invocation에서 NetworkPolicy preflight를 필수 실행하고, PASS receipt를 site·namespace·포트·입력 hash 기준으로 fail-closed 재검증한 뒤 --check --diff 실행. RKE2_HUBBLE_PREFLIGHT_INPUT이 없거나 비어 있거나 잘못되면 ansible에 도달하지 않는다.
  • apply: plan과 동일하게 preflight status: PASS와 결속 receipt 검증 이후에만 진행하며, Cilium Agent를 maxUnavailable=1로 순차 갱신하고 내부 Relay 준비 상태 확인
  • rollback: 최초 활성화 전 0600 백업을 복원하는 위험 작업

applyrollback은 실제 cluster를 변경한다. 이 Draft 작업에서는 실행하지 않는다.

고정된 기술 경계

  • Hubble Relay: ClusterIP, replica 1
  • Hubble UI: 기본 비활성. 아래 내부 시험 예외에서만 허용
  • peer port: TCP 4244
  • Relay port: TCP 4245
  • Agent update: RollingUpdate, maxUnavailable=1
  • Agent↔Relay: chart 관리 mTLS
  • 인증서 생성: chart certgen CronJob
  • image: 내부 OCI repository와 sha256 digest 필수
  • kubeProxyReplacement: false 유지
  • Gateway API·Cilium Gateway·WireGuard·외부 Relay Service: 금지. 예외 없음
  • Ingress: 기본 금지. 아래 예외의 단일 host만 허용

현재 HelmChartConfig와 rke2/hubble/apply 계약은 여전히 hubble.ui.enabled: false이며 Hubble UI Deployment가 생기면 실패한다. 예외는 후속 구현을 승인하는 문서 계약일 뿐, OCI host와 Relay/certgen sha256이 채워지기 전에는 UI 라우트 구현을 시작하지 않는다.

내부 시험 예외: Hubble UI + Ingress

이 예외는 GitHub tirosh-chain/tirosh-infra#86내부 시험 경로다. 제품 registered, Demo Console(#85), E3 판정 경로와 독립이다.

허용

  • Hubble UI 활성화 (내부 시험만)
  • Ingress host는 정확히 hubble.cysec.bloodbag.internal.tirosh.ai. 추가 host·wildcard·제품 host 재사용 금지
  • Ingress는 HTTP :80. ssl-redirect는 끈다. TLS는 OPNSense Caddy가 종료한다
  • Caddy reverse_proxygitops.internal.tirosh.ai와 같은 HTTP :80 패턴이다. backend는 RKE2 Ingress HTTP endpoint(gpu-01, 172.31.0.20:80)다
  • Relay는 계속 ClusterIP replica 1. 증거 접근은 cluster-admin-port-forward-or-approved-internal-collector

선택 Caddy 스니펫:

hubble.cysec.bloodbag.internal.tirosh.ai {
  reverse_proxy 172.31.0.20:80
}

경로:

https://hubble.cysec.bloodbag.internal.tirosh.ai -> OPNSense Caddy -> http://172.31.0.20:80

E3는 Relay 증거만 사용한다

내부 시험 UI가 열려도 E3 판정은 UI에 의존하지 않는다. 승인된 최대 1시간 Hubble JSON-lines와 infra-tools hubble-evidence export로 만든 summary/manifest만 E3 증거다. UI 화면, 스크린샷, 브라우저 세션, Ingress 접속 기록은 E3 증거가 아니다.

거부한 대안

대안 거부 이유
Hubble UI를 기본 운영 계약으로 상시 허용 병원/제품 운영 경계와 내부 시험을 섞는다
Ingress만 추가하고 UI는 비활성 빈 호스트. DNS만으로는 backend가 없다
Gateway API / Cilium Gateway 이 계약과 #86 범위 밖이다
Relay를 NodePort·LoadBalancer·외부 Service로 노출 외부 Relay Service 금지
제품 host cysec.bloodbag.internal.tirosh.ai에 Hubble 바인딩 Hubble 전용 내부 host만 허용한다
Demo Console / 제품 registered에 Hubble UI를 묶음 #86은 Demo(#85)와 독립이다
Hubble UI를 E3 증거로 사용 E3는 Relay JSON-lines와 제한된 집계만 사용한다
빈 digest 또는 발명한 sha256으로 apply 내부 OCI 공급망 계약을 우회한다
외부 registry(quay.io, docker.io, ghcr.io) 기본값 자동 기본값으로 외부 registry를 쓰지 않는다

apply는 계속 BLOCK

현재 site 입력은 rke2_hubble_enabled: false이고 내부 OCI host, Relay repository/digest, certgen repository/digest가 비어 있다. 이 네 값이 독립 검증되어 채워지기 전에는 다음이 성립한다.

  • make rke2/hubble/verify SITE=tirosh-home만 성공해야 한다
  • planapply는 fail-closed가 정상이다
  • UI Ingress 구현, Caddy 적용, rke2/hubble/apply는 시작하지 않는다
  • 이 문서 예외만 Green이다. apply와 라우트 도달은 BLOCK이다

증거 내보내기

infra-tools hubble-evidence export는 승인된 최대 1시간의 Hubble JSON-lines를 읽고 제한된 집계만 생성한다.

PYTHONPATH=packages/infra-tools/src uv run python -m infra_tools.adapters.inbound.cli \
  hubble-evidence export \
  --input /controlled/hubble-flow.jsonl \
  --summary-output /controlled/hubble-summary.json \
  --manifest-output /controlled/hubble-manifest.json \
  --environment tirosh-home \
  --namespace bloodbag-cysec \
  --started-at 2026-07-30T00:00:00Z \
  --ended-at 2026-07-30T00:10:00Z \
  --recorded-at 2026-07-30T00:11:00Z \
  --destination-port 443 \
  --destination-port 6379 \
  --document-id E3-HUBBLE-20260730-001

GitHub에는 원시 flow를 저장하지 않는다. summary에도 IP, workload label, DNS query, payload와 credential을 넣지 않으며 source hash, 관측 구간, namespace, port, verdict별 건수만 남긴다. 실제 E3 판정은 승인된 대표 RKE2 환경과 실행 기록이 확인된 뒤에만 가능하다. 내부 시험 Hubble UI는 이 증거 경로를 대체하지 않는다.

NetworkPolicy 증거 사전 확인 (preflight)

Hubble 관측이 필요한 bloodbag-cysec namespace의 NetworkPolicy가 hubble.vars.yml에 선언된 증거 포트를 모두 커버하는지 사전 검증한다. 단순히 포트 번호가 한 번 등장하는 것으로는 충분하지 않다. 같은 source/destination Pod selector를 잇는 bounded TCP ingress와 egress 규칙이 모두 있어야 완전한 경로로 인정한다. NetworkPolicy가 설정되지 않았거나 증거 포트가 누락된 경우 Hubble을 활성화해도 기대하는 트래픽 관측이 불가능하다.

# 클러스터에서 현재 NetworkPolicy를 수집
kubectl --context <context> get networkpolicies -n bloodbag-cysec -o yaml > /tmp/bloodbag-cysec-np.yaml

# 사전 확인 실행
make rke2/hubble/preflight \
  SITE=tirosh-home \
  RKE2_HUBBLE_PREFLIGHT_INPUT=/tmp/bloodbag-cysec-np.yaml

입력은 개별 networking.k8s.io/v1 NetworkPolicy YAML 문서 또는 kubectl ... -o yaml이 생성하는 v1/List 형식이어야 하며 최대 2 MiB다. 빈 podSelector: {}와 비어 있거나 생략된 ingress/egress 규칙으로 모든 Pod를 격리하는 Kubernetes default-deny 정책은 유효한 입력이지만 허용 경로 증거로는 계산하지 않는다. 그 밖의 빈 target/peer selector, wildcard CIDR(0.0.0.0/0, ::/0), peer가 없는 open rule, named port와 UDP/SCTP 규칙은 증거가 될 수 없으며 fail-closed로 거부한다. 포트·정책·규칙·peer cardinality도 제한된다.

RKE2_HUBBLE_PREFLIGHT_INPUT이 없거나 비어 있거나 잘못된 경우 preflight는 성공으로 간주하지 않고 non-zero로 종료한다. 결과는 canonical JSON으로 출력되며 status: PASS 또는 status: FAIL과 함께 커버된 포트와 누락된 포트 목록을 제공한다. Hubble apply 전에 status: PASS가 확인되어야 한다. rke2/hubble/planrke2/hubble/apply는 별도 preflight 실행 없이 동일 invocation에서 이 검증을 필수로 수행한다. PASS는 site profile, evidence namespace, 포트 집합, 입력 파일의 SHA-256·byte 수를 기록한 receipt(RKE2_HUBBLE_PREFLIGHT_RECEIPT, 기본 .hubble-preflight/preflight-receipt.json)로 결속되며, plan/apply는 ansible 실행 전에 network-policy-preflight receipt verify로 이 결속을 fail-closed 재검증한다. 입력 내용이 receipt와 다르거나 FAIL이면 ansible-playbook을 실행하지 않는다.

receipt 파일 연산은 caller-controlled 경로를 신뢰하지 않는다. 쓰기는 root-pinned openat descriptor chain 아래 temp 파일 O_EXCL 생성과 atomic rename으로 commit되며 mode 0600, regular, hard-link 1개를 검증한다. 삭제와 읽기도 동일 descriptor-relative 경계를 사용하므로 ancestor symlink, hardlink victim overwrite, A-B-A 경로 재사용이 거부되거나 무력화된다. Make interface는 receipt에 대한 path 기반 mkdir/rm을 수행하지 않으며, receipt 부모 디렉터리(기본 .hubble-preflight/, gitignore)는 운영자가 1회 미리 생성해야 한다(mkdir -p .hubble-preflight). freshness는 recordedAt에만 의존하지 않는다: plan/apply는 호출마다 무작위 operation token을 생성해 check에 주입하고, verifier는 receipt의 operationId가 같은 invocation token과 일치해야만 통과한다.

적용 전후 확인

적용 전후에 기존 NGINX, CoreDNS, Cilium Agent, PostgreSQL, Application Redis, Signal Ingress Redis와 기존 서비스 endpoint를 같은 명령으로 확인한다. Hubble 활성화만으로 Gateway API, Cilium Gateway, NodePort/LoadBalancer, 또는 hubble.cysec.bloodbag.internal.tirosh.ai 이외의 외부 listener가 생기면 실패로 처리한다.

내부 OCI host와 Relay/certgen sha256이 결정되지 않은 현재 단계에서는 verify만 성공해야 하며 planapply는 fail-closed가 정상이다. UI/Ingress 예외는 이 차단을 해제하지 않는다.