2024. 4. 14. 08:29ㆍEKS@CloudNet
- CloudNet에서 주관하는 EKS 스터디 2기 내용입니다
- 매번 좋은 스터디를 진행해 주시는 CloudNet 팀 감사드립니다
- 잘못된 점, 업데이트된 지식, 다른 코멘트 언제나 환영입니다!
0. Setup

- 기존에는 1 Bastion, 3 worker nodes 의 구조로 이루어졌지만 이번에는 2 bastions, 3 worker nodes로 구성된다. 한 Bastion은 하나의 유저에 맵핑되므로, 이번 실습 환경은 DevOps 신입사원이 한명 더 들어왔다는 가정의 구현이라고 볼 수 있다.
- 실습 환경은 지난번과 마찬가지로 가시다님의 원클릭 배포 파일을 cloudformation에서 수행하는 방식으로 세팅한다. kubeopsview, prometheus, grafana는 동일하니까 생략하겠다.
https://s3.ap-northeast-2.amazonaws.com/cloudformation.cloudneta.net/K8S/eks-oneclick5.yaml
1. k8s security
우선 k8s의 보안 정책인 인증과 인가에 대해서 간단히 알아보자. 이 과정은 kube-api 서버 접근을 통해 이뤄지는데,
인증 -> 인가 -> Admission Control 의 과정을 거쳐 이뤄진다.
인증(Authentication)
너는 누구니?
- CA cert, Client cert, Clienty key 3가지를 통해 인증한다
인가(Authorization)
그래서 너가 뭘 할 수 있니?
기본적으로 인증 이후에 일어난다.
- RBAC(Role-Based Account Control)
사용자(Role) 와 역할(Service Account)을 별도로 선언하고 이 둘을 조합해서(Role Binding) 해서 관리하므로 확장성이 좋다
실습
1. 개발팀과 인프라팀이 사용할 namespace 를 각각 만들고 namespace별로 SA 를 따로 만들어 준다
(SA 설정할 때 원하는 namespace에 있는지 꼭 확인한다!)
# create namespace
kubectl create namespace dev-team
kubectl create ns infra-team
# check namespace
kubectl get ns
# create SA
kubectl create sa dev-k8s -n dev-team
kubectl create sa infra-k8s -n infra-team
# check SA
kubectl get sa -n dev-team
kubectl get sa -n infra-team


2. SA 를 지정하여 Pod 생성 하기
우선 각 namespace 에 kubectl pod를 새로 생성해준다. 이 kubectl은 1. 에서 만든 SA/Role이 적용되어 있다.
cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
name: dev-kubectl #infra-kubectl
namespace: dev-team #infra-team
spec:
serviceAccountName: dev-k8s #infra-k8s
containers:
- name: kubectl-pod
image: bitnami/kubectl:1.28.5
command: ["tail"]
args: ["-f", "/dev/null"]
terminationGracePeriodSeconds: 0
EOF
- namespace 별로 SA 가 맵핑된 것을 볼 수 있다.

- token 자체를 확인할 필요가 있을 때는 아래와 같이 확인한다
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
- 그러면 이렇게 만들어진 SA 의 권한을 테스트해보자. kubectl auth can-i 명령을 이용할 수 있다
alias k1='kubectl exec -it dev-kubectl -n dev-team -- kubectl'
alias k2='kubectl exec -it infra-kubectl -n infra-team -- kubectl'
k1 auth can-i get pods

현재는 권한이 없다고 나온다.
3. Role 을 이용한 권한 제어하기
Role 은 verb로 권한을 인가한다. 사용가능한 verb는 공식문서를 참고하자.
실습으로는 모든 권한을 가진 (wildcard로 *를 사용) role을 만든다.
# Role 만들기
cat <<EOF | kubectl create -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: role-dev-team #role-infra-team
namespace: dev-team #infra-team
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["*"]
EOF
# Role 확인
kubectl get roles -n dev-team
kubectl describe roles role-dev-team -n dev-team
role이 잘 생성된 것을 확인한다

