티스토리 뷰

AEWS study

6주차 - EKS Security - #2

haru224 2025. 3. 15. 20:53

CloudNet@ 가시다님이 진행하는 AWS EKS Hands-on Study 내용 참고.

 

3. EKS IRSA & Pod Identity


  • [추천 영상]
    • [Youtube] 생활코딩 OAuth 2.0 - 링크
    • AWS IRSA Deep-Dive - 안지완 - Youtube
    • EKS Pod Identity vs IRSA | Securely Connect Kubernetes Pods to AWS Services - Youtube

EC2 Instance Profile : 사용하기 편하지만, 최소 권한 부여 원칙에 위배하며 보안상 권고하지 않음 - 링크

출처: https://sharing-for-us.tistory.com/40

# 설정 예시 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 : 테라폼
...

 

필요 지식 : Service Account Token Volume Projection, Admission Control, JWT(JSON Web Token), OIDC

 

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

  • Service Account Token (SAT) Volume Projection - 링크

  • 서비스 계정 토큰을 이용해서 서비스와 서비스, 즉 파드(pod)와 파드(pod)의 호출에서 자격 증명으로 사용할 수 있을까요?
  • 불행히도 기본 서비스 계정 토큰으로는 사용하기에 부족함이 있습니다. 토큰을 사용하는 대상(audience), 유효 기간(expiration) 등 토큰의 속성을 지정할 필요가 있기 때문입니다.
  • Service Account Token Volume Projection 기능을 사용하면 이러한 부족한 점들을 해결할 수 있습니다.
apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - image: nginx
    name: nginx
    volumeMounts:
    - mountPath: /var/run/secrets/tokens
      name: vault-token
  serviceAccountName: build-robot
  volumes:
  - name: vault-token
    projected:
      sources:
      - serviceAccountToken:
          path: vault-token
          expirationSeconds: 7200
          audience: vault
  • Bound Service Account Token Volume 바인딩된 서비스 어카운트 토큰 볼륨 - 링크 영어
    • FEATURE STATE: Kubernetes v1.22 [stable]
    • 서비스 어카운트 어드미션 컨트롤러는 토큰 컨트롤러에서 생성한 만료되지 않은 서비스 계정 토큰에 시크릿 기반 볼륨 대신 다음과 같은 프로젝티드 볼륨을 추가한다.
- name: kube-api-access-<random-suffix>
  projected:
    defaultMode: 420 # 420은 rw- 로 소유자는 읽고쓰기 권한과 그룹내 사용자는 읽기만, 보통 0644는 소유자는 읽고쓰고실행 권한과 나머지는 읽고쓰기 권한
    sources:
      - serviceAccountToken:
          expirationSeconds: 3607
          path: token
      - configMap:
          items:
            - key: ca.crt
              path: ca.crt
          name: kube-root-ca.crt
      - downwardAPI:
          items:
            - fieldRef:
                apiVersion: v1
                fieldPath: metadata.namespace
              path: namespace
  • 프로젝티드 볼륨은 세 가지로 구성된다. 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:
      sources:
      - secret:
          name: user
      - secret:
          name: pass
  - name: kube-api-access-4fq24
    projected:
      sources:
      - serviceAccountToken:
          expirationSeconds: 3607
          path: token
      - configMap:
          items:
          - key: ca.crt
            path: ca.crt
          name: kube-root-ca.crt
      - downwardAPI:
          items:
          - fieldRef:
              fieldPath: metadata.namespace
            path: namespace

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

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

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

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

 

k8s api 접근 단계

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

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

  • Admission Control도 Webhook으로 사용자에게 API가 열려있고, 사용자는 자신만의 Admission Controller를 구현할 수 있으며, 이를 Dynamic Admission Controller라고 부르고, 크게 MutatingWebhookValidatingWebhook 로 나뉩니다.
  • MutatingWebhook은 사용자가 요청한 request에 대해서 관리자가 임의로 값을 변경하는 작업입니다.
  • ValidatingWebhook은 사용자가 요청한 request에 대해서 관리자기 허용을 막는 작업입니다.
#
kubectl get mutatingwebhookconfigurations
NAME                              WEBHOOKS   AGE
aws-load-balancer-webhook         3          3h43m
kube-prometheus-stack-admission   1          3h36m
pod-identity-webhook              1          6h42m
vpc-resource-mutating-webhook     1          6h42m

#
kubectl get validatingwebhookconfigurations
NAME                              WEBHOOKS   AGE
aws-load-balancer-webhook         3          3h43m
kube-prometheus-stack-admission   1          3h36m
vpc-resource-validating-webhook   2          6h42m

 

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 인코딩이 되어서 .으로 합쳐지게 됩니다.

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

 

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 역할을 사용할 수 있는지 검증

출처: https://github.com/awskrug/security-group/blob/main/files/AWSKRUG_2024_02_EKS_ROLE_MANAGEMENT.pdf

https://youtu.be/wgH9xL_48vM?t=1163

19:23초 부터 시작~

