⭐ 가시다(gasida) 님이 진행하는 AWS EKS Workshop 실습 스터디 게시글입니다.
게시글 상 소스코드, 사진에서 **굵게** 혹은 '''코드쉘''' 에 대한 부분이 들어가있을수도 있습니다 ⭐

이번 게시글은 EKS의 인증/인가 에 관한 내용을 설명합니다.

 

 

 

 

0. 스터디 시작 전, 알아야 하는 내용

기존 실습과의 다른 점

  • 과거의 게시글 : bastion 서버 가 1개인 상태
  • 이번 게시글 : bastion 서버 가 2개인 상태! 유저/권한 에 따라 일부 활동이 제한되는 상황을 가정함
  • → Devops 계정이 2개인 상황을 가정하여 실습진행

사전 배포

Domain : AWS Route 53 Hosting 사용

#domain 호스팅 주소 
jjongguet.com 

실습 환경 배포

# YAML 파일 다운로드
curl -O https://s3.ap-northeast-2.amazonaws.com/cloudformation.cloudneta.net/K8S/**eks-oneclick5.yaml**

# CloudFormation 스택 배포
aws cloudformation deploy --template-file **eks-oneclick5.yaml** --stack-name **myeks** --parameter-overrides KeyName=jjongkey SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32  MyIamUserAccessKeyID=AKX... MyIamUserSecretAccessKey=rn4l... ClusterBaseName=**myeks** --region ap-northeast-2

# CloudFormation 스택 배포 완료 후 작업용 EC2 IP 출력
aws cloudformation describe-stacks --stack-name **myeks** --query 'Stacks[*].**Outputs[0]**.OutputValue' --output text

# 작업용 EC2 SSH 접속
ssh -i jjongkey.pem **root**@$(aws cloudformation describe-stacks --stack-name **myeks** --query 'Stacks[*].Outputs[0].OutputValue' --output text)

기본 설정

# default 네임스페이스 적용
kubectl ns default

# 노드 정보 확인 : t3.medium
kubectl get node --label-columns=node.kubernetes.io/instance-type,eks.amazonaws.com/capacityType,topology.kubernetes.io/zone

# ExternalDNS
MyDomain=jjongguet.com
echo "export MyDomain=jjongguet.com" >> /etc/profile

MyDnzHostedZoneId=$(aws route53 list-hosted-zones-by-name --dns-name "${MyDomain}." --query "HostedZones[0].Id" --output text)
echo $MyDomain, $MyDnzHostedZoneId
curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/aews/externaldns.yaml
MyDomain=$MyDomain MyDnzHostedZoneId=$MyDnzHostedZoneId envsubst < externaldns.yaml | kubectl apply -f -

# kube-ops-view
helm repo add geek-cookbook https://geek-cookbook.github.io/charts/
helm install kube-ops-view geek-cookbook/kube-ops-view --version 1.2.2 --set env.TZ="Asia/Seoul" --namespace kube-system
kubectl patch svc -n kube-system kube-ops-view -p '{"spec":{"type":"LoadBalancer"}}'
kubectl annotate service kube-ops-view -n kube-system "external-dns.alpha.kubernetes.io/hostname=kubeopsview.$MyDomain"
echo -e "Kube Ops View URL = http://kubeopsview.$MyDomain:8080/#scale=1.5"

# AWS LB Controller
helm repo add eks https://aws.github.io/eks-charts
helm repo update
helm install aws-load-balancer-controller eks/aws-load-balancer-controller -n kube-system --set clusterName=$CLUSTER_NAME \
  --set serviceAccount.create=false --set serviceAccount.name=aws-load-balancer-controller

# gp3 스토리지 클래스 생성
kubectl apply -f https://raw.githubusercontent.com/gasida/PKOS/main/aews/gp3-sc.yaml

# 노드 보안그룹 ID 확인
NGSGID=$(aws ec2 describe-security-groups --filters Name=group-name,Values=*ng1* --query "SecurityGroups[*].[GroupId]" --output text)
aws ec2 authorize-security-group-ingress --group-id $NGSGID --protocol '-1' --cidr 192.168.1.100/32

기본 설정

# default 네임스페이스 적용
kubectl ns default

# 노드 정보 확인 : t3.medium
kubectl get node --label-columns=node.kubernetes.io/instance-type,eks.amazonaws.com/capacityType,topology.kubernetes.io/zone

# ExternalDNS
MyDomain=jjougguet.com
echo "export MyDomain=<자신의 도메인>" >> /etc/profile

MyDnzHostedZoneId=$(aws route53 list-hosted-zones-by-name --dns-name "${MyDomain}." --query "HostedZones[0].Id" --output text)
echo $MyDomain, $MyDnzHostedZoneId
curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/aews/externaldns.yaml
MyDomain=$MyDomain MyDnzHostedZoneId=$MyDnzHostedZoneId envsubst < externaldns.yaml | kubectl apply -f -

# kube-ops-view
helm repo add geek-cookbook https://geek-cookbook.github.io/charts/
helm install kube-ops-view geek-cookbook/kube-ops-view --version 1.2.2 --set env.TZ="Asia/Seoul" --namespace kube-system
kubectl patch svc -n kube-system kube-ops-view -p '{"spec":{"type":"LoadBalancer"}}'
kubectl annotate service kube-ops-view -n kube-system "external-dns.alpha.kubernetes.io/hostname=kubeopsview.$MyDomain"
echo -e "Kube Ops View URL = http://kubeopsview.$MyDomain:8080/#scale=1.5"

# AWS LB Controller
helm repo add eks https://aws.github.io/eks-charts
helm repo update
helm install aws-load-balancer-controller eks/aws-load-balancer-controller -n kube-system --set clusterName=$CLUSTER_NAME \
  --set serviceAccount.create=false --set serviceAccount.name=aws-load-balancer-controller

