본문 바로가기

k8s/AWS EKS

[AWS] EKS Security

기초 이론

암호화

정의: 암호화는 평문 원본 데이터를 특정 키를 써서 알아볼 수 없는 암호문으로 바꾸는 과정임.
목적: 데이터를 보호해서 비인가된 사용자가 접근 못 하게 하고 통신 중 데이터가 노출돼도 해독 못 하게 함.

복호화

정의: 복호화는 암호문을 원래 평문으로 되돌리는 과정임.
목적: 복호화는 암호화된 데이터를 인가된 사용자만 읽을 수 있게 해줌.

 

암호화 방식

암호화 과정에서 필요한 키를 쌍방이 확보할 수 있는 방법에서 공유 비밀키 방식(shared secret key)과 공개키 방식(public key)이 있음.

  • 공유 비밀키 암호화 방식 (shared secret key) : DES, 3DES, RC4/5
  • 공개키 암호화 방식 (public key) : RSA

 

공유 비밀키 암호화 방식

암호화랑 복호화에 똑같은 키를 쓰는 대칭키 암호 방식임.

송신자랑 수신자가 같은 비밀키를 공유해야 하고, 이 키를 통해 데이터를 암호화하거나 복호화함.

장점: 계산 속도가 빠르고 효율적임.
단점: 비밀키를 공유하는 과정에서 키가 누출되면 보안이 뚫릴 위험이 있음.

비밀키 분배 방법에는 오프라인으로 직접 만나서 교환하거나, 디피-헬만 알고리즘 같은 공개키 기반 키 교환 방식, 중앙기관을 이용하는 방식이 있음.

 

공개 키 암호화 방식

공개키 방식은 암호화랑 복호화에 서로 다른 키를 쓰는 비대칭 암호 방식임.

하나는 누구나 볼 수 있는 공개키이고 다른 하나는 본인만 알고 있는 개인키임.


암호화 과정
송신자가 수신자의 공개키로 데이터를 암호화함.
암호화된 데이터는 수신자만 자신의 개인키를 써서 복호화할 수 있음.

 

복호화 과정
개인키로 데이터를 암호화하고 공개키로 복호화하면 데이터 제공자의 신원을 확인할 수 있음.


장점: 비밀키를 따로 공유할 필요가 없으므로 유출 가능성이 낮아 보안성이 높음.

단점: 계산이 복잡해서 대칭키 방식보다 속도가 느림.

 

메시지 인증

해시(Hash) 방법

해시 함수를 사용해 메시지를 고정된 길이의 해시 값으로 변환하여 무결성을 확인하는 방식임.

송신자가 메시지를 해싱한 후 그 값을 수신자에게 전달하고 수신자가 동일한 해싱 과정을 통해 결과를 비교함으로써 메시지가 변조되지 않았음을 확인할 수 있음.

장점:
계산이 빠르고 효율적임.
메시지의 무결성을 간단히 검증 가능함.

단점:
기밀성을 제공하지 않음.
해시 값만으로는 송신자의 신원을 확인할 수 없음.

 

MAC 방법

메시지와 비밀 키를 결합하여 생성된 코드로, 메시지의 무결성과 인증을 제공하는 방식임.

송신자는 MAC을 생성해 메시지와 함께 전송하고 수신자는 동일한 키로 MAC을 재생성하여 두 값을 비교함으로써 메시지가 변조되지 않았는지 확인함.

장점:
무결성과 인증을 동시에 제공함.
비밀 키를 사용하므로 송신자의 신원을 확인 가능함.

단점:
비밀 키를 공유해야 하므로 키 관리가 어려울 수 있음.


HMAC 방법

MAC 방식의 일종으로, 해시 함수와 비밀 키를 결합하여 MAC을 생성하는 방식임.

내부 패딩(ipad)과 외부 패딩(opad)을 사용해 보안을 강화하며 SHA-256 같은 해시 알고리즘을 활용함.

장점:
기존 해시 함수의 보안성을 그대로 활용 가능함.
내부/외부 패딩을 사용해 키 유출 위험을 줄임.
다양한 해시 알고리즘과 결합 가능함.

단점:
대칭키 기반이므로 키 관리가 필요함.
메시지 암호 방법(메시지 복호형 디지털 서명 기능)

 

메시지 암호 방법

공개키 암호화를 이용해 송신자가 개인 키로 메시지를 암호화하고 수신자가 공개 키로 복호화하여 메시지의 출처와 무결성을 확인하는 방식임.

이 과정은 디지털 서명의 기능도 포함하며 송신자가 메시지를 보냈음을 인증하는 기능도 함.

장점:
부인 방지가 가능함(송신자가 보냈음을 증명).
기밀성과 무결성을 동시에 제공함.

단점:
공개키 암호화는 계산이 복잡해 속도가 느림.

 

 

RSA 디지털 서명(Sign) 방식

RSA 알고리즘을 기반으로 메시지의 출처를 인증하고 무결성을 보장하는 방식임.

송신자가 개인 키로 메시지의 해시 값을 암호화하여 서명을 생성하고 수신자는 송신자의 공개 키를 사용해 서명을 복호화하여 메시지가 변조되지 않았음을 확인함.

 

서명 과정

  1. 송신자가 메시지를 해싱하여 고정된 길이의 해시 값을 생성함.
  2. 이 해시 값을 개인 키로 암호화하여 디지털 서명을 만듦.
  3. 메시지와 디지털 서명을 함께 수신자에게 전송함.

검증 과정

  1. 수신자가 받은 메시지를 해싱하여 동일한 해시 값을 생성함.
  2. 송신자의 공개 키로 디지털 서명을 복호화하여 원래의 해시 값을 복원함.
  3. 두 해시 값이 일치하면 메시지가 변조되지 않았음을 확인하고, 송신자의 신원을 인증함.

장점:

  • 송신자의 신원을 인증할 수 있음(부인 방지).
  • 메시지의 무결성을 보장함.

단점:

  • RSA 알고리즘은 계산량이 많아 속도가 느림.
  • 공개 키와 개인 키 관리가 필요함.

 

