PKOS2) Kubernetes 보안 시스템 구축

CloudNet@팀에서 진행하는 쿠버네티스 실무 실습 스터디 참가글입니다.

24단계 실습으로 정복하는 쿠버네티스 도서 내용을 기반으로 진행됩니다.


1. Kubernetes 보안 시스템 구축

쿠버네티스 환경을 생성하고, 필요한 패키지들의 배포 및 연동을 확인하면 구축이 완료된다. 이후 운영시 고려해야 하는 부분은 보안이다. 보안 설정을 직접 하는 것은 쉽지 않지만, 쿠버네티스 생태계에서는 다양한 보안 도구가 있어 보다 쉽게 관련 작업을 수행할 수 있다.

실습 환경 배포

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

# CloudFormation 스택 배포
aws cloudformation deploy –template-file kops-oneclick-f1.yaml –stack-name mykops –parameter-overrides KeyName=nasirk17 SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32  MyIamUserAccessKeyID=$AWS_ACCESS_KEY_ID MyIamUserSecretAccessKey=$AWS_SECRET_ACCESS_KEY ClusterBaseName=$KOPS_CLUSTER_NAME S3StateStore=$KOPS_STATE_STORE MasterNodeInstanceType=t3.xlarge WorkerNodeInstanceType=t3.xlarge –region ap-northeast-2

# CloudFormation 스택 배포 완료 후 kOps EC2 IP 출력
aws cloudformation describe-stacks –stack-name mykops –query ‘Stacks[*].Outputs[0].OutputValue’ –output text

# 13분 후 작업 SSH 접속
ssh -i ~/.ssh/nasirk17.pem ec2-user@$(aws cloudformation describe-stacks –stack-name mykops –query ‘Stacks[*].Outputs[0].OutputValue’ –output text)

# EC2 instance profiles 에 IAM Policy 추가(attach) : 처음 입력 시 적용이 잘 안될 경우 다시 한번 더 입력 하자! – IAM Role에서 새로고침 먼저 확인!
aws iam attach-role-policy –policy-arn arn:aws:iam::$ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy –role-name masters.$KOPS_CLUSTER_NAME
aws iam attach-role-policy –policy-arn arn:aws:iam::$ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy –role-name nodes.$KOPS_CLUSTER_NAME

1.1. k8s 보안 도구 활용

클러스터 내부의 모든 파드는, 기본적으로 클러스터 내 다른 모든 파드와 통신이 가능하다. 따라서 특정 파드가 외부 공격자에게 탈취되면, 같은 노드의 파드 뿐만 아니라 통신이 가능한 클러스터 전체의 파드에게까지 영향을 줄 수 있다.

쿠버네티스 기본 설정의 대표적인 취약점

  • 컨테이너
    • 호스트 노드와 커널 공유 > 부적절한 컨테이너의 root 권한은 호스트 노드에 영향을 끼칠 수 있음
  • 클러스터 내 파드
    • 노드 간 이동이 자유로워, 전체 노드로 문제가 확산될 수 있음
  • 멀티 클러스터 환경
    • 온프레미스 k8s, 매니지드 클러스터 등에서 동일한 보안 구성 유지의 어려움

쿠버네티스 클러스터의 보안 적용 대상 (4C) (링크)

  • Cloud / Cluster / Container / Code
  • 각 계층마다 적절한 보안 설정의 적용 필요

EC2 IAM Role & 메타데이터 탈취 테스트

해당 파트에서는, 클러스터의 설정을 수정하여 파드가 탈취되면 어떤 것을 할 수 있는지 확인해 볼것이다.

워커 노드 1대 EC2 메타데이터(IMDSv2) 보안 제거

# 설정 업데이트

kops edit ig nodes-ap-northeast-2a


# 아래 3줄 제거
spec:
instanceMetadata:
httpPutResponseHopLimit: 1
httpTokens: required

# 업데이트 적용 : 노드1대 롤링업데이트