출처: https://youtu.be/iyMcOpXRVWk?si=6uvHWIKH7kwk_EEq&t=1402

  • The IAM service uses these public keys to validate the token. The workflow is as follows - JWT(JSON Web Token), JWKS(JSON Web Key Set)

출처: https://aws.amazon.com/ko/blogs/containers/diving-into-iam-roles-for-service-accounts/

  • AWS SDK는 AWS_ROLE_ARNAWS_WEB_IDENTITY_TOKEN_FILE 이름의 환경변수를 읽어들여 Web Identity 토큰으로 AssumeRoleWithWebIdentify를 호출함으로써 Assume Role을 시도하여 임시 자격 증명을 획득하고, 특정 IAM Role 역할을 사용할 수 있게 됩니다.
  • 이때 Assume Role 동작을 위한 인증은 AWS가 아닌 외부 Web IdP(EKS IdP)에 위임하여 처리합니다.

출처: https://tech.devsisters.com/posts/pod-iam-role/

  • EKS IdP를 identity provider로 등록하고, 파드가 Web Identify 토큰을 통해 IAM 역할을 Assume 할 수 있게 Trust Relationship 설정이 필요합니다.
  • AWS CloudTrail 에 AssumeRoleWithWebIdentity - Link

 

1. Projected service account tokens are identities valid within a Kubernetes cluster. However, you could exchange for a valid token elsewhere.

출처: https://learnk8s.io/authentication-kubernetes

2. The Amazon IAM service can receive such tokens and verify their identities by looking into the iss field of the JWT token.

출처: https://learnk8s.io/authentication-kubernetes

3. If the identity is legit, it can issue its own token.

출처: https://learnk8s.io/authentication-kubernetes

4. The new token can be used to access services in Amazon Web Services.

출처: https://learnk8s.io/authentication-kubernetes

실습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
  • CloudTrail 이벤트 ListBuckets 확인 → 기록 표시까지 약간의 시간 필요 - Link

{
...
    "userIdentity": {
        "type": "AssumedRole",
        "principalId": "AROAU6GDVW6N7QXMJ67YM:i-05beac6b1d64479d6",
        "arn": "arn:aws:sts::339712784283:assumed-role/eksctl-myeks-nodegroup-ng1-NodeInstanceRole-dhnwBVR9OLts/i-05beac6b1d64479d6",
        "accountId": "339712784283",
        "accessKeyId": "ASIAU6GDVW6NTRRRSQ5R",
        "sessionContext": {
            "sessionIssuer": {
                "type": "Role",
                "principalId": "AROAU6GDVW6N7QXMJ67YM",
                "arn": "arn:aws:iam::339712784283:role/eksctl-myeks-nodegroup-ng1-NodeInstanceRole-dhnwBVR9OLts",
                "accountId": "339712784283",
                "userName": "eksctl-myeks-nodegroup-ng1-NodeInstanceRole-dhnwBVR9OLts"
            },
            "attributes": {
                "creationDate": "2025-03-15T21:06:00Z",
                "mfaAuthenticated": "false"
            },
            "ec2RoleDelivery": "2.0"
        }
    },
    "eventTime": "2025-03-15T21:57:15Z",
    "eventSource": "s3.amazonaws.com",
    "eventName": "ListBuckets",
    "awsRegion": "ap-northeast-2",
    "sourceIPAddress": "3.34.183.41",
    "userAgent": "[aws-cli/2.24.24 md/awscrt#0.23.8 ua/2.1 os/linux#6.1.128-136.201.amzn2023.x86_64 md/arch#x86_64 lang/python#3.12.9 md/pyimpl#CPython m/C cfg/retry-mode#standard md/installer#docker md/distrib#amzn.2 md/prompt#off md/command#s3.ls]",
    "errorCode": "AccessDenied",
    "errorMessage": "User: arn:aws:sts::339712784283:assumed-role/eksctl-myeks-nodegroup-ng1-NodeInstanceRole-dhnwBVR9OLts/i-05beac6b1d64479d6 is not authorized to perform: s3:ListAllMyBuckets because no identity-based policy allows the s3:ListAllMyBuckets action",
    "requestParameters": {
        "Host": "s3.ap-northeast-2.amazonaws.com"
    },
...
}

 

☞ 실습2 Service Accounts

  • Kubernetes Pod는 Kubernetes 서비스 계정(Kubernetes Service Account)이라는 개념을 통해 ID(정체성)를 부여받습니다.
  • 서비스 계정이 생성될 때, JWT 토큰이 자동으로 생성되며, 이는 Kubernetes 시크릿(Secret)으로 저장됩니다.
  • Secret은 Pod에 마운트될 수 있으며, 해당 서비스 계정이 Kubernetes API 서버에 인증하는 데 사용될 수 있습니다.
# 파드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": "3538c9b098f6273d45f603196f095176fd82d718"
}