- 이번에는 Role과 SA를 연동하는 Rolebinding을 만들어 준다.
# Rolebinding 만들기
cat <<EOF | kubectl create -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: roleB-dev-team #roleB-infra-team
namespace: dev-team #infra-team
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: role-dev-team #role-infra-team
subjects:
- kind: ServiceAccount
name: dev-k8s #infra-k8s
namespace: dev-team #infra-team
EOF
# Rolebinding 확인
kubectl get rolebindings -n dev-team
kubectl describe rolebindings roleB-dev-team -n dev-team
RB가 잘 생성되었고 SA와 Role이 연동되었다.

- 위에서와 같이 can-i명령을 사용해서 get pod 권한을 조회하면 이제는 가능하다.

- 다음 실습을 위해 namespace를 삭제한다
kubectl delete ns dev-team infra-team
2. EKS security
- 인증 절차는 AWS IAM 에서, 인가는 K8S RBAC 에서 진행한다.
왼쪽 그림은 IAM을 이용한 인증, 오른쪽 그림은 RBAC을 이용한 인가 과정을 설명한다


- RBAC 관련 유용한 krew plugin을 설치한다. 각각 뭔지 간단하게 짚고 넘어가면
kubectl krew install access-matrix rbac-tool rbac-view rolesum whoami
whoami: 인증된 주체를 확인한다

access-matrix: 현재 각 object type에 대해 가진 CRUD 권한을 시각화해준다

rbac-tool: User/groupd/sa의 이름으로 권한 범위와 role, rolebinding을 조회한다

rolesum: 가지고 있는 RBAC Role을 시각화해서 요약해 준다

실습
신입 Devops의 입사를 가정하고, myeks-bastion-2에 설정을 진행해 본다.
- 우선 bastion에서 IAM testuser을 생성하고 Admin 권한을 부여한다
# testuser 사용자 생성
aws iam create-user --user-name testuser
# 사용자에게 프로그래밍 방식 액세스 권한 부여
aws iam create-access-key --user-name testuser
# testuser 사용자에 정책을 추가
aws iam attach-user-policy --policy-arn arn:aws:iam::aws:policy/AdministratorAccess --user-name testuser
# get-caller-identity 확인
aws sts get-caller-identity --query Arn
kubectl whoami #returns test-user
# EC2 IP 확인 : myeks-bastion-EC2-2 PublicIPAdd 확인
aws ec2 describe-instances --query "Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,PrivateIPAdd:PrivateIpAddress,InstanceName:Tags[?Key=='Name']|[0].Value,Status:State.Name}" --filters Name=instance-state-name,Values=running --output table

- 그러나 bastion2에서 접속해서 admin 권한이 필요한 동작을 하면
Unable to locate credentials. You can configure credentials by running "aws configure".
와 함께 거부당한다. 이때는 새로 접속한 Bastion-2에서 aws configure로 pub-priv key를 등록해주면 된다.

- 이제 bastion에서 testuser에게 system:masters 그룹을 부여해서 EKS 관리자 권한을 준다.
eksctl get iamidentitymapping --cluster $CLUSTER_NAME
eksctl create iamidentitymapping --cluster $CLUSTER_NAME --username testuser --group system:masters --arn arn:aws:iam::$ACCOUNT_ID:user/testuser
# 확인
kubectl get cm -n kube-system aws-auth -o yaml | kubectl neat | yh
그러나 그래도 bastion2에는 권한이 없다.

- bastion2에서 testuser를 위한 Kubeconfig를 생성해준 후 Kubectl을 사용해야 한다.
# kubeconfig for testuser
aws eks update-kubeconfig --name $CLUSTER_NAME --user-alias testuser
# kubectl 사용 확인
kubectl ns default
kubectl get node -v6
이제 Kubectl을 정상적으로 사용할 수 있다.

3. IRSA & Pod identity
IRSA: Iam Roles for Service Accounts


- EC2 Instance Profile 은 매 Pod 마다 새로 role/SA를 만들어 주지 않는 이상 최소 권한 원칙에 위배된다. (작동은 가능하나 보안상 취약, 과연 가능하긴 한 걸까...?)
- 따라서 IRSA가 등장했다. 파드가 특정 IAM 역할로 Assume 할때 토큰을 AWS에 전송하고, AWS는 토큰과 EKS IdP(외부 Id Provider)를 통해 해당 IAM 역할을 사용할 수 있는지 검증하여 Pod 내부 어플리캐이션 컨테이너가 역할을 사용할 수 있게 해 준다.
실습
- EKS iam pod을 만들어 SA에 연결되는 IAM Role을 확인한다
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
# pod 확인
k describe pod eks-iam-test
이렇게 Pod을 만들고 나면 AWS console에서 Listbuckets 가 발생하는데 여기에서 이벤트 레코드를 볼 수 있다.