kops update cluster –yes && echo && sleep 3 && kops rolling-update cluster –yes

파드에서 EC2 메타데이터 사용 가능 확인

메타데이터 확인

# netshoot-pod 생성

cat <<EOF | kubectl create -f –

apiVersion: apps/v1

kind: Deployment

metadata:

  name: netshoot-pod

spec:

  replicas: 2

  selector:

    matchLabels:

      app: netshoot-pod

  template:

    metadata:

      labels:

        app: netshoot-pod

    spec:

      containers:

      – name: netshoot-pod

        image: nicolaka/netshoot

        command: [“tail”]

        args: [“-f”, “/dev/null”]

      terminationGracePeriodSeconds: 0

EOF

# 파드 이름 변수 지정

PODNAME1=$(kubectl get pod -l app=netshoot-pod -o jsonpath={.items[0].metadata.name})
PODNAME2=$(kubectl get pod -l app=netshoot-pod -o jsonpath={.items[1].metadata.name})

# EC2 메타데이터 정보 확인

kubectl exec -it $PODNAME1 — curl 169.254.169.254 ;echo
kubectl exec -it $PODNAME2 — curl 169.254.169.254 ;echo

# 파드1에서 EC2 메타데이터 정보 확인

kubectl exec -it $PODNAME1 — curl 169.254.169.254/latest ;echo
kubectl exec -it $PODNAME1 — curl 169.254.169.254/latest/meta-data/iam/security-credentials/ ;echo
kubectl exec -it $PODNAME1 — curl 169.254.169.254/latest/meta-data/iam/security-credentials/nodes.$KOPS_CLUSTER_NAME | jq

# 파드2에서 EC2 메타데이터 정보 확인

kubectl exec -it $PODNAME2 — curl 169.254.169.254/latest ;echo
kubectl exec -it $PODNAME2 — curl 169.254.169.254/latest/meta-data/iam/security-credentials/ ;echo
kubectl exec -it $PODNAME2 — curl 169.254.169.254/latest/meta-data/iam/security-credentials/nodes.$KOPS_CLUSTER_NAME | jq

# 메타데이터 메모해두기

{
“Code”: “Success”,
“LastUpdated”: “2023-04-08T09:58:09Z”,
“Type”: “AWS-HMAC”,
“AccessKeyId”: “ASIA~”,
“SecretAccessKey”: “6fCWh~”,
“Token”: “IQoJb3JpZ2luX2VjE~”,
“Expiration”: “2023-04-08T16:32:53Z”
}

