CCA — 실리움 인증 어소시에이트 · 네트워크 정책 · 이론
정책 하나를 붙이는 순간 벌어지는 일
한 줄 요약
어떤 엔드포인트를 선택하는 정책이 하나라도 적용되면 그 방향은 즉시 화이트리스트 모드로 바뀝니다. 방향은 서로 독립이고, deny 는 언제나 allow 를 이깁니다.
왜 이게 필요했나
쿠버네티스의 기본 상태는 "모든 파드가 모든 파드와 통신 가능"입니다. 결제 서비스가 사내 위키에 붙을 수 있고, 침해당한 프런트엔드가 데이터베이스에 직접 접근할 수 있습니다. 제로트러스트의 출발점은 이 기본값을 뒤집는 것입니다.
표준 NetworkPolicy 로도 시작은 되지만 실무에서 금방 벽에 부딪힙니다. HTTP 경로 단위 제어가 안 되고, 외부 API 를 도메인 이름으로 허용할 수 없고, 명시적 거부 규칙이 없어 "무슨 일이 있어도 막아야 하는 것"을 표현할 방법이 없으며, 무엇보다 거부된 트래픽을 볼 방법이 없습니다. CiliumNetworkPolicy 는 이 간극을 메웁니다.
어떻게 동작하나
사고 모델은 이렇습니다.
ingress 정책 egress 정책 "누가 나에게 올 수 있나" "나는 어디로 갈 수 있나" [소스 아이덴티티] --> (엔드포인트) --> [목적지 아이덴티티/CIDR/FQDN] | per-endpoint 정책 맵에서 O(1) 판정 key: (아이덴티티, 포트, 프로토콜, 방향)여기서 실무 사고를 가장 많이 내는 규칙 두 가지가 나옵니다.
첫째, 방향별로 독립입니다. 어떤 파드에 ingress 정책을 하나 붙이면 그 파드의 ingress 는 화이트리스트가 되지만, egress 는 egress 정책이 붙기 전까지 여전히 전부 허용입니다. "정책을 걸었으니 안전하다"는 착각이 여기서 나옵니다.
둘째, deny 는 항상 allow 를 이깁니다. 같은 트래픽에 allow 와 deny 가 모두 매칭되면 결과는 거부입니다. 그래서 클라우드 메타데이터 엔드포인트 차단처럼 절대 뚫리면 안 되는 항목은 egressDeny 로 한 겹 더 깔아 둡니다.
셀렉터의 미묘한 차이도 시험 단골입니다.
ingress: - fromEndpoints: - {} # 같은 네임스페이스의 모든 엔드포인트 = 허용ingress: - fromEndpoints: [] # 아무것도 매칭되지 않음 = 명시적 기본 거부빈 객체 하나가 든 배열과 빈 배열은 완전히 반대의 의미입니다.
L7 정책이 붙으면 경로가 달라집니다. eBPF 는 해당 플로우만 선별해 노드의 Envoy 로 넘기고, Envoy 가 HTTP 를 파싱해 규칙에 맞지 않으면 403 을 돌려줍니다. L4 차단과 달리 연결 자체는 성립하므로 애플리케이션 로그에는 "접속은 되는데 403"으로 보입니다. 이 차이를 모르면 방화벽 문제로 착각하고 엉뚱한 곳을 파게 됩니다.
가장 흔한 설정 실수는 따로 있습니다. toFQDNs 를 쓰면서 DNS 규칙을 함께 걸지 않는 것입니다. toFQDNs 는 패킷의 SNI 를 들여다보는 방식이 아닙니다. Cilium 의 DNS 프록시가 그 파드의 DNS 응답을 가로채 "이 파드는 api.example.com 이 곧 54.x.x.x 라는 것을 알게 됐다"를 학습하고, 그 IP 를 ipcache 에 등록해 IP 기반으로 허용하는 방식입니다. 따라서 DNS 질의를 허용하고 프록시가 관찰할 수 있게 하는 규칙이 없으면, toFQDNs 는 영원히 매칭되지 않습니다. 증상은 "DNS 는 되는데 연결이 거부됨" 또는 그 반대로 나타납니다.
운영 전환은 4단계로 합니다. 관찰(Hubble 로 실제 통신 매트릭스 수집) → 허용 정책만 먼저 배포 → policy-audit-mode 로 "막혔을 트래픽"만 기록 → 네임스페이스 단위로 강제. 배치 작업처럼 드물게 도는 트래픽을 놓치지 않으려면 관찰 기간을 최소 한 달 주기까지 잡는 편이 안전합니다.
마지막으로, 표준 NetworkPolicy 와 CNP 는 같은 eBPF 데이터패스에서 평가되며 어느 한쪽이라도 허용하면 허용으로 합산됩니다. 두 종류를 섞어 쓰면 "왜 열려 있지?"를 두 곳에서 찾아야 하므로, 팀 차원에서 표준 리소스를 하나로 정해 두는 편이 낫습니다.
현장에서 만나는 모습
저자의 홈랩에서 이것을 실제로 측정했습니다. nginx 를 띄우고 정책 없이 호출했을 때는 GET 도 POST 도 200 이었습니다. 그다음 rules.http: [{method: GET}] 만 담은 CiliumNetworkPolicy 를 적용했더니 결과가 이렇게 갈렸습니다.
GET / -> 200POST / -> 403같은 IP, 같은 포트인데 메서드로 갈렸습니다. 파드에 사이드카를 주입하지 않았고 애플리케이션 코드도 그대로였습니다. iptables 는 L3/L4 에서 동작하므로 이 구분을 원리적으로 할 수 없습니다.
더 흥미로운 것은 Hubble 로그의 모양이었습니다.
client:47918 -> api:80 http-request FORWARDED (HTTP/1.1 GET http://api/)client:47932 -> api:80 http-request DROPPED (HTTP/1.1 POST http://api/)client:47932 <- api:80 http-response FORWARDED (HTTP/1.1 403 0ms POST)요청은 DROPPED 인데 응답은 FORWARDED 입니다. 프록시가 만들어 낸 403 이 정상 응답으로 흘러 나갔기 때문입니다. L4 드롭이라면 응답 줄 자체가 없습니다. 이 로그 모양의 차이가 "정책에 막힌 것인지 네트워크가 끊긴 것인지"를 가르는 가장 빠른 단서입니다.
다음 실습에서 할 것
표준 NetworkPolicy 로 기본 거부와 파드/네임스페이스 셀렉터 허용을 실제로 apply 해 보고, CiliumNetworkPolicy 로 HTTP 메서드·경로 제한, toFQDNs 와 짝이 되는 DNS 규칙, 그리고 메타데이터 엔드포인트 차단을 파일로 작성합니다.