Notice
Recent Posts
Recent Comments
Link
«   2026/04   »
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
Archives
Today
Total
관리 메뉴

근묵자흑

Kubernetes Pattern: Access Control 본문

k8s/kubernetes-pattern

Kubernetes Pattern: Access Control

Luuuuu 2026. 1. 24. 19:19

 

2022년 보안 연구에서 약 100만 개의 Kubernetes 인스턴스가 잘못된 설정으로 인해 인터넷에 노출되어 있었다는 조사 결과가 있습니다. 클라우드 인프라와 컨테이너화에 대한 의존도가 높아지면서 Access Control 설정의 중요성이 커지고 있습니다.

이 글에서는 Kubernetes의 Access Control 패턴을 다룹니다. 기본적인 인증/인가 개념부터 API Server의 내부 로직, etcd 저장 방식 등을 다룹니다.

문제 정의

보안의 핵심에는 두 가지 개념이 있습니다.

인증(Authentication) 은 요청의 주체가 누구인지 식별하고, 승인되지 않은 행위자의 접근을 방지합니다.

인가(Authorization) 는 리소스에 대해 어떤 작업이 허용되는지 권한을 결정합니다.

잘못 구성된 접근 제어는 권한 상승과 배포 실패로 이어질 수 있습니다. 높은 권한을 가진 배포는 다른 배포의 설정과 리소스에 접근하거나 수정할 수 있어, 클러스터 전체가 위험에 노출될 수 있습니다.

Kubernetes의 3단계 보안 체계

Kubernetes API Server로의 모든 요청은 세 단계를 거칩니다.

flowchart LR
    subgraph Stage1["1단계: Authentication"]
        A1[Bearer Token<br/>OIDC]
        A2[X.509<br/>Client Cert]
        A3[Webhook<br/>Token Auth]
    end

    subgraph Stage2["2단계: Authorization"]
        B1[RBAC]
        B2[ABAC]
        B3[Webhook]
    end

    subgraph Stage3["3단계: Admission Control"]
        C1[Validating<br/>Webhook]
        C2[Mutating<br/>Webhook]
    end

    Client([클라이언트]) --> Stage1
    Stage1 -->|인증 성공| Stage2
    Stage1 -->|인증 실패| R1[401 Unauthorized]
    Stage2 -->|인가 성공| Stage3
    Stage2 -->|인가 실패| R2[403 Forbidden]
    Stage3 -->|승인| etcd[(etcd)]
    Stage3 -->|거부| R3[요청 거부]

1단계: 인증 (Authentication)

인증은 주로 관리자 영역이지만, 사용 가능한 옵션을 알아두면 유용합니다.

인증 방식 설명
Bearer Tokens (OIDC) OAuth2 제공자를 통한 OpenID Connect 토큰 인증입니다. Authorization 헤더에 토큰을 전송하여 API Server가 검증합니다.
Client Certificates (X.509) TLS 인증서를 API Server에 제출하여 검증받습니다. 인증서의 CN(Common Name)이 사용자명, O(Organization)가 그룹으로 매핑됩니다.
Authenticating Proxy 사용자 정의 인증 프록시가 클라이언트와 API Server 사이에서 인증을 수행합니다.
Static Token Files 파일에 저장된 토큰을 사용하는 방식입니다.
Webhook Token Authentication 외부 서비스에 토큰 검증을 위임합니다.

Kubernetes는 여러 인증 플러그인을 동시에 사용할 수 있습니다. 한 전략이 요청을 인증하면 다른 전략은 확인하지 않습니다. 다만, 평가 순서가 고정되어 있지 않아 어떤 전략이 먼저 확인되는지 알 수 없습니다.

2단계: 인가 (Authorization)

Kubernetes는 RBAC을 표준 접근 제어 방식으로 제공합니다. RBAC 외에도 ABAC, Webhook, 사용자 정의 권한 위임 등을 사용할 수 있습니다.

ABAC 방식은 JSON 형식의 정책 파일이 필요하고, 변경 시 서버 재시작이 필요하다는 단점이 있어 거의 사용되지 않습니다. 대부분의 Kubernetes 클러스터는 기본 RBAC 기반 접근 제어를 사용합니다.

3단계: Admission Controllers

Admission Controller는 API Server로의 요청을 가로채어 추가 작업을 수행하는 기능입니다. 정책 적용, 유효성 검사, 리소스 수정 등을 수행할 수 있습니다.

외부 웹훅은 전용 리소스로 구성되며, 검증용(ValidatingWebhookConfiguration)과 수정용(MutatingWebhookConfiguration)으로 나뉩니다.