x.509 공인 인증서와 활용

X.509 공인 인증서는 국제 표준화된 공개키 기반 구조(PKI)를 따르는 디지털 인증서임.

이 인증서는 공개키와 사용자(개인, 조직, 혹은 서버)의 신원을 안전하게 연결하여 데이터 암호화, 신원 확인, 디지털 서명 검증 등 다양한 보안 기능을 제공함.

인증서는 신뢰할 수 있는 인증 기관(CA)에 의해 발급되며 이를 통해 사용자나 서버의 신뢰성을 검증함.

 

  • SSL/TLS 암호화: 웹 브라우저와 서버 간의 안전한 통신을 보장하며 HTTPS 연결에 사용됨.
  • 디지털 서명: 문서나 소프트웨어 코드에 디지털 서명을 추가하여 무결성과 출처를 보장함.
  • 이메일 보안(S/MIME): 이메일 메시지를 암호화하고 발신자의 신원을 검증함.
  • VPN 인증: 가상사설망(VPN)에서 사용자나 장치의 신원을 확인하는 데 사용됨.
  • 사용자 인증: 온라인 뱅킹, 전자상거래 등에서 사용자의 신원을 검증함.

 

 

EC2 kind(k8s) x.509 인증서 확인

인증서 정보 확인

#
sudo sysctl fs.inotify.max_user_watches=524288
sudo sysctl fs.inotify.max_user_instances=512

#
kind create cluster --name myk8s

# 인증서 확인
docker exec -it myk8s-control-plane ls -l /etc/kubernetes/pki
 
docker exec -it myk8s-control-plane cat /etc/kubernetes/pki/ca.crt
docker exec -it myk8s-control-plane openssl x509 -in /etc/kubernetes/pki/ca.crt -noout -text

docker exec -it myk8s-control-plane cat /etc/kubernetes/pki/apiserver-kubelet-client.crt
docker exec -it myk8s-control-plane openssl x509 -in /etc/kubernetes/pki/apiserver-kubelet-client.crt -noout -text

# CSR 확인 : EKS에서도 같이 확인해보자!
kubectl get certificatesigningrequests

 

/etc/kubernetes/pki 확인
/etc/kubernetes/pki/ca.crt 확인
/etc/kubernetes/pki/apiserver-kubelet-client.crt 확인
certificatesigingrequests 확인

 

Kind 설치자의 kubeconfig 정보 확인

https://www.base64decode.org/

 

Base64 Decode and Encode - Online

Decode from Base64 format or encode into it with various advanced options. Our site has an easy to use online tool to convert your data.

www.base64decode.org

 

# 아래 출력되는 client-certificate-data 값을 위 사이트에 붙여넣고 DECODE : 끝에 == 빼먹지 말 것
cat $HOME/.kube/config

# base64 디코딩
echo "<MY encrypt>" | base64 -d

# 어떤 인증서인지 확인
vi myuser.crt
-----BEGIN CERTIFICATE-----
MIIDKTCCAhGgAwIBAgIIeKzXmvzBrkswDQYJKoZIhvcNAQELBQAwFTETMBEGA1UE
...

# 아래 출력되는 client-key-data 값 어떤 키인지 확인
cat $HOME/.kube/config
    client-key-data: LS0tLS1CR....

-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAzO6NiTZgSKiIhdwc1KIfrvRPFfstapKbo/70DPXH12hyBaaZ
...

# 아래 출력되는 certificate-authority-data 값 어떤 인증서인지 확인
cat $HOME/.kube/config
    certificate-authority-data: LS0...

CA에 서명해서 받은 내 인증서

 

base64로 인코딩 돼있어서 디코딩 시 인증서 형태로 출력되는 점 확인

base64 디코딩
client-certificate-data base64 디코딩 후 인증서 확인
client-key-data base64 디코딩 시 Private key임을 확인

 

certificate-authority-data base64 디코딩 후 인증서 확인

 

신규 관리자를 위한 인증서 설정

# 하위 인증서를 위한 비밀키 생성
openssl genrsa -out yandhi.key 2048

# 확인
cat yandhi.key

# 인증서 사인 요청 파일(.csr) 파일 생성
openssl req -new -key yandhi.key -out yandhi.csr -subj "/O=kubeadm:cluster-admins/CN=yandhi-cert"

# 확인
cat yandhi.csr

# 출력 값을 아래 request 에 붙여넣기
cat yandhi.csr | base64 | tr -d '\n'

cat <<EOF | kubectl apply -f -
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
  name: yandhi-csr
