이번 포스팅은 EKS Secutity에 대한 내용이다. 인증/인가와 IRSA 2가지 주제에 대해 다뤄보도록 하겠다.
항상 헷갈리는 영단어인 Authentication과 Authorizaiton이다. 먼저 인증을 통해 신원확인을 하고, 인가를 통해서 역할을 확인해서 허용된 범위 만큼의 행동을 허가해주는 개념이다.
포스팅에서는 EKS에서 인증/인가는 어떻게 진행되는 지 알아보고, 신규입사자가 왔을 때를 가정해서 인증/인가를 실습해보겠다.
먼저 환경세팅은 가시다님이 제공해주신 원클릭배포를 참조하자.
curl -O https://s3.ap-northeast-2.amazonaws.com/cloudformation.cloudneta.net/K8S/eks-oneclick5.yaml
CF만 위에걸로 바꿔서 환경세팅을 하면 된다.
추가로 인증관련된 플러그인을 설치해주면 실습 때 편하게 관련정보 조회가 가능하다.
(설치를 하면 뭔가 보안 경고가 뜨긴한다.)
kubectl krew install access-matrix rbac-tool rbac-view rolesum
그럼 아래 그림을 보면서 EKS 인증/인가의 방법에 대해 알아보자.
간략히 정리하면,
- 먼저, 사용자가 kubectl 커맨드를 날리면 사용자PC의 kubeconfig에 세팅된 내용으로 token을 AWS STS를 통해 발급 받는다.
- 토큰을 EKS API서버로 보내면 다시 aws-iam-autheticator-server를 통해서 토큰에 맵핑되어 있는 UserID, User, Role에 대한 ARN을 반환받는다.
- aws-auth configMap에서 User, Group을 EKS API서버로 전달하고, EKS에서는 RBAC을 통해서 인가를한다.
그럼 실제 실습에서 어떻게 되는 지 확인해보자.
aws sts get-caller-identity --query Arn
"arn:aws:iam::<자신의 Account ID>:user/admin"
# kubeconfig 정보 확인
cat ~/.kube/config | yh
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: LS0tLSasdasdasdasUZJQ0FURS0tLSsadasd0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFsadasadaQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
server: https://2sadasdasdasasdasdas25A95.gr7.ap-northeast-2.eks.amazonaws.com
name: myeks.ap-northeast-2.eksctl.io
contexts:
- context:
cluster: myeks.ap-northeast-2.eksctl.io
namespace: default
user: pkos-admin@myeks.ap-northeast-2.eksctl.io
name: kimchigood
current-context: kimchigood
kind: Config
preferences: {}
users:
- name: pkos-admin@myeks.ap-northeast-2.eksctl.io
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
args:
- eks
- get-token
- --output
- json
- --cluster-name
- myeks
- --region
- ap-northeast-2
command: aws
env:
- name: AWS_STS_REGIONAL_ENDPOINTS
value: regional
interactiveMode: IfAvailable
provideClusterInfo: false
args 의 커맨드가 토큰을 발급하는 내용이다.
aws eks get-token --cluster-name $CLUSTER_NAME | jq -r '.status.token'
토큰 값을 https://jwt.io/ 에서 HS384 -> HS256 순으로 바꿔보면, Payload 항목에서 디코딩 정보를 확인할 수 있다.
위의 Payload 값을 https://url-decode.com/ 에서 변환해보자.
호출 URL, Action, Version, Credential, Expires 등의 정보를 확인할 수 있다.
EKS API가 Token Review를 Webhook token authenticator에 요청을 한다.(STS getCallerIdentity 호출)
https://ap-northeast-2.console.aws.amazon.com/cloudtrail/home?region=ap-northeast-2#/events?EventName=GetCallerIdentity
(kimchigood:default) [root@myeks-bastion ~]# kubectl api-resources | grep authentication
tokenreviews authentication.k8s.io/v1 false TokenReview
STS를 통해서, 명령을 날린 유저가 IAM 유저임이 확인되면, User/Role에 대한 ARN을 반환해준다.
이제 k8s RBAC을 통해 인가(Authorization)을 하게된다.
1.aws-auth configMap에서 mapping 정보 확인
2. aws-auth configMap에서 IAM 사용자, 역할 ARN, k8s 오브젝트로 권한 확인 후 최 종 동작 실행
3. EKS를 생성한 IAM principal은 aws-auth 와 상관없이 kubernetes-admin Username으로 system:masters 그룹에 권한을 가짐
(kimchigood:default) [root@myeks-bastion ~]# kubectl get cm -n kube-system aws-auth -o yaml | kubectl neat | yh
apiVersion: v1
data:
mapRoles: |
- groups:
- system:bootstrappers
- system:nodes
rolearn: arn:aws:iam::033359017116:role/eksctl-myeks-nodegroup-ng1-NodeInstanceRole-K52WBJDFORCH
username: system:node:{{EC2PrivateDNSName}}
kind: ConfigMap
metadata:
name: aws-auth
namespace: kube-system
When you create an Amazon EKS cluster, the IAM principal that creates the cluster is automatically granted system:masters permissions in the cluster's role-based access control (RBAC) configuration in the Amazon EKS control plane. This principal doesn't appear in any visible configuration, so make sure to keep track of which principal originally created the cluster.
https://docs.aws.amazon.com/eks/latest/userguide/add-user-role.html
설명대로 최초로 EKS를 생성한 Principal이고 system:masetes 그룹을 부여 받는다. 다만 aws-auth configMap에서는 보이지 않는다.
system:masters, system:authenticated 그룹의 정보와 권한을 확인해보자.
# system:masters , system:authenticated 그룹의 정보 확인
(kimchigood:default) [root@myeks-bastion ~]# kubectl rbac-tool lookup system:masters
W0601 22:16:56.672653 5770 warnings.go:67] policy/v1beta1 PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+
SUBJECT | SUBJECT TYPE | SCOPE | NAMESPACE | ROLE
+----------------+--------------+-------------+-----------+---------------+
system:masters | Group | ClusterRole | | cluster-admin
(kimchigood:default) [root@myeks-bastion ~]# kubectl rbac-tool lookup system:authenticated
W0601 22:16:57.740343 5823 warnings.go:67] policy/v1beta1 PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+
SUBJECT | SUBJECT TYPE | SCOPE | NAMESPACE | ROLE
+----------------------+--------------+-------------+-----------+----------------------------------+
system:authenticated | Group | ClusterRole | | system:discovery
system:authenticated | Group | ClusterRole | | eks:podsecuritypolicy:privileged
system:authenticated | Group | ClusterRole | | system:public-info-viewer
system:authenticated | Group | ClusterRole | | system:basic-user
(kimchigood:default) [root@myeks-bastion ~]# kubectl rolesum -k Group system:masters
Group: system:masters
Policies:
• [CRB] */cluster-admin ⟶ [CR] */cluster-admin
Resource Name Exclude Verbs G L W C U P D DC
*.* [*] [-] [-] ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔
(kimchigood:default) [root@myeks-bastion ~]# kubectl rolesum -k Group system:authenticated
W0601 22:16:59.623340 5929 warnings.go:70] policy/v1beta1 PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+
Group: system:authenticated
Policies:
• [CRB] */eks:podsecuritypolicy:authenticated ⟶ [CR] */eks:podsecuritypolicy:privileged
Name PRIV RO-RootFS Volumes Caps SELinux RunAsUser FSgroup SUPgroup
eks.privileged True False [*] [*] RunAsAny RunAsAny RunAsAny RunAsAny
• [CRB] */system:basic-user ⟶ [CR] */system:basic-user
Resource Name Exclude Verbs G L W C U P D DC
selfsubjectaccessreviews.authorization.k8s.io [*] [-] [-] ✖ ✖ ✖ ✔ ✖ ✖ ✖ ✖
selfsubjectrulesreviews.authorization.k8s.io [*] [-] [-] ✖ ✖ ✖ ✔ ✖ ✖ ✖ ✖
• [CRB] */system:discovery ⟶ [CR] */system:discovery
• [CRB] */system:public-info-viewer ⟶ [CR] */system:public-info-viewer
cluster-admin 롤을 가지고 있으니, 모든 조회가 가능하다.이렇게 해서 사용자가 kubectl로 인증/인가를 받게 되는 것이다.
만약 신규 멤버가 입사해서 k8s에 권한을 주는 시나리오를 가정해보자.
# testuser 사용자 생성
aws iam create-user --user-name testuser
(kimchigood:default) [root@myeks-bastion ~]# aws iam create-user --user-name testuser
{
"User": {
"Path": "/",
"UserName": "testuser",
"UserId": "sadadasdasdasdasd",
"Arn": "arn:aws:iam::033359017116:user/testuser",
"CreateDate": "2023-06-01T13:21:28+00:00"
}
}
(kimchigood:default) [root@myeks-bastion ~]#
(kimchigood:default) [root@myeks-bastion ~]# aws iam create-access-key --user-name testuser
{
"AccessKey": {
"UserName": "testuser",
"AccessKeyId": "asdasdasdasdsad",
"Status": "Active",
"SecretAccessKey": "sadasdasdasdasdasddasdasdasd",
"CreateDate": "2023-06-01T13:21:34+00:00"
}
}
(kimchigood:default) [root@myeks-bastion ~]# aws iam attach-user-policy --policy-arn arn:aws:iam::aws:policy/AdministratorAccess --user-name testuser
포스팅 처음 내용의 CF로 만들었다면 Bastion서버가 2개이다. 나머지 Bastion 서버 IP 확인 후 접속해보자
# IP확인
aws ec2 describe-instances --query "Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,PrivateIPAdd:PrivateIpAddress,InstanceName:Tags[?Key=='Name']|[0].Value,Status:State.Name}" --filters Name=instance-state-name,Values=running --output table
위에서 세팅한 AccessKeyId와 SecretAccessKey 입력 후 ap-northeast-2 (서울) 리전을 세팅해준다.
# testuser 자격증명 설정
aws configure
[root@myeks-bastion-2 ~]# aws sts get-caller-identity --query Arn
"arn:aws:iam::033359017116:user/testuser"
지금은 bastion2에서 어떤 명령도 안먹힌다. testuser의 권한이 없기 때문이다.
그럼 다시 원래 bastion에서 몇가지 세팅을 해주다.
(kimchigood:default) [root@myeks-bastion ~]# eksctl create iamidentitymapping --cluster $CLUSTER_NAME --username testuser --group system:masters --arn arn:aws:iam::$ACCOUNT_ID:user/testuser
(kimchigood:default) [root@myeks-bastion ~]# kubectl get cm -n kube-system aws-auth -o yaml | kubectl neat | yh
apiVersion: v1
data:
mapRoles: |
- groups:
- system:bootstrappers
- system:nodes
rolearn: arn:aws:iam::0777777777666:role/eksctl-myeks-nodegroup-ng1-NodeInstanceRole-K52WBJDFORCH
username: system:node:{{EC2PrivateDNSName}}
mapUsers: |
- groups:
- system:masters
userarn: arn:aws:iam::077777777666:user/testuser
username: testuser
kind: ConfigMap
metadata:
name: aws-auth
namespace: kube-system
# iamidentitymapping 확인
(kimchigood:default) [root@myeks-bastion ~]# eksctl get iamidentitymapping --cluster $CLUSTER_NAME
ARN USERNAME GROUPS ACCOUNT
arn:aws:iam::777777777666:role/eksctl-myeks-nodegroup-ng1-NodeInstanceRole-K52WBJDFORCH system:node:{{EC2PrivateDNSName}} system:bootstrappers,system:nodes
arn:aws:iam::777777777666:user/testuser testuser system:masters
이제 testuser도 system:masters 그룹에 속해졌다. cluster-admin 권한을 받은 것이다. 다시 bastion-2로 돌아와서,
# testuser kubeconfig 생성 >> aws eks update-kubeconfig 실행이 가능한 이유는?, 3번 설정 후 약간의 적용 시간 필요
aws eks update-kubeconfig --name $CLUSTER_NAME --user-alias testuser
# 첫번째 bastic ec2의 config와 비교해보자
cat ~/.kube/config | yh
# kubectl 사용 확인
kubectl ns default
kubectl get node -v6
몇가지 작업을 해주면 cluster-admin 롤로 kubectl 명령을 날릴 수 있다.
(testuser:N/A) [root@myeks-bastion-2 ~]# kubectl get cm -n kube-system aws-auth -o yaml | kubectl neat | yh
apiVersion: v1
data:
mapRoles: |
- groups:
- system:bootstrappers
- system:nodes
rolearn: arn:aws:iam::033359017116:role/eksctl-myeks-nodegroup-ng1-NodeInstanceRole-K52WBJDFORCH
username: system:node:{{EC2PrivateDNSName}}
mapUsers: |
- groups:
- system:masters
userarn: arn:aws:iam::033359017116:user/testuser
username: testuser
kind: ConfigMap
metadata:
name: aws-auth
namespace: kube-system
aws-auth configMap을 확인해보면, 아까와 다르게 mapUsers가 들어와있다.
여기서 바로 그룹을 바꿔보자
kubectl edit cm -n kube-system aws-auth
system:masters --> system:authenticated
(testuser:N/A) [root@myeks-bastion-2 ~]# k get nodes
Error from server (Forbidden): nodes is forbidden: User "testuser" cannot list resource "nodes" in API group "" at the cluster scope
cluster-admin 권한이 먹히지 않는다!
testuser IAM User에 kubernetes 모든 리소스에 대한 읽기 전용 권한(RBAC) 부여 후 kubectl로 확인 해보기
먼저 clusterrole과 clusterrolebinding 을 생성해주고 적용한다.
# clusterrolebinding
(kimchigood:default) [root@myeks-bastion ~]# cat rolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
# This cluster role binding allows anyone in the "manager" group to read secrets in any namespace.
kind: ClusterRoleBinding
metadata:
name: reader
subjects:
- kind: Group
name: readonly # Name is case sensitive
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: reader
apiGroup: rbac.authorization.k8s.io
# readonly 권한을 가진 clusterrole
(kimchigood:default) [root@myeks-bastion ~]# cat readonlyrole.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
# "namespace" omitted since ClusterRoles are not namespaced
name: reader
rules:
- apiGroups: ["*"]
#
# at the HTTP level, the name of the resource for accessing Secret
# objects is "secrets"
resources: ["*"]
verbs: ["get", "watch", "list"]
readonly 권한을 가진 clusterrole을 생성한 후, clusterrolebinding에서 readony 라는 그룹을 만들고, readonly role과 맵핑 시킨다.
맵핑확인
(kimchigood:default) [root@myeks-bastion ~]# kubectl rbac-tool lookup readonly
W0601 23:01:49.435080 9552 warnings.go:67] policy/v1beta1 PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+
SUBJECT | SUBJECT TYPE | SCOPE | NAMESPACE | ROLE
+----------+--------------+-------------+-----------+--------+
readonly | Group | ClusterRole | | reader
다시 이제 testuser 에 권한 부여를 해준다.
kubectl edit cm -n kube-system aws-auth
apiVersion: v1
data:
mapRoles: |
- groups:
- system:bootstrappers
- system:nodes
rolearn: arn:aws:iam::033359017116:role/eksctl-myeks-nodegroup-ng1-NodeInstanceRole-K52WBJDFORCH
username: system:node:{{EC2PrivateDNSName}}
mapUsers: |
- groups:
- readonly
userarn: arn:aws:iam::0777777777666:user/testuser
username: testuser
kind: ConfigMap
metadata:
creationTimestamp: "2023-06-01T11:48:08Z"
name: aws-auth
namespace: kube-system
resourceVersion: "35686"
uid: 685ad877-91bc-4e50-8ecf-57a23dbe7a8f
groups를 readonly로 바꿔주면 끝!
(testuser:N/A) [root@myeks-bastion-2 ~]# k get po
No resources found in default namespace.
(testuser:N/A) [root@myeks-bastion-2 ~]# k get ing
No resources found in default namespace.
(testuser:N/A) [root@myeks-bastion-2 ~]# k get all
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.100.0.1 <none> 443/TCP 147m
이번 포스팅에서는 인증/인가에 대해 알아보았다. 평소에 role, rolebinding 정도 적용해서, RBAC 을 사용하긴 했었는데, 이렇게 디테일하게 kubectl이 어떻게 인증/인가를 거치는지 알아보지는 못했다. 특히 mutatingwebhookconfigurations, validatingwebhookconfigurations 이 웹훅은 admission controller 관련된 내용이었던 것 같은데, cert-manager 트러블 슈팅할 때 애 좀 먹었던 녀석들이다.
이번 스터디도도 깊고 깊은 Kubernetes 해저체험을 하고온 느낌이다.