Subject: 요청의 주체

Subject는 Kubernetes API Server에 대한 요청과 연관된 신원입니다. Kubernetes에는 두 종류의 Subject가 있습니다.

flowchart TB
    subgraph Subjects["Subject 유형"]
        subgraph Human["Human Users"]
            U1[외부 사용자 관리]
            U2[OIDC/X.509로 인증]
            U3[API로 관리 불가]
        end

        subgraph Workload["Service Accounts"]
            S1[Kubernetes API 리소스]
            S2[Pod의 워크로드 신원]
            S3[JWT 토큰 사용]
        end
    end

    Human --> API[API Server]
    Workload --> API

    style Human fill:#e1f5fe
    style Workload fill:#fff3e0

Users (인간 사용자)

Kubernetes의 인간 사용자는 API에서 명시적 리소스로 정의되지 않습니다. 사용자를 API 호출로 관리할 수 없으며, 인증과 사용자 Subject 매핑은 외부 사용자 관리 시스템에서 수행됩니다.

인증 후 사용자 정보는 다음과 같이 표현됩니다.

alice,4bc01e30-406b-4514,"system:authenticated,developers","scopes:openid"

이 정보는 다음 부분으로 구성됩니다.

  • 사용자명 (alice)
  • 고유 사용자 ID (4bc01e30-406b-4514)
  • 소속 그룹 목록 (system:authenticated,developers)
  • 추가 정보 (scopes:openid)

시스템 예약 사용자명

system: 접두사가 붙은 사용자명은 내부 Kubernetes 사용을 위해 예약되어 있습니다.

사용자명 용도
system:anonymous 익명 요청
system:apiserver API Server 자체
system:kube-proxy kube-proxy 서비스
system:kube-controller-manager Controller Manager
system:kube-scheduler Scheduler

Service Accounts (서비스 계정)

Service Account는 클러스터 내의 비인간 행위자를 나타내며 워크로드 신원으로 사용됩니다. Pod와 연관되어 컨테이너 내부 프로세스가 API Server와 통신할 수 있게 합니다.

apiVersion: v1
kind: ServiceAccount
metadata:
  name: build-bot
  namespace: default
automountServiceAccountToken: false  # 불필요한 토큰 마운트 방지

Service Account는 항상 OpenID Connect 핸드셰이크와 JSON Web Token을 사용하여 신원을 증명합니다.

Service Account 토큰

Pod가 생성되면 기본적으로 연결된 ServiceAccount의 토큰이 /var/run/secrets/kubernetes.io/serviceaccount/token에 마운트됩니다.

apiVersion: v1
kind: Pod
metadata:
  name: api-consumer
spec:
  serviceAccountName: build-bot
  containers:
  - name: app
    image: myapp:latest

최신 Kubernetes는 Bound Service Account Token을 사용합니다. 이 토큰은 시간 제한이 있고, 특정 대상(audience)과 객체에 바인딩되어 보안이 강화됩니다.

Groups (그룹)

사용자와 Service Account는 하나 이상의 그룹에 속할 수 있습니다. 그룹은 인증 시스템에 의해 요청에 첨부되어 모든 그룹 멤버에게 권한을 부여하는 데 사용됩니다.

그룹 용도
system:unauthenticated 모든 인증되지 않은 요청에 할당
system:authenticated 인증된 사용자에게 할당
system:masters Kubernetes API Server에 대한 무제한 접근
system:serviceaccounts 클러스터의 모든 ServiceAccount
system:serviceaccounts:<namespace> 해당 네임스페이스의 모든 ServiceAccount

Role-Based Access Control (RBAC)

RBAC에서 Role은 Subject가 특정 리소스에 대해 수행할 수 있는 작업을 정의합니다. RoleBinding을 통해 Subject에 Role을 할당합니다.

flowchart LR
    subgraph Subjects["Subjects"]
        U[User: alice]
        SA[ServiceAccount: contractor]
        G[Group: developers]
    end

    subgraph Binding["RoleBinding"]
        RB[dev-rolebinding]
    end

    subgraph Roles["Role"]
        R[developer-ro<br/>pods: get, list, watch<br/>services: get, list, watch]
    end

    U --> RB
    SA --> RB
    G --> RB
    RB --> R

    style R fill:#c8e6c9
    style RB fill:#fff9c4

Subject와 Role 사이에는 다대다 관계가 있습니다. 하나의 Subject가 여러 Role을 가질 수 있고, 하나의 Role이 여러 Subject에 적용될 수 있습니다.