# gp3 스토리지 클래스 생성
kubectl apply -f https://raw.githubusercontent.com/gasida/PKOS/main/aews/gp3-sc.yaml

# 노드 IP 확인 및 PrivateIP 변수 지정
N1=$(kubectl get node --label-columns=topology.kubernetes.io/zone --selector=topology.kubernetes.io/zone=ap-northeast-2a -o jsonpath={.items[0].status.addresses[0].address})
N2=$(kubectl get node --label-columns=topology.kubernetes.io/zone --selector=topology.kubernetes.io/zone=ap-northeast-2b -o jsonpath={.items[0].status.addresses[0].address})
N3=$(kubectl get node --label-columns=topology.kubernetes.io/zone --selector=topology.kubernetes.io/zone=ap-northeast-2c -o jsonpath={.items[0].status.addresses[0].address})
echo "export N1=$N1" >> /etc/profile
echo "export N2=$N2" >> /etc/profile
echo "export N3=$N3" >> /etc/profile
echo $N1, $N2, $N3

# 노드 보안그룹 ID 확인
NGSGID=$(aws ec2 describe-security-groups --filters Name=group-name,Values=*ng1* --query "SecurityGroups[*].[GroupId]" --output text)
aws ec2 authorize-security-group-ingress --group-id $NGSGID --protocol '-1' --cidr **192.168.1.100/32**
aws ec2 authorize-security-group-ingress --group-id $NGSGID --protocol '-1' --cidr **192.168.1.200/32**

# 워커 노드 SSH 접속
for node in $N1 $N2 $N3; do ssh -o StrictHostKeyChecking=no ec2-user@$node hostname; done
for node in $N1 $N2 $N3; do ssh ec2-user@$node hostname; done

프로메테우스 & 그라파나(admin / prom-operator) 설치 : 대시보드 추천 15757 17900 15172

# 사용 리전의 인증서 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

# 파라미터 파일 생성 : PV/PVC(AWS EBS) 삭제에 불편하니, 4주차 실습과 다르게 PV/PVC 미사용
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
  **defaultDashboardsEnabled: false**

  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'

alertmanager:
  enabled: false
EOT
cat monitor-values.yaml | yh

# 배포
**kubectl create ns monitoring**
helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack --version **57.2.0** \
--**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
****
# 프로메테우스 ingress 도메인으로 웹 접속
echo -e "Prometheus Web URL = https://prometheus.$MyDomain"

# 그라파나 웹 접속 : 기본 계정 - **admin / prom-operator**
echo -e "Grafana Web URL = https://grafana.$MyDomain"

리눅스에서 k9s 설치

#설치
curl -sS https://webinstall.dev/k9s | bash

#적용
source ~/.config/envman/PATH.env

1. K8s 인증/인가

이론: 큰 그림에서의 인증인가 프로세스

아래 그림은 kubectl 명령이 전달되는 과정을 설명한다.

Mutating admission, Validating admission 에서 Webhook 의 방식을 사용해서 다양한것을 수행한다.

 

 

 

큰 그림에서 보면 아래의 형태를 갖는다.

인증(Authentication) → 인가(Authorization) → Admission Control 의 단계를 거쳐서 진행된다.

  • Service Account(게시글에서 SA로 축약하여 사용한다.)

인증(Authentication)

  • 일반적으로 kubeconfig 파일에 쿠버네티스 관련 인증정보가 들어가있다고 생각함.

인가(Authorization)

  • 권한, RBAC, 역할 등 인가에 대한 정보를 다룬다.

./kube/config 파일이 의미하는 내용

  • clusters : kubectl 이 사용할 API서버의 정보
  • users : API서버에 접속하기 위한 사용자 인증 정보 목록
  • contexts : clusters 항목과 users 항목에 정의된 값을 조합한다. 사용할 쿠버네티스클러스터의 정보(contexts)를 설정한다.

실습: SA, Role, Rolebinding

실습환경

  • Service Account(SA) 사용자 : dev-k8s, infra-k8s
  • 각기 다른 권한(Role, 인가) : dev-k8s(ns: dev-team ), infra-k8s(ns: infra-team)
  • 각각 별도의 kubectl pod 를 생성, 해당 파드에 SA 를 지정하여 권한에 대한 테스트 진행

NS, SA 생성 후 확인

# 네임스페이스(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
(jjongiam@myeks:default) [root@myeks-bastion ~]# kubectl get sa dev-k8s -n dev-team -o yaml | yh
apiVersion: v1
kind: ServiceAccount
metadata:
  creationTimestamp: "2024-04-10T05:07:19Z"
  name: dev-k8s
  namespace: dev-team
  resourceVersion: "55041"
  uid: 84e0da01-861a-49ce-b3a3-3415c181c94f

(jjongiam@myeks:default) [root@myeks-bastion ~]#

(jjongiam@myeks:default) [root@myeks-bastion ~]# kubectl get sa -n infra-team
NAME        SECRETS   AGE
default     0         17s
infra-k8s   0         8s

(jjongiam@myeks:default) [root@myeks-bastion ~]# kubectl get sa infra-k8s -n infra-team -o yaml | yh
apiVersion: v1
kind: ServiceAccount
metadata:
  creationTimestamp: "2024-04-10T05:07:20Z"
  name: infra-k8s
  namespace: infra-team
  resourceVersion: "55047"
  uid: 7e45c81c-f173-4488-8784-778f275adcdf

SA 의 토큰 정보 확인 - https://jwt.io → Bearer type → JWT(Json Web Token)

