본문 바로가기
스터디/Kubernetes

[AWES] EKS Security - IRSA

by 큐큐! 2023. 5. 31.

AEWS 스터디에서는 AWS의 관리형 Kubernetes인 Elastic Kubernetes의 다양한 기능들을 실습해보면서 익혀본다. 이 글은 스터디를 참여하면서 학습한 내용을 정리하는 연재 글이다. 스터디 진도에 맞춰 글을 작성한다. 

이 글에서는 EKS Security - IRSA에 대해서 알아본다. 

1. EC2 Instance Profile

IAM 사용자가 사람을 식별하고 권한을 주기 위한 개념이면, EC2 Instance Profile은 인스턴스를 구분하고 권한을 주기 위한 개념이다. Pod가 서비스를 사용할 때 EC2 Instance의 모든 권한을 가져가므로, 최소 권한 부여 원칙에 위배하며 보안상 권고하지 않는다.IRSA 권장!

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

eksctl 사용하여 클러스터 yaml에 managedNodeGroups.iam을 확인 해보면 정책이 보인다. 이 정책이 AWS 콘솔에서의 node가 가진 권한이다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 설정 예시 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 : 테라폼
...
cs

콘솔에서 노드의 역할 확인

2. IRSA

2-1 필요 지식 : 네 가지
1) Service Account Token Volume Projection, 2) k8s api 접근 단계, 3) JWT(JSON Web Token) 4) OIDC

1) Service Account Token Volume Projection

Service Account Token Volume Projection

프로젝티드 볼륨이란 동일한 디렉토리 밑으로 여러개의 파드 외부 볼륨을 마운트하는 방법이다. 컨피그맵과 시크릿을 프로젝티드 볼륨 방식으로 동일한 디렉토리 밑으로 마운트 할 수 있다. 프로젝티드 볼륨에서 ‘Projected’라는 의미는 동일한 목적으로 관련된 것을 묶었다는 의미라고 볼 수 있다. 시크릿, 컨피그맵 뿐 아니라 downwardAPI, serviceAccountToken도 사용 가능하다. 
*참고: https://happycloud-lee.tistory.com/255

여기서 사용하는 serviceAccountToken에는 시크릿이 들어있는 볼륨이 Projected로 작성되어 있다. '서비스 계정 토큰'의 시크릿 기반 볼륨이 아닌 Projected 볼륨을 사용하면 대상(audience), 유효 기간(expiration) 등의 속성을 지정할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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
cs

 

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

FEATURE STATE: Kubernetes v1.22 [stable]
서비스 어카운트 어드미션 컨트롤러는 토큰 컨트롤러에서 생성한 만료되지 않은 서비스 계정 토큰에 시크릿 기반 볼륨 대신 다음과 같은 프로젝티드 볼륨을 추가한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- 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
cs

위 소스 부분의 구성요소를 보면 세 가지로 구성된다. 

  1. kube-apiserver로부터 TokenRequest API를 통해 얻은 서비스어카운트토큰(ServiceAccountToken). 서비스어카운트토큰은 기본적으로 1시간 뒤에, 또는 파드가 삭제될 때 만료된다. 서비스어카운트토큰은 파드에 연결되며 kube-apiserver를 위해 존재한다.
  2. kube-apiserver에 대한 연결을 확인하는 데 사용되는 CA 번들을 포함하는 컨피그맵(ConfigMap).
  3. 파드의 네임스페이스를 참조하는 DownwardAPI

실습을 해보자. 시크릿을 생성하고 위 프로젝티드 볼륨에 저장하여 사용해보자. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# Create the Secrets:
## Create files containing the username and password:
echo -"admin" > ./username.txt
echo -"1f2d1e2e67df" > ./password.txt
 
## Package these files into secrets:
kubectl create secret generic user --from-file=./username.txt
kubectl create secret generic pass --from-file=./password.txt
 
# 파드 생성
kubectl apply -f https://k8s.io/examples/pods/storage/projected.yaml
 
