K8s NetworkPolicy 제로 트러스트 설계

Network Policy란? — Pod 간 트래픽의 방화벽

Kubernetes 클러스터에서는 기본적으로 모든 Pod이 모든 Pod과 통신할 수 있다. 프론트엔드 Pod이 DB Pod에 직접 접근하고, 결제 서비스가 로그 수집기와 통신할 수 있다. 이것은 보안적으로 위험하다. 하나의 Pod이 침해되면 클러스터 전체가 노출된다.

Network Policy는 Pod 레벨의 방화벽 규칙이다. 어떤 Pod이 어떤 Pod과 통신할 수 있는지를 레이블 셀렉터와 네임스페이스 기반으로 선언한다. RBAC이 API 서버 접근을 제어한다면, Network Policy는 네트워크 레벨 접근을 제어한다.

1. 기본 개념 — Ingress와 Egress

방향 의미 예시
Ingress 해당 Pod으로 들어오는 트래픽 DB Pod에 접근 가능한 서비스 제한
Egress 해당 Pod에서 나가는 트래픽 프론트엔드가 외부 API만 호출 가능

핵심 원칙: Network Policy가 하나도 없으면 모든 트래픽이 허용된다. 하나라도 적용되면 명시적으로 허용된 트래픽만 통과한다(화이트리스트 방식).

2. Default Deny — 제로 트러스트의 시작

보안의 첫 단계는 모든 트래픽을 차단하고, 필요한 것만 열어주는 것이다.

# 네임스페이스의 모든 Ingress 차단
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
  namespace: production
spec:
  podSelector: {}              # 빈 셀렉터 = 네임스페이스의 모든 Pod
  policyTypes:
    - Ingress                  # Ingress 규칙이 비어있음 = 모든 인바운드 차단
---
# 네임스페이스의 모든 Egress 차단
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-egress
  namespace: production
spec:
  podSelector: {}
  policyTypes:
    - Egress                   # Egress 규칙이 비어있음 = 모든 아웃바운드 차단
---
# Ingress + Egress 모두 차단 (한 번에)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: production
spec:
  podSelector: {}
  policyTypes:
    - Ingress
    - Egress

주의: Egress를 전부 차단하면 DNS 조회도 차단된다. kube-dns/CoreDNS 접근은 반드시 허용해야 한다.

# DNS 허용 (Egress deny와 항상 함께)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-dns
  namespace: production
spec:
  podSelector: {}              # 모든 Pod
  policyTypes:
    - Egress
  egress:
    - to: []                   # 모든 대상 (DNS 서버)
      ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53

3. Pod 셀렉터 기반 — 서비스 간 통신 허용

# DB Pod: order-service와 payment-service만 접근 허용
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: db-allow-backends
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: postgresql               # 이 정책이 적용되는 Pod
  policyTypes:
    - Ingress
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: order-service     # order-service에서 오는 트래픽 허용
        - podSelector:
            matchLabels:
              app: payment-service   # payment-service에서 오는 트래픽 허용
      ports:
        - protocol: TCP
          port: 5432                 # PostgreSQL 포트만

중요 — from 배열의 OR vs AND:

# OR 조건 (배열의 각 항목) — 둘 중 하나만 만족하면 허용
ingress:
  - from:
      - podSelector:               # 조건 A
          matchLabels:
            app: order-service
      - namespaceSelector:         # 조건 B
          matchLabels:
            env: staging
    # A 또는 B를 만족하면 허용

# AND 조건 (같은 항목에 합침) — 둘 다 만족해야 허용
ingress:
  - from:
      - podSelector:               # 조건 A AND 조건 B
          matchLabels:
            app: order-service
        namespaceSelector:
          matchLabels:
            env: staging
    # staging 네임스페이스의 order-service만 허용

4. 네임스페이스 셀렉터 — 크로스 네임스페이스 제어

# 먼저 네임스페이스에 레이블 부여
kubectl label namespace monitoring purpose=monitoring
kubectl label namespace production env=production
kubectl label namespace staging env=staging
# production의 모든 Pod: monitoring 네임스페이스에서만 메트릭 수집 허용
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-monitoring
  namespace: production
spec:
  podSelector: {}                    # production의 모든 Pod
  policyTypes:
    - Ingress
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              purpose: monitoring    # monitoring 네임스페이스에서만
          podSelector:
            matchLabels:
              app: prometheus        # AND prometheus Pod만
      ports:
        - protocol: TCP
          port: 8080                 # 메트릭 포트만
# staging에서 production DB 접근 차단 (staging → production 격리)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-staging-to-prod-db
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: postgresql
  policyTypes:
    - Ingress
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              env: production        # production 네임스페이스에서만 허용
      ports:
        - protocol: TCP
          port: 5432

5. CIDR 기반 — 외부 IP 범위 제어

# 특정 외부 API만 호출 허용
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: payment-egress
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: payment-service
  policyTypes:
    - Egress
  egress:
    # DNS 허용
    - ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53
    # 내부 서비스 (클러스터 CIDR)
    - to:
        - podSelector:
            matchLabels:
              app: order-service
    # 외부 결제 API (특정 IP 대역만)
    - to:
        - ipBlock:
            cidr: 203.0.113.0/24         # 결제 게이트웨이 IP 대역
      ports:
        - protocol: TCP
          port: 443
    # 사내 네트워크
    - to:
        - ipBlock:
            cidr: 10.0.0.0/8
            except:
              - 10.0.100.0/24            # 민감한 서브넷 제외

6. 실전 아키텍처 — 3-Tier 네트워크 정책

프론트엔드 → 백엔드 → 데이터베이스의 전형적인 3계층 아키텍처에 Network Policy를 적용한다.

# 1. Default Deny
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: production
spec:
  podSelector: {}
  policyTypes: [Ingress, Egress]