# 페이로드 : OAuth2에서 쓰이는 aud, exp 속성 확인! > projectedServiceAccountToken 기능으로 토큰에 audience,exp 항목을 덧붙힘
## iss 속성 : EKS OpenID Connect Provider(EKS IdP) 주소 > 이 EKS IdP를 통해 쿠버네티스가 발급한 토큰이 유요한지 검증
{
  "aud": [
    "https://kubernetes.default.svc"
  ],
  "exp": 1773612197,
  "iat": 1742076197,
  "iss": "https://oidc.eks.ap-northeast-2.amazonaws.com/id/FDAFF52CF67728F6C2259CFE7C292369",
  "jti": "bccf586b-519c-4eb5-ad76-047bdb6e3cc9",
  "kubernetes.io": {
    "namespace": "default",
    "node": {
      "name": "ip-192-168-3-88.ap-northeast-2.compute.internal",
      "uid": "d486eada-9be0-44e6-b64e-872fdb995a36"
    },
    "pod": {
      "name": "eks-iam-test2",
      "uid": "7bf0bfa8-44bc-42ce-a76c-09e5ef6555fb"
    },
    "serviceaccount": {
      "name": "default",
      "uid": "a5c4ad52-8e9d-4dd4-84a9-7d6ca611ea61"
    },
    "warnafter": 1742079804
  },
  "nbf": 1742076197,
  "sub": "system:serviceaccount:default:default"
}

# 파드2 삭제
kubectl delete pod eks-iam-test2

 

  • 이 JWT의 페이로드에서 볼 수 있듯이, 발급자(issuer)는 OIDC 제공자(OIDC Provider)입니다. 이 토큰의 대상(audience)은 https://kubernetes.default.svc로 설정되어 있습니다.
  • 이는 클러스터 내부에서 Kubernetes API 서버에 접근하는 데 사용되는 주소입니다.Kubernetes는 유효성 검사 웹훅(Validating Webhook) 및 **변형 웹훅(Mutating Webhook)**을 지원하며, AWS는 EKS 클러스터에 사전 설치된 **아이덴티티 웹훅(Identity Webhook)**을 제공합니다. 이 웹훅은 Pod 생성 API 호출을 감지하여 Pod에 추가 토큰을 주입할 수 있습니다. 또한, 이 웹훅은 AWS에서 운영하는 자가 관리(Self-Managed) Kubernetes 클러스터에도 설치할 수 있으며, 설치 방법은 이 가이드를 통해 확인할 수 있습니다. 이 OIDC 준수 토큰(compliant OIDC token)은 AWS API에 인증하는 데 사용할 수 있는 토큰을 찾기 위한 기반을 제공합니다.그러나 Kubernetes Pod에서 AWS API를 사용하는 데 필요한 두 번째 토큰을 주입하기 위해 추가 구성 요소가 필요합니다.

실습3 IRSA - 링크

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

출처: https://aws.amazon.com/ko/blogs/containers/diving-into-iam-roles-for-service-accounts/

  • : This webhook is for mutating pods that will require AWS IAM acces

출처: https://dev.to/aws-builders/auditing-aws-eks-pod-permissions-4637

  • 웹훅이 우리 Pod에 새로운 토큰을 주입할 수 있도록 하기 위해, 새로운 Kubernetes 서비스 계정(Service Account)을 생성하고, 해당 서비스 계정에 AWS IAM 역할 ARN을 주석(annotation)으로 추가한 다음, 이 새로운 Kubernetes 서비스 계정을 Kubernetes Pod에서 참조할 것입니다. eksctl 도구를 사용하면 몇 가지 단계를 자동화할 수 있지만, 모든 과정은 수동으로도 수행할 수 있습니다.
  • The eksctl create iamserviceaccount command creates:
    1. A Kubernetes Service Account
    2. An IAM role with the specified IAM policy
    3. A trust policy on that IAM role

출처: https://sharing-for-us.tistory.com/40

  • Finally, it will also annotate the Kubernetes Service Account with the IAM Role Arn created.
# 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>

  • AWS 관리 콘솔에서 이 IAM 역할이 어떻게 구성되어 있는지 확인해 보겠습니다. IAM으로 이동한 후 IAM 역할(IAM Roles)에서 해당 역할을 검색합니다. 서비스 계정을 설명(describe)할 때 Annotations 필드를 확인할 수 있습니다. ⇒ IAM Role 확인
  • 신뢰 관계(Trust relationships) 탭을 선택한 후 "Edit trust relationship"을 클릭하여 정책 문서를 확인합니다.
  • 이 정책에서는 system:serviceaccount:default:my-sa라는 ID가 sts:AssumeRoleWithWebIdentity 액션을 사용하여 역할을 가정(assume)할 수 있도록 허용하고 있습니다. 이 정책의 주체(Principal) 는 OIDC 공급자(OIDC provider)입니다.
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Federated": "arn:aws:iam::339712784283:oidc-provider/oidc.eks.ap-northeast-2.amazonaws.com/id/FDAFF52CF67728F6C2259CFE7C292369"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "StringEquals": {
                    "oidc.eks.ap-northeast-2.amazonaws.com/id/FDAFF52CF67728F6C2259CFE7C292369:aud": "sts.amazonaws.com",
                    "oidc.eks.ap-northeast-2.amazonaws.com/id/FDAFF52CF67728F6C2259CFE7C292369:sub": "system:serviceaccount:default:my-sa"
                }
            }
        }
    ]
}

  • Now let’s see what happens when we use this new Service Account within a Kubernetes Pod : 신규 파드 만들자!
