デフォルト拒否から始めるクラスタ構築
한국어 원문으로 표시합니다.
목표
네임스페이스 하나를 기본 거부 상태로 만든 뒤 필요한 통로만 하나씩 다시 여는 순서를 손에 익히고, 컨트롤 플레인 설정 파일에서 무엇을 확인해야 하는지 직접 써 봅니다.
왜 중요한가
쿠버네티스의 기본값은 전부 열려 있습니다. 정책이 하나도 없는 파드는 클러스터의 어떤 파드와도 통신할 수 있고, 노드의 메타데이터 주소에도 그대로 도달합니다. 그래서 보안 설계의 첫 동작은 무언가를 막는 규칙을 추가하는 것이 아니라, 전부 막아 놓고 필요한 것만 되돌리는 것입니다. 이 순서를 뒤집으면 "무엇을 빠뜨렸는지" 를 영원히 알 수 없습니다. 설정 파일 감사도 같은 발상입니다. 실행 중인 프로세스를 관찰하는 것보다 매니페스트 한 장을 읽는 쪽이 빠르고, 무엇이 빠져 있는지를 목록으로 만들 수 있습니다.
이 실습 환경에는 실제 데이터플레인이 없으므로 정책이 트래픽을 정말 끊는지는 확인할 수 없습니다. 채점은 오브젝트의 스펙이 의도대로 쓰였는지를 봅니다. 시험에서도 요구하는 것은 정확한 스펙입니다.
단계
- 네임스페이스
cks-net을 만들고 라벨tier=restricted를 붙인다. cks-net에 NetworkPolicydefault-deny-all을 만든다.podSelector는 비우고policyTypes에Ingress와Egress를 모두 적되, ingress/egress 규칙은 하나도 두지 않는다.cks-net에 NetworkPolicyallow-dns-egress를 만든다. 모든 파드를 대상으로 하고policyTypes는Egress만, egress 대상은kubernetes.io/metadata.name=kube-system라벨의 네임스페이스 안k8s-app=kube-dns라벨 파드이며 포트는 UDP 53 과 TCP 53 두 개다.cks-net에 NetworkPolicyallow-frontend-to-api를 만든다. 대상은app=api파드, ingress 허용 출처는 같은 네임스페이스의app=frontend파드, 포트는 TCP 8080 이다.cks-net에 NetworkPolicydeny-node-metadata를 만든다. 모든 파드 대상,policyTypes는Egress, egress 대상은ipBlock으로cidr: 0.0.0.0/0에except: [169.254.169.254/32]다.cks-net에kubernetes.io/tls타입 Secretshop-tls를 만든다.tls.crt와tls.key가 모두 비어 있지 않아야 한다.cks-net에 Ingressshop을 만든다.spec.tls[0]의hosts는shop.cks.local,secretName은shop-tls이고,spec.rules[0].host도shop.cks.local이며 경로/는 서비스api의 8080 포트로 보낸다./root/cks-cluster-setup/kube-apiserver.yaml에 CIS 권고를 반영한 정적 파드 매니페스트를 쓴다.kind: Pod, 컨테이너 이름은kube-apiserver,command배열에--anonymous-auth=false,--authorization-mode=Node,RBAC,--profiling=false,--audit-log-path=/var/log/kubernetes/audit.log,--enable-admission-plugins=NodeRestriction,PodSecurity가 들어가고--insecure-port로 시작하는 플래그는 하나도 없어야 한다.
참고
kubectl create ns cks-net뒤에kubectl label ns cks-net tier=restricted- 정책 초안은
kubectl create networkpolicy로 만들 수 없습니다.kubectl explain networkpolicy.spec.egress로 필드를 확인하며 YAML 을 직접 쓰는 편이 빠릅니다. - 자체 서명 인증서 예:
openssl req -x509 -newkey rsa:2048 -nodes -keyout tls.key -out tls.crt -days 365 -subj "/CN=shop.cks.local"뒤에kubectl create secret tls shop-tls --cert=tls.crt --key=tls.key -n cks-net - 흔한 실수 1: 기본 거부를 걸고 DNS egress 를 잊으면 애플리케이션이 "연결 거부"가 아니라 "이름 못 찾음"으로 죽습니다.
- 흔한 실수 2:
except대역이cidr밖에 있으면 API 서버가 정책을 거부합니다.
격리할 네임스페이스 만들기
네임스페이스 cks-net 을 만들고 라벨 tier=restricted 를 붙인다.
네임스페이스를 만들고 라벨을 붙입니다. kubectl create ns 뒤에 kubectl label ns 를 이어 쓰거나, 매니페스트 한 장으로 만들어도 됩니다.
ingress·egress 전부 막는 기본 정책
cks-net 에 NetworkPolicy default-deny-all 을 만든다. podSelector 는 비우고
policyTypes 에 Ingress 와 Egress 를 모두 적되, ingress/egress 규칙은 하나도 두지 않는다.
podSelector: {} 는 네임스페이스의 모든 파드를 고릅니다. policyTypes 에 방향을 적되 ingress/egress 규칙 목록은 아예 두지 않으면 그 방향은 전부 거부됩니다.
DNS 만 예외로 여는 egress
cks-net 에 NetworkPolicy allow-dns-egress 를 만든다. 모든 파드를 대상으로 하고
policyTypes 는 Egress 만, egress 대상은 kubernetes.io/metadata.name=kube-system 라벨의
네임스페이스 안 k8s-app=kube-dns 라벨 파드이며 포트는 UDP 53 과 TCP 53 두 개다.
kube-dns 파드는 kube-system 네임스페이스에 있고 k8s-app=kube-dns 라벨을 답니다. 네임스페이스는 kubernetes.io/metadata.name 라벨로 고를 수 있습니다. 53 포트는 UDP 와 TCP 둘 다 필요합니다.
필요한 파드 쌍만 여는 ingress
cks-net 에 NetworkPolicy allow-frontend-to-api 를 만든다. 대상은 app=api 파드,
ingress 허용 출처는 같은 네임스페이스의 app=frontend 파드, 포트는 TCP 8080 이다.
podSelector 로 받는 쪽을 고르고, ingress[].from[].podSelector 로 보내는 쪽을 고릅니다. 포트를 적지 않으면 모든 포트가 열립니다.
노드 메타데이터로 나가는 길 도려내기
cks-net 에 NetworkPolicy deny-node-metadata 를 만든다. 모든 파드 대상, policyTypes 는
Egress, egress 대상은 ipBlock 으로 cidr: 0.0.0.0/0 에 except: [169.254.169.254/32] 다.
ipBlock 은 cidr 하나에 except 목록을 붙일 수 있고, except 의 대역은 반드시 cidr 안에 들어 있어야 합니다.
Ingress 용 TLS Secret
cks-net 에 kubernetes.io/tls 타입 Secret shop-tls 를 만든다. tls.crt 와 tls.key 가
모두 비어 있지 않아야 한다.
kubernetes.io/tls 타입의 Secret 은 tls.crt 와 tls.key 두 키가 반드시 있어야 합니다. openssl 로 자체 서명 인증서를 만들거나, 매니페스트에 base64 값을 직접 넣어도 됩니다.
Ingress 에 TLS 연결하기
cks-net 에 Ingress shop 을 만든다. spec.tls[0] 의 hosts 는 shop.cks.local,
secretName 은 shop-tls 이고, spec.rules[0].host 도 shop.cks.local 이며 경로 / 는
서비스 api 의 8080 포트로 보낸다.
Ingress 의 spec.tls 는 hosts 목록과 secretName 을 짝지어 적습니다. spec.rules 의 호스트와 spec.tls[].hosts 가 어긋나면 인증서가 붙지 않습니다.
kube-apiserver 매니페스트 감사
/root/cks-cluster-setup/kube-apiserver.yaml 에 CIS 권고를 반영한 정적 파드 매니페스트를
쓴다. kind: Pod, 컨테이너 이름은 kube-apiserver, command 배열에
--anonymous-auth=false, --authorization-mode=Node,RBAC, --profiling=false,
--audit-log-path=/var/log/kubernetes/audit.log,
--enable-admission-plugins=NodeRestriction,PodSecurity 가 들어가고
--insecure-port 로 시작하는 플래그는 하나도 없어야 한다.
정적 파드 매니페스트는 kind: Pod 이고 플래그는 spec.containers[0].command 배열에 들어갑니다. CIS 는 익명 인증 차단, Node+RBAC 인가, 프로파일링 비활성화, NodeRestriction 어드미션을 권고하고, 인증 없는 평문 포트 플래그는 아예 없어야 합니다.