CKA 대비 kubernetes 스터디 - 6. Security (2)
API Group - prerequisite for Authorization
- version: 클러스터 버전 확인용.
- health / monitor: checking health를 위해 사용함.
- logs : 3rd party 프로덕트와 통신할 목적으로 사용함
- API - cluster functionality를 위한 API라서 좀더 상세히 다룬다.
api : Core Group. k8s 자체의 핵심 기능을 주로 담당한다. Namespace, node, Persistent Volumes 등등
apis : Named Group. more organized / newer features are available.
특정 리소스를 docs에서 선택하면, 어떤 API에서 호출할 수 있는지 확인해준다.
아니면 클러스터에 별다른 path 없이 직접 호출해보면 된다. 사용할 수 있는 api 목록을 알 수 있다.
- 클러스터에 api를 요청하려면, 앞서 언급한 cert와 key가 있어야 한다. 없을 경우 403 forbidden이 발생함.
- 인증서와 key값을 curl에 넣고 바로 전송하는 방법도 있고, kubectl proxy 명령어를 사용해 접근할 수도 있다.
- proxy를 사용할 경우, proxy에서 kubeconfig 파일을 확인해서 apiserver로 요청을 전달한다.
cf. kube proxy != kubectl proxy.
- kube-proxy : services, pods의 across different node 통신을 가능하게 해 주는 k8s component
- kubectl proxy: kube-apiserver에 접근할 목적으로 kubectl이 생성한 http proxy.
Authorization
Authenticated된 사용자가 클러스터에 들어와서 무엇을 할 것인가? 를 결정하는 영역.
- 일반적으로 admin은 모든 권한을 가지고 있어야 한다.
- 하지만 developer라면, 예컨대 조회는 가능해도 삭제는 불가능하게 만든다거나
- jenkins같은 프로덕트에서 접근할 때에는 로직을 다른 식으로 적용하고 싶을 수 있다.
Namespace별로 logical partitioning을 구현하면, 각 ns별로 접근할 수 있는 주체의 권한을 지정할 수 있다.
구체적인 Authorization 방법은 크게 네 가지.
kube-apiserver는 사용자에게도 요청을 받고, kubelet을 통해 각 노드를 관리한다.
- kubelet은 kube api에서 service / endpoints / nodes / pods 관련 정보를 읽고
- node / pod status, events를 쓸 수 있어야 한다.
Kubelet에서 필요로 하는 위 작업의 Authorization 담당은 Node Authorizer 라는 형태로 존재한다.
- kubelet으로 certificate 발급할 때 system:nodes 라는 group으로 생성했었다.
- node authorizer는 이 system:node 그룹에 적용된다. 필요한 권한을 지정해줌.
ABAC : User 또는 Group of Users에 permission을 연결하기 위해 사용함.
- policy를 적용한 policy file를 json 포맷으로 만들어서 apiserver에 pass the file.
- 새로운 사용자나 그룹이 추가될 때마다 policy file을 직접 수정해주고, apiserver를 재시작해야 함.
- 관리가 어렵기 때문에 잘 안씀
RBAC : user / userGroup을 직접 연결하는 대신 Role 형태로 binding하는 것.
- 여러 권한을 role로 묶은 뒤, 해당 role에 사용자 / 그룹을 연결한다.
- 권한의 변경이 필요한 경우 role 단위로 변경한다. role이 부여된 사용자는 변경사항이 즉시 반영된다.
- Managing Access 측면에서 좀더 표준에 가까운 형태.
Webhook : 3rd party와의 연결을 위해 사용함.
- ex) Open policy agent라는 프로덕트와 연결할 경우
- kube-apiserver로 요청이 들어왔을 때, 해당 요청을 한 사용자의 authorization을 전적으로 외부에 맡긴 뒤
- 외부 프로덕트의 응답결과를 그대로 따르는 식.
이외에도 AlwaysAllow, AlwaysDeny 옵션이 있다.
Authorization 옵션은 kube-apiserver의 authorization-mode 옵션으로 설정 가능하다.
- 디플트 옵션은 AlwaysAllow.
- 여러 개의 Authorization 옵션을 주려면 위와 같이 하면 된다.
- authorization-mode에 정의한 순서대로 authorization 로직을 타게 된다.
- 예컨대 사용자의 요청이 들어오면, node가 제일 먼저 검증한다.
- node의 로직에 걸리지 않으므로 deny -> 다음 로직으로 chaining하는 구조.
- RBAC에서 적합한 authorize 로직을 찾으면, 해당 로직을 타고 사용자에게 권한 실행을 리턴하는 구조.
- 한 모듈에서 approves the request -> 더 이상의 체크 없이 사용자는 grant the permission.
Role
Role도 하나의 k8s object이므로, yaml파일로 생성할 수 있다.
- rules 필드에는 세 개의 값이 필요하다.
- apiGroups : core group일 경우 공백으로 둬도 된다. 아니라면 그룹을 반드시 지정해야 함.
- resources : 접근 가능한
- verbs : 어떤 동작이 가능한지 정의.
- 하나의 role에는 여러 rule을 정의할 수 있다. 예컨대 configmap을 생성하도록 하려면 configmap 리소스에 해당되는 verb를 추가하는 식.
- User와 Role을 연결하기 위해 RoleBinding object를 생성한다.
- Subjects : user 관련한 정보를 입력하는 란.
- roleRef : 생성해둔 role의 정보를 입력하는 란.
role과 rolebinding은 namespace 범위의 영향을 받는다.
- 위 에시의 경우 developer 사용자는 default 네임스페이스에 한해서만 role이 적용된다.
- 특정 ns에 적용하려면 namespace를 지정하면 된다.
- 특정한 리소스에만 접근권한을 주고 싶다면, resourceName으로 해당 리소스를 지정할 수 있다.
Cluster Roles and Cluster Role Bindings
이전의 Role과 Rolebinding은 특정 namespace 내에서 이루어졌다.
- 따로 ns를 정의하지 않으면 default ns를 기준으로 role의 boundary가 지정되었음.
하지만 namespace로 나뉘지 않는 영역은?
- 예컨대 node는 namespace 범위로 한정할 수 있는 대상이 아니다. 얘는 클러스터 단위임.
- namespace 리소스: 생성하거나 삭제할 때 해당 namespace를 반드시 명시해야 함.
- cluster scope 리소스: 생성할 때 namespace를 요구하지 않음.
이 구분은 kubectl api-resources --namespaced=true
/ kubectl api-resources --namespaced=false
로 확인할 수 있다.
- role과 마찬가지로 clusterRole을 생성하면 된다.
kubectl create -f cluster-admin-role.yaml
형태로 클러스터에 생성할 수 있다.
ClusterRolebinding도 문법은 앞서 설명한 roleBinding과 비슷하다.
- subjects : 해당 role을 부여할 사용자
- roleRef : 사용자에게 적용될 role
꼭 namespace기준으로 Strict하게 구분되는 건 아니다. ClusterRole로도 namespace 기반 리소스의 role을 정할 수 있다.
- 이렇게 설정할 경우, 모든 namespace에서 적용할 수 있는 광역권한을 갖게 된다.
Service Account
k8s에는 두 종류의 Account가 있다. User / Service Account.
- service account를 생성하면, 해당 계정에서 사용할 수 있는 토큰이 발급된다.
kubectl describe serviceaccount <이름>
으로 존재여부를 확인할 수 있다.- token은 secret으로 저장됨. Authentication Bearer Token으로 사용된다.
- serviceaccount를 생성한다
- 올바른 permission을 RBAC로 할당한다 (이 부분은 시험범위 아니라고 함)
- get token -> web 3rd party application에서 사용하면 된다.
만약 3rd party 앱이 자체적으로 k8s hosting하고 있다면?
- ex) k8s에 배포되어 있는 prometheus 같은 경우
- Mounting the Servicetoken Secret inside the pod hosting 3rd party Application.
- exporting the serviceAcount token + Configuring the app to use it 과정을 간소화할 수 잇다.
- 애플리케이션이 쉽게 읽을 수 있고, cluster에도 secret이 이미 등록되어 있으므로 쉽게 사용할 수 있다.
모든 ns에는 default라는 serviceAccount가 기본적으로 생성되어 있다.
- 새로운 Pod을 생성하면, default serviceAccount + token이 volumne mount 형태로 적용되어 있다.
- 마운트된 경로로 가보면 crt, namespace, token 값이 있으며, 토큰은 k8s api를 사용하기 위한 값임.
- default serviceAccount는 basic한 api query만 가능하도록 제한이 많이 걸려있는 account.
다른 serviceAccount를 적용하려면 serviceAccountName 필드를 사용해서 정의하면 된다.
- 단, Pod의 경우 serviceAccount를 수정하면 반드시 새 pod를 생성해야 한다. Edit이 불가능.
- Deployment의 경우, account를 변경하면 자동으로 rollout이 진행된다.
- 만약 default serviceAccount 값을 설정하고 싶지 않다면
automaountServiceAccountToken:false
옵션을 spec 하위필드에 추가하면 된다.
Image Security
- library: 이 영역에 user나 회사 등 특정 계정을 입력하지 않았을 경우 자동으로 입력되는 값.
image: nginx
일 경우 자동으로 앞에image: library/nginx
형태로 인식하는 식이다.- docker의 default account로, docker official image가 저장되어 있다. official image의 경우 전담 팀이 따로 관리하고 있는 이미지를 의미함.
- docker.io: 별다른 입력을 하지 않았을 경우 디폴트로 간주되는 registry. docker의 기본 레지스트리 이기도 하다.
Private Repository를 사용할 경우... 보통 docker login으로 private registry에 로그인한 뒤 이미지를 pull할 수 있다.
- k8s에서는 이 docker login으로 credential을 확보하는 과정을 어떻게 진행하나?
- 이미지는 runtime 때 docker pull 형태로 가져오게 된다. 어떻게 credential pass를 할 수 있을까?
kubectl create secret docker-registry regcred ...
형태로 secret을 생성한다.- 이미지를 호출할 때 imagePullSecrets 옵션을 생성하고, secret값을 넣는다.
Security Context
docker에서는 container의 security 확보를 위한 여러 명령어들이 있다.
- user id 제공하기. ex)
--user=1001
- linux capability that can be added / removed from the container. ex)
--cap-add MAC_ADMIN
k8s에서도 동일한 기능이 있다. k8s는 container를 pod라는 개념으로 encapsulate했기 때문에, pod 안에 있는 여러 개의 container에 해당 옵션이 전부 적용될 수 있다. pod와 container에 다른 옵션이 적용되어 있을 경우 container will overwrite the settings on the pod.
- spec 필드에 securityContext.runAsUser 옵션으로 user id를 제공할 수 있다.
- 만약 pod가 아니라 container 단위로 security context를 걸고 싶다면, containers 내부에 정의하면 된다.
- capability 옵션은 pod 레벨에서는 적용할 수 없다.
Network Policy
Ingress / Egress 구분 기준: 트래픽의 Originate를 기준으로 결정한다.
- Web Client : 사용자의 요청이 ingress, api로 요청을 내보내는 게 egress.
- API : web client로의 요청이 ingress, DB로 요청을 내보내는 게 egress.
- DB : API로부터의 요청이 ingress.
따라서 위와 같이 rule을 구분할 수 있다.
- Web Client
- ingress rule : port 80으로 들어오는 http 트래픽 수용.
- egress rule : api 서버에 port 5000으로 요청 전송.
- API Server
- ingress rule : port 5000으로 들어오는 http 트래픽 수용.
- egress rule : DB 서버에 port 3306으로 요청 전송.
- DB
- ingress rule : port 3306으로 들어오는 트래픽 수용.
k8s에서 network의 중요한 원칙 중 하나
- 클러스터 내 모든 pod는 route같은 추가처리 없이 통신이 가능해야 한다. default Rule = All allow임.
- 예시의 경우 virtual private network -> 클러스터 전체에 적용되어 있음.
- ip주소 / pod name / services 통해서 서로에게 접근 및 통신이 가능함.
- 따라서, 위의 애플리케이션을 k8s pod로 대체할 경우 ingress / egress rule은 기본적으로 '누구와도 통신 가능' 이 된다.
- 이 형태는 보안상 좋지 않기 때문에, Network Policy라는 k8s object를 사용해서 네트워크 ingress / egress 설정을 변경할 수 있다.
- 위처럼 특정 Pod의 특정 port에서만 트래픽을 받도록 변경하면, 그 외의 나머지 요청은 전부 받지 않도록 설정한다.
- 구체적으로는 label / selector 사용해서 연결할 수 있음.
Network policy를 지원하는 / 지원하지 않는 솔루션들이 있다.
- 지원하지 않는 것들도 policy 생성은 되지만, 실제로 적용이 되지 않는 형태임.
Network policy
DB를 외부에서 아무나 접근할 수 없고, 포트 3306으로만 트래픽을 받을 수 있도록 설정하려고 한다.
- ingress.from 옵션으로 podselector에 특정 Label이 붙은 pod만 선택할 수 있다.
- 단, 디폴트 옵션으로는 동일한 label이 붙은 모든 namespace의 pod가 해당된다.
- 만약 namespace도 제한해야 한다면 namespaceSelector를 적용하면 된다.
- 예컨대 DB를 위한 백업 서버를 따로 생성해서 사용한다고 하면, 백업 서버는 같은 namespace도 아니고 pod를 사용할 수도 없다.
- 이 경우 ip주소를 토대로 ingress 옵션을 설정할 수 있다.
위 예시에서 최종적으로 rule의 개수는 총 2개로, 이 두 개의 rule은 OR 형태로 동작한다.
- podSelector + namespaceSelector 두 개를 from에 묶어서 하나
- 대신, from에 묶은 selector는 조건을 전부 충족해야만 ingress 조건을 충족한다.
- ipBlock.cidr로 ip주소에 설정한 rlue 하나.
둘 중에 하나만 조건을 충족하면 연결 가능함.
- 만약 이렇게 생성할 경우, rule의 개수는 3개.
- 각 조건을 하나라도 만족할 경우 통신이 가능하다.
- 따라서 오른쪽 그림처럼, prod Namespace에 있는 모든 pod / 다른 namespace이면서 podName이 api-pod인 경우 전부 DB와 연결할 수 있음.
Egress도 비슷하게 사용한다.
- 예컨대 DB에서 외부 백업서버로 요청을 보낸다고 하면
- policyType에 Egress 설정
- egress.to 필드에 목적지 설정. 예시의 경우 Ip주소 지정방식을 사용했다.