티스토리 뷰

Kubernetes

22 Clusters - AppAmor

zerobig-k8s 2018. 9. 27. 07:09

<출처>

https://kubernetes.io/docs/tutorials/clusters/apparmor/



FEATURE STATE: Kubernetes v1.4 beta

AppArmor는 리눅스 커널 보안 모듈로 제한된 리소스 셋에 대해 프로그램을 한정시키기 위해 퍼미션을 기반으로 표준 리눅스 사용자와 그룹 정책을 보완해준다. AppArmor는 잠재적인 공격영역을 축소시키고 단단한 철벽 방어력을 제공할 수 있도록 어떤 임의의 애플리케이션에 대해서도 구성이 가능하다. 리눅스 처리용량, 네트워크 접근, 파일 퍼미션 등과 같이 특정 프로그램 또는 컨테이너에 의해 요구되어지는 접근은 화이트리스트에서 조율한 프로파일을 통해 구성된다. 각 프로파일은 허용하지 않은 리소스에 대한 접근을 차단시키는 "enforcing" 모드 또는 오직 위반에 대한 보고만 하는 "complain" 모드로 동작될 수 있다.  

AppArmor는 컨테이너가 무엇을 하도록 허용할 것인지에 대한 제한을 통해 보다 안전한 디플로이먼트를 동작시킬 수 있도록 도와주고 시스템 로그를 통해 보다 개선된 감사기능을 제공할 수 있도록 도와준다,  그러나, AppArmor가 묘책이 아니라는 것을 명심하는 것이 중요하다. 그저 애플리케이션 코드 내에서의 부당한 이용가능성을 방지해주는 데 도움이 될 수 있을 뿐이다. 적절한, 제한적인 프로파일을 제공하고 다양한 각도에서 애플리케이션과 클러스터를 강화해 나는 것이 중요하다.






Objectives

  • 노드에 프로파일을 로드하는 방법에 대한 예시를 알아본다.
  • 파드에 프로파일을 강화시키는 방법에 대해 배운다.
  • 로드된 프로파일을 체크하는 방법에 대해 배운다.
  • 프로파일이 위배된 경우 무슨일이 일어나는지 살펴본다.
  • 프로파일이 로드될 수 없는 경우 무슨 일이 일어나는지 살펴본다.




Before you begin

다음 사항을 확인한다.

1. K8S 버전은 최소 1.4여야 한다. – K8S는 버전 1.4에서 AppArmor 지원기능이 추가되었다. 1.4 이전 버전의 K8S 컴포넌트는 새로운 AppArmor 주석(annotation)을 인지하지 못하며, 제공된 AppArmor 설정들은 아무런 반응 없이 무시될 것이다. 파드가 예상한 대로 보호 받고 있는지 알아보기 위해, 노드에 대한 Kubelet 버전을 확인하는 것이 중요하다.

1
2
root@zerobig-vm-u:~# kubectl get nodes -o=jsonpath=$'{range .items[*]}{@.metadata.name}: {@.status.nodeInfo.kubeletVersion}\n{end}'
minikube: v1.10.0
cs


2.  AppArmor 커널 모듈이 사용되도록 설정되었다. – 리눅스 커널이 AppArmor 프로파일을 강화하도록 AppArmor 커널 모듈이 설치되고 사용설정이 이루어져야만 한다. 우분투 그리고 SUSE와 같은 몇몇 배포판은 기본으로 모듈이 사용설정으로 되어 있다. 다른 많은 배포판도 선택적 지원을 제공하고 있다. 모듈이 사용가능 설정되었는지 확인하기 위해, /sys/module/apparmor/parameters/enabled 파일을 확인한다.
1
2
root@zerobig-vm-u:~# cat /sys/module/apparmor/parameters/enabled
Y
cs


Kubelet이 AppArmor를 지원을(>= v1.4) 포함하는 경우, 커널 모듈이 사용하지 않도록 설정된 AppArmor 옵션에서는 파드 동작이 거부될 것이다,