# dev-k8s 서비스어카운트의 토큰 정보 확인 
DevTokenName=$(kubectl get sa dev-k8s -n dev-team -o jsonpath="{.secrets[0].name}")
DevToken=$(kubectl get secret -n dev-team $DevTokenName -o jsonpath="{.data.token}" | base64 -d)
echo $DevToken 
  • 웹/앱 에서 Bearer 토큰을 전송할때에는 JWT(일종의 포맷 형태) 토큰을 사용해서 하는데
  • X.509 인증서의 Lightweight Json 버전이라고 생각하면 됨

SA 지정하여 파드 생성 후 권한 테스트

# 각각 네임스피이스에 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.28.5
    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.28.5
    command: ["tail"]
    args: ["-f", "/dev/null"]
  terminationGracePeriodSeconds: 0
EOF
  • bitnami/kubectl 이미지 : kubectl 명령어를 사용 가능하게 만든 이미지
  • metadata.namespace, spec.serviceAccountName 을 지정해주었음
# 확인
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
  • 각각의 ns 에 있는 kubectl pod 가 각각의 SA 를 갖고있음을 확인(디폴트 : default)
# 각각 파드로 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
  • 전부 실행해보면, 모든 행동이 Error 가 발생한다.
  • 계정은 존재하지만, 계정에 해당 권한(get, run…) 이 없기 때문이다.

각각 Namespace 에 Role 을 생성 후, SA로 바인딩

  • Role : apiGroups, Resources 로 지정된 리소스에 대해 verbs권한을 인가
  • verbs(실행 가능한 조작): *(모두처리), create, delete, get, list, patch, update, watch

  • 이전에 한것: SA 를 만든것
  • 방금 한 것 : Role 을 만드는것
  • 이제 할 것 : RoleBinding 을 만들어서 SA-Role 을 엮는것
# 각각 네임스페이스내의 모든 권한에 대한 롤 생성
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
  ---------  -----------------  --------------  -----
  *.*        []                 []              [*]

  • Role 의 작동범위 : RoleNamespace 내부에서만 작동한다.
    • 하나의 Role 은 다른 Namespace로는 영향을 끼칠 수 없음
# 롤바인딩 생성 : '서비스어카운트 <-> 롤' 간 서로 연동
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

  • 해당 SA 가 해당 Role 을 갖게한다

SA 를 지정하여 생성한 파드에서 다시 권한 테스트

# 각각 파드로 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

  • k1(dev)의 경우, Dev namesapce 내부에서 run, get, delete 등 명령어가 수행되는것 확인
  • k1(dev)의 경우, 다른 ns(kube-system, infra-team) 에서 get 명령어의 권한이 없어서 수행불가능한것 확인
  • 리소스 삭제 : kubectl delete ns dev-team infra-team

2. EKS 인증/인가

실습 : RBAC 확인하기

RBAC관련 플러그인

# 설치
kubectl krew install access-matrix rbac-tool rbac-view rolesum whoami

# k8s 인증된 주체 확인
kubectl whoami
arn:aws:iam::9112...: /admin

# 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:.*'
kubectl rbac-tool policy-rules -e '^system:authenticated'

# 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: "arn:aws:iam::911283...:user/admin",      <<-- 과거 "kubernetes-admin"에서 변경됨
 UID:      "aws-iam-authenticator:911283.:AIDA5ILF2FJI...",
 Groups:   ["system:authenticated"],                 <<-- 과거 "system:master"는 안보임
 Extra:    {accessKeyId:  ["AKIA5ILF2FJI....."],
            arn:          ["arn:aws:iam::9112834...:user/admin"],
            canonicalArn: ["arn:aws:iam::9112834...:user/admin"],
            principalId:  ["AIDA5ILF2FJI...."],
            sessionName:  [""]}}
  • k8s 인증된 주체 : 실제로 kubectl api 를 찌르는 주체
# 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
kubectl rolesum -k Group system:authenticated
Policies:
• [CRB] */system:basic-user ⟶  [CR] */system:basic-user
  Resource                                       Name  Exclude  Verbs  G L W C U P D DC  
  selfsubjectaccessreviews.authorization.k8s.io  [*]     [-]     [-]   ✖ ✖ ✖ ✔ ✖ ✖ ✖ ✖   
  selfsubjectreviews.authentication.k8s.io       [*]     [-]     [-]   ✖ ✖ ✖ ✔ ✖ ✖ ✖ ✖   
  selfsubjectrulesreviews.authorization.k8s.io   [*]     [-]     [-]   ✖ ✖ ✖ ✔ ✖ ✖ ✖ ✖   
• [CRB] */system:discovery ⟶  [CR] */system:discovery
• [CRB] */system:public-info-viewer ⟶  [CR] */system:public-info-viewer

# [터미널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 웹 접속 : 최초 접속 후 정보 가져오는데 다시 시간 걸림 (2~3분 정도 후 화면 출력됨) 
echo -e "RBAC View Web http://$(curl -s ipinfo.io/ip):8800"
  • rolesum : role summarize 의 줄임말

  • rbac 관련해서 웹페이지에서 가시적으로 확인할 수 있음

실습: 인증/인가를 순차적으로 확인하기

인증인가완벽 분석해보기

Kubectl 명령어를 입력하여 ~ 실행하기 까지의 과정

  1. 터미널에서 Kubectl 명령어를 입력
    1. Bearer Token 의 형태로 감싸서 → kube-apiserver 에 명령 전달
  2. EKS의 경우 kube-apiserver 가 받은 Token을 Webhook의 방식으로 Authentication(인증) 과정을 거침
    • 해당 토큰이 옳은가? 에 대한 진위여부를 확인
    • configmap 을 통해서 AWS Config file 을 확인함
    • ⇒ 인증 완료
  3. Token을 kube-apiserver 에 전달
  4. Token을 kube-apiserver 가 받고, K8s RBAC 을 확인함
  5. Role/Rolebinding 을 확인하고 진위여부를 확인함
  6. kube-apiserver 에서 결과를 확인 → Allow/Deny 여부를 전달함

 

