Skip to main content

커널 강화 도구 사용하기

· 8 min read
Jinwoong Kim
Technologist and Cloud Consultant

컨테이너 내부에서 실행 중인 애플리케이션이나 프로세스는 시스템 호출(system call)을 수행할 수 있다. 대표적인 예로 HTTP 요청을 수행하는 curl 명령을 들 수 있다. 시스템 호출은 커널에 서비스를 요청하기 위해 사용자 공간에서 실행되는 추상화된 프로그래밍인데, 커널 강화 도구를 사용하여 허용되는 시스템 호출을 제한할 수 있으며, CKS 시험에서는 AppArmor와 seccomp라는 두 가지 도구를 명시적으로 언급하고 있다. 이 두 도구는 컨테이너화된 환경에서 보안을 강화하는 데 중요한 역할을 하며, Kubernetes와의 통합을 통해 보다 안전한 클러스터 운영을 지원한다. 이 두 도구와 쿠버네티스와 통합하는 메커니즘에 대해 설명한다.

AppArmor​

AppArmor는 Linux 시스템에서 실행되는 프로그램에 대한 액세스 제어 기능을 제공한다. AppArmor는 경로 기반으로 작동하며, 프로필을 통해 특정 프로그램이나 컨테이너가 필요한 접근만 허용하도록 설정할 수 있다. Kubernetes에서는 AppArmor 프로필을 Pod 또는 컨테이너 수준에서 지정할 수 있으며, securityContext를 통해 적용한다. 이 도구는 user space에서 호출되는 애플리케이션과 기본 시스템 기능 사이에 추가적인 보안 계층을 구현한다. 네트워크 호출 또는 파일 시스템 상호 작용을 제한할 수 있다. 많은 Linux 배포판(예: Debian, Ubuntu, openSUSE)이 AppArmor를 기본으로 제공한다. AppArmor를 지원하지 않는 Amazon Linux와 같은 배포판은 AppArmor와 유사한 접근 방식을 취하는 SELinux를 대신 사용할 수 있다.

프로파일(profile) 이해​

프로그램이 수행할 수 있는 작업과 수행할 수 없는 작업을 정의하는 규칙은 AppArmor 프로필에 정의된다. 모든 프로필을 적용하려면 먼저 AppArmor에 로드해야 한다. AppArmor는 로드된 프로파일을 확인할 수 있는 명령줄 도구를 제공한다. aa-status 명령을 실행하면 로드된 모든 프로파일의 요약을 확인할 수 있다. AppArmor에는 Linux 서비스를 보호하기 위한 기본 애플리케이션 프로파일 세트가 이미 포함되어 있음을 확인할 수 있다.

$ sudo aa-status
apparmor module is loaded.
43 profiles are loaded.
41 profiles are in enforce mode.
/snap/snapd/21759/usr/lib/snapd/snap-confine
...
2 profiles are in complain mode.
snap.amazon-ssm-agent.amazon-ssm-agent
snap.amazon-ssm-agent.ssm-cli
0 profiles are in kill mode.
0 profiles are in unconfined mode.
9 processes have profiles defined.
2 processes are in enforce mode.
/usr/sbin/chronyd (429)
/usr/sbin/chronyd (434)
7 processes are in complain mode.
/snap/amazon-ssm-agent/7993/amazon-ssm-agent (104231) snap.amazon-ssm-agent.amazon-ssm-agent
/snap/amazon-ssm-agent/7993/ssm-agent-worker (104261) snap.amazon-ssm-agent.amazon-ssm-agent
/snap/amazon-ssm-agent/7993/ssm-session-worker (138449) snap.amazon-ssm-agent.amazon-ssm-agent
/usr/bin/dash (138466) snap.amazon-ssm-agent.amazon-ssm-agent
/usr/bin/sudo (138468) snap.amazon-ssm-agent.amazon-ssm-agent
/usr/bin/sudo (138469) snap.amazon-ssm-agent.amazon-ssm-agent
/usr/sbin/aa-status (138470) snap.amazon-ssm-agent.amazon-ssm-agent
0 processes are unconfined but have a profile defined.
0 processes are in mixed mode.
0 processes are in kill mode.