주의: 우분투는 부가적인 hook 과 특징을 추가하는 패치를 포함하여 업스트림 리눅스 커널로 병합된 많은 AppArmor 패치들을 가진다. K8S는 오직 업스트림 버전을 이용해 테스트가 수행 되었으므로 다른 특징들에 대한 지원에 대한 보장을 하지 않는다. 


3. 컨테이너 런타임은 Docker이다. – 현재 AppArmor를 지원하는 유일한 K8S 지원 컨테이너 런타임은 Docker이다. 더 많은 런타임들이 AppArmor 지원을 추가하고 있으므로, 옵션은 확장되어질 것이다, 현재 노드가 Docker와 동작 중인지 확인해 본다.

1
2
root@zerobig-vm-u:~# kubectl get nodes -o=jsonpath=$'{range .items[*]}{@.metadata.name}: {@.status.nodeInfo.containerRuntimeVersion}\n{end}'
minikube: docker://17.12.1-ce
cs



Kubelet이 AppArmor를 지원을(>= v1.4) 포함하는 경우, 런타임이 Docker가 아닌 AppArmor 옵션에서는 파드 동작이 거부될 것이다,


4. Profile이 로드 되었다. – AppArmor는  AppArmor 프로파일을 정의하여 파드에 적용된다. 각각의 컨테이너는 이 프로파일과 함께 동작되어야 한다. 정의한 프로파일 중 어떠한 것도 아직 커널에 로드되지 않았다면, Kubelet (>= v1.4)은 파드를 거부할 것이다. /sys/kernel/security/apparmor/profiles 파일을 확인해 보면 노드에 어떤 프로파일이 로드되었는지 확인할 수 있다. 예를들어,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
root@zerobig-vm-u:~# cat /sys/kernel/security/apparmor/profiles | sort
/sbin/dhclient (enforce)
/usr/bin/evince (enforce)
/usr/bin/evince-previewer (enforce)
/usr/bin/evince-previewer//sanitized_helper (enforce)
/usr/bin/evince-thumbnailer (enforce)
/usr/bin/evince-thumbnailer//sanitized_helper (enforce)
/usr/bin/evince//sanitized_helper (enforce)
/usr/lib/NetworkManager/nm-dhcp-client.action (enforce)
/usr/lib/NetworkManager/nm-dhcp-helper (enforce)
/usr/lib/connman/scripts/dhclient-script (enforce)
/usr/lib/cups/backend/cups-pdf (enforce)
/usr/lib/lightdm/lightdm-guest-session (enforce)
/usr/lib/lightdm/lightdm-guest-session//chromium (enforce)
/usr/lib/snapd/snap-confine (enforce)
/usr/lib/snapd/snap-confine//mount-namespace-capture-helper (enforce)
/usr/sbin/cups-browsed (enforce)
/usr/sbin/cupsd (enforce)
/usr/sbin/cupsd//third_party (enforce)
/usr/sbin/ippusbxd (enforce)
/usr/sbin/tcpdump (enforce)
docker-default (enforce)
webbrowser-app (enforce)
webbrowser-app//oxide_helper (enforce)
cs


노드에 로드되는 프로파일에 대한 보다 자세한 정보는, Setting up nodes with profiles 을 참조한다.


Kubelet 버전이 AppArmor을 지원한다면(>= v1.4), 어떠한 전제조건도 충족시키지 못한 AppArmor 옵션에서는 파드를 거부할 것이다. (비록 다음 릴리즈에서 제거되어질 수도 있기는 하나) 노드의 준비 조건 메시지를 확인하여 AppArmor가 노드에서 지원하는지 확인 할 수 있다.

1
2
root@zerobig-vm-u:~# kubectl get nodes -o=jsonpath=$'{range .items[*]}{@.metadata.name}: {.status.conditions[?(@.reason=="KubeletReady")].message}\n{end}'
minikube: kubelet is posting ready status
cs





<참고> 다음 섹션부터는 실습환경 구현이 어려워 원문의 내용을 통해 해석만 하였다.




Securing a Pod


