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

184 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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).
```yaml
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-system``k8s-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 보완 정책과 함께 운영