본문 바로가기
IT/Cloud

[Cloud] 쿠버네티스 보안: RBAC 권한 오남용 방지 및 클라우드 계정 탈취 대응 전략

by 수누다 2026. 4. 14.

2026년, 쿠버네티스 보안은 선택이 아닌 생존의 문제

솔직히 말씀드릴게요. 저도 몇 년 전까지만 해도 "우리 클러스터는 내부망이니까 괜찮겠지"라는 안일한 생각을 했었거든요. 근데 실제로 제가 운영하던 홈랩 클러스터에서 RBAC(Role-Based Access Control, 역할 기반 접근 제어) 설정을 제대로 안 해놨다가 테스트 계정이 클러스터 전체에 접근할 수 있었던 걸 뒤늦게 발견했을 때... 그 찝찝함은 아직도 생생하네요.

2026년 현재, 쿠버네티스 보안 위협은 양적으로도, 질적으로도 완전히 다른 레벨이 됐습니다. 단순히 컨테이너 하나 탈취하는 게 아니라, 클러스터 전체를 장악하고 클라우드 계정까지 탈취하는 공격 체인이 일상화됐거든요. 이 글에서는 제가 직접 구축하고 운영하면서 겪은 13년 인프라 경험을 바탕으로, Kubernetes 권한 관리부터 클라우드 계정 탈취 대응까지 실전 위주로 풀어드리겠습니다.

2026년 쿠버네티스 보안 위협 공격 체인 아키텍처 다이어그램 - 컨테이너 탈취부터 클라우드 계정 탈취까지

▲ 2026년 쿠버네티스 보안 위협의 전형적인 공격 체인 — 컨테이너 탈출부터 클라우드 계정 탈취까지 이어지는 경로를 보여주는 아키텍처 다이어그램

쿠버네티스 권한 오남용, 왜 이렇게 위험한가요?

쉽게 말해서, Kubernetes는 기본적으로 "모든 걸 허용하는 방향"으로 설계된 부분이 있어요. 편의성을 위해서죠. 근데 이게 보안 관점에서는 양날의 검이거든요.

제가 자주 보는 문제 패턴이 몇 가지 있는데요:

  • wildcard 권한 남발: verbs: ["*"], resources: ["*"]를 아무 생각 없이 복붙하는 경우
  • ServiceAccount(서비스 어카운트) 토큰 노출: Pod 내부에서 자동 마운트된 토큰이 공격자에게 악용되는 케이스
  • cluster-admin 남용: "일단 admin 줘버리면 편하니까"라는 논리로 과도한 권한 부여
  • 네임스페이스 격리 미흡: 개발/운영 환경이 같은 클러스터에서 제대로 격리되지 않는 상황

특히 2025~2026년에 급증한 공격 패턴이 있어요. 취약한 웹앱 컨테이너를 통해 Pod에 접근 → ServiceAccount 토큰 탈취 → Kubernetes API 서버 접근 → 클라우드 IAM(Identity and Access Management, 신원 및 접근 관리) 자격증명 탈취로 이어지는 체인 공격이에요. 이게 한 번 뚫리면 정말 답이 없습니다.

RBAC 설정 제대로 하기: 최소 권한 원칙 실전 적용

RBAC(역할 기반 접근 제어)는 쿠버네티스 보안의 핵심이에요. 저도 처음엔 "이거 그냥 Role 하나 만들어서 쓰면 되는 거 아냐?" 했었는데, 실제로 프로덕션 환경에서 운영해보니 생각보다 훨씬 세밀하게 설계해야 하더라고요.

나쁜 예시 vs 좋은 예시

구분 나쁜 예시 (위험) 좋은 예시 (권장)
권한 범위 resources: ["*"] resources: ["pods", "services"]
동작 범위 verbs: ["*"] verbs: ["get", "list", "watch"]
바인딩 대상 ClusterRoleBinding으로 전체 적용 RoleBinding으로 네임스페이스 한정
ServiceAccount default 계정 그대로 사용 용도별 전용 ServiceAccount 생성