주의: AppArmor는 현재 베타 상태이므로 옵션이 주석(annotation)으로 정의된다. 정식 버전(GA) 자격이 주어지면, 주석은 1등급 필드로 대체 될 것이다.(보다 자세한 내용은 Upgrade path to GA을 참고한다.).


AppArmor 프로파일은 per-container 로 정의된다. 파드 컨테이너와 함께 동작시키기 위해 AppArmor 프로파일을 정의하려면, 파드의 메타데이터에 주석을 추가한다. 


container.apparmor.security.beta.kubernetes.io/<container_name>: <profile_ref>


"<container_name>"는 프로파일을 적용하기 위한 컨테이너의 이름이며, "<profile_ref>" 은 적용하려는 프로파일을 정의한다. "profile_ref" 은 다음 중 하나가 될 수 있다.


  • "runtime/default"   런타임의 초기 프로파일을 적용함
  • "localhost/<profile_name>"   "<profile_name>"이름으로 호스트에 로드된 프로파일을 적용함
  • "unconfined"   아무런 프로파일이 로드되지 않을 것을 나타냄


전체 자세한 주석과 프로파일 이름 포맷을 확인하려면, API Reference 를 참고한다.

K8S AppArmor 시행은 먼저 모든 전제조건이 충족되는지 확인함으로써 작용한다. 그런 다음 시행을 위한 컨테이너 런타임에 선택 프로파일을 전달해 준다. 전제조건이 충족되지 않으면, 파드는 거부당하고 작동하지 않는다.

적용된 프로파일을 검증하기 위해, 생성된 컨테이너 이벤트에서 AppArmor 보안 옵션 리스트을 찾아볼 수 있다. 

1
2
3
$ kubectl get events | grep Created
22s        22s         1         hello-apparmor     Pod       spec.containers{hello}   Normal    Created     {kubelet e2e-test-stclair-minion-group-31nt}   Created container with docker id 269a53b202d3; Security:[seccomp=unconfined apparmor=k8s-apparmor-example-deny-write]
 
cs



프로세스 속성을 체크해 봄으로써 컨테이너 루트 프로세스가 올바른 프로파일을 가지고 동작 중인지 직접적으로 검증해 볼 수도 있다.

1
2
$ kubectl exec <pod_name> cat /proc/1/attr/current
k8s-apparmor-example-deny-write (enforce)
cs






Example


이번 예제는 AppArmor를 지원하는 클러스터가 이미 설치 되어 있음을 가정한다.

먼저, 노드에서 이용하기를 원하는 프로파일을 로드하는 것이 필요하다. 우리가 이용할 프로파일은 모든 쓰기를 거부한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ 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
cs


다음, 쓰기-거부(deny-write) 프로파일을 가지고 간단한 “Hello AppArmor” 파드를 동작시킬 것이다, 


pods/security/hello-apparmor.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: Pod
metadata:
  name: hello-apparmor
  annotations:
    # Tell Kubernetes to apply the AppArmor profile "k8s-apparmor-example-deny-write".
    # Note that this is ignored if the Kubernetes node is not running version 1.4 or greater.
    container.apparmor.security.beta.kubernetes.io/hello: localhost/k8s-apparmor-example-deny-write
spec:
  containers:
  - name: hello
    image: busybox
    command: [ "sh""-c""echo 'Hello AppArmor!' && sleep 1h" ]
cs


1
$ kubectl create -f ./hello-apparmor.yaml
cs


파드 이벤트를 확인해 보면, “k8s-apparmor-example-deny-write” AppArmor 프로파일을 가지고 생성했던 파드 컨테이너를 볼 수 있을 것이다. 


1
2
3
4
5
6
$ 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
cs


프로세스 속성을 체크해 봄으로써 프로파일을 가지고 실제 동작하는 컨테이너를 검증할 수 있다. 

1
2
3
$ kubectl exec hello-apparmor cat /proc/1/attr/current
 
k8s-apparmor-example-deny-write (enforce)
cs



마자막으로, 파일에 쓰기를 행하여 프로파일을 위반하도록 시도할 경우, 무슨 일이 발생하는지 볼 수 있다.

1
2
3
$ 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
cs