# 파드 확인
kubectl get pod test-projected-volume -o yaml | kubectl neat | yh
...
volumes:
  - name: all-in-one
    projected:
      defaultMode: 420
      sources:
      - secret:
          name: user
      - secret:
          name: pass
  - name: kube-api-access-n6n9v
    projected:
      defaultMode: 420
      sources:
      - serviceAccountToken:
          expirationSeconds: 3607
          path: token
      - configMap:
          items:
          - key: ca.crt
            path: ca.crt
          name: kube-root-ca.crt
      - downwardAPI:
          items:
          - fieldRef:
              apiVersion: v1
              fieldPath: metadata.namespace
            path: namespace
 
# 시크릿 확인
kubectl exec -it test-projected-volume -- ls /projected-volume/
password.txt  username.txt
 
kubectl exec -it test-projected-volume -- cat /projected-volume/username.txt ;echo
admin
 
kubectl exec -it test-projected-volume -- cat /projected-volume/password.txt ;echo
1f2d1e2e67df
 
# 삭제
kubectl delete pod test-projected-volume && kubectl delete secret user pass
cs
생성한 시크릿을 볼륨 내에서 확인 할 수 있다.

2) k8s api 접근 단계

https://kubernetes.io/blog/2019/03/21/a-guide-to-kubernetes-admission-controllers/

쿠버네티스 API접근하는 단계를 상세히 보면 중간에 2가지 더 있다. 인증 인가가 될 때 모든 내용이 etcd 에 저장 되고 실행될 때는 아래 두 가지 과정이 관여한다. 

  • MutatingWebhook: 사용자가 요청한 request에 대해서 관리자가 임의로 값을 변경하는 작업
  • ValidatingWebhook: 사용자가 요청한 request에 대해서 관리자기 허용을 막는 작업

이 과정을 서드파티로 Webhook으로 사용하는 것을 MutatingWebhook, ValidatingWebhook 이라 한다. 두 가지 과정을 관여하는 리스트를 확인 할 수 있다. 

MutatingWebhook,&nbsp;ValidatingWebhook

3) JWT : Bearer type - JWT(JSON Web Token)

쿠버네티스에서 Bearer 토큰을 전송할 때 주로 JWT (JSON Web Token) 토큰을 사용한다. Bearer type 경우, 서버에서 지정한 어떠한 문자열도 입력할 수 있어 굉장히 허술하다. json형태인 JWT (JSON Web Token) 토큰을 사용하여 편리한 형태로 보완해준다. JWT는 X.509 Certificate의 lightweight JSON 버전이라고 생각하면 된다.

https://research.securitum.com/jwt-json-web-token-security/

각 파트는 base64 URL 인코딩이 되어서 . 으로 합쳐지게 된다.

  • Header: 토큰 형식와 암호화 알고리즘을 선언합니다.
  • Payload: 전송하려는 데이터를 JSON 형식으로 기입합니다.
  • Signature: Header와 Payload의 변조 가능성을 검증합니다.

4) OIDC 

OIDC OpenID Connect = OpenID 인증 + OAuth2.0 인가
사용자를 인증해 사용자에게 액세스 권한을 부여할 수 있게 해주는 프로토콜이다. OpenID 인증을 사용하고 OAuth2.0 인가 기능을 사용한다. 아래 속성을 사용한다. 

OAuth 2.0
권한을 위임 하기 위해서 만들어진 권한허가 처리 프로토콜 (위임 권한 부여 Delegated Authorization)
Access Token 사용 : 발급처(OAuth 2.0), 서버의 리소스 접근 권한

OpenID
비영리기관인 OpenID Foundation에서 추진하는 개방형 표준 및 분산 인증 Authentication 프로토콜

  • iss: 토큰 발행자
  • sub: 사용자를 구분하기 위한 유니크한 구분자
  • email: 사용자의 이메일
  • iat: 토큰이 발행되는 시간을 Unix time으로 표기한 것
  • exp: 토큰이 만료되는 시간을 Unix time으로 표기한 것
  • aud: ID Token이 어떤 Client를 위해 발급된 것인지.

2-2. IRSA 소개

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

https://awskoreamarketingasset.s3.amazonaws.com/2022 Summit/pdf/T10S1_EKS 환경을 더 효율적으로 더 안전하게.pdf

