Kubernetes 의 API 서버 보안

Kubernetes 의 API 서버 보안

이 포스팅에서는 쿠버네티스에에서 API 서버의 보안과 아래의 리소스들에 대해 이야기한다.

  • ServiceAccount
  • RoleRoleBinding
  • ClusterRoleClusterRoleBinding

쿠버네티스에서 API 서버 보안은 왜 필요할까?

쿠버네티스에서는 이론적으로 파드 외부 또는 내부에서 API 서버로 적절한 요청을 하면 어떤 리소스던 생성, 삭제, 수정, 조회가 가능하다.

그런데 만약 개발자의 코딩 실수로 어떤 파드에서 아무 관련이 없는 다른 파드를 삭제해버릴 수 있다면 큰 문제가 될 것이다.

그렇기 때문에 사용자 또는 파드에게 적절하게 권한을 부여하는 기능은 보안과 안정적인 운영을 위해 필수적이다.

회사에서 여러개의 팀이 하나의 공용 클러스터를 함께 사용하는 멀티테넌트 환경에서는 이러한 권한의 분리가 더욱 중요할 것이다.

만약 어떤 팀에서 실행 중인 파드의 목록과 상세 정보를 다른 팀에서 볼 수 있고, 심지어 삭제할 수가 있다면 문제가 될 것이다.

악의적인 목적이건, 실수이건 간에 위와 같은 일은 언제라도 벌어질 수 있기 때문에 모든 유저와 파드에게 적절한 권한을 부여해야 하는 것이다.

쿠버네티스 클러스터는 보통 위와 같은 문제가 발생하지 않도록 하기 위해 API 서버가 사용자나 Pod 의 요청을 받을 때 명시적으로 설정된 권한만 허용하고, 그 이외의 모든 권한은 허용하지 않도록 동작한다.

Service Account

Service Account 란?

서비스 어카운트(Service Account) 는 Kubernetes 의 파드에서 API 서버에 요청을 보냈을 때 이 "파드"를 식별하기 위한 리소스다. (사용자를 식별하는데 사용되지는 않는다)

파드에서 API 서버에 요청을 보내면 이 파드의 정체가 무엇인지 알아야 어떤 권한을 가지고 있는지도 알 수 있고,

이를 기반으로 파드의 요청이 권한에 맞는지를 확인하여 요청을 처리해줄지 말지를 결정할 것이다.

실제로 권한을 정의하고, 설정하는 부분은 이후에 설명할 Role, ClusterRole, RoleBinding, ClusterRoleBinding 의 역할이고,

ServiceAccount 는 이러한 권한을 적용할 수 있는 주체 중 한가지로서, Pod 의 신분증같은 것이라고 생각하면된다.

(물론 하나의 ServiceAccount 는 여러 Pod 가 사용할 수 있기 때문에 정확히 신분증과 동일한 개념은 아닐 것이다.)

ServiceAccount 의 특징

모든 파드는 무조건 하나의 ServiceAccount 와 매핑이 되어야 실행될 수가 있다.

그런데 ServiceAccount 를 만들지 않고, 파드의 매니페스트에 ServiceAccount 를 명시적으로 적어주지 않아도 파드가 잘 생성이 되고 실행 되는 것을 보고 의아할 수도 있다.

사실 이것은, 쿠버네티스의 ServiceAccount Controller 가 모든 네임스페이스에 default 라는 이름의 서비스어카운트가 있도록 자동 생성해주며,

ServiceAccount Admission Controller 가 파드의 매니페스트에 명시적으로 서비스어카운트를 정의하지 않으면 default 서비스어카운트를 매핑해주기 때문에 가능한것이다.

뿐만 아니라, 서비스어카운트는 mountable secrets 에 지정한 시크릿만 파드에 마운트할 수 있도록 강제하는 기능과,

Image pull secrets 기능을 통해 프라이빗 이미지 레지스트리에서 이미지를 가져올 수 있도록 하기 위한 시크릿을 이 서비스어카운트를 사용하는 파드에 자동으로 마운트시켜주는 기능도 가지고 있는데

image pull secrets 기능도 ServiceAccount Admission Controller 가 수행한다.

ServiceAccount 의 동작 방식

kubectl create sa <name> 명령어로 서비스어카운트를 생성할 수 있다.

Token Controller 는 서비스어카운트가 생성될 때마다 자동으로 kubernetes.io/service-account-token 타입의 Secret 을 생성하여 매핑시켜준다.

서비스어카운트 만들기

Token Controller 에 의해 생성된 Secret 에는 아래와 같은 3가지 데이터가 base64 로 인코딩되어 들어있다.

  • ca.crt - API 서버와 통신 시, SSL 인증을 위한 증명서
  • token - 서비스어카운트 이름, Secret 이름 등의 정보에 서명한 JWT. API 서버에 요청시 Bearer 토큰으로 사용됨
  • namespace - 네임스페이스