정리를 위해, 로드되지 않았던 프로파일을 정의하도록 시도할 경우, 무슨 일이 발생하는지 살펴보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
$ kubectl create -/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
    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-minion-group-t1f5}             Warning        AppArmor    Cannot enforce AppArmor: profile "k8s-apparmor-example-allow-write" is not loaded
cs



도움을 줄만한 에러 메시지: "Pod Cannot enforce AppArmor: profile "k8s-apparmor-example-allow-write" is not loaded" 를 발생시키며 파드의 상태가 실패났음을 주목한다. 또한 이벤트도 동일 메시지를 가지고 기록된다.





Administration


Setting up nodes with profiles

현재 K8S는 노드 상에 AppArmor 프로파일 로딩을 위한 어떤 내제된 메커니즘도 제공하고 있지 않다. 그러나 다음과 같이 프로파일을 설정하기 위한 다양한 방법들이 있다.   

  • 올바른 프로파일이 로드되었는지 확인하기 위한 DaemonSet을 통해서. 구현 예시는 여기에서 확인 가능 하다.
  • 노드 초기화 스크립트 (e.g. Salt, Ansible, etc.) 또는 이미지를 이용하여, 노드 초기화 시 
  • 예시에서 보이는 바와 같이, SSH를 통해 각 노드에 프로파일을 복사하고 로드시켜 줌으로써

스케줄러는 어떤 노드에 어떤 프로파일이 로드되었는지 인지하지 못한다. 그래서 프로파일 전체 셋이 모든 노드 상에 로드되어져야 한다. 대체가능한 다른 접근 방식은 노드에 각 프로파일 (또는 프로파일 클래스)에 대한 노드 레이블을 추가해 주는 것이다, 그리고 노드에 요구되는 프로파일과 함께 파드가 동작하는지 확인하기 위해 node selector를 이용하는 것이다.

Restricting profiles with the PodSecurityPolicy

PodSecurityPolicy 확장이 사용가능하게 설정되었다면,  클러스터-와이드 AppArmor 제한은 적용될 수 있다. PodSecurityPolicy을 사용가능하게 설정하기 위해, 다음 플래그를 "apiserver" 상에 설정해줘야 한다.

1
--enable-admission-plugins=PodSecurityPolicy[,others...]
cs


AppArmor 옵션은 PodSecurityPolicy 상에 주석으로써 정의될 수 있다.

1
2
apparmor.security.beta.kubernetes.io/defaultProfileName: <profile_ref>
apparmor.security.beta.kubernetes.io/allowedProfileNames: <profile_ref>[,others...]
cs


아무런 정의가 이루어지지 않은 상태인 경우, 기본 프로파일 이름 옵션이 기본으로 컨테이너에게 적용할 프로파일을 정의해 준다. 허용 가능프로파일 이름 옵션은 파드 컨테이너가 참조하여 동작되어지도록 허용한 프로파일 리스트를 정의해 준다. 두개의 옵션이 제공된 경우, 기본값은 허용되어질 것이다. 프로파일은 컨테이너에서와 같이 동일 형식으로 정의된다. 전체 정의를 확인하려면, API Reference 를 확인한다.


Disabling AppArmor

클러스터에서 AppArmor가 이용가능 상태로 되는 것을 원하지 않는다면,  커맨드-라인 플래그를 통해 이용하지 못하도록 만들 수 있다.

1
--feature-gates=AppArmor=false
cs


이용불가 상태가 되면, AppArmor 프로파일을 포함한 파드는 "Forbidden" 에러와 함께 확인 실패가 발생할 것이다. 기본으로 Docker는 언제나 비특권 파드(AppArmor 커널이 이용가능으로 설정된 경우)에 대해 "docker-default" 프로파일을 이용가능하게 하여 feature-gate가 이용할 수 없도록 설정된 경우라 하더라도 계속 그리되도록 할 것이다. AppArmor 을 이용가능하지 않도록 설정해 주는 옵션은 AppArmor가 정식 버전(GA)이 되면 제거될 것이다.

Upgrading to Kubernetes v1.4 with AppArmor

