Certified Kubernetes Security Specialist (CKS) #20 풀스케일 실기 모의고사: 16개 작업 + 해설
#1 시험 환경에서 #19 시험 팁까지 전 6개 도메인을 모두 다뤘습니다. 이번 글은 실제 CKS 형식에 맞춘 실기 모의고사입니다. 전 도메인을 통합한 16개 작업으로 구성했고, 각 작업별로 배점이 정해져 있습니다.
권장 제한 시간은 실제 시험과 같은 2시간입니다. 합격선은 **67%**이며, 16개 작업의 배점을 합산해 채점합니다. 한 작업에 막히면 표시해 두고 넘어간 뒤 배점 높고 손에 익은 작업부터 점수를 쌓는 운영이 합격선을 넘기는 길입니다.
CKS도 클러스터가 여러 개라 context 전환을 가장 먼저 하는 습관이 오답을 막습니다. 각 작업은 지정된 컨텍스트에서 풀어야 채점되며, AppArmor,seccomp,Falco처럼 노드 안으로 들어가 리눅스 보안 도구를 직접 다루는 작업이 많으므로 SSH,systemctl,파일 편집 감각이 함께 필요합니다. 각 작업은 먼저 스스로 끝까지 푼 뒤 정답을 펼쳐 보십시오. 정답을 먼저 읽으면 손이 익지 않습니다.
풀이 방법 #
- CKA가 보유 전제이므로 노드에 SSH로 들어가
systemctl,파일 편집을 하는 작업은 익숙해야 합니다. 로컬에서 어렵다면 클라우드 VM 두세 대로 control plane 한 대와 워커 한 대를 세워 두십시오. AppArmor,seccomp,Falco는 단일 노드 minikube로는 감각이 살지 않으므로 커널 보안 모듈이 켜진 일반 리눅스 노드에서 푸는 것이 실전에 가깝습니다. - 작업마다 지정된 컨텍스트를 먼저 전환합니다. 이 시리즈에서 반복한 대로 컨텍스트 오설정은 정답을 써도 0점입니다.
k config use-context <문제에서 지정한 컨텍스트>- SSH로 노드에 들어가는 작업이 있으므로, 시험에서 제시하는 호스트명(
node01등)으로 접속이 되는지 미리 확인합니다. 노드 위에서 root 권한이 필요하면sudo -i로 전환합니다. - 16개를 끝까지 푼 뒤 정답을 펼쳐 한 번에 채점합니다. 중간에 정답을 보면 실제 시험 감각이 흐려집니다. kubernetes.io/docs와 함께 Falco,Trivy,AppArmor,gVisor 공식 문서 열람이 허용되므로, 각 도구 문서에서 프로파일 문법과 명령 옵션을 어디서 찾는지 미리 익혀 두면 시간을 아낍니다.
도메인 분포 #
실제 CKS의 도메인 비중에 맞춰 16개 작업을 배치했습니다. 뒤쪽 세 도메인(마이크로서비스,공급망,런타임)이 각각 20%로 합쳐 60%를 차지하므로 작업 수도 그쪽에 더 실었습니다.
| # | 도메인 | 작업 수 | 작업 번호 |
|---|---|---|---|
| 1 | Cluster Setup | 2 | 1, 2 |
| 2 | Cluster Hardening | 2 | 3, 4 |
| 3 | System Hardening | 3 | 5, 6, 7 |
| 4 | Minimize Microservice Vulnerabilities | 3 | 8, 9, 10 |
| 5 | Supply Chain Security | 3 | 11, 12, 13 |
| 6 | Monitoring, Logging and Runtime Security | 3 | 14, 15, 16 |
배점은 도메인 비중과 작업 난이도를 반영해 합계 100점으로 두었습니다. 채점 기준은 글 끝에 정리했습니다.
작업 1 (6점): Cluster Setup #
컨텍스트 cluster1의 네임스페이스 payments에 default deny를 적용하세요. 이 네임스페이스의 모든 Pod는 ingress와 egress가 기본 차단되어야 하되, 단 모든 Pod가 클러스터 DNS(kube-system의 53 포트, TCP,UDP)로는 나갈 수 있어야 합니다.
정답
모든 ingress,egress를 막는 default deny와 DNS egress 허용을 함께 적용합니다.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: payments
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns
namespace: payments
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53k apply -f deny.yaml
k -n payments get netpol해설: 빈 podSelector: {}는 네임스페이스의 모든 Pod를 대상으로 삼습니다. policyTypes에 Ingress와 Egress를 모두 넣고 규칙 본문을 비우면 양방향 default deny가 됩니다. NetworkPolicy는 누적 허용이라 두 번째 정책으로 DNS만 다시 열어 줍니다. egress를 막으면 DNS 조회까지 끊겨 모든 통신이 이름 해석 단계에서 실패하는 것이 가장 흔한 함정이므로 53 포트 허용을 빠뜨리지 않습니다.
작업 2 (6점): Cluster Setup #
control plane 노드에서 kube-bench를 실행해 CIS benchmark를 점검한 결과, --profiling이 켜져 있고 --insecure-port가 0이 아니라는 FAIL이 나왔습니다. kube-apiserver의 두 설정을 CIS 권고에 맞게 고쳐 FAIL을 해소하세요.
정답
control plane 노드에서 apiserver static Pod 매니페스트를 수정합니다.
ssh cluster1-controlplane
sudo -i
vim /etc/kubernetes/manifests/kube-apiserver.yaml문제가 된 두 플래그를 다음과 같이 둡니다.
- --profiling=false
- --insecure-port=0kubelet이 매니페스트 변경을 감지해 apiserver Pod를 재생성할 때까지 기다린 뒤 다시 점검합니다.
crictl ps | grep kube-apiserver
kube-bench run --targets master해설: --profiling=false는 디버깅용 프로파일링 엔드포인트를 닫아 정보 노출을 줄이라는 CIS 권고이고, --insecure-port=0은 인증 없는 평문 포트를 끄라는 권고입니다. apiserver는 static Pod라 매니페스트를 고치면 kubelet이 자동 재생성하므로, 문법 오류로 Pod가 안 뜨면 crictl logs로 원인을 확인합니다. 최신 버전에서는 --insecure-port 자체가 제거되었을 수 있으므로 점검 결과의 플래그 이름을 그대로 따르는 것이 안전합니다.
작업 3 (7점): Cluster Hardening #
컨텍스트 cluster1에서 ClusterRole cluster-admin이 ClusterRoleBinding으로 ServiceAccount dev:ci-runner에 묶여 있어 권한이 과합니다. 이 바인딩을 제거하고, 대신 네임스페이스 dev에서 Pod와 Deployment를 조회(get,list,watch)만 할 수 있는 최소 권한 Role과 RoleBinding으로 교체하세요.
정답
과한 ClusterRoleBinding을 지우고 최소 권한 Role을 새로 묶습니다.
k delete clusterrolebinding ci-runner-admin
k -n dev create role ci-reader \
--verb=get,list,watch \
--resource=pods,deployments.apps
k -n dev create rolebinding ci-runner-read \
--role=ci-reader \
--serviceaccount=dev:ci-runner권한이 의도대로 좁혀졌는지 검증합니다.
k -n dev auth can-i list pods --as=system:serviceaccount:dev:ci-runner
k -n dev auth can-i delete pods --as=system:serviceaccount:dev:ci-runner해설: 최소 권한 원칙은 주체에게 필요한 동사,리소스,네임스페이스만 주는 것입니다. cluster-admin은 전 클러스터의 모든 동작을 허용하므로 CI 러너에게는 과합니다. 검증에서 list pods는 yes, delete pods는 no가 나와야 의도대로 좁혀진 것입니다. ClusterRole은 그대로 두고 바인딩만 제거하는 것이 핵심이며, ClusterRole 자체를 지우면 다른 주체에 영향을 줄 수 있습니다.
작업 4 (7점): Cluster Hardening #
네임스페이스 legacy의 ServiceAccount default와 그 토큰을 마운트해 도는 Pod report가 API 서버에 불필요하게 접근합니다. report Pod가 ServiceAccount 토큰을 자동 마운트하지 않도록 막으세요. Pod를 재생성해도 됩니다.
정답
Pod 매니페스트에 automountServiceAccountToken: false를 넣어 재생성합니다.
k -n legacy get pod report -o yaml > report.yamlspec 아래에 다음 필드를 추가합니다.
spec:
automountServiceAccountToken: false
containers:
- name: report
image: report:1.0k -n legacy delete pod report
k apply -f report.yaml마운트가 사라졌는지 확인합니다.
k -n legacy exec report -- ls /var/run/secrets/kubernetes.io/serviceaccount해설: automountServiceAccountToken: false는 Pod의 /var/run/secrets/kubernetes.io/serviceaccount 경로에 토큰을 주입하지 않게 해, 컨테이너가 탈취돼도 API 서버 자격이 새지 않게 합니다. 이 필드는 Pod spec과 ServiceAccount 양쪽에 둘 수 있는데, Pod spec 쪽이 우선합니다. 검증 명령에서 디렉터리가 없다는 오류가 나면 정상이며, 토큰이 더는 마운트되지 않는다는 뜻입니다.
작업 5 (8점): System Hardening #
워커 노드 node01에 AppArmor 프로파일 k8s-deny-write를 로드하고, 네임스페이스 apps의 Pod guarded(이미지 nginx)가 이 프로파일을 컨테이너 web에 적용하도록 만드세요. 프로파일은 /etc/apparmor.d/k8s-deny-write에 있으며 파일 쓰기를 거부하는 enforce 모드 프로파일입니다.
정답
노드에서 프로파일을 enforce 모드로 로드합니다.
ssh node01
sudo -i
apparmor_parser -q /etc/apparmor.d/k8s-deny-write
aa-status | grep k8s-deny-write
exitPod에 AppArmor 프로파일을 지정합니다.
apiVersion: v1
kind: Pod
metadata:
name: guarded
namespace: apps
spec:
containers:
- name: web
image: nginx
securityContext:
appArmorProfile:
type: Localhost
localhostProfile: k8s-deny-writek apply -f guarded.yaml
k -n apps get pod guarded해설: AppArmor 프로파일은 컨테이너가 뜨는 노드에 미리 로드되어 있어야 적용되므로 apparmor_parser로 enforce로드하는 단계가 빠지면 Pod가 Blocked 상태로 막힙니다. 최신 쿠버네티스는 securityContext.appArmorProfile 필드를 쓰며, type: Localhost와 localhostProfile에 프로파일 이름(파일 경로가 아닌 프로파일 이름)을 적습니다. 구버전의 container.apparmor.security.beta.kubernetes.io/<컨테이너> 어노테이션 방식도 같은 효과지만 시험 버전의 방식을 따릅니다.
작업 6 (8점): System Hardening #
네임스페이스 apps에 Pod seccomp-web(이미지 nginx)을 만들되, 컨테이너가 RuntimeDefault seccomp 프로파일을 쓰도록 하세요. 이 프로파일은 컨테이너 런타임의 기본 차단 목록으로 위험한 시스템 콜을 제한합니다.
정답
securityContext.seccompProfile을 RuntimeDefault로 둡니다.
apiVersion: v1
kind: Pod
metadata:
name: seccomp-web
namespace: apps
spec:
securityContext:
seccompProfile:
type: RuntimeDefault
containers:
- name: web
image: nginxk apply -f seccomp-web.yaml
k -n apps get pod seccomp-web해설: RuntimeDefault는 컨테이너 런타임(containerd 등)이 제공하는 기본 seccomp 프로파일을 적용해 별도 파일 없이 위험한 시스템 콜을 차단합니다. Pod 수준 securityContext에 두면 모든 컨테이너에 상속되고, 컨테이너 수준에 두면 그 컨테이너만 적용됩니다. 커스텀 프로파일이 필요하면 type: Localhost와 localhostProfile로 노드의 /var/lib/kubelet/seccomp/profiles 아래 JSON을 가리키지만, 이 작업은 기본 프로파일이라 파일이 필요 없습니다.
작업 7 (7점): System Hardening #
네임스페이스 apps의 Deployment api가 과한 리눅스 capability를 들고 돕니다. 이 Deployment의 컨테이너가 모든 capability를 버리고 NET_BIND_SERVICE 하나만 다시 갖도록, 그리고 권한 상승을 막도록 securityContext를 고치세요.
정답
Deployment의 컨테이너 securityContext를 수정합니다.
k -n apps edit deploy api컨테이너 spec에 다음을 둡니다.
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE롤아웃이 끝난 뒤 새 Pod에 반영됐는지 확인합니다.
k -n apps rollout status deploy api
k -n apps get pod -l app=api -o jsonpath='{.items[0].spec.containers[0].securityContext}'해설: capabilities.drop: [ALL]로 모든 권한을 먼저 버리고 필요한 것만 add로 되살리는 것이 최소 권한 패턴입니다. NET_BIND_SERVICE는 1024 미만 포트 바인딩에 필요한 권한이라 80 포트를 여는 웹 서버에 흔히 남깁니다. allowPrivilegeEscalation: false는 setuid 바이너리 등으로 프로세스가 부모보다 큰 권한을 얻는 것을 막습니다. Deployment를 고치면 template이 바뀌어 Pod가 새로 떠야 반영됩니다.
작업 8 (7점): Minimize Microservice Vulnerabilities #
네임스페이스 restricted에 Pod Security Admission의 restricted 표준을 enforce 모드로 강제하세요. 그다음 이 표준을 위반하는(root로 도는) Pod를 만들면 거부되는지 확인하세요.
정답
네임스페이스에 PSA 라벨을 붙입니다.
k label namespace restricted \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/enforce-version=latest위반 Pod 생성을 시도해 거부되는지 확인합니다.
k -n restricted run rooty --image=nginx거부 메시지를 확인한 뒤, 표준을 통과하는 Pod로 검증합니다.
apiVersion: v1
kind: Pod
metadata:
name: compliant
namespace: restricted
spec:
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
containers:
- name: web
image: nginx
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL해설: Pod Security Admission은 네임스페이스 라벨로 동작하며 enforce,audit,warn 세 모드가 있습니다. enforce=restricted는 위반 Pod를 아예 생성 거부합니다. restricted 표준은 runAsNonRoot,allowPrivilegeEscalation: false,capabilities.drop: [ALL],seccompProfile을 모두 요구하므로, 평범한 nginx Pod는 root로 떠서 거부됩니다. 라벨 키가 pod-security.kubernetes.io/enforce로 정확해야 적용됩니다.
작업 9 (7점): Minimize Microservice Vulnerabilities #
control plane에서 etcd 저장 데이터의 Secret 암호화를 켜세요. AES-CBC(aescbc) 방식으로 새 Secret이 암호화되어 저장되도록 EncryptionConfiguration을 구성하고 apiserver에 적용하세요.
정답
control plane 노드에서 암호화 설정 파일을 만듭니다.
ssh cluster1-controlplane
sudo -i
vim /etc/kubernetes/enc/enc.yamlapiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- aescbc:
keys:
- name: key1
secret: <base64로 인코딩한 32바이트 키>
- identity: {}apiserver 매니페스트에 설정 파일을 연결합니다.
- --encryption-provider-config=/etc/kubernetes/enc/enc.yaml/etc/kubernetes/enc를 apiserver 컨테이너에 hostPath 볼륨으로 마운트한 뒤, 기존 Secret까지 재암호화합니다.
k get secrets --all-namespaces -o json | kubectl replace -f -해설: providers 목록의 순서가 중요합니다. 첫 provider가 쓰기 암호화에 쓰이고, 읽기는 위에서부터 시도하므로 aescbc를 맨 앞에, identity(평문)를 뒤에 둡니다. 키는 32바이트를 base64로 인코딩해야 하며, head -c 32 /dev/urandom | base64로 만듭니다. 설정을 켜도 기존 Secret은 자동 재암호화되지 않으므로 replace로 다시 써야 실제로 암호문이 됩니다.
작업 10 (6점): Minimize Microservice Vulnerabilities #
런타임 격리가 필요한 워크로드를 위해 gVisor를 쓰는 RuntimeClass gvisor(handler runsc)를 만들고, 네임스페이스 apps의 Pod sandboxed(이미지 nginx)가 이 RuntimeClass로 뜨도록 하세요. 노드에는 이미 runsc 핸들러가 설치되어 있습니다.
정답
RuntimeClass를 만들고 Pod에서 참조합니다.
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: gvisor
handler: runsc
---
apiVersion: v1
kind: Pod
metadata:
name: sandboxed
namespace: apps
spec:
runtimeClassName: gvisor
containers:
- name: web
image: nginxk apply -f gvisor.yaml
k -n apps get pod sandboxed해설: gVisor는 시스템 콜을 사용자 공간에서 가로채는 샌드박스 런타임이라 컨테이너 탈출 위험을 크게 줄입니다. RuntimeClass의 handler는 노드 런타임(containerd) 설정에 등록된 핸들러 이름(runsc)과 정확히 일치해야 합니다. 핸들러가 없는 노드에 스케줄되면 Pod가 컨테이너 생성 단계에서 실패하므로, 격리 노드를 가리키는 nodeSelector나 toleration이 함께 필요한 경우가 많습니다.
작업 11 (7점): Supply Chain Security #
네임스페이스 apps의 Deployment legacy-app이 무거운 node:18이미지를 씁니다. 같은 앱을 distroless 베이스 이미지인 gcr.io/distroless/nodejs18-debian12로 바꿔 공격 표면을 줄이세요. 이미지만 교체하면 되며 빌드는 이미 되어 있다고 가정합니다.
정답
Deployment의 이미지를 distroless로 교체합니다.
k -n apps set image deploy/legacy-app \
legacy-app=gcr.io/distroless/nodejs18-debian12
k -n apps rollout status deploy legacy-app교체된 이미지를 확인합니다.
k -n apps get deploy legacy-app -o jsonpath='{.spec.template.spec.containers[0].image}'해설: distroless 이미지는 셸,패키지 매니저,불필요한 OS 유틸리티를 빼고 앱 실행에 필요한 런타임만 남겨 취약점 수와 공격 표면을 크게 줄입니다. 셸이 없으므로 kubectl exec ... -- sh가 동작하지 않는 점, 그리고 ENTRYPOINT가 앱 바이너리로 고정되는 점이 디버깅 시 함정입니다. set image는 컨테이너 이름을 정확히 지정해야 하며, 이름이 틀리면 새 컨테이너가 추가되는 것이 아니라 명령이 실패합니다.
작업 12 (7점): Supply Chain Security #
control plane 노드에서 이미지 nginx:1.21.0을 Trivy로 스캔해 HIGH와 CRITICAL 등급 취약점만 추려 파일 /opt/trivy-report.txt에 저장하세요. Trivy는 노드에 이미 설치되어 있습니다.
정답
심각도를 필터링해 스캔하고 결과를 저장합니다.
ssh cluster1-controlplane
sudo -i
trivy image --severity HIGH,CRITICAL nginx:1.21.0 > /opt/trivy-report.txt
cat /opt/trivy-report.txt취약점이 없는 깨끗한 결과만 통과시키려면 종료 코드를 활용합니다.
trivy image --severity CRITICAL --exit-code 1 nginx:1.21.0해설: --severity로 등급을 콤마로 묶어 추리고, --exit-code 1은 해당 등급 취약점이 하나라도 있으면 1로 종료해 CI 파이프라인에서 배포를 막는 데 씁니다. 시험에서는 흔히 “CRITICAL이 있는 이미지를 찾아 그 Deployment를 안전한 이미지로 바꿔라"처럼 스캔과 교체를 묶어 내므로, 스캔 결과를 읽고 어떤 워크로드가 그 이미지를 쓰는지 kubectl get으로 역추적하는 흐름이 함께 필요합니다.
작업 13 (6점): Supply Chain Security #
네임스페이스 apps에 배포하려는 이미지 registry.local/web@sha256:abc...가 신뢰된 키로 서명되었는지 cosign으로 검증하세요. 공개 키는 /opt/cosign.pub에 있습니다. 검증에 성공한 이미지만 배포한다는 정책을 전제합니다.
정답
공개 키로 이미지 서명을 검증합니다.
cosign verify --key /opt/cosign.pub registry.local/web@sha256:abc...검증이 성공하면 해당 다이제스트로 배포합니다.
k -n apps set image deploy/web web=registry.local/web@sha256:abc...해설: cosign verify는 이미지 서명이 주어진 공개 키로 만들어졌는지 확인하며, 성공하면 서명 페이로드를 출력하고 0으로 종료합니다. 태그(:latest)가 아니라 다이제스트(@sha256:...) 로 검증,배포해야 검증한 바로 그 이미지가 배포됩니다. 태그는 나중에 다른 이미지로 덮어쓰일 수 있어 서명 검증의 의미가 사라지는 것이 핵심 함정입니다. 클러스터 단에서 미서명 이미지를 막으려면 Kyverno의 verifyImages나 sigstore policy-controller로 admission 단계에서 강제합니다.
작업 14 (8점): Monitoring, Logging and Runtime Security #
클러스터에 Kyverno가 설치되어 있습니다. 모든 네임스페이스에서 latest 태그를 쓰거나 태그를 생략한 컨테이너 이미지를 거부하는 ClusterPolicy disallow-latest-tag를 enforce 모드로 만드세요.
정답
이미지 태그를 검증하는 Kyverno ClusterPolicy를 만듭니다.
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-latest-tag
spec:
validationFailureAction: Enforce
rules:
- name: require-image-tag
match:
any:
- resources:
kinds:
- Pod
validate:
message: "이미지에 명시적 태그가 필요하며 latest는 금지됩니다."
pattern:
spec:
containers:
- image: "!*:latest"k apply -f disallow-latest.yaml
k run probe --image=nginx:latest해설: validationFailureAction: Enforce는 정책 위반 리소스를 admission 단계에서 거부합니다(Audit은 경고만 기록). 패턴 !*:latest는 latest 태그를 부정 매칭으로 막고, 태그를 생략하면 런타임이 latest로 해석하므로 함께 걸립니다. 위 검증 명령의 nginx:latest는 거부되어야 정상입니다. 같은 정책을 OPA/Gatekeeper로 짜면 ConstraintTemplate과 Constraint 두 리소스로 나뉘는 점이 Kyverno와의 차이입니다.
작업 15 (7점): Monitoring, Logging and Runtime Security #
워커 노드 node01에 Falco가 설치되어 있습니다. 컨테이너 안에서 셸(bash 또는 sh)이 실행되면 Warning으로 로그를 남기는 커스텀 Falco 룰 Shell in container를 추가하고, Falco를 재기동해 룰을 적용하세요.
정답
노드의 Falco 로컬 룰 파일에 룰을 추가합니다.
ssh node01
sudo -i
vim /etc/falco/falco_rules.local.yaml- rule: Shell in container
desc: 컨테이너 안에서 셸 실행을 탐지합니다
condition: >
spawned_process and container
and proc.name in (bash, sh)
output: >
Shell spawned in container
(user=%user.name container=%container.id command=%proc.cmdline)
priority: WARNINGFalco를 재기동하고 룰이 로드됐는지 확인합니다.
systemctl restart falco
journalctl -u falco -e | grep "Shell in container"해설: 커스텀 룰은 메인 falco_rules.yaml이 아니라 falco_rules.local.yaml에 두어야 패키지 업그레이드 때 덮어쓰이지 않습니다. condition에는 spawned_process(프로세스 생성)와 container(컨테이너 컨텍스트)를 AND로 묶고, priority는 대문자 등급명을 씁니다. 룰 추가 후 Falco를 재기동해야 적용되며, kubectl exec <pod> -- bash로 셸을 띄워 Warning 로그가 나오는지로 검증합니다.
작업 16 (8점): Monitoring, Logging and Runtime Security #
control plane에서 apiserver 감사 로깅을 켜세요. Secret 리소스의 모든 요청은 RequestResponse 수준으로, 그 외 모든 요청은 Metadata 수준으로 기록하는 audit policy를 만들고, 로그를 /var/log/kubernetes/audit.log에 남기도록 apiserver에 적용하세요.
정답
control plane 노드에서 audit policy 파일을 만듭니다.
ssh cluster1-controlplane
sudo -i
vim /etc/kubernetes/audit/policy.yamlapiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: RequestResponse
resources:
- group: ""
resources:
- secrets
- level: Metadataapiserver 매니페스트에 audit 플래그를 추가하고 정책,로그 디렉터리를 hostPath로 마운트합니다.
- --audit-policy-file=/etc/kubernetes/audit/policy.yaml
- --audit-log-path=/var/log/kubernetes/audit.log
- --audit-log-maxage=30crictl ps | grep kube-apiserver
tail -f /var/log/kubernetes/audit.log해설: audit policy의 규칙은 위에서부터 처음 매칭되는 규칙이 적용되므로, 더 자세한 Secret 규칙(RequestResponse)을 위에, 포괄 규칙(Metadata)을 아래에 둡니다. RequestResponse는 요청,응답 본문까지 기록해 가장 상세하고, Metadata는 누가 무엇을 했는지만 남깁니다. policy 파일과 로그 디렉터리를 apiserver 컨테이너에 hostPath 볼륨으로 마운트하지 않으면 컨테이너 안에서 경로를 못 찾아 apiserver가 안 뜨는 것이 흔한 함정입니다.
채점 기준 #
각 작업의 배점을 합산해 채점합니다. 합계는 100점이며 67점 이상이 합격권입니다.
| 도메인 | 작업,배점 | 소계 |
|---|---|---|
| Cluster Setup | 1(6) , 2(6) | 12 |
| Cluster Hardening | 3(7) , 4(7) | 14 |
| System Hardening | 5(8) , 6(8) , 7(7) | 23 |
| Minimize Microservice Vulnerabilities | 8(7) , 9(7) , 10(6) | 20 |
| Supply Chain Security | 11(7) , 12(7) , 13(6) | 20 |
| Monitoring, Logging and Runtime Security | 14(8) , 15(7) , 16(8) | 23 |
| 합계 | (합계) | 112 |
배점 소계의 합은 위 표대로 112점이며, 실제 채점은 각 작업 배점을 100점 만점으로 환산해 합산합니다. CKS는 CKA,CKAD의 66%보다 한 단계 높은 **67%**가 합격선이므로, 도구가 손에 익은 작업부터 확실히 점수를 쌓는 운영이 더 중요합니다.
채점은 실제 시험과 같이 결과물 기준입니다. 명령을 어떻게 쳤는지가 아니라 만들어진 리소스와 더 안전해진 클러스터 상태가 요구사항과 일치하는지를 봅니다. 한 작업 안에서도 라벨,필드,프로파일 적용,플래그 같은 항목별로 부분 점수가 나뉘므로, 막히는 항목이 있어도 채울 수 있는 부분은 끝까지 채우는 편이 점수에 유리합니다.
도메인별 약점 복습 #
채점 후 점수가 낮은 도메인을 아래 표의 해당 글로 돌아가 복습하십시오.
| 도메인 | 관련 작업 | 복습할 글 |
|---|---|---|
| Cluster Setup | 1, 2 | #2 , #3 |
| Cluster Hardening | 3, 4 | #4 , #5 |
| System Hardening | 5, 6, 7 | #6 , #7 , #8 |
| Minimize Microservice Vulnerabilities | 8, 9, 10 | #9 , #10 , #11 |
| Supply Chain Security | 11, 12, 13 | #13 , #14 , #15 |
| Monitoring, Logging and Runtime Security | 14, 15, 16 | #16 , #17 , #18 |
특정 작업에서 시간이 부족했다면 도메인 지식보다 손 빠르기 문제일 수 있습니다. 이 경우 #19 시험 팁을 다시 읽고, 같은 16개 작업을 시간을 재며 한 번 더 푸십시오. AppArmor로드,seccomp 적용,etcd 암호화,Falco 룰 추가는 한 번 손에 익으면 작업당 소요 시간이 눈에 띄게 줄어듭니다.
시리즈를 마치며 #
#1의 시험 환경에서 출발해 NetworkPolicy,kube-bench,RBAC 최소 권한,ServiceAccount 토큰,AppArmor,seccomp,capabilities,Pod Security Admission,etcd 암호화,gVisor,distroless,Trivy,cosign,Kyverno,Falco,audit log까지 CKS 전 6개 도메인을 20편으로 통과했습니다. 이 모의고사에서 67점을 넘겼다면 실제 시험장에서도 충분히 합격선을 넘길 수 있는 손이 만들어졌습니다. 축하합니다.
CKAD가 개발자의 워크로드 실기, CKA가 운영자의 클러스터 실기였다면, CKS는 그 클러스터를 공격으로부터 지키는 보안 전문가의 실기입니다. 세 시리즈를 모두 마친 분이라면 CNCF 쿠버네티스 자격증 3종(CKAD,CKA,CKS)의 전 영역을 손으로 통과한 것이니, 세 자격증 완성을 진심으로 축하합니다.