- 이번에는 JWT token을 실습해보자. JWT Token은 SA가 생성될 때 같이 생성되어 자동으로 Secret에 추가된다. JWT 토큰의 디코딩은 jwt.io 를 참고하자.
# pod fpr JWT test
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
# SA token 확인
SA_TOKEN=$(kubectl exec -it eks-iam-test2 -- cat /var/run/secrets/kubernetes.io/serviceaccount/token)
echo $SA_TOKEN
이런 식으로 직접 token을 볼 수도 있다.

Pod identity
그러나 이런식으로 SA로 관리하면 모든 pod이 내려갔다 올라가고, 신규 클러스터를 만들게 되기 때문에 버전 업데이트가 굉장히 괴롭다고 한다. 따라서 addon인 eks-pod-identity-agent 를 사용해서 SA 로 활용되는 IAM credententials을 관리할 수 있다.
# 모니터링
watch -d kubectl get pod -A
# ADDON 선언
ADDON=eks-pod-identity-agent
aws eks describe-addon-versions \
--addon-name $ADDON \
--kubernetes-version 1.28 \
--query "addons[].addonVersions[].[addonVersion, compatibilities[].defaultVersion]" \
--output text
# 설치
eksctl create addon --cluster $CLUSTER_NAME --name eks-pod-identity-agent --version 1.2.0
# 획인
eksctl get addon --cluster $CLUSTER_NAME
걸어놓은 모니터링에서 pod-identity-agent 가 2개 추가된 것을 볼 수 있다.
(이전)

(이후)

확인: pod-identity 말고 다른 addon 들도 실행되고 있다는 걸 알 수 있었고, pod-identity가 daemonset으로 구현되었음을 확인
eksctl get addon --cluster $CLUSTER_NAME
kubectl get ds -n kube-system eks-pod-identity-agent -o yaml | kubectl neat | yh

4. Kyverno
Kyverno는 K8s native 한 Policy manager로 정책을 자원으로 관리할 수 있게 해 준다. 타 언어 사용 없이 Custom Resource 만으로 정책을 관리한다. 이 중 Webhook에서 admission review 요청을 처리한다

Kyverno에 대해서는 아래 블로그들에 더 잘 설명되어 있다.
- Devocean: kyverno 소개(OPA의 대항마?)
실습
- kyverno 배포
# 설치
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.2.0-rc.3 -f kyverno-value.yaml -n kyverno
# 확인
kubectl get all -n kyverno
+DEBUG: 실습 환경 세팅 시 Prometheus 를 설치하지 않은 상태로 Kyverno 설치를 시도하면 아래와 같은 에러메시지를 내면서 설치되지 않는다. 실습 환경 세팅 시 빼먹지 말고 Prometheus/Grafana 를 설치하자 (환경 세팅: 지난 포스팅 참고)
Error: INSTALLATION FAILED: unable to build kubernetes objects from release manifest: [resource mapping not found for name: "kyverno-admission-controller" namespace: "default" from "": no matches for kind "ServiceMonitor" in version "monitoring.coreos.com/v1"
ensure CRDs are installed first, resource mapping not found for name: "kyverno-background-controller" namespace: "default" from "": no matches for kind "ServiceMonitor" in version "monitoring.coreos.com/v1"
ensure CRDs are installed first, resource mapping not found for name: "kyverno-cleanup-controller" namespace: "default" from "": no matches for kind "ServiceMonitor" in version "monitoring.coreos.com/v1"
ensure CRDs are installed first, resource mapping not found for name: "kyverno-reports-controller" namespace: "default" from "": no matches for kind "ServiceMonitor" in version "monitoring.coreos.com/v1"
ensure CRDs are installed first]

- 이제는 kyverno 를 사용하기 위해 step-cli를 설치해준다.
wget https://dl.smallstep.com/cli/docs-cli-install/latest/step-cli_amd64.rpm
sudo rpm -i step-cli_amd64.rpm
- kyverno를 이용해 Secret을 조회해 보자
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
가지고 있는 ca, crt, valid date를 확인할 수 있다.