Role

Role은 Kubernetes 리소스에 대한 허용된 작업을 정의합니다.

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: developer-ro
  namespace: default
rules:
- apiGroups:
  - ""  # 빈 문자열은 core API 그룹
  resources:
  - pods
  - services
  verbs:
  - get
  - list
  - watch

각 규칙은 세 가지 필드로 구성됩니다.

필드 설명
apiGroups 대상 API 그룹. 빈 문자열("")은 core API 그룹, 와일드카드(*)는 모든 그룹
resources 접근 대상 리소스. 하위 리소스는 "pods/log" 형태로 지정
verbs 허용되는 작업. get, list, watch, create, update, patch, delete, deletecollection 등

RoleBinding

RoleBinding은 Subject와 Role을 연결합니다.

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: dev-rolebinding
  namespace: default
subjects:
- kind: User
  name: alice
  apiGroup: rbac.authorization.k8s.io
- kind: ServiceAccount
  name: contractor
  namespace: default
  apiGroup: ""
roleRef:
  kind: Role
  name: developer-ro
  apiGroup: rbac.authorization.k8s.io

Subject 유형별 설정

Kind API Group Namespace 설명
User rbac.authorization.k8s.io N/A 인간 사용자 참조
Group rbac.authorization.k8s.io N/A 사용자 그룹 참조
ServiceAccount "" 선택적 ServiceAccount 참조, 다른 네임스페이스 지정 가능

ClusterRole

ClusterRole은 클러스터 전체에 적용되는 Role입니다. 두 가지 주요 용도가 있습니다.

  1. 클러스터 전체 리소스 보호: CustomResourceDefinitions, StorageClasses 등 클러스터 수준 리소스에 대한 접근 제어
  2. 네임스페이스 간 공유 Role 정의: 여러 RoleBinding에서 재사용할 수 있는 일반적인 접근 제어 규칙 정의
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: view-pod
rules:
- apiGroups:
  - ""
  resources:
  - pods
  verbs:
  - get
  - list

기본 제공 ClusterRole

ClusterRole 용도
view Secret을 제외한 대부분의 리소스 읽기 허용
edit Role/RoleBinding을 제외한 대부분의 리소스 읽기/수정 허용
admin Role/RoleBinding 포함 네임스페이스 내 모든 리소스 완전 제어
cluster-admin 클러스터 전체 리소스에 대한 완전 제어

ClusterRole Aggregation

여러 ClusterRole의 권한을 결합할 수 있습니다.

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: monitoring
  labels:
    rbac.authorization.k8s.io/aggregate-to-view: "true"
rules:
- apiGroups: [""]
  resources: ["pods/status"]
  verbs: ["get"]

ClusterRoleBinding

ClusterRoleBinding은 ClusterRole을 클러스터 전체에 적용합니다.

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: test-sa-crb
subjects:
- kind: ServiceAccount
  name: test-sa
  namespace: test
roleRef:
  kind: ClusterRole
  name: view-pod
  apiGroup: rbac.authorization.k8s.io
flowchart TB
    subgraph test["test namespace"]
        SA[ServiceAccount<br/>test-sa]
    end

    CRB[ClusterRoleBinding<br/>test-sa-crb]
    CR[ClusterRole<br/>view-pod]

    subgraph ns1["dev-1 namespace"]
        P1[Pods]
    end

    subgraph ns2["dev-2 namespace"]
        P2[Pods]
    end

    subgraph ns3["kube-system namespace"]
        P3[Pods]
    end

    SA --> CRB
    CRB --> CR
    CR --> P1
    CR --> P2
    CR --> P3

    style CR fill:#ffcdd2
    style CRB fill:#fff9c4

ClusterRoleBinding은 모든 네임스페이스에 권한을 부여하므로 주의가 필요합니다. 관리 작업(Nodes, Namespaces, CustomResourceDefinitions 관리 등)에만 사용하는 것이 좋습니다.

RoleBinding에서 ClusterRole 참조

RoleBinding이 ClusterRole을 참조하면 해당 네임스페이스에만 권한이 적용됩니다.

flowchart TB
    subgraph test["test namespace"]
        SA[ServiceAccount<br/>test-sa]
    end

    CR[ClusterRole<br/>view-pod]

    subgraph dev1["dev-1 namespace"]
        RB1[RoleBinding]
        P1[Pods]
    end

    subgraph dev2["dev-2 namespace"]
        RB2[RoleBinding]
        P2[Pods]
    end

    SA --> RB1
    SA --> RB2
    RB1 --> CR
    RB2 --> CR
    RB1 --> P1
    RB2 --> P2

    style CR fill:#c8e6c9