파드 내 애플리케이션은 위의 데이터를 사용하여 API 서버와 통신한다.

API 서버는 요청의 Authorization 헤더에 있는 Bearer token 을 디코딩하여 어느 서비스어카운트를 사용해 보낸 요청인지 식별하게 된다.

RBAC 란?

어떤 사람, 혹은 파드가 API 서버에 요청을 하면 API 서버는 인증(Authentication)과 인가(Authorization)를 수행한다.

인증은 접근 가능 여부를 확인하는 것이고, 인가는 접근 가능한 요청에 대해 요청된 자원에 접근할 수 있는지를 확인하는 것이다.

RBAC(Role-Based Access Control) 는 API 서버가 인가를 수행하는 여러 방법 중 하나다.

쿠버네티스는 다음과 같은 인가 방식을 제공한다.

  • Node
    • Kubelet 에 의한 요청에 대한 인가를 위한 방식
  • ABAC(Attribute-Based Access Control)
    • 리소스의 속성에 따라 인가를 하는 방식
  • RBAC
    • Role 을 기반으로 인가를 하는 방식
  • Webhook
    • 외부 API 를 통해 인가를 하는 방식

이 중에서 RBAC가 표준이며, 1.8.0 부터는 대부분의 클러스터에서 기본적으로 사용하는 방식이다.

RBAC 는 특정 주체(subject)특정 대상(url, resource 타입, 혹은 특정 resource) 에 대해 특정 행위(verb) 를 할 수 있는지를 지정하는 방식이다.

이를 어떻게 지정하는 지는 이후 Role 과 RoleBinding 에 대한 설명에서 명확해 질 것이다.

Role 과 RoleBinding

앞서 RBAC 는 특정 주체가 특정 대상에 대해 특정 행위를 할 수 있는지를 지정하는 방식이라고 했다.

여기서 Role 에 대상(resource 등)행위(verb) 를 지정하며, RoleBinding 에 주체(subject) 를 지정한다.

여기서 주체는 3가지(User, Group, ServiceAccount) 중 한가지가 된다.

대상은 보통 resource 의 타입을 정하는데, resourceName 으로 특정 리소스를 지정할 수도 있다.

이후에 설명할 ClusterRole 에서는 리소스가 아닌 URL 을 지정할 수도 있다. /healthz 와 같이 특정 리소스에 대한 요청이 아닌 경우도 있기 때문이다.

행위는 아래 표와같이 API 요청에 사용된 HTTP 메서드에 따라 특정 행위에 매핑되는데,

HTTP method verb
POST create
GET, HEAD get(개별 리소스), list(전체 오브젝트 내용을 포함한 리소스 모음), watch(개별 리소스 또는 리소스 모음을 주시)
PUT update
PATCH patch
DELETE delete(개별 리소스), deletecollection(리소스 모음)

행위에 대한 대상이 리소스라면 행위를 verb 로 적어주고, URL 이라면 HTTP 메소드로 적어준다.

이름 그대로 Role 은 역할이고, RoleBinding 은 이러한 역할을, 역할을 수행하는 주체에 연결시켜 주는 것이다.

Role 과 RoleBinding 의 특징

Role 과 RoleBinding 은 특정 네임스페이스에 종속된 개념이다. 그렇기 때문에 RoleBinding 은 다른 네임스페이스의 Role 을 바인딩해줄 수는 없다.

하지만, RoleBinding 이 같은 네임스페이스의 Role 을 다른 네임스페이스의 subject 에게 바인딩해 줄 수는 있다.

그래서 RoleBinding 에 subject 들을 명시할 때는 name 과 namespace, 그리고 kind(user/group/serviceaccount) 를 함께 명시한다.

하나의 Role 은 여러개의 RoleBinding 에 의해 바인드될 수 있고, 하나의 RoleBinding 은 하나의 Role 만 참조할 수 있다.

즉, Role 과 RoleBinding 은 일대다(one-to-many) 관계다.

반면 하나의 RoleBinding 은 하나의 Role 을 여러 주체에 연결시켜 줄 수 있고, 하나의 주체는 여러개의 RoleBinding 에 의해 권한이 부여될 수 있다.

즉, RoleBinding 과 Subject(ServiceAccount 등) 는 다대다(many-to-many) 관계다.

Role 과 RoleBinding 만들기

Role 과 RoleBinidng 을 만드는 방법은 크게 두가지가 있다.

하나는 매니페스트를 통해 만드는 방법이고, 나머지 하나는 kubectl create 명령어를 통해 만드는 방법이다.

다음과 같이 YAML 파일을 작성하지 않고도 직접 verb, resource, role, serviceaccount 등을 인자로 주어 Role 과 RoleBinding 을 생성할 수 있다.

롤 만들기

롤바인딩 만들기

RoleBinding 의 속성 중 Role 은 단수형이고, Subjects 는 복수형이라는 것을 봐도 이들간의 연관관계를 짐작할 수 있다.

