Files
project-infra/docs/standards/infra/security-hardening.md
T

12 KiB
Raw Blame History

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 원소 내에서 namespaceSelectorpodSelector동일 엔트리 안에 두면 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: true
  • spec.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: false
    • privileged: false
    • readOnlyRootFilesystem: true
    • runAsNonRoot: true
    • capabilities.drop: ["ALL"]
    • capabilities.add: 비어있거나 ["NET_BIND_SERVICE"]만 허용
  • Pod spec 금지 필드: hostNetwork, hostPID, hostIPC, hostUsers=false, hostPath volume, 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를 명시한다(권장 범위: 1000065535). 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, 토큰 기본 비마운트

  • default SA 사용 금지. 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:masters group binding 금지.

11. NetworkPolicy default-deny + 명시적 allow

운영 namespace는 생성 직후 다음 3종을 배포한다.

  1. default-deny-all (ingress + egress)
  2. allow-dns-egress (to kube-systemk8s-app=kube-dns, UDP/TCP 53)
  3. 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(netshoot Pod)로 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 pin image: 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로 monitoring namespace 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: restricted enforce pinned to v1.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+podSelector AND/OR 차이를 정확히 사용
  • Secret at-rest encryption + VSO(1순위) delivery 표준화
  • 운영 이미지는 digest pin, private registry only
  • audit policy + Kyverno/Gatekeeper 보완 정책과 함께 운영