๐Ÿซก ํ†ต์‹ ๋ณด์•ˆ! : AEWS - EKS Study 6์ฃผ์ฐจ [ EKS Security ]

Heiheiยท2023๋…„ 6์›” 3์ผ
0

AEWS EKS STUDY

๋ชฉ๋ก ๋ณด๊ธฐ
7/7

๐ŸŽฏ ๋ชฉํ‘œ

  • ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค ๊ถŒํ•œ ์ธ์ฆ/์ธ๊ฐ€์— ๋Œ€ํ•œ ์ดํ•ด ํ™•๋ฆฝ
  • EKS์—์„œ ๊ถŒํ•œ ์ธ์ฆ/์ธ๊ฐ€์— ๋Œ€ํ•œ ์ดํ•ด ํ™•๋ฆฝ
  • IRSA์— ๋Œ€ํ•œ ์ดํ•ด ๋ฐ ์‚ฌ์šฉ

๐Ÿƒ ์ง„ํ–‰

1. ์‹ค์Šต ํ™˜๊ฒฝ ๋ฐฐํฌ

  • oneclick5.yamlํŒŒ์ผ์„ ์‚ฌ์šฉํ•˜์—ฌ ์‹ค์Šต ํ™˜๊ฒฝ ๋ฐฐํฌ
  • ExternalDNS, AWS LB Controller ์„ค์น˜

2. K8S ์ธ์ฆ/์ธ๊ฐ€

๐Ÿ‘‰ K8S(API ์ ‘๊ทผ) ์ธ์ฆ/์ธ๊ฐ€ ์†Œ๊ฐœ

  • ์„œ๋น„์Šค ์–ด์นด์šดํŠธ(Service Account)
  • API ์„œ๋ฒ„ ์‚ฌ์šฉ : kubectl(config, ๋‹ค์ˆ˜ ํด๋Ÿฌ์Šคํ„ฐ ๊ด€๋ฆฌ ๊ฐ€๋Šฅ), ์„œ๋น„์Šค ์–ด์นด์šดํŠธ, https(x.509 Client Certs) - ๋งํฌ
  • API ์„œ๋ฒ„ ์ ‘๊ทผ ๊ณผ์ • : ์ธ์ฆ โ†’ ์ธ๊ฐ€ โ†’ Admission Control(API ์š”์ฒญ ๊ฒ€์ฆ, ํ•„์š” ์‹œ ๋ณ€ํ˜• - ์˜ˆ. ResourceQuota, LimitRange) - ์ฐธ๊ณ 


์ถœ์ฒ˜ : ๊น€ํƒœ๋ฏผ ๊ธฐ์ˆ ๋ธ”๋กœ๊ทธ

์ธ์ฆ(Authentication)

  • X.509 Client Certs : kubeconfig ์— CA crt(๋ฐœ๊ธ‰ ๊ธฐ๊ด€ ์ธ์ฆ์„œ) , Client crt(ํด๋ผ์ด์–ธํŠธ ์ธ์ฆ์„œ) , Client key(ํด๋ผ์ด์–ธํŠธ ๊ฐœ์ธํ‚ค) ๋ฅผ ํ†ตํ•ด ์ธ์ฆ
  • kubectl : ์—ฌ๋Ÿฌ ํด๋Ÿฌ์Šคํ„ฐ(kubeconfig)๋ฅผ ๊ด€๋ฆฌ ๊ฐ€๋Šฅ - contexts ์— ํด๋Ÿฌ์Šคํ„ฐ์™€ ์œ ์ € ๋ฐ ์ธ์ฆ์„œ/ํ‚ค ์ฐธ๊ณ 
  • Service Account : ๊ธฐ๋ณธ ์„œ๋น„์Šค ์–ด์นด์šดํŠธ(default) - ์‹œํฌ๋ฆฟ(CA crt ์™€ token)


์ถœ์ฒ˜ : ๊น€ํƒœ๋ฏผ ๊ธฐ์ˆ ๋ธ”๋กœ๊ทธ

์ธ๊ฐ€(Authorization)

  • ์ธ๊ฐ€ ๋ฐฉ์‹ : RBAC(Role, RoleBinding), ABAC, Webhook, Node Authorizationโ‡’ RBAC ๋ฐœ์Œ์„ ์–ด๋–ป๊ฒŒ ํ•˜์‹œ๋‚˜์š”?
  • RBAC : ์—ญํ•  ๊ธฐ๋ฐ˜์˜ ๊ถŒํ•œ ๊ด€๋ฆฌ, ์‚ฌ์šฉ์ž์™€ ์—ญํ• ์„ ๋ณ„๊ฐœ๋กœ ์„ ์–ธ ํ›„ ๋‘๊ฐ€์ง€๋ฅผ ์กฐํ•ฉ(binding)ํ•ด์„œ ์‚ฌ์šฉ์ž์—๊ฒŒ ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•˜์—ฌ kubectl or API๋กœ ๊ด€๋ฆฌ ๊ฐ€๋Šฅ
    • Namespace/Cluster - Role/ClusterRole, RoleBinding/ClusterRoleBinding, Service Account
    • Role(๋กค) - (RoleBinding ๋กค ๋ฐ”์ธ๋”ฉ) - Service Account(์„œ๋น„์Šค ์–ด์นด์šดํŠธ) : ๋กค ๋ฐ”์ธ๋”ฉ์€ ๋กค๊ณผ ์„œ๋น„์Šค ์–ด์นด์šดํŠธ๋ฅผ ์—ฐ๊ฒฐ
    • Role(๋„ค์ž„์ŠคํŽ˜์ด์Šค๋‚ด ์ž์›์˜ ๊ถŒํ•œ) vs ClusterRole(ํด๋Ÿฌ์Šคํ„ฐ ์ˆ˜์ค€์˜ ์ž์›์˜ ๊ถŒํ•œ)

.kube/config ํŒŒ์ผ ๋‚ด์šฉ

  • clusters : kubectl ์ด ์‚ฌ์šฉํ•  ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค API ์„œ๋ฒ„์˜ ์ ‘์† ์ •๋ณด ๋ชฉ๋ก. ์›๊ฒฉ์˜ ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค API ์„œ๋ฒ„์˜ ์ฃผ์†Œ๋ฅผ ์ถ”๊ฐ€ํ•ด ์‚ฌ์šฉ ๊ฐ€๋Šฅ
  • users : ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค์˜ API ์„œ๋ฒ„์— ์ ‘์†ํ•˜๊ธฐ ์œ„ํ•œ ์‚ฌ์šฉ์ž ์ธ์ฆ ์ •๋ณด ๋ชฉ๋ก. (์„œ๋น„์Šค ์–ด์นด์šดํŠธ์˜ ํ† ํฐ, ํ˜น์€ ์ธ์ฆ์„œ์˜ ๋ฐ์ดํ„ฐ ๋“ฑ)
  • contexts : cluster ํ•ญ๋ชฉ๊ณผ users ํ•ญ๋ชฉ์— ์ •์˜๋œ ๊ฐ’์„ ์กฐํ•ฉํ•ด ์ตœ์ข…์ ์œผ๋กœ ์‚ฌ์šฉํ•  ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค ํด๋Ÿฌ์Šคํ„ฐ์˜ ์ •๋ณด(์ปจํ…์ŠคํŠธ)๋ฅผ ์„ค์ •
    • ์˜ˆ๋ฅผ ๋“ค์–ด clusters ํ•ญ๋ชฉ์— ํด๋Ÿฌ์Šคํ„ฐ A,B ๊ฐ€ ์ •์˜๋ผ ์žˆ๊ณ , users ํ•ญ๋ชฉ์— ์‚ฌ์šฉ์ž a,b ๊ฐ€ ์ •์˜๋ผ ์žˆ๋‹ค๋ฉด cluster A + user a ๋ฅผ ์กฐํ•ฉํ•ด,
      'cluster A ์— user a ๋กœ ์ธ์ฆํ•ด ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค' ๋ผ๋Š” ์ƒˆ๋กœ์šด ์ปจํ…์ŠคํŠธ๋ฅผ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Œ
    • kubectl ์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์ปจํ…์ŠคํŠธ ์ค‘ ํ•˜๋‚˜๋ฅผ ์„ ํƒ

