12 KiB
security hardening 기준
목적
이 문서는 K3s/Kubernetes 기반 인프라(1000+ 서비스 규모)에서
- 어떤 Pod Security Standard(PSS) 수준을 강제할지
- Pod/ServiceAccount/RBAC/NetworkPolicy/Secret/Image supply chain을 어디까지 하드닝할지
- Platform 예외를 어떻게 선언할지 를 단일 ground truth로 고정한다.
이 문서의 목표는 다음과 같다.
- root/privileged/host namespace 사용을 기본 금지하고 예외는 manifest로 증명한다
- allow-all network/RBAC을 운영 기본값으로 두지 않는다
- Secret의 저장·접근·전송 모든 단계에서 신뢰 경계를 명시한다
- K3s production hardening(PSS, NetworkPolicy, audit, at-rest encryption)을 묶음으로 본다
공식 의미 (근거)
- Kubernetes 1.25부터
PodSecurityPolicy(PSP)는 제거되었다. 대체는 Pod Security Admission (PSA) +pod-security.kubernetes.io/*namespace label이다. - Pod Security Standards는
privileged,baseline,restricted세 프로파일이다.restricted는 업계 최신 hardening best practice를 반영한다. - PSA는
enforce,audit,warn세 모드를 지원하고, 각 모드마다 버전을latest/vX.Y로 고정할 수 있다. restricted프로파일이 강제하는 주요 필드:runAsNonRoot=true,allowPrivilegeEscalation=false,capabilities.drop=["ALL"](네트워크 capability는NET_BIND_SERVICE만 추가 허용),seccompProfile.type in {RuntimeDefault, Localhost}, host namespace/Port/Path 금지,privileged=false,procMount=Default, ephemeral volume 화이트리스트.- NetworkPolicy는 namespace 내 매칭되는 Pod가 하나라도 있으면 그 Pod의 해당 방향 트래픽은 정책 합집합만 허용된다(그 외 default deny). 매칭되는 Pod가 없으면 기본은 allow-all이다.
- NetworkPolicy
from/to원소 내에서namespaceSelector와podSelector를 동일 엔트리 안에 두면 AND(교집합), 별도 엔트리로 두면 OR(합집합)로 계산된다. 이 차이가 cross-namespace 정책 버그의 1순위 원인이다. - Secret은 기본적으로 etcd에 base64로만 저장되므로 운영 클러스터는
EncryptionConfiguration(aescbc/aesgcm/KMS)을 필수로 구성한다. K3s는--secrets-encryption플래그로 aescbc provider를 활성화한다. - ServiceAccount token은 Pod에 기본 자동 마운트된다. 1.24부터는 time-bound projected token이 기본이다.
- RBAC는 additive-only이며
Role/RoleBinding(namespace) 우선,ClusterRole/ClusterRoleBinding은 예외적이다.
기본 규칙
1. 모든 application namespace는 PSA restricted enforce 라벨이 기본
namespace 생성 시 다음 라벨을 enforce 수준으로 붙인다(예외는 rule 3).
metadata:
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: v1.29
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/audit-version: v1.29
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/warn-version: v1.29
enforce: deny on violation (hard gate)audit: audit log 기록warn: kubectl 사용자에 경고- version은
latest대신 명시 버전 pin을 기본으로 한다. 업그레이드는 ADR로 관리한다. - PSP는 1.25에서 제거되었으므로 어떤 manifest/차트에도
policy/v1beta1 PodSecurityPolicy를 남기지 않는다.
2. Restricted 프로파일 전 필드 계약
application Pod/Deployment는 아래 전부를 만족해야 한다. 하나라도 빠지면 PSA가 reject한다.
spec.automountServiceAccountToken: false(API 호출 불필요 시)spec.securityContext.runAsNonRoot: truespec.securityContext.runAsUser: <non-zero numeric>(예:10001)spec.securityContext.runAsGroup: <non-zero numeric>(예:10001)spec.securityContext.fsGroup: <numeric>(volume 쓰기 필요 시)spec.securityContext.seccompProfile.type: RuntimeDefault(Pod 또는 컨테이너 레벨)- 컨테이너
securityContext:allowPrivilegeEscalation: falseprivileged: falsereadOnlyRootFilesystem: truerunAsNonRoot: truecapabilities.drop: ["ALL"]capabilities.add: 비어있거나["NET_BIND_SERVICE"]만 허용
- Pod spec 금지 필드:
hostNetwork,hostPID,hostIPC,hostUsers=false,hostPathvolume,hostPort,ephemeralContainers에 한해privileged
3. Platform 예외 namespace는 ADR 문서 + 좁은 enforce
kube-system, ingress-traefik, vault, cert-manager, vault-secrets-operator, monitoring 등은 baseline 또는 privileged 프로파일이 필요할 수 있다. 예외는 다음을 manifest에 고정한다.
- namespace label은 필요한 최저 수준(
baseline우선,privileged는 CNI/CSI/Node-exporter에 한정) - 예외 근거 ADR 링크 annotation:
platform.example.com/psa-exception: "ADR-0042" - 예외 받는 구체 필드(예:
hostNetwork,CAP_NET_ADMIN)만 열고 나머지는 restricted
4. privileged / host namespace 기본 금지
application Pod는 privileged: true, hostNetwork, hostPID, hostIPC, hostPath, hostPort를 쓰지 않는다. 필요성이 있으면 rule 3의 platform 예외로 이관한다.
5. runAsNonRoot + numeric UID 강제
image가 USER 지시어로 non-numeric user만 지정해도 PSA는 runtime에 UID 확인이 불가능하면 reject할 수 있다. 항상 numeric UID를 명시한다(권장 범위: 10000–65535). UID 0은 전 영역 금지.
6. seccompProfile은 RuntimeDefault 우선
Pod 레벨 seccompProfile.type: RuntimeDefault를 기본으로 두어 모든 컨테이너에 상속. 특정 컨테이너가 custom profile이 필요하면 Localhost로 개별 선언하고 profile 파일 경로를 문서화한다. kubelet --seccomp-default=true를 클러스터 플래그로 검토한다.
7. capabilities drop-first
drop: ["ALL"]이 기본이다. 추가 허용 화이트리스트는 NET_BIND_SERVICE뿐. CAP_SYS_ADMIN, CAP_NET_ADMIN, CAP_SYS_PTRACE, CAP_NET_RAW는 platform 예외에서만 허용한다.
8. readOnlyRootFilesystem + writable emptyDir
모든 application container는 readOnlyRootFilesystem: true. 쓰기 경로는 emptyDir(가능하면 medium: Memory, sizeLimit 명시)로 분리한다. /tmp, /var/run, 앱 cache 경로는 별도 volume mount.
9. ServiceAccount는 workload 1:1, 토큰 기본 비마운트
defaultSA 사용 금지. namespace당 Deployment별 전용 SA 생성.automountServiceAccountToken: false를 Pod spec에 기본 명시.- Kubernetes API를 호출해야 하는 Pod만
true+ projected token volume을 명시적으로 선언.
10. RBAC는 namespace Role + RoleBinding 우선
*resource/verb 금지.secrets리소스는get+resourceNames명시.list/watch는 controller/operator에만 허용.ClusterRole/ClusterRoleBinding은 CRD controller, metrics scraper, admission webhook 같은 cluster-wide 컴포넌트에 한정하고 subject는 platform SA로 제한.system:mastersgroup binding 금지.
11. NetworkPolicy default-deny + 명시적 allow
운영 namespace는 생성 직후 다음 3종을 배포한다.
- default-deny-all (ingress + egress)
- allow-dns-egress (to
kube-system의k8s-app=kube-dns, UDP/TCP 53) - allow-from-ingress-controller (namespaceSelector=
ingress-traefik+ podSelector=app.kubernetes.io/name=traefik)
추가 allow는 서비스별 요구(예: DB, Redis, Vault, S3, OIDC endpoint)에 맞춰 한 엔트리 = AND, 여러 엔트리 = OR 규칙을 지켜 작성한다.
12. NetworkPolicy enforcement 전제 검증
- K3s 기본 CNI(flannel) + kube-router policy controller가 실제로 enforce하는지 배포 후 negative test 필수.
- Calico/Cilium 전환 시
--disable-network-policy+--flannel-backend=none조합을 ADR로 관리. - 운영 정책 변경 후에는 synthetic probe(
netshootPod)로 deny/allow 경로를 모두 검증한다.
13. Secret at-rest encryption은 운영 필수
- API Server
--encryption-provider-config지정:aescbc또는 KMS provider(권장: AWS KMS/GCP KMS/HashiCorp Vault Transit). - K3s는
--secrets-encryption플래그 활성화(aescbc). 기존 Secret은kubectl get secrets -A -o json | kubectl replace -f -로 재암호화. - etcd 백업 자체도 별도 암호화 저장.
14. 민감정보 delivery 경로 표준화
1순위: Vault Secrets Operator(VSO)가 Vault → K8s Secret으로 sync → envFrom/volume 2순위: External Secrets Operator(ESO) + AWS/GCP Secret Manager 3순위: CSI Secret Store Driver (volume mount only, K8s Secret 미생성) 4순위: SealedSecrets / SOPS (GitOps + encrypted-at-rest in Git) 모든 경로는 config-and-secrets 문서의 선택 기준 표를 따른다.
15. Image supply chain 통제
imagePullPolicy: Always는 mutable tag(latest,main)에만. 운영은 digest pinimage: registry.example.com/auth-server@sha256:<64hex>을 기본으로.kubernetes.io/dockerconfigjson타입imagePullSecret은 workload SA에spec.imagePullSecrets로 연결.- Private registry만 허용:
ImagePolicyWebhook또는 Kyverno/Gatekeeper로 public Docker Hub deny. - Image signing(cosign)과 SBOM 요구를 CI에서 강제, cluster-level로는
ClusterImagePolicy(sigstore policy-controller) 검토.
16. health/metrics/admin endpoint 내부 전용 기본
/metrics는 Service 별도 포트(name: metrics) + NetworkPolicy로monitoringnamespace Prometheus만 허용./actuator/*,/admin,/debug/pprof는 Ingress 경로에 노출 금지.- Keycloak
/admin/,/metrics,/health는 외부 공개 금지 기본값(network-ingress-tls 문서와 함께 enforce).
17. audit + policy together
- API Server
--audit-policy-file로 최소한 Secret/RBAC/PodSecurity violation을RequestResponse수준으로 기록. - PSA
audit라벨을 모든 namespace에 붙여 violation을 audit log로 수집. - Kyverno 또는 Gatekeeper로 PSA 밖의 policy(resource limits, image registry, required labels)를 보완.
18. Pod spec 기타 하드닝 기본
resources.limits.cpu,resources.limits.memory필수. memory limit 없는 Pod는 OOM-Kill 전파 위험.terminationGracePeriodSeconds명시(기본 30은 서비스별로 재조정).readinessProbe+livenessProbe분리.startupProbe는 JVM/느린 기동 앱에 필수.topologySpreadConstraints또는podAntiAffinity로 node 단일 장애 블라스트 반경 축소.
19. 현재 스택 기본 권장안
auth-server / test-server / keycloak / migration-flyway
- namespace PSA:
restrictedenforce pinned tov1.29 - SA: 서비스별 1:1,
automountServiceAccountToken: false - Secret: VSO로 Vault → K8s Secret sync
- NetworkPolicy: default-deny + dns + ingress + db + vault + metrics-scrape
ingress-traefik
- namespace PSA:
baseline(예외 ADR 기록) hostNetwork금지(ServiceLB 또는 MetalLB 사용), hostPort는 80/443/8443만- CAP_NET_BIND_SERVICE만 add
vault / vault-secrets-operator
- namespace PSA:
baseline(Vault server IPC_LOCK 필요) - storage PVC는 encrypted StorageClass
- unseal key는 cluster 밖(HSM/KMS auto-unseal)
db (Postgres/MySQL)
- namespace PSA:
restricted(StatefulSet, fsGroup 999) - NetworkPolicy: application SA의 Pod만 5432 허용
- backup은 별도 namespace의 Job에서 수행, 해당 Job에만 read-only secret 부여
프로젝트 기준 요약
- 모든 app namespace에
pod-security.kubernetes.io/enforce: restricted+ pinned version 라벨 부착 - PSP는 제거되었으므로 어디에도 남기지 않는다
- Restricted 프로파일 전 필드 계약을 Pod/Deployment가 만족
- default-deny NetworkPolicy + DNS allow + ingress allow + metrics-scrape allow를 namespace 기본 세트로 배포
namespaceSelector+podSelectorAND/OR 차이를 정확히 사용- Secret at-rest encryption + VSO(1순위) delivery 표준화
- 운영 이미지는 digest pin, private registry only
- audit policy + Kyverno/Gatekeeper 보완 정책과 함께 운영