쿠버네티스 6일차 — 보안 + RBAC + NetworkPolicy
운영하다 보면 만나는 가장 무서운 문장:
“그 클러스터 전권 토큰이 git 에 있었습니다.”
6일차는 누가 무엇을 할 수 있는지 를 명시적으로 정하는 날입니다. 안 하면 전부 다 됨 = 모두가 root.
이 글에서 다루는 것
- RBAC: 사람/앱이 무슨 API 를 호출할 수 있나
- ServiceAccount: 앱이 클러스터에 접근하는 신원
- NetworkPolicy: Pod 간 트래픽 제한
- Secret 보안: SOPS / Sealed Secrets / etcd 암호화
- Pod Security Admission: 컨테이너 자체의 권한 제한
1. RBAC — Role-Based Access Control
4 종류의 객체
[Role / ClusterRole] ← 권한의 묶음 (read pods, edit secrets, …)
↑ bound to
[RoleBinding / ClusterRoleBinding]
↑
[Subject: User / Group / ServiceAccount]
- Role = 네임스페이스 한정 / ClusterRole = 클러스터 전역
- RoleBinding 으로 Subject 와 Role 을 연결
예 — 개발자에게 prod 네임스페이스 read-only
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata: { namespace: prod, name: pod-reader }
rules:
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata: { namespace: prod, name: dev-can-read }
subjects:
- kind: User
name: dev-1@lemuel.co.kr
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
황금률 — Least Privilege
“필요한 만큼만, 더는 안 준다.”
- ❌
cluster-admin을 모두에게 - ✅ Role 분리:
viewer,editor,admin(네임스페이스별)
2. ServiceAccount — 앱의 신원
Pod 도 클러스터 API 를 호출할 수 있습니다 (자기 자신 정보 읽기, ConfigMap 가져오기, …). 이때 신원이 ServiceAccount 입니다.
기본 ServiceAccount 의 함정
각 네임스페이스에 default ServiceAccount 가 자동 생성. 모든 Pod 가 명시 안 하면 default 를 씀. RBAC 미설정이면 기본권한이 의외로 넓을 수 있습니다.
패턴 — Pod 마다 전용 SA + 최소 권한
apiVersion: v1
kind: ServiceAccount
metadata: { name: cert-minter, namespace: academy }
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata: { namespace: academy, name: cert-minter }
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get"]
resourceNames: ["minter-key"] # 특정 Secret 만
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata: { namespace: academy, name: cert-minter }
subjects: [ { kind: ServiceAccount, name: cert-minter } ]
roleRef:
kind: Role
name: cert-minter
apiGroup: rbac.authorization.k8s.io
---
# Deployment 에 명시
spec:
template:
spec:
serviceAccountName: cert-minter
3. NetworkPolicy — Pod 간 방화벽
기본값은 모든 Pod 가 모든 Pod 와 통신 가능. 운영 클러스터에서는 위험합니다. NetworkPolicy 로 화이트리스트만 허용하게 좁힙니다.
예 — frontend 만 backend 호출 가능
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: backend-from-frontend, namespace: prod }
spec:
podSelector: { matchLabels: { app: backend } }
policyTypes: ["Ingress"]
ingress:
- from:
- podSelector: { matchLabels: { app: frontend } }
ports:
- protocol: TCP
port: 8080
함정 — CNI 가 지원해야 동작
NetworkPolicy 는 선언만 한 것 이고, 실제 강제는 CNI 가 합니다.
| CNI | NetworkPolicy 지원 |
|---|---|
| Flannel (기본) | ❌ 무시됨 |
| Calico | ✅ |
| Cilium | ✅✅ (eBPF, 가장 강력) |
지원 CNI 가 아니면 yaml 만 들어가고 트래픽은 다 통합니다. 반드시 확인.
4. Secret 보안 강화
3일차에서 봤듯 Secret 의 base64 는 암호화가 아닙니다. 추가로 다음 셋 중 하나는 거의 필수.
A) etcd 암호화
# kube-apiserver --encryption-provider-config=enc.yaml
kind: EncryptionConfiguration
apiVersion: apiserver.config.k8s.io/v1
resources:
- resources: ["secrets"]
providers:
- aescbc:
keys:
- name: key1
secret: <base64 key>
- identity: {}
저장소 (etcd 데이터) 차원에서 암호화.
B) SOPS + age — git 친화적
# 평문 secret.yaml → 암호화된 .enc.yaml
sops --age age1abc... -e secret.yaml > secret.enc.yaml
git add secret.enc.yaml # 안전하게 커밋
# 배포 시 복호화
sops -d secret.enc.yaml | kubectl apply -f -
저는 lemuel-secrets 를 SOPS+age 로 굴립니다. age 키만 안전하게 보관하면 git 에 들어가도 OK.
C) Sealed Secrets — kubeseal
# 평문 secret.yaml → SealedSecret (클러스터만 복호화 가능)
kubeseal -f secret.yaml -w sealed-secret.yaml
git add sealed-secret.yaml
kubectl apply -f sealed-secret.yaml
5. Pod Security — 컨테이너 자체 권한
Pod Security Admission (Standards):
| 등급 | 의미 |
|---|---|
privileged |
모든 권한 (위험) |
baseline |
흔한 위험만 차단 |
restricted |
강력하게 제한 (권장) |
apiVersion: v1
kind: Namespace
metadata:
name: prod
labels:
pod-security.kubernetes.io/enforce: restricted
컨테이너 수준 보안 컨텍스트
spec:
containers:
- name: app
securityContext:
runAsNonRoot: true
runAsUser: 1000
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
이걸 안 박으면 컨테이너 안에서 root 로 돌아갑니다 — 컨테이너 탈출(escape) 시 노드 root.
6. 보안 체크리스트 (운영 시작 전)
- 모든 사람의
kubectlkubeconfig 가 다 다른 user 로 분리 cluster-adminBinding 은 최소 (1~2명)- 각 앱이 전용 ServiceAccount + 최소 권한 Role
- CNI 가 NetworkPolicy 지원하고, default-deny 가 적용됨
- Secret 은 SOPS / Sealed Secrets / etcd 암호화 중 최소 1
- Namespace 에
pod-security.kubernetes.io/enforce: baseline이상 - 컨테이너에
runAsNonRoot: true+drop: ["ALL"]
핵심 한 줄 정리
- RBAC: Role + RoleBinding + Subject. Least Privilege 고집
- ServiceAccount: Pod 의 신원. 기본 SA 의존하지 말고 전용 SA + 최소 권한
- NetworkPolicy: Pod 간 방화벽. CNI 가 지원해야 동작 (Calico/Cilium)
- Secret: SOPS / Sealed Secrets / etcd 암호화 중 하나 필수
- Pod Security: restricted 등급 + runAsNonRoot + capabilities drop
마지막 7일차에서는 지금까지 배운 모든 것을 모아서 종합 프로젝트 — 작은 SaaS 클러스터 운영 — 을 만들어 봅니다.
시리즈 1일차 부터 보시면 좋습니다.