파드(컨테이너) 탈취 후 EC2 메타데이터의 IAM Role 토큰 정보를 활용해 python boto3를 통해 SDK로 AWS 서비스 강제 사용

  • boto3 > 파이썬에서 아마존 웹 서비스(AWS)와 상호작용할 수 있는 SDK(Software Development Kit

탈취한 토큰을 사용해 AWS서비스 조작하기

# boto3 사용을 위한 파드 생성

cat <<EOF | kubectl create -f –

apiVersion: apps/v1

kind: Deployment

metadata:

  name: boto3-pod

spec:

  replicas: 2

  selector:

    matchLabels:

      app: boto3

  template:

    metadata:

      labels:

        app: boto3

    spec:

      containers:

      – name: boto3

        image: jpbarto/boto3

        command: [“tail”]

        args: [“-f”, “/dev/null”]

      terminationGracePeriodSeconds: 0

EOF

# 파드 이름 변수 지정

PODNAME1=$(kubectl get pod -l app=boto3 -o jsonpath={.items[0].metadata.name})

PODNAME2=$(kubectl get pod -l app=boto3 -o jsonpath={.items[1].metadata.name})

# 파드1에서 boto3 사용

kubectl exec -it $PODNAME1 — sh

————

cat <<EOF> ec2.py

import boto3

ec2 = boto3.client(‘ec2’, region_name = ‘ap-northeast-2’)

response = ec2.describe_instances()

print(response)

EOF

python ec2.py  # aws ec2 describe-vpcs

exit

————

# 파드2에서 boto3 사용

kubectl exec -it $PODNAME2 — sh

————

cat <<EOF> ec2.py

import boto3

ec2 = boto3.client(‘ec2’, region_name = ‘ap-northeast-2’)

response = ec2.describe_instances()

print(response)

EOF

python ec2.py  # aws ec2 describe-vpcs

exit

————

# 실습 완료 후 삭제

kubectl delete deploy boto3-pod

워커 노드 중 하나는 보안 설정이 해제되어있고, 하나는 설정되어있다.

따라서 한대는 조회가 되지만, 하나는 되지 않는다.

이에 대응 방안으로는, EC2 메타데이터 접속 제한 or AWS IRSA 사용이 있다

AWS IRSA – IAM Roles for Service Accounts

파드의 별도의 권한설정이 되어있지 않으면, 호스트 워커노드의 권한을 공유한다. 그러나 IRSA를 사용하여, 쿠버네티스의 Service account 리소스를 사용하여 개별 파드의 권한을 IAM Role과 연계하여 사용할 수 있다.

EKS에서 쿠버네티스 포드의 IAM 권한 제어하기: Pod Identity Webhook 중,

kubescape

미국 국가 안보국(National Security Agency)과 사이버 및 인프라 보안국(Cybersecurity and Infrastructure Security Agency )는 주기적으로 (현재시점 최근은 2022년 3월 15일) 쿠버네티스 클러스터의 보안 가이드를 제공한다.

kubescape는 위의 NSA의 보안 권고 사항 대비 컴플라이언스 오픈 소스 프로젝트이다.

NSA-CISA뿐만 아니라, MITRE ATT&CK®, CIS Benchmark의 취약점 또한 체크할 수 있다. (kubescape github)

구성 및 확인

# 설치

curl -s https://raw.githubusercontent.com/kubescape/kubescape/master/install.sh | /bin/bash

# Download all artifacts and save them in the default path (~/.kubescape)

kubescape download artifacts
tree ~/.kubescape/
cat ~/.kubescape/attack-tracks.json | jq

# 제공하는 보안 프레임워크 확인

kubescape list frameworks –format json | jq ‘.[]’

# “AllControls”
# “ArmoBest”
# “DevOpsBest”
# “MITRE”
# “NSA”
# “cis-eks-t1.2.0”
# “cis-v1.23-t1.0.1”

# 제공하는 통제 정책 확인

kubescape list controls

# 모니터링

watch kubectl get pod -A

# 클러스터 스캔

# Deploy Kubescape host-sensor daemonset in the scanned cluster. Deleting it right after we collecting the data.

# Required to collect valuable data from cluster nodes for certain controls.

# Yaml file: https://github.com/kubescape/kubescape/blob/master/core/pkg/hostsensorutils/hostsensor.yaml

kubescape scan –help

kubescape scan –enable-host-scan –verbose

# Controls: 65 (Failed: 28, Passed: 29, Action Required: 8)
# Failed Resources by Severity: Critical — 0, High — 32, Medium — 110, Low — 35

스캔 결과는 아래와 같이 치명도 별 표로 정리되어 볼 수 있다.

스캔을 통해 보안상 업데이트가 필요한 부분들은 식별되었지만, 실제로 해당 내용을 반영하는 것은 어렵다. 수정 권고사항이 명확하지 않거나, 서비스 성격에 따라 조치 이후 동작하기 않음 > 예외 처리 등이 수행될 수 있기 때문이다.

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

SecurityContext는 파드 또는 컨테이너의 보안 설정을 통제하는 기법 중 하나이다. 파드 / 컨테이너 각 수준에서, 실행되는 프로세스에 대한 보안 설정을 정의하고, 제한할 수 있다.

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

컨테이너 보안 컨텍스트 확인 (kube-system 파드 내 컨테이너 대상)

kubectl get pod -n kube-system -o jsonpath={.items[*].spec.containers[*].securityContext} | jq

# {
#   “allowPrivilegeEscalation”: false,
#   “readOnlyRootFilesystem”: true,
#   “runAsNonRoot”: true
# }
# {
#   “capabilities”: {
#     “add”: [
#       “NET_ADMIN”,
#       “NET_RAW”
#     ]
#   }
# }
# …

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

# 파드 상세 정보 확인

kubectl get pod rootfile-readonly -o jsonpath={.spec.containers[0].securityContext} | jq

# {
# “readOnlyRootFilesystem”: true
# }

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

Polaris

위에서 살펴본 kubescape를 사용해 보안 취약점을 스캔할 수 있지만, 구체적인 조치 방법이 제시되지않아 추가적인 작업이 필요하다.

그에 비해, 폴라리스는 실행중인 YAML 파일을 분석해서 부족한 부분을 점검할 수 있다.

폴라리스의 실행 모드

  • Dashboard: Validate Kubernetes resources against policy-as-code. (실습)
  • Admission Controller: Automatically reject or modify workloads that don’t adhere to your organization’s policies.
  • CLI: Incorporate policy-as-code into the CI/CD process to test local YAML files.
Polaris Architecture

설치 및 구성 확인

# 설치

kubectl create ns polaris

#

cat <<EOT > polaris-values.yaml

dashboard:

  replicas: 1

  service:

    type: LoadBalancer

EOT

# 배포

helm repo add fairwinds-stable https://charts.fairwinds.com/stable
helm install polaris fairwinds-stable/polaris –namespace polaris –version 5.7.2 -f polaris-values.yaml

# CLB에 ExternanDNS 로 도메인 연결

kubectl annotate service polaris-dashboard “external-dns.alpha.kubernetes.io/hostname=polaris.$KOPS_CLUSTER_NAME” -n polaris

# 웹 접속 주소 확인 및 접속

echo -e “Polaris Web URL = http://polaris.$KOPS_CLUSTER_NAME”

Polaris Dashboard

  • Score : 모범 사례 대비 전체 클러스터 구성 내역 점수, 권고 사항 만족이 많을 수록 점수 상승
  • Passing/Warning/Dangerous Checks : 위험 등급 분류, 위험 단계 취약점은 조치를 권고
  • 측정 범위 : Efficiency, Reliability, Security
    • Security 보안성 : 보안 관련 구성 확인
    • Efficiency 효율성 : CPU,Memory 리소스 사용 관련
    • Reliability 신뢰성 : 안정성 구성 여부 확인
  • 검사 항목 상세

보안 취약점 점검

  • polaris 웹 접속 → 중간 Filter by Namespace 에서 default 네임스페이스 클릭 후 Apply
  • Namespaces : default 아래 netshoot-pod 부분 ⇒ 아래 항목의 ? 클릭으로 상세 정보 확인

image tag 조치

# 기존 netshoot-pod 삭제

kubectl delete deploy netshoot-pod

# netshoot-pod 1대 생성

cat <<EOF | kubectl create -f –

apiVersion: apps/v1

kind: Deployment

metadata:

  name: netshoot-pod

spec:

  replicas: 1

  selector:

    matchLabels:

      app: netshoot-pod

  template:

    metadata:

      labels:

        app: netshoot-pod

    spec:

      containers:

      – name: netshoot-pod

        image: nicolaka/netshoot:v0.9

        command: [“tail”]

        args: [“-f”, “/dev/null”]

      terminationGracePeriodSeconds: 0

EOF

대시보드 재확인: image tag는 해결, 상단에 replica가 1개인 Warning 확인

> kubectl scale deployment netshoot-pod –replicas 2 로 해결

보안 모범사례 적용 후 재점검

polaris pod의 모범사례 조회

#
kubectl krew install neat
kubectl get deploy/polaris-dashboard -n polaris -o yaml | kubectl neat > polaris-pod.yaml
cat polaris-pod.yaml | yh

...
spec:
  template:
    spec:
      containers:
        imagePullPolicy: Always   # 이미지를 항상 리포지토리에서 가져오게 설정
        resources:                # 리소스 자원 사용 보장 및 제한  
          limits:
            cpu: 150m
            memory: 512Mi
          requests:
            cpu: 100m
            memory: 128Mi
        securityContext:          # 컨테이너, 파드의 권한 및 접근 제어
          allowPrivilegeEscalation: false   
          # 컨테이너의 하위 프로세스가 상위 프로세스보다 많은 권한을 얻을 수 없게 설정
          capabilities:                     
          # 호스트 커널 레벨 수정이 필요 시 root 권한으로 전체 커널을 수정하지 않고        
          # 특정 커널 권한만 부여 후 사용
            drop:
            - ALL
          privileged: false                 
          # true인 경우 호스트의 모든 장치에 접근 권한 가짐, 컨테이너의 root권한
          # 이더라도 namespace/cgroup으로 격리되어 호스트의 다른 장치 접근 불가
          readOnlyRootFilesystem: true      # 컨테이너의 root 파일시스템에 읽기 전용
          runAsNonRoot: true                # root 권한으로 컨테이너를 실행하지 않음
...

netshoot-pod 에 보안 모범 사례 적용

# 삭제
kubectl delete deploy netshoot-pod

# netshoot-pod 생성
cat <<EOF | kubectl create -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: netshoot-pod
spec:
  replicas: 2
  selector:
    matchLabels:
      app: netshoot-pod
  template:
    metadata:
      labels:
        app: netshoot-pod
    spec:
      containers:
      - name: netshoot-pod
        image: nicolaka/netshoot:v0.9
        command: ["tail"]
        args: ["-f", "/dev/null"]
        imagePullPolicy: Always
        resources:
          limits:
            cpu: 150m
            memory: 512Mi
          requests:
            cpu: 100m
            memory: 128Mi
        securityContext:
          allowPrivilegeEscalation: false
          capabilities:
            drop:
            - ALL
          privileged: false
          readOnlyRootFilesystem: true
          runAsNonRoot: true
      terminationGracePeriodSeconds: 0
EOF

#runAsNonRoot: true > root 비활성화의 경우, netshoot-pod 이미지에 root가 포함되어있어 제외하신듯 함 (해당 옵션 적용시 에러)


1.2. RBAC

  • RBAC(Role Based Access Control, 역할 기반 접근 제어)
    • 인증(Authentication): 로그인 등을 통해 사용자가 누구인지 확인
    • 인가(Authorization): 확인된 사용자가 어떤 권한을 갖는지 확인
  • 지금까지는 일종의 모든 권한을 갖는 슈퍼 어드민, 관리자 계정 사용
  • 특정 권한을 제한하는 사용자 계정 사용 가능
    • 특정 네임스페이스에서만 실행 제한
    • get/edit/create등 동작 제한
  • RBAC의 주요 구성 요소
    • Authn. Resources (Authentication)
      • ServiceAccount
      • Token
      • CertificateSigningRequest(CSR)
    • Authz. Resource (Authorization)
      • Role/RoleBinding
      • ClusterRole/ClusterRoleBinding
    • User
    • Kubernetes Context
Dynatrace, RBAC Example

Role/RoleBinding, ClusterRole/ClusterRoleBinding

쿠버네티스 환경에서, 사용자의 권한 관련 설정은 역할(Role 또는 ClusterRole)로 관리된다.

– 사용자 권한 예시: 특정 Pod/Namespace 조회,생성 등

  • Role: 특정 네임스페이스에 속하는 Pod, Service 등
  • ClusterRole: 네임스페이스에 속하지 않는 Node, PV, 전체 NS(-A) 등

생성된 Role의 할당은, RoleBinding 이라는 별도 리소스로 분리

> Role을 관리자가 생성하면, 사용자가 RoleBinding으로 끌어갈 수 있어 편의성 증가

Using RBAC Authorization의 예시

Role/RoleBinding example

# Role example

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: default
  name: pod-reader
rules:
- apiGroups: [""] # "" indicates the core API group
  resources: ["pods"]
  verbs: ["get", "watch", "list"]

# RoleBinding example

apiVersion: rbac.authorization.k8s.io/v1
# This role binding allows "jane" to read pods in the "default" namespace.
# You need to already have a Role named "pod-reader" in that namespace.
kind: RoleBinding
metadata:
  name: read-pods
  namespace: default
subjects:
# You can specify more than one "subject"
- kind: User
  name: jane # "name" is case sensitive
  apiGroup: rbac.authorization.k8s.io
roleRef:
  # "roleRef" specifies the binding to a Role / ClusterRole
  kind: Role #this must be Role or ClusterRole
  name: pod-reader # this must match the name of the Role or ClusterRole you wish to bind to
  apiGroup: rbac.authorization.k8s.io

CR/CRB example

# ClusterRole example

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  # "namespace" omitted since ClusterRoles are not namespaced
  name: secret-reader
rules:
- apiGroups: [""]
  #
  # at the HTTP level, the name of the resource for accessing Secret
  # objects is "secrets"
  resources: ["secrets"]
  verbs: ["get", "watch", "list"]

# ClusterRoleBinding example

apiVersion: rbac.authorization.k8s.io/v1
# This cluster role binding allows anyone in the "manager" group to read secrets in any namespace.
kind: ClusterRoleBinding
metadata:
  name: read-secrets-global
subjects:
- kind: Group
  name: manager # Name is case sensitive
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: secret-reader
  apiGroup: rbac.authorization.k8s.io

ServiceAccount, User, kubeconfig

ServiceAccount는 사용자가 k8s API 서버 이용을 위한 인증 과정 처리 역할.

  • SA 생성시 해당 SA의 토큰 생성.
  • kubeconfig의 사용자 토큰 정보에 SA의 토큰을 등록하여 연결

ServiceAccount example

# SA example

apiVersion: v1
kind: ServiceAccount
metadata:
  name: reader-sa

# Role – RoleBinding – ServiceAccount example

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: read-pods
  namespace: default
roleRef:
  kind: Role
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io
subjects:
- kind: ServiceAccount
  name: reader-sa
  apiGroup: rbac.authorization.k8s.io

RBAC 실습 Scenario

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

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

파드 기동 시 서비스 어카운트 한 개가 할당되며, 서비스 어카운트 기반 인증/인가를 함, 미지정 시 기본 서비스 어카운트가 할당

create ns, sa

# 네임스페이스(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 | yh

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

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

생성한 서비스어카운트가 지정된 파드를 배포하고, 특정 행동(create,list)를 시도

> 권한이 없기 때문에 불가능!

# 각각 네임스페이스에 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.24.10
    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.24.10
    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

# 권한 테스트 결과, 권한이 없어서 명령어 사용 불가
# Error from server (Forbidden): pods is forbidden: User "system:serviceaccount:dev-team:dev-k8s" cannot list/create resource "pods" in API group "" in the namespace "dev-team"
# command terminated with exit code 1
# Error from server (Forbidden): pods is forbidden: User "system:serviceaccount:infra-team:infra-k8s" cannot list/create resource "pods" in API group "" in the namespace "infra-team"
# command terminated with exit code 1

# (옵션) 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(변경감시)

# 각각 네임스페이스내의 모든 권한에 대한 롤 생성
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
# ...
# 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
...
Role:
  Kind:  Role
  Name:  role-dev-team
Subjects:
  Kind            Name     Namespace
  ----            ----     ---------
  ServiceAccount  dev-k8s  dev-team

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

다시 위의 권한 테스트를 수행하면, 가능한 조작이 *으로 되어있어 모든 조작이 가능하다.

다만 조작의 범위가 해당 네임스페이스로 한정되어있기 때문에, 다른 ns / 전체 ns / ns에 한정되지 않은 리소스에 대한 조작은 불가능하다

# 각각 파드로 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 nodes

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
k2 (infra-team)기준 권한 테스트 결과


2. 도전과제

2.1. 도전과제2) Armo 웹 사용