1. k8s 에서 요청 한 내용을 Mutating admission에서 Env, projected 볼륨으로 토큰을 넣어서 처리한다.
2. AWS IAM (STS)OIDC를 통해 EKS k8s의 인증을 처리해서 사용자 확인을 한다.
3. 처음 요청한 IAM 엑세스 토큰을 Pod에 전달한다.

https://learnk8s.io/authentication-kubernetes

2-3. 실습

파드가 버킷을 조회할 수 있도록 권한을 가지도록 하는 실습을 진행해본다. 

실습1: SA token 자동발급을 되지 않게한 Pod

서비스 어카운트 토큰 자동발급을 false로 꺼버린다. SA는 default로 되어 있지만 이 SA에 매핑되는 SA 토큰 볼륨을 만들지 않는다. 파드에서 s3 조회를 하지만 토큰이 없어 권한없음으로 명령이 실패한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 파드1 생성
cat <<EOF | kubectl apply --
apiVersion: v1
kind: Pod
metadata:
  name: eks-iam-test1
spec:
  containers:
    - name: my-aws-cli
      image: amazon/aws-cli:latest
      args: ['s3''ls']
  restartPolicy: Never
  automountServiceAccountToken: false
EOF
 
# 확인
kubectl get pod
kubectl describe pod
 
# 로그 확인
kubectl logs eks-iam-test1
 
# 파드1 삭제
kubectl delete pod eks-iam-test1
                                                                                               
cs

토큰이 담긴 볼륨이 없음
접근 실패

실습2: SA token 자동발급 되도록한 Pod

SA이 생성될 때 JWT token이 자동으로 secret으로 생성된다. 파드를 보면 볼륨 부분에 프로젝티드 볼륨이 생성되어 있다. 파드에서 조회하면, 토큰이 있지만 s3 조회가 되지 않는다!

토큰이 있는지 확인해 보고 토큰에 어떤 정보가 들어가 있는지 디코드를 해본다. 다양한 정보가 토큰에 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# 파드2 생성
cat <<EOF | kubectl apply --
apiVersion: v1
kind: Pod
metadata:
  name: eks-iam-test2
spec:
  containers:
    - name: my-aws-cli
      image: amazon/aws-cli:latest
      command: ['sleep''36000']
  restartPolicy: Never
EOF
 
# 확인
kubectl get pod
kubectl describe pod
 
# aws 서비스 사용 시도
kubectl exec -it eks-iam-test2 -- aws s3 ls
 
# 서비스 어카운트 토큰 확인
SA_TOKEN=$(kubectl exec -it eks-iam-test2 -- cat /var/run/secrets/kubernetes.io/serviceaccount/token)
echo $SA_TOKEN
 
# jwt 혹은 아래 JWT 웹 사이트 이용
jwt decode $SA_TOKEN --json --iso8601
...
 
#헤더
{
  "alg""RS256",
  "kid""1a8fcaee12b3a8f191327b5e9b997487ae93baab"
}
 
# 페이로드 : OAuth2에서 쓰이는 aud, exp 속성 확인! > projectedServiceAccountToken 기능으로 토큰에 audience,exp 항목을 덧붙힘
## iss 속성 : EKS OpenID Connect Provider(EKS IdP) 주소 > 이 EKS IdP를 통해 쿠버네티스가 발급한 토큰이 유요한지 검증
{
  "aud": [
    "https://kubernetes.default.svc"  # 해당 주소는 k8s api의 ClusterIP 서비스 주소 도메인명, kubectl get svc kubernetes
  ],
  "exp"1716619848#만료시간
  "iat"1685083848,
  "kubernetes.io": {
    "namespace""default",
    "pod": {
      "name""eks-iam-test2",#파드 이름
      "uid""10dcccc8-a16c-4fc7-9663-13c9448e107a"
    },
    "serviceaccount": {
      "name""default",#SA정보
      "uid""acb6c60d-0c5f-4583-b83b-1b629b0bdd87"
    },
    "warnafter"1685087455
  },
  "nbf"1685083848,
  "sub""system:serviceaccount:default:default"
}
 
# 파드2 삭제
kubectl delete pod eks-iam-test2
cs

토큰이 담긴 볼륨이 있음

