쿠버네티스는 User가 없다
인증 : 누구인가?
인가 : 뭘할건가?
일반적으로 '누구?'라 함은 User를 뜻함.
쿠버네티스에는 User가 없음. 사용자를 다루는 객체가 없다는 말.
DB에 ID/PW 저장해놓고 읽어오는 일반 웹서비스와 달리, 쿠버네티스 클러스터는 클라이언트의 사용자 정보를 들고 있지 않는다.
그럼 어떻게 '누구'를 인증하나요?
인증서로.
쿠버네티스가 인증서 들고 요청한 놈들은 쿠버네티스 API에 접근이 가능하며, 인증서에 있는 subject필드에 있는 값으로 사용자 이름을 결정함.
이게 뭐가 좋냐?
호환성
.쿠버네티스의 사용자 관리 방식에 종속될 필요가 없어짐.
공개키 기반 인증 방식(PKI, Public Key Infrastructure)의 국제 표준인 X.509를 사용해서 인증한다?
X.509를 준수해서 개발하면 앞에 이것저것 갖다 붙일 수가 있게 됨.ex. Teleport로 로그인 했는데 쿠버네티스를 어떻게 들어가냐? Teleport가 사용하는 let's encrypt도 X.509를 준수한다.
또 한가지는 편해서
임. 뭐가 편하냐?
결국 공개키-개인키 개념인데인증서에 온갖 내용들을 기록해놓고 private key로 암호화 해두면, public key로 복호화 할 수 있음.
근데 private key가 없으면 변조가 안됨. 안된다기보다 변조가 바로 식별된다.
인증서 전체 내용을 private key 해쉬한 해쉬값이 있는데, 개인키는 절대 유출되지 않는다 가정했을 때, 인증서 내용이 바뀌면 해쉬가 바뀌고, 해쉬가 바뀐건 다른 키니까 이건 변조된 인증서다.
기록하고 싶은 내용 다 인증서에 냅다 때려박아놓고, 변조도 식별할 수 있다? 인증서랑 키만 잘 관리하면 되겠네.
보통 공개키-개인키 알고리즘은 RSA를 사용하는데 이거 알고리즘 계산으로 풀어내려면 양자컴퓨터 개발하는게 더 빠름
운좋게 털어도 수명주기 짧게 해놓고 교체해버리면 욕나옴
그럼 어떤게 잘 들고 있는 인증서냐?
당연히 아무 인증서나 들고 와서 "내가 재벌이요" 하면 이랏샤이마세~하는게 아님.
쿠버네티스 클러스터가 Root CA에게 자신의 서버 인증서+개인키로 서명한 데이터를 전달해서 인증받음.
이러면 클라이언트는 Root CA에게 진짜 니가 준 인증서가 맞는지 물어보면 됨.
Client -> server는 이렇게 해결. 이건 일반 웹사이트 https도 똑같음.
문제는 쿠버네티스 클러스터의 경우 서버가 사용자를 신뢰할 수 있는가
임.
그래서 우리가 익숙한 서버측 인증서와 달리, 클라이언트 인증서가 필요함.
- rootCA 통해서 클라이언트 인증서 발급
- 클라이언트의 개인키로 해싱한 데이터+클라이언트 인증서를 rootCA가 들고있음
- 서버는 클라이언트가 자기 인증서랑 공개키 주면서 접근 요청하면, 그거 들고 RootCA로 달려가서 물어봄
- rootCA가 들고있는 정보랑 잘 맞다고 함
- 이랏샤이마세
한마디로 요약하자면 봐봐 나 맞지?
임.
그럼 kubeconfig는 뭐냐?
~/.kube/config 에 있는거 kubeconfig라고 부르겠음.
가령 EKS에서 아래와 같이 kubeconfig를 업데이트 하고 나면aws eks update-kubeconfig --region ap-northeast-2 --name ops-prd
이 안에는
- 클라이언트 인증서 내용이 있거나/인증서 파일 경로를 기재
- k8s api server의 인증서(TLS 통신하려고)
- 서버의 공개키로 암호화해서 데이터 보내고
- 서버는 개인키로 복호화해서 보고
- k8s api 서버 주소
- 내가 그 클러스터 안에서 어떤 Username을 가질건지
- default namespace
등등의 정보가 있다.
이거 털리면? 빤쓰 색깔까지 다 까발려지는거.
잠깐. AWS EKS config는 뭐가 더있던데? AWS Credential 없으면 요청 안 가지 않냐?
kind: Config
preferences: {}
users:
- name: arn:aws:eks:ap-northeast-1:927056181394:cluster/triple-data-dev
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
args:
- --region
- ap-northeast-1
- eks
- get-token
- --cluster-name
- triple-data-dev
- --output
- json
command: aws
env: null
interactiveMode: IfAvailable
provideClusterInfo: false
command /args는 뭐냐?
kubectl이 이 config사용할 떄 내부적으로 저 커맨드를 실행함.
EKS 클러스터에 접근할 사람인지 AWS IAM을 통해 인증한다.
그리고 aws eks get-token
으로 얻은 토큰을 Bearer Token으로서 쿠버네티스 API서버에 요청할 때 HTTP헤더에 넣어 인증함
GET /api/v1/nodes
Host: fucking-k8s-api.kyle.com
Authorization: Bearer <token>
아, 결국 얘도 API서버고 인증은 Bearer token 인 거구나.
그래서 aws eks get-token 명령을 실행하기 위해 AWS에 대한 권한이 필요한 것.
털려도 빤쓰 모양까진 안까발려지니까 다행인가?
그럼 토큰으로 인증 끝난거 아님? 클라이언트 인증서는 뭐 바꿔먹었냐?
쿠버네티스 클러스터에서 클라이언트를 인증하는 방식은 여러가지가 있음.
클라이언트 인증서도 되고, 이렇게 토큰을 헤더에 담아서 인증해도 됨.
많은 API서버들이 클라이언트를 인증할 때 사용하는 방식이고, 이 토큰을 Bearer Token 이라고 함.
k8s 뿐만 아니라 API서버에 토큰 기반 인증하는 방식이고, 토큰 안에는 사용자,권한,유효기간같은게 포함되어 있음.
당연히 토큰 노출되면 화성 가는거.
잠깐. 토큰이라 하면, Service Account도 토큰 만들어주지 않냐?
문득 궁금해졌다.
쿠버네티스 클러스터 안에서의 identity를 정의하는 service account도 토큰을 만들어낼 수 있는데, 이 토큰 있으면 SA에 부여된 특정 권한에 한해서는 k8s api를 사용할 수 있는거 아닌가?
그럼 얘도 토큰 넣어서 보내면 되는거 아님?
해봄.
curl -k -H "Authorization: Bearer sample" \
-H "Accept: application/json" \
https://sample.123.ap-northeast-2.eks.amazonaws.com/api/v1/namespaces
{
"kind": "NamespaceList",
"apiVersion": "v1",
"metadata": {
"resourceVersion": "765903"
},
"items": [
{
"metadata": {
"name": "default",
...(중략)
},
"spec": {
"finalizers": [
"kubernetes"
]
},
"status": {
"phase": "Active"
}
},
{
"metadata": {
"name": "kube-node-lease",
...(중략)
},
"spec": {
"finalizers": [
"kubernetes"
]
},
"status": {
"phase": "Active"
}
},
{
"metadata": {
"name": "kube-public",
...(중략)
},
"spec": {
"finalizers": [
"kubernetes"
]
},
"status": {
"phase": "Active"
}
},
{
"metadata": {
"name": "kube-system",
...(중략)
},
"spec": {
"finalizers": [
"kubernetes"
]
},
"status": {
"phase": "Active"
}
},
{
"metadata": {
"name": "springboot",
...(중략)
},
"spec": {
"finalizers": [
"kubernetes"
]
},
"status": {
"phase": "Active"
}
}
]
}
오 된다!
단, sa token은 단기 토큰임. 기본 만료가 1시간.
pod에 sa를 적용하면 1시간마다 API서버에 요청해서 새 토큰을 가져오고 사용함.
kubelet이 요청하고, api server는 Pod에 마운트된 경로인 /var/run/secrets/kubernetes.io/serviceaccount/token 에 토큰을 업데이트 해줌.
이외에도 OIDC이용하면 Okta같은데다가 쿠버네티스 클러스터 인증 맡기는거.
인증은 알겠고, 인가는?
이렇게 여차저차 쿠버네티스 클러스터에 신뢰할 수 있는 클라이언트로 인정받아서 들어왔다.
근데 내가 뭘 할수 있는지는 어떻게 정의되냐?
뭘 했길래 위에 http요청 날려서 네임스페이스를 조회한거냐? 조회할 권한이 어디 명시되어있냐 도대체?
Role
- 특정 네임스페이스 안에서의 권한을 정의
RoleBinding - 주체와 - Role 연결
ClusterRole - 클러스터 수준에 대한 권한을 정의
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: kyle-user-binding
subjects:
- kind: User
name: kyle
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: list-namespaces
apiGroup: rbac.authorization.k8s.io
인증서 또는 OIDC의 Subjects.username이 kyle인 경우 kyle이라는 ClusterRole을 부여
이 클러스터롤은 네임스페이스를 읽어들일 수 있음
piVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: list-namespaces
rules:
- apiGroups: [""]
resources: ["namespaces"]
verbs: ["get", "list"]
apiGroups는 왜 비어있냐?
쿠버네티스의 기본 컴포넌트들은 기본 그룹(core group)에 들어감.
암것도 안쓰면 코어그룹임.
권한이 없으면 어떻게 되나요?
어케되긴 이렇게 됨HTTP 401 Unauthorized
참고로 403은 차단한 경우 요청 거부의 의미인 Forbidden임. Unauthorized랑은 다름.
curl -k -H "Authorization: Bearer sample" \
-H "Accept: application/json" \
https://sample.123.ap-northeast-2.eks.amazonaws.com/api/v1/namespaces
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "Unauthorized",
"reason": "Unauthorized",
"code": 401
}%
클라이언트-서버간은 알겠고, 컴포넌트 끼리는?
여기도 인증서로 함.
예를들어 노드와 api server간에도 kubelet이 통신을 담당하지만 통신을 TLS로 함.
kubelet은 서버/클라이언트 입장이 다 됨.
각 노드에서 api 서버와 통신하는 경우 kubelet은 클라이언트 입장이 되고, 클라이언트 인증서를 필요로 함.
근데 kubelet은 api 서버이기도 함. 노드 내에서 각 pod에 대한 통제는 kubelet을 통하니까.
따라서 자신이 서버 입장일 때 사용할 서버 인증서도 들고있음.
예를들면 이런 요청.
curl -k -H "Authorization: Bearer <token>" https://<node-ip>:10250/pods
쿠버네티스 안에서는 아니지만 ingress TLS termination
덤으로.
팀원이 궁금해 하던거.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: example-ingress
namespace: default
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
tls:
- hosts:
- "example.com"
secretName: example-tls-secret
rules:
- host: "example.com"
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: example-service
port:
number: 80