spec:
  signerName: kubernetes.io/kube-apiserver-client
  groups:
  - system:masters
  - system:authenticated
  request: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURSBSRVFVRVNULS0tLS0KTUlJQ2ZEQ0NBV1FDQVFBd056RWZNQjBHQTFVRUNnd1dhM1ZpWldGa2JUcGpiSFZ6ZEdWeUxXRmtiV2x1Y3pFVQpNQklHQTFVRUF3d0xlV0Z1WkdocExXTmxjblF3Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLCkFvSUJBUUR1S2owVlhKWWxYalZ2dEwxSjMzM1FyS0NnZFJzV1hndzczbjUzZENqTjRJSGhiN0VqZHdUKytTeHcKYTc1WUJTVnprYzRqWGtDM0M4bzhSV0VEWGpiUjNCbEhHMWd4VnQ0dUlWZU5JRTRlYkZ4TzBQYnUyeHNqQ0picQpZV3ZaenFnNm1VaWJtekgwbUJMM1RaQUhrbTNJNmN5SGhFWGxPVUQ2MWNRWENzOHp1MnBJdjk2SEtPNkw0NkFBCjI5OHBDU1o0cTczdU1EWFlnNk9VNk8vV0Y2dG1IU1NQQW9zd2RUdnJreG9SWEJSejZmL1pTNlNxSUovT3I3ZUQKWjFhNDdHZ0JVMmdqSFQ0OTAzTlVHWDIrc1pyV2hzTC9EK3VPWGp6TW9RK082WnRLYnduTkkxRkEwenVwTUY4cgpWbHpYQW52cmp2UU5nbzVGYlVqZ1ZNWVlGY2duQWdNQkFBR2dBREFOQmdrcWhraUc5dzBCQVFzRkFBT0NBUUVBCkZSZC9yRTlsUlREcmFUWkttU01sM3lRS2JBTzI3QVFJMXQ4M1NmTmh5VldWak1Ua1k5d1lDVExQdnVXSGZEamgKSXZRclNuMzdpbWVMQmpxQ1lIOCtMTG1ZNlJGSEJQQVRFMGlBM0FxQ0dOUFM3bUpBdHBZVStoeTRpUWgxeTJPLwo1ckI2VW9MOGpRR2tic0pLdnhIVnFKNVdad0hUWDd0N3laR1RmcE5MdCtqUytVL0Q2clVNZVpFLzl3VnhBQndtCnhKRnBkN0xPR0IvdVFyaGRWL2dVTlNsNW5RM2dHR0Ruc1ZaSnA4ckI1VEZsdDkvL2FIWHJSb3J5TktNbyt4ME8KV2ZBTjhiblZlaDA3UVkreUdaMzk1ZmptNHpnZU1QMTN6aTdLcEVNd2JLR0hiTitZNFR3Yml1RWhIUnpSSG8ydgpsNkZXWHZCUEhwbjdXYW9vT0VRaHRBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUgUkVRVUVTVC0tLS0tCg==
  usages:
  - digital signature
  - key encipherment
  - client auth
EOF

# csr 확인
kubectl get csr

# 'k8s 관리자' 입장에서 해당 서명 요청을 승인하자
kubectl certificate approve yandhi-csr

# 확인 : 정상적으로 하위 인증서가 발급됨
kubectl get csr

# csr 에서 하위 인증서 추출
kubectl get csr gasida-csr -o jsonpath='{.status.certificate}' | base64 -d
kubectl get csr gasida-csr -o jsonpath='{.status.certificate}' | base64 -d > yandhi.crt

#
openssl x509 -in yandhi.crt -noout -text

# kubeconfig 에 새로운 사용자 등록
kubectl config set-credentials yandhi-user --client-certificate=yandhi.crt --client-key=yandhi.key
kubectl config set-context kind-yandhi --cluster=kind-myk8s --user=yandhi-user
cat ~/.kube/config
kubectl config use-context kind-yandhi # 혹은 kubectl ctx kind-yandhi

# ctx kind-yandhi 로 k8s 정보 확인 시도
kubectl get node

비밀키 생성
인증서 사인 요청 파일(.csr) 생성
CSR 요청
승인 전이라 Pending 상태
승인 후 Approved 확인
csr에서 하위 인증서 추출
kubeconfig에 새로운 사용자로 등록
k8s 정보 확인 성공

 

K8S(API 접근) 인증/인가

API 서버 사용 : kubectl(config, 다수 클러스터 관리 가능), 서비스 어카운트, https(x.509 Client Certs)

API 서버 접근 과정 : 인증 → 인가 → Admission Control(API 요청 검증, 필요 시 변형 - 예. ResourceQuota, LimitRange)

 

인증 (Authentication)

X.509 Client Certs : kubeconfig 에 CA crt(발급 기관 인증서) , Client crt(클라이언트 인증서) , Client key(클라이언트 개인키) 를 통해 인증

kubectl : 여러 클러스터(kubeconfig)를 관리 가능 - contexts 에 클러스터와 유저 및 인증서/키 참고

Service Account : 기본 서비스 어카운트(default) - 시크릿(CA crt 와 token)

 

인가 (Authorization)

인가 방식 : RBAC(Role, RoleBinding), ABAC, Webhook, Node Authorization

RBAC : 역할 기반의 권한 관리, 사용자와 역할을 별개로 선언 후 두가지를 조합(binding)해서 사용자에게 권한을 부여하여 kubectl or API로 관리 가능

  • Namespace/Cluster - Role/ClusterRole, RoleBinding/ClusterRoleBinding, Service Account
  • Role(롤) - (RoleBinding 롤 바인딩) - Service Account(서비스 어카운트) : 롤 바인딩은 롤과 서비스 어카운트를 연결
  • Role(네임스페이스내 자원의 권한) vs ClusterRole(클러스터 수준의 자원의 권한)

 

.kube/config 파일 내용

clusters : kubectl 이 사용할 쿠버네티스 API 서버의 접속 정보 목록. 원격의 쿠버네티스 API 서버의 주소를 추가해 사용 가능

users : 쿠버네티스의 API 서버에 접속하기 위한 사용자 인증 정보 목록. (서비스 어카운트의 토큰, 혹은 인증서의 데이터 등)

contexts : cluster 항목과 users 항목에 정의된 값을 조합해 최종적으로 사용할 쿠버네티스 클러스터의 정보(컨텍스트)를 설정.

  • 예를 들어 clusters 항목에 클러스터 A,B 가 정의돼 있고, users 항목에 사용자 a,b 가 정의돼 있다면 cluster A + user a 를 조합해, 'cluster A 에 user a 로 인증해 쿠버네티스를 사용한다' 라는 새로운 컨텍스트를 정의할 수 있음.
  • kubectl 을 사용하려면 여러 개의 컨텍스트 중 하나를 선택.

 

실습 환경

  • 쿠버네티스에 사용자를 위한 서비스 어카운트(Service Account, SA)를 생성 : dev-k8s, infra-k8s
  • 사용자는 각기 다른 권한(Role, 인가)을 가짐 : dev-k8s(dev-team 네임스페이스 내 모든 동작) , infra-k8s(dev-team 네임스페이스 내 모든 동작)
  • 각각 별도의 kubectl 파드를 생성하고, 해당 파드에 SA 를 지정하여 권한에 대한 테스트를 진행

 