제일 중요한 사실

  • 인증은 AWS IAM, 인가는 K8s RBAC 에서 처리한다(두 곳에서 처리하는 구조)

https://docs.aws.amazon.com/eks/latest/userguide/cluster-auth.html

 

 

1. kubectl 명령

kubectl 명령 → aws eks get-token → EKS Service endpoint(STS)에 토큰 요청 ⇒ 응답값 디코드(Pre-Signed URL 이며 GetCallerIdentity..) - 링크

  • aws sts(Security Toekn Service) 서비스 : AWS 리소스에 대한 엑세스를 제어할 수 있는 임시보안자격증명(STS)
  • AWS CLI 버전 1.16.156 이상에서는 별도 aws-iam-authenticator 설치없이 aws eks get-token 으로 사용 가능
# sts caller id의 ARN 확인
aws sts get-caller-identity --query Arn
"arn:aws:iam::<자신의 Account ID>:user/admin"

# kubeconfig 정보 확인
cat ~/.kube/config | yh
...
- name: 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
  • .kube/config 의 clusters.server 에서 써있는 주소 == EKS 퍼블릭 서버 엔드포인트

 

# 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'
  • AWS CLI 내부에서, AWS STS 에 직접 요청해서 STS(임시보안자격증명 토큰) 을 발급받을 수 있다.

2. 일반적인 AWS API 호출의 형태로 확인

일반적인 AWS API 호출의 형태를 확인가능

3. EKS API : Token Review 목적으로 Webhook 에 요청

EKS API 는 Token Review 를 위해 Webhook token authenticator 에 요청한다 :

https://ap-northeast-2.console.aws.amazon.com/cloudtrail/home?region=ap-northeast-2#/events?EventName=GetCallerIdentity

  • Webhook token authenticator 로 요청해서 확인했는데, 승인나면 ⇒ 다음 절차로 
# 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. Kubernetes RBAC

쿠버네티스 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 그룹에 권한을 가짐 - 링크
  • 예전버전에서는 mapUsers 의 내용부분을 확인 가능했었는데, AWS측에서 숨겨놓은것 같다… 라고 생각하고있음
  • 과거에는 system:masters 가 명세되었다고 생각하면 될듯
# 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***

 

 

validatingwebhookconfigurations 의 역할: 어떠한 Configmap 을 변경할 수 있는 역할

# 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**

  • system:masterscluster-admin 이라는 CR 을 갖고있고, 모든 리소스를 사용 가능하다.
# 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
  ---------  -----------------  --------------  -----
  ***.***        []                 []              [*****]
             [*****]                []              [*]

 

 

 

실습: 시나리오(데브옵스 신입사원을 위한 분산환경상 실습)

데브옵스 신입 사원을 위한 myeks-bastion-2에 설정해보기

  1. [myeks-bation] testuser 사용자 생성
  • 권한 AdministratorAccess
# 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"
    }
}
# 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"

kubectl whoami

# 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 configure 할때에는, myeks-bastion 서버에서 testuser 를 만들때 사용했던 AccessKeyID, SecretAccessKey 를 사용한다.
# 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

 

 

  • 유저를 만들어주었기 때문에 IAM 화면에서도 확인 가능함
# get-caller-identity 확인
aws sts get-caller-identity --query Arn
"arn:aws:iam::911283464785:user/testuser"

  • Administrator 권한은 갖고있으나, get node 했을때 localhost로 엔드포인트가 연결시도하는것을 확인할 수 있음
  • 이는 ~/.kube/config 가 없어서 생긴 문제임