# 파드3번 생성
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: eks-iam-test3
spec:
  serviceAccountName: my-sa
  containers:
    - name: my-aws-cli
      image: amazon/aws-cli:latest
      command: ['sleep', '36000']
  restartPolicy: Never
  terminationGracePeriodSeconds: 0
EOF

# 해당 SA를 파드가 사용 시 mutatingwebhook으로 Env,Volume 추가함 : 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:
      defaultMode: 420
      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::339712784283:role/eksctl-myeks-addon-iamserviceaccount-default--Role1-nPJmqNjof7KO
      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-wkt7f (ro)
...
Volumes:
  aws-iam-token:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  86400
  kube-api-access-wkt7f:
    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::339712784283:assumed-role/eksctl-myeks-addon-iamserviceaccount-default--Role1-nPJmqNjof7KO/botocore-session-1742077181"

# 되는 것고 안되는 것은 왜그런가?
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

 

  • AWS CloudTrail 이벤트 중 AssumeRoleWithWebIdentity - Link
  • Kubectl과 jq를 사용하여 Pod을 검사하면, 이제 두 개의 볼륨이 Pod에 마운트되어 있음을 확인할 수 있습니다.
    그중 두 번째 볼륨은 변형 웹훅(mutating webhook)을 통해 마운트된 것입니다. aws-iam-token은 여전히 Kubernetes API 서버에 의해 생성되지만, 새로운 OIDC JWT 대상(audience)이 설정되었습니다.
# 파드에 볼륨 마운트 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-wkt7f",
    "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"
        }
      }
    ]
  }
}

#
kubectl get MutatingWebhookConfiguration
NAME                            WEBHOOKS   AGE
aws-load-balancer-webhook         3          4h10m
kube-prometheus-stack-admission   1          4h3m
pod-identity-webhook              1          7h9m
vpc-resource-mutating-webhook     1          7h9m

# pod-identity-webhook 확인
kubectl describe MutatingWebhookConfiguration pod-identity-webhook 
kubectl get MutatingWebhookConfiguration pod-identity-webhook -o yaml
...
 name: iam-for-pods.amazonaws.com
# iam-for-pods.amazonaws.com은 AWS EKS에서 Pod Identity Webhook의 Mutating Webhook으로, 다음과 같은 작업을 수행합니다:
# Pod 생성 시 호출되어 ServiceAccount의 IAM 역할 정보를 확인.
# Pod에 환경 변수(AWS_ROLE_ARN, AWS_WEB_IDENTITY_TOKEN_FILE)와 토큰 볼륨을 주입.
# Pod이 AWS 리소스에 안전하고 세밀하게 접근할 수 있도록 인증 메커니즘 제공.

 

  • 실행 중인 Pod에 exec 명령어를 사용하여 접속하고 이 토큰을 검사하면, 이전 서비스 계정(SA) 토큰과 약간 다르게 보인다는 것을 알 수 있습니다.
  • 이제 이 토큰의 대상(audience)이 sts.amazonaws.com으로 설정되어 있으며, 이 토큰을 생성하고 서명한 발급자(issuer)는 여전히 우리의 OIDC 제공자입니다. 또한, 이 토큰의 만료 시간이 24시간으로 훨씬 짧아졌습니다. Pod 정의 또는 서비스 계정(Service Account) 정의에서 eks.amazonaws.com/token-expiration 주석(annotation)을 사용하여 서비스 계정의 만료 시간을 조정할 수 있습니다.
  • 변형 웹훅(mutating webhook)은 Pod에 추가 토큰을 마운트하는 것 이상의 역할을 수행합니다.변형 웹훅은 환경 변수를 주입하는 역할도 합니다.

https://jwt.io/

# AWS_WEB_IDENTITY_TOKEN_FILE 확인
IAM_TOKEN=$(kubectl exec -it eks-iam-test3 -- cat /var/run/secrets/eks.amazonaws.com/serviceaccount/token)
echo $IAM_TOKEN

