보안은 대부분의 조직과 쿠버네티스 클러스터 운영자에게 중요한 관심사다. 쿠버네티스 문서에서 기본 보안 체크리스트를 찾을 수 있다.
쿠버네티스에서 보안 요소를 배포하고 관리하는 방법을 배우려면, 이 섹션에 있는 튜토리얼을 따른다.
이 섹션의 다중 페이지 출력 화면임. 여기를 클릭하여 프린트.
보안은 대부분의 조직과 쿠버네티스 클러스터 운영자에게 중요한 관심사다. 쿠버네티스 문서에서 기본 보안 체크리스트를 찾을 수 있다.
쿠버네티스에서 보안 요소를 배포하고 관리하는 방법을 배우려면, 이 섹션에 있는 튜토리얼을 따른다.
Kubernetes v1.4 [beta]
AppArmor는 표준 리눅스 사용자와 그룹 기반의 권한을 보완하여, 한정된 리소스 집합으로 프로그램을 제한하는 리눅스 커널 보안 모듈이다. AppArmor는 임의의 애플리케이션에 대해서 잠재적인 공격 범위를 줄이고 더욱 심층적인 방어를 제공하도록 구성할 수 있다. 이 기능은 특정 프로그램이나 컨테이너에서 필요한 리눅스 기능, 네트워크 사용, 파일 권한 등에 대한 접근을 허용하는 프로파일로 구성한다. 각 프로파일은 허용하지 않은 리소스 접근을 차단하는 강제(enforcing) 모드 또는 위반만을 보고하는 불평(complain) 모드로 실행할 수 있다.
AppArmor를 이용하면 컨테이너가 수행할 수 있는 작업을 제한하고 또는 시스템 로그를 통해 더 나은 감사를 제공하여 더 안전한 배포를 실행할 수 있다. 그러나 AppArmor가 은탄환(언제나 통하는 무적의 방법)이 아니며, 애플리케이션 코드 취약점을 보호하기 위한 여러 조치를 할 수 있는 것 뿐임을 잊으면 안된다. 양호하고 제한적인 프로파일을 제공하고, 애플리케이션과 클러스터를 여러 측면에서 강화하는 것이 중요하다.
다음을 보장해야 한다.
쿠버네티스 버전은 최소 1.4 이다. -- 쿠버네티스 v1.4부터 AppArmor 지원을 추가했다. v1.4 이전 쿠버네티스 컴포넌트는 새로운 AppArmor 어노테이션을 인식하지 못하고 제공되는 AppArmor 설정을 조용히 무시할 것이다. 파드에서 예상하는 보호를 받고 있는지 확인하려면 해당 노드의 Kubelet 버전을 확인하는 것이 중요하다.
$ kubectl get nodes -o=jsonpath=$'{range .items[*]}{@.metadata.name}: {@.status.nodeInfo.kubeletVersion}\n{end}'
gke-test-default-pool-239f5d02-gyn2: v1.4.0
gke-test-default-pool-239f5d02-x1kf: v1.4.0
gke-test-default-pool-239f5d02-xwux: v1.4.0
AppArmor 커널 모듈을 사용 가능해야 한다. -- 리눅스 커널에 AppArmor 프로파일을 강제 적용하기 위해 AppArmor 커널 모듈은 반드시 설치되어 있고
사용 가능해야 한다. 예를 들어 Ubuntu 및 SUSE 같은 배포판은 모듈을 기본값으로 지원하고, 그 외 많은 다른 배포판들은 선택적으로 지원한다.
모듈이 사용 가능한지 확인하려면
/sys/module/apparmor/parameters/enabled 파일을 확인한다.
$ cat /sys/module/apparmor/parameters/enabled
Y
Kubelet(>=v1.4)이 AppArmor 기능 지원을 포함하지만, 커널 모듈을 사용할 수 없으면 파드에서 AppArmor 옵션을 실행하는 것이 거부된다.
컨테이너 런타임이 AppArmor을 지원한다. -- 현재 모든 일반적인 쿠버네티스를 지원하는 도커 (원문, Docker), CRI-O 또는 containerd 와 같은 컨테이너 런타임들은 AppArmor를 지원해야 한다. 이 런타임 설명서를 참조해서 클러스터가 AppArmor를 사용하기 위한 요구 사항을 충족하는지 확인해야 한다.
프로파일이 적재되어 있다. -- AppArmor는 각 컨테이너와 함께 실행해야 하는 AppArmor 프로파일을 지정하여 파드에 적용한다.
커널에 지정한 프로파일이 적재되지 않았다면, Kubelet(>= v1.4)은 파드를 거부한다. 해당 노드에 어떤 프로파일이 적재되었는지는
/sys/kernel/security/apparmor/profiles 파일을 통해 확인할 수 있다.
예를 들어,
$ ssh gke-test-default-pool-239f5d02-gyn2 "sudo cat /sys/kernel/security/apparmor/profiles | sort"
apparmor-test-deny-write (enforce)
apparmor-test-audit-write (enforce)
docker-default (enforce)
k8s-nginx (enforce)
노드에 프로파일을 적재하는 것에 대해 더 자세한 내용은 프로파일과 함께 노드 설정하기.
AppArmor 지원이 포함된 Kubelet (>= v1.4)이면 어떤 전제 조건이 충족되지 않으면 AppArmor와 함께한 파드를 거부한다. 노드 상에 AppArmor 지원 여부는 노드 준비 조건 메시지를 확인하여(이후 릴리스에서는 삭제될 것 같지만) 검증할 수 있다.
kubectl get nodes -o=jsonpath='{range .items[*]}{@.metadata.name}: {.status.conditions[?(@.reason=="KubeletReady")].message}{"\n"}{end}'
gke-test-default-pool-239f5d02-gyn2: kubelet is posting ready status. AppArmor enabled
gke-test-default-pool-239f5d02-x1kf: kubelet is posting ready status. AppArmor enabled
gke-test-default-pool-239f5d02-xwux: kubelet is posting ready status. AppArmor enabled
AppArmor 프로파일은 컨테이너마다 지정된다. 함께 실행할 파드 컨테이너에 AppArmor 프로파일을 지정하려면 파드의 메타데이터에 어노테이션을 추가한다.
container.apparmor.security.beta.kubernetes.io/<container_name>: <profile_ref>
<container_name>은 프로파일을 적용하는 컨테이너 이름이고, <profile_ref>는
적용할 프로파일을 지정한다. profile_ref는 다음 중에 하나이다.
runtime/default<profile_name>로 이름한 호스트에 적재되는 프로파일을 적용하기 위한 localhost/<profile_name>unconfined어노테이션과 프로파일 이름 형식의 자세한 내용은 API 참조를 살펴본다.
쿠버네티스 AppArmor 의 작동 순서는 모든 선행 조건이 충족되었는지 확인하고, 적용을 위해 선택한 프로파일을 컨테이너 런타임으로 전달하여 이루어진다. 만약 선행 조건이 충족되지 않으면 파드는 거부되고 실행되지 않는다.
프로파일이 적용되었는지 확인하기 위해, 컨테이너 생성 이벤트에 나열된 AppArmor 보안 옵션을 찾아 볼 수 있다.
kubectl get events | grep Created
22s 22s 1 hello-apparmor Pod spec.containers{hello} Normal Created {kubelet e2e-test-stclair-node-pool-31nt} Created container with docker id 269a53b202d3; Security:[seccomp=unconfined apparmor=k8s-apparmor-example-deny-write]
컨테이너의 루트 프로세스가 올바른 프로파일로 실행되는지는 proc attr을 확인하여 직접 검증할 수 있다.
kubectl exec <pod_name> -- cat /proc/1/attr/current
k8s-apparmor-example-deny-write (enforce)
이 예시는 AppArmor를 지원하는 클러스터를 이미 구성하였다고 가정한다.
먼저 노드에서 사용하려는 프로파일을 적재해야 한다. 사용할 프로파일은 파일 쓰기를 거부한다.
#include <tunables/global>
profile k8s-apparmor-example-deny-write flags=(attach_disconnected) {
#include <abstractions/base>
file,
# Deny all file writes.
deny /** w,
}
파드를 언제 스케줄할지 알지 못하므로 모든 노드에 프로파일을 적재해야 한다. 이 예시에서는 SSH를 이용하여 프로파일을 설치할 것이나 다른 방법은 프로파일과 함께 노드 설정하기에서 논의한다.
NODES=(
# The SSH-accessible domain names of your nodes
gke-test-default-pool-239f5d02-gyn2.us-central1-a.my-k8s
gke-test-default-pool-239f5d02-x1kf.us-central1-a.my-k8s
gke-test-default-pool-239f5d02-xwux.us-central1-a.my-k8s)
for NODE in ${NODES[*]}; do ssh $NODE 'sudo apparmor_parser -q <<EOF
#include <tunables/global>
profile k8s-apparmor-example-deny-write flags=(attach_disconnected) {
#include <abstractions/base>
file,
# Deny all file writes.
deny /** w,
}
EOF'
done
다음으로 쓰기 금지 프로파일된 "Hello AppArmor" 파드를 실행한다.
apiVersion: v1
kind: Pod
metadata:
name: hello-apparmor
annotations:
# 쿠버네티스에 'k8s-apparmor-example-deny-write' AppArmor 프로파일을 적용함을 알린다.
# 잊지 말 것은 쿠버네티스 노드에서 실행 중인 버전이 1.4 이상이 아닌 경우에는 이 설정은 무시된다는 것이다.
container.apparmor.security.beta.kubernetes.io/hello: localhost/k8s-apparmor-example-deny-write
spec:
containers:
- name: hello
image: busybox:1.28
command: [ "sh", "-c", "echo 'Hello AppArmor!' && sleep 1h" ]
kubectl create -f ./hello-apparmor.yaml
파드 이벤트를 살펴보면, 'k8s-apparmor-example-deny-write' AppArmor 프로파일로 생성된 파드 컨테이너를 확인할 수 있다.
kubectl get events | grep hello-apparmor
14s 14s 1 hello-apparmor Pod Normal Scheduled {default-scheduler } Successfully assigned hello-apparmor to gke-test-default-pool-239f5d02-gyn2
14s 14s 1 hello-apparmor Pod spec.containers{hello} Normal Pulling {kubelet gke-test-default-pool-239f5d02-gyn2} pulling image "busybox"
13s 13s 1 hello-apparmor Pod spec.containers{hello} Normal Pulled {kubelet gke-test-default-pool-239f5d02-gyn2} Successfully pulled image "busybox"
13s 13s 1 hello-apparmor Pod spec.containers{hello} Normal Created {kubelet gke-test-default-pool-239f5d02-gyn2} Created container with docker id 06b6cd1c0989; Security:[seccomp=unconfined apparmor=k8s-apparmor-example-deny-write]
13s 13s 1 hello-apparmor Pod spec.containers{hello} Normal Started {kubelet gke-test-default-pool-239f5d02-gyn2} Started container with docker id 06b6cd1c0989
proc attr을 확인하여 컨테이너가 실제로 해당 프로파일로 실행 중인지 확인할 수 있다.
kubectl exec hello-apparmor -- cat /proc/1/attr/current
k8s-apparmor-example-deny-write (enforce)
마지막으로 파일 쓰기를 통해 프로파일을 위반하면 어떻게 되는지 확인할 수 있다.
kubectl exec hello-apparmor -- touch /tmp/test
touch: /tmp/test: Permission denied
error: error executing remote command: command terminated with non-zero exit code: Error executing in Docker Container: 1
이제 정리하면서, 적재되지 않은 프로파일을 지정하면 어떻게 되는지 살펴본다.
kubectl create -f /dev/stdin <<EOF
apiVersion: v1
kind: Pod
metadata:
name: hello-apparmor-2
annotations:
container.apparmor.security.beta.kubernetes.io/hello: localhost/k8s-apparmor-example-allow-write
spec:
containers:
- name: hello
image: busybox:1.28
command: [ "sh", "-c", "echo 'Hello AppArmor!' && sleep 1h" ]
EOF
pod/hello-apparmor-2 created
kubectl describe pod hello-apparmor-2
Name: hello-apparmor-2
Namespace: default
Node: gke-test-default-pool-239f5d02-x1kf/
Start Time: Tue, 30 Aug 2016 17:58:56 -0700
Labels: <none>
Annotations: container.apparmor.security.beta.kubernetes.io/hello=localhost/k8s-apparmor-example-allow-write
Status: Pending
Reason: AppArmor
Message: Pod Cannot enforce AppArmor: profile "k8s-apparmor-example-allow-write" is not loaded
IP:
Controllers: <none>
Containers:
hello:
Container ID:
Image: busybox
Image ID:
Port:
Command:
sh
-c
echo 'Hello AppArmor!' && sleep 1h
State: Waiting
Reason: Blocked
Ready: False
Restart Count: 0
Environment: <none>
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from default-token-dnz7v (ro)
Conditions:
Type Status
Initialized True
Ready False
PodScheduled True
Volumes:
default-token-dnz7v:
Type: Secret (a volume populated by a Secret)
SecretName: default-token-dnz7v
Optional: false
QoS Class: BestEffort
Node-Selectors: <none>
Tolerations: <none>
Events:
FirstSeen LastSeen Count From SubobjectPath Type Reason Message
--------- -------- ----- ---- ------------- -------- ------ -------
23s 23s 1 {default-scheduler } Normal Scheduled Successfully assigned hello-apparmor-2 to e2e-test-stclair-minion-group-t1f5
23s 23s 1 {kubelet e2e-test-stclair-node-pool-t1f5} Warning AppArmor Cannot enforce AppArmor: profile "k8s-apparmor-example-allow-write" is not loaded
파드 상태는 Pending이며, 오류 메시지는 Pod Cannot enforce AppArmor: profile "k8s-apparmor-example-allow-write" is not loaded이다. 이벤트도 동일한 메시지로 기록되었다.
현재 쿠버네티스는 AppArmor 프로파일을 노드에 적재하기 위한 네이티브 메커니즘을 제공하지 않는다. 프로파일을 설정하는 여러 방법이 있다. 예를 들면 다음과 같다.
스케줄러는 어떤 프로파일이 어떤 노드에 적재되는지 고려하지 않으니, 프로파일 전체 집합이 모든 노드에 적재되어야 한다. 대안적인 방법은 각 프로파일(혹은 프로파일의 클래스)을 위한 노드 레이블을 노드에 추가하고, 노드 셀렉터를 이용하여 파드가 필요한 프로파일이 있는 노드에서 실행되도록 한다.
클러스터에서 AppArmor를 사용하지 않으려면, 커맨드라인 플래그로 비활성화 할 수 있다.
--feature-gates=AppArmor=false
비활성화되면, AppArmor 프로파일을 포함한 파드는 "Forbidden" 오류로 검증 실패한다.
AppArmor 프로파일을 만들고 올바르게 지정하는 것은 매우 까다로울 수 있다. 다행히 이 작업에 도움 되는 도구가 있다.
aa-genprof와 aa-logprof는 애플리케이션 활동과 로그와 수행에 필요한 행동을 모니터링하여
일반 프로파일 규칙을 생성한다. 자세한 사용방법은
AppArmor 문서에서 제공한다.AppArmor 문제를 디버깅하기 위해서 거부된 것으로 보이는 시스템 로그를 확인할 수 있다.
AppArmor 로그는 dmesg에서 보이며, 오류는 보통 시스템 로그나
journalctl에서 볼 수 있다. 더 많은 정보는
AppArmor 실패에서 제공한다.
컨테이너를 실행할 프로파일을 지정한다.
container.apparmor.security.beta.kubernetes.io/<container_name>
<container_name>는 파드 내에 컨테이너 이름과 일치한다.
분리된 프로파일은 파드 내에 각 컨테이너로 지정할 수 있다.runtime/default: 기본 런타임 프로파일을 참조한다.
localhost/<profile_name>: 노드(localhost)에 적재된 프로파일을 이름으로 참조한다.
unconfined: 이것은 컨테이너에서 AppArmor를 효과적으로 비활성시킨다.다른 어떤 프로파일 참조 형식도 유효하지 않다.
참고 자료
파드 시큐리티 어드미션은 파드가 생성될 때
파드 시큐리티 스탠다드(Pod Security Standards)를
적용하는 어드미션 컨트롤러이다. v1.25에서 정식 출시되었다.
이 튜토리얼은 baseline 파드 시큐리티
스탠다드를 클러스터 수준(level)에 적용하여 표준 구성을 클러스터의 모든 네임스페이스에
적용하는 방법을 보여 준다.
파드 시큐리티 스탠다드를 특정 네임스페이스에 적용하려면, 파드 시큐리티 스탠다드를 네임스페이스 수준에 적용하기를 참고한다.
만약 쿠버네티스 버전이 v1.36이 아니라면, 해당 버전의 문서를 확인한다.
워크스테이션에 다음을 설치한다.
이 튜토리얼은 완전히 제어 가능한 쿠버네티스 클러스터에서 무엇을 설정할 수 있는지 보여준다. 컨트롤 플레인을 구성할 수 없는 관리형 클러스터에서 파드 시큐리티 어드미션을 설정하는 방법을 배우려면, 파드 시큐리티 스탠다드를 네임스페이스 수준에 적용하기를 읽어 본다.
파드 시큐리티 어드미션을 이용하여
enforce, audit, 또는 warn 모드 중 하나로
내장 파드 시큐리티 스탠다드를 적용할 수 있다.
현재 구성에 가장 적합한 파드 시큐리티 스탠다드를 고르는 데 도움이 되는 정보를 수집하려면, 다음을 수행한다.
파드 시큐리티 스탠다드가 적용되지 않은 클러스터를 생성한다.
kind create cluster --name psa-wo-cluster-pss --image kindest/node:v1.24.0
다음과 비슷하게 출력될 것이다.
Creating cluster "psa-wo-cluster-pss" ...
✓ Ensuring node image (kindest/node:v1.24.0) 🖼
✓ Preparing nodes 📦
✓ Writing configuration 📜
✓ Starting control-plane 🕹️
✓ Installing CNI 🔌
✓ Installing StorageClass 💾
Set kubectl context to "kind-psa-wo-cluster-pss"
You can now use your cluster with:
kubectl cluster-info --context kind-psa-wo-cluster-pss
Thanks for using kind! 😊
kubectl context를 새로 생성한 클러스터로 설정한다.
kubectl cluster-info --context kind-psa-wo-cluster-pss
다음과 비슷하게 출력될 것이다.
Kubernetes control plane is running at https://127.0.0.1:61350
CoreDNS is running at https://127.0.0.1:61350/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
클러스터의 네임스페이스 목록을 조회한다.
kubectl get ns
다음과 비슷하게 출력될 것이다.
NAME STATUS AGE
default Active 9m30s
kube-node-lease Active 9m32s
kube-public Active 9m32s
kube-system Active 9m32s
local-path-storage Active 9m26s
--dry-run=server를 사용하여 다른 파드 시큐리티 스탠다드가 적용되었을 때
어떤 것이 변경되는지 확인한다.
kubectl label --dry-run=server --overwrite ns --all \
pod-security.kubernetes.io/enforce=privileged
다음과 비슷하게 출력될 것이다.
namespace/default labeled
namespace/kube-node-lease labeled
namespace/kube-public labeled
namespace/kube-system labeled
namespace/local-path-storage labeled
kubectl label --dry-run=server --overwrite ns --all \
pod-security.kubernetes.io/enforce=baseline
다음과 비슷하게 출력될 것이다.
namespace/default labeled
namespace/kube-node-lease labeled
namespace/kube-public labeled
Warning: existing pods in namespace "kube-system" violate the new PodSecurity enforce level "baseline:latest"
Warning: etcd-psa-wo-cluster-pss-control-plane (and 3 other pods): host namespaces, hostPath volumes
Warning: kindnet-vzj42: non-default capabilities, host namespaces, hostPath volumes
Warning: kube-proxy-m6hwf: host namespaces, hostPath volumes, privileged
namespace/kube-system labeled
namespace/local-path-storage labeled
kubectl label --dry-run=server --overwrite ns --all \
pod-security.kubernetes.io/enforce=restricted
다음과 비슷하게 출력될 것이다.
namespace/default labeled
namespace/kube-node-lease labeled
namespace/kube-public labeled
Warning: existing pods in namespace "kube-system" violate the new PodSecurity enforce level "restricted:latest"
Warning: coredns-7bb9c7b568-hsptc (and 1 other pod): unrestricted capabilities, runAsNonRoot != true, seccompProfile
Warning: etcd-psa-wo-cluster-pss-control-plane (and 3 other pods): host namespaces, hostPath volumes, allowPrivilegeEscalation != false, unrestricted capabilities, restricted volume types, runAsNonRoot != true
Warning: kindnet-vzj42: non-default capabilities, host namespaces, hostPath volumes, allowPrivilegeEscalation != false, unrestricted capabilities, restricted volume types, runAsNonRoot != true, seccompProfile
Warning: kube-proxy-m6hwf: host namespaces, hostPath volumes, privileged, allowPrivilegeEscalation != false, unrestricted capabilities, restricted volume types, runAsNonRoot != true, seccompProfile
namespace/kube-system labeled
Warning: existing pods in namespace "local-path-storage" violate the new PodSecurity enforce level "restricted:latest"
Warning: local-path-provisioner-d6d9f7ffc-lw9lh: allowPrivilegeEscalation != false, unrestricted capabilities, runAsNonRoot != true, seccompProfile
namespace/local-path-storage labeled
위의 출력에서, privileged 파드 시큐리티 스탠다드를 적용하면 모든 네임스페이스에서 경고가 발생하지 않는 것을 볼 수 있다.
그러나 baseline 및 restricted 파드 시큐리티 스탠다드에 대해서는
kube-system 네임스페이스에서 경고가 발생한다.
이 섹션에서는, 다음의 파드 시큐리티 스탠다드를 latest 버전에 적용한다.
baseline 파드 시큐리티 스탠다드는 enforce 모드로 적용restricted 파드 시큐리티 스탠다드는 warn 및 audit 모드로 적용baseline 파드 시큐리티 스탠다드는
예외 목록을 간결하게 유지하고 알려진 권한 상승(privilege escalations)을 방지할 수 있는
편리한 절충안을 제공한다.
추가적으로, kube-system 내의 파드가 실패하는 것을 방지하기 위해,
해당 네임스페이스는 파드 시큐리티 스탠다드가 적용되지 않도록 제외할 것이다.
사용 중인 환경에 파드 시큐리티 어드미션을 적용할 때에는 다음의 사항을 고려한다.
클러스터에 적용된 위험 상태에 따라,
restricted와 같은 더 엄격한 파드 시큐리티 스탠다드가 더 좋을 수도 있다.
kube-system 네임스페이스를 적용 대상에서 제외하면
이 네임스페이스의 파드가 privileged로 실행될 수 있다.
실제 사용 환경에서는,
최소 권한 원칙을 준수하도록,
접근을 kube-system 네임스페이스로 제한하는
엄격한 RBAC 정책을 적용할 것을 강력히 권장한다.
파드 시큐리티 어드미션 컨트롤러가 이러한 파드 시큐리티 스탠다드를 구현하는 데 사용할 수 있는 구성 파일을 생성한다.
mkdir -p /tmp/pss
cat <<EOF > /tmp/pss/cluster-level-pss.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: AdmissionConfiguration
plugins:
- name: PodSecurity
configuration:
apiVersion: pod-security.admission.config.k8s.io/v1
kind: PodSecurityConfiguration
defaults:
enforce: "baseline"
enforce-version: "latest"
audit: "restricted"
audit-version: "latest"
warn: "restricted"
warn-version: "latest"
exemptions:
usernames: []
runtimeClasses: []
namespaces: [kube-system]
EOF
`pod-security.admission.config.k8s.io/v1` 설정은 쿠버네티스 v1.25 이상을 필요로 한다.
쿠버네티스 v1.23 과 v1.24의 경우, [v1beta1](https://v1-24.docs.kubernetes.io/docs/tasks/configure-pod-container/enforce-standards-admission-controller/)을 사용한다.
쿠버네티스 v1.22의 경우, [v1alpha1](https://v1-22.docs.kubernetes.io/docs/tasks/configure-pod-container/enforce-standards-admission-controller/)을 사용한다.
API 서버가 클러스터 생성 과정에서 이 파일을 처리할 수 있도록 구성한다.
cat <<EOF > /tmp/pss/cluster-config.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
kubeadmConfigPatches:
- |
kind: ClusterConfiguration
apiServer:
extraArgs:
admission-control-config-file: /etc/config/cluster-level-pss.yaml
extraVolumes:
- name: accf
hostPath: /etc/config
mountPath: /etc/config
readOnly: false
pathType: "DirectoryOrCreate"
extraMounts:
- hostPath: /tmp/pss
containerPath: /etc/config
# optional: if set, the mount is read-only.
# default false
readOnly: false
# optional: if set, the mount needs SELinux relabeling.
# default false
selinuxRelabel: false
# optional: set propagation mode (None, HostToContainer or Bidirectional)
# see https://kubernetes.io/ko/docs/concepts/storage/volumes/#마운트-전파-propagation
# default None
propagation: None
EOF
/tmp를 Shared Directory로 추가할 수 있다.이러한 파드 시큐리티 스탠다드를 적용하기 위해 파드 시큐리티 어드미션을 사용하는 클러스터를 생성한다.
kind create cluster --name psa-with-cluster-pss --image kindest/node:v1.24.0 --config /tmp/pss/cluster-config.yaml
다음과 비슷하게 출력될 것이다.
Creating cluster "psa-with-cluster-pss" ...
✓ Ensuring node image (kindest/node:v1.24.0) 🖼
✓ Preparing nodes 📦
✓ Writing configuration 📜
✓ Starting control-plane 🕹️
✓ Installing CNI 🔌
✓ Installing StorageClass 💾
Set kubectl context to "kind-psa-with-cluster-pss"
You can now use your cluster with:
kubectl cluster-info --context kind-psa-with-cluster-pss
Have a question, bug, or feature request? Let us know! https://kind.sigs.k8s.io/#community 🙂
kubectl context를 새로 생성한 클러스터로 설정한다.
kubectl cluster-info --context kind-psa-with-cluster-pss
다음과 비슷하게 출력될 것이다.
Kubernetes control plane is running at https://127.0.0.1:63855
CoreDNS is running at https://127.0.0.1:63855/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
기본 네임스페이스에 파드를 생성한다.
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- image: nginx
name: nginx
ports:
- containerPort: 80
kubectl apply -f https://k8s.io/examples/security/example-baseline-pod.yaml
파드는 정상적으로 시작되었지만, 출력 결과에 경고가 포함되어 있다.
Warning: would violate PodSecurity "restricted:latest": allowPrivilegeEscalation != false (container "nginx" must set securityContext.allowPrivilegeEscalation=false), unrestricted capabilities (container "nginx" must set securityContext.capabilities.drop=["ALL"]), runAsNonRoot != true (pod or container "nginx" must set securityContext.runAsNonRoot=true), seccompProfile (pod or container "nginx" must set securityContext.seccompProfile.type to "RuntimeDefault" or "Localhost")
pod/nginx created
위에서 생성한 클러스터들을 다음 명령어를 실행하여 삭제한다.
kind delete cluster --name psa-with-cluster-pss
kind delete cluster --name psa-wo-cluster-pss
파드 시큐리티 어드미션은 파드가 생성될 때
파드 시큐리티 스탠다드를 적용하는
어드미션 컨트롤러이다. v1.25에서 정식 출시되었다.
이 튜토리얼에서는,
각 네임스페이스별로 baseline 파드 시큐리티 스탠다드를 강제(enforce)할 것이다.
파드 시큐리티 스탠다드를 클러스터 수준에서 여러 네임스페이스에 한 번에 적용할 수도 있다. 이에 대한 안내는 파드 시큐리티 스탠다드를 클러스터 수준에 적용하기를 참고한다.
워크스테이션에 다음을 설치한다.
다음과 같이 KinD 클러스터를 생성한다.
kind create cluster --name psa-ns-level
다음과 비슷하게 출력될 것이다.
Creating cluster "psa-ns-level" ...
✓ Ensuring node image (kindest/node:v1.23.0) 🖼
✓ Preparing nodes 📦
✓ Writing configuration 📜
✓ Starting control-plane 🕹️
✓ Installing CNI 🔌
✓ Installing StorageClass 💾
Set kubectl context to "kind-psa-ns-level"
You can now use your cluster with:
kubectl cluster-info --context kind-psa-ns-level
Not sure what to do next? 😅 Check out https://kind.sigs.k8s.io/docs/user/quick-start/
kubectl context를 새로 생성한 클러스터로 설정한다.
kubectl cluster-info --context kind-psa-ns-level
다음과 비슷하게 출력될 것이다.
Kubernetes control plane is running at https://127.0.0.1:50996
CoreDNS is running at https://127.0.0.1:50996/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
example이라는 네임스페이스를 생성한다.
kubectl create ns example
다음과 비슷하게 출력될 것이다.
namespace/example created
내장 파드 시큐리티 어드미션이 지원하는 레이블을 사용하여
이 네임스페이스에 파드 시큐리티 스탠다드를 활성화한다.
이 단계에서는 latest 버전(기본값)에 따라 baseline(기준) 파드 시큐리티 스탠다드에 대해 경고를 설정한다.
kubectl label --overwrite ns example \
pod-security.kubernetes.io/warn=baseline \
pod-security.kubernetes.io/warn-version=latest
어떠한 네임스페이스에도 복수 개의 파드 시큐리티 스탠다드를 활성화할 수 있으며,
이는 레이블을 이용하여 가능하다.
다음 명령어는 최신 버전(기본값)에 따라, baseline(기준) 파드 시큐리티 스탠다드는 enforce(강제)하지만
restricted(제한된) 파드 시큐리티 스탠다드에 대해서는 warn(경고) 및 audit(감사)
하도록 설정한다.
kubectl label --overwrite ns example \
pod-security.kubernetes.io/enforce=baseline \
pod-security.kubernetes.io/enforce-version=latest \
pod-security.kubernetes.io/warn=restricted \
pod-security.kubernetes.io/warn-version=latest \
pod-security.kubernetes.io/audit=restricted \
pod-security.kubernetes.io/audit-version=latest
클러스터의 example 네임스페이스에 해당 파드 스펙을 적용한다.
kubectl apply -n example -f /tmp/pss/nginx-pod.yaml
다음과 비슷하게 출력될 것이다.
Warning: would violate PodSecurity "restricted:latest": allowPrivilegeEscalation != false (container "nginx" must set securityContext.allowPrivilegeEscalation=false), unrestricted capabilities (container "nginx" must set securityContext.capabilities.drop=["ALL"]), runAsNonRoot != true (pod or container "nginx" must set securityContext.runAsNonRoot=true), seccompProfile (pod or container "nginx" must set securityContext.seccompProfile.type to "RuntimeDefault" or "Localhost")
pod/nginx created
클러스터의 default 네임스페이스에 해당 파드 스펙을 적용한다.
kubectl apply -n default -f /tmp/pss/nginx-pod.yaml
다음과 비슷하게 출력될 것이다.
pod/nginx created
파드 시큐리티 스탠다드는 example 네임스페이스에만 적용되었다.
동일한 파드를 default 네임스페이스에 생성하더라도
경고가 발생하지 않는다.
위에서 생성한 클러스터들을 다음 명령어를 실행하여 삭제한다.
kind delete cluster --name psa-ns-level
다음의 모든 단계를 한 번에 수행하려면 셸 스크립트를 실행한다.
baseline 파드 시큐리티 스탠다드는 enforce 모드로 적용하고
restricted 파드 시큐리티 스탠다드는 warn 및 audit 모드로 적용한다.Kubernetes v1.19 [stable]
Seccomp는 secure computing mode의 약자로, 리눅스 커널 2.6.12 버전부터 제공되는 기능이다. 이 기능을 사용하면 프로세스의 권한을 샌드박스로 격리해서, 유저스페이스(userspace)에서 커널로 실행할 수 있는 호출을 제한할 수 있다. 쿠버네티스를 사용하면 노드에 로드된 seccomp 프로파일을 파드와 컨테이너에 자동으로 적용할 수 있다.
워크로드에 필요한 권한을 식별하기란 어려울 수 있다. 이 튜토리얼에서는 로컬 쿠버네티스 클러스터에 seccomp 프로파일을 로드하는 방법과, 이를 파드에 적용하는 방법, 그리고 컨테이너 프로세스에 필요한 권한만 부여하는 프로파일 작성 방법을 살펴본다.
이 튜토리얼의 모든 단계를 완료하려면 kind와 kubectl을 설치해야 한다.
이 튜토리얼에서 사용하는 명령어는 컨테이너 런타임으로 도커를
사용한다고 가정한다. (kind가 생성하는 클러스터는 내부적으로 다른 컨테이너
런타임을 사용할 수도 있다). Podman을 사용할 수도 있지만,
이 경우에는 작업을 성공적으로 완료하기 위해 특정
지침를 따라야
한다.
이 튜토리얼은 아직 베타(v1.25부터)인 일부 예시와, 일반적으로 사용 가능한(GA) seccomp 기능만 사용하는 다른 예시를 함께 보여준다. 사용 중인 버전에 맞게 클러스터가 올바르게 구성되어 있는지 확인해야 한다.
이 튜토리얼은 또한 예시를 컴퓨터에 다운로드하기 위해 curl 도구를 사용한다.
원한다면 다른 도구를 사용하도록 단계를 조정해도 된다.
securityContext에 privileged: true가 설정되어 실행
중인 컨테이너에는 seccomp 프로파일을 적용할 수 없다. 특권을 가진(privileged)
컨테이너는 항상 Unconfined로 실행된다.이 프로파일들의 내용은 나중에 살펴볼 것이다. 우선은 클러스터에 적재할 수
있도록 profiles/라는 이름의 디렉터리에
다운로드한다.
{
"defaultAction": "SCMP_ACT_LOG"
}{
"defaultAction": "SCMP_ACT_ERRNO"
}{
"defaultAction": "SCMP_ACT_ERRNO",
"architectures": [
"SCMP_ARCH_X86_64",
"SCMP_ARCH_X86",
"SCMP_ARCH_X32"
],
"syscalls": [
{
"names": [
"accept4",
"epoll_wait",
"pselect6",
"futex",
"madvise",
"epoll_ctl",
"getsockname",
"setsockopt",
"vfork",
"mmap",
"read",
"write",
"close",
"arch_prctl",
"sched_getaffinity",
"munmap",
"brk",
"rt_sigaction",
"rt_sigprocmask",
"sigaltstack",
"gettid",
"clone",
"bind",
"socket",
"openat",
"readlinkat",
"exit_group",
"epoll_create1",
"listen",
"rt_sigreturn",
"sched_yield",
"clock_gettime",
"connect",
"dup2",
"epoll_pwait",
"execve",
"exit",
"fcntl",
"getpid",
"getuid",
"ioctl",
"mprotect",
"nanosleep",
"open",
"poll",
"recvfrom",
"sendto",
"set_tid_address",
"setitimer",
"writev",
"fstatfs",
"getdents64",
"pipe2",
"getrlimit"
],
"action": "SCMP_ACT_ALLOW"
}
]
}다음 명령어를 실행한다.
mkdir ./profiles
curl -L -o profiles/audit.json https://k8s.io/examples/pods/security/seccomp/profiles/audit.json
curl -L -o profiles/violation.json https://k8s.io/examples/pods/security/seccomp/profiles/violation.json
curl -L -o profiles/fine-grained.json https://k8s.io/examples/pods/security/seccomp/profiles/fine-grained.json
ls profiles
마지막 단계가 끝나면 세 개의 프로파일이 나열되는 것을 볼 수 있다.
audit.json fine-grained.json violation.json
간단하게, kind를 사용하면 seccomp 프로파일이 적재된 단일 노드 클러스터를 생성할 수 있다. kind는 도커에서 쿠버네티스를 실행하므로, 클러스터의 각 노드는 컨테이너이다. 따라서 노드에 파일을 적재하는 것과 유사하게, 각 컨테이너의 파일시스템에 파일을 마운트할 수 있다.
apiVersion: kind.x-k8s.io/v1alpha4
kind: Cluster
nodes:
- role: control-plane
extraMounts:
- hostPath: "./profiles"
containerPath: "/var/lib/kubelet/seccomp/profiles"예시 kind 구성을 다운로드해서 kind.yaml이라는 이름의 파일로 저장한다.
curl -L -O https://k8s.io/examples/pods/security/seccomp/kind.yaml
노드의 컨테이너 이미지를 설정해서 특정 쿠버네티스 버전을 지정할 수 있다. 자세한 내용은 kind 문서의 구성에 대한 노드 부분을 참고한다. 이 튜토리얼은 쿠버네티스 v1.36을 사용한다고 가정한다.
베타 기능으로, Unconfined로 대체되는 대신
컨테이너 런타임이
기본으로 선호하는 프로파일을 사용하도록 쿠버네티스를 구성할 수 있다.
이를 시도해보고 싶다면, 계속 진행하기 전에
모든 워크로드에 기본 seccomp 프로파일로 RuntimeDefault 사용 활성화를
참고한다.
kind 구성이 준비되었으면, 해당 구성으로 kind 클러스터를 생성한다.
kind create cluster --config=kind.yaml
새 쿠버네티스 클러스터가 준비되면, 단일 노드 클러스터로 실행 중인 도커 컨테이너를 확인한다.
docker ps
kind-control-plane이라는 이름으로 컨테이너가 실행 중임을 나타내는 출력을
볼 수 있다. 출력 결과는 다음과 비슷하다.
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
6a96207fed4b kindest/node:v1.18.2 "/usr/local/bin/entr…" 27 seconds ago Up 24 seconds 127.0.0.1:42223->6443/tcp kind-control-plane
해당 컨테이너의 파일시스템을 살펴보면, profiles/ 디렉터리가 kubelet의
기본 seccomp 경로에 성공적으로 적재된 것을 확인할 수 있다. docker exec를
사용해서 파드에서 명령어를 실행한다.
docker exec -it kind-control-plane ls /var/lib/kubelet/seccomp/profiles
audit.json fine-grained.json violation.json
이제 kind 내에서 실행 중인 kubelet에서 이 seccomp 프로파일들을 사용할 수 있음을 확인했다.
대부분의 컨테이너 런타임은 허용되거나 허용되지 않는 시스템 콜에 대해 합리적인
기본 구성을 제공한다. 파드나 컨테이너의 시큐리티 컨텍스트에서 seccomp
유형을 RuntimeDefault로 설정하면 워크로드에 이 기본값을 적용할 수 있다.
seccompDefault 구성이
활성화되어 있으면, 다른 seccomp 프로파일이 지정되지 않았을 때 파드는
RuntimeDefault seccomp 프로파일을 사용한다. 그렇지 않으면 기본값은 Unconfined이다.다음은 모든 컨테이너에 대해 RuntimeDefault seccomp 프로파일을 요청하는
파드의 매니페스트이다.
apiVersion: v1
kind: Pod
metadata:
name: default-pod
labels:
app: default-pod
spec:
securityContext:
seccompProfile:
type: RuntimeDefault
containers:
- name: test-container
image: hashicorp/http-echo:1.0
args:
- "-text=just made some more syscalls!"
securityContext:
allowPrivilegeEscalation: false해당 파드를 생성한다.
kubectl apply -f https://k8s.io/examples/pods/security/seccomp/ga/default-pod.yaml
kubectl get pod default-pod
파드가 성공적으로 시작된 것으로 표시될 것이다.
NAME READY STATUS RESTARTS AGE
default-pod 1/1 Running 0 20s
다음 섹션으로 넘어가기 전에 파드를 삭제한다.
kubectl delete pod default-pod --wait --now
우선, 프로세스의 모든 시스템 콜을 기록하는 audit.json 프로파일을 새
파드에 적용한다.
다음은 해당 파드의 매니페스트이다.
apiVersion: v1
kind: Pod
metadata:
name: audit-pod
labels:
app: audit-pod
spec:
securityContext:
seccompProfile:
type: Localhost
localhostProfile: profiles/audit.json
containers:
- name: test-container
image: hashicorp/http-echo:1.0
args:
- "-text=just made some syscalls!"
securityContext:
allowPrivilegeEscalation: false.spec.securityContext 내의 필드를 사용하는 것만 지원하며, 이
튜토리얼에서는 이 방식을 설명한다.클러스터에 파드를 생성한다.
kubectl apply -f https://k8s.io/examples/pods/security/seccomp/ga/audit-pod.yaml
이 프로파일은 어떠한 시스템 콜도 제한하지 않으므로, 파드는 성공적으로 시작될 것이다.
kubectl get pod audit-pod
NAME READY STATUS RESTARTS AGE
audit-pod 1/1 Running 0 30s
이 컨테이너가 노출하는 엔드포인트와 상호작용할 수 있도록, kind 컨트롤 플레인 컨테이너 내부에서 해당 엔드포인트에 접근할 수 있게 해주는 NodePort 서비스를 생성한다.
kubectl expose pod audit-pod --type NodePort --port 5678
서비스가 노드에서 할당받은 포트를 확인한다.
kubectl get service audit-pod
출력 결과는 다음과 비슷하다.
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
audit-pod NodePort 10.111.36.142 <none> 5678:32373/TCP 72s
이제 이 서비스가 노출한 포트로, kind 컨트롤 플레인 컨테이너 내부에서
curl을 사용해서 해당 엔드포인트에 접근할 수 있다. 해당 컨트롤 플레인
컨테이너에 속한 컨테이너 내에서 curl 명령어를 실행하려면 docker exec를 사용한다.
# 32373을 "kubectl get service audit-pod"에서 확인한 포트 번호로 변경한다.
docker exec -it kind-control-plane curl localhost:32373
just made some syscalls!
프로세스가 실행 중인 것은 확인했지만, 실제로 어떤 시스템 콜을 만들었을까?
이 파드는 로컬 클러스터에서 실행되고 있으므로, 로컬 시스템의
/var/log/syslog에서 이를 확인할 수 있다. 새 터미널 창을 열고 http-echo가
호출한 내용에 대한 출력을 tail한다.
# 컴퓨터의 로그 경로는 "/var/log/syslog"와 다를 수 있다.
tail -f /var/log/syslog | grep 'http-echo'
이미 http-echo가 만든 시스템 콜의 로그 일부를 볼 수 있을 것이며, 컨트롤 플레인
컨테이너 내부에서 curl을 다시 실행하면 로그에 더 많은 출력이 기록되는 것을 볼 수 있다.
예를 들면 다음과 같다.
Jul 6 15:37:40 my-machine kernel: [369128.669452] audit: type=1326 audit(1594067860.484:14536): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=51 compat=0 ip=0x46fe1f code=0x7ffc0000
Jul 6 15:37:40 my-machine kernel: [369128.669453] audit: type=1326 audit(1594067860.484:14537): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=54 compat=0 ip=0x46fdba code=0x7ffc0000
Jul 6 15:37:40 my-machine kernel: [369128.669455] audit: type=1326 audit(1594067860.484:14538): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=202 compat=0 ip=0x455e53 code=0x7ffc0000
Jul 6 15:37:40 my-machine kernel: [369128.669456] audit: type=1326 audit(1594067860.484:14539): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=288 compat=0 ip=0x46fdba code=0x7ffc0000
Jul 6 15:37:40 my-machine kernel: [369128.669517] audit: type=1326 audit(1594067860.484:14540): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=0 compat=0 ip=0x46fd44 code=0x7ffc0000
Jul 6 15:37:40 my-machine kernel: [369128.669519] audit: type=1326 audit(1594067860.484:14541): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=270 compat=0 ip=0x4559b1 code=0x7ffc0000
Jul 6 15:38:40 my-machine kernel: [369188.671648] audit: type=1326 audit(1594067920.488:14559): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=270 compat=0 ip=0x4559b1 code=0x7ffc0000
Jul 6 15:38:40 my-machine kernel: [369188.671726] audit: type=1326 audit(1594067920.488:14560): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=202 compat=0 ip=0x455e53 code=0x7ffc0000
각 줄의 syscall= 항목을 살펴보면 http-echo 프로세스에 필요한 시스템
콜을 파악하기 시작할 수 있다. 이 목록이 프로세스가 사용하는 모든 시스템
콜을 포함하지는 않겠지만, 이 컨테이너를 위한 seccomp 프로파일의
기초로 사용할 수 있다.
다음 섹션으로 넘어가기 전에 서비스와 파드를 삭제한다.
kubectl delete service audit-pod --wait
kubectl delete pod audit-pod --wait --now
시연을 위해, 어떠한 시스템 콜도 허용하지 않는 프로파일을 파드에 적용한다.
이 시연을 위한 매니페스트는 다음과 같다.
apiVersion: v1
kind: Pod
metadata:
name: violation-pod
labels:
app: violation-pod
spec:
securityContext:
seccompProfile:
type: Localhost
localhostProfile: profiles/violation.json
containers:
- name: test-container
image: hashicorp/http-echo:1.0
args:
- "-text=just made some syscalls!"
securityContext:
allowPrivilegeEscalation: false클러스터에 파드 생성을 시도한다.
kubectl apply -f https://k8s.io/examples/pods/security/seccomp/ga/violation-pod.yaml
파드는 생성되지만 문제가 발생한다. 파드의 상태를 확인해보면 시작에 실패한 것을 볼 수 있다.
kubectl get pod violation-pod
NAME READY STATUS RESTARTS AGE
violation-pod 0/1 CrashLoopBackOff 1 6s
이전 예시에서 봤듯이, http-echo 프로세스는 상당히 많은 시스템 콜이
필요하다. 여기서는 "defaultAction": "SCMP_ACT_ERRNO"를 설정해서 어떤
시스템 콜에도 오류를 발생시키도록 seccomp에 지시했다. 이는 매우
안전하지만, 의미 있는 작업을 수행할 수 있는 능력을 없애버린다. 실제로
원하는 것은 워크로드에 필요한 권한만 부여하는 것이다.
다음 섹션으로 넘어가기 전에 파드를 삭제한다.
kubectl delete pod violation-pod --wait --now
fine-grained.json 프로파일을 살펴보면, "defaultAction": "SCMP_ACT_LOG"로 설정했던 첫 번째 예시의 syslog에서 봤던 시스템 콜
중 일부를 확인할 수 있다. 이번 프로파일은 "defaultAction": "SCMP_ACT_ERRNO"로
설정하지만, "action": "SCMP_ACT_ALLOW" 블록에서 일련의 시스템 콜을
명시적으로 허용한다. 이상적으로는 컨테이너가 성공적으로 실행되고
syslog로 전송되는 메시지가 없을 것이다.
이 예시를 위한 매니페스트는 다음과 같다.
apiVersion: v1
kind: Pod
metadata:
name: fine-pod
labels:
app: fine-pod
spec:
securityContext:
seccompProfile:
type: Localhost
localhostProfile: profiles/fine-grained.json
containers:
- name: test-container
image: hashicorp/http-echo:1.0
args:
- "-text=just made some syscalls!"
securityContext:
allowPrivilegeEscalation: false클러스터에 파드를 생성한다.
kubectl apply -f https://k8s.io/examples/pods/security/seccomp/ga/fine-pod.yaml
kubectl get pod fine-pod
파드가 성공적으로 시작된 것으로 표시될 것이다.
NAME READY STATUS RESTARTS AGE
fine-pod 1/1 Running 0 30s
새 터미널 창을 열고 http-echo의 호출을 언급하는 로그 항목을
모니터링하기 위해 tail을 사용한다.
# 컴퓨터의 로그 경로는 "/var/log/syslog"와 다를 수 있다.
tail -f /var/log/syslog | grep 'http-echo'
다음으로, NodePort 서비스로 파드를 노출한다.
kubectl expose pod fine-pod --type NodePort --port 5678
서비스가 노드에서 할당받은 포트를 확인한다.
kubectl get service fine-pod
출력 결과는 다음과 비슷하다.
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
fine-pod NodePort 10.111.36.142 <none> 5678:32373/TCP 72s
kind 컨트롤 플레인 컨테이너 내부에서 curl을 사용해서 해당 엔드포인트에 접근한다.
# 32373을 "kubectl get service fine-pod"에서 확인한 포트 번호로 변경한다.
docker exec -it kind-control-plane curl localhost:32373
just made some syscalls!
syslog에 출력이 없는 것을 확인할 수 있다. 이는 프로파일이 필요한 모든
시스템 콜을 허용하고, 목록에 없는 시스템 콜이 호출되면 오류가 발생하도록
지정했기 때문이다. 이는 보안 관점에서는 이상적인 상황이지만, 프로그램을
분석하는 데 어느 정도의 노력이 필요했다. 이러한 노력을 크게 들이지
않고도 이 수준의 보안에 가깝게 도달할 수 있는 간단한 방법이 있다면 좋을 것이다.
다음 섹션으로 넘어가기 전에 서비스와 파드를 삭제한다.
kubectl delete service fine-pod --wait
kubectl delete pod fine-pod --wait --now
RuntimeDefault 사용 활성화Kubernetes v1.27 [stable]
seccomp 프로파일 기본값 지정 기능을 사용하려면, 이를 사용하려는 각
노드에서 --seccomp-default
커맨드라인 플래그를
활성화한 상태로 kubelet을 실행해야 한다.
활성화하면, kubelet은 Unconfined(seccomp 비활성화) 모드를 사용하는 대신
컨테이너 런타임이 정의하는 RuntimeDefault seccomp 프로파일을 기본으로
사용한다. 기본 프로파일은 워크로드의 기능을 유지하면서 강력한 보안
기본값 집합을 제공하는 것을 목표로 한다. 예를 들어 CRI-O와 containerd를
비교해보면 알 수 있듯이, 기본 프로파일은 컨테이너 런타임과 해당 릴리스
버전에 따라 다를 수 있다.
securityContext.seccompProfile API 필드가 변경되거나 워크로드에 사용
중단된 어노테이션이 추가되지 않는다. 이를 통해 사용자는 워크로드 구성을
실제로 변경하지 않고도 언제든지 롤백할 수 있다. 컨테이너가 사용 중인
seccomp 프로파일을 확인하려면
crictl inspect와 같은 도구를 사용할 수 있다.일부 워크로드는 다른 워크로드보다 더 적은 수준의 시스템 콜 제한을
필요로 할 수 있다. 즉, RuntimeDefault 프로파일을 사용하더라도 런타임
중에 실패할 수 있다는 의미이다. 이러한 실패를 완화하려면 다음과 같이 할 수 있다.
Unconfined로 실행한다.SeccompDefault 기능을 비활성화한다. 이때 워크로드가 이 기능이
비활성화된 노드에 스케줄되도록 해야 한다.운영 환경과 유사한 클러스터에 이 기능을 도입하는 경우, 쿠버네티스 프로젝트는 노드의 일부에만 이 기능 게이트를 활성화하고 워크로드 실행을 테스트한 다음, 클러스터 전체에 변경 사항을 적용할 것을 권장한다.
가능한 업그레이드 및 다운그레이드 전략에 대한 자세한 내용은 관련 쿠버네티스 개선 제안(KEP)인 기본적으로 seccomp 활성화하기에서 확인할 수 있다.
쿠버네티스 1.36에서는 파드의 스펙에 특정 seccomp 프로파일이 정의되어 있지 않을 때 적용되는 seccomp 프로파일을 구성할 수 있다. 다만 이 기본값 지정 기능을 사용하려는 각 노드에서는 여전히 이를 활성화해야 한다.
쿠버네티스 1.36 클러스터를 실행 중이고 이 기능을
활성화하고 싶다면, --seccomp-default 커맨드라인 플래그로 kubelet을
실행하거나 kubelet 구성 파일을
통해 활성화한다. kind에서 기능 게이트를
활성화하려면, kind가 최소 요구 쿠버네티스 버전을 제공하는지 확인하고
kind 구성에서
SeccompDefault 기능을 활성화한다.
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
image: kindest/node:v1.28.0@sha256:9f3ff58f19dcf1a0611d11e8ac989fdb30a28f40f236f59f0bea31fb956ccf5c
kubeadmConfigPatches:
- |
kind: JoinConfiguration
nodeRegistration:
kubeletExtraArgs:
seccomp-default: "true"
- role: worker
image: kindest/node:v1.28.0@sha256:9f3ff58f19dcf1a0611d11e8ac989fdb30a28f40f236f59f0bea31fb956ccf5c
kubeadmConfigPatches:
- |
kind: JoinConfiguration
nodeRegistration:
kubeletExtraArgs:
seccomp-default: "true"
클러스터가 준비되었으면, 파드를 실행한다.
kubectl run --rm -it --restart=Never --image=alpine alpine -- sh
이제 기본 seccomp 프로파일이 연결되어 있을 것이다. 이는 kind 워커의
컨테이너에 대해 docker exec로 crictl inspect를 실행해서
확인할 수 있다.
docker exec -it kind-worker bash -c \
'crictl inspect $(crictl ps --name=alpine -q) | jq .info.runtimeSpec.linux.seccomp'
{
"defaultAction": "SCMP_ACT_ERRNO",
"architectures": ["SCMP_ARCH_X86_64", "SCMP_ARCH_X86", "SCMP_ARCH_X32"],
"syscalls": [
{
"names": ["..."]
}
]
}
리눅스 seccomp에 대해 더 알아보려면 다음을 참고한다.