카테고리 없음

[AEWS] 6주차 - EKS Security

summary-aws 2025. 3. 10. 21:24

1. 쿠버네티스의 보안
  - 인증/인가
  - kube/config

 - 쿠버네티스의 API 접근 제어

2. 실습환경

3. EKS IRSA & Pod Identity

4. OWASP Kubernetes Top Ten

 

 

 

쿠버네티스(Kubernetes)에서 사용하는 암호화 방식

암호화 유형 대상 및 목적 사용 기술 및 알고리즘 특징 및 설명
Encryption at Rest
(저장 시 암호화)
- etcd에 저장되는 데이터 보호
- Secret, ConfigMap, PersistentVolumeClaim 등 민감 정보
- AES-GCM
- AES-CBC
- Secretbox
- 데이터를 etcd에 저장할 때 암호화하여 디스크 상의 데이터를 보호
- API서버 설정(EncryptionConfiguration)으로 적용
Encryption in Transit
(데이터 전송 암호화)
- 클러스터 내 컴포넌트 간 통신 보호
- 마스터 <-> 워커노드 간 통신 보호
- 사용자와 API 서버 간 통신 보호
- TLS 1.2 이상 (TLS 1.3 권장)
- X.509 인증서 기반
- API 서버는 기본적으로 HTTPS 통신
- 클러스터 내 모든 컴포넌트 간 통신을 TLS로 암호화하여 중간자 공격 방지
Secret 관리 및 보호 - 쿠버네티스의 Secret 오브젝트를 보호하기 위한 추가 보안 조치 - 외부 보안 솔루션(HashiCorp Vault, AWS Secrets Manager, GCP Secret Manager 등)
- CSI 드라이버(Secrets Store CSI Driver)
- 기본 Secret은 단순 Base64 인코딩으로 안전하지 않음
- 외부 Secret 관리 솔루션을 통해 강력한 암호화와 접근 제어 가능
Service Mesh 암호화
(Mutual TLS, mTLS)
- Pod 간 통신 보호
- 클러스터 내 서비스 간 요청과 응답 데이터 암호화
- Pod 간 통신을 추가적으로 보호
- Istio, Linkerd, Consul Connect 등 서비스 메쉬 기술
- Mutual TLS (mTLS)
- X.509 인증서 기반
- 클러스터 내부 네트워크 트래픽까지 보호 가능
- 서비스 간 상호 인증을 통한 인증된 통신 및 데이터 무결성 확보
컨테이너 이미지 암호화 - 컨테이너 레지스트리에 저장된 컨테이너 이미지 보호
- 악의적 접근 및 변조 방지
- OCI(컨테이너 이미지) 서명 및 검증 기술
- Notary, Cosign 등 이미지 서명 도구 활용
- 레지스트리 데이터 암호화(storage encryption)
- 컨테이너 이미지 무결성 보장
- 신뢰 가능한 이미지만 클러스터에 배포
노드 및 호스트 디스크 암호화 - 쿠버네티스 노드의 운영체제와 호스트 디스크 데이터 보호
- 전체 노드 레벨의 데이터 보호
- OS 레벨 디스크 암호화 (예: LUKS, AWS EBS 암호화 등) - 노드 또는 VM 디스크가 탈취되더라도 데이터 보호 가능
- OS/클라우드 공급자 레벨에서 지원됨
쿠버네티스 인증서 관리 - 클러스터 내부 인증서 및 API 서버 인증서의 안전한 관리 및 주기적 갱신 - cert-manager, kubeadm 등 인증서 관리 도구
- 자동 갱신(Automated rotation) 사용
- 인증서 유효기간 만료로 인한 보안 사고 예방
- 인증서 관리를 자동화하여 실수 방지
사용자 인증 및 접근제어(RBAC) - 인증된 사용자와 서비스 어카운트만 API 접근 허용
- 권한 있는 리소스 접근만 허용
- RBAC (역할 기반 접근제어)
- OIDC, LDAP, OAuth2 기반 인증
- API 접근권한 최소화
- 무단 접근 및 정보 유출 방지

 

EKS 인증/인가