# JWT 웹 확인 
{
  "aud": [
    "sts.amazonaws.com"
  ],
  "exp": 1742163249,
  "iat": 1742076849,
  "iss": "https://oidc.eks.ap-northeast-2.amazonaws.com/id/FDAFF52CF67728F6C2259CFE7C292369",
  "jti": "1961584b-5ab1-4d46-b27b-04a7fd99dfd1",
  "kubernetes.io": {
    "namespace": "default",
    "node": {
      "name": "ip-192-168-3-88.ap-northeast-2.compute.internal",
      "uid": "d486eada-9be0-44e6-b64e-872fdb995a36"
    },
    "pod": {
      "name": "eks-iam-test3",
      "uid": "797479f4-1ed6-4a3f-92ae-73072c29b7f1"
    },
    "serviceaccount": {
      "name": "my-sa",
      "uid": "58e3bdd7-955b-40b3-a46c-29a311ff06fc"
    }
  },
  "nbf": 1742076849,
  "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::339712784283:role/eksctl-myeks-addon-iamserviceaccount-default--Role1-nPJmqNjof7KO"
  },
  {
    "name": "AWS_WEB_IDENTITY_TOKEN_FILE",
    "value": "/var/run/secrets/eks.amazonaws.com/serviceaccount/token"
  }
]

  • 이제 우리의 워크로드가 IAM과 인증을 시도할 수 있는 토큰을 가지게 되었으므로, 다음 단계는 AWS IAM이 이러한 토큰을 신뢰하도록 만드는 것입니다. AWS IAM은 OIDC(OpenID Connect) ID 공급자를 사용한 연합된 ID(Federated Identities)를 지원합니다. 이 기능을 통해 IAM은 유효한 OIDC JWT를 받은 후, 지원되는 ID 공급자를 사용하여 AWS API 호출을 인증할 수 있습니다. 그런 다음, 이 토큰을 AWS STS의 AssumeRoleWithWebIdentity API 작업에 전달하여 임시 IAM 자격 증명을 받을 수 있습니다.
  • Kubernetes 워크로드에서 사용하는 OIDC JWT 토큰은 암호학적으로 서명되어 있으며, IAM은 AWS STS AssumeRoleWithWebIdentity API 작업이 임시 자격 증명을 발급하기 전에 이러한 토큰을 신뢰하고 검증해야 합니다.
    Kubernetes의 Service Account Issuer Discovery 기능의 일환으로, EKS는 퍼블릭 OpenID 공급자 구성 문서(Discovery endpoint) 및 토큰 서명을 검증하기 위한 공개 키(JSON Web Key Sets – JWKS)를 https://OIDC_PROVIDER_URL/.well-known/openid-configuration 에서 호스팅하고 있습니다.
# 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 '.'

  • AWS CloudTrail 이벤트 중 AssumeRoleWithWebIdentity - Link

 

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

출처: https://github.com/awskrug/security-group/blob/main/files/AWSKRUG_2024_02_EKS_ROLE_MANAGEMENT.pdf

  • 실습 확인 후 파드 삭제 및 IRSA 제거
# 실습 확인 후 파드 삭제 및 IRSA 제거
kubectl delete pod eks-iam-test3
eksctl delete iamserviceaccount --cluster $CLUSTER_NAME --name my-sa --namespace default
eksctl get iamserviceaccount --cluster $CLUSTER_NAME
kubectl get sa

 

☞ [신기능] EKS Pod Identity

출처: https://www.youtube.com/watch?v=yuXF-NXaelI
출처: https://github.com/awskrug/security-group/blob/main/files/AWSKRUG_2024_02_EKS_ROLE_MANAGEMENT.pdf

 

  • Amazon EKS Pod Identity: a new way for applications on EKS to obtain IAM credentials - Link
  • Amazon EKS Pod Identity simplifies IAM permissions for applications on Amazon EKS clusters - Link
  • [EKS Workshop] EKS Pod Identity : 오픈소스 Agent, Add-on 설치 지원 - Link

출처: https://youtu.be/iyMcOpXRVWk?si=fFiMV9c7E0pg8Img&t=1409
출처: https://aws.amazon.com/blogs/containers/amazon-eks-pod-identity-a-new-way-for-applications-on-eks-to-obtain-iam-credentials/
출처: https://github.com/awskrug/security-group/blob/main/files/AWSKRUG_2024_02_EKS_ROLE_MANAGEMENT.pdf

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

  • EKS → 액세스 : Pod Identity 연결 확인 → 편집 클릭 해보기

  • 테스트용 파드 생성 및 확인 : AssumeRoleForPodIdentity - Link

출처: https://youtu.be/iyMcOpXRVWk?si=J4q7vOe-W4UhQ1wu&t=2501

  • AWS CloudTrail 중 AssumeRoleForPodIdentity - Link

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

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

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

# 토큰 정보 확인
kubectl exec -it eks-pod-identity -- ls /var/run/secrets/pods.eks.amazonaws.com/serviceaccount/
kubectl exec -it eks-pod-identity -- cat /var/run/secrets/pods.eks.amazonaws.com/serviceaccount/eks-pod-identity-token
  • 실습 리소스 삭제
eksctl delete podidentityassociation --cluster $CLUSTER_NAME --namespace default --service-account-name s3-sa
kubectl delete pod eks-pod-identity
kubectl delete sa s3-sa
  • IRSA vs EKS Pod Identity
 