kubescape를 제작하고 관리하는 Armosec은 별도의 웹 서비스를 통해 스캔 내용을 관리하고, 대응하는데 도움을 준다.

해당 홈페이지에 접속하여, 우측 상단의 Start Free를 눌러 회원가입을 한다.

처음 대시보드에 접속하면, 아래와 같이 클러스터 연동을 위한 코드스니펫?을 제공한다.

Helm을 사용하여 연동에 필요한 리소스가 배포되고, 실행 후 2분 정도 뒤에 대시보드에서 클러스터 분석 결과를 조회할 수 있다

좌측 메뉴바를 살펴보면, Dashboard 외에도 Compliance, Vulnerablilites, RBAC Visualizer등의 기능이 제공된다.

그 중에서 Compliance와 Vulnerablilites 간 어떤 차이가 있는건지 궁금했는데,

  • Compliance > 법/규제적인 측면의 개선 사항
  • Vulnerablilites > 기술/테크적인 측면의 개선 사항

로 이해하였다.

Compliance vs Vunerability (ChatGPT)

Compliance 화면에서는 교재에서 언급되었던 바와 같이 MITRE, NSA등 기관별 개선 사항이 제시된다.

Compliance board

지적사항 별로 fix 버튼을 눌러 타고들어가면 아래의 방식으로 해결할 수 있다.

  1. manifest의 몇번째 줄을 어떻게 수정해야하는지, (?)버튼 > 근거는 무엇인지
  2. 수정을 하지 못하면, ignore로 경고 보지않기