네임스페이스와 서비스 어카운트 생성 후 확인

  • 파드 기동 시 서비스 어카운트 한 개가 할당되며, 서비스 어카운트 기반 인증/인가를 함, 미지정 시 기본 서비스 어카운트가 할당
  • 서비스 어카운트에 자동 생성된 시크릿에 저장된 토큰으로 쿠버네티스 API에 대한 인증 정보로 사용 할 수 있다 ← 1.23 이전 버전의 경우에만 해당
# 네임스페이스(Namespace, NS) 생성 및 확인
kubectl create namespace dev-team
kubectl create ns infra-team

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

# 네임스페이스에 각각 서비스 어카운트 생성 : serviceaccounts 약자(=sa)
kubectl create sa dev-k8s -n dev-team
kubectl create sa infra-k8s -n infra-team

# 서비스 어카운트 정보 확인
kubectl get sa -n dev-team
kubectl get sa dev-k8s -n dev-team -o yaml

kubectl get sa -n infra-team
kubectl get sa infra-k8s -n infra-team -o yaml

네임스페이스 생성 및 확인
서비스 어카운트 정보 확인

 

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

# 각각 네임스페이스에 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.31.4
    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.31.4
    command: ["tail"]
    args: ["-f", "/dev/null"]
  terminationGracePeriodSeconds: 0
EOF

# 확인
kubectl get pod -A
kubectl get pod -o dev-kubectl -n dev-team -o yaml
kubectl get pod -o infra-kubectl -n infra-team -o yaml

# 파드에 기본 적용되는 서비스 어카운트(토큰) 정보 확인
kubectl exec -it dev-kubectl -n dev-team -- ls /run/secrets/kubernetes.io/serviceaccount
kubectl exec -it dev-kubectl -n dev-team -- cat /run/secrets/kubernetes.io/serviceaccount/token
kubectl exec -it dev-kubectl -n dev-team -- cat /run/secrets/kubernetes.io/serviceaccount/namespace
kubectl exec -it dev-kubectl -n dev-team -- cat /run/secrets/kubernetes.io/serviceaccount/ca.crt

# 각각 파드로 Shell 접속하여 정보 확인 : 단축 명령어(alias) 사용
alias k1='kubectl exec -it dev-kubectl -n dev-team -- kubectl'
alias k2='kubectl exec -it infra-kubectl -n infra-team -- kubectl'

# 권한 테스트
k1 get pods # kubectl exec -it dev-kubectl -n dev-team -- kubectl get pods 와 동일한 실행 명령이다!
k1 run nginx --image nginx:1.20-alpine
k1 get pods -n kube-system

k2 get pods # kubectl exec -it infra-kubectl -n infra-team -- kubectl get pods 와 동일한 실행 명령이다!
k2 run nginx --image nginx:1.20-alpine
k2 get pods -n kube-system

# (옵션) kubectl auth can-i 로 kubectl 실행 사용자가 특정 권한을 가졌는지 확인
k1 auth can-i get pods
no

dev-kubectl pod 확인
infra-kubectl pod 확인
파드에 기본 적용되는 서비스 어카운트(토큰) 정보 확인
Pod에 접근하여 명령어 실행할 권한이 있는지 테스트 시 권한이 없어서 실행되지 않음
kubectl auth can-i 로 권한 보유 여부를 확인 가능

 

각각 네임스페이스에 Role을 생성 후 서비스 어카운트 바인딩

  • 롤(Role) : apiGroups 와 resources 로 지정된 리소스에 대해 verbs 권한을 인가
  • 실행 가능한 조작(verbs) : *(모두 처리), create(생성), delete(삭제), get(조회), list(목록조회), patch(일부업데이트), update(업데이트), watch(변경감시)

# Print the supported API resources on the server.
kubectl api-resources

# Print the supported API resources with more information
kubectl api-resources -o wide
  
# Print the supported API resources with a specific APIGroup
kubectl api-resources --api-group=""
kubectl api-resources --api-group="apps"
kubectl api-resources --api-group=metrics.k8s.io
kubectl api-resources --api-group=admissionregistration.k8s.io
kubectl api-resources --api-group=rbac.authorization.k8s.io
kubectl api-resources --api-group=apiextensions.k8s.io

Role, RoleBinding, and subjects
Possible types for an element subjects list in a RoleBinding
Possible types for a roleRef field in a RoleBinding
standard user-facing ClusterRoles

 

# 각각 네임스페이스내의 모든 권한에 대한 롤 생성
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

 

모든 권한을 부여하는 롤을 넣어준 뒤 확인

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

롤 바인딩 확인

 

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

# 각각 파드로 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 pods -n kube-system -v=6
k1 get nodes
k1 get nodes -v=6

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

# (옵션) kubectl auth can-i 로 kubectl 실행 사용자가 특정 권한을 가졌는지 확인
k1 auth can-i get pods
yes

권한을 부여 받은 네임스페이스에서는 명령어 수행이 가능
kubectl auth can-i 확인

 

EKS 인증/인가

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

 

RBAC 관련 krew 플러그인

  • 롤(Role) : apiGroups 와 resources 로 지정된 리소스에 대해 verbs 권한을 인가
  • 실행 가능한 조작(verbs) : *(모두 처리), create(생성), delete(삭제), get(조회), list(목록조회), patch(일부업데이트), update(업데이트), watch(변경감시)

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

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

# 서버 리소스 RBAC 접근 매트릭스 표시
kubectl access-matrix -h
kubectl access-matrix # 클러스터 범위 리소스 접근 검토
kubectl access-matrix --namespace default # 'default' 네임스페이스 리소스 접근 검토

# Subject(사용자/그룹/서비스 계정) RBAC 조회
kubectl rbac-tool -h
kubectl rbac-tool lookup
kubectl rbac-tool lookup system:masters

kubectl rbac-tool lookup system:nodes # eks:node-bootstrapper 역할 조회
kubectl rbac-tool lookup system:bootstrappers # eks:node-bootstrapper 역할 조회
kubectl describe ClusterRole eks:node-bootstrapper # eks:node-bootstrapper 역할 설명

