CKS — Kubernetes Security Specialist
CKS Mock Exam A
한국어 원문으로 표시합니다.
목표
실제 CKS 와 같은 조건에서 과제 17개를 120분 안에 풉니다. 합격선은 67% 이고 부분 점수제이므로, 17개 중 12개를 통과하면 완료로 처리됩니다.
모의고사입니다. 힌트와 정답지를 보지 말고 먼저 끝까지 풀어 보십시오. 막힌 과제는 표시해 두고 넘어갔다가 남은 시간에 돌아오는 편이 낫습니다. 채점은 언제든 눌러도 되고, 여러 번 눌러도 결과가 달라지지 않습니다.
왜 중요한가
CKS 는 CKA 합격이 선수 조건인 유일한 시험입니다. 쿠버네티스를 다루는 손은 이미 갖췄다고 전제하므로, 문제는 "무엇을 만드세요" 보다 "이 설정의 무엇이 위험한지 찾아 고치세요" 쪽에 가깝습니다. 방어와 진단이 시험 대상이고, 공격 기법은 다루지 않습니다.
실무에서도 같습니다. 클러스터가 뚫리는 자리는 대개 새로 만든 기능이 아니라 기본값을 그대로 둔 곳입니다. 네임스페이스에 파드 보안 승인을 걸지 않았고, 기본 서비스 계정 토큰이 모든 파드에 마운트되고 있고, 이미지가 움직이는 태그로 배포되고 있습니다. 이 시험이 묻는 것이 정확히 그 목록입니다.
시험 환경 (실제 시험에서 확인된 사실)
- 볼 수 있는 문서는
kubernetes.io/docs,kubernetes.io/blog,falco.org/docs,kubernetes-sigs.github.io/bom,etcd.io/docs,kubernetes.github.io/ingress-nginx,docs.cilium.io,istio.io/latest/docs입니다. - 문서 사이트 안에서 검색하는 것은 허용되지만, 결과가 허용 목록 밖으로 나가면 안 됩니다.
k별칭과 bash 자동완성이 이미 설정되어 있습니다. 별칭을 만드는 데 시간을 쓰지 마십시오.- 과제마다 지정된 호스트로
ssh해서 작업하며, 중첩 ssh 는 지원되지 않습니다. 다음 과제로 넘어가기 전에exit하십시오. - 도구는 SSH 로 접속한 호스트에만 있습니다.
- 복사는
Ctrl+Shift+C, 붙여넣기는Ctrl+Shift+V입니다.
이 모의고사 환경에서 다른 점
이 실습의 클러스터는 파드 안에서 도는 1인용 클러스터입니다. kube-apiserver 는 진짜라서 매니페스트, RBAC, 파드 보안 승인, 승인 정책은 실제로 동작하고 실제로 거부합니다. 다만 컨테이너를 실제로 돌리는 런타임이 없으므로 다음 두 가지는 확인할 수 없습니다.
- NetworkPolicy 는 적용되지만 실제 차단은 일어나지 않습니다. CNI 가 없기 때문입니다. 1번과 2번 과제는 정책 객체가 올바른지만 봅니다.
- seccomp 와 AppArmor 프로파일은 커널에 적재되지 않습니다. 7번과 8번 과제는 프로파일 파일의 내용과 파드에 물린 배선을 봅니다.
나머지는 전부 살아 있는 클러스터에서 다시 계산해 채점합니다. 권한은
kubectl auth can-i 로, 승인 정책은 서버 dry-run 으로 그 순간의 판정을
다시 물어봅니다.
단계
Cluster Setup
- 네임스페이스
prod를 만들고, 그 안의 모든 파드에 대해 인그레스와 이그레스를 함께 기본 거부하는 NetworkPolicydefault-deny를 만드십시오. 허용 규칙은 하나도 두지 않습니다. - 네임스페이스
prod에서app=web라벨이 붙은 파드가 노드 메타데이터 엔드포인트169.254.169.254/32로 나가지 못하게 하되, 그 밖의 이그레스는 그대로 열어 두는 NetworkPolicydeny-metadata를 만드십시오. CN=shop.internal인 자체 서명 인증서로 네임스페이스prod에 TLS 시크릿web-tls를 만들고, 호스트shop.internal을 그 시크릿으로 TLS 종료하는 인그레스web을 만드십시오. 백엔드는 서비스web의 80 포트입니다.
Cluster Hardening
- 네임스페이스
prod에 서비스 계정report-runner를 만들고, 이 계정이prod안에서 파드를get,list,watch만 할 수 있게 Rolepod-reader와 RoleBindingreport-runner-pod-reader를 만드십시오. 그 밖의 권한은 주지 않습니다. - 네임스페이스
prod의default서비스 계정이 토큰을 자동 마운트하지 않게 하십시오. 그리고prod에 파드frontend를 만들되 서비스 계정report-runner를 쓰고, 파드 스펙에서도 토큰 자동 마운트를 끄십시오. - 보안 감사자 그룹
security-audit이 클러스터 전역에서 파드, 네임스페이스, 네트워크폴리시를 읽기만 할 수 있도록 ClusterRolesecurity-auditor와 ClusterRoleBindingsecurity-auditor를 만드십시오. 쓰기 권한과 시크릿 접근은 주지 않습니다.
System Hardening
/root/exam/seccomp/audit.json에 seccomp 프로파일을 작성하십시오. 기본 동작은SCMP_ACT_ERRNO이고, 최소한 몇 개의 시스템 콜은SCMP_ACT_ALLOW로 열어야 합니다. 그리고 네임스페이스prod에 파드probe를 만들어 이 프로파일을Localhost방식으로profiles/audit.json경로에서 쓰게 하십시오./root/exam/apparmor/k8s-deny-write에 AppArmor 프로파일을 작성하십시오. 프로파일 이름은k8s-deny-write이고,#include <tunables/global>줄과 모든 쓰기를 막는deny /** w,규칙이 있어야 합니다. 그리고 네임스페이스prod에 Deploymentlogshipper를 만들어 파드 템플릿의securityContext.appArmorProfile이 이 프로파일을Localhost로 쓰게 하십시오.
Minimize Microservice Vulnerabilities
- 네임스페이스
payments를 만들고, 파드 보안 승인의enforce,audit,warn세 모드를 모두restricted로 걸고 세 모드의 버전을latest로 고정하십시오. - 네임스페이스
payments에restricted를 위반하는 파드bad-pod를 적용해 보고, 거부된 응답을 표준 오류까지 포함해/root/exam/denied.txt에 저장하십시오. 파드는 실제로 만들어지면 안 됩니다. - RuntimeClass
gvisor(handler 는runsc)를 만들고, 네임스페이스payments에 레플리카 2개짜리 Deploymentcheckout을 만드십시오. 파드 템플릿은 이 RuntimeClass 를 쓰고restricted를 통과해야 합니다. 컨테이너 이름은app이고, 파드 수준에runAsNonRoot: true와seccompProfile.type: RuntimeDefault를, 컨테이너 수준에allowPrivilegeEscalation: false,capabilities.drop: [ALL],readOnlyRootFilesystem: true를 두십시오.
Supply Chain Security
- 네임스페이스
supply를 만들고 Deploymentpayments-api를 만드십시오. 컨테이너 이름은api이고, 이미지는 태그가 아니라 다이제스트로 고정해야 합니다. 값은registry.internal/payments-api@sha256:140eab0459241fb1643767dd4cc3576d282b0059a6328521e714cb545fa4adea이며imagePullPolicy는IfNotPresent입니다. /root/exam/Dockerfile에 배포용 이미지를 짓는 Dockerfile 을 작성하십시오. 빌드 단계와 실행 단계를 나눈 멀티스테이지여야 하고, 모든FROM의 베이스를@sha256:다이제스트로 고정해야 하며, 산출물은COPY --from=으로만 가져와야 합니다.ADD를 쓰지 말고, 마지막USER는 0 이 아닌 숫자 UID 여야 하며,ENV나ARG에 비밀로 보이는 이름을 남기지 마십시오.- 승인되지 않은 레지스트리의 이미지를 막으십시오. ValidatingAdmissionPolicy
trusted-images와 ValidatingAdmissionPolicyBindingtrusted-images를 만들고, 모든 컨테이너 이미지가registry.internal/로 시작할 것을 요구하게 하십시오. 바인딩의validationActions는Deny이고, 적용 범위는 네임스페이스supply뿐입니다. 다른 네임스페이스는 영향을 받으면 안 됩니다.
Monitoring, Logging and Runtime Security
/root/exam/audit/policy.yaml에 감사 정책을 작성하십시오.apiVersion은audit.k8s.io/v1,kind는Policy, 최상위omitStages는RequestReceived하나입니다. 규칙은 정확히 세 개이고 순서가 중요합니다. 첫째, 코어 그룹의secrets와configmaps를Metadata레벨로 남깁니다. 둘째,pods/exec,pods/attach,pods/portforward를RequestResponse레벨로 남깁니다. 셋째, 대상을 적지 않은Metadata규칙으로 나머지 전부를 받습니다./root/exam/audit/kube-apiserver.yaml에 감사 로그를 켠 kube-apiserver 정적 파드 매니페스트를 작성하십시오. 첫 컨테이너 이름은kube-apiserver이고 다음 다섯 플래그가 있어야 합니다.--audit-policy-file=/etc/kubernetes/audit/policy.yaml,--audit-log-path=/var/log/kubernetes/audit/audit.log,--audit-log-maxage=30,--audit-log-maxbackup=10,--audit-log-maxsize=100. 그리고 hostPath 볼륨audit-policy와audit-logs를 각각 마운트하되, 정책 마운트는readOnly: true이고 로그 마운트는 읽기 전용이면 안 됩니다.- 네임스페이스
prod에 Deploymentledger를 만드십시오. 컨테이너 이름은app이고readOnlyRootFilesystem: true,allowPrivilegeEscalation: false,capabilities.drop: [ALL]을 둡니다. 쓰기가 필요한/tmp는emptyDir볼륨scratch로 제공하고,hostPath볼륨과hostNetwork,hostPID,hostIPC는 쓰지 마십시오.
참고
- 라벨을 다시 걸 때는
kubectl label ... --overwrite를 쓰십시오. 없이 쓰면 이미 있는 라벨에서 오류가 납니다. - 파드 보안 승인은 Deployment 를 막지 않고 그 Deployment 가 만드는 파드를
막습니다. Deployment 는 만들어졌는데 파드가 하나도 없다면
kubectl -n <네임스페이스> describe rs로 사유를 보십시오. - 서비스 계정에 준 권한이 맞는지는 짐작하지 말고
kubectl auth can-i <동사> <자원> --as=system:serviceaccount:<ns>:<이름>으로 확인하십시오. 그룹은--as-group으로 흉내 냅니다. - 흔한 실수 두 가지입니다. NetworkPolicy 에
policyTypes를 적지 않으면 이그레스는 통제되지 않습니다. 그리고automountServiceAccountToken은 서비스 계정과 파드 양쪽에 있고, 파드 쪽이 이깁니다.
prod 네임스페이스 기본 거부
NetworkPolicy 의 podSelector 를 빈 객체로 두면 그 네임스페이스의 모든 파드를 고릅니다. 그리고 policyTypes 에 Ingress 와 Egress 를 모두 적어야 양방향이 막힙니다. 규칙(ingress/egress)을 아예 적지 않으면 '허용할 것이 없다' 는 뜻이 됩니다.
노드 메타데이터 차단
ipBlock 은 cidr 로 범위를 열고 except 로 그 안의 일부를 도려냅니다. 전부 허용하면서 한 주소만 막으려면 cidr 은 0.0.0.0/0 이고 except 에 그 주소를 적습니다. 이 과제는 Egress 만 다룹니다.
TLS 종료 인그레스
openssl req -x509 -nodes 로 키와 인증서를 한 번에 만들고, kubectl create secret tls 로 넣습니다. 인그레스는 spec.tls 에 호스트와 secretName 을 적어야 그 호스트를 그 인증서로 종료합니다. spec.rules 의 host 와 같은 이름이어야 합니다.
서비스 계정 최소 권한
Role 은 네임스페이스 안에서만 효력이 있습니다. verbs 에 적지 않은 동사는 허용되지 않으므로 get, list, watch 만 적으면 됩니다. RoleBinding 의 주체는 --serviceaccount=<네임스페이스>:<이름> 형식으로 지정합니다. 채점은 kubectl auth can-i 로 경계를 양쪽에서 확인합니다.
토큰 자동 마운트 차단
automountServiceAccountToken 은 서비스 계정과 파드 양쪽에 있고 파드 쪽이 이깁니다. 서비스 계정 쪽은 kubectl patch serviceaccount 로 끄고, 파드는 spec 에 직접 적습니다. 두 곳 모두 false 여야 합니다.
읽기 전용 클러스터 감사자
네임스페이스를 가로지르는 권한은 Role 이 아니라 ClusterRole 로 주고 ClusterRoleBinding 으로 묶습니다. 주체 종류는 ServiceAccount 가 아니라 Group 이며 apiGroup 이 rbac.authorization.k8s.io 입니다. networkpolicies 는 코어 그룹이 아니라 networking.k8s.io 그룹에 있습니다.
seccomp 프로파일 작성과 연결
seccomp 프로파일은 defaultAction 으로 기본 판정을 정하고 syscalls 배열에서 예외를 엽니다. 기본이 거부(SCMP_ACT_ERRNO)면 허용 목록이 반드시 있어야 합니다. 파드에서는 securityContext.seccompProfile 의 type 을 Localhost 로 두고 localhostProfile 에 노드의 seccomp 루트 기준 상대 경로를 적습니다.
AppArmor 프로파일 작성과 연결
AppArmor 프로파일은 profile <이름> { ... } 블록이고 안에 접근 규칙을 적습니다. 쓰기를 전부 막는 규칙은 deny 로 시작해 경로 패턴과 권한 문자, 쉼표로 끝납니다. 파드 쪽은 securityContext.appArmorProfile 필드를 씁니다. 예전 애너테이션 방식이 아니라 필드 방식으로 적으십시오.
파드 보안 승인 restricted
파드 보안 승인은 네임스페이스 라벨로 켭니다. 모드는 enforce, audit, warn 세 가지이고 각각 pod-security.kubernetes.io/<모드> 라벨에 수준을 적습니다. 버전은 같은 접두어에 -version 을 붙인 라벨입니다. 라벨은 여섯 개가 됩니다.
위반 파드가 거부되는지 확인
거부 메시지는 표준 오류로 나옵니다. 리다이렉션에 2>&1 을 빠뜨리면 빈 파일이 남습니다. 위반 파드는 어렵게 만들 것 없이 securityContext 를 아무것도 적지 않으면 됩니다. restricted 는 네 가지를 한꺼번에 요구하므로 그대로 걸립니다.
샌드박스 런타임 워크로드
RuntimeClass 는 클러스터 범위 자원이고 handler 하나만 있으면 만들어집니다. 파드 템플릿에서는 runtimeClassName 으로 가리킵니다. restricted 는 네 가지를 요구하는데 두 개는 파드 수준, 두 개는 컨테이너 수준에 적습니다. Deployment 는 만들어졌는데 파드가 없다면 restricted 에 걸린 것입니다.
이미지를 다이제스트로 고정
태그는 움직이고 다이제스트는 움직이지 않습니다. 다이제스트로 고정할 때는 이름과 값을 쌍점이 아니라 골뱅이로 잇고, 태그를 함께 적지 않습니다. 다이제스트로 고정하면 imagePullPolicy 를 Always 로 둘 이유가 없습니다.
이미지 빌드 위생
멀티스테이지는 FROM 을 두 번 이상 쓰고 앞 단계에 AS 로 이름을 붙인 뒤 COPY --from= 으로 산출물만 가져오는 방식입니다. 베이스도 태그가 아니라 다이제스트로 고정합니다. ADD 는 원격 자원을 가져오고 압축을 자동으로 풀어서 무엇이 들어올지 예측하기 어렵습니다. USER 에 이름을 쓰면 그 이름이 이미지 안에 있어야 하므로 숫자 UID 가 안전합니다.
신뢰 레지스트리 강제
ValidatingAdmissionPolicy 는 CEL 식으로 판정하고, 실제로 막으려면 바인딩의 validationActions 에 Deny 가 있어야 합니다. Warn 만 두면 경고만 나가고 파드는 만들어집니다. 적용 범위는 바인딩의 matchResources 로 좁힙니다. 네임스페이스 하나만 고르려면 kubernetes.io/metadata.name 라벨을 쓰면 됩니다. 이 라벨은 모든 네임스페이스에 자동으로 붙습니다.
감사 정책 작성
감사 정책은 위에서부터 훑어 처음 맞는 규칙에서 멈춥니다. 그래서 포괄 규칙을 앞에 두면 뒤의 세밀한 규칙은 영영 쓰이지 않습니다. 하위 리소스는 pods/exec 처럼 빗금으로 적습니다. omitStages 는 최상위에 두면 모든 규칙에 적용됩니다.
감사 로그 배선
플래그만 적으면 apiserver 는 정책 파일을 찾지 못합니다. 정적 파드는 노드 파일 시스템을 hostPath 볼륨으로 물려야 그 경로를 볼 수 있습니다. 정책 파일은 읽기만 하면 되고 로그 디렉터리는 써야 하므로 두 마운트의 readOnly 가 달라집니다. hostPath 에 type 을 적으면 파일과 디렉터리를 구분해 줍니다.
런타임 불변성
읽기 전용 루트로 만들면 대부분의 애플리케이션이 임시 파일을 쓰지 못해 죽습니다. 그래서 쓰기가 필요한 경로만 골라 볼륨으로 열어 줍니다. 노드 파일 시스템을 그대로 물리는 것은 격리를 깨뜨리므로 emptyDir 을 씁니다. 호스트 네임스페이스를 공유하면 컨테이너 경계 자체가 없어집니다.