์‹ค์Šต ํ™˜๊ฒฝ

  • ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค์— ์‚ฌ์šฉ์ž๋ฅผ ์œ„ํ•œ ์„œ๋น„์Šค ์–ด์นด์šดํŠธ(Service Account, SA)๋ฅผ ์ƒ์„ฑ : dev-k8s, infra-k8s
  • ์‚ฌ์šฉ์ž๋Š” ๊ฐ๊ธฐ ๋‹ค๋ฅธ ๊ถŒํ•œ(Role, ์ธ๊ฐ€)์„ ๊ฐ€์ง : dev-k8s(dev-team ๋„ค์ž„์ŠคํŽ˜์ด์Šค ๋‚ด ๋ชจ๋“  ๋™์ž‘) , infra-k8s(dev-team ๋„ค์ž„์ŠคํŽ˜์ด์Šค ๋‚ด ๋ชจ๋“  ๋™์ž‘)
  • ๊ฐ๊ฐ ๋ณ„๋„์˜ kubectl ํŒŒ๋“œ๋ฅผ ์ƒ์„ฑํ•˜๊ณ , ํ•ด๋‹น ํŒŒ๋“œ์— SA ๋ฅผ ์ง€์ •ํ•˜์—ฌ ๊ถŒํ•œ์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰

๋„ค์ž„์ŠคํŽ˜์ด์Šค์™€ ์„œ๋น„์Šค ์–ด์นด์šดํŠธ ์ƒ์„ฑ ํ›„ ํ™•์ธ

  • ํŒŒ๋“œ ๊ธฐ๋™ ์‹œ ์„œ๋น„์Šค ์–ด์นด์šดํŠธ ํ•œ ๊ฐœ๊ฐ€ ํ• ๋‹น๋˜๋ฉฐ, ์„œ๋น„์Šค ์–ด์นด์šดํŠธ ๊ธฐ๋ฐ˜ ์ธ์ฆ/์ธ๊ฐ€๋ฅผ ํ•จ, ๋ฏธ์ง€์ • ์‹œ ๊ธฐ๋ณธ ์„œ๋น„์Šค ์–ด์นด์šดํŠธ๊ฐ€ ํ• ๋‹น
  • ์„œ๋น„์Šค ์–ด์นด์šดํŠธ์— ์ž๋™ ์ƒ์„ฑ๋œ ์‹œํฌ๋ฆฟ์— ์ €์žฅ๋œ ํ† ํฐ์œผ๋กœ ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค API์— ๋Œ€ํ•œ ์ธ์ฆ ์ •๋ณด๋กœ ์‚ฌ์šฉ ํ•  ์ˆ˜ ์žˆ๋‹ค โ† 1.23 ์ด์ „ ๋ฒ„์ „์˜ ๊ฒฝ์šฐ์—๋งŒ ํ•ด๋‹น
    # ๋„ค์ž„์ŠคํŽ˜์ด์Šค(Namespace, NS) ์ƒ์„ฑ ๋ฐ ํ™•์ธ
    kubectl create namespace dev-team
    kubectl create ns infra-team
    
    # ๋„ค์ž„์ŠคํŽ˜์ด์Šค ํ™•์ธ
    kubectl get ns
    
    # ๋„ค์ž„์ŠคํŽ˜์ด์Šค์— ๊ฐ๊ฐ ์„œ๋น„์Šค ์–ด์นด์šดํŠธ ์ƒ์„ฑ : 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
    
    kubectl get sa -n infra-team
    kubectl get sa infra-k8s -n infra-team -o yaml | yh

์„œ๋น„์Šค ์–ด์นด์šดํŠธ๋ฅผ ์ง€์ •ํ•˜์—ฌ ํŒŒ๋“œ ์ƒ์„ฑ ํ›„ ๊ถŒํ•œ ํ…Œ์ŠคํŠธ


์ถœ์ฒ˜ : https://kubetm.github.io/practice/intermediate/object-authentication/

# ๊ฐ๊ฐ ๋„ค์ž„์Šคํ”ผ์ด์Šค์— 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
kubectl exec -it dev-kubectl -n dev-team -- cat /run/secrets/kubernetes.io/serviceaccount/token
kubectl exec -it dev-kubectl -n dev-team -- cat /run/secrets/kubernetes.io/serviceaccount/namespace
kubectl exec -it dev-kubectl -n dev-team -- cat /run/secrets/kubernetes.io/serviceaccount/ca.crt

# ๊ฐ๊ฐ ํŒŒ๋“œ๋กœ 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 ์™€ ๋™์ผํ•œ ์‹คํ–‰ ๋ช…๋ น์ด๋‹ค!
k1 run nginx --image nginx:1.20-alpine
k1 get pods -n kube-system

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

๊ฐ๊ฐ ๋„ค์ž„์ŠคํŽ˜์ด์Šค์— ๋กค(Role)๋ฅผ ์ƒ์„ฑ ํ›„ ์„œ๋น„์Šค ์–ด์นด์šดํŠธ ๋ฐ”์ธ๋”ฉ

  • ๋กค(Role) : apiGroups ์™€ resources ๋กœ ์ง€์ •๋œ ๋ฆฌ์†Œ์Šค์— ๋Œ€ํ•ด verbs ๊ถŒํ•œ์„ ์ธ๊ฐ€
  • ์‹คํ–‰ ๊ฐ€๋Šฅํ•œ ์กฐ์ž‘(verbs) : *(๋ชจ๋‘ ์ฒ˜๋ฆฌ), create(์ƒ์„ฑ), delete(์‚ญ์ œ), get(์กฐํšŒ), list(๋ชฉ๋ก์กฐํšŒ), patch(์ผ๋ถ€์—…๋ฐ์ดํŠธ), update(์—…๋ฐ์ดํŠธ), watch(๋ณ€๊ฒฝ๊ฐ์‹œ)

    ์ถœ์ฒ˜ : https://kubetm.github.io/practice/intermediate/object-authorization/
# ๊ฐ๊ฐ ๋„ค์ž„์ŠคํŽ˜์ด์Šค๋‚ด์˜ ๋ชจ๋“  ๊ถŒํ•œ์— ๋Œ€ํ•œ ๋กค ์ƒ์„ฑ
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
kubectl get roles -n infra-team
kubectl get roles -n dev-team -o yaml
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
kubectl get rolebindings -n infra-team
kubectl get rolebindings -n dev-team -o yaml
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

์„œ๋น„์Šค ์–ด์นด์šดํŠธ๋ฅผ ์ง€์ •ํ•˜์—ฌ ์ƒ์„ฑํ•œ ํŒŒ๋“œ์—์„œ ๋‹ค์‹œ ๊ถŒํ•œ ํ…Œ์ŠคํŠธ

# ๊ฐ๊ฐ ํŒŒ๋“œ๋กœ 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 
k1 run nginx --image nginx:1.20-alpine
k1 get pods
k1 delete pods nginx
k1 get pods -n kube-system
k1 get nodes

k2 get pods 
k2 run nginx --image nginx:1.20-alpine
k2 get pods
k2 delete pods nginx
k2 get pods -n kube-system
k2 get nodes

# (์˜ต์…˜) kubectl auth can-i ๋กœ kubectl ์‹คํ–‰ ์‚ฌ์šฉ์ž๊ฐ€ ํŠน์ • ๊ถŒํ•œ์„ ๊ฐ€์กŒ๋Š”์ง€ ํ™•์ธ
k1 auth can-i get pods
yes
  • ๋ฆฌ์†Œ์Šค ์‚ญ์ œ : kubectl delete ns dev-team infra-team

3. EKS ์ธ์ฆ/์ธ๊ฐ€

  • ์‚ฌ์šฉ์ž/์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ โ†’ k8s ์‚ฌ์šฉ ์‹œ โ‡’ ์ธ์ฆ์€ AWS IAM, ์ธ๊ฐ€๋Š” K8S RBAC

    ์ถœ์ฒ˜ : https://kimalarm.tistory.com/65

๐Ÿ‘‰ RBAC ๊ด€๋ จ krew ํ”Œ๋Ÿฌ๊ทธ์ธ

# ์„ค์น˜
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"
  • ํ”Œ๋Ÿฌ๊ทธ์ธ ์„ค์น˜ ํ›„ ์ž‘์—…์šฉPC ๊ณต์ธ IP:8800์— ์›น ์ ‘์†

๐Ÿ‘‰ ์ธ์ฆ/์ธ๊ฐ€ ์™„๋ฒฝ ๋ถ„์„



์ถœ์ฒ˜ : https://docs.aws.amazon.com/eks/latest/userguide/cluster-auth.html

  • ํ•ต์‹ฌ : ์ธ์ฆ์€ AWS IAM, ์ธ๊ฐ€๋Š” K8S RBAC์—์„œ ์ฒ˜๋ฆฌ

    ์ถœ์ฒ˜ : https://awskoreamarketingasset.s3.amazonaws.com/2022 Summit/pdf/T10S1_EKS ํ™˜๊ฒฝ์„ ๋” ํšจ์œจ์ ์œผ๋กœ ๋” ์•ˆ์ „ํ•˜๊ฒŒ.pdf