EKS Pod Identity
IRSA
Role extensibility
You have to update the IAM role’s trust policy with the new EKS cluster OIDC provider endpoint each time you want to use the role in a new cluster.
You have to setup the role one time, to establish trust with the newly introduced EKS service principal “pods.eks.amazonaws.com”. After this one-time step, you don’t need to update the role’s trust policy each time it is used in a new cluster.
Account scalability
EKS cluster has an OpenID Connect (OIDC) issuer URL associated with it. To use IRSA, a unique OpenID connect provider needs to be created in IAM for each EKS cluster. IAM OIDC provider has a default global limit of 100 per AWS account. Keep this limit in consideration as you grow the number of EKS clusters per account.
EKS Pod Identity doesn’t require users to setup IAM OIDC provider, so this limit doesn’t apply.
Role scalability
In IRSA, you define the trust relationship between an IAM role and service account in the role’s trust policy. By default, the length of trust policy size is 2048. This means that you can typically define four trust relationships in a single policy. While you can get the trust policy length limit increased, you are typically limited to a maximum of eight trust relationships within a single policy.
EKS Pod Identity doesn’t require users to define trust relationship between IAM role and service account in IAM trust policy, so this limit doesn’t apply.
Role reusability
IAM role session tags are not supported.
IAM credentials supplied by EKS Pod Identity include support for role session tags. Role session tags enable administrators to author a single IAM role that can be used with multiple service accounts, with different effective permissions, by allowing access to AWS resources based on tags attached to them.
Cluster readiness
IAM roles used in IRSA need to wait for the cluster to be in a “Ready” state, to get the cluster’s OpenID Connect Provider URL to complete the IAM role trust policy configuration
IAM roles used in Pod identity can be created ahead of time.
Environments supported
IRSA can be used in EKS, EKS-A, ROSA, self-managed Kubernetes clusters on Amazon EC2
EKS Pod Identity is purpose built for EKS.
Supported EKS versions
All supported EKS versions
EKS version 1.24 and above. See EKS user guide for details.
Cross account access
Cross account here refers to the scenario where your EKS cluster is in one AWS account and the AWS resources that are being accessed by your applications is in another AWS account. In IRSA, you can configure cross account IAM permissions either by creating an IAM identity provider in the account your AWS resources live or by using chained AssumeRole operation. See EKS user guide on IRSA Cross-account IAM permissions for details.
EKS Pod Identity supports cross account access through resource policies and chained AssumeRole operation. See the previous section “How to perform cross account access with EKS Pod Identity” for details.
Mapping inventory
You can find the mapping of IAM roles to service accounts by parsing individual IAM role’s trust policy or by inspecting the annotations added to service accounts.
EKS Pod Identity offers a new ListPodIdentityAssociations API to centrally see the mapping of roles to service accounts.
  • 고려사항
    • SDK 최신 버전 확인 - Link
    • 워커노드에 IAM Policy 확인 : Action(eks-auth:AssumeRoleForPodIdentity)
    • 보안 솔루션으로 링크 로컬 주소 사용 가능 여부 확인, 혹은 이미 사용 중인 주소인지, iptables 로 막혀있는지 확인
    • How to migrate from IRSA to EKS Pod Identity : 기존 IRSA → PodIdentity 마이그레이션 - Link

 

 

4. OWASP Kubernetes Top Ten


☞ 파드 탈옥하여 호스트 탈취 - Blog

  • 파드 권한과 호스트 네임스페이스 공유로 호스트 탈취
NODE_NAME=myk8s-control-plane

kubectl create -n kube-system -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
  name: root-shell-$NODE_NAME
  namespace: kube-system
spec:
  nodeName: $NODE_NAME
  containers:
  - command:
    - /bin/cat
    image: alpine:3
    name: root-shell
    securityContext:
      privileged: true
    tty: true
    stdin: true
    volumeMounts:
    - mountPath: /host
      name: hostroot
  hostNetwork: true
  hostPID: true
  hostIPC: true
  tolerations:
  - effect: NoSchedule
    operator: Exists
  - effect: NoExecute
    operator: Exists
  volumes:
  - hostPath:
      path: /
    name: hostroot
EOF

kubectl -n kube-system exec -it root-shell-$NODE_NAME -- chroot /host /bin/bash
root@myk8s-control-plane:/# id
uid=0(root) gid=0(root) groups=0(root),1(daemon),2(bin),3(sys),4(adm),6(disk),10(uucp),11,20(dialout),26(tape),27(sudo)
  • 데몬셋으로 실행 ttl
kubectl create serviceaccount -n kube-system root-shell
kubectl create -n kube-system -f - <<EOF
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: root-shell
  namespace: kube-system
spec:
  revisionHistoryLimit: 0
  selector:
    matchLabels:
      app: root-shell
  template:
    metadata:
      labels:
        app: root-shell
    spec:
      terminationGracePeriodSeconds: 0
      containers:
      - command:
        - /bin/cat
        image: alpine:3
        name: root-shell
        tty: true
        stdin: true
        volumeMounts:
        - mountPath: /host
          name: hostroot
        securityContext:
          privileged: true
      hostNetwork: true
      hostPID: true
      hostIPC: true
      serviceAccountName: root-shell
      hostNetwork: true
      tolerations:
      - effect: NoSchedule
        operator: Exists
      - effect: NoExecute
        operator: Exists
      volumes:
      - hostPath:
          path: /
        name: hostroot
  updateStrategy:
    rollingUpdate:
      maxUnavailable: 100%
    type: RollingUpdate