인증 IAM 기반 인증 IAM 사용자/역할로 클러스터 접근 eksctl, aws CLI
인증 IRSA Kubernetes의 Service Account에 IAM 역할 매핑 Pod AWS API 사용
인증 OIDC 연동 외부 IdP와 연동하여 인증 수행 Okta, Azure AD 연동
인가 RBAC 리소스 접근 권한을 세부적으로 설정 Role, ClusterRole
인가 IAM과 RBAC 연동 IAM 인증과 RBAC 정책을 함께 사용하여 인가 aws-auth ConfigMap

 

인증

 

인가

쿠버네티스의 API 접근 제어

인증
(Authentication)
API 요청자의 신원을 확인합니다. - 클라이언트 인증서
- 베어러 토큰
- HTTP 기본 인증
- 익명 요청
kubeconfig의 클라이언트 인증서, ServiceAccount 토큰
인가
(Authorization)
인증된 사용자가 수행할 수 있는 작업의 권한을 확인합니다. - RBAC (Role-based Access Control)
- ABAC (Attribute-based Access Control)
- Node 인증
- Webhook 인증
Role, RoleBinding 설정
어드미션 제어
(Admission Control)
클러스터 정책에 따라 요청을 승인, 수정, 거부합니다. - 리소스 쿼터(ResourceQuota)
- PodSecurityAdmission
- LimitRanger
- ValidatingAdmissionWebhook
- MutatingAdmissionWebhook
파드 생성 시 CPU, Memory 제한, 보안 정책 적용

.kube/config는 일반적으로 다음과 같은 항목을 가진다.

  • clusters: 접근할 Kubernetes 클러스터 정보를 정의
  • users: 사용자 인증 정보를 정의
  • contexts: 클러스터, 사용자, 네임스페이스를 묶어 컨텍스트를 정의
  • current-context: 현재 사용 중인 컨텍스트 지정
섹션 하위필드 설명 비고
clusters name 클러스터의 고유 이름 kubectl config에서 사용
server Kubernetes API 서버 URL API 서버의 HTTPS 주소
certificate-authority
certificate-authority-data
클러스터 인증에 사용되는 CA 인증서 base64 인코딩된 데이터 또는 파일경로 지정
users name 사용자 식별 이름  
user.exec 인증 명령어 설정(예: AWS IAM 인증) IAM Authenticator 등
user.client-certificate
user.client-key
클라이언트 TLS 인증서 및 키 클라이언트 인증서 방식
user.token 직접 베어러 토큰을 지정할 경우 사용 토큰 인증 방식
contexts name 컨텍스트의 고유 이름 사용자가 지정한 컨텍스트 이름
context.cluster 연결할 클러스터 지정 clusters의 이름
user 사용할 사용자 정보 users에서 정의된 이름
namespace 기본 네임스페이스 생략 가능
current-context - 현재 사용할 컨텍스트 이름 kubectl 명령어가 기본으로 사용

 

.kube/config 예시

apiVersion: v1
kind: Config
preferences: {}

clusters:
- cluster:
    certificate-authority-data: <base64 encoded CA certificate>
    server: https://123456789ABCDEF.gr7.ap-northeast-2.eks.amazonaws.com
  name: my-cluster

users:
- name: my-user
  user:
    exec:
      apiVersion: client.authentication.k8s.io/v1beta1
      command: aws
      args:
        - eks
        - get-token
        - --cluster-name
        - my-cluster
        - --region
        - ap-northeast-2

contexts:
- context:
    cluster: my-cluster
    user: my-user
    namespace: default
  name: my-context

current-context: my-context

 

실습환경

# YAML 파일 다운로드
curl -O https://s3.ap-northeast-2.amazonaws.com/cloudformation.cloudneta.net/K8S/myeks-6week.yaml

# 변수 지정
CLUSTER_NAME=myeks
SSHKEYNAME=<SSH 키 페이 이름>
MYACCESSKEY=<IAM Uesr 액세스 키>
MYSECRETKEY=<IAM Uesr 시크릿 키>

# CloudFormation 스택 배포
aws cloudformation deploy --template-file myeks-6week.yaml --stack-name $CLUSTER_NAME --parameter-overrides KeyName=$SSHKEYNAME SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32  MyIamUserAccessKeyID=$MYACCESSKEY MyIamUserSecretAccessKey=$MYSECRETKEY ClusterBaseName=$CLUSTER_NAME --region ap-northeast-2