프로파일 모드는 일치하는 이벤트가 발생할 경우 런타임에 규칙의 처리를 결정한다. AppArmor는 두 가지 유형의 프로필 모드를 구분한다.

Enforce​

시스템이 규칙을 적용하고 위반을 리포트하고 시스템 로그에 기록한다. 이 모드를 사용하면 프로그램이 특정 호출을 하지 못하도록 방지할 수 있다.

Complain​

시스템에서 규칙을 적용하지는 않지만 위반 사항을 로그에 기록한다. 이 모드는 프로그램의 시스템 호출을 발견하려는 경우에 유용하다.

아래 프로파일은 파일 쓰기 액세스를 제한하기 위해 k8s-deny-write 파일에 사용자 지정 프로필을 정의하는 부분이다. 해당 파일은 워크로드를 실행하는 모든 워커 노드의 /etc/apparmor.d 디렉터리에 배치해야 한다. 자세한 문법은 AppArmor 위키를 참조하자.

#include <tunables/global>

profile k8s-deny-write flags=(attach_disconnected) { //profile 키워드 뒤의 식별자는 프로필의 이름이다.
#include <abstractions/base>
file, //파일 작업에 적용한다.
deny /** w, //모든 파일 쓰기를 거부한다.
}

사용자 지정 프로필 설정​

AppArmor에 프로파일을 로드하려면 워커 노드에서 다음 명령을 실행한다.

sudo apparmor_parser /etc/apparmor.d/k8s-deny-write

해당 명령은 기본적으로 Enforce 모드를 사용한다. Complain 모드에서 프로파일을 로드하려면 -C 옵션을 사용한다. 다시 aa-status 명령을 수행하면 기본 프로필과 함께 프로필을 보여준다. Enforce 모드를 사용하는 것을 확인할 수 있다.

$ sudo aa-status
apparmor module is loaded.
49 profiles are loaded.
45 profiles are in enforce mode.
...
docker-default
k8s-deny-write
...

AppArmor는 유틸리티 패키지의 일부로 추가 명령을 지원하는 도구를 설치 할 수 있다. 설치가 완료되면 aa-enforce 명령을 사용하여 Enforce 모드에서 프로필을 로드하고 aa-complain 명령을 사용하여 Complain 모드에서 프로필을 로드할 수 있다.

sudo apt-get update
sudo apt-get install apparmor-utils

컨테이너에 프로파일 적용하기​

파드에 AppArmor 규칙을 적용하기 전에 다음과 같은 전제조건을 확인해야 한다.

  • 컨테이너 런타임 지원: AppArmor 규칙을 적용하려면 사용 중인 컨테이너 런타임이 AppArmor를 지원해야 한다. Docker와 같은 대부분의 현대적인 컨테이너 런타임은 AppArmor를 지원한다.
  • 워커 노드에 AppArmor 설치: AppArmor가 제대로 작동하려면 파드를 실행하는 모든 워커 노드에 AppArmor가 설치되어 있어야 한다.
  • 프로파일 로드 확인: AppArmor 프로파일이 시스템에 로드되어 있는지 확인해야 한다.

컨테이너에 프로파일을 적용하려면 아래 예시 yaml 파일과 같이 특정 어노테이션을 설정해야 한다. 어노테이션 키는 container.apparmor.security.beta. kubernetes.io/<컨테이너 이름> 형식의 키를 사용해야 한다. 이 경우 컨테이너 이름은 test이다. 전체 키는 container.apparmor.security.beta.kubernetes.io/test이다. 어노테이션의 값은 localhost/<프로필 이름> 패턴을 따른다. 여기서 사용하려는 사용자 정의 프로파일은 k8s-deny-write이다. 구성 옵션에 대한 자세한 내용은 쿠버네티스 문서를 참조한다.

apiVersion: v1
kind: Pod
metadata:
name: test-apparmor
annotations:
container.apparmor.security.beta.kubernetes.io/test: localhost/k8s-deny-write
spec:
containers:
- name: test
image: busybox:1.28
command: ["sh", "-c", "echo 'Test AppArmor!' && sleep 100h"]

kubectl apply 명령을 실행하고 파드가 “Running” 상태로 전환될 때까지 기다린다:

$ kubectl apply -f apparmor.yaml
pod/test-apparmor created

$ kubectl get pod test-apparmor
NAME READY STATUS RESTARTS AGE
test-apparmor 1/1 Running 0 4s

이제 컨테이너에 셸을 넣고 파일 쓰기 작업을 수행할 수 있다.

$ kubectl exec -it test-apparmor -- /bin/sh
/ # touch test.txt
touch: test.txt: Permission denied

AppArmor는 컨테이너의 파일시스템에 파일을 쓰지 못하게 한다. 작업을 수행하려고 하면 “Permission denied”라는 메시지가 표시된다.

Seccomp 사용​

Seccomp는 Linux 커널의 보안 기능으로, "Secure Computing Mode"의 줄임말이다. seccomp는 user space에서 커널로의 호출을 제한할 수 있는 또 다른 Linux 커널 기능이다. seccomp 프로파일에 시스템 호출과 그 인수를 제한하는 규칙을 정의하게 된다. seccomp를 사용하면 Linux 커널 취약점을 악용할 위험을 줄일 수 있다. Kubernetes에서는 seccomp 프로필을 Pod의 securityContext를 통해 적용할 수 있고, 기본적으로 RuntimeDefault 프로필을 사용하여 보안 민감도가 높은 시스템 호출을 차단할 수 있다. 쿠버네티스의 seccomp에 대한 자세한 내용은 쿠버네티스 문서를 참조한다.

컨테이너에 기본 컨테이너 런타임 프로파일 적용하기​

도커 엔진이나 컨테이너와 같은 컨테이너 런타임은 기본 seccomp 프로파일과 함께 제공된다. 기본 seccomp 프로파일은 애플리케이션에서 가장 일반적으로 사용되는 syscalls을 허용하는 동시에 위험하다고 간주되는 syscalls의 사용을 금지한다. 쿠버네티스는 파드를 생성할 때 기본 컨테이너 런타임 프로파일을 컨테이너에 적용하지 않지만, SeccompDefault feature gate를 사용하여 이를 활성화할 수 있다. 또는 spec.securityContext.seccompProfile을 사용하여 seccomp 프로파일 유형을 RuntimeDefault로 설정하여 파드 단위로 이 기능을 선택할 수 있다.

apiVersion: v1
kind: Pod
metadata:
name: test-seccomp
spec:
securityContext:
seccompProfile:
type: RuntimeDefault
containers:
- name: test
image: busybox:1.28
command: ["sh", "-c", "echo 'Test seccomp!' && sleep 100h"]

기본 컨테이너 런타임 프로파일을 적용한다. kubectl apply 명령을 사용하여 파드를 시작한다.

$ kubectl apply -f seccomp.yaml
pod/test-seccomp created

$ kubectl get pod test-seccomp
NAME READY STATUS RESTARTS AGE
hello-seccomp 1/1 Running 0 4s

컨테이너에서 실행되는 echo 명령은 기본 seccomp 프로파일에 의해 보안 관점에서 문제가 없는 것으로 본다.

$ kubectl logs hello-seccomp
Test seccomp!

RuntimeDefault로 syscalls이 허용되었고 그 결과 표준 출력에 "Test seccomp!”라는 메시지를 볼 수 있다.

사용자 지정 프로필 설정​

기본 컨테이너 런타임 프로파일 외에 사용자 정의 프로파일을 생성하고 설정할 수 있다. 이러한 파일의 표준 디렉터리는 /var/lib/kubelet/seccomp이다. 사용자 정의 프로파일은 하위 디렉터리 프로파일에 구성한다. 디렉터리가 없는 경우 디렉터리를 생성한다.

sudo mkdir -p /var/lib/kubelet/seccomp/profiles

프로필 디렉터리에 있는 violation.json 파일에 사용자 정의 프로필을 생성한다.

{
"defaultAction": "SCMP_ACT_ALLOW",
"architectures": [
"SCMP_ARCH_X86_64",
"SCMP_ARCH_X86",
"SCMP_ARCH_X32"
],
"syscalls": [
{
"names": [
"mkdir"
],
"action": "SCMP_ACT_ERRNO"
}
]
}

간단히 말해서, 이 규칙 세트는 mkdir의 사용을 허용하지 않는다. 아래 규칙은 모든 syscalls을 허용하고, syscalls 리스트에 특별히 정의된 것만 거부하는 블랙리스트 방식이다. 반대로 화이트리스트 방식은 syscalls에 SCMP_ACT_ALLOW, SCMP_ACT_ERRNO 위치를 바꿔서 사용하면 된다.

기본 조치는 모든 시스템 호출에 적용된다. 여기서는 블랙리스트 방식으로 SCMP_ACT_ALLOW를 사용하는 모든 시스템 호출을 허용하고, SCMP_ACT_ERRNO 액션은 mkdir syscall의 실행을 방지하도록 규칙을 작성했다.

사용자 정의 프로파일을 /var/lib/kubelet/seccomp 디렉터리에 배치해도 자동으로 규칙이 파드에 적용되지는 않기 때문에 파드에 프로파일을 적용해야 한다.

컨테이너에 사용자 정의 프로파일 적용하기​

사용자 정의 프로파일을 적용하는 것은 기본 컨테이너 런타임 프로파일을 적용하는 것과 비슷한 패턴을 따르지만 약간의 차이가 있다. 보안 프로파일의 seccompProfile 속성을 violation.json 파일을 가리키고 유형을 Localhost로 설정한다.

apiVersion: v1
kind: Pod
metadata:
name: test-seccomp2
spec:
securityContext:
seccompProfile:
type: Localhost // 노드의 프로필을 참조
localhostProfile: profiles/violation.json //하위 디렉터리 프로필에 violation.json이라는 이름의 프로필을 적용
containers:
- name: test2
image: busybox:1.28
command: ["sh", "-c", "echo 'Test seccomp!' && sleep 100h"]
securityContext:
allowPrivilegeEscalation: false

커스텀 프로파일을 적용한다. kubectl apply 명령을 사용하여 파드를 시작한다. 파드가 "Running" 상태로 전환될 때까지 기다린다:

$ kubectl apply -f seccomp.yaml
pod/test-seccomp2 created

$ kubectl get pod test-seccomp2
NAME READY STATUS RESTARTS AGE
test-seccomp2 1/1 Running 0 40s

컨테이너에 셸을 실행하여 seccomp가 적용된 규칙을 제대로 적용했는지 확인한다.

$ kubectl exec -it test-seccomp2 -- /bin/sh
/ # mkdir test
mkdir: can't create directory 'test': Operation not permitted

출력에서 볼 수 있듯이, 이 작업은 mkdir 명령을 실행하려고 할 때 오류 메시지를 렌더링한다. 사용자 지정 프로필의 규칙을 위반했다고 볼 수 있다.

요약​

컨테이너에서 실행되는 애플리케이션과 프로세스가 시스템 호출을 하는 것은 매우 일반적이다. 보안 측면을 다루는 것은 호스트 시스템 수준에서 확인해야 할 게 많기 때문에 AppArmor 및 seccomp와 같은 Linux 커널 강화 도구를 사용하여 이러한 시스템 호출을 제한할 수 있다. 이런 도구들을 잘 활용하면 Kubernetes와 통합하여 보다 안전한 클러스터 운영을 할 수 있다.