- kyverno는 Prometheus/Grafana와도 연동해서 secret을 관리할 수 있다(아래참조).
- kyverno에서 validation을 진행해보기 위해 ClusterPolicy 를 새로 만든다.
# 모니터링
watch -d kubectl get pod -n kyverno
# ClusterPolicy 적용
kubectl create -f- << EOF
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-labels
spec:
validationFailureAction: Enforce
rules:
- name: check-team
match:
any:
- resources:
kinds:
- Pod
validate:
message: "label 'team' is required"
pattern:
metadata:
labels:
team: "?*"
EOF
# 확인
kubectl get validatingwebhookconfigurations
kubectl get ClusterPolicy
cleanup-admission-reports 와 cleanup-cluster-admissions 가 생성된 것을 확인한다

실행 후 grafana 에도 이렇게 새 cluster policy 와 rule 6개가 생성된 것을 확인한다.
+ scan success rate이 올라오기까지는 약 10분 정도 기다린 것 같다.


- 이번에는 deployment 생성을 시도하면서 validation이 되는지를 알아보자
우선 적용할 clusterpolicy 를 생성한다
# 모니터링
watch -d kubectl get pod -n kyverno
# ClusterPolicy 적용
kubectl create -f- << EOF
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-labels
spec:
validationFailureAction: Enforce
rules:
- name: check-team
match:
any:
- resources:
kinds:
- Pod
validate:
message: "label 'team' is required"
pattern:
metadata:
labels:
team: "?*"
EOF
# 확인
kubectl get validatingwebhookconfigurations
kubectl get ClusterPolicy
- 그리고 바로 deployment 생성을 시도하면 실패한다: ClusterPolicy.spec.validate.team 에 따르면 label로 team 이 명시되어 있지 않으면 실패하기 때문이다.
# 실패
kubectl create deployment nginx --image=nginx
# 성공
kubectl run nginx --image nginx --labels team=backend
# 확인
kubectl get policyreport -o wide
실행 후 확인해 보면, nginx Pod 만 Policyreport를 패스하는 것을 알 수 있다.

- rules에 Mutation을 추가할 수도 있다. Mutation과 validation의 차이는 달라도 같은 Field가 있으면 통과 되는 것이므로, team: bravo가 아니라 team: alpha 로도 생성 가능하다. 만약 label이 명시되어 있지 않으면 default로 하나 더 생성된다
kubectl create -f- << EOF
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: add-labels
spec:
rules:
- name: add-team
match:
any:
- resources:
kinds:
- Pod
mutate:
patchStrategicMerge:
metadata:
labels:
+(team): bravo
EOF
# 확인
kubectl get mutatingwebhookconfigurations
kubectl get ClusterPolicy
NAME ADMISSION BACKGROUND VALIDATE ACTION READY AGE MESSAGE
add-labels true true Audit True 6m41s Ready
# 파드 생성 후 label 확인
kubectl run redis --image redis
kubectl get pod redis --show-labels
# 파드 생성 후 label 확인 : 바로 위와 차이점은?
kubectl run newredis --image redis -l team=alpha
kubectl get pod newredis --show-labels

잊지 말아요 자원 삭제
eksctl delete cluster --name $CLUSTER_NAME && aws cloudformation delete-stack --stack-name $CLUSTER_NAME
+ 생성된 IAM user 는 웹 콘솔에서 삭제해준다.
이번에는 개념적으로 알아야 할 분량이 많아서 카테고리별로 실습을 맵핑해서 만들었는데 가독성이 어떤지 모르겠다..! 아직도 나 확실히 잘안다! 최고다! 까지는 아니지만 이번 세션을 통해 막막했던 보안에 대해 부담을 덜 수 있었다.
'EKS@CloudNet' 카테고리의 다른 글
[EKS@CloudNet] EKS by Terraform (1) | 2024.04.28 |
---|---|
[EKS@CloudNet] EKS CI/CD (0) | 2024.04.21 |
[EKS@CloudNet] EKS Autoscaling: Karpenter 외 (0) | 2024.04.07 |
[EKS@CloudNet] EKS Observability: Prometheus & Grafana (0) | 2024.03.31 |
[EKS@CloudNet] EKS Storage & Nodegroup (1) | 2024.03.23 |