# kubectl 시도 >> testuser도 AdministratorAccess 권한을 가지고 있는데, 실패 이유는?
kubectl get node -v6
ls ~/.kube

  1. [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 get iamidentitymapping --cluster $CLUSTER_NAME
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
...

kubectl get validatingwebhookconfigurations eks-aws-auth-configmap-validation-webhook -o yaml | kubectl neat | yh
# 방안2 : 아래 edit로 mapUsers 내용 직접 추가!
kubectl edit cm -n kube-system aws-auth
---
apiVersion: v1
data: 
  mapRoles: |
    - groups:
      - system:bootstrappers
      - system:nodes
      rolearn: arn:aws:iam::911283464785:role/eksctl-myeks-nodegroup-ng1-NodeInstanceRole-LHQ7DWHQQRZJ
      username: system:node:{{EC2PrivateDNSName}}
  mapUsers: |
    - groups:
      - system:masters
      userarn: arn:aws:iam::911283464785:user/testuser
      username: testuser
...

# 확인 : 기존에 있는 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

iamidentitymapping 을 만들기 전

iamidentitymapping 을 만든 후

  1. [myeks-bastion-2] testuser kubeconfig 생성 및 kubectl 사용 확인
  • 방금직전에 testuser 에 system:masters 권한을 얻었기에 eks update-kubeconfig 를 하였을때에 ~/.kube/config 정보를 얻을 수 있는것임
# testuser kubeconfig 생성 >> aws eks update-kubeconfig 실행이 가능한 이유는?, 3번 설정 후 약간의 적용 시간 필요
aws eks update-kubeconfig --name $CLUSTER_NAME --user-alias testuser

# 첫번째 bastic ec2의 config와 비교해보자
cat ~/.kube/config | yh

  • Group system:masters 이 생성되어있는것 확인
# 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"],
...

⇒ 이 내용을 가능케 해주는 것 Bastion 의 Configmap

  1. [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

변경 전

변경 후

변경 후 확인

  • Groups 가 system:masters 에서 system:authenticated 로 변경

  1. [myeks-bastion-2] testuser kubectl 사용 확인
  • bastion 의 configmap 에서 testuser(bastion-2) 의 그룹을 authenticated 로 변경 하였기에 get nodes 등의 명령어가 작동하지않음
  • bastion2(testuser) 에서 bastion1(root) 의 configmap 을 확인한다는것
  • node 는 불가능, api-resources 는 확인 가능
# 시도
kubectl get node -v6
**kubectl api-resources -v5**

  1. [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
  • mapUsers 에 아에 없는거 확인

  1. [myeks-bastion-2] testuser kubectl 사용 확인
  • node 불가능, api-resource 불가능
# 시도
kubectl get node -v6
**kubectl api-resources -v5**

https://ap-northeast-2.console.aws.amazon.com/cloudtrail/home?region=ap-northeast-2#/events?EventSource=sts.amazonaws.com

https://ap-northeast-2.console.aws.amazon.com/cloudtrail/home?region=ap-northeast-2#/events?EventName=GetCallerIdentity

실습: EC2 Instance Profile(IAM Role)에 매핑된 K8s rbac 확인해보기

 

1. 노드 mapRoles 확인

# 노드에 STS ARN 정보 확인 : Role 뒤에 인스턴스 ID!
for node in $N1 $N2 $N3; do ssh ec2-user@$node aws sts get-caller-identity --query Arn; done
"arn:aws:sts::911283464785:assumed-role/eksctl-myeks-nodegroup-ng1-NodeInstanceRole-LHQ7DWHQQRZJ/i-07c9162ed08d23e6f"
"arn:aws:sts::911283464785:assumed-role/eksctl-myeks-nodegroup-ng1-NodeInstanceRole-LHQ7DWHQQRZJ/i-00d9d24c0af0d6815"
"arn:aws:sts::911283464785:assumed-role/eksctl-myeks-nodegroup-ng1-NodeInstanceRole-LHQ7DWHQQRZJ/i-031e672f89572abe8"

# aws-auth 컨피그맵 확인 >> system:nodes 와 system:bootstrappers 의 권한은 어떤게 있는지 찾아보세요!
# username 확인! 인스턴스 ID? EC2PrivateDNSName?
kubectl describe configmap -n kube-system aws-auth
...
mapRoles:
----
- groups:
  - system:nodes
  - system:bootstrappers
  rolearn: arn:aws:iam::911283464785:role/eksctl-myeks-nodegroup-ng-f6c38e4-NodeInstanceRole-1OU85W3LXHPB2
  username: system:node:{{EC2PrivateDNSName}}
...

# Get IAM identity mapping(s)
eksctl get iamidentitymapping --cluster $CLUSTER_NAME
ARN                                                USERNAME        GROUPS                    ACCOUNT
arn:aws:iam::911283464785:role/eksctl-myeks-nodegroup-ng1-NodeInstanceRole-1OS1WSTV0YB9X    system:node:{{EC2PrivateDNSName}}    system:bootstrappers,system:nodes
...

 

2. awscli 파드를 추가하고, 해당 노드(EC2)의 IMDS 정보 확인 : AWS CLI v2 파드 생성 - 링크 공식이미지링크

# awscli 파드 생성
cat <<EOF | kubectl create -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: awscli-pod
spec:
  replicas: 2
  selector:
    matchLabels:
      app: awscli-pod
  template:
    metadata:
      labels:
        app: awscli-pod
    spec:
      containers:
      - name: awscli-pod
        image: amazon/aws-cli
        command: ["tail"]
        args: ["-f", "/dev/null"]
      terminationGracePeriodSeconds: 0
EOF

# 파드 생성 확인
kubectl get pod -owide

# 파드 이름 변수 지정
APODNAME1=$(kubectl get pod -l app=awscli-pod -o jsonpath={.items[0].metadata.name})
APODNAME2=$(kubectl get pod -l app=awscli-pod -o jsonpath={.items[1].metadata.name})
echo $APODNAME1, $APODNAME2

# awscli 파드에서 EC2 InstanceProfile(IAM Role)의 ARN 정보 확인
kubectl exec -it $APODNAME1 -- aws sts get-caller-identity --query Arn
kubectl exec -it $APODNAME2 -- aws sts get-caller-identity --query Arn

# awscli 파드에서 EC2 InstanceProfile(IAM Role)을 사용하여 AWS 서비스 정보 확인 >> 별도 IAM 자격 증명이 없는데 어떻게 가능한 것일까요?
# > 최소권한부여 필요!!! >>> 보안이 허술한 아무 컨테이너나 탈취 시, IMDS로 해당 노드의 IAM Role 사용 가능!
kubectl exec -it $APODNAME1 -- aws ec2 describe-instances --region ap-northeast-2 --output table --no-cli-pager
kubectl exec -it $APODNAME2 -- aws ec2 describe-vpcs --region ap-northeast-2 --output table --no-cli-pager

# EC2 메타데이터 확인 : IDMSv1은 Disable, IDMSv2 활성화 상태, IAM Role - 링크
kubectl exec -it $APODNAME1 -- bash
-----------------------------------
아래부터는 파드에 bash shell 에서 실행
curl -s http://169.254.169.254/ -v
...

# Token 요청 
curl -s -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600" ; echo
curl -s -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600" ; echo

# Token을 이용한 IMDSv2 사용
TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
echo $TOKEN
curl -s -H "X-aws-ec2-metadata-token: $TOKEN" –v http://169.254.169.254/ ; echo
curl -s -H "X-aws-ec2-metadata-token: $TOKEN" –v http://169.254.169.254/latest/ ; echo
curl -s -H "X-aws-ec2-metadata-token: $TOKEN" –v http://169.254.169.254/latest/meta-data/iam/security-credentials/ ; echo

# 위에서 출력된 IAM Role을 아래 입력 후 확인
curl -s -H "X-aws-ec2-metadata-token: $TOKEN" –v http://169.254.169.254/latest/meta-data/iam/security-credentials/eksctl-myeks-nodegroup-ng1-NodeInstanceRole-1DC6Y2GRDAJHK
{
  "Code" : "Success",
  "LastUpdated" : "2023-05-27T05:08:07Z",
  "Type" : "AWS-HMAC",
  "AccessKeyId" : "ASIA5ILF2FJI5VPIYJHP",
  "SecretAccessKey" : "rke6HXkaG4B/YLGUyAYtDnd3eMrjXlCpiB4h+9XJ",
  "Token" : "IQoJb3JpZ2luX2VjEAUaDmFwLW5vcnRoZWFzdC0yIkcwRQIhAOLQr2c2k4UwqTR2Iof2wq9Faduno1a2FX07ASHsO/rCAiAvCqQRv/JrSDZNZKNMloaBTR4s91O+RNWfSlfNluimmirLBQg+EAEaDDkxMTI4MzQ2NDc4NSIM1/RJmwkWziNhz8TEKqgFZZr1FpwHmLWNzdCdbNxtPk/YhbqbED6JzFiWJssqdRq4UniamoGrkV75oaf7o0CXQTlgK7r6f07goYA268UlqGx9XHKmeSoUt3ZTG79B1BIiSW22JVFzs4/fMpcwQLFv1lJKcGOqKehXrlq4yQ2zln4QTi/S30rp2ARiUfjgdp2+gkWKzOVWkdgoKtn3OAfdI/hBJHiz1eDPZsqqzv8eyP6sdo1xHJ6OY7xjLtTHWRaQpt6SStKTzsN88sJi9NebBXV63FJ6EkGNaC7eFo/nq9xZtGJqsu3PEuseadl8a8LJzOfNO0NP+4p8o0fMV4oeKSItZUIu88CvinvGd3bp1FWlVItDsGwjo6qOTxCg2ov6p7cAbTudEA5AwSjDlHm/BX08JN4XN7kDKtBQhHoWRbeI3suqZmtLPrSu5NCfgVu2jJpMiwOEhVV9W+fBUica345sIp94qIVVwrVbDnuLC0QDSXKxD+GRhcqdtA54QmUodqxv/bEUlRy1wVUty7Umucxl3B6MYBVSXR7PRzcf2U3vvqbJDJAT5dhFTRI1gK1YcXLzpT1T3wluMsyMPFpEWYMe/QEDAn0UwJ55pZt2pKohioiLJ3amWfNUhzoDkmXXZhAOM71e8gUVdrtAVcnl30MTDjHlIWIOBWrVMshunM5Wfmr4H4BAV+8of6xGz5AhoodWNVE+/x+XifO6h9l+Plwq24Jp8SbiCF3ZFQVe20ijsfDqK6SFAveL4vcVz7sEGLTZXLNLycgeGQmcvkb7Mmmoir/9UwNCWFWBbWXZfsEbNfSLhInw+k53FLb5I+axJPhEDSE5Iqmu+cuvoZfLy+bOailVgQN/jX6vZSL3ihhJwsP7t58urN34tKP+sjOpIBWv2bV2OnntaAqbc24tmc0wjWkaw5IwqKDGowY6sQGFB40kmsXmxihug/yKwcMK/pg5xFknPFO56P6BzErLmt1hcpNF4QBQzh/sdFi7Y/EOh9NqU/XFdFeJLp6KgaxUASLSW/k6ee+RzhbW0aSJb9GYi7tZdArcjg4YaQ6hdXdCFXiYWbNyIMs2MH8APT5jFDnwpbqSnlO2Ao64XY12cm2tMWVH+KTUyLGICHP1az7kD3/tV9glw9rJB2AOL4iA3TTuK+U2o+pHWEHRQOVh3p4=",
  "Expiration" : "2023-05-27T11:09:07Z"
}
## 출력된 정보는 AWS API를 사용할 수 있는 어느곳에서든지 Expiration 되기전까지 사용 가능

# 파드에서 나오기
exit
---

3. awscli 파드에 kubeconfig (mapRoles) 정보 생성 및 확인

# node 의 IAM Role ARN을 변수로 지정
eksctl get iamidentitymapping --cluster $CLUSTER_NAME
NODE_ROLE=<각자 자신의 노드 Role 이름>
NODE_ROLE=eksctl-myeks-nodegroup-ng1-NodeInstanceRole-1DC6Y2GRDAJHK

# awscli 파드에서 kubeconfig 정보 생성 및 확인 >> kubeconfig 에 정보가 기존 iam user와 차이점은?
kubectl exec -it $APODNAME1 -- aws eks update-kubeconfig --name $CLUSTER_NAME --role-arn $NODE_ROLE
kubectl exec -it $APODNAME1 -- cat /root/.kube/config | yh
...
  - --role
  - eksctl-myeks-nodegroup-ng1-NodeInstanceRole-3GQR27I04PAJ

kubectl exec -it $APODNAME2 -- aws eks update-kubeconfig --name $CLUSTER_NAME --role-arn $NODE_ROLE
kubectl exec -it $APODNAME2 -- cat /root/.kube/config | yh

 

 

만들어진 이유: EKS 를 한번쓰려면 AWS 에서도 인증을 받아야하고, K8s RBAC 에서도 인증을 받아야함.

인증을 두번 받는게 너무 귀찮다…!? 에서 출발

  • EKS → 액세스 : IAM 액세스 항목

# EKS API 액세스모드로 변경
aws eks update-cluster-config --name $CLUSTER_NAME --access-config authenticationMode=API

# List all access policies : 클러스터 액세스 관리를 위해 지원되는 액세스 정책
## AmazonEKSClusterAdminPolicy – 클러스터 관리자
## AmazonEKSAdminPolicy – 관리자
## AmazonEKSEditPolicy – 편집
## AmazonEKSViewPolicy – 보기
aws eks list-access-policies | jq

# 맵핑 클러스터롤 정보 확인
kubectl get clusterroles -l 'kubernetes.io/bootstrapping=rbac-defaults' | grep -v 'system:'
NAME                                                                   CREATED AT
admin                                                                  2024-04-06T05:58:32Z
cluster-admin                                                          2024-04-06T05:58:32Z
edit                                                                   2024-04-06T05:58:32Z
view                                                                   2024-04-06T05:58:32Z

kubectl describe clusterroles admin
kubectl describe clusterroles cluster-admin
kubectl describe clusterroles edit
kubectl describe clusterroles view

API 엑세스모드 변경 전

API 액세스모드 변경 후

  • !실습시 주의(개인환경에 맞게 바꾸기)
    • 실습코드 : ACCOUNT_ID user/admin ⇒ user/jjongiam
    • 실습코드: role/eksctl-myeks-nodegroup-ng1-NodeInstanceRole-t4MD9Py4ZCyK ⇒ role/eksctl-myeks-nodegroup-ng1-NodeInstanceRole-8mj3PaoIK748
      • 캡쳐이미지에서는 aws eks list-access-entries --cluster-name $CLUSTER_NAME | jq 에서 나온 accessEntries 의 값
#
aws eks list-access-entries --cluster-name $CLUSTER_NAME | jq
{
  "accessEntries": [
    "arn:aws:iam::911283...:role/eksctl-myeks-nodegroup-ng1-NodeInstanceRole-t4MD9Py4ZCyK",
    "arn:aws:iam::911283...:user/admin"
  ]
}

# 
aws eks list-associated-access-policies --cluster-name $CLUSTER_NAME --principal-arn arn:aws:iam::$ACCOUNT_ID:user/admin | jq
{
  "associatedAccessPolicies": [
    {
      "policyArn": "arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy",
      "accessScope": {
        "type": "cluster",
        "namespaces": []
      },
      "associatedAt": "2024-04-06T14:53:36.982000+09:00",
      "modifiedAt": "2024-04-06T14:53:36.982000+09:00"
    }
  ],
  "clusterName": "myeks",
  "principalArn": "arn:aws:iam::91128...:user/admin"
}

#
aws eks list-associated-access-policies --cluster-name $CLUSTER_NAME --principal-arn arn:aws:iam::$ACCOUNT_ID:role/eksctl-myeks-nodegroup-ng1-NodeInstanceRole-t4MD9Py4ZCyK | jq
{
  "associatedAccessPolicies": [],
  "clusterName": "myeks",
  "principalArn": "arn:aws:iam::9112834...:role/eksctl-myeks-nodegroup-ng1-NodeInstanceRole-t4MD9Py4ZCyK"
}

#
aws eks describe-access-entry --cluster-name $CLUSTER_NAME --principal-arn arn:aws:iam::$ACCOUNT_ID:user/admin | jq
...
    "kubernetesGroups": [],
    ...
    "username": "arn:aws:iam::9112...:user/admin",
    "type": "STANDARD"
...

aws eks describe-access-entry --cluster-name $CLUSTER_NAME --principal-arn arn:aws:iam::$ACCOUNT_ID:role/eksctl-myeks-nodegroup-ng1-NodeInstanceRole-t4MD9Py4ZCyK | jq
...
    "kubernetesGroups": [
      "system:nodes"
    ...
    "username": "system:node:{{EC2PrivateDNSName}}",
    "type": "EC2_LINUX"
...

 

현재상태 : testuser(Bastion2)에 대해서 는 유저의 iam, configmap 이 없는 상태 ⇒ Kubectl 명령어를 못씀

testuser 설정

# testuser 의 access entry 생성
aws eks create-access-entry --cluster-name $CLUSTER_NAME --principal-arn arn:aws:iam::$ACCOUNT_ID:user/testuser
aws eks list-access-entries --cluster-name $CLUSTER_NAME | jq -r .accessEntries[]

# testuser에 AmazonEKSClusterAdminPolicy 연동
aws eks associate-access-policy --cluster-name $CLUSTER_NAME --principal-arn arn:aws:iam::$ACCOUNT_ID:user/testuser \
  --policy-arn arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy --access-scope type=cluster

#
aws eks list-associated-access-policies --cluster-name $CLUSTER_NAME --principal-arn arn:aws:iam::$ACCOUNT_ID:user/testuser | jq
aws eks describe-access-entry --cluster-name $CLUSTER_NAME --principal-arn arn:aws:iam::$ACCOUNT_ID:user/testuser | jq

액세스 항목에 추가된 모습 확인가능함

[myeks-bastion-2]에서 testuser로 확인

  • kubectl get cm, eksctl get iam 에는 값이 없음!
# testuser 정보 확인
**aws sts get-caller-identity --query Arn**
**kubectl whoami**

# kubectl 시도
**kubectl get node -v6
kubectl api-resources -v5
kubectl rbac-tool whoami
kubectl auth can-i delete pods --all-namespaces**
kubectl get cm -n kube-system aws-auth -o yaml | kubectl neat | yh
eksctl get iamidentitymapping --cluster $CLUSTER_NAME

Access entries and Kubernetes groups - Link

  • 방금 사용한 user/testuser 를 제거
  • IAM 액세스 항목에서도 제거된 것 확인
# 기존 testuser access entry 제거
**aws eks delete-access-entry --cluster-name $CLUSTER_NAME --principal-arn arn:aws:iam::$ACCOUNT_ID:user/testuser**
aws eks list-access-entries --cluster-name $CLUSTER_NAME | jq -r .accessEntries[]

 

원하는 권한을 설정하는 Role 로 설정해서 테스트

[myeks-bastion-2]에서 testuser로 확인

# testuser 정보 확인
**aws sts get-caller-identity --query Arn**
**kubectl whoami**

# kubectl 시도
**kubectl get pod -v6
kubectl api-resources -v5
kubectl auth can-i get pods --all-namespaces
kubectl auth can-i delete pods --all-namespaces**

kubernetesGroups 업데이트 적용

  • pod adminpod viewer 로 변경하려고 함
#
aws eks **update**-access-entry --cluster-name $**CLUSTER_NAME** --principal-arn arn:aws:iam::$ACCOUNT_ID:user/**testuser** --kubernetes-group **pod-admin** | jq -r .accessEntry
...
  "kubernetesGroups": [
    "pod-admin"
...
aws eks describe-access-entry **--cluster-name $CLUSTER_NAME --principal-arn arn:aws:iam::$ACCOUNT_ID:user/testuser | jq**
...
    "**kubernetesGroups**": [
      "**pod-admin**"
    ],
...

적용 전

적용 후

  • CR 을 만들어서 직접배포
  • Clusterrolebinding으로 CR 과 Kubernetes Group을 연결
  • pod-viewer-role 은 list, get , watch 명령만 가능한것으로 설정. delete는 없음에 주의
#
**cat <<EoF> ~/pod-viewer-role.yaml**
apiVersion: rbac.authorization.k8s.io/v1
kind: **ClusterRole**
metadata:
  name: pod-**viewer**-role
rules:
- apiGroups: [""]
  resources: ["pods"]
  **verbs: ["list", "get", "watch"]**
**EoF**

**cat <<EoF> ~/pod-admin-role.yaml**
apiVersion: rbac.authorization.k8s.io/v1
kind: **ClusterRole**
metadata:
  name: pod-**admin**-role
rules:
- apiGroups: [""]
  resources: ["pods"]
  **verbs: ["*"]**
**EoF**

**kubectl apply -f ~/pod-viewer-role.yaml
kubectl apply -f ~/pod-admin-role.yaml**

#
kubectl create **clusterrolebinding** viewer-role-binding --clusterrole=pod-viewer-role --group=**pod-viewer**
kubectl create **clusterrolebinding** admin-role-binding --clusterrole=pod-admin-role --group=**pod-admin**

#
aws eks create-access-entry --cluster-name $**CLUSTER_NAME** --principal-arn arn:aws:iam::$ACCOUNT_ID:user/**testuser** --kubernetes-group **pod-viewer**
...
    "accessEntry": {
        "clusterName": "myeks",
        "principalArn": "arn:aws:iam::91128...:user/**testuser**",
        "**kubernetesGroups**": [
            "**pod-viewer**"
        ],

#
aws eks list-associated-access-policies --cluster-name $CLUSTER_NAME --principal-arn arn:aws:iam::$ACCOUNT_ID:user/testuser
aws eks describe-access-entry **--cluster-name $CLUSTER_NAME --principal-arn arn:aws:iam::$ACCOUNT_ID:user/testuser | jq**
...
    "**kubernetesGroups**": [
      "**pod-viewer**"
    ],
...

 

 

 

 

3. EKS IRSA, Pod Identitty

  • POD 에 권한을 주는 방법

IRSA 가 나오기 이전의 프로세스가 문제가 되는 이유

  • Workernode 의 iam 의 Policy 를 확인해보면, iam 에 적용된 기능이 보이는데
    • Worker node에 적용되어있는거라, Workernode 에 올라가는 Pod 1개가 장악되면, Pod 1개가 Workernode의 Policy 를 모두 사용할 수 있다.
    • → 보안의 최소권한 원칙에 위배됨

⇒ IRSA 로 최소화 시킬수 있다. 안쓸이유가없다

동작 : k8s파드 → AWS 서비스 사용 시 ⇒ AWS STS/IAM ↔ IAM OIDC Identity Provider(EKS IdP) 인증/인가

  • 특정한 Pod에 특정한권한을 연결시켜서, 특정한 서비스를 사용할 수 있게 한다!

 

필요 지식 : 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

 프로젝티드 볼륨은 세 가지로 구성된다.

- kube-apiserver로부터 TokenRequest API를 통해 얻은 서비스어카운트토큰(ServiceAccountToken). 서비스어카운트토큰은 기본적으로 1시간 뒤에, 또는 파드가 삭제될 때 만료된다. 서비스어카운트토큰은 파드에 연결되며 kube-apiserver를 위해 존재한다.

- kube-apiserver에 대한 연결을 확인하는 데 사용되는 CA 번들을 포함하는 컨피그맵(ConfigMap).

- 파드의 네임스페이스를 참조하는 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 접근 단계

  • AuthNAuthZAdmisstion 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라고 부르고, 크게 MutatingWebhookValidatingWebhook 로 나뉩니다.
  • MutatingWebhook은 사용자가 요청한 request에 대해서 관리자가 임의로 값을 변경하는 작업입니다.
  • ValidatingWebhook은 사용자가 요청한 request에 대해서 관리자기 허용을 막는 작업입니다.
kubectl get **validatingwebhook**configurations
kubectl get **mutatingwebhook**configurations

 

jjongguet