이번엔 k8s와 EKS를 떠나서 인프라를 설계하고 구축하는데 있어 가장 중요하다고 볼 수 있는 보안에 대해 학습할 예정이다.
다른 사람들한테 설명할 때도 아리까리하지만 내가 이해하기에도 아리까리한 인증/인가에 대한 내용부터 k8s와 EKS에서 인증/인가를 어떻게 작동시키는지에 대해서 다룰 예정이다.
0. 환경 구성
이번 환경 구성은 지난 번과 마찬가지로 특별하게 변경하는 내용은 없다.
제공되는 yaml 파일로 배포를 진행하며 External DNS만 Cross-Account External DNS 환경으로 별도 수정 구성하였다.
이후 프로메테우스, 그라파나와 metric-server 설치를 진행하였다. 그라파나 대쉬보드는 15757, 17900, 15172 3개를 사용하였다.
# 사용 리전의 인증서 ARN 확인
CERT_ARN=`aws acm list-certificates --query 'CertificateSummaryList[].CertificateArn[]' --output text`
echo $CERT_ARN
# repo 추가
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
# 파라미터 파일 생성
cat <<EOT > monitor-values.yaml
prometheus:
prometheusSpec:
podMonitorSelectorNilUsesHelmValues: false
serviceMonitorSelectorNilUsesHelmValues: false
retention: 5d
retentionSize: "10GiB"
ingress:
enabled: true
ingressClassName: alb
hosts:
- prometheus.$MyDomain
paths:
- /*
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}, {"HTTP":80}]'
alb.ingress.kubernetes.io/certificate-arn: $CERT_ARN
alb.ingress.kubernetes.io/success-codes: 200-399
alb.ingress.kubernetes.io/load-balancer-name: myeks-ingress-alb
alb.ingress.kubernetes.io/group.name: study
alb.ingress.kubernetes.io/ssl-redirect: '443'
grafana:
defaultDashboardsTimezone: Asia/Seoul
adminPassword: prom-operator
ingress:
enabled: true
ingressClassName: alb
hosts:
- grafana.$MyDomain
paths:
- /*
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}, {"HTTP":80}]'
alb.ingress.kubernetes.io/certificate-arn: $CERT_ARN
alb.ingress.kubernetes.io/success-codes: 200-399
alb.ingress.kubernetes.io/load-balancer-name: myeks-ingress-alb
alb.ingress.kubernetes.io/group.name: study
alb.ingress.kubernetes.io/ssl-redirect: '443'
defaultRules:
create: false
kubeControllerManager:
enabled: false
kubeEtcd:
enabled: false
kubeScheduler:
enabled: false
alertmanager:
enabled: false
EOT
# 배포
kubectl create ns monitoring
helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack --version 45.27.2 \
--set prometheus.prometheusSpec.scrapeInterval='15s' --set prometheus.prometheusSpec.evaluationInterval='15s' \
-f monitor-values.yaml --namespace monitoring
# Metrics-server 배포
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
1. K8S 인증/인가
k8s에서는 인증/인가를 위해 다양한 메커니즘과 도구를 제공하는데 예를 들어 인증 정보를 저장하고 관리하는 kubeconfig가 있다. kubeconfig는 클러스터와 상호작용하는 사용자나 애플리케이션에게 필요한 인증 정보를 제공한다. 또한, 인가를 보면 k8s는 RBAC을 구성하고 관리하기 위한 자체적인 API을 제공하며, 관리자는 이를 사용하여 인가 규칙을 설정하고 조정할 수 있다.
k8s에서의 API 서버 접근 과정은 인증->인가->Admission Control과 같은 과정으로 이루어지는데 아래 그림을 참고하면 된다.
k8s에서의 인증은 사용자나 애플리케이션이 자신의 신원을 증명하는 과정으로 k8s에서 인증은 클러스터에 접근하려는 개체가 실제로 그들이 주장하는 사용자 또는 시스템이 맞는지 확인하는 프로세스를 의미한다.
인증은 주로 사용자 이름과 비밀번호, 클라이언트 인증서, 토큰 등을 사용하여 이루어진다. k8s는 다양한 인증 메커니즘을 지원하며, 각각의 메커니즘에 따라 다른 인증 프로세스를 수행한다.
인가는 인증된 개체가 특정 작업 또는 자원에 접근할 수 있는지 여부를 결정하는 프로세스이다. 인가는 인증된 개체의 권한과 역할을 기반으로 이루어진다. k8s에서는 인가 규칙을 정의하여 사용자 또는 그룹에 대한 접근 권한을 제어한다. 인가 규칙은 일반적으로 Role-based Control(RBAC)모델을 사용하여 정의된다.
RBAC을 통해 개발아, 운영자 그리고 시스템 관리자 등 다양한 역할을 정의하고 이 역할에 기반하여 특정 작업을 수행할 수 있는 권한을 부여할 수 있습니다.
위의 내용에 대해 실습을 진행해본다. 실습은 Namespace, ServiceAccount을 생성하고 확인하는 내용으로 진행된다.
ServiceAccount는 각기 다른 권한을 갖는데 이는 각기 다른 Namespace에서 동작하는 것으로 표현된다.
Pod을 기동하게 될 경우 Pod에 ServiceAccount가 할당되며 해당 ServiceAccount 기반 인증/인가를 진행하게 된다.
* 과거 1.23 이전 버전의 경우에는 Service Account에 자동 생성 된 Secret에 저장 된 Token으로 k8s API에 대한 인증 정보를 사용할 수 있었다.
# 네임스페이스(Namespace, NS) 생성 및 확인
kubectl create namespace dev-team
kubectl create ns infra-team
# 네임스페이스 확인
kubectl get ns
NAME STATUS AGE
default Active 25h
dev-team Active 6s
infra-team Active 4s
kube-node-lease Active 25h
kube-public Active 25h
kube-system Active 25h
monitoring Active 11h
# 네임스페이스에 각각 서비스 어카운트 생성 : serviceaccounts 약자(=sa)
kubectl create sa dev-k8s -n dev-team
kubectl create sa infra-k8s -n infra-team
# 서비스 어카운트 정보 확인
kubectl get sa -n dev-team
kubectl get sa dev-k8s -n dev-team -o yaml | yh
apiVersion: v1
kind: ServiceAccount
metadata:
creationTimestamp: "2023-06-01T13:01:52Z"
name: dev-k8s
namespace: dev-team
resourceVersion: "336481"
uid: 3004fea5-b
kubectl get sa -n infra-team
kubectl get sa infra-k8s -n infra-team -o yaml | yh
apiVersion: v1
kind: ServiceAccount
metadata:
creationTimestamp: "2023-06-01T13:01:52Z"
name: infra-k8s
namespace: infra-team
resourceVersion: "336483"
uid: 1bb57fc
Pod을 생성할 때 ServiceAccount을 지정하여 생성하고 권한이 제대로 부여됐는지 테스트를 진행해봤다.
dev-team, infra-team Namespace에 각각 dev-k8s, infra-k8s ServiceAccount을 지정한 Pod을 생성하고 권한 테스트를 진행하였는데 권한을 부여하지 않았기 때문에 권한 오류가 발생하였다.
# 각각 네임스피이스에 kubectl 파드 생성 - 컨테이너이미지
# docker run --rm --name kubectl -v /path/to/your/kube/config:/.kube/config bitnami/kubectl:latest
cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
name: dev-kubectl
namespace: dev-team
spec:
serviceAccountName: dev-k8s
containers:
- name: kubectl-pod
image: bitnami/kubectl:1.24.10
command: ["tail"]
args: ["-f", "/dev/null"]
terminationGracePeriodSeconds: 0
EOF
cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
name: infra-kubectl
namespace: infra-team
spec:
serviceAccountName: infra-k8s
containers:
- name: kubectl-pod
image: bitnami/kubectl:1.24.10
command: ["tail"]
args: ["-f", "/dev/null"]
terminationGracePeriodSeconds: 0
EOF
# 확인
kubectl get pod -A
kubectl get pod -o dev-kubectl -n dev-team -o yaml
serviceAccount: dev-k8s
...
kubectl get pod -o infra-kubectl -n infra-team -o yaml
serviceAccount: infra-k8s
...
# 파드에 기본 적용되는 서비스 어카운트(토큰) 정보 확인
kubectl exec -it dev-kubectl -n dev-team -- ls /run/secrets/kubernetes.io/serviceaccount
ca.crt namespace token
kubectl exec -it dev-kubectl -n dev-team -- cat /run/secrets/kubernetes.io/serviceaccount/token
eyJhbGciOiJSUzI1NiIsImtpZCI6IjlmNGNmYWVjMTA...
kubectl exec -it dev-kubectl -n dev-team -- cat /run/secrets/kubernetes.io/serviceaccount/namespace
dev-team
kubectl exec -it dev-kubectl -n dev-team -- cat /run/secrets/kubernetes.io/serviceaccount/ca.crt
-----BEGIN CERTIFICATE-----
MIIC/j...
# 각각 파드로 Shell 접속하여 정보 확인 : 단축 명령어(alias) 사용
alias k1='kubectl exec -it dev-kubectl -n dev-team -- kubectl'
alias k2='kubectl exec -it infra-kubectl -n infra-team -- kubectl'
# 권한 테스트
k1 get pods # kubectl exec -it dev-kubectl -n dev-team -- kubectl get pods 와 동일한 실행 명령이다!
Error from server (Forbidden): pods is forbidden: User "system:serviceaccount:dev-team:dev-k8s" cannot list resource "pods" in API group "" in the namespace "dev-team"
command terminated with exit code 1
k1 run nginx --image nginx:1.20-alpine
Error from server (Forbidden): pods is forbidden: User "system:serviceaccount:dev-team:dev-k8s" cannot create resource "pods" in API group "" in the namespace "dev-team"
command terminated with exit code 1
k1 get pods -n kube-system
Error from server (Forbidden): pods is forbidden: User "system:serviceaccount:dev-team:dev-k8s" cannot list resource "pods" in API group "" in the namespace "kube-system"
command terminated with exit code 1
k2 get pods # kubectl exec -it infra-kubectl -n infra-team -- kubectl get pods 와 동일한 실행 명령이다!
k2 run nginx --image nginx:1.20-alpine
k2 get pods -n kube-system
# (옵션) kubectl auth can-i 로 kubectl 실행 사용자가 특정 권한을 가졌는지 확인
k1 auth can-i get pods
no
command terminated with exit code 1
위에서 진행한 내용에서 k1 get pods 등의 명령을 수행하기 위해서 Role Binding을 진행해보려고 한다.
모든 리소스에 대해 모든 권한을 포함한 Role을 생성하고 해당 Role을 ServiceAccount와 연동(Binding)한다.
# 각각 네임스페이스내의 모든 권한에 대한 롤 생성
cat <<EOF | kubectl create -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: role-dev-team
namespace: dev-team
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["*"]
EOF
cat <<EOF | kubectl create -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: role-infra-team
namespace: infra-team
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["*"]
EOF
# 롤 확인
kubectl get roles -n dev-team
NAME CREATED AT
role-dev-team 2023-06-01T13:38:54Z
kubectl get roles -n infra-team
NAME CREATED AT
role-infra-team 2023-06-01T13:38:56Z
kubectl get roles -n dev-team -o yaml
rules:
- apiGroups:
- '*'
resources:
- '*'
verbs:
- '*'
kubectl describe roles role-dev-team -n dev-team
...
PolicyRule:
Resources Non-Resource URLs Resource Names Verbs
--------- ----------------- -------------- -----
*.* [] [] [*]
# 롤바인딩 생성 : '서비스어카운트 <-> 롤' 간 서로 연동
cat <<EOF | kubectl create -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: roleB-dev-team
namespace: dev-team
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: role-dev-team
subjects:
- kind: ServiceAccount
name: dev-k8s
namespace: dev-team
EOF
cat <<EOF | kubectl create -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: roleB-infra-team
namespace: infra-team
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: role-infra-team
subjects:
- kind: ServiceAccount
name: infra-k8s
namespace: infra-team
EOF
# 롤바인딩 확인
kubectl get rolebindings -n dev-team
NAME ROLE AGE
roleB-dev-team Role/role-dev-team 9s
kubectl get rolebindings -n infra-team
NAME ROLE AGE
roleB-infra-team Role/role-infra-team 8s
kubectl get rolebindings -n dev-team -o yaml
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: role-dev-team
kubectl describe rolebindings roleB-dev-team -n dev-team
...
Role:
Kind: Role
Name: role-dev-team
Subjects:
Kind Name Namespace
---- ---- ---------
ServiceAccount dev-k8s dev-team
Role 생성 및 RoleBinding을 마쳤다면 위에서 실패한 권한 테스트를 마저 다시 진행해본다.
해당 Namespace에서 작동하는 get, create, delete 등은 정상적으로 작동했다. 하지만 kube-system에 대한 get 명령어나 get node 등과 같이 Namespace을 벗어난 다른 Namespace 혹은 클러스터 정보 등에 대한 내용은 조회할 수 없었다. 이는 해당 ServiceAccount는 해당 Namespace에 대한 Role만 보유하기 때문이다.
# 각각 파드로 Shell 접속하여 정보 확인 : 단축 명령어(alias) 사용
alias k1='kubectl exec -it dev-kubectl -n dev-team -- kubectl'
alias k2='kubectl exec -it infra-kubectl -n infra-team -- kubectl'
# 권한 테스트
k1 get pods
NAME READY STATUS RESTARTS AGE
dev-kubectl 1/1 Running 0 15m
k1 run nginx --image nginx:1.20-alpine
pod/nginx created
k1 get pods
NAME READY STATUS RESTARTS AGE
dev-kubectl 1/1 Running 0 15m
nginx 1/1 Running 0 12s
k1 delete pods nginx
pod "nginx" deleted
k1 get pods -n kube-system
rror from server (Forbidden): pods is forbidden: User "system:serviceaccount:dev-team:dev-k8s" cannot list resource "pods" in API group "" in the namespace "kube-system"
command terminated with exit code 1
k1 get nodes
Error from server (Forbidden): nodes is forbidden: User "system:serviceaccount:dev-team:dev-k8s" cannot list resource "nodes" in API group "" at the cluster scope
command terminated with exit code 1
k2 get pods
NAME READY STATUS RESTARTS AGE
infra-kubectl 1/1 Running 0 16m
k2 run nginx --image nginx:1.20-alpine
pod/nginx created
k2 get pods
NAME READY STATUS RESTARTS AGE
infra-kubectl 1/1 Running 0 16m
nginx 1/1 Running 0 5s
k2 delete pods nginx
pod "nginx" deleted
k2 get pods -n kube-system
Error from server (Forbidden): pods is forbidden: User "system:serviceaccount:infra-team:infra-k8s" cannot list resource "pods" in API group "" in the namespace "kube-system"
command terminated with exit code 1
k2 get nodes
Error from server (Forbidden): nodes is forbidden: User "system:serviceaccount:infra-team:infra-k8s" cannot list resource "nodes" in API group "" at the cluster scope
command terminated with exit code 1
# (옵션) kubectl auth can-i 로 kubectl 실행 사용자가 특정 권한을 가졌는지 확인
k1 auth can-i get pods
yes
2. EKS 인증/인가
앞서 k8s에서의 인증/인가에 다뤘다면 이번에는 EKS에서의 인증/인가를 다룬다. EKS에서의 인증은 AWS IAM을 사용하고 인가는 k8s RBAC을 통해 진행한다.
RBAC 관련 KREW 플러그인을 설치하고 테스트해보았다.
rbac-view을 통해 Role을 확인할 수 있었다.
# 설치
kubectl krew install access-matrix rbac-tool rbac-view rolesum
# Show an RBAC access matrix for server resources
kubectl access-matrix # Review access to cluster-scoped resources
kubectl access-matrix --namespace default # Review access to namespaced resources in 'default'
# RBAC Lookup by subject (user/group/serviceaccount) name
kubectl rbac-tool lookup
kubectl rbac-tool lookup system:masters
SUBJECT | SUBJECT TYPE | SCOPE | NAMESPACE | ROLE
+----------------+--------------+-------------+-----------+---------------+
system:masters | Group | ClusterRole | | cluster-admin
kubectl rbac-tool lookup system:nodes # eks:node-bootstrapper
kubectl rbac-tool lookup system:bootstrappers # eks:node-bootstrapper
kubectl describe ClusterRole eks:node-bootstrapper
# RBAC List Policy Rules For subject (user/group/serviceaccount) name
kubectl rbac-tool policy-rules
kubectl rbac-tool policy-rules -e '^system:.*'
# Generate ClusterRole with all available permissions from the target cluster
kubectl rbac-tool show
# Shows the subject for the current context with which one authenticates with the cluster
kubectl rbac-tool whoami
{Username: "kubernetes-admin",
UID: "aws-iam-authenticator:911283.....:AIDA5ILF2FJ......",
Groups: ["system:masters",
"system:authenticated"],
Extra: {accessKeyId: ["AKIA5ILF2FJ....."],
arn: ["arn:aws:iam::911283....:user/admin"],
canonicalArn: ["arn:aws:iam::911283....:user/admin"],
principalId: ["AIDA5ILF2FJ....."],
sessionName: [""]}}
# Summarize RBAC roles for subjects : ServiceAccount(default), User, Group
kubectl rolesum -h
kubectl rolesum aws-node -n kube-system
kubectl rolesum -k User system:kube-proxy
kubectl rolesum -k Group system:masters
# [터미널1] A tool to visualize your RBAC permissions
kubectl rbac-view
INFO[0000] Getting K8s client
INFO[0000] serving RBAC View and http://localhost:8800
## 이후 해당 작업용PC 공인 IP:8800 웹 접속
echo -e "RBAC View Web http://$(curl -s ipinfo.io/ip):8800"
kubectl 명령 → aws eks get-token → EKS Service endpoint(STS)에 토큰 요청 ⇒ 응답값 디코드 과정은 아래와 같이 실습을 진행할 수 있다.
# sts caller id의 ARN 확인
aws sts get-caller-identity --query Arn
"arn:aws:iam::MyAccount:user/k8sadmin"
# kubeconfig 정보 확인
cat ~/.kube/config | yh
...
- name: k8sadmin@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
# Get a token for authentication with an Amazon EKS cluster.
# This can be used as an alternative to the aws-iam-authenticator.
aws eks get-token help
# 임시 보안 자격 증명(토큰)을 요청 : expirationTimestamp 시간경과 시 토큰 재발급됨
aws eks get-token --cluster-name $CLUSTER_NAME | jq
aws eks get-token --cluster-name $CLUSTER_NAME | jq -r '.status.token'
k8s-aws-v1.aHR0cHM6Ly9zdHMuYXAtbm9ydGhl...
위에서 추출한 Token 값을 JWT 사이트에 조회하면 디코드 정보를 확인할 수 있다.
위에서 뽑아낸 PAYLOAD 값을 URL Decode Online에서 Decode로 확인할 수 있다.
아래 데이터는 내 실습 환경의 Token 값에서 추출한 PAYLOAD 값을 Decode 한 내용에 대한 값이다.
https://sts.ap-northeast-2.amazonaws.com/?
Action=GetCallerIdentity&
Version=2011-06-15&
X-Amz-Algorithm=AWS4-HMAC-SHA256&
X-Amz-Credential=AKIAYRM.../20230602/ap-northeast-2/sts/aws4_request&
X-Amz-Date=20230602T055815Z&
X-Amz-Expires=60&
X-Amz-SignedHeaders=host;x-k8s-aws-id&
X-Amz-Signature=3047f3a91d9780659c.....
EKS API는 Token Review을 Webhook Token Authenticator에 요청하고 AWS IAM에 해당 호출 인증 완료 후 User/Role에 대한 ARN을 반환하게 된다. 해당 과정은 아래와 같이 테스트해볼 수 있다.
# tokenreviews api 리소스 확인
kubectl api-resources | grep authentication
tokenreviews authentication.k8s.io/v1 false TokenReview# List the fields for supported resources.
# List the fields for supported resources.
kubectl explain tokenreviews
...
DESCRIPTION:
TokenReview attempts to authenticate a token to a known user. Note:
TokenReview requests may be cached by the webhook token authenticator
plugin in the kube-apiserver.
그 다음 단계는 k8s RBAC 인가를 처리하는 단계이다. 해당 IAM User/Role이 확인 되면 k8s aws-auth-configmap에서 mapping 정보를 확인하게 되고 권한 확인 후 k8s 인가 허가가 되면 동작 실행을 하게 된다.
# Webhook api 리소스 확인
kubectl api-resources | grep Webhook
mutatingwebhookconfigurations admissionregistration.k8s.io/v1 false MutatingWebhookConfiguration
validatingwebhookconfigurations admissionregistration.k8s.io/v1 false ValidatingWebhookConfiguration
# validatingwebhookconfigurations 리소스 확인
kubectl get validatingwebhookconfigurations
NAME WEBHOOKS AGE
aws-load-balancer-webhook 3 29h
eks-aws-auth-configmap-validation-webhook 1 43h
kube-prometheus-stack-admission 1 29h
vpc-resource-validating-webhook 2 43h
kubectl get validatingwebhookconfigurations eks-aws-auth-configmap-validation-webhook -o yaml | kubectl neat | yh
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: eks-aws-auth-configmap-validation-webhook
...
# aws-auth 컨피그맵 확인
kubectl get cm -n kube-system aws-auth -o yaml | kubectl neat | yh
apiVersion: v1
kind: ConfigMap
metadata:
name: aws-auth
namespace: kube-system
data:
mapRoles: |
- groups:
- system:bootstrappers
- system:nodes
rolearn: arn:aws:iam::MyAccount:role/eksctl-myeks-nodegroup-ng1-NodeInstanceRole-1US96WGO4SJJE
username: system:node:{{EC2PrivateDNSName}}
# EKS 설치한 IAM User 정보 >> system:authenticated는 어떤 방식으로 추가가 되었는지 궁금???
kubectl rbac-tool whoami
{Username: "kubernetes-admin",
UID: "aws-iam-authenticator:MyAccount:AIDAYR...",
Groups: ["system:masters",
"system:authenticated"],
...
# system:masters , system:authenticated 그룹의 정보 확인
kubectl rbac-tool lookup system:masters
W0602 15:44:41.617127 6662 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
kubectl rbac-tool lookup system:authenticated
W0602 15:44:58.968522 6716 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:basic-user
system:authenticated | Group | ClusterRole | | system:public-info-viewer
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
*.* [*] [-] [-] ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔
kubectl rolesum -k Group system:authenticated
W0602 15:45:24.506385 6825 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
# system:masters 그룹이 사용 가능한 클러스터 롤 확인 : cluster-admin
kubectl describe clusterrolebindings.rbac.authorization.k8s.io cluster-admin
Name: cluster-admin
Labels: kubernetes.io/bootstrapping=rbac-defaults
Annotations: rbac.authorization.kubernetes.io/autoupdate: true
Role:
Kind: ClusterRole
Name: cluster-admin
Subjects:
Kind Name Namespace
---- ---- ---------
Group system:masters
# cluster-admin 의 PolicyRule 확인 : 모든 리소스 사용 가능!
kubectl describe clusterrole cluster-admin
Name: cluster-admin
Labels: kubernetes.io/bootstrapping=rbac-defaults
Annotations: rbac.authorization.kubernetes.io/autoupdate: true
PolicyRule:
Resources Non-Resource URLs Resource Names Verbs
--------- ----------------- -------------- -----
*.* [] [] [*]
[*] [] [*]
# system:authenticated 그룹이 사용 가능한 클러스터 롤 확인
kubectl describe ClusterRole system:discovery
Name: system:discovery
Labels: kubernetes.io/bootstrapping=rbac-defaults
Annotations: rbac.authorization.kubernetes.io/autoupdate: true
...
kubectl describe ClusterRole system:public-info-viewer
Name: system:public-info-viewer
Labels: kubernetes.io/bootstrapping=rbac-defaults
Annotations: rbac.authorization.kubernetes.io/autoupdate: true
...
kubectl describe ClusterRole system:basic-user
Name: system:basic-user
Labels: kubernetes.io/bootstrapping=rbac-defaults
Annotations: rbac.authorization.kubernetes.io/autoupdate: true
...
kubectl describe ClusterRole eks:podsecuritypolicy:privileged
Name: eks:podsecuritypolicy:privileged
Labels: eks.amazonaws.com/component=pod-security-policy
kubernetes.io/cluster-service=true
...
위의 내용을 기반으로 하나의 시나리오를 만들어서 테스트를 해보려고 한다. 신입 사원을 위한 myeks-bastion-2 EC2에 설정을 진행해본다.
기존 이용 중인 bastion인 myeks-bastion에서 testuser라는 IAM User을 생성하고 Accesskey을 생성하고 AdminstratorAccess 정책 권한을 부여한다. 그리고 접속을 위해 myeks-bastion-2의 IP을 확인한다.
# testuser 사용자 생성
aws iam create-user --user-name testuser
{
"User": {
"Path": "/",
"UserName": "testuser",
"UserId": "AIDAY...",
"Arn": "arn:aws:iam::MyAccount:user/testuser",
"CreateDate": "2023-06-02T07:36:50+00:00"
}
}
# 사용자에게 프로그래밍 방식 액세스 권한 부여
aws iam create-access-key --user-name testuser
{
"AccessKey": {
"UserName": "testuser",
"AccessKeyId": "AKIAY...",
"Status": "Active",
"SecretAccessKey": "+U5aviQ.....",
"CreateDate": "2023-06-02T07:37:11+00:00"
}
}
# testuser 사용자에 정책을 추가
aws iam attach-user-policy --policy-arn arn:aws:iam::aws:policy/AdministratorAccess --user-name testuser
# get-caller-identity 확인
aws sts get-caller-identity --query Arn
"arn:aws:iam::MyAccount:user/k8sadmin"
# EC2 IP 확인 : myeks-bastion-EC2-2 PublicIPAdd 확인
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
-----------------------------------------------------------------------
| DescribeInstances |
+----------------------+----------------+------------------+----------+
| InstanceName | PrivateIPAdd | PublicIPAdd | Status |
+----------------------+----------------+------------------+----------+
| myeks-ng1-Node | 192.168.3.248 | 54.180.xxx.xx | running |
| myeks-ng1-Node | 192.168.2.161 | 3.38.xxx.xx | running |
| myeks-bastion-EC2-2 | 192.168.1.200 | 54.180.xxx.xxx | running |
| myeks-bastion-EC2 | 192.168.1.100 | 43.201.xxx.xxx | running |
| myeks-ng1-Node | 192.168.1.41 | 52.78.xxx.xxx | running |
+----------------------+----------------+------------------+----------+
myeks-bastion-2에서 testuser의 자격증명 설정하고 확인한다.
testuser로 get node와 ~/.kube에 대한 내용을 조회할 경우 조회가 되지 않는다. AdministratorAccess을 갖고있음에도 불구하고 조회를 할 수 없는데 그 이유는 EKS의 경우 해당 클러스터에 대한 권한을 갖고 있어야 조회가 가능하다.
# testuser 자격증명 설정
aws configure
AWS Access Key ID [None]: AKIAY...
AWS Secret Access Key [None]: +U5aviQ.....
Default region name [None]: ap-northeast-2
Default output format [None]: json
# get-caller-identity 확인
aws sts get-caller-identity --query Arn
"arn:aws:iam::MyAccount:user/testuser"
# kubectl 시도
kubectl get node -v6
I0602 16:43:03.667055 6352 round_trippers.go:553] GET http://localhost:8080/api?timeout=32s in 1 milliseconds
E0602 16:43:03.667226 6352 memcache.go:265] couldn't get current server API group list: Get "http://localhost:8080/api?timeout=32s": dial tcp 127.0.0.1:8080: connect: connection refused
ls ~/.kube
ls: cannot access /root/.kube: No such file or directory
testuser에 system:masters 그룹 부여로 EKS 관리자 수준 권한을 부여하기 위해 myeks-bastion에서 작업을 진행한다. eksctl을 사용해서 aws-auth configmap을 작성하는 방식으로 진행한다.
# eksctl 사용 >> iamidentitymapping 실행 시 aws-auth 컨피그맵 작성해줌
# Creates a mapping from IAM role or user to Kubernetes user and groups
eksctl create iamidentitymapping --cluster $CLUSTER_NAME --username testuser --group system:masters --arn arn:aws:iam::$ACCOUNT_ID:user/testuser
2023-06-02 16:48:02 [ℹ] checking arn arn:aws:iam::MyAccount:user/testuser against entries in the auth ConfigMap
2023-06-02 16:48:02 [ℹ] adding identity "arn:aws:iam::MyAccount:user/testuser" to auth ConfigMap
# 확인
kubectl get cm -n kube-system aws-auth -o yaml | kubectl neat | yh
...
data:
mapRoles: |
- groups:
- system:bootstrappers
- system:nodes
rolearn: arn:aws:iam::MyAccount:role/eksctl-myeks-nodegroup-ng1-NodeInstanceRole-1US96WGO4SJJE
username: system:node:{{EC2PrivateDNSName}}
mapUsers: |
- groups:
- system:masters
userarn: arn:aws:iam::MyAccount:user/testuser
username: testuser
...
# 확인 : 기존에 있는 role/eksctl-myeks-nodegroup-ng1-NodeInstanceRole-YYYYY 는 어떤 역할/동작을 하는 걸까요?
eksctl get iamidentitymapping --cluster $CLUSTER_NAME
ARN USERNAME GROUPS ACCOUNT
arn:aws:iam::MyAccount:role/eksctl-myeks-nodegroup-ng1-NodeInstanceRole-1US96WGO4SJJE system:node:{{EC2PrivateDNSName}} system:bootstrappers,system:nodes
arn:aws:iam::MyAccount:user/testuser testuser system:masters
testuser에서 kubeconfig 생성 및 kubectl 사용을 myeks-bastion-2에서 확인 진행해봤다.
# testuser kubeconfig 생성
aws eks update-kubeconfig --name $CLUSTER_NAME --user-alias testuser
Added new context testuser to /root/.kube/config
# kubectl 사용 확인
kubectl ns default
Context "testuser" modified.
Active namespace is "default".
kubectl get node -v6
I0602 17:28:46.036886 6734 loader.go:373] Config loaded from file: /root/.kube/config
I0602 17:28:46.867677 6734 round_trippers.go:553] GET https://D5534....yl4.ap-northeast-2.eks.amazonaws.com/api/v1/nodes?limit=500 200 OK in 819 milliseconds
NAME STATUS ROLES AGE VERSION
ip-192-168-1-41.ap-northeast-2.compute.internal Ready <none> 44h v1.24.13-eks-0a21954
ip-192-168-2-161.ap-northeast-2.compute.internal Ready <none> 44h v1.24.13-eks-0a21954
ip-192-168-3-248.ap-northeast-2.compute.internal Ready <none> 44h v1.24.13-eks-0a21954
# rbac-tool 후 확인
kubectl krew install rbac-tool && kubectl rbac-tool whoami
{Username: "testuser",
UID: "aws-iam-authenticator:MyAccount:AIDAYR...",
Groups: ["system:masters",
"system:authenticated"],
Extra: {accessKeyId: ["AKIAYRMZ..."],
arn: ["arn:aws:iam::MyAccount:user/testuser"],
canonicalArn: ["arn:aws:iam::MyAccount:user/testuser"],
principalId: ["AIDAYRM..."],
sessionName: [""]}}
...
testuser의 Group을 system:masters에서 system:authenticated로 RBAC 동작 확인을 진행해본다. 제대로 변경 된 것을 확인할 수 있다.
# 아래 edit로 mapUsers 내용 직접 수정 system:authenticated
kubectl edit cm -n kube-system aws-auth
...
# 확인
eksctl get iamidentitymapping --cluster $CLUSTER_NAME
ARN USERNAME GROUPS ACCOUNT
arn:aws:iam::MyAccount:role/eksctl-myeks-nodegroup-ng1-NodeInstanceRole-1US96WGO4SJJE system:node:{{EC2PrivateDNSName}} system:bootstrappers,system:nodes
arn:aws:iam::MyAccount:user/testuser testuser system:authenticated
testuser의 kubectl 사용을 확인해보았다. masters에서 authenticated로 변경되어서 get node -v6 명령이 Forbidden 처리되는 것을 확인할 수 있다.
# 시도
kubectl get node -v6
I0602 17:41:46.148222 7108 loader.go:373] Config loaded from file: /root/.kube/config
I0602 17:41:47.185378 7108 round_trippers.go:553] GET https://D55341D506A04AA2DE918CAD37BF2459.yl4.ap-northeast-2.eks.amazonaws.com/api/v1/nodes?limit=500 403 Forbidden in 1014 milliseconds
...
"message": "nodes is forbidden: User \"testuser\" cannot list resource \"nodes\" in API group \"\" at the cluster scope",
"reason": "Forbidden",
...
kubectl api-resources -v5
testuser의 IAM Mapping 삭제를 첫번째 bastion EC2에서 k8sadmin user로 진행한다.
IAM Mapping 삭제 후 확인하면 k8sadmin에 대한 IAM Mapping만 확인할 수 있다.
# testuser IAM 맵핑 삭제
eksctl delete iamidentitymapping --cluster $CLUSTER_NAME --arn arn:aws:iam::$ACCOUNT_ID:user/testuser
2023-06-02 17:47:39 [ℹ] removing identity "arn:aws:iam::MyAccount:user/testuser" from auth ConfigMap (username = "testuser", groups = ["system:authenticated"])
# Get IAM identity mapping(s)
eksctl get iamidentitymapping --cluster $CLUSTER_NAME
ARN USERNAME GROUPS ACCOUNT
arn:aws:iam::MyAccount:role/eksctl-myeks-nodegroup-ng1-NodeInstanceRole-1US96WGO4SJJE system:node:{{EC2PrivateDNSName}} system:bootstrappers,system:nodes
kubectl get cm -n kube-system aws-auth -o yaml | yh
myeks-bastion-2 EC2에서 testuser 권한으로 get node을 실행하면 서버에 접속하지 못하는 것을 확인할 수 있다. 이는 authenticated 그룹에 할당된 것과는 다르게 아예 IAM Mapping이 되지 않았기 때문에 서버에 접속하지 못하는 상황이라고 볼 수 있다.
# 시도
kubectl get node -v6
error: You must be logged in to the server (the server has asked for the client to provide credentials)
kubectl api-resources -v5
error: You must be logged in to the server (the server has asked for the client to provide credentials)
이렇게 간단한 테스트를 진행해보았다. IAM User에게 기존 생성 된 group Mapping을 해주면 언제든 권한을 사용할 수 있음을 확인하였다.
3. IRSA
EKS을 사용 중에 EC2에 Instance Profile을 사용하여 권한을 부여 받을 경우 사용하기에는 편리하지만 보안에는 취약해지게 된다. 그렇기 때문에 IRSA 사용을 권장한다.
IRSA(IAM Role for Service Accounts)는 AWS EKS에서 제공하는 기능으로, 쿠버네티스의 서비스 계정에 AWS IAM 역할을 할당하여 AWS 리소스에 대한 액세스 권한을 제어하는 메커니즘이다.
일반적으로, 쿠버네티스 클러스터에서 AWS 리소스에 액세스하기 위해 AWS API를 호출하는 애플리케이션은 IAM 역할을 사용하여 권한을 부여받는다. 그러나 이러한 방식은 모든 애플리케이션에 대해 개별적으로 IAM 역할을 생성하고 관리해야 한다는 번거로움이 있기 때문에 IRSA를 사용하여 쿠버네티스의 서비스 계정에 IAM 역할을 연결하여 AWS API 액세스를 간편하게 제어한다.
IRSA의 동작 방식은 k8s파드 → AWS 서비스 사용 시 ⇒ AWS STS/IAM ↔ IAM OIDC Identity Provider(EKS IdP) 인증/인가로 이루어진다.
IRSA에 대한 내용을 실습할 겸 도전과제인 awscli pod에서 IRSA을 사용해서 AWS 서비스의 내용들을 조회해보는 테스트를 진행해볼 예정이다.
과정은 SA 생성 및 IRSA 설정과 Pod 배포 후 테스트로 이어진다.
IRSA에 사용하는 Policy는 AWS에서 ManagedPolicy로 제공하는 ReadOnlyAccess을 사용하였다. 테스트 결과 내용을 조회하는데는 문제없었으나 그 이외의 명령은 권한 오류가 발생하는 것으로 정상적으로 IRSA 테스트가 완료되었음을 알 수 있다.
# service account 생성
kubectl create serviceaccount awscli-sa --namespace default
serviceaccount/awscli-sa created
# IRSA 설정
eksctl create iamserviceaccount --cluster $CLUSTER_NAME --namespace default --name awscli-sa --attach-policy-arn arn:aws:iam::aws:policy/ReadOnlyAccess --approve --override-existing-serviceaccounts
2023-06-03 09:46:51 [ℹ] 1 existing iamserviceaccount(s) (kube-system/aws-load-balancer-controller) will be excluded
2023-06-03 09:46:51 [ℹ] 1 iamserviceaccount (default/awscli-sa) was included (based on the include/exclude rules)
2023-06-03 09:46:51 [!] metadata of serviceaccounts that exist in Kubernetes will be updated, as --override-existing-serviceaccounts was set
2023-06-03 09:46:51 [ℹ] 1 task: {
2 sequential sub-tasks: {
create IAM role for serviceaccount "default/awscli-sa",
create serviceaccount "default/awscli-sa",
} }2023-06-03 09:46:51 [ℹ] building iamserviceaccount stack "eksctl-myeks-addon-iamserviceaccount-default-awscli-sa"
2023-06-03 09:46:51 [ℹ] deploying stack "eksctl-myeks-addon-iamserviceaccount-default-awscli-sa"
2023-06-03 09:46:51 [ℹ] waiting for CloudFormation stack "eksctl-myeks-addon-iamserviceaccount-default-awscli-sa"
2023-06-03 09:47:21 [ℹ] waiting for CloudFormation stack "eksctl-myeks-addon-iamserviceaccount-default-awscli-sa"
2023-06-03 09:48:04 [ℹ] waiting for CloudFormation stack "eksctl-myeks-addon-iamserviceaccount-default-awscli-sa"
2023-06-03 09:48:04 [ℹ] serviceaccount "default/awscli-sa" already exists
2023-06-03 09:48:04 [ℹ] updated serviceaccount "default/awscli-sa"
# pod 배포
## aws-cli-pod.yaml 작성
apiVersion: v1
kind: Pod
metadata:
name: awscli-pod
spec:
containers:
- name: awscli-container
image: amazon/aws-cli
command: ["sleep", "infinity"]
serviceAccountName: awscli-sa
## yaml 사용 pod 배포
kubectl create -f aws-cli-pod.yaml
pod/awscli-pod created
## 배포 확인
kubectl get pod
NAME READY STATUS RESTARTS AGE
awscli-pod 1/1 Running 0 22s
# aws cli pod 접속 후 테스트 진행
kubectl exec -it awscli-pod -- sh
aws s3 ls
2023-04-27 04:20:41 cloudtrail-awslogs-MyAccount-vlhszz1b-do-not-delete
2023-04-29 05:28:11 do-not-delete-gatedgarden-audit-MyAccount
aws ec2 describe-instances --query "Reservations[].Instances[].Tags[?Key=='Name'].Value" --output text
myeks-ng1-Node
myeks-ng1-Node
test
myeks-bastion-EC2-2
myeks-bastion-EC2
myeks-ng1-Node
# ec2 stop 명령 -> 실행되지 않음
aws ec2 stop-instances --instance-id i-08d649dc....
An error occurred (UnauthorizedOperation) when calling the StopInstances operation: You are not authorized to perform this operation. Encoded authorization failure message: 0wcTjz3wqszNCw...
5. 정리
인증/인가는 특히 다른 파트들에 비해 조금 어려웠다.
내용 자체가 이해가 가지 않는 건 아니었으나 이를 실습에 적용하는 과정에서 이 부분이 지금 내가 이해한 부분대로 작동하는 게 맞는지에 대해 계속 고민하게 만든 것 같다.
IAM User에게 Readonly 권한만 주고 테스트를 해보려고 했으나 계속 작동하지 않아 이 부분은 차근차근 다시 도전해볼 계획이다.