# Subject별 RBAC 정책 규칙 나열
kubectl rbac-tool policy-rules
kubectl rbac-tool policy-rules -e '^system:.*' # 'system:'으로 시작하는 규칙 나열
kubectl rbac-tool policy-rules -e '^system:authenticated' # 'system:authenticated' 규칙 나열

# 클러스터 권한으로 ClusterRole 생성
kubectl rbac-tool show

# 현재 컨텍스트 인증 Subject 확인
kubectl rbac-tool whoami

# Subject(ServiceAccount, 사용자, 그룹) RBAC 역할 요약
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:nodes      # 그룹 요약
kubectl rolesum -k Group system:authenticated # 그룹 요약

# [운영서버1 EC2 : 터미널1] A tool to visualize your RBAC permissions
kubectl rbac-view

## 이후 해당 운영서버1 EC2 공인 IP:8800 웹 접속 : 최초 접속 후 정보 가져오는데 다시 시간 걸림 (2~3분 정도 후 화면 출력됨) 
echo -e "RBAC View Web http://$(curl -s ipinfo.io/ip):8800"

RBAC 정보 수집
웹에서 롤을 더 편하게 볼 수 있음

 

약어 Verbs 설명
G Get 리소스 조회 가능
L List 리소스 목록 나열 가능
W Watch 리소스 감시 가능
C Create 새 리소스 생성 가능
U Update 기존 리소스 수정 가능
P Patch 리소스 일부 패치 가능
D Delete 리소스 삭제 가능
DC Delete Collection 리소스 컬렉션(여러 개) 삭제 가능

 

EKS 인증/인가 확인

kubectl 요청 시 흐름

 

인증은 AWS IAM, 인가는 K8S RBAC에서 처리함.

 

  1. kubectl 명령 → aws eks get-token → STS에 토큰 요청 ⇒ 응답값 디코드(Pre-Signed URL 이며 GetCallerIdentity)
    • STS Security Token Service : AWS 리소스에 대한 액세스를 제어할 수 있는 임시 보안 자격 증명(STS)을 생성하여 신뢰받는 사용자에게 제공할 수 있음
    • AWS CLI 버전 1.16.156 이상에서는 별도 aws-iam-authenticator 설치 없이 aws eks get-token으로 사용 가능
# sts caller id의 ARN 확인
aws sts get-caller-identity --query Arn
"arn:aws:iam::<자신의 Account ID>:user/admin"

# kubeconfig 정보 확인
cat ~/.kube/config
...
- 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'
aws eks get-token --cluster-name $CLUSTER_NAME --debug | jq

 

./kube/config 확인, Regional_endpoint를 사용

 

eks get-token help 확인, 인증을 위해 토큰을 요청함
토큰을 발급 받을 때마다 expire 시간도 달라짐

 

EKS 인증/인가 실습

testuser 자격증명 설정 및 확인

# testuser 사용자 생성
aws iam create-user --user-name testuser

# 사용자에게 프로그래밍 방식 액세스 권한 부여
aws iam create-access-key --user-name testuser

# 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

testuser 생성
사용자에게 프로그래밍 방식 액세스 권한 부여
kubectl whoami 확인
운영서버2번 PublicIPAdd 확인

 

[운영서버-2] testuser 자격증명 설정 및 확인

# 아래 실습 진행을 위해, kind(k8s) 삭제
kind delete cluster --name myk8s
mv ~/.kube/config ~/.kube/config.old

# 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 설정을 해줘야함
자격 증명 설정 후 get-caller-identity 확인 가능
kubectl get node -v6 확인, AdministratorAccess 권한을 가지고 있어도 실패 확

 

[운영서버-1] 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

testuser를 system:masters 그룹으로 변경해 EKS 관리자 수준 권한으로 설정

 

[운영서버-2] testuser kubeconfig 생성 및 kubectl 사용 확인

# testuser kubeconfig 생성
CLUSTER_NAME=myeks
aws eks update-kubeconfig --name $CLUSTER_NAME --user-alias testuser

# 운영서버의 config와 비교해보자
cat ~/.kube/config

# kubectl 사용 확인
kubectl ns default
kubectl get node -v6

# rbac-tool 후 확인 >> 기존 계정과 비교해보자
kubectl rbac-tool whoami

 

노드 조회 가능
system:authenticated 그룹 추가된 점 확인

 

[운영서버-1] 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

삭제 후에는 조회 불가

 

EC2 Instance Profile(IAM Role)에 맵핑된 k8s rbac 확인 해보기

 

노드 mapRoles 확인

# 노드에 STS ARN 정보 확인 : Role 뒤에 인스턴스 ID
for node in $N1 $N2 $N3; do ssh ec2-user@$node aws sts get-caller-identity --query Arn; done

# aws-auth 컨피그맵 확인 >> system:nodes 와 system:bootstrappers 의 권한은 어떤게 있는지 확인
# username 확인, 인스턴스 ID? EC2PrivateDNSName?
kubectl describe configmap -n kube-system aws-auth

# Get IAM identity mapping(s)
eksctl get iamidentitymapping --cluster $CLUSTER_NAME

노드 STS ARN 정보 확인
IAM identity mapping 확인

 

awscli 파드를 추가하고, 해당 노드(EC2)의 IMDS 정보 확인 : AWS CLI v2 파드 생성

# awscli 파드 생성
cat <<EOF | kubectl apply -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

awscli 파드에서 EC2 InstanceProfile(IAM Role)의 ARN 정보 확인
파드 Bash shell에서 실행
Token을 이용한 IMDSv2 사용
출력된 정보는 AWS API를 사용할 수 있는 어느 곳에서나 Expiration 되기 전까지 사용 가능
워커노드에 연결된 IAM 역할 AWS 콘솔에서 확인

 

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

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

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

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

Pod내 kubeconfig 파일 확인

 

EKS IRSA & Pod Identity

EC2 Instance Profile는 사용하기 편하지만 최소 권한 부여 원칙에 위배돼 보안상 권고하지 않음

