Search
Duplicate
🏈

12장. 쿠버네티스 API 서버 보안

인증에 대한 이해 (understanding authentication)
service account 란 무엇이며 사용하는 이유
역할 기반 access 제어(RBAC) plugin 이해
using Roles and RoleBindings
클러스터롤과 클러스터롤 바인딩 사용
default role 과 바인딩 이해
8장에서 잠깐 언급했던 serviceAccount 에 대해서 주로 다루게 되며, 그 외에도 cluster 를 사용하는 다른 주제에 권한을 설정하는 방법을 학습한다

인증(authentication) 이해

11장에서 API 서버를 하나 이상의 authentication plugin 으로 구성할 수 있다고 했다.
요청을 받으면 여러 authentication plugin 을 거친다.
첫번째로 거치는 플러그인에서 요청을 보낸 사람이 누구인가를 알아내 API 서버 코어에 반환한다
사용자 이름, id, client 가 그룹
이어 API 서버는 나머지 authentication plugin 호출을 중지하고, 인가 단계를 진행한다
authentication plugin 은 다음 방법을 사용해 client 의 identity 를 얻는다
client 인증서
HTTP header 로 전달된 인증 토큰
기본 HTTP 인증
기타
authentication plugin 은 API 서버를 시작할 때 CLI 옵션을 통해 활성화 할 수 있다.

사용자와 그룹

authentication plugin(인증 플러그인) 은 사용자의 이름, 그룹을 반환한다
k8s 는 해당 정보를 어디에도 저장하지 않는다.
k8s 는 단지 사용자가 작업을 수행할 권한이 있는지만 확인한다

사용자

k8s API server 에 접속하는 두 종류의 client 를 구분한다
실제 사람(사용자)
pod (구체적으로 파드 내부에서 실행되는 application)

이 두가지 유형 모두 authentication plugin 을 사용해 인증한다.

사용자는 SSO(Single Sign on) 과 같은 외부 시스템에 의해 관리 돼야 한다
k8s 내에 사용자 계정을 나타내는 자원은 없다
즉, k8s 가 사용자를 CRUD 할 수 없다.
pod 는 serviceAccount 사용한다
이는 cluster 에 serviceAccount 라는 resource 로 생성되고 저장된다.
serviceAccount 는 pod 를 실행하는데 필수적이므로 자세히 살펴볼 것이다.

그룹

한번에 여러 사용자에게 권한을 부여하는데 사용되는 개념이다.
human 사용자와 serviceAccount 는 하나 이상의 그룹에 속할 수 있다.
인증 plugin 이 반환하는 그룹은 임의의 그룹을 나타내는 문자열이지만, built-in 그룹은 특별한 의미가 있다.
그룹의 의미
system:unauthenticated: 어떤 authentication plugin 에서도 client 를 인증할 수 없다.
system:authenticated: 성공적으로 인증된 사용자 이다.
system:serviceaccounts: 시스템의 모든 serviceAccount 를 포함한다
system:serviceaccounts:<namespace>: 특정 namespace 의 모든 serviceAccount

Introducing ServiceAccounts

8장에서 proxy 를 거치지 않고 API 서버로 직접 호출 했던것을 기억하는가?
API 서버에 작업을 수행하기 전에 그 작업을 수행하는 자신을 인증해야 했다.
이때 사용했던 방식은 secret volume 의 /var/run/secrets/kubernetes.io/serviceaccount/token 파일의 내용을 전송하여 인증했다 이 파일이 의미하는 것은 무엇일까?
모든 pod 는 pod 에서 실행 중인 application 의 identity 를 나타내는 serviceaccount 와 연계돼 있다
이 token file 은 serviceAccount 의 인증 토큰을 갖고 있다.
serviceAccount 를 인증하고, serviceAccount 의 사용자 이름을 API core server 로 전달한다
system:serviceaccount:<namespace>:<service account name> → serviceAccount 사용자 이름
API 서버는 authentication plugin 에 사용자 이름을 전달하며, authorize plugin 이 수행하고자 하는 작업의 수행 가능 여부를 결정한다
serviceAccount 는 API server 에 자신을 인증하는 방법에 지나지 않는다.

