LabHub
배우기 러닝패스 코스

CKS — Kubernetesセキュリティスペシャリスト

CKS模擬試験A

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

목표

실제 CKS 와 같은 조건에서 과제 17개를 120분 안에 풉니다. 합격선은 67% 이고 부분 점수제이므로, 17개 중 12개를 통과하면 완료로 처리됩니다.

모의고사입니다. 힌트와 정답지를 보지 말고 먼저 끝까지 풀어 보십시오. 막힌 과제는 표시해 두고 넘어갔다가 남은 시간에 돌아오는 편이 낫습니다. 채점은 언제든 눌러도 되고, 여러 번 눌러도 결과가 달라지지 않습니다.

왜 중요한가

CKS 는 CKA 합격이 선수 조건인 유일한 시험입니다. 쿠버네티스를 다루는 손은 이미 갖췄다고 전제하므로, 문제는 "무엇을 만드세요" 보다 "이 설정의 무엇이 위험한지 찾아 고치세요" 쪽에 가깝습니다. 방어와 진단이 시험 대상이고, 공격 기법은 다루지 않습니다.

실무에서도 같습니다. 클러스터가 뚫리는 자리는 대개 새로 만든 기능이 아니라 기본값을 그대로 둔 곳입니다. 네임스페이스에 파드 보안 승인을 걸지 않았고, 기본 서비스 계정 토큰이 모든 파드에 마운트되고 있고, 이미지가 움직이는 태그로 배포되고 있습니다. 이 시험이 묻는 것이 정확히 그 목록입니다.

시험 환경 (실제 시험에서 확인된 사실)

이 모의고사 환경에서 다른 점

이 실습의 클러스터는 파드 안에서 도는 1인용 클러스터입니다. kube-apiserver 는 진짜라서 매니페스트, RBAC, 파드 보안 승인, 승인 정책은 실제로 동작하고 실제로 거부합니다. 다만 컨테이너를 실제로 돌리는 런타임이 없으므로 다음 두 가지는 확인할 수 없습니다.

나머지는 전부 살아 있는 클러스터에서 다시 계산해 채점합니다. 권한은 kubectl auth can-i 로, 승인 정책은 서버 dry-run 으로 그 순간의 판정을 다시 물어봅니다.

단계

Cluster Setup

  1. 네임스페이스 prod 를 만들고, 그 안의 모든 파드에 대해 인그레스와 이그레스를 함께 기본 거부하는 NetworkPolicy default-deny 를 만드십시오. 허용 규칙은 하나도 두지 않습니다.
  2. 네임스페이스 prod 에서 app=web 라벨이 붙은 파드가 노드 메타데이터 엔드포인트 169.254.169.254/32 로 나가지 못하게 하되, 그 밖의 이그레스는 그대로 열어 두는 NetworkPolicy deny-metadata 를 만드십시오.
  3. CN=shop.internal 인 자체 서명 인증서로 네임스페이스 prod 에 TLS 시크릿 web-tls 를 만들고, 호스트 shop.internal 을 그 시크릿으로 TLS 종료하는 인그레스 web 을 만드십시오. 백엔드는 서비스 web 의 80 포트입니다.

Cluster Hardening

  1. 네임스페이스 prod 에 서비스 계정 report-runner 를 만들고, 이 계정이 prod 안에서 파드를 get, list, watch 만 할 수 있게 Role pod-reader 와 RoleBinding report-runner-pod-reader 를 만드십시오. 그 밖의 권한은 주지 않습니다.
  2. 네임스페이스 proddefault 서비스 계정이 토큰을 자동 마운트하지 않게 하십시오. 그리고 prod 에 파드 frontend 를 만들되 서비스 계정 report-runner 를 쓰고, 파드 스펙에서도 토큰 자동 마운트를 끄십시오.
  3. 보안 감사자 그룹 security-audit 이 클러스터 전역에서 파드, 네임스페이스, 네트워크폴리시를 읽기만 할 수 있도록 ClusterRole security-auditor 와 ClusterRoleBinding security-auditor 를 만드십시오. 쓰기 권한과 시크릿 접근은 주지 않습니다.