# 설정 예시 1 : eksctl 사용 시
eksctl create cluster --name $CLUSTER_NAME ... --external-dns-access --full-ecr-access --asg-access

# 설정 예시 2 : eksctl로 yaml 파일로 노드 생성 시
cat myeks.yaml
...
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 : 테라폼
...

 

위와 같이 설정할 경우 Node 전체에 권한이 들어가게 됨(Instance Profile)

IRSA보다는 Pod Profile을 권장

 

필요 지식

Service Account Token Volume Projection : '서비스 계정 토큰'의 시크릿 기반 볼륨 대신 'projected volume' 사용

파드가 특정 IAM 역할로 Assume 할때 토큰을 AWS에 전송하고, AWS는 토큰과 EKS IdP를 통해 해당 IAM 역할을 사용할 수 있는지 검증

  • 기본 서비스 계정 토큰으로는 사용하기에 부족함이 있음. 토큰을 사용하는 대상(audience), 유효 기간(expiration) 등 토큰의 속성을 지정할 필요가 있기 때문.
  • Service Account Token Volume Projection 기능을 사용하면 이러한 부족한 점들을 해결할 수 있음.

 

Bound Service Account Token Volume 바인딩된 서비스 어카운트 토큰 볼륨

  • FEATURE STATE: Kubernetes v1.22 [stable]
  • 서비스 어카운트 어드미션 컨트롤러는 토큰 컨트롤러에서 생성한 만료되지 않은 서비스 계정 토큰에 시크릿 기반 볼륨 대신 다음과 같은 프로젝티드 볼륨을 추가함.
- name: kube-api-access-<random-suffix>
  projected:
    defaultMode: 420 # 420은 rw- 로 소유자는 읽고쓰기 권한과 그룹내 사용자는 읽기만, 보통 0644는 소유자는 읽고쓰고실행 권한과 나머지는 읽고쓰기 권한
    sources:
      - serviceAccountToken:
          expirationSeconds: 3607
          path: token
      - configMap:
          items:
            - key: ca.crt
              path: ca.crt
          name: kube-root-ca.crt
      - downwardAPI:
          items:
            - fieldRef:
                apiVersion: v1
                fieldPath: metadata.namespace
              path: namespace

 

프로젝티드 볼륨은 세 가지로 구성됨. PSAT (Projected Service Account Tokens)

 

  1. kube-apiserver로부터 TokenRequest API를 통해 얻은 서비스어카운트토큰(ServiceAccountToken). 서비스어카운트토큰은 기본적으로 1시간 뒤에, 또는 파드가 삭제될 때 만료된다. 서비스어카운트토큰은 파드에 연결되며 kube-apiserver를 위해 존재한다.
  2. kube-apiserver에 대한 연결을 확인하는 데 사용되는 CA 번들을 포함하는 컨피그맵(ConfigMap).
  3. 파드의 네임스페이스를 참조하는 DownwardA
  • Configure a Pod to Use a Projected Volume for Storage : 시크릿 컨피그맵 downwardAPI serviceAccountToken의 볼륨 마운트를 하나의 디렉터리에 통합
    • This page shows how to use a projected Volume to mount several existing volume sources into the same directory. Currently, secret, configMap, downwardAPI, and serviceAccountToken volumes can be projected.
    • Note: serviceAccountToken is not a volume type.
apiVersion: v1
kind: Pod
metadata:
  name: test-projected-volume
spec:
  containers:
  - name: test-projected-volume
    image: busybox:1.28
    args:
    - sleep
    - "86400"
    volumeMounts:
    - name: all-in-one
      mountPath: "/projected-volume"
      readOnly: true
  volumes:
  - name: all-in-one
    projected:
      sources:
      - secret:
          name: user
      - secret:
          name: pass
# Create the Secrets:
## Create files containing the username and password:
echo -n "admin" > ./username.txt
echo -n "1f2d1e2e67df" > ./password.txt

## Package these files into secrets:
kubectl create secret generic user --from-file=./username.txt
kubectl create secret generic pass --from-file=./password.txt

# 파드 생성
kubectl apply -f https://k8s.io/examples/pods/storage/projected.yaml

# 파드 확인
kubectl get pod test-projected-volume -o yaml | kubectl neat
...
volumes:
  - name: all-in-one
    projected:
      defaultMode: 420
      sources:
      - secret:
          name: user
      - secret:
          name: pass
  - name: kube-api-access-n6n9v
    projected:
      defaultMode: 420
      sources:
      - serviceAccountToken:
          expirationSeconds: 3607
          path: token
      - configMap:
          items:
          - key: ca.crt
            path: ca.crt
          name: kube-root-ca.crt
      - downwardAPI:
          items:
          - fieldRef:
              apiVersion: v1
              fieldPath: metadata.namespace
            path: namespace

# 시크릿 확인
kubectl exec -it test-projected-volume -- ls /projected-volume/
password.txt  username.txt

kubectl exec -it test-projected-volume -- cat /projected-volume/username.txt ;echo
admin

kubectl exec -it test-projected-volume -- cat /projected-volume/password.txt ;echo
1f2d1e2e67df

# 삭제
kubectl delete pod test-projected-volume && kubectl delete secret user pass

 

k8s api 접근 단계

  • AuthN → AuthZ → Admisstion Control 권한이 있는 사용자에 한해서 관리자(Admin)가 특정 행동을 제한(validate) 혹은 변경(mutate)
  • AuthN & AuthZ - MutatingWebhook - Object schema validation - ValidatingWebhook → etcd

  • Admission Control도 Webhook으로 사용자에게 API가 열려있고, 사용자는 자신만의 Admission Controller를 구현할 수 있으며, 이를 Dynamic Admission Controller라고 부르고, 크게 MutatingWebhookValidatingWebhook 로 나뉨.
  • MutatingWebhook은 사용자가 요청한 request에 대해서 관리자가 임의로 값을 변경하는 작업임.
  • ValidatingWebhook은 사용자가 요청한 request에 대해서 관리자기 허용을 막는 작업임.