1. kubectl ๋ช…๋ น โ†’ aws eks get-token โ†’ EKS Service endpoint(STS)์— ํ† ํฐ ์š”์ฒญ โ‡’ ์‘๋‹ต๊ฐ’ ๋””์ฝ”๋“œ(Pre-Signed URL ์ด๋ฉฐ GetCallerIdentity..) - ๋งํฌ

  • sts caller id์˜ ARN ํ™•์ธ
aws sts get-caller-identity --query Arn

  • kubeconfig ์ •๋ณด ํ™•์ธ
cat ~/.kube/config | yh

  • ์ž„์‹œ ๋ณด์•ˆ ์ž๊ฒฉ ์ฆ๋ช…(ํ† ํฐ)์„ ์š”์ฒญ : expirationTimestamp ์‹œ๊ฐ„๊ฒฝ๊ณผ ์‹œ ํ† ํฐ ์žฌ๋ฐœ๊ธ‰๋จ
aws eks get-token --cluster-name $CLUSTER_NAME | jq
aws eks get-token --cluster-name $CLUSTER_NAME | jq -r '.status.token'


"expirationTimestamp": "2023-06-03T07:12:18Z",
    "token": "k8s-aws-v1.aHR0cHM6Ly9zdHMuYXAtbm9ydGhlYXN0LTIuYW1hem9uYXdzLmNvbS8_QWN0aW9uPUdldENhbGxlcklkZW50aXR5JlZlcnNpb249MjAxMS0wNi0xNSZYLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFTNzdKQk9ZNVVZTjZSUzdPJTJGMjAyMzA2MDMlMkZhcC1ub3J0aGVhc3QtMiUyRnN0cyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjMwNjAzVDA2NTgxOFomWC1BbXotRXhwaXJlcz02MCZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QlM0J4LWs4cy1hd3MtaWQmWC1BbXotU2lnbmF0dXJlPTAwZWFiM2MwZTlmMDExYzBkYmMzNjU2Zjc5OTJmYjFiZGI5NmMwMTc1Y2FmYjhjZmNjZGE4MGY0ZGRhZjZkODk"
  }
}

2. kubectl์˜ Client-Go ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” Pre-Signed URL์„ Bearer Token์œผ๋กœ EKS API Cluster Endpoint๋กœ ์š”์ฒญ์„ ๋ณด๋ƒ„

3. EKS API๋Š” Token Review ๋ฅผ Webhook token authenticator์— ์š”์ฒญ โ‡’ (STS GetCallerIdentity ํ˜ธ์ถœ) 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.
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.

4. ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค RBAC ์ธ๊ฐ€ ์ฒ˜๋ฆฌ

  • ํ•ด๋‹น IAM User/Role ํ™•์ธ์ด ๋˜๋ฉด k8s aws-auth configmap์—์„œ mapping ์ •๋ณด๋ฅผ ํ™•์ธ
  • aws-auth ์ปจํ”ผ๊ทธ๋งต์— 'IAM ์‚ฌ์šฉ์ž, ์—ญํ•  arm, K8S ์˜ค๋ธŒ์ ํŠธ' ๋กœ ๊ถŒํ•œ ํ™•์ธ ํ›„ k8s ์ธ๊ฐ€ ํ—ˆ๊ฐ€๊ฐ€ ๋˜๋ฉด ์ตœ์ข…์ ์œผ๋กœ ๋™์ž‘ ์‹คํ–‰
  • ์ฐธ๊ณ ๋กœ EKS๋ฅผ ์ƒ์„ฑํ•œ IAM principal์€ aws-auth ์™€ ์ƒ๊ด€์—†์ด kubernetes-admin Username์œผ๋กœ system:masters ๊ทธ๋ฃน์— ๊ถŒํ•œ์„ ๊ฐ€์ง - ๋งํฌ
# 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
eks-aws-auth-configmap-validation-webhook   1          50m
vpc-resource-validating-webhook             2          50m
aws-load-balancer-webhook                   3          8m27s

kubectl get validatingwebhookconfigurations eks-aws-auth-configmap-validation-webhook -o yaml | kubectl neat | yh

# 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::91128.....:role/eksctl-myeks-nodegroup-ng1-NodeInstanceRole-1OS1WSTV0YB9X
      username: system:node:{{EC2PrivateDNSName}}
#---<์•„๋ž˜ ์ƒ๋žต(์ถ”์ •), ARN์€ EKS๋ฅผ ์„ค์น˜ํ•œ IAM User , ์—ฌ๊ธฐ ์žˆ์—ˆ์„๊ฒฝ์šฐ ๋งŒ์•ฝ ์‹ค์ˆ˜๋กœ ์‚ญ์ œ ์‹œ ๋ณต๊ตฌ๊ฐ€ ๊ฐ€๋Šฅํ–ˆ์„๊นŒ?---
  mapUsers: |
    - groups:
      - system:masters
      userarn: arn:aws:iam::111122223333:user/admin
      username: kubernetes-admin