# CloudFormation 스택 배포 완료 후 작업용 EC2 IP 출력
aws cloudformation describe-stacks --stack-name myeks --query 'Stacks[*].Outputs[0].OutputValue' --output text

 

#eksctl get cluster

# kubeconfig 생성
aws eks update-kubeconfig --name myeks --user-alias $(aws sts get-caller-identity --query Arn --output text)
aws eks update-kubeconfig --name myeks --user-alias admin


kubectl ns default
kubectl get node --label-columns=node.kubernetes.io/instance-type,eks.amazonaws.com/capacityType,topology.kubernetes.io/zone
kubectl get pod -A
kubectl get pdb -n kube-system

 

# EC2 공인 IP 변수 지정
export N1=$(aws ec2 describe-instances --filters "Name=tag:Name,Values=myeks-ng1-Node" "Name=availability-zone,Values=ap-northeast-2a" --query 'Reservations[*].Instances[*].PublicIpAddress' --output text)
export N2=$(aws ec2 describe-instances --filters "Name=tag:Name,Values=myeks-ng1-Node" "Name=availability-zone,Values=ap-northeast-2b" --query 'Reservations[*].Instances[*].PublicIpAddress' --output text)
export N3=$(aws ec2 describe-instances --filters "Name=tag:Name,Values=myeks-ng1-Node" "Name=availability-zone,Values=ap-northeast-2c" --query 'Reservations[*].Instances[*].PublicIpAddress' --output text)
echo $N1, $N2, $N3

# *remoteAccess* 포함된 보안그룹 ID
aws ec2 describe-security-groups --filters "Name=group-name,Values=*remoteAccess*" | jq
export MNSGID=$(aws ec2 describe-security-groups --filters "Name=group-name,Values=*remoteAccess*" --query 'SecurityGroups[*].GroupId' --output text)

# 해당 보안그룹 inbound 에 자신의 집 공인 IP 룰 추가
aws ec2 authorize-security-group-ingress --group-id $MNSGID --protocol '-1' --cidr $(curl -s ipinfo.io/ip)/32

# 해당 보안그룹 inbound 에 운영서버 내부 IP 룰 추가
aws ec2 authorize-security-group-ingress --group-id $MNSGID --protocol '-1' --cidr 172.20.1.100/32
aws ec2 authorize-security-group-ingress --group-id $MNSGID --protocol '-1' --cidr 172.20.1.200/32

# 워커 노드 SSH 접속
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh -o StrictHostKeyChecking=no ec2-user@$i hostname; echo; done

 

# default 네임스페이스 적용
kubectl ns default

# 환경변수 정보 확인
export | egrep 'ACCOUNT|AWS_|CLUSTER|KUBERNETES|VPC|Subnet'
export | egrep 'ACCOUNT|AWS_|CLUSTER|KUBERNETES|VPC|Subnet' | egrep -v 'KEY'

# krew 플러그인 확인 : 보안 관련 플러그인 다수 설치
kubectl krew list


# 인스턴스 정보 확인
aws ec2 describe-instances --query "Reservations[*].Instances[*].{InstanceID:InstanceId, PublicIPAdd:PublicIpAddress, PrivateIPAdd:PrivateIpAddress, InstanceName:Tags[?Key=='Name']|[0].Value, Status:State.Name}" --filters Name=instance-state-name,Values=running --output table

# 노드 IP 확인 및 PrivateIP 변수 지정
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
N1=$(kubectl get node --label-columns=topology.kubernetes.io/zone --selector=topology.kubernetes.io/zone=ap-northeast-2a -o jsonpath={.items[0].status.addresses[0].address})
N2=$(kubectl get node --label-columns=topology.kubernetes.io/zone --selector=topology.kubernetes.io/zone=ap-northeast-2b -o jsonpath={.items[0].status.addresses[0].address})
N3=$(kubectl get node --label-columns=topology.kubernetes.io/zone --selector=topology.kubernetes.io/zone=ap-northeast-2c -o jsonpath={.items[0].status.addresses[0].address})
echo "export N1=$N1" >> /etc/profile
echo "export N2=$N2" >> /etc/profile
echo "export N3=$N3" >> /etc/profile
echo $N1, $N2, $N3