실제로 제가 운영 환경에서 적용하는 최소 권한 Role 설정 예시를 보여드릴게요:

# 읽기 전용 Role 예시 (특정 네임스페이스 한정)
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: production
  name: pod-reader
rules:
- apiGroups: [""]  # core API group
  resources: ["pods", "pods/log"]
  verbs: ["get", "list", "watch"]
---
# RoleBinding으로 특정 ServiceAccount에만 부여
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: read-pods
  namespace: production
subjects:
- kind: ServiceAccount
  name: monitoring-sa
  namespace: production
roleRef:
  kind: Role
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io

💡 : 기존 클러스터의 Kubernetes 권한 설정이 얼마나 위험한지 빠르게 확인하고 싶다면 kubectl-who-can이라는 도구를 써보세요. "누가 secrets를 읽을 수 있는가?" 같은 질문에 즉시 답해줍니다.

# kubectl-who-can 설치 및 사용
kubectl krew install who-can

# secrets를 get할 수 있는 주체 확인
kubectl who-can get secrets -n production

# 모든 네임스페이스에서 pods를 delete할 수 있는 주체 확인
kubectl who-can delete pods --all-namespaces

처음 이걸 돌려봤을 때 생각보다 너무 많은 계정이 secrets에 접근 가능하다는 걸 보고 식겁했던 기억이 있어요 ㅎㅎ

ServiceAccount 토큰 자동 마운트 비활성화

Kubernetes는 기본적으로 모든 Pod에 ServiceAccount 토큰을 자동으로 마운트해줘요. 근데 대부분의 애플리케이션은 이 토큰이 필요 없거든요. 그냥 공격 표면(attack surface)만 늘어나는 거예요.

# Pod spec에서 자동 마운트 비활성화
apiVersion: v1
kind: Pod
metadata:
  name: my-app
spec:
  automountServiceAccountToken: false  # 이 한 줄이 중요!
  containers:
  - name: my-app
    image: my-app:latest
---
# 또는 ServiceAccount 레벨에서 비활성화
apiVersion: v1
kind: ServiceAccount
metadata:
  name: no-token-sa
  namespace: production
automountServiceAccountToken: false

▲ 최소 권한 원칙을 적용한 RBAC 구성도 — Role, RoleBinding, ServiceAccount의 관계와 네임스페이스 격리 구조를 시각화

클라우드 계정 탈취 방지: IRSA와 Workload Identity 활용

이게 요즘 제일 핫한 주제에요. 클라우드(AWS, GCP, Azure) 환경에서 Kubernetes를 운영할 때, Pod에서 클라우드 리소스에 접근하는 방식이 잘못되면 클라우드 계정 전체가 위험해지거든요.

예전에는 EC2 인스턴스 프로파일에 광범위한 권한을 주고, 클러스터 전체 노드가 그 권한을 쓰는 방식이 많았어요. 근데 이러면 하나의 Pod가 탈취돼도 그 클러스터 노드의 모든 클라우드 권한을 가져갈 수 있게 되는 거죠. 실제로 이런 방식으로 S3 버킷 전체가 털린 사례를 들은 적도 있어요.

AWS IRSA(IAM Roles for Service Accounts) 설정

IRSA는 특정 ServiceAccount에만 특정 AWS IAM Role을 부여하는 방식이에요. Pod 단위로 권한을 분리할 수 있어서 훨씬 안전합니다.

# 1. OIDC Provider 확인 (EKS 기준)
aws eks describe-cluster --name my-cluster --query "cluster.identity.oidc.issuer" --output text

# 2. IAM Role Trust Policy 설정
# trust-policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::ACCOUNT_ID:oidc-provider/OIDC_PROVIDER"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "OIDC_PROVIDER:sub": "system:serviceaccount:production:s3-access-sa",
          "OIDC_PROVIDER:aud": "sts.amazonaws.com"
        }
      }
    }
  ]
}

# 3. ServiceAccount에 어노테이션 추가
apiVersion: v1
kind: ServiceAccount
metadata:
  name: s3-access-sa
  namespace: production
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::ACCOUNT_ID:role/my-s3-role

