Add platform infrastructure configuration

This commit is contained in:
donghyeon-ka
2026-08-28 17:35:41 +09:00
parent fa76531e5b
commit 16c337bcc9
302 changed files with 83259 additions and 1 deletions
@@ -0,0 +1,2 @@
secrets-encryption: true
secrets-encryption-provider: aescbc
+34
View File
@@ -0,0 +1,34 @@
# k3s Secret 암호화 host drop-in
`40-secrets-encryption.yaml`은 Kubernetes API에 적용하는 Kustomize resource가
아니라, k3s server host의 설정 조각이다. bootstrap 담당자는 이 파일을 다음처럼
live drop-in 경로에 설치한다.
```sh
install -o root -g root -m 0644 \
infrastructure/security/k3s/40-secrets-encryption.yaml \
/etc/rancher/k3s/config.yaml.d/40-secrets-encryption.yaml
```
따라서 이 디렉터리에는 `kustomization.yaml`을 만들지 않는다. 이 파일은 정확히
`secrets-encryption``secrets-encryption-provider` 두 key만 가지며, Kubernetes
manifest metadata나 다른 host 설정을 넣지 않는다.
실제 적용 순서, validator 판정, 서버 token 취급, 재암호화 대기와 중단 조건은
[`bootstrap/manual/k3s-secret-encryption.md`](../../../bootstrap/manual/k3s-secret-encryption.md)를
따른다. 이 artifact 자체를 자동 삭제하거나 기존 운영 설정을 덮어쓰지 않는다.
## 로컬 recovery 하드웨어 계약
`local-recovery.env`는 로컬 암호화 recovery 저장소에 사용할 디스크 identity,
mount 경로와 용량 기준만 담는 비밀 없는 계약 파일이다. 이 파일은 shell에서
`source`하지 않는다. `scripts/lib/k3s-local-recovery.sh`의 parser가 허용된 key가
정확히 한 번씩 있는지, 각 행의 `KEY=VALUE` 형식과 값 문법이 맞는지를 확인한 뒤
데이터로만 읽는다. 알 수 없는 key, 중복·누락 key, 상대 장치 경로, 공백·인용부호·명령
치환을 포함한 값은 모두 실패 처리한다.
용량 helper는 Bash signed 64-bit 정수 범위를 넘기기 전에 실패한다. 내부 recovery
공간은 각 backup phase의 125%를 두 번과 10GiB 최소 여유로 계산한다. 외부 NTFS
공간 판정은 최종 container와 header-proof용 완전 할당 copy, 10GiB 최소 여유,
전체 용량의 20% 잔여 공간을 함께 예약한다. 이 단계는 순수 parser·계산만 제공하며
mount, format, package 설치, `cryptsetup`, k3s 변경을 수행하지 않는다.
+92
View File
@@ -0,0 +1,92 @@
# 플랫폼 Secret 계약
상태: Phase 1·Keycloak DB·Gitea OIDC·AIStor Secret 적용
이 디렉터리에는 Secret 값이나 Secret 매니페스트를 저장하지 않는다. 수동
초기 구축 스크립트가 터미널 또는 지정된 파일에서 값을 읽어 Kubernetes API로
직접 전달한다.
## Phase 1 Secret 계약
| Namespace | 이름 | 타입 | 키 | 소비자 |
| --- | --- | --- | --- | --- |
| `platform-data` | `gitea-db-credentials` | `kubernetes.io/basic-auth` | `username`, `password` | CloudNativePG `DatabaseRole` 및 최초 `initdb` |
| `gitea` | `gitea-db-credentials` | `kubernetes.io/basic-auth` | `username`, `password` | Gitea |
| `gitea` | `gitea-admin` | `kubernetes.io/basic-auth` | `username`, `password` | Gitea 초기 관리자 생성 |
두 namespace의 `gitea-db-credentials`는 동일한 `username=gitea`와 동일한
비밀번호를 가져야 한다. Kubernetes Secret은 namespace를 넘어서 참조할 수
없기 때문에 두 객체가 필요하다. `scripts/bootstrap/create-phase1-secrets.sh`
세 Secret이 모두 없을 때만 최초 생성하고, 모두 있으면 계약을 검증해 재사용하며,
일부만 존재하면 중단한다.
## Keycloak-only Secret 계약
| Namespace | 이름 | 타입 | 키 | 소비자 |
| --- | --- | --- | --- | --- |
| `platform-data` | `keycloak-db-credentials` | `kubernetes.io/basic-auth` | `username`, `password` | CloudNativePG `DatabaseRole` |
| `keycloak` | `keycloak-db-credentials` | `kubernetes.io/basic-auth` | `username`, `password` | Keycloak |
| `gitea` | `gitea-keycloak-oidc` | `Opaque` | `key`, `secret` | Gitea OAuth2 source |
두 DB Secret은 `username=keycloak`과 동일한 비밀번호를 가져야 하며
`platform-data` 객체에는 `cnpg.io/reload=true` 라벨이 필요하다.
`create-keycloak-secrets.sh`는 두 DB Secret만 two-or-none으로 생성·검증한다.
`configure-keycloak-gitea-oidc.sh`는 Keycloak의 기존 confidential client secret을
새로 회전하지 않고 Gitea namespace의 `gitea-keycloak-oidc`에 전달한다. `key`
client ID `gitea`이며 `secret` payload는 출력·디코드·문서화하지 않는다. 세 객체는
실제 적용됐고 타입과 정확한 key 이름만 검증했다.
```sh
bash scripts/bootstrap/create-keycloak-secrets.sh --generate --execute
bash scripts/bootstrap/configure-keycloak-gitea-oidc.sh --execute
```
Keycloak Operator가 만든 `keycloak/keycloak-initial-admin`도 실제 존재한다. 이
Secret은 저장소가 렌더하는 자격 증명 manifest가 아니다. named 관리자와 MFA를
검증한 뒤 초기 관리자의 보존·비활성화 정책을 별도 운영 절차로 결정한다.
## AIStor Secret 계약
| Namespace | 이름 | 타입 | 키 | 소비자 |
| --- | --- | --- | --- | --- |
| `aistor` | `minio-license` | `Opaque` | `minio.license` | AIStor Operator |
| `object-storage` | `aistor-root-configuration` | `Opaque` | `config.env` | AIStor ObjectStore |
두 AIStor Secret은 실제 적용됐다. `minio.license`는 사용자가 지정한 읽기 가능한
절대 regular file에서 직접 읽는다. `config.env`에는 강한 `MINIO_ROOT_USER`,
`MINIO_ROOT_PASSWORD` export를 각각 이중 따옴표 형식으로 저장한다.
```sh
export MINIO_ROOT_USER="<redacted>"
export MINIO_ROOT_PASSWORD="<redacted>"
```
실제 값은 이 문서나 Git에 기록하지 않는다. AIStor 전용
`create-aistor-secrets.sh`는 두 객체를 two-or-none으로 생성·검증하고, 현재
클러스터에서는 기존 값을 회전하지 않고 재사용한다.
기존 `create-phase2-secrets.sh`는 Keycloak DB 두 객체와 AIStor 두 객체를
four-or-none으로 관리한다. 현재 Keycloak DB 두 객체만 의도적으로 존재하므로 이
스크립트는 실행하지 않는다. Keycloak Secret을 삭제하거나 검사를 우회하지 않는다.
기존 four-or-none helper는 호환용으로만 남긴다. AIStor 작업에는 전용 helper만
사용하며 one-of-two 상태에서는 자동 삭제·보정·덮어쓰기 없이 중단한다.
## 공통 운영 규칙
- 평문, Base64 값, 실제 Secret YAML, 라이선스 본문, 개인 키를 Git에 커밋하지
않는다.
- 스크립트는 호출자가 `bash -x`를 사용해도 명령 추적을 즉시 비활성화한다.
- 최초 생성 경로의 임시 파일은 `mktemp` 전용 디렉터리에 `umask 077`로 만들고
정상 종료·오류·신호 종료 시 삭제한다.
- 비밀값 대신 권한이 제한된 임시 파일 경로만 `kubectl` 인자로 전달한다. 생성
YAML은 두 `kubectl` 프로세스 사이의 파이프로만 전달하며 터미널에 출력하지
않는다.
- Secret 이름과 키만 선언형 리소스에서 참조한다.
- `gitea-admin`은 Keycloak OIDC 도입 후에도 비상용 로컬 관리자 계정으로
유지한다. `initialOnlyRequireReset` 모드이므로 Secret 값만 바꿔도 이미 생성된
관리자 계정 비밀번호는 바뀌지 않는다.
- Argo CD 전환 전 별도 ADR로 SOPS, Sealed Secrets 또는 외부 Secret 동기화
방식을 선택한다.
- 초기 구축 스크립트는 DB 비밀번호, 관리자 비밀번호, AIStor root 자격 증명,
라이선스를 회전하지 않는다. 회전은 소비자 재시작과 실제 접속 검증을 포함한
별도 운영 절차로 수행한다.