실습3: iamserviceaccount 명령으로 IRSA 세팅

amazon-eks-pod-identity-webhook을 사용해서 Kubernetes service account에 AWS IAM role을 바인딩한다. 
eksctl create iamserviceaccount 명령으로 사용하는 것은 IRSA 이고, 이 명령의 목적은 파드가 AWS 사용할 수 있도록 IAM 롤 바인딩한다. 

eksctl create iamserviceaccount 명령 수행 과정

  1. A Kubernetes Service Account: 쿠버네티스 SA를 생성
  2. An IAM role with the specified IAM policy: IAM 역할 생성
  3. A trust policy on that IAM role: 신뢰 정책 생성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Create an iamserviceaccount - AWS IAM role bound to a Kubernetes service account
eksctl create iamserviceaccount \
  --name my-sa \
  --namespace default \
  --cluster $CLUSTER_NAME \
  --approve \
  --attach-policy-arn $(aws iam list-policies --query 'Policies[?PolicyName==`AmazonS3ReadOnlyAccess`].Arn' --output text)
 
# 확인 >> 웹 관리 콘솔에서 CloudFormation Stack >> IAM Role 확인
# aws-load-balancer-controller IRSA는 어떤 동작을 수행할 것 인지 생각해보자!
eksctl get iamserviceaccount --cluster $CLUSTER_NAME
 
# Inspecting the newly created Kubernetes Service Account, we can see the role we want it to assume in our pod.
kubectl get sa
kubectl describe sa my-sa
Name:                my-sa
Namespace:           default
Labels:              app.kubernetes.io/managed-by=eksctl
Annotations:         eks.amazonaws.com/role-arn: arn:aws:iam::911283464785:role/eksctl-myeks-addon-iamserviceaccount-default-Role1-1MJUYW59O6QGH
Image pull secrets:  <none>
Mountable secrets:   <none>
Tokens:              <none>
Events:              <none>
cs

1. 명령어를 입력하면 cloudformation 스택으로 role이 생성되는 것을 확인 할 수 있다. 

cloudformation

2. SA와 매핑된 권한을 세팅한다. 여기서는 s3 read only 권한이다. 
3. IAM 역할이 신뢰할 수 있는 Federated (아이덴티티를 연결하는 방법)의 ARN은? 쿠버네티스의 OIDC된 OpenID 주소이다. 

명령어를 사용했을 때의 세 가지 수행을 AWS 콘솔에서 보면 다음과 같다. 

iamserviceaccount 명령어의 세 가지 수행

이 SA를 사용하는 파드를 생성해서 조회를 진행해보자.
파드를 생성하고 mutatingwebhook pod-identity-webhook을 확인 해보면, mutatingwebhook 과정에서 아래 2개가 추가된다. 

1) Environment의 AWS_ROLE_ARN, AWS_WEB_IDENTITY_TOKEN_FILE 등
2) Volumes의 aws-iam-token

처음 추가해준 IAM 역할의 권한만 바인딩 되기 때문에 s3 조회만 가능하다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# 파드3번 생성
cat <<EOF | kubectl apply --
apiVersion: v1
kind: Pod
metadata:
  name: eks-iam-test3
spec:
  serviceAccountName: my-sa
  containers:
    - name: my-aws-cli
      image: amazon/aws-cli:latest
      command: ['sleep''36000']
  restartPolicy: Never
EOF
 
# 해당 SA를 파드가 사용 시 mutatingwebhook으로 Env,Volume 추가함
kubectl get mutatingwebhookconfigurations pod-identity-webhook -o yaml | kubectl neat | yh
 
# 파드 생성 yaml에 없던 내용이 추가됨!!!!!
# Pod Identity Webhook은 mutating webhook을 통해 아래 Env 내용과 1개의 볼륨을 추가함
kubectl get pod eks-iam-test3
kubectl describe pod eks-iam-test3
...
Environment:
      AWS_STS_REGIONAL_ENDPOINTS:   regional
      AWS_DEFAULT_REGION:           ap-northeast-2
      AWS_REGION:                   ap-northeast-2
      AWS_ROLE_ARN:                 arn:aws:iam::911283464785:role/eksctl-myeks-addon-iamserviceaccount-default-Role1-GE2DZKJYWCEN
      AWS_WEB_IDENTITY_TOKEN_FILE:  /var/run/secrets/eks.amazonaws.com/serviceaccount/token
    Mounts:
      /var/run/secrets/eks.amazonaws.com/serviceaccount from aws-iam-token (ro)
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-69rh8 (ro)
...
Volumes:
  aws-iam-token:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  86400
  kube-api-access-sn467:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    ConfigMapOptional:       <nil>
    DownwardAPI:             true