Understanding the ServiceAccount Resource

serviceAccount 는 pod, secret, config map 과 같은 resource이다
개별 namespace 로 범위가 지정된다.
각 namespace 마다 default serviceAccount 가 자동으로 생성된다
다른 resource 와 마찬가지로 serviceAccount 를 나열할 수 있다.
serviceAccount 약어는 sa 이다.
현재 namespace 에는 default 만 갖고 있다.
필요한 경우 serviceAccount 를 추가할 수 있다.
각 pod 는 딱 하나의 service 와 연계되지만, 여러 파드가 같은 serviceAccount를 사용할 수 있다.
각 파드는 pod의 namespace 에 있는 하나의 serviceAccount 와 연계된다.

serviceAccount 가 인가와 어떻게 밀접하게 연계돼 있는지 이해하기

pod menifest 에 serviceAccount 의 이름을 지정해 할당할 수 있다
명시적인 할당이 없으면 default serviceAccount 를 사용한다
pod 에 서로 다른 serviceAccount 를 할당하면 pod 가 access 할 수 있는 resource를 제어할 수 있다
API 서버 토큰을 사용해 요청을 보낸 client 를 인증한 다음 관련 serviceAccount 가 요청된 작업을 수행할 수 있는지 여부를 결정한다
인가 플러그인 중 하나는 역할 기반 access 제어 플러그인(RBAC) 이다 k8s 1.6부터는 RBAC 를 대부분의 cluster 에서 사용해야 한다

serviceAccount 생성

모든 namespace 는 고유한 default serviceAccount 가 포함돼 있지만, 필요한 경우 추가로 만들 수 있다.
어떤 경우에 만들어야 할까?
목적은 cluster 의 보안 때문이다.
cluster 의 metadata 를 읽을 필요가 없는 pod 는 cluster 에 배포된 resource 를 검색, 수정이 불가능한 제한된 serviceAccount 로 실행해야 한다
resource metadata 를 검색하는 pod는 metadata 를 읽을 수 있는 serviceAccount를 사용한다
object 를 수정해야 하는 pod 는 API object를 수정할 수 있는 serviceAccount로 실행해야 한다

serviceAccount 생성(예제)

kubectl create serviceaccount foo
YAML
kubectl describe serviceaccount
YAML
sa 로 serviceaccount 를 축약할 수 있다.
Name: foo Namespace: default Labels: <none> Annotations: <none> Image pull secrets: <none> # 이 sa를 사용하는 파드에 이 필드 값이 자동으로 추가된다 Mountable secrets: foo-token-z5qnm Tokens: foo-token-z5qnm Events: <none>
YAML
kubectl 의 describe를 통해 serviceAccount 검사하기
Mountable secrets: mount 가능한 secret 이 강제화된 경우 이 serviceAccount를 사용하는 파드만 해당 secret 을 마운트 할 수 있다
Tokens: 인증 토큰, 첫 번째 토큰이 container 에 mount 된다
kubectl describe secret foo-token-z5qnm
YAML
Name: foo-token-z5qnm Namespace: default Labels: <none> Annotations: kubernetes.io/service-account.name: foo kubernetes.io/service-account.uid: 9088f30f-3bf8-4260-9b49-d6f40e247292 Type: kubernetes.io/service-account-token Data ==== ca.crt: 1111 bytes namespace: 7 bytes token: eyJhbGciOiJSUzI1NiIsImtpZCI6ImlyNjhKMXR2UnVoY05lekEtOWZDYTRoNmJfUFNpUHpIV3NST2tkdm5OTGcifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImZvby10b2tlbi16NXFubSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJmb28iLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiI5MDg4ZjMwZi0zYmY4LTQyNjAtOWI0OS1kNmY0MGUyNDcyOTIiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6ZGVmYXVsdDpmb28ifQ.EElmvfJe83seQ9QwtKULpVAPof67YvcG25bFGQPA6XQzPXbQZQHtvpN-d5PveyrP3Ke0g8tY3MSYwW-WshEFb-rhwJVIhfeDYgoRrF84nQSpiKF8vCOLuY9tmpNt2yPf-lByuzcahDldvjbH34_4dUP_rFqwI5MMSudIsi833cnImlonT0xuPQF4gPn3BPugNwbamB6Jdse3B3a7FB6XddB--UXcLvHM1mG8Di8YF8nbtyfDKgME1qrFk5P7NxF51yLS5a8zuPc1x-qo4e5NGGmmam2i2N2IdoMKQLzW0voEiNsT4QuUkq_K-fw29vl3Az3NKjYC11PqxAey9fgMVQ
YAML
사용자 정의 token secret
serviceAccount 에 사용된 인증 토큰은 JWT 이다