EOF
  • 윈도우 쿠버네티스에서 persistence volume에 명령어를 적으면, 명령어가 실행되는 취약점 - Link
  • OWASP Kubernetes Top Ten : K8S 보안 위협 - Link

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

 

☞ (최성욱님) 성코드석님 : EKS pod가 IMDS API를 악용하는 시나리오 - 링크 Github Youtube실습*

☞ (최성욱님) 성코드석님 : Kubelet 미흡한 인증/인가 설정 시 위험kubeletct 툴 - 링크 Youtube → 실습*

 

 

5. Kyverno


 

Cloud Native Live: Cloud Native Policy as code with Kyverno!

[CNKCD2024] 쿠버네티스 정책 및 권한 관리를 위한 기술들 (이상근)

Kyverno : K8S Native Policy Mgmt - Link , Blog , Playground , Policy , Docs , Github , ddii , devocean , whchoi98 , Youtube

  • [EKS Workshop] Policy management with Kyverno - Link
  • Managing Pod Security on Amazon EKS with Kyverno - 링크 & PSS - Link
  • Kyverno (Greek for “govern”) is a policy engine designed specifically for Kubernetes.
    • Kyverno는 클라우드 네이티브 컴퓨팅 재단(CNCF, Cloud Native Computing Foundation) 프로젝트로, 팀이 협업하고 정책을 코드로(Policy-as-Code) 적용할 수 있도록 합니다.
    • Kyverno는 YAML로 작성된 선언형 Kubernetes 리소스를 사용하며, 새로운 정책 언어를 배울 필요가 없습니다. 또한, 정책 적용 결과는 Kubernetes 리소스 및 이벤트로 제공됩니다.
    • Kyverno 정책은 리소스 종류(Resource Kind), 이름(Name), 라벨 선택자(Label Selectors) 등을 사용하여 리소스를 매칭할 수 있으며, 다양한 방법으로 세밀한 정책 적용이 가능합니다.
    • Kyverno 정책은 **리소스 구성을 검증(Validate), 변형(Mutate), 생성(Generate)**할 수 있으며,
      이미지 서명 및 인증(Attestation) 검증도 지원하여 **소프트웨어 공급망 보안 표준(Supply Chain Security Standards)**을 완벽하게 적용할 수 있도록 합니다.
  • 기능 - Link
    • Kubernetes 리소스로서의 정책 (새로운 언어를 배울 필요 없음!)
    • 모든 리소스를 검증(Validate), 변형(Mutate), 생성(Generate), 또는 정리(Cleanup, 제거) 가능
    • 소프트웨어 공급망 보안을 위해 컨테이너 이미지를 검증
    • 이미지 메타데이터 검사
    • 라벨 선택자(Label Selectors) 및 와일드카드(Wildcards)를 사용하여 리소스 매칭
    • 오버레이(Overlays, Kustomize와 유사!)를 사용하여 검증 및 변형 수행
    • 네임스페이스 간 구성(Configuration) 동기화
    • 허가 제어(Admission Controls)를 통해 규정을 준수하지 않는 리소스를 차단하거나 정책 위반 사항을 보고
    • 자체 서비스 보고(Self-Service Reports, 독점적인 감사 로그 없음!)
    • 자체 서비스 정책 예외(Self-Service Policy Exceptions) 지원
    • Kyverno CLI를 사용하여 CI/CD 파이프라인에서 정책을 테스트하고 리소스를 검증한 후 클러스터에 적용
    • Git 및 Kustomize와 같은 익숙한 도구를 사용하여 정책을 코드(Code)로 관리
  • 동작 : Dynamic Admission Control 로 실행, Mutating/Validating admission 에서 동작하여 허용/거부 결과 반환

출처: https://kyverno.io/docs/introduction/

  • 두 개의 주요 구성 요소는 Webhook Server와 Webhook Controller입니다.
    • Webhook Server는 Kubernetes API 서버에서 들어오는 AdmissionReview 요청을 처리하고, 이를 엔진(Engine)으로 전달하여 처리합니다.
    • 이 Webhook Server는 Webhook Controller에 의해 동적으로 구성되며, Webhook Controller는 설치된 정책을 감시하고, 해당 정책과 일치하는 리소스에 대해서만 웹훅 요청을 수행하도록 웹훅을 수정합니다.
  • Webhook은 Kubernetes API 서버에서 들어오는 AdmissionReview 요청을 처리하고 엔진(Engine)으로 전달하는 서버입니다.
  • 이 Webhook은 Webhook Controller에 의해 동적으로 구성되며, 설치된 정책을 감시하고 해당 정책과 일치하는 리소스에 대해서만 웹훅 요청을 수행하도록 웹훅을 수정합니다.
  • Cert Renewer(인증서 갱신기)는 웹훅에 필요한 인증서(Certificate)를 감시하고 갱신하는 역할을 합니다. 이 인증서는 Kubernetes Secrets에 저장됩니다.
  • Background Controller기존 리소스를 변형(Mutate-Existing)하거나 새로운 리소스를 생성(Generate)하는 모든 정책을 처리하며, UpdateRequests(중간 리소스)를 조정(Reconcile)하여 이를 수행합니다.
  • Report Controllers정책 보고서(Policy Reports)를 생성하고 조정하는 역할을 하며, 이를 위한 중간 리소스인 Admission ReportsBackground Scan Reports를 기반으로 동작합니다.

 