JWT : Bearer type - JWT(JSON Web Token) X.509 Certificate의 lightweight JSON 버전

  • Bearer type 경우, 서버에서 지정한 어떠한 문자열도 입력할 수 있으나 굉장히 허술한 느낌을 받을 수 있음.
  • 이를 보완하고자 쿠버네티스에서 Bearer 토큰을 전송할 때 주로 JWT (JSON Web Token) 토큰을 사용.
  • JWT는 X.509 Certificate와 마찬가지로 private key를 이용하여 토큰을 서명하고 public key를 이용하여 서명된 메세지를 검증.
  • 이러한 메커니즘을 통해 해당 토큰이 쿠버네티스를 통해 생성된 valid한 토큰임을 인증할 수 있음.
  • X.509 Certificate의 lightweight JSON 버전이라고 생각하면 편리.
  • jwtJSON 형태로 토큰 형식을 정의한 스펙. jwt는 쿠버네티스에서 뿐만 아니라 다양한 웹 사이트에서 인증, 권한 허가, 세션관리 등의 목적으로 사용.
    • Header: 토큰 형식와 암호화 알고리즘을 선언.
    • Payload: 전송하려는 데이터를 JSON 형식으로 기입.
    • Signature: Header와 Payload의 변조 가능성을 검증.
  • 각 파트는 base64 URL 인코딩이 되어서 .으로 합쳐지게 됨.

 

OIDC : 사용자를 인증해 사용자에게 액세스 권한을 부여할 수 있게 해주는 프로토콜 ⇒ [커피고래]님 블로그 OpenID Connect

  • OAuth 2.0 : 권한허가 처리 프로토콜, 다른 서비스에 접근할 수 있는 권한을 획득하거나 반대로 다른 서비스에게 권한을 부여할 수 있음
    • 위임 권한 부여 Delegated Authorization, 사용자 인증 보다는 제한된 사람에게(혹은 시스템) 제한된 권한을 부여하는가, 예) 페이스북 posting 권한
    • Access Token : 발급처(OAuth 2.0), 서버의 리소스 접근 권한
  • OpenID : 비영리기관인 OpenID Foundation에서 추진하는 개방형 표준 및 분산 인증 Authentication 프로토콜, 사용자 인증 및 사용자 정보 제공(id token)
    • ID Token : 발급처(OpenID Connect), 유저 프로필 정보 획득
  • OIDC OpenID Connect = OpenID 인증 + OAuth2.0 인가, JSON 포맷을 이용한 RESful API 형식으로 인증
    • iss: 토큰 발행자
    • sub: 사용자를 구분하기 위한 유니크한 구분자
    • email: 사용자의 이메일
    • iat: 토큰이 발행되는 시간을 Unix time으로 표기한 것
    • exp: 토큰이 만료되는 시간을 Unix time으로 표기한 것
    • aud: ID Token이 어떤 Client를 위해 발급된 것인지.
  • IdP Open Identify Provider : 구글, 카카오와 같이 OpenID 서비스를 제공하는 신원 제공자.
    • OpenID Connect에서 IdP의 역할을 OAuth가 수행
  • RP Relying Party : 사용자를 인증하기 위해 IdP에 의존하는 주체

 

IRSA

 파드가 특정 IAM 역할로 Assume 할때 토큰을 AWS에 전송하고, AWS는 토큰과 EKS IdP를 통해 해당 IAM 역할을 사용할 수 있는지 검증

 

실습1

# 파드1 생성
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: eks-iam-test1
spec:
  containers:
    - name: my-aws-cli
      image: amazon/aws-cli:latest
      args: ['s3', 'ls']
  restartPolicy: Never
  automountServiceAccountToken: false
  terminationGracePeriodSeconds: 0
EOF

# 확인
kubectl get pod
kubectl describe pod

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

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

권한이 없음

실습2

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

# 확인
kubectl get pod
kubectl describe pod
kubectl get pod eks-iam-test2 -o yaml 
kubectl exec -it eks-iam-test2 -- ls /var/run/secrets/kubernetes.io/serviceaccount
kubectl exec -it eks-iam-test2 -- cat /var/run/secrets/kubernetes.io/serviceaccount/token ;echo

# aws 서비스 사용 시도
kubectl exec -it eks-iam-test2 -- aws s3 ls

# 서비스 어카운트 토큰 확인
SA_TOKEN=$(kubectl exec -it eks-iam-test2 -- cat /var/run/secrets/kubernetes.io/serviceaccount/token)
echo $SA_TOKEN