serviceAccount 의 mount 가능한 secret 이해

Mountable secrets 목록이 무엇을 표시하는 걸까?
pod 는 원하는 모든 secret 을 mount 할 수 있다.
하지만 pod 는 serviceAccount 의 mountable secret 만 mount 하도록 serviceAccount 를 설정할 수 있다
이 기능을 사용하려면 kubernetes.io/enforce-mountable-secrets="true" annotation 을 포함해야 한다
이 annotation 이 포함되면 pod 는 serviceAccount 의 mount 가능한 secret 만 mount 할 수 있다

serviceAccount 의 Image pull secret 이해

Image pull 은 private repository 에서 container 이미지를 가져오는 데 필요한 자격증명을 갖고 있는 secret 이다
다음 예제는 7장에서 생성한 이미지 풀 secret 을 포함하는 serviceAccount 정의의 예제이다.
apiVersion: v1 kind: ServiceAccount metadata: name: my-service-account imagePullSecrets: - name: my-dockerhub-secret
YAML
Image pull secret 을 갖는 serviceAccount
mountable secret 과 약간 다르게 동작하는데
image pull secret 은 어떤 image pull secret 을 사용할 수 있는지를 결정하는게 아니라 모든 pod 에 특정 image pull secret 을 자동으로 추가한다
mountable secret 은 각각의 pod 가 어떤 secret 을 사용하는지 할당한다

pod 에 serviceAccount 할당

추가 serviceAccount 를 만들었다면 이를 pod 에 할당해야 한다
spec.serviceAccountName 필드에 serviceAccount 이름을 설정하면 된다
💡
단, pod 를 생성할 때 accountService 를 설정해야 한다 나중에 변경이 안된다.

사용자 정의 serviceAccount 를 사용하는 pod 생성

이전에 사라진 tutum/curl 이미지 기반의 container 와 함께 엠베서더 container 를 실행하는 pod 를 배포했다
이 파드를 이용해 API 서버의 interface 를 탐색했다. (kubectl proxy)
이제는 foo serviceAccount 를 사용하도록 pod 를 수정하자
apiVersion: v1 kind: Pod metadata: name: curl-custom-sa spec: serviceAccountName: foo # default 대신 foo 를 사용한다 containers: - name: main image: tutum/ubuntu command: ["sleep", "9999999"] - name: ambassador image: luksa/kubectl-proxy:1.6.2
YAML
이제 shell 로 붙어서 serviceAccount 내부의 token 값을 확인해보자
root@curl-custom-sa:/# ls bin dev home lib64 mnt proc run sbin srv tmp var boot etc lib media opt root run.sh set_root_pw.sh sys usr root@curl-custom-sa:/# cat /var/run/secrets/kubernetes.io/serviceaccount/token eyJhbGciOiJSUzI1NiIsImtpZCI6ImlyNjhKMXR2UnVoY05lekEtOWZDYTRoNmJfUFNpUHpIV3NST2tkdm5OTGcifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImZvby10b2tlbi16NXFubSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJmb28iLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiI5MDg4ZjMwZi0zYmY4LTQyNjAtOWI0OS1kNmY0MGUyNDcyOTIiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6ZGVmYXVsdDpmb28ifQ.EElmvfJe83seQ9QwtKULpVAPof67YvcG25bFGQPA6XQzPXbQZQHtvpN-d5PveyrP3Ke0g8tY3MSYwW-WshEFb-rhwJVIhfeDYgoRrF84nQSpiKF8vCOLuY9tmpNt2yPf-lByuzcahDldvjbH34_4dUP_rFqwI5MMSudIsi833cnImlonT0xuPQF4gPn3BPugNwbamB6Jdse3B3a7FB6XddB--UXcLvHM1mG8Di8YF8nbtyfDKgME1qrFk5P7NxF51yLS5a8zuPc1x-qo4e5NGGmmam2i2N2IdoMKQLzW0voEiNsT4QuUkq_K-fw29vl3Az3NKjYC11PqxAey9fgMVQ
YAML
token 을 봤을때 앞에서 나왔던 token 과 동일할까? jwt를 decode해보자
{ "alg": "RS256", "kid": "ir68J1tvRuhcNezA-9fCa4h6b_PSiPzHWsROkdvnNLg" }
YAML
{ "iss": "kubernetes/serviceaccount", "kubernetes.io/serviceaccount/namespace": "default", "kubernetes.io/serviceaccount/secret.name": "foo-token-z5qnm", "kubernetes.io/serviceaccount/service-account.name": "foo", "kubernetes.io/serviceaccount/service-account.uid": "9088f30f-3bf8-4260-9b49-d6f40e247292", "sub": "system:serviceaccount:default:foo" }
YAML
serviceAccount 에 사용된 인증 토큰은 JWT 이다
일치하는 것을 확인할 수 있다