이 방식은 일반적인 접근 제어 규칙을 정의하고 여러 네임스페이스에서 재사용할 때 유용합니다.

권한 상승 방지 (Privilege Escalation Prevention)

RBAC 하위 시스템은 권한 상승을 방지하기 위해 다음 제한을 적용합니다.

  • 사용자가 Role을 업데이트하려면 해당 Role의 모든 권한을 이미 가지고 있거나, rbac.authorization.k8s API 그룹의 모든 리소스에 대한 escalate verb 권한이 있어야 합니다.
  • RoleBinding의 경우, 참조된 Role의 모든 권한을 가지고 있거나 RBAC 리소스에 대한 bind verb 권한이 있어야 합니다.

API Server 내부 동작

API Server가 실제로 어떻게 인증과 인가를 처리하는지 살펴보겠습니다.

요청 처리 Handler Chain

모든 API 요청은 DefaultBuildHandlerChain()에서 구성된 데코레이터 기반 핸들러 체인을 통과합니다.

flowchart TB
    subgraph chain["Handler Chain"]
        direction TB
        TLS[TLS Termination] --> Panic[WithPanicRecovery]
        Panic --> Audit1[WithAuditInit]
        Audit1 --> ReqInfo[WithRequestInfo<br/>URL 파싱]
        ReqInfo --> Auth[WithAuthentication<br/>인증]
        Auth --> Audit2[WithAudit]
        Audit2 --> Imp[WithImpersonation]
        Imp --> Authz[WithAuthorization<br/>인가]
        Authz --> REST[REST Handler]
        REST --> Admission[Admission Controllers]
        Admission --> etcd[(etcd Storage)]
    end

    Auth -->|실패| R401[401 Unauthorized]
    Authz -->|실패| R403[403 Forbidden]

    style Auth fill:#e3f2fd
    style Authz fill:#fff3e0

핸들러 체인은 fail-fast 방식으로 동작합니다. 인증 실패는 HTTP 401, 인가 거부는 HTTP 403을 반환합니다.

etcd 저장 구조와 캐싱

RBAC 리소스 저장 구조

RBAC 리소스는 /registry 접두사 아래에 계층적 키 구조로 저장됩니다.

리소스 유형 키 패턴 예시
ClusterRole /registry/clusterroles/{name} /registry/clusterroles/cluster-admin
ClusterRoleBinding /registry/clusterrolebindings/{name} /registry/clusterrolebindings/system:basic-user
Role /registry/roles/{namespace}/{name} /registry/roles/default/pod-reader
RoleBinding /registry/rolebindings/{namespace}/{name} /registry/rolebindings/kube-system/system:controller:bootstrap-signer

Cacher 아키텍처

저장 계층은 정교한 캐싱 아키텍처를 구현합니다.

flowchart LR
    etcd[(etcd)] -->|단일 WATCH| Reflector
    Reflector --> WC[watchCache]
    WC --> Cacher
    Cacher --> CW1[cacheWatcher 1]
    Cacher --> CW2[cacheWatcher 2]
    Cacher --> CW3[cacheWatcher N]

    subgraph memory["메모리 내 저장소"]
        WC
        Cacher
    end

    style memory fill:#e8f5e9

이 아키텍처의 핵심 특징은 다음과 같습니다.

  • 리소스 타입당 하나의 etcd watch가 무제한의 API Server 클라이언트를 서비스합니다.
  • Indexer를 통한 메모리 내 조회로 밀리초 미만의 인가 성능을 제공합니다.
  • LIST+WATCH 패턴을 통한 자동 무효화가 이루어집니다.

RBAC 인가기는 직접 etcd 쿼리 대신 SharedInformer가 지원하는 Lister를 사용합니다.

type RBACAuthorizer struct {
    authorizationRuleResolver RequestToRuleMapper
}

func (l *RoleBindingLister) ListRoleBindings(namespace string) ([]*rbacv1.RoleBinding, error) {
    return l.Lister.RoleBindings(namespace).List(labels.Everything())
}

Bound Service Account Token

최신 Kubernetes는 레거시 시크릿 기반 토큰 대신 Bound Service Account Token (KEP-1205)을 사용합니다.

토큰 구조

