Overview
CloudNet@ AEWS 스터디 6주차는 EKS Security 이다.
0. 실습 환경 배포
Amazon EKS (myeks) 윈클릭 배포
(bastion ec2 2대, eksctl 0.143.0 - EKS 1.27 support, IMDSv2 by default - 링크) & 기본 설정
# 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=somaz-key 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
3.35.18.59
# 작업용 EC2 SSH 접속
ssh -i ~/.ssh/somaz-key.pem ec2-user@$(aws cloudformation describe-stacks --stack-name myeks --query 'Stacks[*].Outputs[0].OutputValue' --output text)
기본 설정
# default 네임스페이스 적용
kubectl ns default
# (옵션) context 이름 변경
NICK=<각자 자신의 닉네임>
NICK=somaz
kubectl ctx
somaz@myeks.ap-northeast-2.eksctl.io
kubectl config rename-context admin@myeks.ap-northeast-2.eksctl.io $NICK
# ExternalDNS
MyDomain=<자신의 도메인>
echo "export MyDomain=<자신의 도메인>" >> /etc/profile
MyDomain=somaz.link
echo "export MyDomain=somaz.link" >> /etc/profile
MyDnzHostedZoneId=$(aws route53 list-hosted-zones-by-name --dns-name "${MyDomain}." --query "HostedZones[0].Id" --output text)
echo $MyDomain, $MyDnzHostedZoneId
somaz.link, /hostedzone/Z03204211VEUZG9O0RLE5
curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/aews/externaldns.yaml
MyDomain=$MyDomain MyDnzHostedZoneId=$MyDnzHostedZoneId envsubst < externaldns.yaml | kubectl apply -f -
# 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
# 노드 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
192.168.1.143, 192.168.2.151, 192.168.3.8
# 노드 보안그룹 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.0/24
# 워커 노드 SSH 접속
for node in $N1 $N2 $N3; do ssh ec2-user@$node hostname; done
프로메테우스 & 그라파나(admin / prom-operator) 설치
- 대시보드 추천 15757 17900 15172
# 사용 리전의 인증서 ARN 확인
CERT_ARN=`aws acm list-certificates --query 'CertificateSummaryList[].CertificateArn[]' --output text`
echo $CERT_ARN
# repo 추가
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
# 파라미터 파일 생성
cat <<EOT > monitor-values.yaml
prometheus:
prometheusSpec:
podMonitorSelectorNilUsesHelmValues: false
serviceMonitorSelectorNilUsesHelmValues: false
retention: 5d
retentionSize: "10GiB"
ingress:
enabled: true
ingressClassName: alb
hosts:
- prometheus.$MyDomain
paths:
- /*
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}, {"HTTP":80}]'
alb.ingress.kubernetes.io/certificate-arn: $CERT_ARN
alb.ingress.kubernetes.io/success-codes: 200-399
alb.ingress.kubernetes.io/load-balancer-name: myeks-ingress-alb
alb.ingress.kubernetes.io/group.name: study
alb.ingress.kubernetes.io/ssl-redirect: '443'
grafana:
defaultDashboardsTimezone: Asia/Seoul
adminPassword: prom-operator
ingress:
enabled: true
ingressClassName: alb
hosts:
- grafana.$MyDomain
paths:
- /*
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}, {"HTTP":80}]'
alb.ingress.kubernetes.io/certificate-arn: $CERT_ARN
alb.ingress.kubernetes.io/success-codes: 200-399
alb.ingress.kubernetes.io/load-balancer-name: myeks-ingress-alb
alb.ingress.kubernetes.io/group.name: study
alb.ingress.kubernetes.io/ssl-redirect: '443'
defaultRules:
create: false
kubeControllerManager:
enabled: false
kubeEtcd:
enabled: false
kubeScheduler:
enabled: false
alertmanager:
enabled: false
EOT
# 배포
kubectl create ns monitoring
helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack --version 45.27.2 \
--set prometheus.prometheusSpec.scrapeInterval='15s' --set prometheus.prometheusSpec.evaluationInterval='15s' \
-f monitor-values.yaml --namespace monitoring
# Metrics-server 배포
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
1. K8S 인증/인가
2023.05.10 - [Container Orchestration/Kubernetes] - Kuberntes Service Account란?
K8S(API 접근) 인증/인가 소개
- 서비스 어카운트(Service Account)
- API 서버 사용 : kubectl(config, 다수 클러스터 관리 가능), 서비스 어카운트, https(x.509 Client Certs) ⇒ X.509 발음을 어떻게 하시나요? - 링크
- API 서버 접근 과정 : 인증 → 인가 → Admission Control(API 요청 검증, 필요 시 변형 - 예. ResourceQuota, LimitRange) - 참고
인증(Authentication)
- X.509 Client Certs : kubeconfig 에 CA crt(발급 기관 인증서) , Client crt(클라이언트 인증서) , Client key(클라이언트 개인키) 를 통해 인증
- kubectl : 여러 클러스터(kubeconfig)를 관리 가능 - contexts 에 클러스터와 유저 및 인증서/키 참고
- Service Account : 기본 서비스 어카운트(default) - 시크릿(CA crt 와 token)
인가(Authorization)
- 인가 방식 : RBAC(Role, RoleBinding), ABAC, Webhook, Node Authorization
- RBAC : 역할 기반의 권한 관리, 사용자와 역할을 별개로 선언 후 두가지를 조합(binding)해서 사용자에게 권한을 부여하여 kubectl or API로 관리 가능
- Namespace/Cluster - Role/ClusterRole, RoleBinding/ClusterRoleBinding, Service Account
- Role(롤) - (RoleBinding 롤 바인딩) - Service Account(서비스 어카운트) : 롤 바인딩은 롤과 서비스 어카운트를 연결
- Role(네임스페이스내 자원의 권한) vs ClusterRole(클러스터 수준의 자원의 권한)
.kube/config 파일 내용
- clusters : kubectl 이 사용할 쿠버네티스 API 서버의 접속 정보 목록. 원격의 쿠버네티스 API 서버의 주소를 추가해 사용 가능
- users : 쿠버네티스의 API 서버에 접속하기 위한 사용자 인증 정보 목록. (서비스 어카운트의 토큰, 혹은 인증서의 데이터 등)
- contexts : cluster 항목과 users 항목에 정의된 값을 조합해 최종적으로 사용할 쿠버네티스 클러스터의 정보(컨텍스트)를 설정.
- 예를 들어 clusters 항목에 클러스터 A,B 가 정의돼 있고, users 항목에 사용자 a,b 가 정의돼 있다면 cluster A + user a 를 조합해, 'cluster A 에 user a 로 인증해 쿠버네티스를 사용한다' 라는 새로운 컨텍스트를 정의할 수 있다.
- kubectl 을 사용하려면 여러 개의 컨텍스트 중 하나를 선택
cat .kube/config
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM1ekNDQWMrZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRJeE1Ea3dNVEl5TkRjMU1sb1hEVE14TURnek1ESXlORGMxTWxvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTG1qCml1cW11UUxWTXN6UE83VUpxTkdCNHdXQ3RnYTl1cFcwYUVNVmUrZm41YXZZMWxUWUZqZjBCb1VlQXhOWmc5YXoKRU1FZVJMWCt1ZzhqTDNETjhCTzEwdUEwSzF6b3ZpQVVtbDlCU2dNWU9FOHpUMFJsV2tvcnBtVDNGai9td1lJagpEemRxYld6MlpuQ1FoQ3dvYURzdlpoUVNMRTh6dnFwU0F5c0hNSUdzV3J0anI4aC9QaW52dnF5bUo0UlFhWlY3CnNuZ0lzMDBqakdGbFowcUVueWZMSGtBeHpjSktVUnJHamFsZm1RdmZ3WkZ2Z0pjam5rSG9jb3g0T0JKUEh0N2EKdFE1OEpBTTF3cng0b3pFSjh1MExsa21LOWYwWGVzQmRGeUhFamZ1elhTYml0Q09sbTR1Q1o3UkVRVmRjZWk1SAo3Tjg1M1RjbWRIck9tRkQwZVpVQ0F3RUFBYU5DTUVBd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0hRWURWUjBPQkJZRUZLRVYvZFNBUkJteVhyLytxUkVnb1h5QUg3UTZNQTBHQ1NxR1NJYjMKRFFFQkN3VUFBNElCQVFDQ0M4cDRQRmdoVVFDbW5weWk1SDAxYVRNYXp0Si9pdkw0amxiMWJNdXc3ZjJNZmM0UQpDRGw2UWVNd2FpYk9raHNrVGhMTEtRckQwQ0xqWXNCSy9iNVhQSTNtMmoxS0cvc1ExREFPL0hNdmt6RmkzUDdrCmJHOUErdWk1YXJPREs5eWJFQ2NtUG5adnVmWkFSY3d3dkp1ZGRMUy9QZERkOW9ZVGgzV3FQMjloVk9tZnZUS3kKNFhzeVg0cHk5dzVTNkYxaGVpUE9odnprMWRzNWFZZENBR1E5R0ZRb3BIQSs1Wm9YOWJjazFuN0FiMDVua0UrUQprMTVnc1VhQWFEMGVGUlRHY0tRTzM5dW1ZdkxhVnUrL20xcDFFRWU0YWdLdktvUGZlZ1VJTFQ0dGtLdjFwcWYvCmhIZldDUFo3Vy9ldmRZODI5WmtudE1HWHZ5QXZaWHFUZE1KZwotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
server: https://192.168.100.10:6443
name: kubernetes
contexts:
- context:
cluster: kubernetes
namespace: default
user: kubernetes-admin
name: admin@k8s
current-context: admin@k8s
kind: Config
preferences: {}
users:
- name: kubernetes-admin
user:
client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURJVENDQWdtZ0F3SUJBZ0lJUzFnbmhwU0N5Q2d3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TVRBNU1ERXlNalEzTlRKYUZ3MHlNakE1TURFeU1qUTNOVFZhTURReApGekFWQmdOVkJBb1REbk41YzNSbGJUcHRZWE4wWlhKek1Sa3dGd1lEVlFRREV4QnJkV0psY201bGRHVnpMV0ZrCmJXbHVNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQW52eXoxc1R1SXRpKzE3WmQKVVRXTFVxMUxIL2VJN01lMkI0K2ZNZlhKSStlM2xCVnp5RXpIV0ZOR1phM2JYbkYvS0VJaDJRcmpOcXh0bGswSgpIOW83dUtVZmRyVjhNL3IzZmxidUN1VG9lZnN3UFROQmJhbGladzVPRXl0VWV6V3ZxK3VUZzFmeExZVUl6Zk4xCldxMzhiU2pjYlhQa3Q3UWJZVThqUEpMMmlKalBlbVFRN1FnTW9pUmlsNXM2TzRCZnNYbzNCbDNrdUY0VDlCK1MKVzE2VmpQTnRMQ0pxQW1ENEt1ZWdBcWl3RHdDNFVScjhNbDhJaHJmL2FzT2JTZnVqTG5HL1Npd2V6dnJ4bHJnUgo0QVBlNjFSOU1RZFFjaldsT1Z2TXQrSXhlSnlrbWdmeHJsNFJmbytFOWVNK0VTNzFHaVhnQmtycFp0NGxQWURsClllSVZQd0lEQVFBQm8xWXdWREFPQmdOVkhROEJBZjhFQkFNQ0JhQXdFd1lEVlIwbEJBd3dDZ1lJS3dZQkJRVUgKQXdJd0RBWURWUjBUQVFIL0JBSXdBREFmQmdOVkhTTUVHREFXZ0JTaEZmM1VnRVFac2w2Ly9xa1JJS0Y4Z0IrMApPakFOQmdrcWhraUc5dzBCQVFzRkFBT0NBUUVBa0ZqdDJPNW5ZQUkxRHRrZnh6R1RPbFdGT1F3b3FKelBHQXJSCmRoTnFXL3JjUlhyYkgzZ3FHaXF4cmQ2anczblJiYThCRWxOazE0YUtYWGVYRnU0U0YyYTJCY3RzKzhkNE9VSkwKeU1pUVBpN0g2Q3RrQ0o2QzRCZDU4Vk5XaVM0YVg4b0ExQWloZWp0cURRc2U2MCtna2JoSlJwdnM0WGRVUkNTdgpFL3NqZWgvc1JIVjBJYWNrNzlTVEduSUdlVVUrbUxwVlF1bHZkd1lkVDhXK08zMkpRbFk1Z3pTZllFMkI2YjB4Ci9TK1dORU9QTzhhaTlmQkQ5cWJ1dWdRd2wzSkNYT005amZLV1gzOTBZZzhYcWhndEhuR0JDdlcwbjQxY0ZLUDgKQVFFdXRnbDNhQ0ZibWZFZ2Z3cWlUVFc3R3EzSklZSTZrZ3EwNGxUbVdKa1gvQnZmaXc9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBbnZ5ejFzVHVJdGkrMTdaZFVUV0xVcTFMSC9lSTdNZTJCNCtmTWZYSkkrZTNsQlZ6CnlFekhXRk5HWmEzYlhuRi9LRUloMlFyak5xeHRsazBKSDlvN3VLVWZkclY4TS9yM2ZsYnVDdVRvZWZzd1BUTkIKYmFsaVp3NU9FeXRVZXpXdnErdVRnMWZ4TFlVSXpmTjFXcTM4YlNqY2JYUGt0N1FiWVU4alBKTDJpSmpQZW1RUQo3UWdNb2lSaWw1czZPNEJmc1hvM0JsM2t1RjRUOUIrU1cxNlZqUE50TENKcUFtRDRLdWVnQXFpd0R3QzRVUnI4Ck1sOElocmYvYXNPYlNmdWpMbkcvU2l3ZXp2cnhscmdSNEFQZTYxUjlNUWRRY2pXbE9Wdk10K0l4ZUp5a21nZngKcmw0UmZvK0U5ZU0rRVM3MUdpWGdCa3JwWnQ0bFBZRGxZZUlWUHdJREFRQUJBb0lCQUQzOHFPR0R4cFV2akxqdQpFVlFvWERuUDl3cHZxS01vK24vWUwybDdPd0VVeHk2bGJvOFo0RjgvbUtMc05pdU1kTmR0Y1dUK0tiaVhZZUxJCkJsYTA3N1ArTFZaTFRERzRGK2JhWGRWQmlxS0VuVG8vVWJNLzUyM20xZW9EYXR6ZkFhODJHajJMZkMwVFFXdUwKRUtaYVQ2RC8zWEdQVGcyUjIxc0ZUK2UrSlFEOGRnc25oNE9vVlQrTkRacC9kU0JHYXZNQTFZUmo0bFhwY1U5RAo5bW15ckxRZFlRcE56K1U4cGZKdHhIcXlGSWhOakZmK0JkNHdRdEhrN3NOODE4Um9JalZHV3RYeGVhZXFOMXVtCnFlWEhFNHVDRG5tYS9qTElLLzBRaWlMZTZ1WGVTMk1udG1UUjJ1d0paOWh5V3NsYnlTb2oyQmNONVBaaHpGK3kKMUtyZEFZRUNnWUVBenNEeUFtZ1dUUXI5M083ZnlSR1U5azBad01LRFVSK25Lb0xQcUNhSmxQeE4xaG1zTkJmWApKWURsZ3cwVTk5R1lmRGJZUTdjS3BaRE8xWHZpWTI4K1UxY21nM2xVMVFVOTdFR0N3ejVxMnNjUFY0SDBhZmxnCmNUQko5dGo1ZTkzVS9sVDFpd0M1eEFONlpjektTbzhYSytNQ29nUkEyeEFZZjFJZnJTZmhoVzBDZ1lFQXhOc2kKQ2oxS29FQzV0TjlEaW41eFQzMUVBTjlwVmtONkZlcy9nZC9JSFREWXJLSytaMnNpVVNhR1NyaHYwZkc1ZGVwagpIMjdEeVF6cW1aUUlpaE44cFB5TzRSOXMya21la3RISUZqMjRnSUpQZDNzS3BaS1QwQjJmZUErTXVCOFlsclRGCk0ycTJ2V1JHeHFmMERMZmpWNm5JVkZkQ1hJWFZLMjlRcWprdkZkc0NnWUFmUGRxVDhJU0dLY1lJajNQelh4dkMKU0E0L0tXVk1hZHNKdW5DRWVTWkxCQUVDL0NnZ1N3WHduZFNRZy9hS0ovckJza3ZsbDVBZFNvOW1oT3pGbDdhMApRelFIbzlya3dZRUU1VFZNS1c5ZUZieEV2ZGRmK0JYUnBMbFllcHJnVTdudW9Jbmw4anNmMm1LeFpVdWdEcFV5CnhYL05XWlV2UlBSZXNOc21nQ004MVFLQmdRQ0xSOFFJM0o3TlRaNVhNOVJVeSt1ZDR6SlhMN3NXMXIwdGZ2bTcKQ1R0TU5BQkovUWVjb25kd1ZVS1U0WFAwWmdQalF3Z0krRlM4RGxCNmd2dWJ2ZmZsdisvVHBtbGM5Tk9tYTVrVwo2MnA4T2piQmdhUGh6QmliR2lwM1J3RTRVSUFVT1NpQm5aSlg0L2dUbkVlWExCQkZPUkpOWWtQSXRNUkRiQW4xCnRtbnpHd0tCZ0J3NHhLanNEUUozcCtxWW50cTdtVzhLS2hPWTFMRWczOVJ4Snd1aEord0VSZUh5TGhIcEU5SFkKUndxbUVCYjdvY2dDcmV6bWR5WndUSXZkMGEzaStBbWpucTd1QU1DUFpNUjU0a2FkNUpmZmVib0FzbXcwSW5aeApvVGltQXNya3BmRlVxZzZsSVBIMEtuUEVTVWQxQlJLS2I5dTUzTWpwZEZiVkhWZVZhVEtlCi0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg==
실습 환경
- 쿠버네티스에 사용자를 위한 서비스 어카운트(Service Account, SA)를 생성 : dev-k8s, infra-k8s
- 사용자는 각기 다른 권한(Role, 인가)을 가짐 : dev-k8s(dev-team 네임스페이스 내 모든 동작) , infra-k8s(dev-team 네임스페이스 내 모든 동작)
- 각각 별도의 kubectl 파드를 생성하고, 해당 파드에 SA 를 지정하여 권한에 대한 테스트를 진행
네임스페이스와 서비스 어카운트 생성 후 확인
- 파드 기동 시 서비스 어카운트 한 개가 할당되며, 서비스 어카운트 기반 인증/인가를 함, 미지정 시 기본 서비스 어카운트가 할당
- 서비스 어카운트에 자동 생성된 시크릿에 저장된 토큰으로 쿠버네티스 API에 대한 인증 정보로 사용 할 수 있다. ← 1.23 이전 버전의 경우에만 해당
설치되는 EKS 버전은 1.24 이다.
따라서, SA를 생성해도 Secret이 자동으로 생성되지 않는다.
# 네임스페이스(Namespace, NS) 생성 및 확인
kubectl create namespace dev-team
kubectl create ns infra-team
# 네임스페이스 확인
kubectl get ns
NAME STATUS AGE
default Active 14m
dev-team Active 15s
infra-team Active 14s
kube-node-lease Active 14m
kube-public Active 14m
kube-system Active 14m
# 네임스페이스에 각각 서비스 어카운트 생성 : serviceaccounts 약자(=sa)
k version --short | grep Server
Server Version: v1.24.13-eks-0a21954
# 쿠버네티스 버전이 1.24 이기 때문에 아래와 같이 serviceaccount와 secret RBAC를 생성해준다.
# kubectl create sa dev-k8s -n dev-team (x)
# kubectl create sa infra-k8s -n infra-team (x)
cat <<EOF | kubectl create -f -
apiVersion: v1
kind: ServiceAccount
metadata:
name: dev-k8s
namespace: dev-team
---
apiVersion: v1
kind: Secret
type: kubernetes.io/service-account-token
metadata:
name: dev-k8s
namespace: dev-team
annotations:
kubernetes.io/service-account.name: "dev-k8s"
EOF
k apply -f dev-team-serviceaccount.yaml -n dev-team
serviceaccount/dev-k8s unchanged
secret/dev-k8s unchanged
# 서비스 어카운트 정보 확인
kubectl get sa -n dev-team
NAME SECRETS AGE
default 0 34m
dev-k8s 0 4m17s
kubectl get secret -n dev-team
NAME TYPE DATA AGE
dev-k8s kubernetes.io/service-account-token 3 4m22s
kubectl get sa dev-k8s -n dev-team -o yaml | yh
apiVersion: v1
kind: ServiceAccount
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"v1","kind":"ServiceAccount","metadata":{"annotations":{},"name":"dev-k8s","namespace":"dev-team"}}
creationTimestamp: "2023-05-29T14:58:24Z"
name: dev-k8s
namespace: dev-team
resourceVersion: "10585"
uid: dae5f46b-e130-4759-87a4-e9a1fd5bc774
cat <<EOF | kubectl create -f -
apiVersion: v1
kind: ServiceAccount
metadata:
name: infra-k8s
namespace: infra-team
---
apiVersion: v1
kind: Secret
type: kubernetes.io/service-account-token
metadata:
name: infra-k8s
namespace: infra-team
annotations:
kubernetes.io/service-account.name: "infra-k8s"
EOF
k apply -f infra-team-serviceaccount.yaml -n infra-team
serviceaccount/infra-k8s created
secret/infra-k8s created
kubectl get sa -n infra-team
NAME SECRETS AGE
default 0 42s
infra-k8s 0 16s
k get secret -n infra-team
NAME TYPE DATA AGE
infra-k8s kubernetes.io/service-account-token 3 7s
kubectl get sa infra-k8s -n infra-team -o yaml | yh
apiVersion: v1
kind: ServiceAccount
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"v1","kind":"ServiceAccount","metadata":{"annotations":{},"name":"infra-k8s","namespace":"infra-team"}}
creationTimestamp: "2023-05-29T15:08:06Z"
name: infra-k8s
namespace: infra-team
resourceVersion: "13024"
uid: 743b8888-5808-4c86-81e9-749202fa1f4a
(심화 참고) dev-k8s 서비스어카운트의 토큰 정보 확인
- https://jwt.io/ → Bearer type - JWT(JSON Web Token) - 링크
# dev-k8s 서비스어카운트의 토큰 정보 확인?? 안된다.
DevTokenName=$(kubectl get sa dev-k8s -n dev-team -o jsonpath="{.secrets[0].name}")
DevToken=$(kubectl get secret -n dev-team $DevTokenName -o jsonpath="{.data.token}" | base64 -d)
echo $DevToken
# 직접 sa 이름을 적어준다.
kubectl get secret -n dev-team dev-k8s -o jsonpath="{.data.token}" | base64 -d
eyJhbGciOiJSUzI1NiIsImtpZCI6Ijg2dFRWNVNaeFlGcDJlR0tuRDJDWWpWaHJxbXF4clBnNnIzbjlmMktvTjgifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZXYtdGVhbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJkZXYtazhzIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImRldi1rOHMiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiJkYWU1ZjQ2Yi1lMTMwLTQ3NTktODdhNC1lOWExZmQ1YmM3NzQiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6ZGV2LXRlYW06ZGV2LWs4cyJ9.mHYqgVHJ96aeI4YwG8Jr3MZy5694TpgPb8srtvaVc32MhmUst9J4MY_Ynq_G-Nw79SA6kJz_9-A77GYUVOhLrBfseee33nDSdBRuG_-40_kD1RW_E7Ysn7353YUQr__1ipILW6pOIHlTEDPJL27f6CQ9uMKnzEvKM2nLMXboQVp2V3PWL_CO-N-1oBMrKLYFp6yMSTWc9ZCZrksTntaFG-qNWwPmWvb0gDqPPkgEmhxT4PKUycwaRx0h7EOQIheoD5XXAtTEsad7iKya06rh5AogG5Ci_bTC87Tdds4MV-kRNhPQ-0-GstIk6uApToEAdh5E8GjVrnBkkDAK4wTikQ
서비스 어카운트를 지정하여 파드 생성 후 권한 테스트
# 각각 네임스피이스에 kubectl 파드 생성 - 컨테이너이미지
# docker run --rm --name kubectl -v /path/to/your/kube/config:/.kube/config bitnami/kubectl:latest
cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
name: dev-kubectl
namespace: dev-team
spec:
serviceAccountName: dev-k8s
containers:
- name: kubectl-pod
image: bitnami/kubectl:1.24.10
command: ["tail"]
args: ["-f", "/dev/null"]
terminationGracePeriodSeconds: 0
EOF
k get po -n dev-team
NAME READY STATUS RESTARTS AGE
dev-kubectl 1/1 Running 0 5s
cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
name: infra-kubectl
namespace: infra-team
spec:
serviceAccountName: infra-k8s
containers:
- name: kubectl-pod
image: bitnami/kubectl:1.24.10
command: ["tail"]
args: ["-f", "/dev/null"]
terminationGracePeriodSeconds: 0
EOF
k get po -n infra-team
NAME READY STATUS RESTARTS AGE
infra-kubectl 1/1 Running 0 7s
# 확인
kubectl get pod -A
kubectl get pod -A
NAMESPACE NAME READY STATUS RESTARTS AGE
dev-team dev-kubectl 1/1 Running 0 6m32s
infra-team infra-kubectl 1/1 Running 0 17s
kube-system aws-load-balancer-controller-5f99d5f58f-4jzmz 1/1 Running 0 42m
kube-system aws-load-balancer-controller-5f99d5f58f-wt9hn 1/1 Running 0 42m
kube-system aws-node-9ljgx 1/1 Running 0 46m
...
kubectl get pod -o dev-kubectl -n dev-team -o yaml
serviceAccount: dev-k8s
...
kubectl get pod -o infra-kubectl -n infra-team -o yaml
serviceAccount: infra-k8s
...
# 파드에 기본 적용되는 서비스 어카운트(토큰) 정보 확인
kubectl exec -it dev-kubectl -n dev-team -- ls /run/secrets/kubernetes.io/serviceaccount
ca.crt namespace token
kubectl exec -it dev-kubectl -n dev-team -- cat /run/secrets/kubernetes.io/serviceaccount/token
eyJhbGciOiJSUzI1NiIsImtpZCI6...
kubectl exec -it dev-kubectl -n dev-team -- cat /run/secrets/kubernetes.io/serviceaccount/namespace
dev-team
kubectl exec -it dev-kubectl -n dev-team -- cat /run/secrets/kubernetes.io/serviceaccount/ca.crt
-----BEGIN CERTIFICATE-----
MIIC/jCCAeagAwIBAgIBADANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwprdWJl
...
ee4Feyg0iB2jMq1pWPiUzFbhnOdBB/GkMB5bK/mg8bt9WKxAw9U/CF3NB5D7pShF
NJU=
-----END CERTIFICATE-----
# 각각 파드로 Shell 접속하여 정보 확인 : 단축 명령어(alias) 사용
alias k1='kubectl exec -it dev-kubectl -n dev-team -- kubectl'
alias k2='kubectl exec -it infra-kubectl -n infra-team -- kubectl'
# 권한 테스트
k1 get pods # kubectl exec -it dev-kubectl -n dev-team -- kubectl get pods 와 동일한 실행 명령이다!
Error from server (Forbidden): pods is forbidden: User "system:serviceaccount:dev-team:dev-k8s" cannot list resource "pods" in API group "" in the namespace "dev-team"
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 와 동일한 실행 명령이다!
Error from server (Forbidden): pods is forbidden: User "system:serviceaccount:infra-team:infra-k8s" cannot list resource "pods" in API group "" in the namespace "infra-team"
k2 run nginx --image nginx:1.20-alpine
k2 get pods -n kube-system
# (옵션) kubectl auth can-i 로 kubectl 실행 사용자가 특정 권한을 가졌는지 확인
k1 auth can-i get pods
no
각각 네임스페이스에 롤(Role)를 생성 후 서비스 어카운트 바인딩
아래에는 간단하게 admin 역할을 생성 후 바인딩 한다.
- 롤(Role) : apiGroups 와 resources 로 지정된 리소스에 대해 verbs 권한을 인가
- 실행 가능한 조작(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
NAME CREATED AT
role-dev-team 2023-05-29T15:25:03Z
kubectl get roles -n infra-team
NAME CREATED AT
role-infra-team 2023-05-29T15:25:09Z
kubectl get roles -n dev-team -o yaml
kubectl describe roles role-dev-team -n dev-team
...
PolicyRule:
Resources Non-Resource URLs Resource Names Verbs
--------- ----------------- -------------- -----
*.* [] [] [*]
# 롤바인딩 생성 : '서비스어카운트 <-> 롤' 간 서로 연동
cat <<EOF | kubectl create -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: roleB-dev-team
namespace: dev-team
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: role-dev-team
subjects:
- kind: ServiceAccount
name: dev-k8s
namespace: dev-team
EOF
cat <<EOF | kubectl create -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: roleB-infra-team
namespace: infra-team
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: role-infra-team
subjects:
- kind: ServiceAccount
name: infra-k8s
namespace: infra-team
EOF
# 롤바인딩 확인
kubectl get rolebindings -n dev-team
NAME ROLE AGE
roleB-dev-team Role/role-dev-team 13s
kubectl get rolebindings -n infra-team
NAME ROLE AGE
roleB-infra-team Role/role-infra-team 3m26s
kubectl get rolebindings -n dev-team -o yaml
kubectl describe rolebindings roleB-dev-team -n dev-team
...
Role:
Kind: Role
Name: role-dev-team
Subjects:
Kind Name Namespace
---- ---- ---------
ServiceAccount dev-k8s dev-team
서비스 어카운트를 지정하여 생성한 파드에서 다시 권한 테스트
# 각각 파드로 Shell 접속하여 정보 확인 : 단축 명령어(alias) 사용
alias k1='kubectl exec -it dev-kubectl -n dev-team -- kubectl'
alias k2='kubectl exec -it infra-kubectl -n infra-team -- kubectl'
# 권한 테스트
k1 get pods
k1 run nginx --image nginx:1.20-alpine
k1 get pods
k1 delete pods nginx
k1 get pods -n kube-system
k1 get nodes
k2 get pods
NAME READY STATUS RESTARTS AGE
infra-kubectl 1/1 Running 0 31s
k2 run nginx --image nginx:1.20-alpine
pod/nginx created
k2 get pods
NAME READY STATUS RESTARTS AGE
infra-kubectl 1/1 Running 0 52s
nginx 1/1 Running 0 16s
k2 delete pods nginx
pod "nginx" deleted
# 당연히 안된다! 이유는 다들 아실거라고 생각이 된다~ 모르면 구글링!
k2 get pods -n kube-system
Error from server (Forbidden): pods is forbidden: User "system:serviceaccount:infra-team:infra-k8s" cannot list resource "pods" in API group "" in the namespace "kube-system"
k2 get nodes
# (옵션) kubectl auth can-i 로 kubectl 실행 사용자가 특정 권한을 가졌는지 확인
k1 auth can-i get pods
yes
리소스 삭제
kubectl delete ns dev-team infra-team
namespace "dev-team" deleted
namespace "infra-team" deleted
2. EKS 인증/인가
사용자/애플리케이션 → k8s 사용 시 ⇒ 인증은 AWS IAM, 인가는 K8S RBAC 이다.
2023.03.30 - [AWS] - AWS IAM이란?
2023.04.19 - [Container Orchestration/Kubernetes] - Kubernetes API Server, Group / RBAC란?
RBAC 관련 krew 플러그인
# 설치
kubectl krew install access-matrix rbac-tool rbac-view rolesum
# Show an RBAC access matrix for server resources
kubectl access-matrix # Review access to cluster-scoped resources
kubectl access-matrix --namespace default # Review access to namespaced resources in 'default'
# RBAC Lookup by subject (user/group/serviceaccount) name
kubectl rbac-tool lookup
kubectl rbac-tool lookup system:masters
SUBJECT | SUBJECT TYPE | SCOPE | NAMESPACE | ROLE
+----------------+--------------+-------------+-----------+---------------+
system:masters | Group | ClusterRole | | cluster-admin
kubectl rbac-tool lookup system:nodes # eks:node-bootstrapper
kubectl rbac-tool lookup system:bootstrappers # eks:node-bootstrapper
kubectl describe ClusterRole eks:node-bootstrapper
# RBAC List Policy Rules For subject (user/group/serviceaccount) name
kubectl rbac-tool policy-rules
kubectl rbac-tool policy-rules -e '^system:.*'
kubectl rbac-tool show
#
kubectl rbac-tool whoami
{Username: "kubernetes-admin",
UID: "aws-iam-authenticator:6118xxxxxxxx:AIDAY45ESHEKOUPOLBOMM",
Groups: ["system:masters",
"system:authenticated"],
Extra: {accessKeyId: ["AKIAY45ESHEKEHEB7SXG"],
arn: ["arn:aws:iam::6118xxxxxxxx:user/somaz"],
canonicalArn: ["arn:aws:iam::6118xxxxxxxx:user/somaz"],
principalId: ["AIDAYxxxxxxxxxxxxxxxxxx"],
sessionName: [""]}}
# Summarize RBAC roles for subjects : ServiceAccount(default), User, Group
kubectl rolesum -h
kubectl rolesum aws-node -n kube-system
kubectl rolesum -k User system:kube-proxy
kubectl rolesum -k Group system:masters
# [터미널1] A tool to visualize your RBAC permissions
kubectl rbac-view
INFO[0000] Getting K8s client
INFO[0000] serving RBAC View and http://localhost:8800
## 이후 해당 작업용PC 공인 IP:8800 웹 접속
echo -e "RBAC View Web http://$(curl -s ipinfo.io/ip):8800"
RBAC View Web http://3.xx.xx.xx:8800
인증/인가 완벽 분석 해보기
핵심 : 인증은 AWS IAM, 인가는 K8S RBAC에서 처리
1. kubectl 명령 → aws eks get-token → EKS Service endpoint(STS)에 토큰 요청한다.
- STS Security Token Service : AWS 리소스에 대한 액세스를 제어할 수 있는 임시 보안 자격 증명(STS)을 생성하여 신뢰받는 사용자에게 제공할 수 있다.
- AWS CLI 버전 1.16.156 이상에서는 별도 aws-iam-authenticator 설치 없이 aws eks get-token으로 사용 가능 - Docs
# sts caller id의 ARN 확인
aws sts get-caller-identity --query Arn
"arn:aws:iam::<자신의 Account ID>:user/admin"
"arn:aws:iam::6118xxxxxxxx:user/somaz"
# 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
{
"kind": "ExecCredential",
"apiVersion": "client.authentication.k8s.io/v1beta1",
"spec": {},
"status": {
"expirationTimestamp": "2023-05-29T15:52:20Z",
"token": "k8s-aws-v1.aHR0cHMxxxx...."
}
}
aws eks get-token --cluster-name $CLUSTER_NAME | jq -r '.status.token'
k8s-aws-v1.aHR0cHMxxx....
2. kubectl의 Client-Go 라이브러리는 Pre-Signed URL을 Bearer Token으로 EKS API Cluster Endpoint로 요청을 보낸다.
- 토큰을 jwt 사이트에 복붙으로 디코드 정보 확인(HS384 → HS256) PAYLOAD 정보 확인 : 일반적인 AWS API 호출과 유사하다.
- JWT(JSON Web Token) : 당사자 간에 정보를 JSON 객체로 안전하게 전송하기 위한 간결하고 독립적인 방법을 정의하는 표준(RFC 7519)이다.
PAYLOAD의 값을 URL Decode Online 에서 DECODE로 확인
https://sts.ap-northeast-2.amazonaws.com/?
Action=GetCallerIdentity&
Version=2011-06-15&
X-Amz-Algorithm=AWS4-HMAC-SHA256&
X-Amz-Credential=AKIA5ILF.../20230525/ap-northeast-2/sts/aws4_request&
X-Amz-Date=20230525T120720Z&
X-Amz-Expires=60&
X-Amz-SignedHeaders=host;x-k8s-aws-id&
X-Amz-Signature=6e09b846da702767f38c78831986cb558.....
3. EKS API는 Token Review 를 Webhook token authenticator에 요청 ⇒ (STS GetCallerIdentity 호출) AWS IAM 해당 호출 인증 완료 후 User/Role에 대한 ARN 반환한다.
# tokenreviews api 리소스 확인
kubectl api-resources | grep authentication
tokenreviews authentication.k8s.io/v1 false TokenReview
# List the fields for supported resources.
kubectl explain tokenreviews
...
DESCRIPTION:
TokenReview attempts to authenticate a token to a known user. Note:
TokenReview requests may be cached by the webhook token authenticator
plugin in the kube-apiserver.
4. 쿠버네티스 RBAC 인가를 처리한다. 가장 중요한 정보는 aws-auth configmap이다.
- 해당 IAM User/Role 확인이 되면 k8s aws-auth configmap에서 mapping 정보를 확인하게 됩니다.
- aws-auth 컨피그맵에 'IAM 사용자, 역할 arm, K8S 오브젝트' 로 권한 확인 후 k8s 인가 허가가 되면 최종적으로 동작 실행을 합니다.
- 참고로 EKS를 생성한 IAM principal은 aws-auth 와 상관없이 kubernetes-admin Username으로 system:masters 그룹에 권한을 가짐 - 링크
# Webhook api 리소스 확인
kubectl api-resources | grep Webhook
mutatingwebhookconfigurations admissionregistration.k8s.io/v1 false MutatingWebhookConfiguration
validatingwebhookconfigurations admissionregistration.k8s.io/v1 false ValidatingWebhookConfiguration
# validatingwebhookconfigurations 리소스 확인
kubectl get validatingwebhookconfigurations
NAME WEBHOOKS AGE
eks-aws-auth-configmap-validation-webhook 1 50m
vpc-resource-validating-webhook 2 50m
aws-load-balancer-webhook 3 8m27s
kubectl get validatingwebhookconfigurations eks-aws-auth-configmap-validation-webhook -o yaml | kubectl neat | yh
# aws-auth 컨피그맵 확인(해당 컨피그맵을 활용해서, 다른 account의 계정도 접근가능하게 설정할 수 있다.)
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::61184.....:role/eksctl-myeks-nodegroup-ng1-NodeInstanceRole-I6F14U2QK492
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:61184...:AIDA5ILF2FJIR2.....",
Groups: ["system:masters",
"system:authenticated"],
...
# system:masters , system:authenticated 그룹의 정보 확인
kubectl rbac-tool lookup system:masters
kubectl rbac-tool lookup system:authenticated
kubectl rolesum -k Group system:masters
kubectl rolesum -k Group system:authenticated
# system:masters 그룹이 사용 가능한 클러스터 롤 확인 : cluster-admin
kubectl describe clusterrolebindings.rbac.authorization.k8s.io cluster-admin
Name: cluster-admin
Labels: kubernetes.io/bootstrapping=rbac-defaults
Annotations: rbac.authorization.kubernetes.io/autoupdate: true
Role:
Kind: ClusterRole
Name: cluster-admin
Subjects:
Kind Name Namespace
---- ---- ---------
Group system:masters
# cluster-admin 의 PolicyRule 확인 : 모든 리소스 사용 가능!
kubectl describe clusterrole cluster-admin
Name: cluster-admin
Labels: kubernetes.io/bootstrapping=rbac-defaults
Annotations: rbac.authorization.kubernetes.io/autoupdate: true
PolicyRule:
Resources Non-Resource URLs Resource Names Verbs
--------- ----------------- -------------- -----
*.* [] [] [*]
[*] [] [*]
# system:authenticated 그룹이 사용 가능한 클러스터 롤 확인
kubectl describe ClusterRole system:discovery
kubectl describe ClusterRole system:public-info-viewer
kubectl describe ClusterRole system:basic-user
kubectl describe ClusterRole eks:podsecuritypolicy:privileged
데브옵스 신입 사원을 위한 myeks-bastion-2에 설정해보기
1. testuser 사용자 생성
# testuser 사용자 생성
aws iam create-user --user-name testuser
{
"User": {
"Path": "/",
"UserName": "testuser",
"UserId": "AIDAYxxxxx",
"Arn": "arn:aws:iam::6118xxxxxxxx:user/testuser",
"CreateDate": "2023-05-29T15:59:15+00:00"
}
}
# 사용자에게 프로그래밍 방식 액세스 권한 부여
aws iam create-access-key --user-name testuser
{
"AccessKey": {
"UserName": "testuser",
"AccessKeyId": "AIDAYxxx",
"Status": "Active",
"SecretAccessKey": "b1J8Bh7Y##",
"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::6118xxxxxxxx:user/somaz"
# EC2 IP 확인 : myeks-bastion-EC2-2 PublicIPAdd 확인
aws ec2 describe-instances --query "Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,PrivateIPAdd:PrivateIpAddress,InstanceName:Tags[?Key=='Name']|[0].Value,Status:State.Name}" --filters Name=instance-state-name,Values=running --output table
----------------------------------------------------------------------
| DescribeInstances |
+---------------------+-----------------+-----------------+----------+
| InstanceName | PrivateIPAdd | PublicIPAdd | Status |
+---------------------+-----------------+-----------------+----------+
| myeks-ng1-Node | 192.168.3.8 | 3.34.183.xxx | running |
| myeks-ng1-Node | 192.168.2.151 | 3.34.30.xxx | running |
| myeks-ng1-Node | 192.168.1.143 | 3.36.72.xx | running |
| myeks-bastion-EC2-2| 192.168.1.200 | 43.201.31.168 | running |
| myeks-bastion-EC2 | 192.168.1.100 | 3.35.18.xxx | running |
+---------------------+-----------------+-----------------+----------+
2. testuser 자격증명 설정 및 확인
# get-caller-identity 확인 >> 왜 안될까요?(aws configure) 때문
aws sts get-caller-identity --query Arn
Unable to locate credentials. You can configure credentials by running "aws configure".
# 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::6118xxxxxxxx:user/somaz"
# kubectl 시도 >> testuser도 AdministratorAccess 권한을 가지고 있는데, 실패 이유는? .kube/config 파일이 없기 때문!
kubectl get node -v6
I0530 01:04:19.950008 32707 round_trippers.go:553] GET http://localhost:8080/api?timeout=32s in 0 milliseconds
E0530 01:04:19.950108 32707 memcache.go:265] couldn't get current server API group list: Get "http://localhost:8080/api?timeout=32s": dial tcp 127.0.0.1:8080: connect: connection refused
ls ~/.kube
ls: cannot access /root/.kube: No such file or directory
3. testuser에 system:masters 그룹 부여로 EKS 관리자 수준 권한 설정
# 방안1 : eksctl 사용 >> iamidentitymapping 실행 시 aws-auth 컨피그맵 작성해줌(첫번째 bastion서버로 와서 실행)
# Creates a mapping from IAM role or user to Kubernetes user and groups
eksctl create iamidentitymapping --cluster $CLUSTER_NAME --username testuser --group system:masters --arn arn:aws:iam::$ACCOUNT_ID:user/testuser
# 확인
kubectl get cm -n kube-system aws-auth -o yaml | kubectl neat | yh
apiVersion: v1
data:
mapRoles: |
- groups:
- system:bootstrappers
- system:nodes
rolearn: arn:aws:iam::6118xxxxxxxx:role/eksctl-myeks-nodegroup-ng1-NodeInstanceRole-I6F14U2QK492
username: system:node:{{EC2PrivateDNSName}}
mapUsers: |
- groups:
- system:masters
userarn: arn:aws:iam::6118xxxxxxxx:user/testuser
username: testuser
kind: ConfigMap
metadata:
name: aws-auth
namespace: kube-system
kubectl get node -v6
I0530 01:07:23.027601 16013 loader.go:374] Config loaded from file: /root/.kube/config
I0530 01:07:24.003117 16013 round_trippers.go:553] GET https://BFCC1FC7944D4ADF6C383773FB300C86.yl4.ap-northeast-2.eks.amazonaws.com/api/v1/nodes?limit=500 200 OK in 966 milliseconds
NAME STATUS ROLES AGE VERSION
ip-192-168-1-143.ap-northeast-2.compute.internal Ready <none> 103m v1.24.13-eks-0a21954
ip-192-168-2-151.ap-northeast-2.compute.internal Ready <none> 103m v1.24.13-eks-0a21954
ip-192-168-3-8.ap-northeast-2.compute.internal Ready <none> 103m v1.24.13-eks-0a21954
# 방안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::6118xxxxxxxx:role/eksctl-myeks-nodegroup-ng1-NodeInstanceRole-I6F14U2QK492 system:node:{{EC2PrivateDNSName}} system:bootstrappers,system:nodes
arn:aws:iam::6118xxxxxxxx:user/testuser testuser system:masters
4. testuser kubeconfig 생성 및 kubectl 사용 확인
# testuser kubeconfig 생성 >> aws eks update-kubeconfig 실행이 가능한 이유는?, 3번 설정 후 약간의 적용 시간 필요
aws eks update-kubeconfig --name $CLUSTER_NAME --user-alias testuser
Added new context testuser to /root/.kube/config
(testuser:N/A) [root@myeks-bastion-2 ~]#
# 첫번째 bastic ec2의 config와 비교해보자
cat ~/.kube/config | yh
# kubectl 사용 확인
kubectl ns default
kubectl get node -v6
NAME STATUS ROLES AGE VERSION
ip-192-168-1-143.ap-northeast-2.compute.internal Ready <none> 105m v1.24.13-eks-0a21954
ip-192-168-2-151.ap-northeast-2.compute.internal Ready <none> 105m v1.24.13-eks-0a21954
ip-192-168-3-8.ap-northeast-2.compute.internal Ready <none> 105m v1.24.13-eks-0a21954
# 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::6118xxxxxxxx:user/testuser"],
canonicalArn: ["arn:aws:iam::6118xxxxxxxx:user/testuser"],
...
5. 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
6. testuser kubectl 사용 확인
# 시도
kubectl get node -v6
kubectl api-resources -v5
7. testuser IAM 맵핑 삭제
# testuser IAM 맵핑 삭제
eksctl delete iamidentitymapping --cluster $CLUSTER_NAME --arn arn:aws:iam::$ACCOUNT_ID:user/testuser
2023-05-30 01:10:39 [ℹ] removing identity "arn:aws:iam::611841095956:user/testuser" from auth ConfigMap (username = "testuser", groups = ["system:masters"])
# Get IAM identity mapping(s)
eksctl get iamidentitymapping --cluster $CLUSTER_NAME
ARN USERNAME GROUPS ACCOUNT
arn:aws:iam::6118xxxxxxxx:role/eksctl-myeks-nodegroup-ng1-NodeInstanceRole-I6F14U2QK492 system:node:{{EC2PrivateDNSName}} system:bootstrappers,system:nodes
kubectl get cm -n kube-system aws-auth -o yaml | yh
apiVersion: v1
data:
mapRoles: |
- groups:
- system:bootstrappers
- system:nodes
rolearn: arn:aws:iam::6118xxxxxxxx:role/eksctl-myeks-nodegroup-ng1-NodeInstanceRole-I6F14U2QK492
username: system:node:{{EC2PrivateDNSName}}
mapUsers: |
[]
kind: ConfigMap
metadata:
creationTimestamp: "2023-05-29T14:23:12Z"
name: aws-auth
namespace: kube-system
resourceVersion: "28794"
uid: 6c71a563-7354-4cc4-88dc-8a6919121dd2
8. testuser kubectl 사용 확인
# 시도
kubectl get node -v6
I0530 01:15:24.813685 1274 round_trippers.go:553] GET http://localhost:8080/api?timeout=32s in 0 milliseconds
E0530 01:15:24.814005 1274 memcache.go:265] couldn't get current server API group list: Get "http://localhost:8080/api?timeout=32s": dial tcp 127.0.0.1:8080: connect: connection refused
kubectl api-resources -v5
9. config 샘플 - 링크
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: v1
data:
mapRoles: |
- groups:
- system:bootstrappers
- system:nodes
rolearn: arn:aws:iam::111122223333:role/my-role
username: system:node:{{EC2PrivateDNSName}}
- groups:
- eks-console-dashboard-full-access-group
rolearn: arn:aws:iam::111122223333:role/my-console-viewer-role
username: my-console-viewer-role
mapUsers: |
- groups:
- system:masters
userarn: arn:aws:iam::111122223333:user/admin
username: admin
- groups:
- eks-console-dashboard-restricted-access-group
userarn: arn:aws:iam::444455556666:user/my-user
username: my-user
3. IRSA
간단하게 IRSA에 대해서 정리해 보았다.
2023.05.28 - [AWS] - AWS IRSA(IAM Roles for Service Accounts)란?
EC2 Instance Profile : 사용하기 편하지만, 최소 권한 부여 원칙에 위배하며 보안상 권고하지 않음 - 링크
→ IRSA를 써야한다.
# 설정 예시 1 : eksctl 사용 시
eksctl create cluster --name $CLUSTER_NAME ... --external-dns-access --full-ecr-access --asg-access
# 설정 예시 2 : eksctl로 yaml 파일로 노드 생성 시
cat myeks.yaml | yh
...
managedNodeGroups:
- amiFamily: AmazonLinux2
iam:
withAddonPolicies:
albIngress: false
appMesh: false
appMeshPreview: false
autoScaler: true
awsLoadBalancerController: false
certManager: true
cloudWatch: true
ebs: false
efs: false
externalDNS: true
fsx: false
imageBuilder: true
xRay: false
...
# 설정 예시 3 : 테라폼
...
동작은 아래와 같다.
k8s파드 → AWS 서비스 사용 시 ⇒ AWS STS/IAM ↔ IAM OIDC Identity Provider(EKS IdP) 인증/인가
Service Account Token Volume Projection이란?
서비스 계정 토큰의 시크릿 기반 볼륨 대신 projected volume 사용한다.
토큰을 사용하는 대상(audience), 유효 기간(expiration) 등 토큰의 속성을 지정할 필요가 있기 때문이다.
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- image: nginx
name: nginx
volumeMounts:
- mountPath: /var/run/secrets/tokens
name: vault-token
serviceAccountName: build-robot
volumes:
- name: vault-token
projected:
sources:
- serviceAccountToken:
path: vault-token
expirationSeconds: 7200
audience: vault
Bound Service Account Token Volume 바인딩된 서비스 어카운트 토큰 볼륨
서비스 어카운트 어드미션 컨트롤러는 토큰 컨트롤러에서 생성한 만료되지 않은 서비스 계정 토큰에 시크릿 기반 볼륨 대신 다음과 같은 프로젝티드 볼륨을 추가한다.
- name: kube-api-access-<random-suffix>
projected:
defaultMode: 420 # 420은 rw- 로 소유자는 읽고쓰기 권한과 그룹내 사용자는 읽기만, 보통 0644는 소유자는 읽고쓰고실행 권한과 나머지는 읽고쓰기 권한
sources:
- serviceAccountToken:
expirationSeconds: 3607
path: token
- configMap:
items:
- key: ca.crt
path: ca.crt
name: kube-root-ca.crt
- downwardAPI:
items:
- fieldRef:
apiVersion: v1
fieldPath: metadata.namespace
path: namespace
IRSA 소개
AWS SDK는 AWS_ROLE_ARN 및 AWS_WEB_IDENTITY_TOKEN_FILE 이름의 환경변수를 읽어들여 Web Identity 토큰으로 AssumeRoleWithWebIdentify를 호출함으로써 Assume Role을 시도하여 임시 자격 증명을 획득하고, 특정 IAM Role 역할을 사용할 수 있게 된다.
이때 Assume Role 동작을 위한 인증은 AWS가 아닌 외부 Web IdP(EKS IdP)에 위임하여 처리한다.
실습1
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
EOF
# 확인(SA Token 볼륨이 없다?)
kubectl get pod
NAME READY STATUS RESTARTS AGE
eks-iam-test1 0/1 Error 0 15s
kubectl describe pod
Name: eks-iam-test1
Namespace: default
Priority: 0
Service Account: default
Node: ip-192-168-2-223.ap-northeast-2.compute.internal/192.168.2.223
Start Time: Sat, 03 Jun 2023 18:56:42 +0900
Labels: <none>
Annotations: kubernetes.io/psp: eks.privileged
Status: Failed
IP: 192.168.2.81
IPs:
IP: 192.168.2.81
Containers:
my-aws-cli:
Container ID: containerd://426d21da280e9709f6204ab41db79c6b802e572d43a4018aba629e5e705dc9e6
Image: amazon/aws-cli:latest
Image ID: docker.io/amazon/aws-cli@sha256:221ff6588005667221ff7e5e3b6cde2e1d2dd04fbe124652292e5fac652a8234
...
Volumes: <none>
# 로그 확인
kubectl logs eks-iam-test1
An error occurred (AccessDenied) when calling the ListBuckets operation: Access Denied
# 파드1 삭제
kubectl delete pod eks-iam-test1
- CloudTrail 이벤트 ListBuckets 확인 → 기록 표시까지 약간의 시간 필요
{
...
"userIdentity": {
"type": "AssumedRole",
"principalId": "xxxx",
"arn": "arn:aws:sts::111122223333:assumed-role/eksctl-eks-oidc-demo-nodegroup-ng-NodeInstanceRole-xxxx/xxxx",
"accountId": "111122223333",
"accessKeyId": "AKIAIOSFODNN7EXAMPLE",
"sessionContext": {
"sessionIssuer": {
"type": "Role",
"principalId": "xxxx",
"arn": "arn:aws:iam::xxxx:role/eksctl-eks-oidc-demo-nodegroup-ng-NodeInstanceRole-xxxx",
"accountId": "111122223333",
"userName": "eksctl-eks-oidc-demo-nodegroup-ng-NodeInstanceRole-xxxx"
},
"webIdFederationData": {},
"attributes": {
"creationDate": "2021-12-04T14:54:49Z",
"mfaAuthenticated": "false"
},
"ec2RoleDelivery": "2.0"
}
},
"eventTime": "2021-12-04T15:09:20Z",
"eventSource": "s3.amazonaws.com",
"eventName": "ListBuckets",
"awsRegion": "us-east-2",
"sourceIPAddress": "192.0.2.1",
"userAgent": "[aws-cli/2.4.5 Python/3.8.8 Linux/5.4.156-83.273.amzn2.x86_64 docker/x86_64.amzn.2 prompt/off command/s3.ls]",
"errorCode": "AccessDenied",
"errorMessage": "Access Denied",
"requestParameters": {
"Host": "s3.us-east-2.amazonaws.com"
},
...
}
실습2 - Kubernetes Service Accounts : https://jwt.io/
이번에는 ServiceAccountToken의 자동 발급 기능 OFF 하지 않았다.
Service Account를 생성하게 되면 Kubernetes Secret에서 JWT token을 자동으로 생성한다.
# 파드2 생성
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: eks-iam-test2
spec:
containers:
- name: my-aws-cli
image: amazon/aws-cli:latest
command: ['sleep', '36000']
restartPolicy: Never
EOF
# 확인( 이번엔 SA volume이 잘 생성되었다. )
kubectl get pod
NAME READY STATUS RESTARTS AGE
eks-iam-test2 1/1 Running 0 4s
kubectl describe pod
Name: eks-iam-test2
Namespace: default
Priority: 0
Service Account: default
Node: ip-192-168-2-223.ap-northeast-2.compute.internal/192.168.2.223
Start Time: Sat, 03 Jun 2023 18:59:28 +0900
Labels: <none>
Annotations: kubernetes.io/psp: eks.privileged
Status: Running
IP: 192.168.2.81
...
Volumes:
kube-api-access-4rz29:
Type: Projected (a volume that contains injected data from multiple sources)
TokenExpirationSeconds: 3607
# aws 서비스 사용 시도(그래도 아직도 조회 되지 않는다.)
kubectl exec -it eks-iam-test2 -- aws s3 ls
An error occurred (AccessDenied) when calling the ListBuckets operation: Access Denied
command terminated with exit code 254
# 서비스 어카운트 토큰 확인
SA_TOKEN=$(kubectl exec -it eks-iam-test2 -- cat /var/run/secrets/kubernetes.io/serviceaccount/token)
echo $SA_TOKEN
eyJhbGciOiJSUzI1NiIsImtpZCI6IjNiYT...
# jwt 혹은 아래 JWT 웹 사이트 이용
jwt decode $SA_TOKEN --json --iso8601
...
#헤더
{
"alg": "RS256",
"kid": "3ba384644812773d17b7b6e0514eafb99bf31037"
}
# 페이로드 : OAuth2에서 쓰이는 aud, exp 속성 확인! > projectedServiceAccountToken 기능으로 토큰에 audience,exp 항목을 덧붙힘
## iss 속성 : EKS OpenID Connect Provider(EKS IdP) 주소 > 이 EKS IdP를 통해 쿠버네티스가 발급한 토큰이 유요한지 검증
{
"aud": [
"https://kubernetes.default.svc"
],
"exp": 1717322859,
"iat": 1685786859,
"iss": "https://oidc.eks.ap-northeast-2.amazonaws.com/id/7ED7FBC0122AD2374C52640AD4B0F4E4",
"kubernetes.io": {
"namespace": "default",
"pod": {
"name": "eks-iam-test2",
"uid": "2cc0c42a-af3c-4d1d-bcc4-f0a676940d93"
},
"serviceaccount": {
"name": "default",
"uid": "881c3d7f-1518-41ff-8a4d-83f3840404ae"
},
"warnafter": 1685790466
},
"nbf": 1685786859,
"sub": "system:serviceaccount:default:default"
}
# 파드2 삭제
kubectl delete pod eks-iam-test2
실습3
- amazon-eks-pod-identity-webhook : This webhook is for mutating pods that will require AWS IAM access
- The eksctl create iamserviceaccount command creates
- A Kubernetes Service Account
- An IAM role with the specified IAM policy
- A trust policy on that IAM role
aws-eks-pod-identity-webhook을 사용해 IRSA를 사용할 수 있게 조치해본다.
# Create an iamserviceaccount - AWS IAM role bound to a Kubernetes service account
# my-sa라는 Serviceaccount에 S3를 읽을 수 있는 권한을 부여하고 새로 생성한다.
eksctl create iamserviceaccount \
--name my-sa \
--namespace default \
--cluster $CLUSTER_NAME \
--approve \
--attach-policy-arn $(aws iam list-policies --query 'Policies[?PolicyName==`AmazonS3ReadOnlyAccess`].Arn' --output text)
# 확인 >> 웹 관리 콘솔에서 CloudFormation Stack >> IAM Role 확인
# aws-load-balancer-controller IRSA는 어떤 동작을 수행할 것 인지 생각해보자!
eksctl get iamserviceaccount --cluster $CLUSTER_NAME
NAMESPACE NAME ROLE ARN
default my-sa arn:aws:iam::6118xxxxxxxx:role/eksctl-myeks-addon-iamserviceaccount-default-Role1-1SO30XF33627L
kube-system aws-load-balancer-controller arn:aws:iam::6118xxxxxxxx:role/eksctl-myeks-addon-iamserviceaccount-kube-sy-Role1-DKYNY4P03SZF
# Inspecting the newly created Kubernetes Service Account, we can see the role we want it to assume in our pod.
kubectl get sa
NAME SECRETS AGE
default 0 48m
my-sa 0 94s
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::611841095956:role/eksctl-myeks-addon-iamserviceaccount-default-Role1-1SO30XF33627L
Image pull secrets: <none>
Mountable secrets: <none>
Tokens: <none>
Events: <none>
# 파드3번 생성
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: eks-iam-test3
spec:
serviceAccountName: my-sa
containers:
- name: my-aws-cli
image: amazon/aws-cli:latest
command: ['sleep', '36000']
restartPolicy: Never
EOF
# 해당 SA를 파드가 사용 시 mutatingwebhook으로 Env,Volume 추가함
kubectl get mutatingwebhookconfigurations pod-identity-webhook -o yaml | kubectl neat | yh
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
name: pod-identity-webhook
webhooks:
- admissionReviewVersions:
- v1beta1
clientConfig:
caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUMvakNDQWVhZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRJek1EWXdNekE1TXpNek1Wb1hEVE16TU
...
# 파드 생성 yaml에 없던 내용이 추가됨!!!!!
# Pod Identity Webhook은 mutating webhook을 통해 아래 Env 내용과 1개의 볼륨을 추가함
kubectl get pod eks-iam-test3
NAME READY STATUS RESTARTS AGE
eks-iam-test3 1/1 Running 0 48s
kubectl describe pod eks-iam-test3
...
Environment:
AWS_STS_REGIONAL_ENDPOINTS: regional
AWS_DEFAULT_REGION: ap-northeast-2
AWS_REGION: ap-northeast-2
AWS_ROLE_ARN: arn:aws:iam::6118xxxxxxxx:role/eksctl-myeks-addon-iamserviceaccount-default-Role1-1SO30XF33627L
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-fkrsc (ro)
...
Volumes:
aws-iam-token:
Type: Projected (a volume that contains injected data from multiple sources)
TokenExpirationSeconds: 86400
kube-api-access-sn467:
Type: Projected (a volume that contains injected data from multiple sources)
TokenExpirationSeconds: 3607
ConfigMapName: kube-root-ca.crt
ConfigMapOptional: <nil>
DownwardAPI: true
...
# 파드에서 aws cli 사용 확인
eksctl get iamserviceaccount --cluster $CLUSTER_NAME
kubectl exec -it eks-iam-test3 -- aws sts get-caller-identity --query Arn
"arn:aws:sts::6118xxxxxx:assumed-role/eksctl-myeks-addon-iamserviceaccount-default-Role1-1SO30XF33627L/botocore-session-1685788242"
# 되는 것고 안되는 것은 왜그런가?
# 그 이유는 SA가 S3ReadOnlyAccess 권한만 가지고 있기 때문이다.
kubectl exec -it eks-iam-test3 -- aws s3 ls
2023-01-08 11:53:24 somaz-k8s-s3
kubectl exec -it eks-iam-test3 -- aws ec2 describe-instances --region ap-northeast-2
An error occurred (UnauthorizedOperation) when calling the DescribeInstances operation: You are not authorized to perform this operation.
kubectl exec -it eks-iam-test3 -- aws ec2 describe-vpcs --region ap-northeast-2
An error occurred (UnauthorizedOperation) when calling the DescribeVpcs operation: You are not authorized to perform this operation.
# 파드에 볼륨 마운트 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-fkrsc",
"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
동일하게 Web Token 파일도 https://jwt.io/ 로 가서 $IAM_TOKEN 넣고 확인하면 된다!
# 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
eyJhbGciOiJSUzI1NiIsIm....
# 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"
}
]
# Let’s take a look at this endpoint. We can use the aws eks describe-cluster command to get the OIDC Provider URL.
IDP=$(aws eks describe-cluster --name myeks --query cluster.identity.oidc.issuer --output text)
# Reach the Discovery Endpoint
curl -s $IDP/.well-known/openid-configuration | jq -r '.'
# In the above output, you can see the jwks (JSON Web Key set) field, which contains the set of keys containing the public keys used to verify JWT (JSON Web Token).
# Refer to the documentation to get details about the JWKS properties.
curl -s $IDP/keys | jq -r '.'
#실습 완료 후 파드 삭제
kubectl delete pod eks-iam-test3
4. OWASP Kubernetes Top Ten
EKS pod가 IMDS API를 악용하는 시나리오
< 기초 지식 >
WAF란 Web Application Firewall의 약자로 웹 애플리케이션 보안에 특화된 전용 방화벽이다.
SQL Injection 공격, Cross-Site Scripting(XSS) 공격, Cross-Site Request Forgery(CSRF) 공격 등과 같이 웹 서비스 취약점에 대한 공격을 탐지하고 차단하는 기능을 수행한다.
Kubelet 미흡한 인증/인가 설정 시 위험 + kubeletct 툴
myeks-bastion
# 노드의 kubelet API 인증과 인가 관련 정보 확인
ssh ec2-user@$N1 cat /etc/kubernetes/kubelet/kubelet-config.json | jq
{
"kind": "KubeletConfiguration",
"apiVersion": "kubelet.config.k8s.io/v1beta1",
"address": "0.0.0.0",
"authentication": {
"anonymous": {
"enabled": false
},
"webhook": {
"cacheTTL": "2m0s",
"enabled": true
},
"x509": {
"clientCAFile": "/etc/kubernetes/pki/ca.crt"
}
...
ssh ec2-user@$N1 cat /var/lib/kubelet/kubeconfig | yh
apiVersion: v1
kind: Config
clusters:
- cluster:
certificate-authority: /etc/kubernetes/pki/ca.crt
server: https://7ED7FBC0122AD2374C52640AD4B0F4E4.yl4.ap-northeast-2.eks.amazonaws.com
name: kubernetes
contexts:
- context:
cluster: kubernetes
user: kubelet
name: kubelet
current-context: kubelet
users:
- name: kubelet
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
command: /usr/bin/aws-iam-authenticator
args:
- "token"
- "-i"
- "myeks"
- --region
- "ap-northeast-2"
# 노드의 kubelet 사용 포트 확인
ssh ec2-user@$N1 sudo ss -tnlp | grep kubelet
LISTEN 0 4096 127.0.0.1:10248 0.0.0.0:* users:(("kubelet",pid=2940,fd=20))
LISTEN 0 4096 *:10250 *:* users:(("kubelet",pid=2940,fd=21))
# 데모를 위해 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
EOF
# 파드 사용
kubectl exec -it myawscli -- aws sts get-caller-identity --query Arn
"arn:aws:sts::6118xxxxxxxx:assumed-role/eksctl-myeks-nodegroup-ng1-NodeInstanceRole-CX0XBAW3ADLD/i-02fa5431cf9632ac2"
# default SA이기 때문에 s3 조회는 안된다.
kubectl exec -it myawscli -- aws s3 ls
# ec2 instance profile 덕분에 해당 명령어는 조회가 된다.
kubectl exec -it myawscli -- aws ec2 describe-instances --region ap-northeast-2 --output table --no-cli-pager
------------------------------------------------------------------------------------------------------------------------------------------------
| DescribeInstances |
+----------------------------------------------------------------------------------------------------------------------------------------------+
|| Reservations ||
|+----------------------------------------------------------+---------------------------------------------------------------------------------+|
|| OwnerId | 611841095956 ||
|| RequesterId | 154997438628 ||
|| ReservationId | r-0e31f2df842254967 ||
|+----------------------------------------------------------+---------------------------------------------------------------------------------+|
....
kubectl exec -it myawscli -- aws ec2 describe-vpcs --region ap-northeast-2 --output table --no-cli-pager
myeks-bastion2 kubeletct 설치 및 사용
aws ec2 describe-instances --query "Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,PrivateIPAdd:PrivateIpAddress,InstanceName:Tags[?Key=='Name']|[0].Value,Status:State.Name}" --filters Name=instance-state-name,Values=running --output table
----------------------------------------------------------------------
| DescribeInstances |
+---------------------+-----------------+-----------------+----------+
| InstanceName | PrivateIPAdd | PublicIPAdd | Status |
+---------------------+-----------------+-----------------+----------+
| myeks-ng1-Node | 192.168.3.103 | 3.34.197.127 | running |
| myeks-ng1-Node | 192.168.2.223 | 3.35.15.52 | running |
| myeks-bastion-EC2 | 192.168.1.100 | 3.36.92.226 | running |
| myeks-ng1-Node | 192.168.1.42 | 52.79.53.67 | running |
| myeks-bastion-EC2-2| 192.168.1.200 | 13.125.106.96 | running |
+---------------------+-----------------+-----------------+----------+
ssh -i ~/.ssh/somaz-key.pem ec2-user@13.125.106.96
The authenticity of host '13.125.106.96 (13.125.106.96)' can't be established.
ED25519 key fingerprint is SHA256:JiHkqWa07U39UTx4DhM4aeF88Ln+nLR7vWcXWGEpmlY.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '13.125.106.96' (ED25519) to the list of known hosts.
__| __|_ )
_| ( / Amazon Linux 2 AMI
___|\___|___|
https://aws.amazon.com/amazon-linux-2/
6 package(s) needed for security, out of 7 available
Run "sudo yum update" to apply all updates.
[root@myeks-bastion-2 ~]#
# 기존 kubeconfig 삭제
rm -rf ~/.kube
k get nodes
E0603 20:39:59.764641 316 memcache.go:265] couldn't get current server API group list: Get "http://localhost:8080/api?timeout=32s": dial tcp 127.0.0.1:8080: connect: connection refused
E0603 20:39:59.764855 316 memcache.go:265] couldn't get current server API group list: Get "http://localhost:8080/api?timeout=32s": dial tcp 127.0.0.1:8080: connect: connection refused
E0603 20:39:59.766235 316 memcache.go:265] couldn't get current server API group list: Get "http://localhost:8080/api?timeout=32s": dial tcp 127.0.0.1:8080: connect: connection refused
E0603 20:39:59.767570 316 memcache.go:265] couldn't get current server API group list: Get "http://localhost:8080/api?timeout=32s": dial tcp 127.0.0.1:8080: connect: connection refused
E0603 20:39:59.768829 316 memcache.go:265] couldn't get current server API group list: Get "http://localhost:8080/api?timeout=32s": dial tcp 127.0.0.1:8080: connect: connection refused
The connection to the server localhost:8080 was refused - did you specify the right host or port?
# 다운로드
curl -LO https://github.com/cyberark/kubeletctl/releases/download/v1.9/kubeletctl_linux_amd64 && chmod a+x ./kubeletctl_linux_amd64 && mv ./kubeletctl_linux_amd64 /usr/local/bin/kubeletctl
kubeletctl version
_ _ _ _
| | | | | | _ _ | |
| | _ _ _| |__ _____| | _____ _| |_ ____ _| |_| |
| |_/ ) | | | _ \| ___ | || ___ (_ _) ___|_ _) |
| _ (| |_| | |_) ) ____| || ____| | |( (___ | |_| |
|_| \_)____/|____/|_____)\_)_____) \__)____) \__)\_)
Author: Eviatar Gerzi
Version: 1.9
kubeletctl help
# 노드1 IP 변수 지정
N1=<각자 자신의 노드1의 PrivateIP>
[root@myeks-bastion-2 ~]# N1=192.168.1.42
# 노드1 IP로 Scan
[root@myeks-bastion-2 ~]# kubeletctl scan --cidr $N1/32
# 노드1에 kubelet API 호출 시도
[root@myeks-bastion-2 ~]# curl -k https://$N1:10250/pods; echo
Unauthorized
myeks-bastion → 노드1 접속 : kubelet-config.json 수정
(somaz@myeks:N/A) [root@myeks-bastion ~]# N1=192.168.1.42
# 노드1 접속
ssh ec2-user@$N1
-----------------------------
# 미흡한 인증/인가 설정으로 변경
vi /etc/kubernetes/kubelet/kubelet-config.json
...
"authentication": {
"anonymous": {
"enabled": true
...
},
"authorization": {
"mode": "AlwaysAllow",
...
# kubelet restart
systemctl restart kubelet
systemctl status kubelet
● kubelet.service - Kubernetes Kubelet
Loaded: loaded (/etc/systemd/system/kubelet.service; enabled; vendor preset: disabled)
Drop-In: /etc/systemd/system/kubelet.service.d
└─10-kubelet-args.conf, 30-kubelet-extra-args.conf
Active: active (running) since Sat 2023-06-03 11:52:02 UTC; 4s ago
-----------------------------
myeks-bastion-2 kubeletctl 사용
# 파드 목록 확인
curl -s -k https://$N1:10250/pods | jq
{
"kind": "PodList",
"apiVersion": "v1",
"metadata": {},
"items": [
{
...
# kubelet-config.json 설정 내용 확인
curl -k https://$N1:10250/configz | jq
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 2905 100 2905 0 0 410k 0 --:--:-- --:--:-- --:--:-- 472k
{
"kubeletconfig": {
"enableServer": true,
"syncFrequency": "1m0s",
...
# kubeletct 사용
# Return kubelet's configuration
kubeletctl -s $N1 configz | jq
# Get list of pods on the node
kubeletctl -s $N1 pods
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Pods from Kubelet │
├───┬───────────────────────────────────────────────┬─────────────┬──────────────────────────────┤
│ │ POD │ NAMESPACE │ CONTAINERS │
├───┼───────────────────────────────────────────────┼─────────────┼──────────────────────────────┤
│ 1 │ kube-proxy-4zwbp │ kube-system │ kube-proxy │
│ │ │ │ │
├───┼───────────────────────────────────────────────┼─────────────┼──────────────────────────────┤
│ 2 │ coredns-6777fcd775-kkgsl │ kube-system │ coredns │
│ │ │ │ │
├───┼───────────────────────────────────────────────┼─────────────┼──────────────────────────────┤
│ 3 │ ebs-csi-node-w9n7w │ kube-system │ ebs-plugin │
│ │ │ │ node-driver-registrar │
│ │ │ │ liveness-probe │
│ │ │ │ │
├───┼───────────────────────────────────────────────┼─────────────┼──────────────────────────────┤
│ 4 │ aws-load-balancer-controller-5f99d5f58f-qjc9r │ kube-system │ aws-load-balancer-controller │
│ │ │ │ │
├───┼───────────────────────────────────────────────┼─────────────┼──────────────────────────────┤
│ 5 │ myawscli │ default │ my-aws-cli │
│ │ │ │ │
├───┼───────────────────────────────────────────────┼─────────────┼──────────────────────────────┤
│ 6 │ aws-node-cvg25 │ kube-system │ aws-node │
│ │ │ │ │
└───┴───────────────────────────────────────────────┴─────────────┴──────────────────────────────┘
# Scans for nodes with opened kubelet API > Scans for for all the tokens in a given Node
kubeletctl -s $N1 scan token
1. Pod: myawscli
Namespace: default
Container: my-aws-cli
Url: https://192.168.1.42:10250/run/default/myawscli/my-aws-cli
Output:
eyJhb...
# kubelet API로 명령 실행 : <네임스페이스> / <파드명> / <컨테이너명>
curl -k https://$N1:10250/run/default/myawscli/my-aws-cli -d "cmd=aws --version"
aws-cli/2.11.25 Python/3.11.3 Linux/5.10.179-166.674.amzn2.x86_64 docker/x86_64.amzn.2 prompt/off
# Scans for nodes with opened kubelet API > remote code execution on their containers
kubeletctl -s $N1 scan rce
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Node with pods vulnerable to RCE │
├───┬──────────────┬───────────────────────────────────────────────┬─────────────┬──────────────────────────────┬─────┤
│ │ NODE IP │ PODS │ NAMESPACE │ CONTAINERS │ RCE │
├───┼──────────────┼───────────────────────────────────────────────┼─────────────┼──────────────────────────────┼─────┤
│ │ │ │ │ │ RUN │
├───┼──────────────┼───────────────────────────────────────────────┼─────────────┼──────────────────────────────┼─────┤
│ 1 │ 192.168.1.42 │ aws-node-cvg25 │ kube-system │ aws-node │ - │
├───┼──────────────┼───────────────────────────────────────────────┼─────────────┼──────────────────────────────┼─────┤
│ 2 │ │ kube-proxy-4zwbp │ kube-system │ kube-proxy │ - │
├───┼──────────────┼───────────────────────────────────────────────┼─────────────┼──────────────────────────────┼─────┤
....
│ 8 │ │ myawscli │ default │ my-aws-cli │ + │
└───┴──────────────┴───────────────────────────────────────────────┴─────────────┴──────────────────────────────┴─────┘
# Run commands inside a container
kubeletctl -s $N1 exec "/bin/bash" -n default -p myawscli -c my-aws-cli
--------------------------------
export
aws --version
aws-cli/2.11.25 Python/3.11.3 Linux/5.10.179-166.674.amzn2.x86_64 docker/x86_64.amzn.2 prompt/off
aws ec2 describe-vpcs --region ap-northeast-2 --output table --no-cli-pager
----------------------------------------------------------------------------------------------------------------------------------------------
| DescribeVpcs |
+--------------------------------------------------------------------------------------------------------------------------------------------+
|| Vpcs ||
|+---------------------------------------------------------+--------------------------------------------------------------------------------+|
|| CidrBlock | 172.31.0.0/16 ||
|| DhcpOptionsId | dopt-0be4e016089ff564e ||
|| InstanceTenancy | default ||
|| IsDefault | True ||
|| OwnerId | 611841095956 ||
|| State | available ||
|| VpcId | vpc-0be33812ca98ab8fa ||
|+---------------------------------------------------------+--------------------------------------------------------------------------------+|
...
exit
--------------------------------
# Return resource usage metrics (such as container CPU, memory usage, etc.)
kubeletctl -s $N1 metrics
6. Securing Secrets
- Valut Secret Operator on K8s
- Youtube 개요 설치 실습 비교 GitHub
- VSO 실습 코드(Dynamic,PKI,Static) : https://github.com/hashicorp/vault-secrets-operator/tree/main/demo/infra/app
- VSO 실습 코드(Static) : https://github.com/hashicorp-education/learn-vault-secrets-operator/tree/main/vault
- Vault Static Secret 샘플 : https://developer.hashicorp.com/vault/tutorials/kubernetes/vault-secrets-operator
- VSO API Reference : https://developer.hashicorp.com/vault/docs/platform/k8s/vso/api-reference
2022.10.11 - [Networking, Security, Protocols] - Vault란?
7. 파드/컨테이너 보안 컨텍스트
보안 컨텍스트 내용과 실습 참고 책 ⇒ 쿠버네티스 완벽 가이드 👍🏻
컨테이너 보안 컨텍스트 SecurityContext
- 링크 ← 파드가 아님 주의한다.
각 컨테이너에 대한 보안 설정 → 침해사고 발생 시 침해사고를 당한 권한을 최대한 축소하여 그 사고에 대한 확대를 방치한다.
컨테이너 보안 컨텍스트 확인 : kube-system 파드 내 컨테이너 대상
kubectl get pod -n kube-system -o jsonpath={.items[*].spec.containers[*].securityContext} | jq
{
"allowPrivilegeEscalation": false,
"readOnlyRootFilesystem": true,
"runAsNonRoot": true
}
...
readOnlyRootFilesystem : root 파일 시스템을 읽기 전용으로 사용
#
cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
name: rootfile-readonly
spec:
containers:
- name: netshoot
image: nicolaka/netshoot
command: ["tail"]
args: ["-f", "/dev/null"]
securityContext:
readOnlyRootFilesystem: true
terminationGracePeriodSeconds: 0
EOF
# 파일 생성 시도
kubectl exec -it rootfile-readonly -- touch /tmp/text.txt
touch: /tmp/text.txt: Read-only file system
command terminated with exit code 1
# 기존 파일 수정 시도 : 아래 /etc/hosts파일 말고 다른 파일로 예제 만들어 두자
## 기본적으로 mount 옵션이 ro 이긴 한데. 특정 파일이나 폴더가 rw로 mount가 되어서 그곳에서는 파일 생성, 삭제등이 가능하네요.
## 특히 /etc/hosts 파일은 HostAliases로 항목 추가가 가능한데, 해당 파링은 kubelet에 의해 관리되고, 파드 생성/재시작 중 덮었여질 수 있다.
## /dev 라던가 /sys/fs/cgroup 폴더 안에서도 가능하네요.
## /etc/hostname 같은 경우는 호스트와 별도의 파일이지만 mount가 / (ro)에 속하게 되어 제한이 걸리네요.
kubectl exec -it rootfile-readonly -- cat /etc/hosts
kubectl exec -it rootfile-readonly -- sh -c "echo write > /etc/hosts"
kubectl exec -it rootfile-readonly -- cat /etc/hosts
# 특정 파티션, 파일의 ro/rw 확인
kubectl exec -it rootfile-readonly -- mount | grep hosts
/dev/root on /etc/hosts type ext4 (rw,relatime,discard)
kubectl exec -it rootfile-readonly -- mount | grep ro
overlay on / type overlay (ro,relatime~~~~~~~~~~
## /proc, /dev, /sys/fs/cgroup, /etc/hosts, /proc/kcore, /proc/keys, /proc/timer_list
kubectl exec -it rootfile-readonly -- mount | grep rw
proc on /proc type proc (rw,nosuid,nodev,noexec,relatime)
tmpfs on /dev type tmpfs (rw,nosuid,size=65536k,mode=755,inode64)
...
# 파드 상세 정보 확인
kubectl get pod rootfile-readonly -o jsonpath={.spec.containers[0].securityContext} | jq
{
"readOnlyRootFilesystem": true
}
Linux Capabilities : Give a process some privileges, but not all the privileges of the root user - 링크
Linux Capabilities : 슈퍼 유저의 힘을 작은 조각으로 나눔, Capabilities are a per-thread attribute - 링크 man-pages
# Linux Capabilities 확인 : 현재 38개
capsh --print
...
Bounding set =cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,cap_audit_read
# proc 에서 확인 : bit 별 Capabilities - 링크
cat /proc/1/status | egrep 'CapPrm|CapEff'
CapPrm: 0000003fffffffff
CapEff: 0000003fffffffff
# 노드 Linux Capabilities 확인 : 아래 굵은색은 파드 기본 Linux Capabilities(14개)
ssh ec2-user@$N1 sudo yum -y install libcap-ng-utils
ssh ec2-user@$N1 sudo capsh --print
cap_chown 파일이나 디렉토리의 소유자를 변경할 수 있는 권한
cap_dac_override 파일이나 디렉토리의 접근 권한을 무시하고 파일이나 디렉토리에 대한 접근을 수행할 수 있는 권한 (DAC의 약자는 Discretionary access control이다)
cap_dac_read_search 파일이나 디렉토리를 읽거나 검색할 수 있는 권한
cap_fowner 파일이나 디렉토리의 소유자를 변경할 수 있는 권한
cap_fsetid 일이나 디렉토리의 Set-User-ID (SUID) 또는 Set-Group-ID (SGID) 비트를 설정할 수 있는 권한
cap_kill 다른 프로세스를 종료할 수 있는 권한
cap_setgid 프로세스가 그룹 ID를 변경할 수 있는 권한
cap_setuid 프로세스가 사용자 ID를 변경할 수 있는 권한
cap_setpcap 프로세스가 자신의 프로세스 권한을 변경할 수 있는 권한
cap_linux_immutable 파일의 immutability(불변성) 속성을 변경할 수 있는 권한을 제공
cap_net_bind_service 프로그램이 특정 포트에 바인딩(bind)하여 소켓을 개방할 수 있는 권한
cap_net_broadcast 프로세스가 네트워크 브로드캐스트 메시지를 보낼 수 있는 권한
cap_net_admin 네트워크 인터페이스나 소켓 설정을 변경할 수 있는 권한
cap_net_raw 네트워크 패킷을 송수신하거나 조작할 수 있는 권한
cap_ipc_lock 메모리 영역을 잠금(lock)하고 언락(unlock)할 수 있는 권한
cap_ipc_owner IPC 리소스(Inter-Process Communication Resources)를 소유하고, 권한을 변경할 수 있는 권한
cap_sys_module 커널 모듈을 로드하거나 언로드할 수 있는 권한
cap_sys_rawio 입출력(I/O) 포트와 같은 하드웨어 리소스를 직접 접근할 수 있는 권한
cap_sys_chroot 프로세스가 chroot() 시스템 콜을 호출하여 프로세스의 루트 디렉토리를 변경할 수 있는 권한
cap_sys_ptrace 다른 프로세스를 추적(trace)하거나 디버깅할 수 있는 권한
cap_sys_pacct 프로세스 회계(process accounting)를 위한 파일에 접근할 수 있는 권한
cap_sys_admin 시스템 관리자 권한을 제공하는 권한
cap_sys_boot 시스템 부팅과 관련된 작업을 수행할 수 있는 권한
cap_sys_nice 프로세스의 우선순위를 변경할 수 있는 권한
cap_sys_resource 자원 제한(resource limit)과 관련된 작업을 수행할 수 있는 권한
cap_sys_time 시스템 시간을 변경하거나, 시간 관련 시스템 콜을 사용할 수 있는 권한
cap_sys_tty_config 터미널 설정을 변경할 수 있는 권한
cap_mknod mknod() 시스템 콜을 사용하여 파일 시스템에 특수 파일을 생성할 수 있는 권한
cap_lease 파일의 잠금과 관련된 작업을 수행할 수 있는 권한
cap_audit_write 시스템 감사(audit) 로그에 대한 쓰기 권한
cap_audit_control 시스템 감사(audit) 설정과 관련된 작업을 수행할 수 있는 권한
cap_setfcap 파일 시스템 캡러빌리티(file system capability)을 설정할 수 있는 권한
cap_mac_override SELinux 또는 AppArmor과 같은 MAC(Mandatory Access Control) 시스템을 우회하고 자신의 프로세스가 접근 가능한 파일, 디바이스, 네트워크 등을 제한 없이 접근할 수 있는 권한
cap_mac_admin SELinux 또는 AppArmor과 같은 MAC(Mandatory Access Control) 시스템을 관리하고 수정할 수 있는 권한
cap_syslog 시스템 로그를 읽거나, 쓸 수 있는 권한
cap_wake_alarm 시스템의 RTC(Real-Time Clock)를 사용하여 장치를 깨우거나 슬립 모드를 해제할 수 있는 권한
cap_block_suspend 시스템의 전원 관리 기능 중 하나인 Suspend(절전 모드)를 방지하는 권한
cap_audit_read 시스템 감사(audit) 로그를 읽을 수 있는 권한
파드의 Linux Capabilities 기본 확인
# 샘플 파드 생성
cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
name: sample-capabilities
spec:
containers:
- name: nginx-container
image: masayaaoyama/nginx:capsh
command: ["tail"]
args: ["-f", "/dev/null"]
terminationGracePeriodSeconds: 0
EOF
# 파드의 Linux Capabilities 기본 확인
kubectl exec -it sample-capabilities -- capsh --print | grep Current
Current: = cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap+ep
cap_chown,
cap_dac_override,
cap_fowner,
cap_fsetid,
cap_kill,
cap_setgid,
cap_setuid,
cap_setpcap,
cap_net_bind_service,
cap_net_raw,
cap_sys_chroot,
cap_mknod,
cap_audit_write,
cap_setfcap+ep
# proc 에서 확인 : bit 별 Capabilities - 링크
kubectl exec -it sample-capabilities -- cat /proc/1/status | egrep 'CapPrm|CapEff'
CapPrm: 00000000a80425fb
CapEff: 00000000a80425fb
# 파드에서 시간 변경 시도
kubectl exec -it sample-capabilities -- date
Sat Apr 1 02:43:58 UTC 2023
# 파드에서 시간 변경 시도
kubectl exec -it sample-capabilities -- date -s "12:00:00"
kubectl exec -it sample-capabilities -- date
---
# 파드가 배포된 워커노드 확인
NAME NOMINATED NODE
sample-capabilities2 i-038c6921803a6372b
# 파드가 배포된 워커노드 IP 확인
kubectl get node -o wide | grep i-038c6921803a6372b
i-038c6921803a6372b 172.30.42.82 3.36.92.81
# 노드1로 접근
ssh ec2-user@$N1
# 노드1에 systemd-timesyncd 종료
root@i-038c6921803a6372b:~# systemctl stop systemd-timesyncd
# 파드에서 시간 변경 확인 >> 파드 변경 확인 후 노드의 date와 비교해보자
kubectl exec -it sample-capabilities2 -- date -s "12:00:00"
kubectl exec -it sample-capabilities2 -- date
파드에 Linux Capabilities 부여 및 삭제
#
cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
name: sample-capabilities2
spec:
containers:
- name: nginx-container
image: masayaaoyama/nginx:capsh
command: ["tail"]
args: ["-f", "/dev/null"]
securityContext:
capabilities:
add: ["NET_ADMIN", "SYS_TIME"]
drop: ["AUDIT_WRITE"]
terminationGracePeriodSeconds: 0
EOF
# 파드의 Linux Capabilities 기본 확인
kubectl exec -it sample-capabilities2 -- capsh --print | grep Current
Current: = cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_admin,cap_net_raw,cap_sys_chroot,cap_sys_time,cap_mknod,cap_setfcap+ep
cap_chown,
cap_dac_override,
cap_fowner,
cap_fsetid,
cap_kill,
cap_setgid,
cap_setuid,
cap_setpcap,
cap_net_bind_service,
cap_net_admin, # 추가
cap_net_raw,
cap_sys_chroot,
cap_sys_time, # 추가
cap_mknod,
cap_setfcap+ep
# 제거됨 cap_audit_write
# 파드 상세 정보 확인
kubectl get pod sample-capabilities2 -o jsonpath={.spec.containers[0].securityContext} | jq
{
"capabilities": {
"add": [
"NET_ADMIN",
"SYS_TIME"
],
"drop": [
"AUDIT_WRITE"
]
}
}
# proc 에서 확인 : bit 별 Capabilities - 링크
kubectl exec -it sample-capabilities2 -- cat /proc/1/status | egrep 'CapPrm|CapEff'
CapPrm: 000000008a0435fb
CapEff: 000000008a0435fb
# 파드에서 시간 변경 시도 : 시간 동기화하는 다른 우선순위가 있는것 같습니다! 혹신 아시는 분들은 댓글 부탁드립니다!
## 확인해 보니 시간이 변경되었지만 해당 파드가 동작되는 노드의 시간을 sync 하고 있습니다.
## 노드에 접근해서 systemd-timesyncd 종료하면은 노드의 바뀐 시간까지는 따라가는 것을 확인했습니다.
## systemctl stop systemd-timesyncd 그리고 파드에서 시간 변경시 해당 노드의 시간도 바뀌는 것을 확인 했습니다.
kubectl exec -it sample-capabilities2 -- date
kubectl exec -it sample-capabilities2 -- date -s "12:00:00"
kubectl exec -it sample-capabilities2 -- date
특수 권한 컨테이너 생성 : 호스트와 동등한 권한 부여됨
#
cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
name: sample-capabilities3
spec:
containers:
- name: nginx-container
image: masayaaoyama/nginx:capsh
command: ["tail"]
args: ["-f", "/dev/null"]
securityContext:
privileged: true
terminationGracePeriodSeconds: 0
EOF
# 파드의 Linux Capabilities 기본 확인
kubectl exec -it sample-capabilities3 -- capsh --print | grep Current
# 파드 상세 정보 확인
kubectl get pod sample-capabilities3 -o jsonpath={.spec.containers[0].securityContext} | jq
{
"privileged": true
}
# proc 에서 확인 : bit 별 Capabilities - 링크
kubectl exec -it sample-capabilities3 -- cat /proc/1/status | egrep 'CapPrm|CapEff'
CapPrm: 000001ffffffffff
CapEff: 000001ffffffffff
# 다음 실습을 위해서 파드 삭제
kubectl delete pod --all
파드 보안 컨텍스트
- 파드 레벨에서 보안 컨텍스트를 적용 : 파드에 포함된 모든 컨테이너가 영향을 받음
- 파드와 컨테이너 정책 중복 시, 컨테이너 정책이 우선 적용됨
컨테이너 보안 컨텍스트 확인 : kube-system 파드 내 컨테이너 대상
kubectl get pod -n kube-system -o jsonpath={.items[*].spec.securityContext} | jq
...
실행 사용자 변경
runuser 파드는 실행 사용자를 nobody(UID:65534) 사용자로 실행, 실행권한에 서브그룹 1001/1002 추가한다.
#
cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
name: rundefault
spec:
containers:
- name: centos
image: centos:7
command: ["tail"]
args: ["-f", "/dev/null"]
securityContext:
readOnlyRootFilesystem: true
terminationGracePeriodSeconds: 0
---
apiVersion: v1
kind: Pod
metadata:
name: runuser
spec:
securityContext:
runAsUser: 65534
runAsGroup: 65534
supplementalGroups:
- 1001
- 1002
containers:
- name: centos
image: centos:7
command: ["tail"]
args: ["-f", "/dev/null"]
securityContext:
readOnlyRootFilesystem: true
terminationGracePeriodSeconds: 0
EOF
#
kubectl get pod rundefault -o jsonpath={.spec.securityContext} | jq
kubectl get pod runuser -o jsonpath={.spec.securityContext} | jq
# 실행 사용자 정보 확인
kubectl exec -it rundefault -- id
uid=0(root) gid=0(root) groups=0(root)
kubectl exec -it runuser -- id
uid=65534 gid=65534 groups=65534,1001,1002
# 프로세스 정보 확인
kubectl exec -it rundefault -- ps -axo uid,user,gid,group,pid,comm
UID USER GID GROUP PID COMMAND
0 root 0 root 1 tail
0 root 0 root 13 ps
kubectl exec -it runuser -- ps -axo uid,user,gid,group,pid,comm
UID USER GID GROUP PID COMMAND
65534 65534 65534 65534 1 tail
65534 65534 65534 65534 19 ps
root 사용자로 실행 제한
사용자를 변경하지 않고 단순히 root 사용자로 실행을 거부하도록 설정 시 동작은 어떻게 될까?
#
cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
name: nonroot
spec:
securityContext:
runAsNonRoot: true
containers:
- name: netshoot
image: nicolaka/netshoot
command: ["tail"]
args: ["-f", "/dev/null"]
terminationGracePeriodSeconds: 0
EOF
# 이벤트 확인
kubectl events --for pod/nonroot
파일 시스템 그룹 지정
- 일반적으로 마운트한 볼륨의 소유자와 그룹은 root:root로 되어 있다. 실행 사용자를 변경한 경우에는 마운트한 볼륨에 권한이 없는 경우가 있다
- 따라서 마운트하는 볼륨의 그룹을 변경할 수 있도록 되어 있다 (setdig도 설정된다) - 예) emptyDir 혹은 PV 등 volumeMounts
#
cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
name: fsgoup1
spec:
volumes:
- name: vol1
emptyDir: {}
containers:
- name: centos
image: centos:7
command: [ "sh", "-c", "sleep 1h" ]
volumeMounts:
- name: vol1
mountPath: /data/demo
terminationGracePeriodSeconds: 0
---
apiVersion: v1
kind: Pod
metadata:
name: fsgoup2
spec:
securityContext:
runAsUser: 1000
runAsGroup: 3000
fsGroup: 2000
volumes:
- name: vol2
emptyDir: {}
containers:
- name: centos
image: centos:7
command: [ "sh", "-c", "sleep 1h" ]
volumeMounts:
- name: vol2
mountPath: /data/demo
terminationGracePeriodSeconds: 0
EOF
#
kubectl get pod fsgoup1 -o jsonpath={.spec.securityContext} | jq
kubectl get pod fsgoup2 -o jsonpath={.spec.securityContext} | jq
# 실행 사용자 정보 확인
kubectl exec -it fsgoup1 -- id
uid=0(root) gid=0(root) groups=0(root)
kubectl exec -it fsgoup2 -- id
uid=1000 gid=3000 groups=3000,2000
# 프로세스 정보 확인
kubectl exec -it fsgoup1 -- ps -axo uid,user,gid,group,pid,comm
kubectl exec -it fsgoup2 -- ps -axo uid,user,gid,group,pid,comm
# 디렉터리 정보 확인 : fsgoup2파드의 마운트 볼륨 그룹의 GID가 2000 (fsGroup: 2000)
kubectl exec -it fsgoup1 -- ls -l /data
drwxrwxrwx 2 root root 4096 Apr 1 05:15 demo
kubectl exec -it fsgoup2 -- ls -l /data
drwxrwsrwx 2 root 2000 4096 Apr 1 05:15 demo
# fsgoup2파드에서 파일 생성 및 확인
kubectl exec -it fsgoup2 -- sh -c "echo write > /data/demo/sample.txt"
kubectl exec -it fsgoup2 -- cat /data/demo/sample.txt
kubectl exec -it fsgoup2 -- ls -l /data/demo/sample.txt
-rw-r--r-- 1 1000 2000 6 Apr 1 05:20 /data/demo/sample.txt
# fsgoup2파드에서 다른 디렉토리에 파일 생성 시도 >> 안되는 이유가 멀까요?
kubectl exec -it fsgoup2 -- sh -c "echo write > /data/sample.txt"
sh: /data/sample.txt: Permission denied
command terminated with exit code 1
sysctl을 사용한 커널 파라미터 설정
커널 파라미터 변경 적용을 위해서는 컨테이너에서도 설정 필요, 파드 수준 적용으로 컨테이너 간에 공유된다.
- 링크
- 커널 파라미터는 안전한 것(safe)과 안전하지 않은 것(unsafe)으로 분류된다.
- 안전한 것 safe : 호스트의 커널과 적절하게 분리되어 있으며 다른 파드에 영향이 없는 것, 파드가 예상치 못한 리소스를 소비하지 않는 것
- kernel.shm_rmid_forced
- net.ipv4.ip_local_port_range
- net.ipv4.tcp_syncookies
- net.ipv4.ping_group_range (since Kubernetes 1.18),
- net.ipv4.ip_unprivileged_port_start (since Kubernetes 1.22).
- 안전하지 않은 것 unsafe : 사실상 대부분의 커널 파라미터 ⇒ 적용을 위해서는 kubelet 설정 필요
- 안전한 것 safe : 호스트의 커널과 적절하게 분리되어 있으며 다른 파드에 영향이 없는 것, 파드가 예상치 못한 리소스를 소비하지 않는 것
# Unsafe sysctls are enabled on a node-by-node basis with a flag of the kubelet
kubelet --allowed-unsafe-sysctls 'kernel.msg*,net.core.somaxconn' ...
unsafe 파라미터를 변경 시도한다.
#
cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
name: unsafe
spec:
securityContext:
sysctls:
- name: net.core.somaxconn
value: "12345"
containers:
- name: centos-container
image: centos:7
command: ["/bin/sleep", "3600"]
terminationGracePeriodSeconds: 0
EOF
#
kubectl events --for pod/unsafe
LAST SEEN TYPE REASON OBJECT MESSAGE
4s Normal Scheduled Pod/unsafe Successfully assigned default/unsafe to i-01af337c3d1004e24
safe 파라미터 수정
sysctl2 파드가 배포된 노드의 net.ipv4.ip_local_port_range 값과 다를 경우에는 어떻게 동작할까요?
#
cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
name: sysctl1
spec:
containers:
- name: centos-container
image: centos:7
command: ["/bin/sleep", "3600"]
terminationGracePeriodSeconds: 0
---
apiVersion: v1
kind: Pod
metadata:
name: sysctl2
spec:
securityContext:
sysctls:
- name: net.ipv4.ip_local_port_range
value: "1025 61000"
containers:
- name: centos-container
image: centos:7
command: ["/bin/sleep", "3600"]
terminationGracePeriodSeconds: 0
EOF
#
kubectl get pod sysctl1 -o jsonpath={.spec.securityContext} | jq
kubectl get pod sysctl2 -o jsonpath={.spec.securityContext} | jq
#
kubectl exec -it sysctl1 -- sysctl net.ipv4.ip_local_port_range
net.ipv4.ip_local_port_range = 32768 60999
kubectl exec -it sysctl2 -- sysctl net.ipv4.ip_local_port_range
net.ipv4.ip_local_port_range = 1025 61000
initContainer 와 privileged 를 활용하여 unsafe 커널 파라미터를 강제로 변경한다.
#
curl -s -O https://raw.githubusercontent.com/MasayaAoyama/kubernetes-perfect-guide/ko/2nd-edition/samples/chapter13/sample-sysctl-initcontainer.yaml
cat sample-sysctl-initcontainer.yaml| yh
kubectl apply -f sample-sysctl-initcontainer.yaml
#
kubectl describe pod sample-sysctl-initcontainer
kubectl get pod sample-sysctl-initcontainer -o json | jq
# 확인
kubectl exec -it sample-sysctl-initcontainer -c tools-container -- sysctl net.core.somaxconn
net.core.somaxconn = 12345
# 다음 실습을 위해서 파드 삭제
kubectl delete pod --all
(실습 완료 후) 자원 삭제
1. testuser IAM User는 AWS 웹 관리콘솔에서 삭제
2. DVWA Ingress 삭제
kubectl delete ingress ingress-dvwa
3. Helm Chart 삭제
helm uninstall -n monitoring kube-prometheus-stack
4. EKS 클러스터 삭제
eksctl delete cluster --name $CLUSTER_NAME && aws cloudformation delete-stack --stack-name $CLUSTER_NAME
Reference
- [커피고래님] - 인증 시리즈 X.509 HTTP인증 OpenID Connect Webhook Proxy인증 , Admisstion Control
- AWS Cross-Accounts IRSA 적용기 - 링크
- [learnk8s] User and workload identities in Kubernetes - 링크
- [Youtube] 쿠버네티스 해킹과 방어 (데모 포함)
- [Youtube] Kubecon 2023 Europe
'교육, 커뮤니티 후기 > AEWS 스터디' 카테고리의 다른 글
AEWS 스터디 7주차 - EKS Automation (0) | 2023.06.08 |
---|---|
AEWS 스터디 5주차 - EKS Autoscaling (0) | 2023.05.27 |
AEWS 스터디 4주차 - EKS Observability (0) | 2023.05.17 |
AEWS 스터디 3주차 - EKS Storage & Node 관리 (1) | 2023.05.11 |
AEWS 스터디 2주차 - EKS Networking (0) | 2023.05.01 |