⚠️ 주의: IRSA 설정할 때 Condition에서 ServiceAccount 네임스페이스와 이름을 정확히 지정해야 해요. 와일드카드 쓰면 다른 네임스페이스의 Pod도 해당 Role을 assume할 수 있어서 위험합니다. 저도 처음에 이걸 실수해서 한참 디버깅했거든요.

GKE Workload Identity 설정

# GKE Workload Identity 바인딩
gcloud iam service-accounts add-iam-policy-binding \
  gsa-name@project-id.iam.gserviceaccount.com \
  --role roles/iam.workloadIdentityUser \
  --member "serviceAccount:project-id.svc.id.goog[namespace/ksa-name]"

# Kubernetes ServiceAccount 어노테이션
kubectl annotate serviceaccount ksa-name \
  --namespace namespace \
  iam.gke.io/gcp-service-account=gsa-name@project-id.iam.gserviceaccount.com

⚠️ 실제 겪은 트러블슈팅: 이런 실수들 조심하세요

제가 직접 겪거나 팀에서 겪은 문제들을 공유할게요. 미리 알면 삽질을 줄일 수 있으니까요.

문제 1: ClusterRole이 의도치 않게 모든 네임스페이스에 적용

ClusterRole + RoleBinding 조합과 ClusterRole + ClusterRoleBinding 조합의 차이를 헷갈려서 의도치 않게 전체 클러스터 권한을 준 적이 있어요.

  • ClusterRole + RoleBinding: 특정 네임스페이스에서만 ClusterRole의 권한 적용
  • ⚠️ ClusterRole + ClusterRoleBinding: 전체 클러스터에 ClusterRole의 권한 적용
# 현재 ClusterRoleBinding 목록 확인 (위험한 것들 체크)
kubectl get clusterrolebindings -o json | \
  jq '.items[] | select(.roleRef.name == "cluster-admin") | \
  {name: .metadata.name, subjects: .subjects}'

문제 2: Secrets를 환경변수로 노출

Kubernetes Secret을 환경변수로 마운트하면 kubectl describe pod/proc/PID/environ을 통해 노출될 수 있어요. Volume 마운트 방식이 훨씬 안전하고, 아예 External Secrets Operator나 HashiCorp Vault를 연동하는 게 베스트예요.

# 나쁜 예시: 환경변수로 Secret 노출
env:
- name: DB_PASSWORD
  valueFrom:
    secretKeyRef:
      name: db-secret
      key: password

# 더 나은 예시: Volume으로 마운트
volumes:
- name: secret-volume
  secret:
    secretName: db-secret
containers:
- name: app
  volumeMounts:
  - name: secret-volume
    mountPath: /etc/secrets
    readOnly: true

문제 3: etcd 암호화 미설정

etcd(Kubernetes의 핵심 데이터 저장소)에 Secrets가 평문으로 저장되는 경우가 꽤 많아요. 특히 직접 kubeadm으로 클러스터를 구성할 때 이 부분을 놓치기 쉽거든요.

# etcd 암호화 설정 확인
kubectl get secrets -n kube-system -o yaml | grep -A5 "encryption"

# EncryptionConfiguration 설정 예시
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
  - secrets
  providers:
  - aescbc:
      keys:
      - name: key1
        secret: BASE64_ENCODED_32BYTE_KEY
  - identity: {}  # 기존 평문 데이터 읽기용 (마이그레이션 후 제거)

Pod Security Standards로 컨테이너 보안 강화

Kubernetes 1.25부터 PodSecurityPolicy가 완전히 제거되고 Pod Security Standards(PSS)가 공식 대안이 됐어요. 저도 이 마이그레이션 때 꽤 고생했는데, 결과적으로는 훨씬 깔끔해졌더라고요.

세 가지 레벨이 있어요:

  • Privileged: 제한 없음 (사실상 쓰면 안 됨)
  • Baseline: 최소한의 제한 (일반 워크로드에 적합)
  • Restricted: 가장 엄격한 제한 (보안 중요 워크로드)