API 서버와 통신하기 위해 사용자 정의 serviceAccount token 사용

이 token 을 사용해 API 서버와 통신할 수 있을까?
엠베서더 container 를 통해서 토큰을 테스트 해보자
kubectl exec -it curl-custom-sa — curl localhost:8001/api/v1/pods
YAML
이것은 사용자 정의 serviceAccount 로 pod 를 나열할 수 있다는 뜻이다
cluster 가 적절한 인가를 사용하지 않는 경우 default serviceAccount 만으로 모든 작업을 수행할 수 있으므로 굳이 추가적인 serviceAccount 를 생성하고 사용하는 것은 의미가 없다

역할 기반 access 제어로 cluster 보안

k8s 1.6.0 부터는 cluster 보안이 크게 향상 되었다.
pod 중 하나는 인증 토큰을 획득하는데 성공하면 이를 이용해 cluster에서 어떤 작업이든 수행할 수 있었다
그래서 경로 탐색 취약점을 이용해 token 을 가져와 안전하지 않은 k8s cluster 의 악성 파드를 실행하는 데모를 볼 수 있다
k8s 1.8.0 부터는 RBAC 인가 플러그인이 GA(General Avalialbility) 로 승격되어 많은 cluster 에 기본적으로 활성화돼 있다
RBAC 는 권한이 없는 사용자가 cluster 상태를 보거나 수정하지 못하게 한다

RBAC 인가 plugin 소개

k8s API 서버는 인가 plugin 을 통해 action 을 요청하는 사용자가 action 을 사용할 수 있는지 점검한다
API 서버가 REST interface 를 제공하므로 사용자는 서버에 HTTP 요청을 보내 액션을 수행한다
사용자는 자격증명을 포함하여 자신을 인증한다

액션 이해하기

액션은 GET, POST, PUT, DELETE 유형의 HTTP 요청을 특정 resource의 REST 의 endpoint 로 호출한다
k8s 에선 이러한 리소스에 pod, service, secret 등이 있다.
파드 가져오기 GET
서비스 생성하기 CREATE
시크릿 업데이트 UPDATE
기타 등등
권한 부여 동사와 HTTP 메서드 매핑
API 서버 내에서 실행되는 RBAC 와 같은 인가 플러그인은 client 가 요청한 자원에서 요청한 동사를 수행할 수 있는지 그러한 권한이 있는지 확인한다
그 권한 외에도 특정 resource instance 에도 적용할 수 있따
resource 가 아닌 URL 경로에도 권한을 적용할 수 있다
TOP