---
# 2. DNS 허용
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-dns
  namespace: production
spec:
  podSelector: {}
  policyTypes: [Egress]
  egress:
    - ports:
        - {protocol: UDP, port: 53}
        - {protocol: TCP, port: 53}
---
# 3. Frontend: Ingress(외부) 허용, Backend으로만 Egress
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: frontend-policy
  namespace: production
spec:
  podSelector:
    matchLabels:
      tier: frontend
  policyTypes: [Ingress, Egress]
  ingress:
    - ports:
        - {protocol: TCP, port: 80}
        - {protocol: TCP, port: 443}
  egress:
    - to:
        - podSelector:
            matchLabels:
              tier: backend
      ports:
        - {protocol: TCP, port: 8080}
    - ports:
        - {protocol: UDP, port: 53}
---
# 4. Backend: Frontend에서만 Ingress, DB로만 Egress
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: backend-policy
  namespace: production
spec:
  podSelector:
    matchLabels:
      tier: backend
  policyTypes: [Ingress, Egress]
  ingress:
    - from:
        - podSelector:
            matchLabels:
              tier: frontend
      ports:
        - {protocol: TCP, port: 8080}
  egress:
    - to:
        - podSelector:
            matchLabels:
              tier: database
      ports:
        - {protocol: TCP, port: 5432}
    - to:
        - podSelector:
            matchLabels:
              app: redis
      ports:
        - {protocol: TCP, port: 6379}
    - ports:
        - {protocol: UDP, port: 53}
---
# 5. Database: Backend에서만 Ingress, Egress 없음
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: database-policy
  namespace: production
spec:
  podSelector:
    matchLabels:
      tier: database
  policyTypes: [Ingress, Egress]
  ingress:
    - from:
        - podSelector:
            matchLabels:
              tier: backend
      ports:
        - {protocol: TCP, port: 5432}
  egress:
    - ports:
        - {protocol: UDP, port: 53}

7. CNI 호환성 — 모든 클러스터에서 되는 것은 아니다

Network Policy는 CNI(Container Network Interface) 플러그인이 구현해야 동작한다. API에서 리소스는 생성되지만, 실제 트래픽 필터링은 CNI가 담당한다.

CNI Network Policy 지원 비고
Calico ✅ 완전 지원 가장 널리 사용, GlobalNetworkPolicy 확장
Cilium ✅ 완전 지원 eBPF 기반, L7 정책까지 지원
Weave Net ✅ 지원 기본적인 Ingress/Egress
Flannel ❌ 미지원 Calico와 함께 사용 (Canal)
AWS VPC CNI ⚠️ Calico 추가 필요 EKS에서 Calico addon 설치

확인 방법:

# CNI 확인
kubectl get pods -n kube-system | grep -E 'calico|cilium|weave|flannel'

# Network Policy 동작 테스트
kubectl run test-pod --image=busybox --rm -it -- wget -qO- --timeout=2 http://target-service
# 차단되면 timeout, 허용되면 응답 반환

8. 디버깅과 검증

# 적용된 Network Policy 목록
kubectl get networkpolicy -n production

# 특정 Policy 상세 확인
kubectl describe networkpolicy backend-policy -n production

# Pod에 적용된 정책 확인 (Calico)
calicoctl get workloadEndpoint -n production --output=yaml

# 연결 테스트 — 임시 Pod에서
kubectl run debug --image=nicolaka/netshoot --rm -it -n production -- bash

# 내부 연결 테스트
curl -v --connect-timeout 2 http://order-service:8080/health

# DNS 테스트
nslookup order-service.production.svc.cluster.local

# 포트 스캔
nc -zv postgresql 5432

9. Kustomize와 조합 — 환경별 Network Policy

# base/network-policies/kustomization.yaml
resources:
  - default-deny.yaml
  - allow-dns.yaml
  - backend-policy.yaml
  - database-policy.yaml

# overlays/dev/kustomization.yaml — 개발 환경에서는 완화
patches:
  - target:
      kind: NetworkPolicy
      name: default-deny-all
    patch: |
      - op: remove
        path: /spec/policyTypes/1    # Egress deny 제거 (개발 편의)

# overlays/prod/kustomization.yaml — 프로덕션은 모든 정책 적용
resources:
  - ../../base/network-policies

10. 운영 체크리스트

항목 권장 사항 위반 시 증상
CNI 확인 Calico/Cilium 등 지원 CNI 사용 Policy 생성되지만 실제 차단 안 됨
Default Deny 네임스페이스마다 적용 어떤 Pod이든 다른 Pod에 자유 접근
DNS 허용 Egress deny 시 UDP/TCP 53 반드시 허용 서비스 디스커버리 불가
from 배열 OR vs AND 구분 명확히 의도보다 넓거나 좁은 허용 범위
NS 레이블 네임스페이스에 레이블 부여 namespaceSelector 매칭 불가
연결 테스트 netshoot Pod으로 적용 후 검증 배포 후 통신 장애 발생

마무리 — 네트워크 격리는 선택이 아닌 필수다

Kubernetes Network Policy는 클러스터 내부 트래픽을 Pod 레벨에서 제어하는 유일한 네이티브 메커니즘이다. Default Deny로 시작하여 필요한 통신만 화이트리스트로 열어주는 것이 제로 트러스트 보안의 기본이다.

핵심은 세 가지다. 첫째, Default Deny를 먼저 적용하고 필요한 트래픽만 허용하라. 둘째, Egress deny 시 DNS(포트 53)를 반드시 허용하라. 셋째, from 배열의 OR과 AND 조건을 정확히 구분하라 — YAML 들여쓰기 한 줄 차이로 보안 구멍이 생긴다.

위로 스크롤
WordPress Appliance - Powered by TurnKey Linux