Image Security
Image name
아래와 같이 Pod의 manifest 파일의 `spec.image` 값이 `nginx` 인 경우를 가정해보자.
`pod-definition.yaml`
spec:
image: nginx
- Pod manifest 파일에서의 `image` 는 이미지 또는 저장소(Repository) 명이다.
- 위의 `image` 값은 실제로는 `library/nginx` 에 해당한다. (첫 번째 part는 사용자 또는 계정 이름이나, 제공되지 않으면 라이브러리라고 인식한다.)
- Cf. `library` 는 Docker의 공식 이미지가 저장되는 기본 계정의 이름이다.
- 더 나아가, 이미지를 어디서 pull 해오는지를 구체적으로 지정하지 않았기 때문에 도커의 기본 레지스트리 값인 Docker Hub로 추정할 수 있다. 도커의 DNS값은 `docker.io` 이다. 따라서 이는 `docker.io/library/nginx`로도 볼 수 있다.
새 이미지를 생성하거나 업데이트할 때마다 레지스트리에 push하면 누군가 이 애플리케이션을 배포할 때마다 레지스트리에서 pull해간다. 예를들어 구글의 레지스트리 gcr.io 에는 종단 간 테스트 등의 쿠버네티스와 관련된 많은 이미지가 저장되어 있다.
Private Repository
일반인에게 공개되어선 안되는 내부용 애플리케이션의 경우 private 레지스트리를 이용하는 것이 좋다. AWS, Azure, GCP 등 많은 클라우드 서비스 제공자는 기본값으로 private 레지스트리를 제공한다. 자격 증명 모음(Set of credential)을 이용해 액세스 가능하다.
1. 도커의 관점에서 private image를 통해 컨테이너를 실행하고자 한다면, 우선 `docker login` 커맨드를 통해 private registry에 로그인 해야 한다.
docker login private-registry.io
2. private registry의 이미지를 통해 애플리케이션 실행
docker run private-registry.io/apps/internal-app
3. Pod manifest file로 돌아가서 이미지 명에 이미지의 전체 경로를 명시
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod
spec:
containers:
- name: nginx
image: private-registry.io/apps/internal-app
4. 쿠버네티스에서 이미지는 워커 노드의 도커 런타임에 의해 pull되고 run된다. 워커 노드의 도커 런타임에 자격 증명을 전달하는 방법은 아래와 같다.
1) 내부에 자격 증명(credential)을 지닌 Secret 오브젝트를 생성
Ex) Secret name을 `regcred` 로 생성
kubectl create secret docker-registry regcred \
--docker-server=private-registry.io \
--docker-username=registry-user \
--docker-password=registry-password \
--docker-email=registry-user@org.com
- 해당 Secret은 docker registry 타입이다. 때문에 `kubectl create secret` 뒤에 해당 타입을 명시 후 Secret name을 명시한다.
Cf. docker-registry 는 built-in Secret type으로 Docker credential을 저장하기 위해 존재한다.
2) 해당 자격 증명이 필요한 Pod manifest file(3번)의 `spec.imagePullSecrets` 하위에 해당 Secret을 나열
...
spec:
constainers:
- name: nginx
image: private-registry.io/apps/internal-app
imagePullSecrets:
- name: regcred
...
Cf. Pre-requisite(Security in docker)
Docker가 설치된 호스트가 있다고 가정해보자.
프로세스
가상머신과 달리 컨테이너는 그것이 속한 호스트와 완전히 분리되어있지 않다. 때문에 컨테이너와 호스트는 동일한 커널을 공유한다.
컨테이너는 리눅스의 네임스페이스를 이용해 구분된다. 호스트 역시 네임스페이스를 가진다. 여기에 속한 컨테이너는 더 나아가 자신만의 네임스페이스를 다시 가진다. 때문에 컨테이너에 의해 동작되는 모든 프로세스들은 사실은 호스트 위에서 동작되고 있으나 자신만의 네임스페이스에 속할뿐이다.
컨테이너에 프로세스를 실행시키는 경우 컨테이너에서는 자신 또는 자신의 하위(child) 네임스페이스에 속한 프로세스들만을 조회(`ps aux`)할 수 있다. 외부 혹은 다른 네임스페이스의 프로세스는 조회할 수 없다. 도커 호스트(Host에서 실행되는 도커 호스트) 역시 자신 또는 자신의 하위(child) 네임스페이스에 속한 프로세스들을 조회할 수 있지만, 컨테이너의 상위에 존재하기 때문에 컨테이너에서 동작되는 프로세스들 + α를 조회할 수 있다. 이 때 조회되는 컨테이너의 프로세스들은 컨테이너 내에서 동작되는 프로세스들을 조회했을 때와 다른 PID를 가진다. 이는 프로세스들이 다른 네임스페이스에서는 다른 프로세스 ID를 가질 수 있기 때문이다.
유저
도커 호스트는 root 유저와 root 유저가 아닌 유저들을 가진다.
기본적으로 도커는 컨테이너에서 프로세스를 실행시킬 때 root 유저로 실행시킨다. 이를 방지하려면(root 유저가 아닌 유저로 프로세스를 실행하려면) docker run 커맨드에 user 옵션을 이용해 새로운 유저 ID를 명시해주어야 한다.
ex. 1시간동안 sleep하는 우분투 컨테이너를 유저ID 1000으로 실행시킨다.
docker run --user=1000 ubuntu sleep 3600
또 다른 방법은 아예 도커 이미지의 생성 시점에 이(root 유저가 아닌 새로운 유저 ID)를 정의하는 것이다. 아래는 이미지의 도커 파일을 작성한 예시이다.
FROM ubuntu
USER 1000
앞서 작성한 도커 파일을 통해 이미지를 빌드한다.
docker run my-ubuntu-image sleep 3600
위와 같이 도커 이미지 파일에 USER를 명시하고 해당 이미지를 빌드 및 실행시키면 프로세스 실행시 명시한 USER 로 조회되는 것을 확인할 수 있다.
ps aux
도커는 컨테이너 내에서의 root 유저 권한을 제한하는 보안 기능들을 구현했다. 컨테이너 내에서의 root 유저는 호스트의 root 유저와 다르다. 도커는 이를 리눅스의 기능을 이용해 구현한다.
시스템(호스트라고 가정)에서의 root 유저는 모든 작업을 수행할 수 있고, 이는 루트 사용자가 실행하는 프로세스도 마찬가지이다.
기본적으로 도커는 컨테이너를 제한된 기능으로 실행한다. 때문에 컨테이너 내에서 실행된 프로세스는 호스트를 reboot하거나 호스트 또는 같은 호스트에서 실행중인 다른 컨테이너를 방해하는 작업을 수행할 수 있는 권한이 없다. 이를 override하고 현재 사용 가능한 권한보다 추가적인 권한을 부여하려면 실행시 `--cap-add` 옵션을 사용할 수 있다.
docker run --cap-add MAC_ADMIN ubuntu
비슷하게, 권한들을 제거하고 싶다면 `--cap-drop` 옵션을 사용할 수 있다.
docker run --cap-drop KILL ubuntu
만약 모든 권한을 가능하게 실행하고자 한다면 `--privileged` 옵션을 사용할 수 있다.
docker run --privileged ubuntu
Security Contexts
Kubernetes Security
도커 컨테이너를 실행시 `--cap-add`, `--cap-drop` 등의 옵션을 통해 권한을 달리할 수 있는데, 쿠버네티스에서도 이러한 리눅스 기능들이 설정되게 할 수 있다.
쿠버네티스에서 컨테이너들은 파드 내에 캡슐화된다. 보안 설정은 컨테이너 레벨 또는 파드 레벨에서 할 수 있다. 만약 파드 레벨에서 설정하게 되면, 파드 내의 모든 컨테이너들에 영향을 미친다. 파드 레벨과 컨테이너 레벨 둘 다에서 설정한다면 컨테이너 레벨의 설정이 파드 레벨의 설정을 덮어쓰게 된다.(override)
파드 레벨에서 security context 설정시 `spec.securityContext` 필드를 작성한다.
apiVersion: v1
kind: Pod
metadata:
name: web-pod
spec:
# To configure security context on the container at a pod level,
# add a field called 'security context'
securityContext:
runAsUser: 1000
containers:
- name: ubuntu
image: ubuntu
command: ["sleep", "3600"]
# To configure security context on the container at the container level,
# add a field called 'security context'
securityContext:
runAsUser: 1000
# field called 'capabilities' are only suppported at the container level(NOT at the Pod level)
capabilities:
add: ["MAC_ADMIN"]
# To add all privileges, specify 'privileged: true'
⚠️ 이미 존재하는 파드를 edit하여 `securityContext`를 변경하려하니 적용되지 않았다. 삭제 후 새로 생성해야 했다.
k get po [pod-name] -o yaml > [pod-name].yaml
# after modify manifest file
k apply -f [pod-name].yaml
🤗 참고한 블로그
'Kubernetes' 카테고리의 다른 글
k8s) Storage - 1. Volume : PV(Persistent Volume), PVC(Persistent Volume Claim) (0) | 2024.03.11 |
---|---|
k8s) Security - 5. Network Policy(Ingress & Egress) (0) | 2024.03.10 |
k8s) Security - 3. Authorization(RBAC, Cluster Role, Service Account) (0) | 2024.03.10 |
k8s) Security - 2. KubeConfig, API Groups (0) | 2024.03.10 |
k8s) Security - 1. Kubernetes의 Authentication, TLS 구조 (0) | 2024.03.10 |