K8s Security - 09. network security
"내부라서 안전하다"를 부정하는 모델 — zero-trust와 네트워크 보안
한 조직이 클러스터 네트워크를 "내부는 믿을 수 있다"는 가정 위에 세웠다. Pod는 서로 자유롭게 통신했고, NetworkPolicy는 없었다(이게 Kubernetes의 기본값이다 ). 그러다 한 Pod가 취약점으로 뚫렸다. 공격자는 그 Pod에서 출발해 클러스터 전체를 횡단했다. 다른 Pod로의 연결을 막는 벽이 아예 없었으니까. zero-trust는 바로 이 가정을 부정한다 — "내부도 불신한다. 누가 누구와 통신하나를 명시적으로 허용한다." 이 글은 네트워크 보안이 이 원칙을 어떻게 실현하고, 이미 본 NetworkPolicy와 Calico/Cilium 정책·메시 정책이 어떻게 함께 한 벽이 되는지를 따진다.
zero-trust가 부정하는 것 — 성벽 모델
전통적 네트워크 보안은 성벽 모델(castle-and-moat)이었다. 외부는 강하게 막되, 성벽 안(내부망)은 일단 들어오면 자유. "VPN만 연결하면 다 믿는다"가 이 모델이다. zero-trust는 이 전제를 뒤집는다. NIST의 정의를 빌리면, "자산이나 계정에 물리적·네트워크적 위치만으로 암묵적 신뢰를 부여하지 않는다"가 출발점이다. (NIST SP 800-207 - Zero Trust Architecture)
이 원칙이 구체적으로 내려오면 세 가지 실천이 된다:
- 기본 거부(default-deny) — 아무것도 안 정하면 다 막힌다. 허용한 것만 열린다.
- 최소 권한 — A→B 통신이 필요하면 그것만 연다. C→B는 닫혀 있다.
- 심층 방어 — 한 층에 기대지 않는다. 통신이 뚫려도 다음 층이 또 검사한다.
Kubernetes의 기본 NetworkPolicy 동작이 이 원칙과 정반대라는 게 함정이다. NetworkPolicy가 하나도 없으면 모든 트래픽이 허용된다. 즉 무설정 클러스터는 zero-trust와 가장 먼 상태다. zero-trust로 가려면 의도적으로 기본 거부를 얹어야 한다.
Kubernetes에서 zero-trust를 쌓는 벽돌들
이 서브프로젝트에서 이미 본 것들이 zero-trust의 벽돌이다. 따로 새 도구가 아니라, 이전 장들의 층이 합쳐져 zero-trust가 된다.
| 층 | 검사하는 것 |
|---|---|---|
| NetworkPolicy | L3/L4. 라벨·IP·포트 기본 허용/거부 |
| Calico/Cilium CRD 정책 | deny 규칙, L7, namespace 간, 글로벌 |
| 메시 AuthorizationPolicy | 서비스 신원(mTLS 인증서) 기반 인가, L7 |
| Pod Security Admission | Pod 자체의 권한 상승(privileged 등) 차단 |
| RBAC | apiserver 접근 최소 권한 |
어느 하나 빠지면 그 층이 뚫린다. NetworkPolicy만 있고 RBAC가 느슨하면 apiserver 쪽으로 새고, 메시 정책만 있고 Pod Security가 없으면 privileged Pod로 노드를 빠져나간다.
IP 기반과 신원 기반 — 두 정책이 보는 것이 다르다
네트워크 정책은 두 축으로 작동할 수 있다. 차이가 zero-trust의 강도를 결정한다.
NetworkPolicy / Calico·Cilium CRD는 IP·라벨·포트로 평가한다. CNI가 결정한다. Kubernetes 기본 NetworkPolicy는 L3/L4 한계가 있지만, Calico의 GlobalNetworkPolicy나 Cilium의 CiliumNetworkPolicy는 deny 규칙·L7·글로벌 정책까지 커버한다. (Calico docs - Network policy)
# Calico GlobalNetworkPolicy — db는 web의 5432만 허용, 나머지 거부
apiVersion: crd.projectcalico.org/v1
kind: GlobalNetworkPolicy
metadata:
name: db-allow-web
spec:
order: 20
selector: app == 'db'
ingress:
- action: Allow
source:
selector: app == 'web'
protocol: TCP
destination:
ports: [5432]
- action: Deny
메시 AuthorizationPolicy는 신원으로 평가한다. mTLS로 증명된 SPIFFE ID(spiffe://cluster.local/ns/<ns>/sa/<sa>)가 기준이다. Istio에선 source.principals로 그 신원을 지정한다. (Istio docs - AuthorizationPolicy)
# Istio AuthorizationPolicy — web-sa 신원만 db에 접근 허용
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: db-allow-web
namespace: app
spec:
selector:
matchLabels:
app: db
action: ALLOW
rules:
- from:
- source:
principals: ["cluster.local/ns/app/sa/web-sa"]
두 기반의 보안 강도 차이는 "무엇을 위조해야 우회되나"에서 온다. IP 기반 정책은 공격자가 IP/라벨을 바꾸면 우회 가능하다. 신원 기반은 인증서를 위조해야 하는데, 인증서는 메시 CA가 발급하므로 훨씬 어렵다. 그래서 둘을 겹치면 심층이 된다 — IP로 1차, 신원으로 2차. 다만 관리 부담이 있으니, 메시 유무에 따라 한쪽을 중심으로 잡는 게 현실적이다. Istio의 DENY 정책이 ALLOW보다 우선이라는 점도 쓸 만하다 — "이 namespace 외부의 모든 접근 거부"를 DENY로 먼저 깔고, 세부 허용을 ALLOW로 얹는 패턴이 흔하다.
통신 격리의 표준 패턴 — default-deny에 허용을 얹는다
zero-trust 통신의 구체적 형태는 마이크로세그멘테이션(microsegmentation)이다. Pod/서비스를 작은 격리 단위로 나누고, 각 단위 간 통신을 명시적으로만 허용한다. 과거의 "네트워크 존(데이터센터 전체 신뢰)"이 아니라 "Pod 단위 격리"다.
flowchart TD
WEB["web Pod<br/>default-deny + api:8080만"] --> API["api Pod<br/>default-deny + db:5432만"]
API --> DB["db Pod<br/>default-deny, 수신 전용"]
WEB -. 차단 .-> X["db 직접 접근"]
각 Pod가 자기 역할에 필요한 통신만 열고 나머지는 default-deny. 한 Pod가 뚫려도 인접 단위로만 퍼지고, 건너건너 횡단은 차단된다. "성벽 모델"이었다면 web Pod는 클러스터 안의 아무 Pod에나 접근할 수 있었다.
침해된 Pod가 층마다 부딪힌다
zero-trust의 가치는 한 Pod가 뚫렸을 때 드러난다. 공격자가 횡단을 시도할 때 각 층이 어떻게 막는지를 보면, 왜 한 층만으론 부족한지 체감된다.
flowchart TD
COMP["침해된 web Pod"] --> TRY1["1. 다른 Pod 직접 접속"]
TRY1 --> NP["NetworkPolicy: web→api만 허용<br/>→ 거부"]
COMP --> TRY2["2. 허용된 api 경로로 비밀 탈취"]
TRY2 --> SEC["Secret etcd 암호화 + External Secrets<br/>→ 평문 비밀 없음 "]
COMP --> TRY3["3. apiserver 권한 상승"]
TRY3 --> RBAC["RBAC 최소 권한 + 어드미션<br/>→ 거부"]
COMP --> TRY4["4. 컨테이너 탈출(privileged)"]
TRY4 --> PSA["Pod Security: restricted<br/>→ 불가 "]
COMP --> TRY5["5. 통신 내용 탈취"]
TRY5 --> MTLS["mTLS 암호화<br/>→ 평문 안 보임"]
각 층이 독립적으로 막는다. 한 층(예: NetworkPolicy)이 설정 누락으로 뚫려도 다음 층(mTLS·RBAC·Pod Security)이 남은 경로를 막는다. 이게 "심층 방어"의 뜻이다 — 단일 방어선에 의존하지 않는다. 반대로 이 사고 추적은 "어느 층을 빼면 가장 위험한가"도 가르쳐 준다. mTLS 없이 NetworkPolicy만 있으면, 허용된 경로(web→api)의 통신이 평문이라 그 경로의 침해가 도청으로 이어진다.
zero-trust는 스펙트럼이다 — 점진 도입
zero-trust는 스위치가 아니라 스펙트럼이다. 클러스터가 어느 단계인가에 따라 다음에 얹을 층이 달라진다.
| 단계 | 상태 | 다음 할 일 |
|---|---|---|
| 0 (무방비) | 모든 Pod 자유 통신, RBAC 느슨 | default-deny NetworkPolicy 도입 검토 |
| 1 (기본 격리) | 핵심 서비스만 NetworkPolicy | RBAC 최소 권한, Pod Security Admission |
| 2 (심층) | NetworkPolicy + CRD + RBAC 강화 | 메시 mTLS 도입, 인증서 자동화(cert-manager) |
| 3 (zero-trust) | 위 전부 + 감사 + 런타임(Falco) | 정책 코드화(OPA/Kyverno), 자동 대응 |
한 번에 3단계로 가는 게 아니다. 0단계 클러스터에 메시부터 깔면 동작은 하되 운영이 못 따라간다. 현재 단계에서 한 층씩 올리는 게 현실적이다.
정책의 코드화 — zero-trust를 운영 가능하게
정책이 YAML(CRD·ValidatingAdmissionPolicy)로 관리되면 Git에 커밋하고 코드 리뷰·버전 관리가 가능하다. 이것이 policy-as-code다. "운영자가 콘솔에서 수동으로 정책을 바꾼다"에서 "Git PR로 정책을 바꾼다"로의 전환이, 대규모 zero-trust 운영을 가능하게 하는 관행이다. OPA Gatekeeper, Kyverno, ValidatingAdmissionPolicy가 모두 이 철학을 따른다. 정책 변경이 리뷰를 거치고 감사 가능해야 수백 개 Pod의 통신 규칙을 사람이 유지할 수 있다.
직접 확인하기
# Kubernetes 1.36 — 현재 namespace의 NetworkPolicy 확인
kubectl get networkpolicy -A
확인할 것: 아무 정책도 없는 namespace는 "무방비(0단계)" 상태. default-deny가 있는지가 zero-trust의 첫 체크포인트.
# Istio 설치 클러스터 — 신원 기반 정책 확인
kubectl get authorizationpolicy -A
Calico CRD / CiliumNetworkPolicy / Istio AuthorizationPolicy의 세부 필드는 각 공식 문서로 실측.
흔히 묻는 것, 흔히 틀리는 것
| 오해 | 정정 |
|---|---|
| "zero-trust는 도구다" | 모델/원칙. 여러 도구(NetworkPolicy·메시·어드미션)로 실현 |
| "NetworkPolicy만 있으면 zero-trust다" | 한 층. 메시·Pod Security·RBAC과 함께 심층 |
| "내부는 믿을 수 있다" | zero-trust의 정반대 전제. 부정해야 |
| "메시 정책은 NetworkPolicy를 대체한다" | 보완. IP 기반(NetworkPolicy) + 신원 기반(메시) |
| "default-deny는 위험하다" | 반대. 허용 항목 명시가 안전. 기본 허용이 위험 |
| "Kubernetes 기본은 통신이 막혀 있다" | 거짓. NetworkPolicy가 없으면 모두 허용 |
요약 — 이 글의 결론
- zero-trust가 "내부도 불신" 모델. NIST SP 800-207에 따르면 위치만으로 암묵 신뢰를 주지 않는다. 기본 거부 + 최소 권한 + 심층 방어.
- Kubernetes의 기본은 zero-trust와 정반대 — NetworkPolicy가 없으면 모든 통신이 허용. 의도적으로 default-deny를 얹어야 한다.
- zero-trust의 벽돌: NetworkPolicy + Calico/Cilium CRD + 메시 AuthorizationPolicy + Pod Security + RBAC. 한 층이라도 빠지면 그 층이 구멍.
- IP 기반(NetworkPolicy)과 신원 기반(메시)은 보완 — 신원 위조가 IP 위조보다 어려워 메시 정책이 더 강한 보증. Istio
DENY가ALLOW보다 우선. - 마이크로세그멘테이션(default-deny + 허용 명시)이 통신 격리의 표준 패턴. 한 Pod 침해가 인접 단위로만 퍼진다.
- 침해된 Pod의 횡단을 여러 층이 독립적으로 막는 게 심층 방어. 한 층 뚫려도 다음 층.
- zero-trust는 스펙트럼 — 0~3단계 점진 도입. policy-as-code(Git 리뷰)가 대규모 운영을 가능하게.
생각해 볼 문제
- 침해된 Pod가 같은 노드의 다른 Pod로 횡단하려 한다. 어떤 층이 막나? (여럿 가능)
- NetworkPolicy만 켜고 메시 정책은 안 켰다. 침해된 Pod가 IP를 바꾸면? 메시 신원이 있었다면?
- Istio
AuthorizationPolicy가web-sa신원만 허용한다.web-sa의 SA 토큰이 탈취되면? (신원 도용) - default-deny를 켰다가 모니터링(Prometheus)이 멈췄다. zero-trust에서 이걸 어떻게 다루나?
- zero-trust의 "성능 오버해드"와 "침해 피해 감소"의 trade-off를 Pod 1000개 클러스터 관점에서.
- zero-trust를 "완전히" 달성할 수 있는가? 불가능하다면 그 의미는? (심층 방어의 한계)
- Istio에서
DENY-first 정책을 먼저 까는 이유를,ALLOW만 썼을 때의 빈틸로 설명하라.
참고
- NIST SP 800-207 - Zero Trust Architecture - 접근 2026-07-16
- Istio docs - AuthorizationPolicy (principals / ALLOW / DENY) - 접근 2026-07-16
- Istio docs - Security concepts - 접근 2026-07-16
- Calico docs - Network policy (GlobalNetworkPolicy) - 접근 2026-07-16
- Cilium docs - Kubernetes policy - 접근 2026-07-16
- Kubernetes docs - NetworkPolicy - 접근 2026-07-16
'Tech Artifacts > K8s Security' 카테고리의 다른 글
| K8s Security - 08, mtls supply-chain (0) | 2026.07.16 |
|---|---|
| K8s Security - 07. cert-manager (0) | 2026.07.16 |
| K8s Security - 06. secrets management (0) | 2026.07.16 |
| K8s Security - 05. pod-security-admission (0) | 2026.07.16 |
| K8s Security - 04. admission-control (0) | 2026.07.16 |