12. Securing the Kubernetes API Server
Roles grant permissions, whereas RoleBindings bind Roles to subjects (출처: https://livebook.manning.com/book/kubernetes-in-action/chapter-12)
주요 내용
- Authentication 과 ServiceAccount 에 대한 이해
- RBAC plugin, Role/RoleBinding, ClusterRole/ClusterRoleBinding 에 대한 이해
12.1 Understanding authentication
API server 가 요청을 받게 되면, authentication plugin 을 거치며 요청을 보낸 주체가 누구인지 분석한다.
Plugin 을 거치게 되면 username, user ID, 클라이언트가 속하는 group 등의 정보가 API server 에게 전달된다.
12.1.1 Users and groups
Understanding users
Kubernetes 에서는 API server 에 연결하는 주체를 2가지로 분류한다.
- 사람 (users)
- Pods (정확히는 pod 내에 실행 중인 애플리케이션)
실제 사람과 같은 경우 외부 시스템 (SSO 등) 에 의해 관리된다. (관심 대상이 아니다) 실제 사용자의 계정을 관리하는 리소스는 존재하지 않는다.
한편 pod 의 경우 service accounts 를 사용하는데, 이는 클러스터에 ServiceAccount 리소스로 저장된다.
Understanding groups
모든 사용자는 하나 혹은 그 이상의 group 에 속할 수 있다. Group 을 사용하게 되면 여러 명의 user 에게 동시에 권한을 줄 수 있다.
Authentication plugin 에서 group 을 알려준다고 했는데, plugin 은 group 을 string 으로 알려준다. 몇 가지의 내장된 group 이 있다.
system:unauthenticated
: 모든 authentication plugin 이 요청을 authenticate 할 수 없는 경우system:authenticated
: Authentication 이 성공한 user 에게 자동으로 설정된다system:serviceaccounts
: 시스템(클러스터)에 존재하는 모든 ServiceAccounts 를 포함한다system:serviceaccounts:<namespace>
: 특정 namespace 안의 모든 ServiceAccounts 를 포함한다
12.1.2 Introducing ServiceAccounts
Pod 가 API server 와 통신할 때는 /var/run/secrets/kubernetes.io/serviceaccount/token
에 있는 token 을 이용해서 authentication 한다는 것을 배웠다. (이 파일은 secret
volume 을 통해 mount 된다) 이제 이 파일이 정확히 무엇을 의미하는지 알아본다.
모든 pod 는 ServiceAccount 와 연결되게 되는데, 이 token 파일에는 ServiceAccount 의 authentication token 이 들어있다. 그래서 애플리케이션이 token 을 이용해서 API server 와 통신하게 되면, authentication plugin 에서는 해당 ServiceAccount 를 authenticate 하고, API server 에게 ServiceAccount username 을 알려준다.
ServiceAccount username 은 다음과 같은 형식으로 구성되어 있다.
1
system:serviceaccount:<namespace>:<service account name>
이제 API server 는 이 ServiceAccount username 을 authorization plugin 에게 넘기고, 요청의 action 을 수행할 권한이 있는지 확인한다.
결국 ServiceAccount 는 pod 내의 애플리케이션이 API server 와의 통신에서 자신을 authenticate 하는 방법이다.
Understanding the ServiceAccount resource
ServiceAccount 도 namespace 에 귀속된다.
각 namespace 에는 default ServiceAccount 가 기본적으로 생성되며, pod 들은 이 default ServiceAccount 를 기본적으로 사용한다.
1
2
3
$ kubectl get sa
NAME SECRETS AGE
default 1 79d
각 pod 는 오직 하나의 ServiceAccount 와 연결될 수 있지만, 같은 ServiceAccount 를 여러 pod 에서 재사용 할 수 있다. 단 제약 조건이 있는데, 같은 namespace 의 ServiceAccount 만 사용할 수 있다.
Understanding how ServiceAccounts tie into authorization
Pod manifest 에 ServiceAccount 를 지정하게 되는데, (지정하지 않으면 default 사용) ServiceAccount 마다 역할이 정해져 있기 때문에 pod 의 권한 또한 제어가 가능하다.
API server 가 token 을 받으면 어떤 ServiceAccount 인지 확인하고, 해당 ServiceAccount 의 권한을 확인하여 요청을 수행할 권한이 있는지 확인하게 되는 방식이다.
12.1.3 Creating ServiceAccounts
권한 부여는 최소로 하는 것이 원칙이다. 그래서 pod 의 용도에 맞게 ServiceAccount 를 직접 설정하고 pod 에 연결해야한다.
생성은 아래와 같이 하면 된다.
1
$ kubectl create serviceaccount <NAME>
이제 describe
로 확인해 보면,
1
2
3
4
5
6
7
8
9
$ kubectl describe sa foo
Name: foo
Namespace: default
Labels: <none>
Annotations: <none>
Image pull secrets: <none>
Mountable secrets: foo-token-hnx6x
Tokens: foo-token-hnx6x
Events: <none>
Tokens: ...
에 새로운 token이 생성된 것을 확인할 수 있고, 이는 Secret 으로 관리된다.
Mountable secrets
원래 pod 는 원하는 Secret 을 임의로 mount 할 수 있다. 하지만 pod 의 ServiceAccount 에서, 이 ServiceAccount 를 사용하는 pod 이 mount 할 수 있는 Secret 을 제한할 수 있다.
Mountable secrets 목록은 이 pod 가 mount 할 수 있는 Secret 의 목록이다. 다른 Secret 은 사용할 수 없다.
Image pull secrets
Image pull secret 은 private image repository 에서 image 를 pull 받을 때 사용하는 credential 이다. Mountable secrets 처럼 사용 가능한 secret 을 제한하는 것은 아니고, image pull secret 에 있는 secret 은 해당 ServiceAccount 를 사용하는 모든 pod 에 자동으로 mount 된다.
12.1.4 Assigning a ServiceAccount to a pod
Pod definition 에서 .spec.serviceAccountName
필드에 적어주면 된다. Pod 생성할 때 미리 정해야 하며, pod 가 생성된 뒤에는 수정할 수 없다.
12.2 Securing the cluster with role-based access control
Role-based access control (RBAC) plugin 을 사용하게 되면, unauthorized user 가 클러스터의 상태를 조회하거나 수정하는 것을 막을 수 있다. RBAC 를 사용하여 클러스터의 authorization 을 관리하는 방법을 살펴본다.
12.2.1 Introducing the RBAC authorization plugin
Kubernetes API server 는 authorization plugin 을 사용해서 요청을 보낸 주체가 작업(action)을 수행할 수 있는지 판단한다고 했다.
Understanding actions
작업의 종류에는 다음과 같은 것들이 있다. 기본적으로는 HTTP 요청을 사용하기 때문에 GET, POST, PUT, DELETE 등이 있고, 위 요청을 하면서 특정 리소스의 정보를 요구하게 된다. 예를 들면 pod 의 목록 가져오기 (GET), Service 생성하기 (POST), Secret 업데이트 (PUT) 등과 같은 요청이 있을 것이다.
이제 RBAC 와 같은 authorization plugin 을 사용하게 되면, 클라이언트가 리소스에 대한 요청을 할 수 있는지 없는지 판단하게 된다. (리소스의 종류와 작업의 종류 별도로 관리 가능)
추가로 RBAC 의 경우, 리소스의 특정 인스턴스 (특정 이름을 가진 service)에 대해서도 권한을 관리할 수 있게 된다.
Understanding the RBAC plugin
RBAC plugin 은 user 의 role 을 기반으로 authorization 을 하게 된다. User (사용자/ServiceAccount) 는 하나 이상의 role 이 부여되며, 각 role 마다 어떤 리소스에 어떤 작업을 할 수 있는지 정해져 있다.
만약 user 가 다수의 role 을 부여받았다면, role 에서 부여한 권한들의 합집합이 user 의 권한이 된다.
12.2.2 Introducing RBAC resources
RBAC authorization rules 은 4개의 리소스를 통해 설정된다. 크게 2가지 분류로 나뉜다.
- Role 과 ClusterRole: 어떤 리소스에 어떤 작업을 수행할 수 있는지 관리한다
- RoleBinding 과 ClusterRoleBinding: 위 role 을 적용할 user, group, ServiceAccounts 를 관리한다
Roles 는 무엇(what)을 할 수 있는지 관리하는 것이고, bindings 는 누가(who)할 수 있는지 관리한다.
그리고 Cluster 가 붙은 리소스의 차이점은 Role/RoleBinding 는 namespace 가 존재하는 리소스를 관리하는 반면, ClusterRole/ClusterRoleBinding 의 경우 namespace 가 없는 클러스터 레벨의 리소스를 관리한다는 점이다.
Creating the namespaces and running to pods
Namespace 별로 동작이 달라지는 것을 확인하기 위해 namespace 를 만든다.
1
2
3
4
$ kubectl create ns foo
$ kubectl run test --image=luksa/kubectl-proxy -n foo
$ kubectl create ns bar
$ kubectl run test --image=luksa/kubectl-proxy -n bar
이제 터미널을 2개 열고 각 pod 안으로 들어간다.
1
2
$ kubectl exec -it test -n foo -- sh
$ kubectl exec -it test -n bar -- sh
Listing services from your pods
RBAC 가 동작하고 있음을 확인하기 위해 curl
을 사용해 foo
namespace 의 Service 목록을 가져올 것이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ curl localhost:8001/api/v1/namespaces/foo/services
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {
},
"status": "Failure",
"message": "services is forbidden: User \"system:serviceaccount:foo:default\" cannot list resource \"services\" in API group \"\" in the namespace \"foo\"",
"reason": "Forbidden",
"details": {
"kind": "services"
},
"code": 403
}
Default ServiceAccount 에는 아무 권한이 주어져 있지 않으므로 실패해야 한다. RBAC 가 잘 동작하고 있다.
12.2.3 Using Roles and RoleBindings
Creating Roles
이제 Role 을 생성해보자. Service 목록을 조회할 수 있도록 할 것이다.
1
2
3
4
5
6
7
8
9
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: foo # Role 은 namespaced resource 이다
name: service-reader
rules:
- apiGroups: [""] # Service 는 core apiGroup 에 있는 리소스이다
verbs: ["get", "list"] # 특정 리소스를 get 하는 요청, 전체를 list 하는 요청 허용
resources: ["services"] # 대상 리소스는 service 이고, 반드시 복수형으로 적어야 한다
리소스를 명시할 때 반드시 복수형으로 적어야 한다. 그리고 리소스의 특정 인스턴스에만 접근을 허용하고 싶을 경우
resourceNames
field 를 적어주면 된다.
생성은 마찬가지로 YAML 파일을 POST 하면 되고, namespace 에 주의해야 한다.
1
$ kubectl create -f service-reader.yaml -n foo
Role 을 생성하는 또 다른 방법으로 CLI 에 직접 입력하는 방법이 있다. bar
namespace 에는 CLI 로 생성한다.
1
2
$ kubectl create role service-reader --verb=get --verb=list \
--resource=services -n bar
Binding a role to a ServiceAccount
이제 이 Role 을 ServiceAccount 에 bind 해줘야 Role 이 실제로 적용된다. RoleBinding 리소스를 생성한다.
1
2
$ kubectl create rolebinding test --role=service-reader \
--serviceaccount=foo:default -n foo
User 에게 bind 하려면
--user
, group 에게 bind 하려면--group
옵션을 주면 된다.
RoleBinding 은 하나의 Role 만 reference 할 수 있다. 다만 Role 자체는 재사용이 가능하므로 여러 RoleBinding 을 생성할 수 있다.
이제 foo
의 default
ServiceAccount 에 service-reader
Role 이 bind 되었다. Service 의 목록을 다시 조회해 본다.
1
2
3
4
5
6
7
8
9
$ curl localhost:8001/api/v1/namespaces/foo/services
{
"kind": "ServiceList",
"apiVersion": "v1",
"metadata": {
"resourceVersion": "812515"
},
"items": []
}
조회가 되는 것을 확인할 수 있다.
Including ServiceAccounts from other namespaces in a RoleBinding
현재 namespace bar
에 생성한 pod 내에서는 Service 목록을 조회할 수 없다. RoleBinding 을 수정해보자.
1
$ kubectl edit rolebinding test -n foo
그리고 아래와 같이 수정한다.
1
2
3
4
5
6
7
subjects:
- kind: ServiceAccount
name: default
namespace: foo
- kind: ServiceAccount
name: default
namespace: bar
그러면 이 RoleBinding 이 bar
namespace 의 default ServiceAccount 에도 적용되고, bar
안의 pod 에서 foo
namespace 의 Service 목록을 조회할 수 있다. 다만, 여전히 bar
namespace 의 Service 목록은 조회가 불가능하다.
12.2.4 Using ClusterRoles and ClusterRoleBindings
ClusterRole 과 ClusterRoleBinding 을 사용하면 클러스터 레벨의 리소스에 대한 권한을 관리할 수 있게 된다.
Role 의 경우 Role 이 존재하고 있는 namespace 안의 리소스에 대한 권한만 부여할 수 있다. (위 예시에서 bar
namespace 의 pod 가 foo
namespace 의 Service 를 조회했지만, 이는 부여된 Role 이 foo
namespace 에 속해 있기 때문) 그래서 만약 여러 namespace 에 접근해야 한다면, 각 namespace 마다 Role 과 RoleBinding 을 하나씩 전부 만들어줘야 한다.
추가로 Node, PV, Namespace 리소스의 경우 애초에 namespace 가 존재하지 않기도 하고, API server 의 endpoint 중 리소스를 나타내고 있지 않은 URL (/healthz
) 도 있기 때문에 Role 만 사용해서는 위와 같은 정보를 API server 에 요청할 수 없게 된다.
ClusterRole 은 클러스터 레벨의 리소스로, namespace 가 없는 리소스나 리소스가 아닌 URL 에 대한 접근 권한을 관리할 수 있게 해준다. 또는 여러 namespace 에서 사용할 공통적인 권한을 정할 때도 사용한다.
Allowing access to cluster-level resources
우선 ClusterRole 을 생성해 본다. Namespace 가 없음에 유의한다.
1
2
$ kubectl create clusterrole pv-reader --verb=get,list \
--resource=persistentvolumes
ClusterRole 을 bind 하기 전에 권한이 있는지 확인해보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ curl localhost:8001/api/v1/persistentvolumes
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {
},
"status": "Failure",
"message": "persistentvolumes is forbidden: User \"system:serviceaccount:foo:default\" cannot list resource \"persistentvolumes\" in API group \"\" at the cluster scope",
"reason": "Forbidden",
"details": {
"kind": "persistentvolumes"
},
"code": 403
}
요청이 실패한다. 이제 ClusterRoleBinding 을 생성한다. 마찬가지로 namespace 가 없음에 유의한다.
클러스터 레벨의 리소스에 대해 권한을 관리하려면 반드시 ClusterRoleBinding 을 이용해야 한다.
1
2
$ kubectl create clusterrolebinding pv-test --clusterrole=pv-reader \
--serviceaccount=foo:default
이제 다시 요청해 보면 성공하는 것이 확인된다.
Allowing access to non-resource URLs
리소스를 reference 하지 않는 URL 에 대한 요청 권한도 직접 허용해야 한다. 대부분의 경우 system:discovery
라는 이름의 ClusterRole/ClusterRoleBinding 이 존재하여 이를 해결해준다.
다음은 kubectl get clusterrole system:discovery -o yaml
의 output 이다.
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
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: "2021-03-17T15:34:10Z"
labels:
kubernetes.io/bootstrapping: rbac-defaults
managedFields:
- apiVersion: rbac.authorization.k8s.io/v1
fieldsType: FieldsV1
fieldsV1:
f:metadata:
f:annotations:
.: {}
f:rbac.authorization.kubernetes.io/autoupdate: {}
f:labels:
.: {}
f:kubernetes.io/bootstrapping: {}
f:rules: {}
manager: kube-apiserver
operation: Update
time: "2021-03-17T15:34:10Z"
name: system:discovery
resourceVersion: "76"
uid: a752c732-0544-455c-8c63-968ab2da0351
rules:
- nonResourceURLs: # 리소스를 참조하지 않는 URL 목록
- /api
- /api/*
- /apis
- /apis/*
- /healthz
- /livez
- /openapi
- /openapi/*
- /readyz
- /version
- /version/
verbs: # GET 만 허용된다
- get
마찬가지로 ClusterRoleBinding 도 확인해본다. kubectl get clusterrolebinding system:discovery -o yaml
의 output 이다.
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
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: "2021-03-17T15:34:10Z"
labels:
kubernetes.io/bootstrapping: rbac-defaults
managedFields:
- apiVersion: rbac.authorization.k8s.io/v1
fieldsType: FieldsV1
fieldsV1:
f:metadata:
f:annotations:
.: {}
f:rbac.authorization.kubernetes.io/autoupdate: {}
f:labels:
.: {}
f:kubernetes.io/bootstrapping: {}
f:roleRef:
f:apiGroup: {}
f:kind: {}
f:name: {}
f:subjects: {}
manager: kube-apiserver
operation: Update
time: "2021-03-17T15:34:10Z"
name: system:discovery
resourceVersion: "138"
uid: 2942e213-29be-4ecb-8439-be3167c1177b
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:discovery
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: Group
name: system:authenticated
마지막 subjects
를 보면 system:authenticated
group 에 bind 되어있는 것을 확인할 수 있다. Authenticated user 라면 ClusterRole 의 적용을 받는다.
그래서 로컬 머신에서 curl
요청을 보내면 실패하게 된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ curl https://$(minikube ip):8443/api -k
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {
},
"status": "Failure",
"message": "forbidden: User \"system:anonymous\" cannot get path \"/api\"",
"reason": "Forbidden",
"details": {
},
"code": 403
}
Using ClusterRoles to grant access to resources in specific namespaces
ClusterRole 이 반드시 ClusterRoleBinding 과 연결된 필요는 없다. Namespace 가 존재하는 RoleBinding 과도 연결될 수 있다.
view
라는 ClusterRole 을 살펴본다. kubectl get clusterrole view -o yaml
중 일부이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
rules:
- apiGroups:
- ""
resources:
- configmaps
- endpoints
- persistentvolumeclaims
- persistentvolumeclaims/status
- pods
- replicationcontrollers
- replicationcontrollers/scale
- serviceaccounts
- services
- services/status
verbs:
- get
- list
- watch
...
자세히 보면 ConfigMap, Endpoints, PVC, Pods 와 같이 namespace 된 리소스들이 적혀있다.
이러한 경우 ClusterRole 이 RoleBinding/ClusterRoleBinding 중 어느 쪽과 연결되느냐에 따라 권한이 달라진다. 만약 RoleBinding 에 연결하게 되면, RoleBinding 은 namespace 가 있으므로 RoleBinding 의 namespace 안에 있는 리소스에 권한이 부여된다. 반면 ClusterRoleBinding 을 사용하게 되면 임의의 namespace 안에 있는 리소스에 대해 권한이 부여된다.
Summary
접근 | Role Type | Binding Type |
---|---|---|
클러스터 레벨 리소스 | ClusterRole | ClusterRoleBinding |
리소스를 참조하지 않는 URL | ClusterRole | ClusterRoleBinding |
임의의 namespace 에서 namespace 가 있는 리소스 접근 | ClusterRole | ClusterRoleBinding |
특정 namespace 에서 namespace 가 있는 리소스 접근 (ClusterRole 을 재사용하는 경우) | ClusterRole | RoleBinding |
특정 namespace 에서 namespace 가 있는 리소스 접근 (각 namespace 에 Role 생성) | Role | RoleBinding |
12.2.5 Understanding default ClusterRoles and ClusterRoleBindings
Kubernetes 에는 기본적으로 설정되어 있는 ClusterRole 과 ClusterRoleBinding 이 있다. API server 가 시작될 때 매 번 업데이트 되기 때문에 실수로 삭제하는 경우 role 이 꼬이는 것을 방지하고, Kubernetes 가 업데이트 될 때 같이 업데이트 된다.
중요한 ClusterRole 몇 가지만 살펴본다.
Allowing read-only access to resources with view
ClusterRole
이 ClusterRole 은 namespace 가 존재하는 대부분의 리소스를 조회 (view) 할 수 있다. 다만 Role, RoleBinding, Secret 은 제외되는데, 이는 privilege escalation 을 막기 위함이다. 어떤 secret 는 더 많은 권한을 가지고 있을 수도 있다.
Allowing modifying resources with edit
ClusterRole
말 그대로 수정을 하게 해준다. 이 경우 Secret 도 접근하여 수정할 수 있지만, Role, RoleBinding 은 여전히 제외된다. 이 또한 privilege escalation 을 방지하기 위함이다.
Full control of a namespace with admin
ClusterRole
Namespace 에 속한 모든 리소스를 읽고 변경할 수 있다. 다만 ResourceQuota 와 Namespace 자체는 건드릴 수 없다. 앞의 edit
ClusterRole 과의 차이점은 Role, RoleBindings 를 읽고 쓸 수 있다는 점이다.
Privilege escalation 을 막기 위해서 한 사용자가 Role 을 업데이트 하는 경우, 그 사용자가 업데이트 하려는 Role 에 정의된 모든 권한을 가지고 있는지 확인한다. 당연히 권한의 범위 (namespace) 도 동일해야 한다. 가지고 있어야만 수정할 수 있다.
Complete control with cluster-admin
ClusterRole
모든 권한이 주어진다!
Other ClusterRoles
system:
으로 시작하는 ClusterRole 들은 Kubernetes 컴포넌트들이 사용한다. kube-scheduler
(Scheduler 가 사용), node
(Kubelet 이 사용), controller:
등등이 있다.
12.2.6 Granting authorization permissions wisely
- Principle of least privilege 만 기억하면 된다. 권한은 최소한으로 준다.
- 각 pod 마다 ServiceAccount 를 생성하는 것이 바람직하다.