Vulnerabilities 화면에서는 보고된 취약점과 해당하는 리소스를 표로 보여준다.

Vulnerabilities board

조치가 필요한 리소스를 클릭하면, 보고된 취약점에 대한 설명과 위험도, 연관된 nist의 CVE Report 또는 GitHub Advisory Database/ Amazon Linux Security Center로의 링크를 제공해준다.

RBAC Visualizer는 Datadog이나 Grafana처럼, 클러스터 내 RBAC에 관한 시각화와 쿼리등을 지원한다. RBAC에 대한 시각화는 생각도 못했었는데 괜찮은 기능인듯 하다. 어떻게 써먹을 수 있을지는 당장 떠오르진 않지만..


3. Outro

  • 할까말까 고민될때는 하자

이로써 약 5주차의 스터디가 종료되었다.

신청하였을 시점(1월)과 다르게 스터디하는 시점(3월)에는 업무가 조금 바빠진 감이 있어서, 끝까지 완주할 수 있을까 고민을 했었는데 역시 참가하는게 맞는 선택인듯 하다.

교재/강의로 공부하는것은 혼자서도 할 수 있다. 그러면 ‘왜 굳이 스터디를 해야하는가?’는 생각을 했었는데, 나에게는 열심히 진행해주시는 / 참여해주시는 다른 훌륭하신 분들을 보고 자극받는게 큰 이유인듯 하다.

첫번째 테라폼 스터디에서는 수동적으로 주어진 과제만 제출했다면 두번째 스터디에서는 조금 더 능동적으로 교재랑 같이 병행해서 내용을 정리해보았다. 세번째 스터디(에 참가할 수 있다면) 좀 더 추가적인것들을 많이 찾아보고 공부하고 정리해보고 싶다. 끝!

Leave a Reply