System Hardening

  1. /root/exam/seccomp/audit.json 에 seccomp 프로파일을 작성하십시오. 기본 동작은 SCMP_ACT_ERRNO 이고, 최소한 몇 개의 시스템 콜은 SCMP_ACT_ALLOW 로 열어야 합니다. 그리고 네임스페이스 prod 에 파드 probe 를 만들어 이 프로파일을 Localhost 방식으로 profiles/audit.json 경로에서 쓰게 하십시오.
  2. /root/exam/apparmor/k8s-deny-write 에 AppArmor 프로파일을 작성하십시오. 프로파일 이름은 k8s-deny-write 이고, #include <tunables/global> 줄과 모든 쓰기를 막는 deny /** w, 규칙이 있어야 합니다. 그리고 네임스페이스 prod 에 Deployment logshipper 를 만들어 파드 템플릿의 securityContext.appArmorProfile 이 이 프로파일을 Localhost 로 쓰게 하십시오.

Minimize Microservice Vulnerabilities

  1. 네임스페이스 payments 를 만들고, 파드 보안 승인의 enforce, audit, warn 세 모드를 모두 restricted 로 걸고 세 모드의 버전을 latest 로 고정하십시오.
  2. 네임스페이스 paymentsrestricted 를 위반하는 파드 bad-pod 를 적용해 보고, 거부된 응답을 표준 오류까지 포함해 /root/exam/denied.txt 에 저장하십시오. 파드는 실제로 만들어지면 안 됩니다.
  3. RuntimeClass gvisor(handler 는 runsc)를 만들고, 네임스페이스 payments 에 레플리카 2개짜리 Deployment checkout 을 만드십시오. 파드 템플릿은 이 RuntimeClass 를 쓰고 restricted 를 통과해야 합니다. 컨테이너 이름은 app 이고, 파드 수준에 runAsNonRoot: trueseccompProfile.type: RuntimeDefault 를, 컨테이너 수준에 allowPrivilegeEscalation: false, capabilities.drop: [ALL], readOnlyRootFilesystem: true 를 두십시오.

Supply Chain Security

  1. 네임스페이스 supply 를 만들고 Deployment payments-api 를 만드십시오. 컨테이너 이름은 api 이고, 이미지는 태그가 아니라 다이제스트로 고정해야 합니다. 값은 registry.internal/payments-api@sha256:140eab0459241fb1643767dd4cc3576d282b0059a6328521e714cb545fa4adea 이며 imagePullPolicyIfNotPresent 입니다.
  2. /root/exam/Dockerfile 에 배포용 이미지를 짓는 Dockerfile 을 작성하십시오. 빌드 단계와 실행 단계를 나눈 멀티스테이지여야 하고, 모든 FROM 의 베이스를 @sha256: 다이제스트로 고정해야 하며, 산출물은 COPY --from= 으로만 가져와야 합니다. ADD 를 쓰지 말고, 마지막 USER 는 0 이 아닌 숫자 UID 여야 하며, ENVARG 에 비밀로 보이는 이름을 남기지 마십시오.
  3. 승인되지 않은 레지스트리의 이미지를 막으십시오. ValidatingAdmissionPolicy trusted-images 와 ValidatingAdmissionPolicyBinding trusted-images 를 만들고, 모든 컨테이너 이미지가 registry.internal/ 로 시작할 것을 요구하게 하십시오. 바인딩의 validationActionsDeny 이고, 적용 범위는 네임스페이스 supply 뿐입니다. 다른 네임스페이스는 영향을 받으면 안 됩니다.

Monitoring, Logging and Runtime Security

  1. /root/exam/audit/policy.yaml 에 감사 정책을 작성하십시오. apiVersionaudit.k8s.io/v1, kindPolicy, 최상위 omitStagesRequestReceived 하나입니다. 규칙은 정확히 세 개이고 순서가 중요합니다. 첫째, 코어 그룹의 secretsconfigmapsMetadata 레벨로 남깁니다. 둘째, pods/exec, pods/attach, pods/portforwardRequestResponse 레벨로 남깁니다. 셋째, 대상을 적지 않은 Metadata 규칙으로 나머지 전부를 받습니다.
  2. /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-policyaudit-logs 를 각각 마운트하되, 정책 마운트는 readOnly: true 이고 로그 마운트는 읽기 전용이면 안 됩니다.
  3. 네임스페이스 prod 에 Deployment ledger 를 만드십시오. 컨테이너 이름은 app 이고 readOnlyRootFilesystem: true, allowPrivilegeEscalation: false, capabilities.drop: [ALL] 을 둡니다. 쓰기가 필요한 /tmpemptyDir 볼륨 scratch 로 제공하고, hostPath 볼륨과 hostNetwork, hostPID, hostIPC 는 쓰지 마십시오.

참고

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 을 씁니다. 호스트 네임스페이스를 공유하면 컨테이너 경계 자체가 없어집니다.