...
 
# 파드에서 aws cli 사용 확인
eksctl get iamserviceaccount --cluster $CLUSTER_NAME
kubectl exec -it eks-iam-test3 -- aws sts get-caller-identity --query Arn
"arn:aws:sts::911283464785:assumed-role/eksctl-myeks-addon-iamserviceaccount-default-Role1-GE2DZKJYWCEN/botocore-session-1685179271"
 
# 되는 것고 안되는 것은 왜그런가?
kubectl exec -it eks-iam-test3 -- aws s3 ls
kubectl exec -it eks-iam-test3 -- aws ec2 describe-instances --region ap-northeast-2
kubectl exec -it eks-iam-test3 -- aws ec2 describe-vpcs --region ap-northeast-2
cs

[도전과제] 실습4: IRSA - awscli 파드를 생성하면서 해당 파드에서 AWS의 모든서비스를 조회(ViewOnly)할 수 있는 권한설정 해보기

AWS의 모든서비스를 조회(ViewOnly)할 수 있는 정책 ARN을 확인한다. 

ViewOnly all resource

my-sa-view-all SA에 해당 정책을 바인딩하도록 eksctl create iamserviceaccount 명령 수행한다. AWS 콘솔에서도 해당 명령어로 생성된 role에 권한 정책, EKS openID arn이 신뢰 정책 설정된 것을 확인 할 수 있다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Create an iamserviceaccount - AWS IAM role bound to a Kubernetes service account
eksctl create iamserviceaccount \
  --name my-sa-view-all \
  --namespace default \
  --cluster $CLUSTER_NAME \
  --approve \
  --attach-policy-arn arn:aws:iam::aws:policy/job-function/ViewOnlyAccess
 
# 확인 >> 웹 관리 콘솔에서 CloudFormation Stack >> IAM Role 확인
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-view-all
Name:                my-sa-view-all
Namespace:           default
Labels:              app.kubernetes.io/managed-by=eksctl
Annotations:         eks.amazonaws.com/role-arn: arn:aws:iam::991354587926:role/eksctl-myeks-addon-iamserviceaccount-default-Role1-RPMBM4FHI3QQ
Image pull secrets:  <none>
Mountable secrets:   <none>
Tokens:              <none>
Events:              <none>
cs

role의 권한, 신뢰관계 확인

여러 리소스의 조회를 해본다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 파드4번 생성
cat <<EOF | kubectl apply --
apiVersion: v1
kind: Pod
metadata:
  name: eks-iam-test4
spec:
  serviceAccountName: my-sa-view-all
  containers:
    - name: my-aws-cli
      image: amazon/aws-cli:latest
      command: ['sleep''36000']
  restartPolicy: Never
EOF
 
# 파드에서 aws cli 조회 사용 확인
kubectl exec -it eks-iam-test4 -- aws s3 ls
kubectl exec -it eks-iam-test4 -- aws ec2 describe-instances --region ap-northeast-2
kubectl exec -it eks-iam-test4 -- aws ec2 describe-vpcs --region ap-northeast-2
cs

 

 

인스턴스, VPC 등 모든 리소스의 조회도 가능해졌다. 

인스턴스 상세조회
vpc 상세조회

 

'스터디 > Kubernetes' 카테고리의 다른 글

[AWES] EKS Automation - flux  (1) 2023.06.10
[AWES] EKS Security - K8S 및 EKS의 인증/인가  (1) 2023.05.30
[AWES] EKS - Karpenter  (0) 2023.05.28
[AWES] EKS - Autoscaling  (0) 2023.05.27
[AWES] EKS Observability  (0) 2023.05.21

댓글