클러스터를 1.4로 업그레이드 하는데 있어 AppArmor에서는 필요한 조치는 없다. 그러나, AppArmor 주석이 포함된 기존 파드는 확인(또는 PodSecurityPolicy 승인)을 거키지 않게 될 것이다. 노드 상에 허가된 프로파일이 로드되어 있다면, 악의적인 사용자가 docker-default를 넘어서 파드 특권을 확대하기 위해 허가된 프로파일을 사전 적용 할 수도 있다.  이런한 점에 우려가 생긴다면, "apparmor.security.beta.kubernetes.io" 주석을 포함하는 파드의 클러스터를 제거하는 것이 권고된다.

Upgrade path to General Availability

AppArmor가 정식버전(GA)으로 되면, 현재 주석형태로 된 옵션은 필드로 전환될 것이다. 전환을 거치는 과정에서의 업그레이드와 다운그레이드에 대한 지원은 상당히 미묘한 뉘앙스를 갖는다. 전환이 이루어 질때 상세하게 설명이 이루어 질 것이다. 최소 2번의 릴리즈 동안 필드와 주석 둘을 지원하도록 할 것이다. 그리고 그 이후 최소 2번의 릴리즈 동안 명확하게 주석을 거부할 것이다.





Authoring Profiles


AppArmor 프로파일을 정확하게 정의하는 것은 곤란한  업무일 수 있다. 다행히도 도움이 될 몇 가지 도구들이 있다.

  • "aa-genprof" 와 "aa-logprof" 는 애플리케이션 활동과 로그를 모니터링하여 프로파일 룰을 생성해주고 취해야 할 조치를 승인해준다.  추가적인 안내는 AppArmor documentation에서 제공된다.
  • bane 은 간소화한 프로파일 언어를 이용하는 Docker 용 AppArmor 프로파일 생성기다.

프로파일을 생성하기 위해 개발용 워크스테이션에 Docker를 통해 애플리케이션을 동작시키도록 권고되기는 하지만, 파드가 동작하고 있는 K8S 노드상에 도구의 동작을 막는 것은 아무것도 없다.

AppArmor의 문제를 디버깅하기 위해, 특별하게 무엇이 거부되었는지 보기 위해 시스템 로그를 확인할 수 있다. AppArmor는 "dmesg"에 장황한 메시지를 로깅하므로, 보통 시스템 로그 또는 "journalctl"을 통해서 에러가 발견될 수 있다. 추가적인 정보는 AppArmor failures 에서 제공된다.





API Reference


Pod Annotation

컨테이너와 함께 동작하게 될 프로파일을 정의하기


Profile Reference

  • "runtime/default": 기본 런타임 프로파일을 참조한다.

    • AppArmor가 이용가능하도록 설정되어질 필요가 있는 경우를 제외하고, (PodSecurityPolicy 기본 없이) 프로파일을 정의하지 않는 경우와 같다.

    • Docker의 경우, 비특권 컨테이너에 대해서는 "docker-default" 프로파일에서 특권 컨테이너에 대해서는 비제한(프로파일 없음)에서 이를 해결한다. 

  • "localhost/<profile_name>": 이름으로 노드(로컬호스트)에 로드된 프로파일을 참조한다. 
  • unconfined: 이는 효과적으로 컨테이너에서 AppArmor를 이용할 수 없도록 한다.

여타 프로파일 참조 포맷은 유효하지 않다.


PodSecurityPolicy Annotations

아무것도 제공되지 않은 경우 컨테이너에 적요할 기본 프로파일을 정의하기 

파드 컨테이너가 정의할 수 있도록 허용된 프로파일 리스트를 정의하기

  • key: "apparmor.security.beta.kubernetes.io/allowedProfileNames"
  • value: 콤마로 구분된 프로파일 레퍼런스 리스트 (아래 설명한다.)
    • 비록 이스케이프 콤마가 프로파일 이름에서 적법한 캐릭터이기는 하나, 명백하게 여기서는 허용되지 않는다.





What's next

부가적인 리소스들로 다음이 있다.


댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함