# jwt 혹은 아래 JWT 웹 사이트 이용 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",
  "jti": "ee823c34-f020-4f77-90f3-61fab4de244a",
  "kubernetes.io": {
    "namespace": "default",
    "node": {
      "name": "ip-192-168-1-70.ap-northeast-2.compute.internal",
      "uid": "f4fabf42-4a9c-43f0-8a1e-edeb2a8fbb42"
    },
    "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

IRSA 세팅 안해서 권한이 없음

 

실습3

my-sa 이름의 Sub account 부착

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


eksctl get iamserviceaccount --cluster $CLUSTER_NAME

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

생성한 sa가 Role로 들어간 점 확인



# 파드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 추가함 : AWS IAM 역할을 Pod에 자동으로 주입
kubectl get mutatingwebhookconfigurations pod-identity-webhook -o yaml

# 파드 생성 yaml에 없던 내용이 추가됨!!!!!
# Pod Identity Webhook은 mutating webhook을 통해 아래 Env 내용과 1개의 볼륨을 추가함
kubectl get pod eks-iam-test3
kubectl get pod eks-iam-test3 -o yaml
...
    volumeMounts: 
    - 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
...

kubectl exec -it eks-iam-test3 -- ls /var/run/secrets/eks.amazonaws.com/serviceaccount
token

kubectl exec -it eks-iam-test3 -- cat /var/run/secrets/eks.amazonaws.com/serviceaccount/token ; echo
...

kubectl describe pod eks-iam-test3
...
Environment:
      AWS_STS_REGIONAL_ENDPOINTS:   regional
      AWS_DEFAULT_REGION:           ap-northeast-2
      AWS_REGION:                   ap-northeast-2
      AWS_ROLE_ARN:                 arn:aws:iam::911283464785:role/eksctl-myeks-addon-iamserviceaccount-default-Role1-GE2DZKJYWCEN
      AWS_WEB_IDENTITY_TOKEN_FILE:  /var/run/secrets/eks.amazonaws.com/serviceaccount/token
    Mounts:
      /var/run/secrets/eks.amazonaws.com/serviceaccount from aws-iam-token (ro)
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-69rh8 (ro)
...
Volumes:
  aws-iam-token:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  86400
  kube-api-access-sn467:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    ConfigMapOptional:       <nil>
    DownwardAPI:             true
...

# 파드에서 aws cli 사용 확인
eksctl get iamserviceaccount --cluster $CLUSTER_NAME
kubectl exec -it eks-iam-test3 -- aws sts get-caller-identity --query Arn
"arn:aws:sts::911283464785:assumed-role/eksctl-myeks-addon-iamserviceaccount-default-Role1-GE2DZKJYWCEN/botocore-session-1685179271"

# 되는 것고 안되는 것은 왜그런가?
kubectl exec -it eks-iam-test3 -- aws s3 ls
kubectl exec -it eks-iam-test3 -- aws ec2 describe-instances --region ap-northeast-2
kubectl exec -it eks-iam-test3 -- aws ec2 describe-vpcs --region ap-northeast-2

에러 발생하지 않고 명령어 가능

 

  • IRSA를 가장 취약하게 사용하는 방법 : 정보 탈취 시 키/토큰 발급 약용 가능
  • AWS는 JWT 토큰의 유효성만 확인 하지만 토큰 파일과 서비스 계정에 지정된 실제 역할 간의 일관성을 보장하지는 않음 → Condition 잘못 설정 시, 토큰과 역할 ARN만 있다면 동일 토큰으로 다른 역할을 맡을 수 있음

 

Pod Identity

 

 

기존에는 외부 OIDC를 사용했으나 AWS 내부에서 전부 처리하게 됨

Pod Identity 사용하려면 클러스터에 EKS Pod Identity Agent를 설치해야 함

#
ADDON=eks-pod-identity-agent
aws eks describe-addon-versions \
    --addon-name $ADDON \
    --kubernetes-version 1.31 \
    --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.3.5

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

 

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 ap-northeast-2

# 확인
kubectl get sa
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"
      ]
    }
  ]
}

 

 

Kyverno

Cloud Native Computing Foundation (CNCF)에서 관리하는 Kubernetes 네이티브 정책 엔진

 

주요 특징

  • YAML 기반 선언적 Kubernetes 리소스 사용
  • 새로운 정책 언어 학습 불필요
  • 리소스 구성 검증, 변경, 생성 가능
  • 이미지 서명 및 증명 검증 지원

기능

  • Kubernetes 리소스로서의 정책 관리
  • 모든 리소스 검증, 변경, 생성, 제거
  • 컨테이너 이미지 및 메타데이터 검증
  • 레이블 선택기 및 와일드카드 사용 리소스 매칭
  • 네임스페이스 간 구성 동기화
  • 비준수 리소스 차단 및 정책 위반 보고
  • 셀프 서비스 보고서 및 정책 예외 지원
  • CI/CD 파이프라인에서 정책 테스트 가능

 

Kyverno 작동 방식

  • Dynamic Admission Controller로 작동
  • Kubernetes API 서버와 통합
  • 주요 구성 요소: Webhook 서버, Webhook 컨트롤러
  • AdmissionReview 요청 처리 및 정책 적용
# 설치
# 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.3.7 -f kyverno-value.yaml -n kyverno**

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

# (참고) 기본 인증서 확인 <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

Policy and Role : Kyverno Policy는 rules 모음


각 규칙은 match선언, 선택적 exclude선언 및 validate, mutate, generate또는 verifyImages선언 중 하나로 구성됨. 
각 규칙에는 단일 validate, mutate, generate또는 verifyImages하위 선언만 포함될 수 있음.

 

Validation : 파드에 라벨(lables) 검증

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

# ClusterPolicy 적용
kubectl apply -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/'

# 디플로이먼트 생성 시도
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

kubectl get policyreport e1073f10-84ef-4999-9651-9983c49ea76a -o yaml
apiVersion: wgpolicyk8s.io/v1alpha2
kind: PolicyReport
metadata: 
  labels: 
    app.kubernetes.io/managed-by: kyverno
  name: e1073f10-84ef-4999-9651-9983c49ea76a
  namespace: default
results: 
- message: validation rule 'check-team' passed.
  policy: require-labels
  result: pass
  rule: check-team
  scored: true
  source: kyverno
  timestamp: 
    nanos: 0
    seconds: 1712473900
scope: 
  apiVersion: v1
  kind: Pod
  name: nginx
  namespace: default
  uid: e1073f10-84ef-4999-9651-9983c49ea76a
summary: 
  error: 0
  fail: 0
  pass: 1
  skip: 0
  warn: 0

# 정책 삭제
kubectl delete clusterpolicy require-labels

 

Mutation : 파드에 라벨(lables) 추가

#
kubectl apply -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

 

Kyverno Cli 

# Install Kyverno CLI using kubectl krew plugin manager
kubectl krew install kyverno

# test the Kyverno CLI
kubectl kyverno version
kubectl kyverno --help

# 정책 테스트 : --policy-report 옵션은 로컬에서도 리포트 출력을 할 수 있는 기능
kubectl kyverno apply require-probes.yaml --resource nginx.yaml --policy-report

...

 

'k8s > AWS EKS' 카테고리의 다른 글

[AWS] EKS Upgrade  (0) 2025.04.02
[AWS] EKS Mode/Nodes  (0) 2025.03.22
[AWS] EKS AutoScaling  (0) 2025.03.08
[AWS] EKS Observability  (0) 2025.03.01
[AWS] EKS Storage  (0) 2025.02.23