OPA/Gatekeeper, Kyverno, Pod Security Standards, RBAC 모범 사례, External Secrets Operator, 공급망 보안(SLSA, Sigstore), 이미지 서명을 다룹니다.
지금까지 배포, 스케일링, 비용 최적화, 인프라 관리를 다루었습니다. 하지만 이 모든 것이 적절한 보안 없이는 의미가 없습니다. Kubernetes 클러스터는 공격 표면(Attack Surface)이 넓고, 잘못된 설정 하나가 심각한 보안 사고로 이어질 수 있습니다. 이번 장에서는 Kubernetes 보안의 핵심 영역인 정책 관리, 시크릿 관리, 공급망 보안을 다루겠습니다.
O픈 폴리시 에이전트(Open Policy Agent, OPA)는 CNCF Graduated 프로젝트로, 범용 정책 엔진입니다. Gatekeeper는 OPA를 Kubernetes Admission Controller로 통합한 프로젝트입니다.
Gatekeeper에서 정책은 ConstraintTemplate과 Constraint 두 단계로 정의됩니다.
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequiredlabels
spec:
crd:
spec:
names:
kind: K8sRequiredLabels
validation:
openAPIV3Schema:
type: object
properties:
labels:
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredlabels
violation[{"msg": msg}] {
provided := {label | input.review.object.metadata.labels[label]}
required := {label | label := input.parameters.labels[_]}
missing := required - provided
count(missing) > 0
msg := sprintf("Missing required labels: %v", [missing])
}apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
name: require-team-labels
spec:
enforcementAction: deny # deny | dryrun | warn
match:
kinds:
- apiGroups: ["apps"]
kinds: ["Deployment", "StatefulSet"]
namespaces:
- production
- staging
parameters:
labels:
- "app.kubernetes.io/name"
- "app.kubernetes.io/part-of"
- "cost.example.com/team"새로운 정책을 도입할 때는 반드시 enforcementAction: dryrun으로 먼저 배포하여 기존 리소스에 미치는 영향을 파악하십시오. Gatekeeper의 감사(Audit) 기능으로 위반 현황을 확인한 후, 점진적으로 warn 그리고 deny로 전환하는 것이 안전합니다.
Kyverno는 Kubernetes 네이티브 정책 엔진으로, Rego 대신 YAML로 정책을 작성합니다. 학습 곡선이 낮아 많은 팀에서 선호합니다.
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-labels
annotations:
policies.kyverno.io/title: Require Labels
policies.kyverno.io/severity: medium
spec:
validationFailureAction: Enforce # Enforce | Audit
background: true
rules:
- name: check-team-label
match:
any:
- resources:
kinds:
- Deployment
- StatefulSet
namespaces:
- production
- staging
validate:
message: "The label 'cost.example.com/team' is required."
pattern:
metadata:
labels:
cost.example.com/team: "?*"Kyverno는 정책 검증 외에도 리소스 변환(Mutate)과 자동 생성(Generate) 기능을 제공합니다.
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: add-default-security-context
spec:
rules:
- name: add-run-as-non-root
match:
any:
- resources:
kinds:
- Pod
mutate:
patchStrategicMerge:
spec:
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
containers:
- (name): "*"
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALLapiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: generate-network-policy
spec:
rules:
- name: default-deny-ingress
match:
any:
- resources:
kinds:
- Namespace
generate:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
name: default-deny-ingress
namespace: "{{request.object.metadata.name}}"
data:
spec:
podSelector: {}
policyTypes:
- Ingress| 기준 | Gatekeeper | Kyverno |
|---|---|---|
| 정책 언어 | Rego (전용 언어) | YAML (Kubernetes 네이티브) |
| 학습 곡선 | 높음 (Rego 학습 필요) | 낮음 (YAML만 알면 됨) |
| 기능 | Validate | Validate, Mutate, Generate, Verify Image |
| 감사 | 별도 감사 모드 | 배경 스캔 기본 제공 |
| 생태계 | OPA 생태계 공유 (Kubernetes 외에도 활용) | Kubernetes 전용 |
Kubernetes 1.25부터 Pod 보안 표준(Pod Security Standards, PSS)이 PodSecurityPolicy를 대체했습니다. 세 가지 보안 수준을 정의합니다.
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
# PSS 적용
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restrictedPrivileged: 제한 없음. 시스템 컴포넌트용.
Baseline: 알려진 권한 상승을 방지. hostNetwork, hostPID, privileged 컨테이너를 차단합니다.
Restricted: 최소 권한 원칙 적용. non-root 실행, 모든 capability 드롭, seccomp 프로필 필수입니다.
PSS의 restricted 수준을 모든 프로덕션 네임스페이스에 적용하는 것을 권장합니다. 처음에는 warn 모드로 적용하여 위반 사항을 파악하고, 애플리케이션을 수정한 후 enforce 모드로 전환하십시오.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: developer
namespace: backend
rules:
# Pod 조회 및 로그 확인만 허용
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch"]
# Deployment 조회만 허용 (수정 불가)
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "watch"]
# ConfigMap/Secret은 조회만
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list"]
# Secret은 조회 불가 (보안)
# - apiGroups: [""]
# resources: ["secrets"]
# verbs: []
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: developer-binding
namespace: backend
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: developer
subjects:
- kind: Group
name: backend-developers
apiGroup: rbac.authorization.k8s.iocluster-admin ClusterRoleBinding은 절대 일반 사용자에게 부여하지 마십시오. 네임스페이스 범위의 Role/RoleBinding을 우선 사용하고, 클러스터 범위가 필요한 경우에만 ClusterRole을 최소한의 권한으로 정의하십시오.
External Secrets Operator(ESO)는 외부 시크릿 저장소(AWS Secrets Manager, HashiCorp Vault 등)의 시크릿을 Kubernetes Secret으로 동기화합니다.
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: aws-secrets
namespace: production
spec:
provider:
aws:
service: SecretsManager
region: ap-northeast-2
auth:
jwt:
serviceAccountRef:
name: external-secrets-sa
---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: database-credentials
namespace: production
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets
kind: SecretStore
target:
name: database-credentials
creationPolicy: Owner
template:
type: Opaque
data:
DATABASE_URL: "postgresql://{{ .username }}:{{ .password }}@{{ .host }}:5432/appdb"
data:
- secretKey: username
remoteRef:
key: production/database
property: username
- secretKey: password
remoteRef:
key: production/database
property: password
- secretKey: host
remoteRef:
key: production/database
property: hostSealed Secrets는 시크릿을 암호화하여 Git에 안전하게 저장할 수 있게 합니다.
# kubeseal로 Secret을 SealedSecret으로 변환
kubectl create secret generic api-key \
--from-literal=key=my-secret-api-key \
--dry-run=client -o yaml | \
kubeseal \
--controller-name=sealed-secrets \
--controller-namespace=kube-system \
--format=yaml > sealed-api-key.yamlapiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: api-key
namespace: production
spec:
encryptedData:
key: AgBy3i4OJSWK+PiTySYZZA9rO... # 암호화된 값
template:
type: OpaqueExternal Secrets Operator와 Sealed Secrets 중 선택할 때, 이미 AWS Secrets Manager나 HashiCorp Vault를 사용하고 있다면 ESO가 더 적합합니다. 별도의 시크릿 관리 시스템이 없다면 Sealed Secrets가 더 간단합니다. 두 도구 모두 GitOps 워크플로우와 자연스럽게 통합됩니다.
SLSA(Supply chain Levels for Software Artifacts)는 소프트웨어 공급망 보안을 위한 프레임워크입니다. 빌드 과정의 무결성을 보장하는 4단계 성숙도 모델을 정의합니다.
Level 1: 빌드 프로세스 문서화
→ 빌드가 어떻게 수행되었는지 기록
Level 2: 빌드 서비스 사용
→ 호스팅된 빌드 서비스에서 빌드 수행
Level 3: 소스와 빌드 무결성
→ 소스 코드의 출처 검증, 빌드 환경 격리
Level 4: 양방향 검증
→ 모든 종속성의 출처 검증
Sigstore는 소프트웨어 아티팩트에 디지털 서명을 하는 오픈소스 프로젝트입니다. Cosign 도구를 사용하여 컨테이너 이미지에 서명하고 검증합니다.
# 이미지 서명 (키리스 방식 - OIDC 인증)
cosign sign --yes ghcr.io/org/api-server:v1.2.3
# 서명 검증
cosign verify \
--certificate-identity=ci@org.com \
--certificate-oidc-issuer=https://token.actions.githubusercontent.com \
ghcr.io/org/api-server:v1.2.3apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: verify-image-signature
spec:
validationFailureAction: Enforce
webhookTimeoutSeconds: 30
rules:
- name: verify-cosign-signature
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/org/*"
attestors:
- entries:
- keyless:
subject: "https://github.com/org/*"
issuer: "https://token.actions.githubusercontent.com"
rekor:
url: https://rekor.sigstore.dev
mutateDigest: true
verifyDigest: truename: Build, Sign, and Push
on:
push:
branches: [main]
permissions:
contents: read
packages: write
id-token: write # Cosign 키리스 서명에 필요
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/build-push-action@v6
id: build
with:
push: true
tags: ghcr.io/org/api-server:${{ github.sha }}
- uses: sigstore/cosign-installer@v3
- name: Sign image
run: |
cosign sign --yes \
ghcr.io/org/api-server@${{ steps.build.outputs.digest }}
- name: Generate SBOM
uses: anchore/sbom-action@v0
with:
image: ghcr.io/org/api-server@${{ steps.build.outputs.digest }}
format: spdx-json
output-file: sbom.spdx.json
- name: Attach SBOM to image
run: |
cosign attach sbom \
--sbom sbom.spdx.json \
ghcr.io/org/api-server@${{ steps.build.outputs.digest }}Kubernetes 보안은 정책 관리(Gatekeeper/Kyverno), 접근 제어(RBAC/PSS), 시크릿 관리(ESO/Sealed Secrets), 공급망 보안(SLSA/Sigstore)의 네 가지 축으로 구성됩니다. 모든 보안 설정은 GitOps로 관리하여 변경 이력을 추적하고, 정책 코드 리뷰를 통해 실수를 방지해야 합니다.
다음 장에서는 이 시리즈에서 다룬 모든 내용을 종합하여 실전 프로젝트 -- GitOps 기반 Kubernetes 플랫폼 구축을 진행하겠습니다. ArgoCD, Karpenter, Crossplane, Kyverno를 통합하여 완전한 플랫폼을 구축하는 과정을 단계별로 안내합니다.
이 글이 도움이 되셨나요?
ArgoCD, Karpenter, Crossplane, Kyverno를 통합한 GitOps 기반 Kubernetes 플랫폼을 구축합니다. app-of-apps 패턴, 환경 프로모션, 모니터링, Day-2 운영 체크리스트를 다룹니다.
Crossplane의 아키텍처, Provider와 Managed Resource, XRD와 Composition을 활용한 인프라 추상화, GitOps 통합, Terraform과의 비교를 다룹니다.
Kubernetes 리소스 requests/limits 모범 사례, VPA/HPA 전략, Goldilocks, Kubecost/OpenCost, 스팟 인스턴스, 네임스페이스 쿼터, FinOps 실천법을 다룹니다.