{
  "aud": ["https://kubernetes.default.svc"],
  "exp": 1730727933,
  "iat": 1730720733,
  "iss": "https://kubernetes.default.svc",
  "kubernetes.io": {
    "namespace": "default",
    "pod": {"name": "myapp-6445ccd844-7vs45", "uid": "35275f74-..."},
    "serviceaccount": {"name": "myapp-sa", "uid": "0973e7b0-..."},
    "node": {"name": "node-1", "uid": "2d0b5885-..."}
  },
  "sub": "system:serviceaccount:default:myapp-sa"
}

토큰 검증 과정

func (v *validator) Validate(ctx context.Context, tokenData string,
    public *jwt.Claims, private *privateClaims) (*ServiceAccountInfo, error) {

    // 1. ServiceAccount 존재 및 UID 일치 확인
    sa, _ := v.getter.GetServiceAccount(namespace, saref.Name)
    if sa.UID != types.UID(saref.UID) { return nil, errors.New("SA UID 불일치") }

    // 2. 바인딩된 Pod 존재 확인 (지정된 경우)
    if podref != nil {
        pod, _ := v.getter.GetPod(namespace, podref.Name)
        if pod.UID != types.UID(podref.UID) { return nil, errors.New("토큰 무효화됨") }
    }

    // 3. 바인딩된 Secret 존재 확인 (지정된 경우)
    // 4. 바인딩된 Node 존재 확인 (지정된 경우)
}

Pod를 삭제하면 해당 프로젝션된 토큰이 즉시 무효화됩니다. 레거시 토큰 대비 보안이 개선되었습니다.

Projected Volume을 통한 자동 토큰 갱신

spec:
  volumes:
  - name: token
    projected:
      sources:
      - serviceAccountToken:
          path: token
          expirationSeconds: 7200
          audience: vault

Kubelet의 토큰 매니저가 다음을 수행합니다.

  1. TokenRequest API를 통해 토큰 요청
  2. 컨테이너 파일시스템에 토큰 작성
  3. 수명의 약 80%가 경과하면 자동 갱신
  4. 기본 만료 시간은 3607초 (1시간 + 7초)

RBAC 디버깅

kubectl auth can-i

특정 권한을 확인할 수 있습니다.

# 현재 사용자의 권한 확인
kubectl auth can-i list pods --namespace dev-1

# 특정 ServiceAccount의 권한 확인
kubectl auth can-i list pods \
  --namespace dev-1 \
  --as system:serviceaccount:test:test-sa

# 모든 권한 확인
kubectl auth can-i --list --namespace dev-1

SubjectAccessReview API

프로그래밍 방식으로 권한을 확인할 수 있습니다.

apiVersion: authorization.k8s.io/v1
kind: SubjectAccessReview
spec:
  resourceAttributes:
    namespace: production
    verb: delete
    resource: pods
  user: jane
  groups:
  - developers

응답에서 status.allowed가 true/false로, status.reason이 "RBAC: allowed by..."로 반환됩니다.

유용한 도구

  • rakkess: kubectl access-matrix 명령으로 Subject가 수행할 수 있는 작업의 매트릭스 뷰를 제공합니다.
  • KubiScan: RBAC 설정에서 위험한 권한을 스캔합니다.

RBAC 관련 확인사항

와일드카드 권한 피하기

최소 권한 원칙을 따르세요. 와일드카드 권한은 의도치 않은 작업을 허용할 수 있습니다.

# 피해야 할 설정
rules:
- apiGroups: ["*"]
  resources: ["*"]
  verbs: ["*"]

# 권장 설정
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "list"]

cluster-admin ClusterRole 사용 금지

높은 권한의 ServiceAccount는 모든 리소스에 대한 작업을 수행할 수 있어 보안 위협이 됩니다. Pod에 cluster-admin ClusterRole을 할당하지 않는 것이 좋습니다.

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

애플리케이션이 API Server와 통신할 필요가 없다면 토큰 마운트를 비활성화하세요.

apiVersion: v1
kind: ServiceAccount
metadata:
  name: my-sa
automountServiceAccountToken: false

Pod가 침해되더라도 공격자가 ServiceAccount의 권한으로 API Server와 통신할 수 없습니다.

RBAC 선택 가이드

시나리오 사용할 리소스
특정 네임스페이스의 리소스 보호 Role + RoleBinding
여러 네임스페이스에서 동일한 접근 규칙 재사용 ClusterRole + RoleBinding (각 네임스페이스별)
기존 ClusterRole 확장 ClusterRole + aggregationRule
모든 네임스페이스의 특정 리소스 접근 ClusterRole + ClusterRoleBinding
클러스터 전체 리소스(CRD 등) 관리 ClusterRole + ClusterRoleBinding

 

참고 자료