# 네임스페이스에 PSS 레이블 적용
kubectl label namespace production \
  pod-security.kubernetes.io/enforce=restricted \
  pod-security.kubernetes.io/enforce-version=latest \
  pod-security.kubernetes.io/warn=restricted \
  pod-security.kubernetes.io/warn-version=latest

# Restricted 레벨을 만족하는 Pod 스펙 예시
apiVersion: v1
kind: Pod
metadata:
  name: secure-app
  namespace: production
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    seccompProfile:
      type: RuntimeDefault
  containers:
  - name: app
    image: my-app:latest
    securityContext:
      allowPrivilegeEscalation: false
      readOnlyRootFilesystem: true
      capabilities:
        drop: ["ALL"]

💡 처음 restricted 레벨 적용하면 기존 Pod들이 많이 거부될 수 있어요. warn 모드로 먼저 적용해서 어떤 Pod가 문제인지 파악한 다음에 enforce로 전환하는 게 훨씬 스트레스가 덜합니다 ㅎㅎ

네트워크 정책으로 측면 이동 차단

공격자가 하나의 Pod를 탈취했을 때, 클러스터 내부를 자유롭게 돌아다닐 수 없게 막는 게 중요해요. 이걸 측면 이동(Lateral Movement) 차단이라고 하는데, NetworkPolicy가 핵심 수단이에요.

# 기본 deny-all 정책 (먼저 모든 트래픽 차단)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: production
spec:
  podSelector: {}  # 네임스페이스의 모든 Pod에 적용
  policyTypes:
  - Ingress
  - Egress
---
# 필요한 트래픽만 허용
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-frontend-to-backend
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: backend
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: frontend
    ports:
    - protocol: TCP
      port: 8080

⚠️ 중요: NetworkPolicy는 CNI(Container Network Interface) 플러그인이 지원해야 해요. Flannel은 기본적으로 NetworkPolicy를 지원하지 않아요. Calico, Cilium, Weave Net 같은 CNI를 써야 합니다.

Falco 기반 쿠버네티스 보안 이벤트 모니터링 대시보드 - 실시간 위협 탐지 및 RBAC 위반 이벤트 시각화

▲ Falco + Grafana를 연동한 Kubernetes 보안 이벤트 모니터링 대시보드 — 실시간 위협 탐지 및 RBAC 위반 이벤트 시각화 화면

런타임 보안 모니터링: Falco로 이상 행동 탐지

설정을 아무리 잘 해도 100% 완벽할 순 없어요. 그래서 런타임 단계에서의 이상 행동 탐지가 중요한데, Falco가 현재 가장 많이 쓰이는 오픈소스 솔루션이에요.

Falco는 컨테이너 내부에서 일어나는 시스템 콜(system call)을 모니터링해서 의심스러운 행동을 탐지해요. 예를 들어 "웹 서버 컨테이너에서 갑자기 /etc/passwd를 읽으려 한다"거나 "bash 셸이 실행됐다"같은 걸 잡아내는 거죠.

# Helm으로 Falco 설치
helm repo add falcosecurity https://falcosecurity.github.io/charts
helm repo update

helm install falco falcosecurity/falco \
  --namespace falco \
  --create-namespace \
  --set falcosidekick.enabled=true \
  --set falcosidekick.webui.enabled=true

# 커스텀 룰 예시 (ServiceAccount 토큰 파일 접근 탐지)
# /etc/falco/rules.d/custom-rules.yaml
- rule: ServiceAccount Token Read by Unexpected Process
  desc: Detect unexpected process reading ServiceAccount token
  condition: >
    open_read and
    fd.name startswith "/var/run/secrets/kubernetes.io/serviceaccount" and
    not proc.name in (allowed_sa_readers)
  output: >
    ServiceAccount token read by unexpected process
    (user=%user.name proc=%proc.name file=%fd.name
    container=%container.id image=%container.image.repository)
  priority: WARNING
  tags: [kubernetes, token, mitre_credential_access]

전체 보안 체크리스트 및 마무리