# 노드 IP 로 ping 테스트
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ping -c 1 $i ; echo; done

# 변수 지정
export CLUSTER_NAME=myeks
export VPCID=$(aws ec2 describe-vpcs --filters "Name=tag:Name,Values=$CLUSTER_NAME-VPC" --query 'Vpcs[*].VpcId' --output text)
export PubSubnet1=$(aws ec2 describe-subnets --filters Name=tag:Name,Values="$CLUSTER_NAME-Vpc1PublicSubnet1" --query "Subnets[0].[SubnetId]" --output text)
export PubSubnet2=$(aws ec2 describe-subnets --filters Name=tag:Name,Values="$CLUSTER_NAME-Vpc1PublicSubnet2" --query "Subnets[0].[SubnetId]" --output text)
export PubSubnet3=$(aws ec2 describe-subnets --filters Name=tag:Name,Values="$CLUSTER_NAME-Vpc1PublicSubnet3" --query "Subnets[0].[SubnetId]" --output text)
export N1=$(aws ec2 describe-instances --filters "Name=tag:Name,Values=$CLUSTER_NAME-ng1-Node" "Name=availability-zone,Values=ap-northeast-2a" --query 'Reservations[*].Instances[*].PublicIpAddress' --output text)
export N2=$(aws ec2 describe-instances --filters "Name=tag:Name,Values=$CLUSTER_NAME-ng1-Node" "Name=availability-zone,Values=ap-northeast-2b" --query 'Reservations[*].Instances[*].PublicIpAddress' --output text)
export N3=$(aws ec2 describe-instances --filters "Name=tag:Name,Values=$CLUSTER_NAME-ng1-Node" "Name=availability-zone,Values=ap-northeast-2c" --query 'Reservations[*].Instances[*].PublicIpAddress' --output text)
export CERT_ARN=$(aws acm list-certificates --query 'CertificateSummaryList[].CertificateArn[]' --output text) #사용 리전의 인증서 ARN 확인
MyDomain=hey-aws.click
MyDnzHostedZoneId=$(aws route53 list-hosted-zones-by-name --dns-name "$MyDomain." --query "HostedZones[0].Id" --output text)


echo $CLUSTER_NAME $VPCID $PubSubnet1 $PubSubnet2 $PubSubnet3
echo $N1 $N2 $N3 $MyDomain $MyDnzHostedZoneId
tail -n 15 ~/.bashrc

 

# AWS LoadBalancerController
helm repo add eks https://aws.github.io/eks-charts
helm install aws-load-balancer-controller eks/aws-load-balancer-controller -n kube-system --set clusterName=$CLUSTER_NAME \
  --set serviceAccount.create=false --set serviceAccount.name=aws-load-balancer-controller

# ExternalDNS
echo $MyDomain
curl -s https://raw.githubusercontent.com/gasida/PKOS/main/aews/externaldns.yaml | MyDomain=$MyDomain MyDnzHostedZoneId=$MyDnzHostedZoneId envsubst | kubectl apply -f -

# gp3 스토리지 클래스 생성
cat <<EOF | kubectl apply -f -
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: gp3
  annotations:
    storageclass.kubernetes.io/is-default-class: "true"
allowVolumeExpansion: true
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer
parameters:
  type: gp3
  allowAutoIOPSPerGBIncrease: 'true'
  encrypted: 'true'
  fsType: xfs # 기본값이 ext4
EOF
kubectl get sc


# kube-ops-view
helm repo add geek-cookbook https://geek-cookbook.github.io/charts/ https://geek-cookbook.github.io/charts
helm install kube-ops-view geek-cookbook/kube-ops-view --version 1.2.2 --set service.main.type=ClusterIP  --set env.TZ="Asia/Seoul" --namespace kube-system