# EKS ์„ค์น˜ํ•œ IAM User ์ •๋ณด >> system:authenticated๋Š” ์–ด๋–ค ๋ฐฉ์‹์œผ๋กœ ์ถ”๊ฐ€๊ฐ€ ๋˜์—ˆ๋Š”์ง€ ๊ถ๊ธˆ???
kubectl rbac-tool whoami
{Username: "kubernetes-admin",
 UID:      "aws-iam-authenticator:9112834...:AIDA5ILF2FJIR2.....",
 Groups:   ["system:masters",
            "system:authenticated"],
...

# system:masters , system:authenticated ๊ทธ๋ฃน์˜ ์ •๋ณด ํ™•์ธ
kubectl rbac-tool lookup system:masters
kubectl rbac-tool lookup system:authenticated
kubectl rolesum -k Group system:masters
kubectl rolesum -k Group system:authenticated

# 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
kubectl describe ClusterRole system:public-info-viewer
kubectl describe ClusterRole system:basic-user
kubectl describe ClusterRole eks:podsecuritypolicy:privileged

๐Ÿ‘‰ ๋ฐ๋ธŒ์˜ต์Šค ์‹ ์ž… ์‚ฌ์›์„ ์œ„ํ•œ myeks-bastion-2์— ์„ค์ • ํ•ด๋ณด๊ธฐ

1. [myeks-bastion] testuser ์‚ฌ์šฉ์ž ์ƒ์„ฑ
# testuser ์‚ฌ์šฉ์ž ์ƒ์„ฑ
aws iam create-user --user-name testuser

# ์‚ฌ์šฉ์ž์—๊ฒŒ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๋ฐฉ์‹ ์•ก์„ธ์Šค ๊ถŒํ•œ ๋ถ€์—ฌ
aws iam create-access-key --user-name testuser
{
    "AccessKey": {
        "UserName": "testuser",
        "AccessKeyId": "AKIA5ILF2##",
        "Status": "Active",
        "SecretAccessKey": "TxhhwsU8##",
        "CreateDate": "2023-05-23T07:40:09+00:00"
    }
}
{
    "AccessKey": {
        "UserName": "testuser",
        "AccessKeyId": "AKIAS77JBOY5VWZZMROH",
        "Status": "Active",
        "SecretAccessKey": "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",
        "CreateDate": "2023-06-03T07:57:35+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::911283464785:user/admin"

# 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

2. [myeks-bastion-2] testuser ์ž๊ฒฉ์ฆ๋ช… ์„ค์ • ๋ฐ ํ™•์ธ

# get-caller-identity ํ™•์ธ >> ์™œ ์•ˆ๋ ๊นŒ์š”?
aws sts get-caller-identity --query Arn

# testuser ์ž๊ฒฉ์ฆ๋ช… ์„ค์ •
aws configure
AWS Access Key ID [None]: AKIA5ILF2F...
AWS Secret Access Key [None]: ePpXdhA3cP....
Default region name [None]: ap-northeast-2

# get-caller-identity ํ™•์ธ
aws sts get-caller-identity --query Arn
"arn:aws:iam::911283464785:user/testuser"

# kubectl ์‹œ๋„ >> testuser๋„ AdministratorAccess ๊ถŒํ•œ์„ ๊ฐ€์ง€๊ณ  ์žˆ๋Š”๋ฐ, ์‹คํŒจ ์ด์œ ๋Š”?
kubectl get node -v6
ls ~/.kube

3. [myeks-bastion] testuser์— system:masters ๊ทธ๋ฃน ๋ถ€์—ฌ๋กœ EKS ๊ด€๋ฆฌ์ž ์ˆ˜์ค€ ๊ถŒํ•œ ์„ค์ •

# ๋ฐฉ์•ˆ1 : 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

# ํ™•์ธ
kubectl get cm -n kube-system aws-auth -o yaml | kubectl neat | yh
...

# ํ™•์ธ : ๊ธฐ์กด์— ์žˆ๋Š” role/eksctl-myeks-nodegroup-ng1-NodeInstanceRole-YYYYY ๋Š” ์–ด๋–ค ์—ญํ• /๋™์ž‘์„ ํ•˜๋Š” ๊ฑธ๊นŒ์š”?
eksctl get iamidentitymapping --cluster $CLUSTER_NAME
ARN											USERNAME				GROUPS					ACCOUNT
arn:aws:iam::911283464785:role/eksctl-myeks-nodegroup-ng1-NodeInstanceRole-LHQ7DWHQQRZJ	system:node:{{EC2PrivateDNSName}}	system:bootstrappers,system:nodes	
arn:aws:iam::911283464785:user/testuser							testuser				system:masters

4. [myeks-bastion-2] testuser kubeconfig ์ƒ์„ฑ ๋ฐ kubectl ์‚ฌ์šฉ ํ™•์ธ

# 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

# rbac-tool ํ›„ ํ™•์ธ >> ๊ธฐ์กด ๊ณ„์ •๊ณผ ๋น„๊ตํ•ด๋ณด์ž >> system:authenticated ๋Š” system:masters ์„ค์ • ์‹œ ๋”ฐ๋ผ์˜ค๋Š” ๊ฒƒ ๊ฐ™์€๋ฐ, ์ถ”๊ฐ€ ๋™์ž‘ ์›๋ฆฌ๋Š” ๋ชจ๋ฅด๊ฒ ๋„ค์š”???
kubectl krew install rbac-tool && kubectl rbac-tool whoami
{Username: "testuser",
 UID:      "aws-iam-authenticator:911283464785:AIDA5ILF2FJIV65KG6RBM",
 Groups:   ["system:masters",
            "system:authenticated"],
 Extra:    {accessKeyId:  ["AKIA5ILF2FJIZJUZSG4D"],
            arn:          ["arn:aws:iam::911283464785:user/testuser"],
            canonicalArn: ["arn:aws:iam::911283464785:user/testuser"],
...

5. [myeks-bastion] testuser ์˜ Group ๋ณ€๊ฒฝ(system:masters โ†’ system:authenticated)์œผ๋กœ RBAC ๋™์ž‘ ํ™•์ธ

# ๋ฐฉ์•ˆ2 : ์•„๋ž˜ edit๋กœ mapUsers ๋‚ด์šฉ ์ง์ ‘ ์ˆ˜์ • system:authenticated
kubectl edit cm -n kube-system aws-auth
...

# ํ™•์ธ
eksctl get iamidentitymapping --cluster $CLUSTER_NAME

# ์‹œ๋„
kubectl get node -v6
kubectl api-resources -v5

7. [myeks-bastion]์—์„œ testuser IAM ๋งตํ•‘ ์‚ญ์ œ

# testuser IAM ๋งตํ•‘ ์‚ญ์ œ
eksctl delete iamidentitymapping --cluster $CLUSTER_NAME --arn  arn:aws:iam::$ACCOUNT_ID:user/testuser

# Get IAM identity mapping(s)
eksctl get iamidentitymapping --cluster $CLUSTER_NAME
kubectl get cm -n kube-system aws-auth -o yaml | yh

8. [myeks-bastion-2] testuser kubectl ์‚ฌ์šฉ ํ™•์ธ

# ์‹œ๋„
kubectl get node -v6
kubectl api-resources -v5

9. (์ฐธ๊ณ ) config ์ƒ˜ํ”Œ

# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: v1
data:
  mapRoles: |
    - groups:
      - system:bootstrappers
      - system:nodes
      rolearn: arn:aws:iam::111122223333:role/my-role
      username: system:node:{{EC2PrivateDNSName}}
    - groups:
      - eks-console-dashboard-full-access-group
      rolearn: arn:aws:iam::111122223333:role/my-console-viewer-role
      username: my-console-viewer-role
  mapUsers: |
    - groups:
      - system:masters
      userarn: arn:aws:iam::111122223333:user/admin
      username: admin
    - groups:
      - eks-console-dashboard-restricted-access-group      
      userarn: arn:aws:iam::444455556666:user/my-user
      username: my-user

4. IRSA

  • EC2 Instance Profile : ์‚ฌ์šฉํ•˜๊ธฐ ํŽธํ•˜์ง€๋งŒ, ์ตœ์†Œ ๊ถŒํ•œ ๋ถ€์—ฌ ์›์น™์— ์œ„๋ฐฐํ•˜๋ฉฐ ๋ณด์•ˆ์ƒ ๊ถŒ๊ณ ํ•˜์ง€ ์•Š์Œ - https://malwareanalysis.tistory.com/579 โ†’ IRSA ์‚ฌ์šฉ ๊ถŒ์žฅ
# ์„ค์ • ์˜ˆ์‹œ 1 : eksctl ์‚ฌ์šฉ ์‹œ
eksctl create cluster --name $CLUSTER_NAME ... --external-dns-access --full-ecr-access --asg-access

# ์„ค์ • ์˜ˆ์‹œ 2 : eksctl๋กœ yaml ํŒŒ์ผ๋กœ ๋…ธ๋“œ ์ƒ์„ฑ ์‹œ
cat myeks.yaml | yh
...
managedNodeGroups:
- amiFamily: AmazonLinux2
  iam:
    withAddonPolicies:
      albIngress: false
      appMesh: false
      appMeshPreview: false
      autoScaler: true
      awsLoadBalancerController: false
      certManager: true
      cloudWatch: true
      ebs: false
      efs: false
      externalDNS: true
      fsx: false
      imageBuilder: true
      xRay: false
...

# ์„ค์ • ์˜ˆ์‹œ 3 : ํ…Œ๋ผํผ
...

ํ•„์š” ์ง€์‹ : Service Account Token Volume Projection, Admission Control, JWT(JSON Web Token), OIDC

Service Account Token Volume Projection : '์„œ๋น„์Šค ๊ณ„์ • ํ† ํฐ'์˜ ์‹œํฌ๋ฆฟ ๊ธฐ๋ฐ˜ ๋ณผ๋ฅจ ๋Œ€์‹  'projected volume' ์‚ฌ์šฉ

  • Service Account Token Volume Projection - ๋งํฌ
    • ์„œ๋น„์Šค ๊ณ„์ • ํ† ํฐ์„ ์ด์šฉํ•ด์„œ ์„œ๋น„์Šค์™€ ์„œ๋น„์Šค, ์ฆ‰ ํŒŒ๋“œ(pod)์™€ ํŒŒ๋“œ(pod)์˜ ํ˜ธ์ถœ์—์„œ ์ž๊ฒฉ ์ฆ๋ช…์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์„๊นŒ์š”?
    • ๋ถˆํ–‰ํžˆ๋„ ๊ธฐ๋ณธ ์„œ๋น„์Šค ๊ณ„์ • ํ† ํฐ์œผ๋กœ๋Š” ์‚ฌ์šฉํ•˜๊ธฐ์— ๋ถ€์กฑํ•จ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ํ† ํฐ์„ ์‚ฌ์šฉํ•˜๋Š” ๋Œ€์ƒ(audience), ์œ ํšจ ๊ธฐ๊ฐ„(expiration) ๋“ฑ ํ† ํฐ์˜ ์†์„ฑ์„ ์ง€์ •ํ•  ํ•„์š”๊ฐ€ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.
    • Service Account Token Volume Projectionย ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๋ฉด ์ด๋Ÿฌํ•œ ๋ถ€์กฑํ•œ ์ ๋“ค์„ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - image: nginx
    name: nginx
    volumeMounts:
    - mountPath: /var/run/secrets/tokens
      name: vault-token
  serviceAccountName: build-robot
  volumes:
  - name: vault-token
    projected:
      sources:
      - serviceAccountToken:
          path: vault-token
          expirationSeconds: 7200
          audience: vault

Bound Service Account Token Volume ๋ฐ”์ธ๋”ฉ๋œ ์„œ๋น„์Šค ์–ด์นด์šดํŠธ ํ† ํฐ ๋ณผ๋ฅจ** - ๋งํฌ ์˜์–ด

  • FEATURE STATE:ย Kubernetes v1.22 [stable]
  • ์„œ๋น„์Šค ์–ด์นด์šดํŠธ ์–ด๋“œ๋ฏธ์…˜ ์ปจํŠธ๋กค๋Ÿฌ๋Š” ํ† ํฐ ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ์ƒ์„ฑํ•œ ๋งŒ๋ฃŒ๋˜์ง€ ์•Š์€ ์„œ๋น„์Šค ๊ณ„์ • ํ† ํฐ์— ์‹œํฌ๋ฆฟ ๊ธฐ๋ฐ˜ ๋ณผ๋ฅจ ๋Œ€์‹  ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ”„๋กœ์ ํ‹ฐ๋“œ ๋ณผ๋ฅจ์„ ์ถ”๊ฐ€ํ•จ
    - name: kube-api-access-<random-suffix>
      projected:
        defaultMode: 420 # 420์€ rw- ๋กœ ์†Œ์œ ์ž๋Š” ์ฝ๊ณ ์“ฐ๊ธฐ ๊ถŒํ•œ๊ณผ ๊ทธ๋ฃน๋‚ด ์‚ฌ์šฉ์ž๋Š” ์ฝ๊ธฐ๋งŒ, ๋ณดํ†ต 0644๋Š” ์†Œ์œ ์ž๋Š” ์ฝ๊ณ ์“ฐ๊ณ ์‹คํ–‰ ๊ถŒํ•œ๊ณผ ๋‚˜๋จธ์ง€๋Š” ์ฝ๊ณ ์“ฐ๊ธฐ ๊ถŒํ•œ
        sources:
          - serviceAccountToken:
              expirationSeconds: 3607
              path: token
          - configMap:
              items:
                - key: ca.crt
                  path: ca.crt
              name: kube-root-ca.crt
          - downwardAPI:
              items:
                - fieldRef:
                    apiVersion: v1
                    fieldPath: metadata.namespace
                  path: namespace
    ํ”„๋กœ์ ํ‹ฐ๋“œ ๋ณผ๋ฅจ์€ ์„ธ ๊ฐ€์ง€๋กœ ๊ตฌ์„ฑ๋จ
    1. kube-apiserver๋กœ๋ถ€ํ„ฐ TokenRequest API๋ฅผ ํ†ตํ•ด ์–ป์€ย ์„œ๋น„์Šค์–ด์นด์šดํŠธํ† ํฐ(ServiceAccountToken). ์„œ๋น„์Šค์–ด์นด์šดํŠธํ† ํฐ์€ ๊ธฐ๋ณธ์ ์œผ๋กœ 1์‹œ๊ฐ„ ๋’ค์—, ๋˜๋Š” ํŒŒ๋“œ๊ฐ€ ์‚ญ์ œ๋  ๋•Œ ๋งŒ๋ฃŒ๋œ๋‹ค. ์„œ๋น„์Šค์–ด์นด์šดํŠธํ† ํฐ์€ ํŒŒ๋“œ์— ์—ฐ๊ฒฐ๋˜๋ฉฐ kube-apiserver๋ฅผ ์œ„ํ•ด ์กด์žฌํ•จ
    2. kube-apiserver์— ๋Œ€ํ•œ ์—ฐ๊ฒฐ์„ ํ™•์ธํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋˜๋Š” CA ๋ฒˆ๋“ค์„ ํฌํ•จํ•˜๋Š”ย ์ปจํ”ผ๊ทธ๋งต(ConfigMap).
    3. ํŒŒ๋“œ์˜ ๋„ค์ž„์ŠคํŽ˜์ด์Šค๋ฅผ ์ฐธ์กฐํ•˜๋Š”ย DownwardA
  • Configure a Pod to Use a Projected Volume for Storage : ์‹œํฌ๋ฆฟ ์ปจํ”ผ๊ทธ๋งต downwardAPI serviceAccountToken์˜ ๋ณผ๋ฅจ ๋งˆ์šดํŠธ๋ฅผ ํ•˜๋‚˜์˜ ๋””๋ ‰ํ„ฐ๋ฆฌ์— ํ†ตํ•ฉ - ๋งํฌ
    • This page shows how to use aย [projected](https://kubernetes.io/docs/concepts/storage/volumes/#projected)ย Volume to mount several existing volume sources into the same directory. Currently,ย secret,ย configMap,ย downwardAPI, andย serviceAccountTokenย volumes can be projected.

    • Note:ย serviceAccountTokenย is not a volume type.

      apiVersion: v1
      kind: Pod
      metadata:
        name: test-projected-volume
      spec:
        containers:
        - name: test-projected-volume
          image: busybox:1.28
          args:
          - sleep
          - "86400"
          volumeMounts:
          - name: all-in-one
            mountPath: "/projected-volume"
            readOnly: true
        volumes:
        - name: all-in-one
          **projected**:
            sources:
            - secret:
                name: user
            - secret:
                name: pass
      # Create the Secrets:
      ## Create files containing the username and password:
      echo -n "admin" > ./username.txt
      echo -n "1f2d1e2e67df" > ./password.txt
      
      ## Package these files into secrets:
      kubectl create secret generic user --from-file=./username.txt
      kubectl create secret generic pass --from-file=./password.txt
      
      # ํŒŒ๋“œ ์ƒ์„ฑ
      kubectl apply -f https://k8s.io/examples/pods/storage/projected.yaml
      
      # ํŒŒ๋“œ ํ™•์ธ
      kubectl get pod test-projected-volume -o yaml | kubectl neat | yh
      ...
      volumes:
        - name: all-in-one
          **projected**:
            defaultMode: 420
            sources:
            - secret:
                name: user
            - secret:
                name: pass
        - name: kube-api-access-n6n9v
          **projected**:
            defaultMode: 420
            sources:
            - serviceAccountToken:
                expirationSeconds: 3607
                path: token
            - configMap:
                items:
                - key: ca.crt
                  path: ca.crt
                name: kube-root-ca.crt
            - downwardAPI:
                items:
                - fieldRef:
                    apiVersion: v1
                    fieldPath: metadata.namespace
                  path: namespace
      
      # ์‹œํฌ๋ฆฟ ํ™•์ธ
      kubectl exec -it test-projected-volume -- ls /projected-volume/
      ***password.txt  username.txt***
      
      kubectl exec -it test-projected-volume -- cat /projected-volume/username.txt ;echo
      ***admin***
      
      kubectl exec -it test-projected-volume -- cat /projected-volume/password.txt ;echo
      ***1f2d1e2e67df***
      
      # ์‚ญ์ œ
      kubectl delete pod test-projected-volume && kubectl delete secret user pass

k8s api ์ ‘๊ทผ ๋‹จ๊ณ„

  • AuthN โ†’ AuthZ โ†’ Admisstion Control ๊ถŒํ•œ์ด ์žˆ๋Š” ์‚ฌ์šฉ์ž์— ํ•œํ•ด์„œ ๊ด€๋ฆฌ์ž(Admin)๊ฐ€ ํŠน์ • ํ–‰๋™์„ ์ œํ•œ(validate) ํ˜น์€ ๋ณ€๊ฒฝ(mutate) - ๋งํฌ Slack
  • AuthN & AuthZ - MutatingWebhook - Object schema validation - ValidatingWebhook โ†’ etcd

์ถœ์ฒ˜: https://kubernetes.io/blog/2019/03/21/a-guide-to-kubernetes-admission-controllers/

  • Admission Control๋„ Webhook์œผ๋กœ ์‚ฌ์šฉ์ž์—๊ฒŒ API๊ฐ€ ์—ด๋ ค์žˆ๊ณ , ์‚ฌ์šฉ์ž๋Š” ์ž์‹ ๋งŒ์˜ Admission Controller๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ,
    ์ด๋ฅผ Dynamic Admission Controller๋ผ๊ณ  ๋ถ€๋ฅด๊ณ , ํฌ๊ฒŒ MutatingWebhook ๊ณผ ValidatingWebhook ๋กœ ๋‚˜๋‰จ
  • MutatingWebhook์€ ์‚ฌ์šฉ์ž๊ฐ€ ์š”์ฒญํ•œ request์— ๋Œ€ํ•ด์„œ ๊ด€๋ฆฌ์ž๊ฐ€ ์ž„์˜๋กœ ๊ฐ’์„ ๋ณ€๊ฒฝํ•˜๋Š” ์ž‘์—…
  • ValidatingWebhook์€ ์‚ฌ์šฉ์ž๊ฐ€ ์š”์ฒญํ•œ request์— ๋Œ€ํ•ด์„œ ๊ด€๋ฆฌ์ž๊ธฐ ํ—ˆ์šฉ์„ ๋ง‰๋Š” ์ž‘์—…
kubectl get **validatingwebhook**configurations
kubectl get **mutatingwebhook**configurations

JWT : Bearer type - JWT(JSON Web Token) X.509 Certificate์˜ lightweight JSON ๋ฒ„์ „

  • Bearer type ๊ฒฝ์šฐ, ์„œ๋ฒ„์—์„œ ์ง€์ •ํ•œ ์–ด๋– ํ•œ ๋ฌธ์ž์—ด๋„ ์ž…๋ ฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๊ต‰์žฅํžˆ ํ—ˆ์ˆ ํ•œ ๋Š๋‚Œ์„ ๋ฐ›์Šต๋‹ˆ๋‹ค.
  • ์ด๋ฅผ ๋ณด์™„ํ•˜๊ณ ์ž ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค์—์„œ Bearer ํ† ํฐ์„ ์ „์†กํ•  ๋•Œ ์ฃผ๋กœ JWT (JSON Web Token) ํ† ํฐ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • JWT๋Š” X.509 Certificate์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ private key๋ฅผ ์ด์šฉํ•˜์—ฌ ํ† ํฐ์„ ์„œ๋ช…ํ•˜๊ณ  public key๋ฅผ ์ด์šฉํ•˜์—ฌ ์„œ๋ช…๋œ ๋ฉ”์„ธ์ง€๋ฅผ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค.
  • ์ด๋Ÿฌํ•œ ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ํ†ตํ•ด ํ•ด๋‹น ํ† ํฐ์ด ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค๋ฅผ ํ†ตํ•ด ์ƒ์„ฑ๋œ validํ•œ ํ† ํฐ์ž„์„ ์ธ์ฆํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • X.509 Certificate์˜ lightweight JSON ๋ฒ„์ „์ด๋ผ๊ณ  ์ƒ๊ฐํ•˜๋ฉด ํŽธ๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
  • jwt๋Š” JSON ํ˜•ํƒœ๋กœ ํ† ํฐ ํ˜•์‹์„ ์ •์˜ํ•œ ์ŠคํŽ™์ž…๋‹ˆ๋‹ค. jwt๋Š” ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค์—์„œ ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ๋‹ค์–‘ํ•œ ์›น ์‚ฌ์ดํŠธ์—์„œ ์ธ์ฆ, ๊ถŒํ•œ ํ—ˆ๊ฐ€, ์„ธ์…˜๊ด€๋ฆฌ ๋“ฑ์˜ ๋ชฉ์ ์œผ๋กœ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
    • Header: ํ† ํฐ ํ˜•์‹์™€ ์•”ํ˜ธํ™” ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค.
    • Payload: ์ „์†กํ•˜๋ ค๋Š” ๋ฐ์ดํ„ฐ๋ฅผ JSON ํ˜•์‹์œผ๋กœ ๊ธฐ์ž…ํ•ฉ๋‹ˆ๋‹ค.
    • Signature: Header์™€ Payload์˜ ๋ณ€์กฐ ๊ฐ€๋Šฅ์„ฑ์„ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค.
  • ๊ฐ ํŒŒํŠธ๋Š” base64 URL ์ธ์ฝ”๋”ฉ์ด ๋˜์–ด์„œย .์œผ๋กœ ํ•ฉ์ณ์ง€๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

OIDC : ์‚ฌ์šฉ์ž๋ฅผ ์ธ์ฆํ•ด ์‚ฌ์šฉ์ž์—๊ฒŒ ์•ก์„ธ์Šค ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ํ”„๋กœํ† ์ฝœ โ‡’ [์ปคํ”ผ๊ณ ๋ž˜]๋‹˜ ๋ธ”๋กœ๊ทธ OpenID Connect - ๋งํฌ

  • OAuth 2.0 : ๊ถŒํ•œํ—ˆ๊ฐ€ ์ฒ˜๋ฆฌ ํ”„๋กœํ† ์ฝœ, ๋‹ค๋ฅธ ์„œ๋น„์Šค์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š” ๊ถŒํ•œ์„ ํš๋“ํ•˜๊ฑฐ๋‚˜ ๋ฐ˜๋Œ€๋กœ ๋‹ค๋ฅธ ์„œ๋น„์Šค์—๊ฒŒ ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•  ์ˆ˜ ์žˆ์Œ - ์ƒํ™œ์ฝ”๋”ฉ
    • ์œ„์ž„ ๊ถŒํ•œ ๋ถ€์—ฌ Delegated Authorization, ์‚ฌ์šฉ์ž ์ธ์ฆ ๋ณด๋‹ค๋Š” ์ œํ•œ๋œ ์‚ฌ๋žŒ์—๊ฒŒ(ํ˜น์€ ์‹œ์Šคํ…œ) ์ œํ•œ๋œ ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•˜๋Š”๊ฐ€, ์˜ˆ) ํŽ˜์ด์Šค๋ถ posting ๊ถŒํ•œ
    • Access Token : ๋ฐœ๊ธ‰์ฒ˜(OAuth 2.0), ์„œ๋ฒ„์˜ ๋ฆฌ์†Œ์Šค ์ ‘๊ทผ ๊ถŒํ•œ
  • OpenID : ๋น„์˜๋ฆฌ๊ธฐ๊ด€์ธ OpenID Foundation์—์„œ ์ถ”์ง„ํ•˜๋Š” ๊ฐœ๋ฐฉํ˜• ํ‘œ์ค€ ๋ฐ ๋ถ„์‚ฐ ์ธ์ฆ Authentication ํ”„๋กœํ† ์ฝœ, ์‚ฌ์šฉ์ž ์ธ์ฆ ๋ฐ ์‚ฌ์šฉ์ž ์ •๋ณด ์ œ๊ณต(id token) - ๋งํฌ
    • ID Token : ๋ฐœ๊ธ‰์ฒ˜(OpenID Connect), ์œ ์ € ํ”„๋กœํ•„ ์ •๋ณด ํš๋“
  • OIDC OpenID Connect = OpenID ์ธ์ฆ + OAuth2.0 ์ธ๊ฐ€, JSON ํฌ๋งท์„ ์ด์šฉํ•œ RESful API ํ˜•์‹์œผ๋กœ ์ธ์ฆ - ๋งํฌ
    • iss: ํ† ํฐ ๋ฐœํ–‰์ž
    • sub: ์‚ฌ์šฉ์ž๋ฅผ ๊ตฌ๋ถ„ํ•˜๊ธฐ ์œ„ํ•œ ์œ ๋‹ˆํฌํ•œ ๊ตฌ๋ถ„์ž
    • email: ์‚ฌ์šฉ์ž์˜ ์ด๋ฉ”์ผ
    • iat: ํ† ํฐ์ด ๋ฐœํ–‰๋˜๋Š” ์‹œ๊ฐ„์„ Unix time์œผ๋กœ ํ‘œ๊ธฐํ•œ ๊ฒƒ
    • exp: ํ† ํฐ์ด ๋งŒ๋ฃŒ๋˜๋Š” ์‹œ๊ฐ„์„ Unix time์œผ๋กœ ํ‘œ๊ธฐํ•œ ๊ฒƒ
    • aud: ID Token์ด ์–ด๋–ค Client๋ฅผ ์œ„ํ•ด ๋ฐœ๊ธ‰๋œ ๊ฒƒ์ธ์ง€.
  • IdP Open Identify Provider : ๊ตฌ๊ธ€, ์นด์นด์˜ค์™€ ๊ฐ™์ด OpenID ์„œ๋น„์Šค๋ฅผ ์ œ๊ณตํ•˜๋Š” ์‹ ์› ์ œ๊ณต์ž.
    • OpenID Connect์—์„œ IdP์˜ ์—ญํ• ์„ OAuth๊ฐ€ ์ˆ˜ํ–‰ - ๋งํฌ
  • RP Relying Party : ์‚ฌ์šฉ์ž๋ฅผ ์ธ์ฆํ•˜๊ธฐ ์œ„ํ•ด IdP์— ์˜์กดํ•˜๋Š” ์ฃผ์ฒด

๐Ÿ‘‰ IRSA ์†Œ๊ฐœ

  • ํŒŒ๋“œ๊ฐ€ ํŠน์ • IAM ์—ญํ• ๋กœ Assume ํ• ๋•Œ ํ† ํฐ์„ AWS์— ์ „์†กํ•˜๊ณ , AWS๋Š” ํ† ํฐ๊ณผ EKS IdP๋ฅผ ํ†ตํ•ด ํ•ด๋‹น IAM ์—ญํ• ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ๊ฒ€์ฆ
  • The IAM service uses these public keys to validate the token. The workflow is as follows - JWT(JSON Web Token), JWKS(JSON Web Key Set)

    ์ถœ์ฒ˜ : https://aws.amazon.com/ko/blogs/containers/diving-into-iam-roles-for-service-accounts/

    ์ถœ์ฒ˜ : https://awskoreamarketingasset.s3.amazonaws.com/2022 Summit/pdf/T10S1_EKS ํ™˜๊ฒฝ์„ ๋” ํšจ์œจ์ ์œผ๋กœ ๋” ์•ˆ์ „ํ•˜๊ฒŒ.pdf
  • AWS SDK๋Š” AWS_ROLE_ARN ๋ฐ AWS_WEB_IDENTITY_TOKEN_FILE ์ด๋ฆ„์˜ ํ™˜๊ฒฝ๋ณ€์ˆ˜๋ฅผ ์ฝ์–ด๋“ค์—ฌ Web Identity ํ† ํฐ์œผ๋กœ AssumeRoleWithWebIdentify๋ฅผ ํ˜ธ์ถœํ•จ์œผ๋กœ์จ Assume Role์„ ์‹œ๋„ํ•˜์—ฌ ์ž„์‹œ ์ž๊ฒฉ ์ฆ๋ช…์„ ํš๋“ํ•˜๊ณ , ํŠน์ • IAM Role ์—ญํ• ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
  • ์ด๋•Œ Assume Role ๋™์ž‘์„ ์œ„ํ•œ ์ธ์ฆ์€ AWS๊ฐ€ ์•„๋‹Œ ์™ธ๋ถ€ Web IdP(EKS IdP)์— ์œ„์ž„ํ•˜์—ฌ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

    ์ถœ์ฒ˜ : https://tech.devsisters.com/posts/pod-iam-role/

์‹ค์Šต 1

# ํŒŒ๋“œ1 ์ƒ์„ฑ
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: eks-iam-test1
spec:
  containers:
    - name: my-aws-cli
      image: amazon/aws-cli:latest
      args: ['s3', 'ls']
  restartPolicy: Never
  automountServiceAccountToken: false
EOF

# ํ™•์ธ
kubectl get pod
kubectl describe pod

# ๋กœ๊ทธ ํ™•์ธ
kubectl logs eks-iam-test1

  • cloudtrail ํ™•์ธ
# ํŒŒ๋“œ1 ์‚ญ์ œ
kubectl delete pod eks-iam-test1

์‹ค์Šต 2

Kubernetes Service Accounts : https://jwt.io/

  • Kubernetes Pods are given an identity through a Kubernetes concept called aย Kubernetes Service Account.
  • When a Service Account is created, a JWT token is automatically created as a Kubernetes Secret.
  • This Secret can then be mounted into Pods and used by that Service Account to authenticate to the Kubernetes API Server.
# ํŒŒ๋“œ2 ์ƒ์„ฑ
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: eks-iam-test2
spec:
  containers:
    - name: my-aws-cli
      image: amazon/aws-cli:latest
      command: ['sleep', '36000']
  restartPolicy: Never
EOF

# ํ™•์ธ
kubectl get pod
kubectl describe pod

# aws ์„œ๋น„์Šค ์‚ฌ์šฉ ์‹œ๋„
kubectl exec -it eks-iam-test2 -- aws s3 ls

# ์„œ๋น„์Šค ์–ด์นด์šดํŠธ ํ† ํฐ ํ™•์ธ
SA_TOKEN=$(kubectl exec -it eks-iam-test2 -- cat /var/run/secrets/kubernetes.io/serviceaccount/token)
echo $SA_TOKEN
# jwt ํ˜น์€ ์•„๋ž˜ JWT ์›น ์‚ฌ์ดํŠธ ์ด์šฉ
jwt decode $SA_TOKEN --json --iso8601
...

#ํ—ค๋”
{
  "alg": "RS256",
  "kid": "1a8fcaee12b3a8f191327b5e9b997487ae93baab"
}

# ํŽ˜์ด๋กœ๋“œ : OAuth2์—์„œ ์“ฐ์ด๋Š” aud, exp ์†์„ฑ ํ™•์ธ! > projectedServiceAccountToken ๊ธฐ๋Šฅ์œผ๋กœ ํ† ํฐ์— audience,exp ํ•ญ๋ชฉ์„ ๋ง๋ถ™ํž˜
## iss ์†์„ฑ : EKS OpenID Connect Provider(EKS IdP) ์ฃผ์†Œ > ์ด EKS IdP๋ฅผ ํ†ตํ•ด ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค๊ฐ€ ๋ฐœ๊ธ‰ํ•œ ํ† ํฐ์ด ์œ ์š”ํ•œ์ง€ ๊ฒ€์ฆ
{
  "aud": [
    "https://kubernetes.default.svc"  # ํ•ด๋‹น ์ฃผ์†Œ๋Š” k8s api์˜ ClusterIP ์„œ๋น„์Šค ์ฃผ์†Œ ๋„๋ฉ”์ธ๋ช…, kubectl get svc kubernetes
  ],
  "exp": 1716619848,
  "iat": 1685083848,
  "iss": "https://oidc.eks.ap-northeast-2.amazonaws.com/id/F6A7523462E8E6CDADEE5D41DF2E71F6",
  "kubernetes.io": {
    "namespace": "default",
    "pod": {
      "name": "eks-iam-test2",
      "uid": "10dcccc8-a16c-4fc7-9663-13c9448e107a"
    },
    "serviceaccount": {
      "name": "default",
      "uid": "acb6c60d-0c5f-4583-b83b-1b629b0bdd87"
    },
    "warnafter": 1685087455
  },
  "nbf": 1685083848,
  "sub": "system:serviceaccount:default:default"
}

# ํŒŒ๋“œ2 ์‚ญ์ œ
kubectl delete pod eks-iam-test2

์‹ค์Šต 3

amazon-eks-pod-identity-webhook: This webhook is for mutating pods that will require AWS IAM access

  • For the webhook to inject a new Token into our Pod, we are going to create a new Kubernetes Service Account, annotate our Service Account with an AWS IAM role ARN, and then reference this new Kubernetes Service Account in a Kubernetes Pod. The eksctl tool can be used to automate a few steps for us, but all of these steps can also be done manually.
  • Theย eksctl create iamserviceaccountย command creates:
    1. A Kubernetes Service Account
    2. An IAM role with the specified IAM policy
    3. A trust policy on that IAM role
  • Finally, it will also annotate the Kubernetes Service Account with the IAM Role Arn created.
# Create an iamserviceaccount - AWS IAM role bound to a Kubernetes service account
eksctl create iamserviceaccount \
  --name my-sa \
  --namespace default \
  --cluster $CLUSTER_NAME \
  --approve \
  --attach-policy-arn $(aws iam list-policies --query 'Policies[?PolicyName==`AmazonS3ReadOnlyAccess`].Arn' --output text)

# ํ™•์ธ >> ์›น ๊ด€๋ฆฌ ์ฝ˜์†”์—์„œ CloudFormation Stack >> IAM Role ํ™•์ธ
# aws-load-balancer-controller IRSA๋Š” ์–ด๋–ค ๋™์ž‘์„ ์ˆ˜ํ–‰ํ•  ๊ฒƒ ์ธ์ง€ ์ƒ๊ฐํ•ด๋ณด์ž!
eksctl get iamserviceaccount --cluster $CLUSTER_NAME


# Inspecting the newly created Kubernetes Service Account, we can see the role we want it to assume in our pod.
kubectl get sa
kubectl describe sa my-sa
Name:                my-sa
Namespace:           default
Labels:              app.kubernetes.io/managed-by=eksctl
Annotations:         eks.amazonaws.com/role-arn: arn:aws:iam::911283464785:role/eksctl-myeks-addon-iamserviceaccount-default-Role1-1MJUYW59O6QGH
Image pull secrets:  <none>
Mountable secrets:   <none>
Tokens:              <none>
Events:              <none>

# ํŒŒ๋“œ3๋ฒˆ ์ƒ์„ฑ
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: eks-iam-test3
spec:
  serviceAccountName: my-sa
  containers:
    - name: my-aws-cli
      image: amazon/aws-cli:latest
      command: ['sleep', '36000']
  restartPolicy: Never
EOF

# ํ•ด๋‹น SA๋ฅผ ํŒŒ๋“œ๊ฐ€ ์‚ฌ์šฉ ์‹œ mutatingwebhook์œผ๋กœ Env,Volume ์ถ”๊ฐ€ํ•จ
kubectl get mutatingwebhookconfigurations pod-identity-webhook -o yaml | kubectl neat | yh


# ํŒŒ๋“œ ์ƒ์„ฑ yaml์— ์—†๋˜ ๋‚ด์šฉ์ด ์ถ”๊ฐ€๋จ!!!!!
# Pod Identity Webhook์€ mutating webhook์„ ํ†ตํ•ด ์•„๋ž˜ Env ๋‚ด์šฉ๊ณผ 1๊ฐœ์˜ ๋ณผ๋ฅจ์„ ์ถ”๊ฐ€ํ•จ
kubectl get pod eks-iam-test3
kubectl describe pod eks-iam-test3
...
Environment:
      AWS_STS_REGIONAL_ENDPOINTS:   regional
      AWS_DEFAULT_REGION:           ap-northeast-2
      AWS_REGION:                   ap-northeast-2
      AWS_ROLE_ARN:                 arn:aws:iam::911283464785:role/eksctl-myeks-addon-iamserviceaccount-default-Role1-GE2DZKJYWCEN
      AWS_WEB_IDENTITY_TOKEN_FILE:  /var/run/secrets/eks.amazonaws.com/serviceaccount/token
    Mounts:
      /var/run/secrets/eks.amazonaws.com/serviceaccount from aws-iam-token (ro)
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-69rh8 (ro)
...
Volumes:
  aws-iam-token:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  86400
  kube-api-access-sn467:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    ConfigMapOptional:       <nil>
    DownwardAPI:             true
...

# ํŒŒ๋“œ์—์„œ aws cli ์‚ฌ์šฉ ํ™•์ธ
eksctl get iamserviceaccount --cluster $CLUSTER_NAME
kubectl exec -it eks-iam-test3 -- aws sts get-caller-identity --query Arn
"arn:aws:sts::911283464785:assumed-role/eksctl-myeks-addon-iamserviceaccount-default-Role1-GE2DZKJYWCEN/botocore-session-1685179271"

# ๋˜๋Š” ๊ฒƒ๊ณ  ์•ˆ๋˜๋Š” ๊ฒƒ์€ ์™œ๊ทธ๋Ÿฐ๊ฐ€?
kubectl exec -it eks-iam-test3 -- aws s3 ls
kubectl exec -it eks-iam-test3 -- aws ec2 describe-instances --region ap-northeast-2
kubectl exec -it eks-iam-test3 -- aws ec2 describe-vpcs --region ap-northeast-2

  • If we inspect the Pod using Kubectl and jq, we can see there are now two volumes mounted into our Pod.
    The second one has been mounted via thatย mutating webhook.
    Theย aws-iam-tokenย is still being generated by the Kubernetes API Server, but with a new OIDC JWT audience.
# ํŒŒ๋“œ์— ๋ณผ๋ฅจ ๋งˆ์šดํŠธ 2๊ฐœ ํ™•์ธ
kubectl get pod eks-iam-test3 -o json | jq -r '.spec.containers | .[].volumeMounts'
[
  {
    "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount",
    "name": "kube-api-access-sn467",
    "readOnly": true
  },
  {
    "mountPath": "/var/run/secrets/eks.amazonaws.com/serviceaccount",
    "name": "aws-iam-token",
    "readOnly": true
  }
]

# aws-iam-token ๋ณผ๋ฅจ ์ •๋ณด ํ™•์ธ : JWT ํ† ํฐ์ด ๋‹ด๊ฒจ์ ธ์žˆ๊ณ , exp, aud ์†์„ฑ์ด ์ถ”๊ฐ€๋˜์–ด ์žˆ์Œ
kubectl get pod eks-iam-test3 -o json | jq -r '.spec.volumes[] | select(.name=="aws-iam-token")'
{
  "name": "aws-iam-token",
  "projected": {
    "defaultMode": 420,
    "sources": [
      {
        "serviceAccountToken": {
          "audience": "sts.amazonaws.com",
          "expirationSeconds": 86400,
          "path": "token"
        }
      }
    ]
  }
}

# api ๋ฆฌ์†Œ์Šค ํ™•์ธ
kubectl api-resources |grep hook
mutatingwebhookconfigurations                  admissionregistration.k8s.io/v1        false        MutatingWebhookConfiguration
validatingwebhookconfigurations                admissionregistration.k8s.io/v1        false        ValidatingWebhookConfiguration

#
kubectl explain mutatingwebhookconfigurations

#
kubectl get MutatingWebhookConfiguration
NAME                            WEBHOOKS   AGE
pod-identity-webhook            1          147m
vpc-resource-mutating-webhook   1          147m

# pod-identity-webhook ํ™•์ธ
kubectl describe MutatingWebhookConfiguration pod-identity-webhook 
kubectl get MutatingWebhookConfiguration pod-identity-webhook -o yaml | yh

IRSA๋ฅผ ๊ฐ€์žฅ ์ทจ์•ฝํ•˜๊ฒŒ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ• : ์ •๋ณด ํƒˆ์ทจ ์‹œ ํ‚ค/ํ† ํฐ ๋ฐœ๊ธ‰ ์•ฝ์šฉ ๊ฐ€๋Šฅ

  • If we exec into the running Pod and inspect this token, we can see that it looks slightly different from the previous SA Token.
  • You can see that the intended audience for this token is nowย sts.amazonaws.com, the issuer who has created and signed this token is still our OIDC provider, and finally, the expiration of the token is much shorter at 24 hours. We can modify the expiration duration for the service account usingย eks.amazonaws.com/token-expirationย annotation in our Pod definition or Service Account definition.
  • The mutating webhook does more than just mount an additional token into the Pod. The mutating webhook also injects environment variables.

https://jwt.io/

# AWS_WEB_IDENTITY_TOKEN_FILE ํ™•์ธ
IAM_TOKEN=$(kubectl exec -it eks-iam-test3 -- cat /var/run/secrets/eks.amazonaws.com/serviceaccount/token)
echo $IAM_TOKEN

# JWT ์›น ํ™•์ธ 
{
  "aud": [
    "sts.amazonaws.com"
  ],
  "exp": 1685175662,
  "iat": 1685089262,
  "iss": "https://oidc.eks.ap-northeast-2.amazonaws.com/id/F6A7523462E8E6CDADEE5D41DF2E71F6",
  "kubernetes.io": {
    "namespace": "default",
    "pod": {
      "name": "eks-iam-test3",
      "uid": "73f66936-4d66-477a-b32b-853f7a1c22d9"
    },
    "serviceaccount": {
      "name": "my-sa",
      "uid": "3b31aa85-2718-45ed-8c1c-75ed012c1a68"
    }
  },
  "nbf": 1685089262,
  "sub": "system:serviceaccount:default:my-sa"
}

# env ๋ณ€์ˆ˜ ํ™•์ธ
kubectl get pod eks-iam-test3 -o json | jq -r '.spec.containers | .[].env'
[
  {
    "name": "AWS_STS_REGIONAL_ENDPOINTS",
    "value": "regional"
  },
  {
    "name": "AWS_DEFAULT_REGION",
    "value": "ap-northeast-2"
  },
  {
    "name": "AWS_REGION",
    "value": "ap-northeast-2"
  },
  {
    "name": "AWS_ROLE_ARN",
    "value": "arn:aws:iam::911283464785:role/eksctl-myeks-addon-iamserviceaccount-default-Role1-1MJUYW59O6QGH"
  },
  {
    "name": "AWS_WEB_IDENTITY_TOKEN_FILE",
    "value": "/var/run/secrets/eks.amazonaws.com/serviceaccount/token"
  }
]

  • Now that our workload has a token it can use to attempt to authenticate with IAM, the next part is getting AWS IAM to trust these tokens.ย AWS IAM supportsย federated identities using OIDC identity providers. This feature allows IAM to authenticate AWS API calls with supported identity providers after receiving a valid OIDC JWT. This token can then be passed to AWS STSย AssumeRoleWithWebIdentityย API operation to get temporary IAM credentials.
  • The OIDC JWT token we have in our Kubernetes workload is cryptographically signed, and IAM should trust and validate these tokens before the AWS STSย AssumeRoleWithWebIdentityย API operation can send the temporary credentials. As part of theย Service Account Issuer Discoveryย feature of Kubernetes, EKS is hosting a public OpenID provider configuration document (Discovery endpoint) and the public keys to validate the token signature (JSON Web Key Sets โ€“ JWKS) atย https://OIDC_PROVIDER_URL/.well-known/openid-configuration.
# Letโ€™s take a look at this endpoint. We can use the aws eks describe-cluster command to get the OIDC Provider URL.
IDP=$(aws eks describe-cluster --name myeks --query cluster.identity.oidc.issuer --output text)

# Reach the Discovery Endpoint
curl -s $IDP/.well-known/openid-configuration | jq -r '.'

# In the above output, you can see the jwks (JSON Web Key set) field, which contains the set of keys containing the public keys used to verify JWT (JSON Web Token). 
# Refer to the documentation to get details about the JWKS properties.
curl -s $IDP/keys | jq -r '.'

  • ์‹ค์Šต ํ™•์ธ ํ›„ ํŒŒ๋“œ ์‚ญ์ œ: kubectl delete pod eks-iam-test3
profile
๋ชจ๋ฅด๋ฉด ๋ฐฐ์šฐ๋ฉด ๋œ๋‹ค!

0๊ฐœ์˜ ๋Œ“๊ธ€