지금까지 다룬 내용들을 체크리스트로 정리해봤어요. 인프라 엔지니어로서 제가 클러스터 보안 점검할 때 항상 확인하는 항목들이에요.

2026년 쿠버네티스 보안 체크리스트 인포그래픽 - RBAC, 클라우드 계정 보호, 컨테이너 보안, 네트워크 정책 영역별 핵심 항목

▲ 2026년 쿠버네티스 보안 체크리스트 요약 인포그래픽 — RBAC, 네트워크 정책, 런타임 보안, 클라우드 계정 보호 영역별 핵심 항목 정리

✅ RBAC 및 권한 관리

  1. 모든 ServiceAccount에 최소 권한 원칙 적용 확인
  2. cluster-admin ClusterRoleBinding 최소화 (사람 계정에 부여 금지)
  3. 불필요한 Pod의 automountServiceAccountToken: false 설정
  4. 정기적인 RBAC 감사 (kubectl-who-can 활용)
  5. 서비스 계정 토큰 만료 시간 설정 (Bound Service Account Tokens)

✅ 클라우드 계정 보안

  1. AWS: IRSA, GCP: Workload Identity, Azure: AAD Pod Identity 적용
  2. 노드 IAM Role에 최소 권한만 부여
  3. IMDS(Instance Metadata Service) v2 강제 적용 (AWS 기준)
  4. 클라우드 자격증명을 하드코딩하거나 환경변수로 노출 금지

✅ 컨테이너 및 런타임 보안

  1. Pod Security Standards Restricted 레벨 적용
  2. root로 실행되는 컨테이너 금지
  3. 읽기 전용 루트 파일시스템 설정
  4. Falco 또는 동급 런타임 보안 도구 운영
  5. 컨테이너 이미지 취약점 스캔 파이프라인 구축 (Trivy, Grype 등)

✅ 네트워크 보안

  1. 기본 deny-all NetworkPolicy 적용 후 필요한 것만 허용
  2. etcd 접근을 API 서버만으로 제한
  3. API 서버 감사 로그(Audit Log) 활성화

13년 동안 인프라를 운영하면서 느끼는 건, 보안은 한 번에 완성되는 게 아니라 지속적으로 개선하는 과정이라는 거예요. 오늘 소개한 내용을 한꺼번에 다 적용하려고 하면 부담스러울 수 있어요. RBAC 정리부터 시작해서 하나씩 차근차근 적용해 나가는 걸 추천드려요.

다음 글에서는 Falco + Slack 연동으로 실시간 보안 알림 시스템 구축하는 방법을 다룰 예정이에요. 그리고 OPA(Open Policy Agent) Gatekeeper를 이용한 정책 자동화도 곧 다룰 계획이니 기대해 주세요!

혹시 클러스터 운영하시면서 보안 관련해서 막히는 부분 있으시면 댓글로 남겨주세요. 제 경험 안에서 최대한 도움드리겠습니다 😊


자주 묻는 질문 (FAQ)

Q. RBAC 설정 없이 운영 중인데 지금 당장 바꾸면 서비스 장애가 나지 않을까요?
A. 네, 실제로 기존 서비스에 갑자기 restrict 정책을 적용하면 장애가 날 수 있어요. audit 모드로 먼저 적용해서 어떤 영향이 있는지 확인한 다음, 단계적으로 적용하는 걸 강력히 권장합니다.
Q. 소규모 팀인데 Falco까지 운영할 여력이 없어요. 최소한 무엇을 해야 하나요?
A. 최우선은 RBAC 최소 권한 적용과 ServiceAccount 토큰 자동 마운트 비활성화예요. 그 다음이 클라우드 IRSA/Workload Identity 적용이고요. Falco는 여력이 생기면 추가하는 방식으로 접근하세요.
Q. kubectl audit log는 어디서 확인하나요?
A. 관리형 서비스(EKS, GKE, AKS)는 각 클라우드의 로그 서비스(CloudWatch, Cloud Logging, Azure Monitor)와 연동됩니다. 셀프 호스팅이라면 /etc/kubernetes/audit-policy.yaml을 설정해야 해요.