# kubeopsview 용 Ingress 설정 : group 설정으로 1대의 ALB를 여러개의 ingress 에서 공용 사용
echo $CERT_ARN
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    alb.ingress.kubernetes.io/certificate-arn: $CERT_ARN
    alb.ingress.kubernetes.io/group.name: study
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}, {"HTTP":80}]'
    alb.ingress.kubernetes.io/load-balancer-name: $CLUSTER_NAME-ingress-alb
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/ssl-redirect: "443"
    alb.ingress.kubernetes.io/success-codes: 200-399
    alb.ingress.kubernetes.io/target-type: ip
  labels:
    app.kubernetes.io/name: kubeopsview
  name: kubeopsview
  namespace: kube-system
spec:
  ingressClassName: alb
  rules:
  - host: kubeopsview.$MyDomain
    http:
      paths:
      - backend:
          service:
            name: kube-ops-view
            port:
              number: 8080  # name: http
        path: /
        pathType: Prefix
EOF


# 설치된 파드 정보 확인
kubectl get pods -n kube-system

# service, ep, ingress 확인
kubectl get ingress,svc,ep -n kube-system

# Kube Ops View 접속 정보 확인 : 조금 오래 기다리면 접속됨...
echo -e "Kube Ops View URL = https://kubeopsview.$MyDomain/#scale=1.5"
open "https://kubeopsview.$MyDomain/#scale=1.5" # macOS

 

 

# 네임스페이스(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 create token dev-k8s -n dev-team

 

dev-k8s 서비스어카운트의 토큰 정보 확인

  - https://jwt.io/ → Bearer type - JWT(JSON Web Token) 확인

 

 

 

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

 

 

# 각각 네임스피이스에 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
 serviceAccount: dev-k8s
 ...
kubectl get pod -o infra-kubectl -n infra-team -o yaml
 serviceAccount: infra-k8s
...

# 파드에 기본 적용되는 서비스 어카운트(토큰) 정보 확인
kubectl exec -it dev-kubectl -n dev-team -- ls /run/secrets/kubernetes.io/serviceaccount
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

 

