쿠버네티스는 API 서버를 사용하여 리소스들의 생성을 감독합니다. 실제로 API 서버를 사용하는 리소스들을 살펴보면 아래와 같습니다. etcd를 제외하고서 거의 대부분의 중요한 리소스들이 API 서버를 의존하고 있습니다.
API 서버는 그 이름처럼 API를 받아서 이를 처리하는 리소스인데, 외부 클라리언트가 kubectl 등으로 요청을 전송하면, RESTful API로 변경하여 클러스터를 조작합니다. API 서버 내부에서는 그 요청을 보낸 주체가 누구인지 인증 플러그인을 거치고, 권한 승인 플러그인으로 리소스에 대해 요청을 수행할 수 있는지 결정한 다음, 승인 제어 플러그인을 통해 리소스를 수정/삭제 혹은 거절합니다. 최종적으로 리소스의 상태와 데이터 등을 etcd에 저장합니다.
API 서버가 직접 리소스를 생성하거나 추가하는 것은 아니고, API 서버가 리소스의 생성 및 배포를 위해 리소스 객체들을 담아두고, 이를 컨트롤러 매니저가 감시하여 필요한 리소스들을 생성하며 스케쥴러가 노드에 파드를 할당하여 각 노드의 kubelet에 전달하여 생성합니다.
서비스 어카운트
이때, API 서버에 접속할 수 있는 클라이언트는 외부 사용자도 있지만, 파드 내부의 애플리케이션이 될 수도 있습니다. 이때, 파드는 서비스 어카운트(Service account)를 통해 접근하게 됩니다. 앞서 API 서버에는 인증 플러그인을 거친다고 언급했는데, 파드에서는 /var/run/secrets/kubernetes.io/serviceaccount에 secret 볼륨이 마운트 되어 있는데 /token 디렉터리에 토큰이 있어서 이 토큰으로 파드를 인증합니다.
서비스 어카운트라는 자격을 토큰이라는 자격증으로 인증하는 느낌으로 바라볼 수도 있겠네요. 이 서비스 어카운트는 그 네임스페이스에 속한 리소스만이 소지할 수 있습니다. 예를 들어, foo 라는 네임스페이스에 있는 파드는 foo 네임스페이스에 속한 서비스 어카운트만 사용 가능합니다.
파드는 자신이 가진 서비스 어카운트라는 자격증을 사용해서 원하는 작업을 요청할 수 있습니다. 자격증에서 특정 자격을 가지지 않고 있다면 그 자격을 요구하는 작업은 불가능하게 되겠죠? 이렇게 토큰을 인증하더라도, 승인이 허가되거나 불허가되기도 합니다. API 서버는 승인 플러그인을 사용할 수 있는데, 이중의 하나가 역할 기반 제어 플러그인(RBAC)입니다.
서비스 어카운트는 시크릿을 마운트하여 사용하는데 이는 이미지 풀 시크릿도 포함됩니다. 따라서, 서비스 어카운트에 사용할 이미지 풀 시크릿을 지정해 놓으면 파드에 서비스 어카운트를 지정함으로써 이미지 풀 시크릿을 지정하지 않더라도 해당하는 파드에 추가할 필요가 없습니다.
apiVersion: v1
kind: ServiceAccount
metadata:
name: erc-service-account
imagePullSecrets:
- name: erc-cred
apiVersion: v1
kind: Pod
metadata:
name: curl-custom-sa
spec:
serviceAccountName: foo
containers:
- name: main
image: curlimages/curl
command: ["sleep", "9999999"]
- name: ambassador
image: stae1102/kubectl-proxy:1.0.3
서비스 어카운트를 사용하면 토큰이 파드 속 컨테이너에 마운트 되므로 컨테이너 내부로 접속하면 각 토큰을 확인할 수 있습니다
API 서버와 통신하기 위해 서비스 어카운트의 토큰을 사용할 수 있습니다. 하지만 지금은 별도의 권한을 설정하지 않은 서비스 어카운트이므로 획득할 수 없습니다.
RBAC 인증 플러그인
RBAC는 권한을 제공하여 사용자가 클러스터 관련 작업을 가능케 합니다. AWS IAM 사용자에게 정책을 추가하여 역할을 달리하는 것과 같은 느낌입니다. API 서버는 REST 인터페이스를 통해 HTTP 요청을 서버로 전송해 작업을 수행하므로, 요청과 함께 자격증명을 포함해 인증합니다.
또한, 수행할 종류도 제한할 수 있습니다. get, create, update 등의 작업이 HTTP 메서드의 GET, POST, PUT에 매핑됩니다.
RBAC 리소스
RBAC는 네 가지 리소스로 구성됩니다. 롤과 롤바인딩, 클러스터롤과 클러스터롤바인딩이며 리소스 접근 수준이 네임스페이스인지 클러스터인지의 차이뿐입니다.
롤은 역할을 만들고, 롤바인딩은 역할을 주체에 지정해줍니다. 서비스 어카운트에 역할을 지정해주기 위해서는 미리 역할을 만들고, 이 역할을 사용할 서비스 어카운트에 역할을 이어줍니다.
📍 롤, 롤바인딩 실습
RBAC를 직접 테스트해보는 방법이 가장 이해하기 쉬울 것 같습니다. 롤과 클러스터롤의 차이를 확인하기 위해 네임스페이스와 파드를 생성합니다.
kubectl create ns foo
kubectl run test --image=stae1102/kubectl-proxy:1.0.3 -n foo
kubectl create ns bar
kubectl run test --image=stae1102/kubectl-proxy:1.0.3 -n bar
여기서 kubectl proxy는 kubectl의 proxy 명령어로 API 서버로 요청을 보내기 위한 작업을 수행하는 이미지입니다. 각, 운영체제마다 kubectl 바이너리가 다르므로 직접 이미지를 생성하는 것이 좋습니다. 위 이미지는 arm64를 사용하는 호스트 OS를 기준으로 생성한 이미지입니다. 따라서, cpu 아키텍처가 다르다면 다른 버전을 지정해주어야 합니다. 아래는 Dockerfile과 해당 도커파일에서 사용하는 스크립트입니다.
FROM alpine
RUN apk update && apk add curl && curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/arm64/kubectl" && chmod 700 kubectl && mv kubectl /
ADD kubectl-proxy.sh /kubectl-proxy.sh
RUN chmod 700 kubectl-proxy.sh
ENTRYPOINT /kubectl-proxy.sh
#!/bin/sh
API_SERVER="https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT"
CA_CRT="/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
TOKEN="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)"
./kubectl proxy --server="$API_SERVER" --certificate-authority="$CA_CRT" --token="$TOKEN" --accept-paths='^.*'
kubectl 버전은 여기서 확인할 수 있습니다.
이제 kubectl proxy를 통해 서비스 목록을 나열하면 아래와 같은 결과를 확인할 수 있습니다.
디폴트로 생성된 서비스 어카운트로는 서비스 목록을 나열할 수가 없군요. 그러면 이제 역할을 만들어줄 차례입니다.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: foo
name: service-reader
rules:
- apiGroups: [""]
verbs: ["get", "list"]
resources: ["services"]
위는 foo namespace의 service 리소스에 get과 list를 허용하는 역할을 의미합니다. apiGroups는 서비스의 apiGroup으로 core apiGroup은 공백으로 표기합니다. 그림으로 보자면 아래와 같습니다.
foo 네임스페이스에 생성한 롤은 foo 네임스페이스에 있는 서비스에만 역할을 부여하는 것이죠. 당연히 명령어로도 간단하게 롤을 생성할 수 있습니다.
kubectl create role service-reader --verb=get,list --resource=services -n bar
bar 네임스페이스에 서비스를 가져오고 나열할 수 있는 롤을 생성합니다.
롤을 생성했다면, 이 롤을 사용할 수 있도록 서비스 어카운트(혹은 유저, 그룹)와 이어줘야 합니다.
kubectl create rolebinding test --role=service-reader --serviceaccount=foo:default -n foo
test 롤바인딩을 만드는데 역할은 이전에 만든 service-reader를 고르고, 바인딩할 서비스 어카운트는 foo 네임스페이스의 default 서비스 어카운트를 선택합니다. 아래는 현재의 RBAC 구조입니다.
롤바인딩은 서비스 어카운트 외에도 여러 개의 주체들에 롤을 바인딩할 수 있습니다. 이제 foo 네임스페이스의 service에 조회할 권한이 생겼으므로 확인해봅시다.
/ # curl localhost:8001/api/v1/namespaces/foo/services
foo 네임스페이스에 있는 파드 컨테이너에 접속하여 API 서버로 service 조회가 가능합니다. bar 네임스페이스에도 권한을 부여합니다. kubectl edit 명령어를 사용합니다. 다른 네임스페이스에 있더라도 다른 포드의 서비스 어카운트를 추가할 수 있습니다.
kubectl edit rolebinding test -n foo
subjects:
- kind: ServiceAccount
name: default
namespace: foo
- kind: ServiceAccount
name: default
namespace: bar
이렇게 하면 롤 바인딩은 foo 네임스페이스에 있지만, bar 네임스페이스의 default 서비스 어카운트에서 foo 네임스페이스의 서비스에 get, list할 수 있는 역할을 바인딩받습니다.
📍 클러스터롤, 클러스터롤바인딩 실습
롤과 클러스터롤의 차이는 클러스터 범위의 리소스에 역할을 가지는지의 차이입니다. 롤은 네임스페이스 스코프의 리소스에 역할을 가지며, 클러스터롤은 클러스터 스코프의 리소스에 역할을 가집니다. 그래서 둘의 사용법은 크게 다르지 않습니다.
이번에는 클러스터에서 PersistentVolume을 나열하는 역할을 생성합니다.
kubectl create clusterrole pv-reader --verb=get,list --resource=persistentvolumes
위 클러스터롤로 pv들을 가져오고 나열할 수 있는 권한을 생성했으므로, 이 권한을 실제로 사용할 수 있도록 바인딩해줍시다.
kubectl create rolebinding pv-test --clusterrole=pv-reader --serviceaccount=foo:default -n foo
일반 롤바인딩을 생성하여 클러스터롤을 foo 네임스페이스의 default 서비스 어카운트에 바인딩했습니다. 클러스터롤과 롤바인딩의 조합은 여전히 네임스페이스 수준의 리소스에만 접근할 수 있습니다. 클러스터롤을 참조하는 롤바인딩은 클러스터 수준의 리소스에 대한 접근 권한을 부여하지 않기 때문입니다.
즉, 클러스터 수준의 리소스에 접근하고자 한다면 클러스터롤과 클러스터롤바인딩을 사용해야만 합니다. 이전 롤바인딩은 삭제하고 클러스터롤바인딩을 생성합니다.
kubectl create clusterrolebinding pv-test --clusterrole=pv-reader --serviceaccount=foo:default
이제 클러스터 수준으로 리소스에 작업을 수행할 수 있습니다. 클러스터롤과 클러스터롤바인딩을 사용하면 아래와 같은 리소스 형태를 가집니다.
즉, 조합에 따라서 리소스에 접근할 수 있는 수준이 달라집니다. 클러스터 수준의 리소스에 접근해야 하거나 여러 네임스페이스에 있는 네임스페이스에 접근해야 한다면 클러스터롤-클러스터롤바인딩을, 특정 네임스페이스 리소스에 접근하기 위해 역할을 재사용해야 한다면 클러스터롤-롤바인딩을, 특정 네임스페이스 리소스에 접근하기 위해 역할을 한 번 사용한다면 롤-롤바인딩을 사용하면 좋습니다.
클러스터롤은 기본적으로 다양하게 생성되므로 필요한 역할에 따라서 롤바인딩, 클러스터롤바인딩을 사용해야 합니다. 너무 과도한 권한은 사용하지 않는 게 좋습니다. 대표적으로 view, edit, admin, cluster-admin 클러스터롤이 포드의 서비스 어카운트에 사용되는 역할이며, 포드에 적절한 클러스터롤을 서비스 어카운트에 바인딩 해주어야 합니다. 예를 들어, view 클러스터롤에는 시크릿을 읽을 권한이 없습니다. 시크릿에서 view 클러스터롤보다 더 많은 권한을 가지는 토큰을 가져와 사용할 수 있기 때문입니다.
정리하며
너무 과한 역할은 쿠버네티스 보안에 좋지 않습니다. AWS에서 IAM 유저를 만들고 역할을 지정하여 사용하는 것을 권장하는 것처럼 각 포드가 적절한 권한의 서비스 어카운트를 부여하여 보안을 높이는 것이 중요합니다.
원래는 RBAC에 대해 이해하기 좀 어려웠는데, AWS IAM과 비교하며 공부하니까 이해가 잘 돼서 글을 작성하게 됐습니다. 글을 정리하면서 한 번 더 공부하니까 더 머리에 잘 들어오네요.
참고
쿠버네티스 인 액션, 마르코 룩샤, 에이콘 출판, 12장
'데브옵스 & 인프라 > Kubernetes' 카테고리의 다른 글
[k8s/argocd] Helm과 ArgoCD, Github Actions, ECR를 활용한 배포 파이프라인 구축 (minikube) (0) | 2023.11.15 |
---|---|
[Kubernetes/Helm] 헬름(Helm)으로 서버 애플리케이션 배포하기(node.js + mysql) (1) | 2023.11.12 |
[Kubernetes] 쿠버네티스에 데이터베이스 배포하고 애플리케이션 연결하기(Secret 사용) (0) | 2023.11.10 |
[Kubernets] Pod 디버깅 - 오류 정보 확인하고 해결하기 (1) | 2023.11.09 |
[Kubernetes/helm] 쿠버네티스의 패키지 관리자 Helm 알아보기 (5) | 2023.11.09 |