6. 파드/컨테이너 보안 컨텍스트


☞ 컨테이너 보안 컨텍스트 SecurityContext - 링크파드가 아님음 주의!

  • 각 컨테이너에 대한 보안 설정 → 침해사고 발생 시 침해사고를 당한 권한을 최대한 축소하여 그 사고에 대한 확대를 방치
종류
개요
privileged
특수 권한을 가진 컨테이너로 실행
capabilities
Capabilities 의 추가와 삭제
allowPrivilegeEscalation
컨테이너 실행 시 상위 프로세스보다 많은 권한을 부여할지 여부
readOnlyRootFilesystem
root 파일 시스템을 읽기 전용으로 할지 여부
runAsUser
실행 사용자
runAsGroup
실행 그룹
runAsNonRoot
root 에서 실행을 거부
seLinuxOptions
SELinux 옵션

 

컨테이너 보안 컨텍스트 확인 : 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 Mar 15 22:54:26 UTC 2025

# 파드에서 시간 변경 시도
kubectl exec -it sample-capabilities -- date -s "12:00:00"
kubectl exec -it sample-capabilities -- date

---
# 파드가 배포된 워커노드 확인
NAME                            NOMINATED NODE
sample-capabilities2            i-05beac6b1d64479d6

# 파드가 배포된 워커노드 IP 확인 
kubectl get node -o wide | grep i-05beac6b1d64479d6
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-capabilities -- date -s "12:00:00"
kubectl exec -it sample-capabilities -- 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

☞ 파드 보안 컨텍스트

  • 파드 레벨에서 보안 컨텍스트를 적용 : 파드에 포함된 모든 컨테이너가 영향을 받음
  • 파드와 컨테이너 정책 중복 시, 컨테이너 정책이 우선 적용됨
종류
개요
runAsUser
실행 사용자
runAsGroup
실행 그룹
runAsNonRoot
root 에서 실행을 거부
supplementalGroups
프라이머리 GUI에 추가로 부여할 GID 목록을 지정
fsGroup
파일 시스템 그룹 지정
systls
덮어 쓸 커널 파라미터 지정
seLinuxOptions
SELinux 옵션 지정

 

컨테이너 보안 컨텍스트 확인 : 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 설정 필요
# 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

 

initContainerprivileged 를 활용하여 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| yq
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

 

7. Securing Secrets


EKS 워크숍 보안 솔루션 정리 : Sealed Secrets + AWS KMS , Amazon GuardDuty + 사고 대응 , AWS WAF

  • [EKS Workshop] Securing Secrets Using Sealed Secrets - Link

Valut Secret Operator on K8s - Youtube 개요 설치 실습 비교 GitHub

https://www.youtube.com/watch?v=DQLDH2bgmro

 

 

8. Amazon EKS Best Practices Guide for Security


 

 

보안 모범 사례 - Amazon EKS

이 페이지에 작업이 필요하다는 점을 알려 주셔서 감사합니다. 실망시켜 드려 죄송합니다. 잠깐 시간을 내어 설명서를 향상시킬 수 있는 방법에 대해 말씀해 주십시오.

docs.aws.amazon.com

kube-bench - Link

Falco : [workshop studio] Real Time Threat Detection with Falco - Link , Blog

datree - Link

 

 

 

(실습 완료 후) 자원 삭제


 

1. [운영서버 EC2]에서 원클릭 삭제 진행 : Karpenter 실습 환경 준비를 위해서 현재 EKS 실습 환경 전부 삭제

# eksctl delete cluster --name $CLUSTER_NAME && aws cloudformation delete-stack --stack-name $CLUSTER_NAME
nohup sh -c "eksctl delete cluster --name $CLUSTER_NAME && aws cloudformation delete-stack --stack-name $CLUSTER_NAME" > /root/delete.log 2>&1 &

# (옵션) 삭제 과정 확인
tail -f delete.log

 

2. testuser IAM User는 AWS 웹 관리콘솔에서 삭제

'AEWS study' 카테고리의 다른 글

7주차 - EKS Mode/Nodes - #2  (0) 2025.03.22
7주차 - EKS Mode/Nodes - #1  (0) 2025.03.20
6주차 - EKS Security - #1  (0) 2025.03.15
5주차 - EKS Autoscaling - #2  (0) 2025.03.08
5주차 - EKS Autoscaling - #1  (0) 2025.03.07
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2025/07   »
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
글 보관함