-  각각 네임스페이스에 롤(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



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

Name:         role-dev-team
Labels:       <none>
Annotations:  <none>
PolicyRule:
  Resources  Non-Resource URLs  Resource Names  Verbs
  ---------  -----------------  --------------  -----
  *.*        []                 []              [*]

# 롤바인딩 생성 : '서비스어카운트 <-> 롤' 간 서로 연동
cat <<EOF | kubectl create -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: roleB-dev-team
  namespace: dev-team
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: role-dev-team
subjects:
- kind: ServiceAccount
  name: dev-k8s
  namespace: dev-team
EOF

cat <<EOF | kubectl create -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: roleB-infra-team
  namespace: infra-team
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: role-infra-team
subjects:
- kind: ServiceAccount
  name: infra-k8s
  namespace: infra-team
EOF

# 롤바인딩 확인
kubectl get rolebindings -n dev-team
kubectl get rolebindings -n infra-team
kubectl get rolebindings -n dev-team -o yaml
kubectl describe rolebindings roleB-dev-team -n dev-team

Name:         roleB-dev-team
Labels:       <none>
Annotations:  <none>
Role:
  Kind:  Role
  Name:  role-dev-team
Subjects:
  Kind            Name     Namespace
  ----            ----     ---------
  ServiceAccount  dev-k8s  dev-team

 

생성 후 다시 권한 테스트

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

# 권한 테스트
k1 get pods 
k1 run nginx --image nginx:1.20-alpine
k1 get pods
k1 delete pods nginx
k1 get pods -n kube-system
k1 get 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

 

2. EKS 인증/인가

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

 

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

 

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

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

# Show an RBAC access matrix for server resources
kubectl access-matrix -h
kubectl access-matrix # Review access to cluster-scoped resources

LIST  CREATE  UPDATE  DELETE
apiservices.apiregistration.k8s.io                              ✔     ✔       ✔       ✔

kubectl access-matrix --namespace default # Review access to namespaced resources in 'default'
configmaps                                      ✔     ✔       ✔       ✔

# RBAC Lookup by subject (user/group/serviceaccount) name

특정 ServiceAccount나 유저가 어떤 권한을 가지고 있는지 확인.
특정 리소스에 접근 가능한 계정 또는 서비스어카운트를 확인할 수 있음.
어떤 role이나 clusterrole이 어떤 리소스에 어떤 액션을 허용하는지 보기 쉽게 보여줌.

kubectl rbac-tool -h
kubectl rbac-tool lookup
kubectl rbac-tool lookup system:masters
  SUBJECT        | SUBJECT TYPE | SCOPE       | NAMESPACE | ROLE
+----------------+--------------+-------------+-----------+---------------+
  system:masters | Group        | ClusterRole |           | cluster-admin

kubectl rbac-tool lookup system:nodes # eks:node-bootstrapper
kubectl rbac-tool lookup system:bootstrappers # eks:node-bootstrapper
kubectl describe ClusterRole eks:node-bootstrapper

# RBAC List Policy Rules For subject (user/group/serviceaccount) name
kubectl rbac-tool policy-rules
kubectl rbac-tool policy-rules -e '^system:.*'
kubectl rbac-tool policy-rules -e '^system:authenticated'

# Generate ClusterRole with all available permissions from the target cluster
kubectl rbac-tool show


# Shows the subject for the current context with which one authenticates with the cluster
kubectl rbac-tool whoami



# Summarize RBAC roles for subjects : ServiceAccount(default), User, Group
kubectl rolesum -h
주체별 역할 요약: 특정 서비스 어카운트, 사용자, 또는 그룹에 할당된 역할과 권한을 요약하여 표시
kubectl rolesum aws-node -n kube-system    # sa
kubectl rolesum -k User system:kube-proxy  # user
kubectl rolesum -k Group system:masters    # group
kubectl rolesum -k Group system:nodes
kubectl rolesum -k Group system:authenticated

  • GLW: Get, List, Watch (읽기 권한)
  • CUP: Create, Update, Patch (생성 및 수정 권한)
  • DC: Delete, Create (삭제 및 생성 권한)


# [운영서버1 EC2 : 터미널1] A tool to visualize your RBAC permissions
kubectl rbac-view
INFO[0000] Getting K8s client
INFO[0000] serving RBAC View and http://localhost:8800

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

 

kubectl rbac-tool visualize 

 

EKS 인증/인가 확인 :

핵심 : 인증은 AWS IAM, 인가는 K8S RBAC에서 처리

 

1. kubectl 명령 → aws eks get-tokenSTS토큰 요청 ⇒ 응답값 디코드(Pre-Signed URL 이며 GetCallerIdentity..)

# 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

"expirationTimestamp": "2025-03-11T12:56:14Z",

 

[ConfigMap 방식] 이제 쿠버네티스 RBAC 인가를 처리

kubectl get cm -n kube-system aws-auth -o yaml
apiVersion: v1
data:
  mapRoles: |
    - groups:
      - system:bootstrappers
      - system:nodes
      rolearn: arn:aws:iam::866816795:role/eksctl-myeks-nodegroup-ng1-NodeInstanceRole-yiaf3M53O
      username: system:node:{{EC2PrivateDNSName}}
kind: ConfigMap
metadata:
  creationTimestamp: "2025-03-11T11:38:02Z"
  name: aws-auth
  namespace: kube-system
  resourceVersion: "1975"
  uid: c7b7c38f-2e24-4b32-894b-9e89d3bda67b

(chunki:default) [root@operator-host ~]# kubectl rbac-tool lookup system:authenticated
  SUBJECT              | SUBJECT TYPE | SCOPE       | NAMESPACE | ROLE                      | BINDING
-----------------------+--------------+-------------+-----------+---------------------------+----------------------------
  system:authenticated | Group        | ClusterRole |           | system:basic-user         | system:basic-user
  system:authenticated | Group        | ClusterRole |           | system:discovery          | system:discovery
  system:authenticated | Group        | ClusterRole |           | system:public-info-viewer | system:public-info-viewer
(chunki:default) [root@operator-host ~]# kubectl rolesum -k Group system:masters
Group: system:masters

Policies:
• [CRB] */cluster-admin ⟶  [CR] */cluster-admin
  Resource  Name  Exclude  Verbs  G L W C U P D DC
  *.*       [*]     [-]     [-]   ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔

(chunki:default) [root@operator-host ~]# kubectl rolesum -k Group system:authenticated
Group: system:authenticated

Policies:
• [CRB] */system:basic-user ⟶  [CR] */system:basic-user
  Resource                                       Name  Exclude  Verbs  G L W C U P D DC
  selfsubjectaccessreviews.authorization.k8s.io  [*]     [-]     [-]   ✖ ✖ ✖ ✔ ✖ ✖ ✖ ✖
  selfsubjectreviews.authentication.k8s.io       [*]     [-]     [-]   ✖ ✖ ✖ ✔ ✖ ✖ ✖ ✖
  selfsubjectrulesreviews.authorization.k8s.io   [*]     [-]     [-]   ✖ ✖ ✖ ✔ ✖ ✖ ✖ ✖


• [CRB] */system:discovery ⟶  [CR] */system:discovery


• [CRB] */system:public-info-viewer ⟶  [CR] */system:public-info-viewer

kubectl describe clusterrolebindings.rbac.authorization.k8s.io cluster-admin
Name:         cluster-admin
Labels:       kubernetes.io/bootstrapping=rbac-defaults
Annotations:  rbac.authorization.kubernetes.io/autoupdate: true
Role:
  Kind:  ClusterRole
  Name:  cluster-admin
Subjects:
  Kind   Name            Namespace
  ----   ----            ---------
  Group  system:masters


 

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

# 사용자에게 프로그래밍 방식 액세스 권한 부여
aws iam create-access-key --user-name testuser
{
    "AccessKey": {
        "UserName": "testuser",
        "AccessKeyId": "AKIA5ILF2##",
        "Status": "Active",
        "SecretAccessKey": "TxhhwsU8##",
        "CreateDate": "2023-05-23T07:40:09+00:00"
    }
}
# testuser 사용자에 정책을 추가
aws iam attach-user-policy --policy-arn arn:aws:iam::aws:policy/AdministratorAccess --user-name testuser

 

2번서버에서 testuser로 접근 

# get-caller-identity 확인 >> 왜 안될까요?
aws sts get-caller-identity --query Arn

# testuser 자격증명 설정
aws configure
AWS Access Key ID [None]: AKIA5ILF2F...
AWS Secret Access Key [None]: ePpXdhA3cP....
Default region name [None]: ap-northeast-2

# get-caller-identity 확인
aws sts get-caller-identity --query Arn
"arn:aws:iam::911283464785:user/testuser"

kubectl get node -v6



권한 복사(기존계정에서)
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

  mapUsers: |
    - groups:
      - system:masters
      userarn: arn:aws:iam::826816795:user/testuser
      username: testuser

kubectl ns default

 

[myeks-bastion] testuser 의 Group 변경(system:masters → system:authenticated)으로 RBAC 동작 확인

# 방안2 : 아래 edit로 mapUsers 내용 직접 수정
system:masters -> system:authenticated을 복원한다
kubectl edit cm -n kube-system aws-auth



[myeks-bastion-2] testuser kubectl 사용 확인
 권한이 없어진것을 확인할수있다.

 

3. EKS IRSA & Pod Identity

 - Service Account Token Volume Projection, Admission Control, JWT(JSON Web Token), OIDC

 

- 서비스 계정 토큰을 이용해서 서비스와 서비스, 즉 파드(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 바인딩된 서비스 어카운트 토큰 볼륨

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 "1f2d1e2abcdf" > ./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
...
  preemptionPolicy: PreemptLowerPriority
  priority: 0
  serviceAccountName: default
  tolerations:
  - effect: NoExecute
    key: node.kubernetes.io/not-ready
    operator: Exists
    tolerationSeconds: 300
  - effect: NoExecute
    key: node.kubernetes.io/unreachable
    operator: Exists
    tolerationSeconds: 300
  volumes:
  - name: all-in-one
    projected:
      sources:
      - secret:
          name: user
      - secret:
          name: pass
  - name: kube-api-access-d6blw
    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 delete pod test-projected-volume && kubectl delete secret user pass

 

k8s api 접근 단계

 

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

 

 

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

....
 because no identity-based policy allows the s3:ListAllMyBuckets action

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


# 파드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
 - no identity-based policy allows the s3:ListAllMyBuckets action

# 서비스 어카운트 토큰 확인
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
  ],

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

 

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


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

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

kubectl exec -it eks-iam-test3 -- aws ec2 describe-instances --region ap-northeast-2
 - policy allows the ec2:DescribeInstances action
kubectl exec -it eks-iam-test3 -- aws ec2 describe-vpcs --region ap-northeast-2
 - because no identity-based policy allows the ec2:DescribeVpcs action

 

4. OWASP Kubernetes Top Ten

OWASP Kubernetes Top Ten 항목

 

순위 항목 내용
K01 Insecure Workload Configurations
(불안전한 워크로드 구성)
워크로드가 적절히 보안 설정되지 않아 발생하는 위험
K02 Supply Chain Vulnerabilities
(공급망 취약점)
컨테이너 이미지 및 소프트웨어 공급망의 취약점
K03 Overly Permissive RBAC Configurations
(과도하게 허용적인 RBAC 설정)
권한 관리가 부적절하여 공격자가 시스템 권한을 얻기 쉬운 설정
K04 Lack of Centralized Policy Enforcement
(중앙화된 정책 집행의 부재)
일관된 보안 정책이 없어 위험 요소 관리가 어렵거나 미흡한 경우
K05 Inadequate Logging and Monitoring
(불충분한 로깅 및 모니터링)
위협이나 사고를 탐지할 수 없을 만큼 부족한 로깅/모니터링
K06 Broken Authentication Mechanisms
(취약한 인증 메커니즘)
인증 체계가 약하거나 잘못 구현되어 접근 통제가 무력화될 수 있는 경우
K07 Missing Network Segmentation Controls
(부족한 네트워크 분리 및 통제)
워크로드 간 네트워크 분리가 제대로 되지 않아 위협이 확산될 수 있는 경우
K08 Secrets Management Failures
(시크릿 관리 실패)
인증 정보(Secrets)가 안전하게 관리되지 않아 정보가 유출될 위험
K09 Misconfigured Cluster Components
(잘못 구성된 클러스터 구성 요소)
Kubernetes 자체 구성 요소(API Server, etcd, kubelet 등)가 안전하게 구성되지 않은 경우
K10 Outdated and Vulnerable Kubernetes Components
(오래되고 취약한 Kubernetes 구성요소)
Kubernetes 자체 버전이 오래되었거나 업데이트되지 않아 존재하는 취약점

 

파드 권한과 호스트 네임스페이스 공유로 호스트 탈취

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)

 

