184 lines
12 KiB
Markdown
184 lines
12 KiB
Markdown
# 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를 명시한다(권장 범위: 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, 토큰 기본 비마운트
|
||
- `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 보완 정책과 함께 운영
|