다음과 같이 YAML 파일을 통해 Role 과 RoleBinding 을 생성할 수도 있다.

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: foo
name: service-reader
rules:
- apiGroups: [""]
verbs: ["get", "list"]
resources: ["services"]

ClusterRole 과 ClusterRoleBinding

ClusterRole 과 ClusterRoleBinding 이 필요한 이유는, Role 과 RoleBinding 이 특정 네임스페이스에 종속된 개념이기 때문이다.

그렇기 때문에 크게 다음과 같은 3가지 한계가 존재한다.

Role 과 RoleBinding 의 한계

첫번째로, 모든 네임스페이스에 대해 똑같은 권한을 부여하고 싶은 경우에 각 네임스페이스마다 매번 똑같은 내용의 Role 을 만들어줘야 할 것이다.

하지만 이런 방식은 번거롭고 실수하기도 쉽다.

두번째로, Role 은 같은 네임스페이스에 존재하는 리소스에 대한 권한만 허용할 수 있다.

그렇기 때문에 하나의 서비스어카운트에 모든 네임스페이스에 존재하는 특정 리소스에 대한 권한을 주고 싶은 경우에

모든 네임스이스마다 Role 과 RoleBinding 을 만들고 이 서비스어카운트에 바인딩해줘야 한다.

세번째로, 네임스페이스에 독립적인 클러스터 수준의 리소스나 리소스와 관련이 없는 요청도 있다.

예를 들어, Node 나 Namespace 와 같은 리소스은 물론이고, ClusterRole 과 ClusterRoleBinding 또한 특정 네임스페이스에 속한 리소스가 아니며,

앞서 언급한 /healthz 와 같은 URL 은 리소스와는 관련이 없는 요청이다.

위와 같은 3가지 경우 ClusterRole 과 ClusterRoleBinding 을 사용해야 한다.

첫번째의 경우는, RoleBinding 은 Role 뿐 아니라 ClusterRole 도 참조할 수 있기 때문에

ClusterRole 만 하나 만들어 두고, 각 네임스페이스에 있는 RoleBinding 이 이를 참조하여 subject 에 바인딩해주도록 하면

네임스페이스마다 Role 을 정의하는 대신 ClusterRole 이라는 글로벌한 Role 을 정의할 수 있게 된다.

두번째의 경우는, ClusterRole 에 위와 같이 RoleBinding 을 사용하는 것이 아니라, ClusterRoleBinding 을 사용하면 해결할 수 있다.

ClusterRole 을 사용하더라도 RoleBinding 을 사용하면 RoleBinding 과 같은 네임스페이스에 속한 리소스에대한 권한만 얻을 수 있다.

반면, ClusterRoleBinding 을 사용하여 subject 에 ClusterRole 을 바인딩을 해주면 모든 네임스페이스에 속한 리소스에 대한 권한을 얻을 수 있다.

세번째의 경우도 두번째의 경우와 마찬가지로 ClusterRole 과 ClusterRoleBinding 을 사용하면 해결할 수 있다.

ClusterRole 에는 Role 과는 달리 Resources 는 물론 Non-Resource URLs 을 정의할 수가 있다.

다음은 kubectl 을 통해 ClusterRole 을 만들고 이를 조회한 결과 화면이다.

클러스터롤 만들기

디폴트 ClusterRole & ClusterRoleBinding

API 서버는 기본적으로 디폴트 ClusterRole 과 ClusterRoleBinding 을 생성한다.

보통 디폴트로 생성된 클러스터롤과 클러스터롤바인딩은 system: 접두사를 가진다.

대부분의 디폴트 ClusterRole 들은 같은 이름을 가진 ClusterRoleBinding 이 존재한다.

디폴트 클러스터롤 조회하기
디폴트 클러스터롤바인딩 조회하기

그 중에서도 system:discoverysystem:public-info-viewer 의 경우

공개되어도 안전하다고 여겨지는 정보들에 대한 읽기 권한을 system:authenticated 이나 system:unauthenticated group 에 바인딩하며,

system:controller: 접두사를 가지는 것들은 쿠버네티스의 여러 컨트롤러들에게 권한을 부여하는데 사용된다.

다음과같이 system: 접두사를 가지지 않은 디폴트 클러스터롤(& 클러스터롤바인딩)도 존재한다.

cluster-admin 모든 리소스에 대한 모든 권한
admin ResourceQuota 와 Namespace 를 제외한 모든 리소스에 대한 조회 및 수정 가능
edit admin 의 권한에서 (Cluster)Role 과 (Cluster)RoleBinding 은 조회만 가능
view (Cluster)Role, (Cluster)RoleBinding, Secret 을 제외한 모든 리소스 조회만 가능

이 중 system:masters group 에 바인딩 되어있는 cluster-admin 을 제외하고

나머지 ClusterRole 들은 기본적으로 ClusterRoleBinding 에 의해 어딘가에 바인딩 되어 있진 않다.

참조

Comments