Kyverno

다음은 Kyverno의 주요 기능과 예시를 보기 쉽게 정리한 표입니다.

기능  설명 예시
Validation
(정책 기반 검증)
Kubernetes 리소스가 정책에 정의된 규칙을 준수하는지 확인하고, 위반된 리소스를 거부하거나 경고합니다. - Privileged 모드 컨테이너 배포 차단
- 허용되지 않은 이미지 배포 방지
🔄 Mutation
(자동 변경 및 기본값 주입)
리소스가 배포될 때 특정 속성(예: label, annotation)을 자동으로 추가하거나 설정을 변경합니다. - 모든 리소스에 보안 관련 Annotation 자동 추가
- 컨테이너 리소스 제한 기본값 자동 설정
🚧 Generate
(리소스 자동 생성)
특정 이벤트 발생 시 자동으로 추가 리소스를 생성합니다. - Namespace 생성 시 NetworkPolicy 자동 생성
- 네임스페이스 리소스 쿼터 자동 설정
🔐 Software Supply Chain
(소프트웨어 공급망 보호)
이미지 서명 및 SBOM 등을 이용해 배포되는 컨테이너 이미지의 무결성과 보안을 검증합니다. - 미서명 이미지 또는 취약 이미지 배포 차단
- 이미지 서명 검증 정책 적용

 

# 설치
# 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


 

프로메테우스에서 검색했을 시 

 -  count(count(kyverno_policy_rule_info_total{policy_type="cluster"}==1) by (policy_name)) 조회불가.

 

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 사용자로 실행을 거부하도록 설정 시 동작

#
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

 

후기

쿠버네티스에 접근방법을 다양하게 제어할 수 있는 방법을 테스트할 수 있는 기회였으며

argoCD에서 viewer 계정을 생성하여 namespace 별 접근/제어이 가능 할 수 있도록 응용해보았다.