RKE2 CySec latest provisioning

이 문서는 bloodbag-cysec-latest GitOps 24-Secret 공급 lane이다. signed 기준 환경 bloodbag-cysec(22-Secret)와 bloodbag-dev 계약은 변경하지 않는다.

latest는 항상 TEMPORARY NON-CLINICAL이며 regulatoryEvidenceEligible=false다. 이 lane은 규제 PASS/E2 증적을 만들지 않는다.

권위는 GitOps origin/main PR #151 계약 문서다.

  • 경로: clusters/tirosh-home/rke2-gpu/apps/bloodbag/cysec-latest/infra-readiness-contract.json
  • GitOps-facing contractDigest: sha256:dcb89695c9ce0d9378e5c4509637a238e7695f56aac08b74cd2f12d8b56011e6
  • secretNameSetHash: sha256:e939ca14ba777daf315e49b4208c24e5dd68c06a52f96c582de47729b38382c0
  • secretKeySetHash: sha256:5276cf9cf2b2687479d6f7574460d5f9fb396d0aef87f394932322fcc0712687

stale digest d0e4874, f4893ed, 7-Secret 5b7008는 권위가 아니다. Infra 내부 mapping digest는 GitOps digest와 분리해 pin한다.

정정 기록:

관련 GitOps 이슈: tirosh-gitops#150. Namespace bloodbag-cysec-latest는 GitOps bootstrap이 이미 소유하고 sync한다. Infra는 이 Namespace를 생성·변경·adopt하지 않으며 app.kubernetes.io/managed-by=infra-tools를 요구하지 않는다. 없으면 preflight/apply/verify는 fail-closed다.

계약 요약

  • namespace: bloodbag-cysec-latest (GitOps bootstrap 소유)
  • workload Secret 24개, 모두 이 namespace. GitOps 이름을 쓰며 -latest suffix를 붙이지 않는다.
  • bloodbag-secret, Product API route TLS, writeback은 workload24 밖이다. Product API route TLS는 runtime green 전까지 deferred.
  • registry: bloodbag-cysec-registry (kubernetes.io/dockerconfigjson, ghcr.io)
  • TLS leaf 3개 / SAN 3개: postgres, signal-ingress Redis, internal-processing Redis. 전용 latest ECDSA P-256 CA.
  • StorageClass: bloodbag-cysec-latest-local-retain (cluster-scoped). PVC는 만들지 않는다.
  • writeback: 향후 K3s lifecycle (tirosh-home/k3sargocd/argocd-image-updater-gitops-writeback-cysec-latest). GPU RKE2에 적용하지 않는다.
  • 전용 계약: sites/tirosh-home/rke2/bloodbag-cysec-latest-provisioning.toml. sites/tirosh-home/secrets.toml에는 latest runtime material을 추가하지 않는다.

exact latest24 names/key sets는 GitOps JSON 순서를 유지한다.

  1. bloodbag-cysec-registry {.dockerconfigjson}
  2. bloodbag-postgres-bootstrap {admin-password}
  3. bloodbag-postgres-tls {ca.crt,tls.crt,tls.key}
  4. bloodbag-database-migration {database-user,database-password}
  5. bloodbag-product-api-database {password}
  6. bloodbag-product-api-key-material {session-key,csrf-key,patient-key}
  7. bloodbag-super-admin-bootstrap-database {password}
  8. bloodbag-super-admin-bootstrap-account {username,display-name,password}
  9. bloodbag-result-writer-database {database-url}
  10. bloodbag-signal-relay-source {password}source-url 없음
  11. bloodbag-signal-relay-source-tls {ca.crt}
  12. bloodbag-signal-ingress-redis-tls {ca.crt,tls.crt,tls.key}
  13. bloodbag-signal-ingress-redis-signal-ingress-publisher {password}
  14. bloodbag-signal-ingress-redis-signal-decoder {password}
  15. bloodbag-signal-ingress-redis-health {password}
  16. bloodbag-signal-ingress-redis-acl-audit {password}
  17. bloodbag-internal-processing-redis-tls {ca.crt,tls.crt,tls.key}
  18. bloodbag-internal-processing-redis-signal-decoder-internal {password}
  19. bloodbag-internal-processing-redis-inference-orchestrator {password}
  20. bloodbag-internal-processing-redis-result-writer-lifecycle {password}
  21. bloodbag-internal-processing-redis-result-writer-worker {password}
  22. bloodbag-internal-processing-redis-product-api {password}
  23. bloodbag-internal-processing-redis-health {password}
  24. bloodbag-internal-processing-redis-acl-audit {password}

이전 7-Secret 초안과의 관계

초기 Infra 초안은 application dotenv 하나(bloodbag-secret), -latest suffix Secret 이름, Product API TLS, GPU 클러스터 writeback, Infra 소유 Namespace를 가정했다. 그 모델은 GitOps PR #151 24-Secret 권위와 충돌하므로 폐기한다. 유용한 잔여 규칙은 유지한다.

  • dotenv는 shell source하지 않는다. 값은 출력하지 않는다.
  • create-only material bootstrap, mode 0600/0700/0400, staging 후 atomic rename.
  • signed bloodbag-cysec 22-Secret lease와 bloodbag-dev registry는 건드리지 않는다.
  • ImageUpdater per-image writeback을 이 Secret에 연결하지 않는다.

Material bootstrap

운영자 승인 임시 예외: registry dockerconfig만 기존 GHCR_USERNAME/GHCR_PULL_TOKEN byte를 재사용한다. Kubernetes Secret 이름은 bloodbag-cysec-registry다. 이 예외는 identity 분리 완료가 아니며 dedicated credential 발급 후 rotation이 필요하다. signed-lane PKI/password byte는 복사하지 않는다.

전제:

  • credential dotenv (GHCR_USERNAME, GHCR_PULL_TOKEN, BLOODBAG_CYSEC_LATEST_CA_PASSPHRASE)
  • 필수 operator dotenv (아래 2개 BLOODBAG_CYSEC_LATEST_* 키, 값 문법 포함)
  • 출력 latest24 dotenv, source-redis adjunct dotenv, issuer 디렉터리는 아직 없어야 한다 (create-only)
  • 입력 파일은 현재 사용자 소유, mode 0600, symlink/hardlink 금지

operator dotenv가 없거나 키가 비어 있으면 bootstrap은 생성/커밋 전에 fail-closed다. create-only이므로 부분 출력은 남기지 않는다. 예전 6키 operator 파일이 남아 있으면 unexpected operator keys로 실패한다.

bootstrap이 생성하는 값:

  • 전용 latest ECDSA P-256 CA와 leaf 3개 (위 SAN 3개)
  • Opaque password 16개 (token_urlsafe(32)): postgres admin, migration password, product-api DB, super-admin DB, relay-source, signal Redis 4, internal Redis 7
  • SuperAdmin username/display 리터럴 tirosh
  • database-user 리터럴 bloodbag_migration
  • product-api key material 3개 (token_urlsafe(32)): session-key, csrf-key, patient-key
  • registry dockerconfig (위 재사용 예외)
  • 별도 source-redis CA와 Opaque server TLS/health Secret 재료 (문서 14)

bootstrap이 발명하지 않는 운영자 키 (latest prefix, 이름만 보고 값은 출력하지 않음):

  • BLOODBAG_CYSEC_LATEST_SUPER_ADMIN_BOOTSTRAP_ACCOUNT_PASSWORD (non-promotable test material)
  • BLOODBAG_CYSEC_LATEST_RESULT_WRITER_DATABASE_URL (consumer username unresolved; Secret apply ≠ DB provisioned)
make rke2/cysec-latest/material/bootstrap SITE=tirosh-home \
  RKE2_BLOODBAG_CYSEC_LATEST_CREDENTIAL_ENV_FILE=/absolute/path/to/credentials.env \
  RKE2_BLOODBAG_CYSEC_LATEST_OPERATOR_ENV_FILE=/absolute/path/to/operator.env \
  RKE2_BLOODBAG_CYSEC_LATEST_OUTPUT_ENV_FILE=/absolute/path/to/bloodbag-cysec-latest.env \
  RKE2_BLOODBAG_CYSEC_LATEST_SOURCE_REDIS_OUTPUT_ENV_FILE=/absolute/path/to/bloodbag-cysec-latest-source-redis.env \
  RKE2_BLOODBAG_CYSEC_LATEST_ISSUER_DIR=/absolute/path/to/cysec-latest-issuer

[CHANGES]: 로컬 secret 파일을 만든다. 클러스터 명령을 실행하지 않는다. 터미널 JSON은 경로와 키 이름/개수만 출력하고 값은 출력하지 않는다.

출력 안전:

  • 출력 dotenv는 O_EXCL/create-only, mode 0600. overwrite, symlink, source alias, issuer 내부 경로를 거부한다.
  • issuer 디렉터리는 mode 0700, ca.key/ca.crt/ca-metadata.json은 mode 0400. ca.keyBestAvailableEncryptionBLOODBAG_CYSEC_LATEST_CA_PASSPHRASE로 암호화한다. leaf TLS key는 Kubernetes TLS Secret 요구로 비암호화다. passphrase는 출력 dotenv와 metadata에 복사하지 않는다.
  • staging 후 atomic rename. 실패 시 최종 부분 출력을 남기지 않는다.
  • metadata receipt는 이름·SAN·시각만 담고 값이나 private-key digest를 담지 않는다.

운영자 값 검사 (값을 출력하지 않음):

  • super-admin username/display는 생성된 리터럴 tirosh
  • super-admin password: GitOps 12–256 byte single-line, non-promotable test material. 값은 문서/로그/JSON에 쓰지 않는다
  • result-writer database-url: postgresql+psycopg://<user>:<password>@bloodbag-postgres.bloodbag-cysec-latest.svc:5432/bloodbag?sslmode=verify-full&sslrootcert=/run/database-trust/ca.crt, fragment 금지. URL 적용은 PostgreSQL 프로비저닝이 아니다
  • relay-source password와 client-trust CA는 bootstrap이 생성한다. source-redis adjunct는 문서 14

MacBook 실행 절차

기존 BLOODBAG .env를 저장소로 복사하거나 shell source하지 않는다. bootstrap이 만든 출력 dotenv 경로를 --env-file / Make 변수로만 넘긴다.

make rke2/cysec-latest/preflight SITE=tirosh-home \
  RKE2_BLOODBAG_CYSEC_LATEST_ENV_FILE=/absolute/path/to/bloodbag-cysec-latest.env

make rke2/cysec-latest/plan SITE=tirosh-home \
  RKE2_BLOODBAG_CYSEC_LATEST_ENV_FILE=/absolute/path/to/bloodbag-cysec-latest.env

make rke2/cysec-latest/storage-class/plan SITE=tirosh-home
make rke2/cysec-latest/storage-class/apply SITE=tirosh-home
make rke2/cysec-latest/storage-class/verify SITE=tirosh-home

make rke2/cysec-latest/apply SITE=tirosh-home \
  RKE2_BLOODBAG_CYSEC_LATEST_ENV_FILE=/absolute/path/to/bloodbag-cysec-latest.env

make rke2/cysec-latest/verify SITE=tirosh-home

Secret-only 실행에서는 네 lifecycle 모두 RKE2_BLOODBAG_CYSEC_LATEST_STORAGE_CLASS_MANIFEST=를 명시한다. 이때 plan은 StorageClass를 렌더링하지 않고 apply/verify는 StorageClass API를 조회하거나 변경하지 않는다. StorageClass가 필요한 경우 위 storage-class/* lifecycle을 별도로 사용한다.

  • preflight [SAFE]: 계약 closed schema, GitOps hash pin, dotenv 문법/파일 경계, 36개 BLOODBAG_CYSEC_LATEST_* 키 존재. 값은 출력하지 않는다. 빠진 키는 이름만 보고 nonzero. kubeconfig가 있으면 GitOps Namespace 존재를 확인하고 없으면 fail-closed. Secret get은 Namespace가 있을 때만.
  • plan [SAFE]: 선택된 StorageClass와 Secret을 REDACTED로만 렌더링한다. Namespace 매니페스트를 만들지 않는다. 운영자 키가 없어도 blocked/redacted plan을 낼 수 있다. kubectl diff를 쓰지 않는다.
  • apply [CHANGES]: apply-readiness가 빠진 키 이름만 보고 실패할 수 있다. 그 다음 Namespace를 조회하고 없으면 중단한다. 없는 namespace에서 kubectl get secret이나 namespaced Secret server-dry-run을 하지 않는다. StorageClass 계약을 넘긴 경우에만 cluster-scoped 객체를 조회·적용한다. Secret-bearing 임시 파일과 kubectl diff를 쓰지 않는다.
  • verify [SAFE]: GitOps Namespace canonical label/annotation, 선택된 StorageClass, Secret type/key names, and tirosh.ai/material-digest lowercase 64-hex shape만 확인한다. 값을 읽거나 digest byte를 비교하지 않는다. extra Argo CD tracking label은 selfHeal과 싸우지 않기 위해 허용한다.
  • storage-class/*: cluster-scoped Retain 클래스만. PVC를 만들지 않는다. 기존 객체가 있으면 app.kubernetes.io/managed-by=infra-tools가 없으면 dry-run/apply 전에 fail-closed.

GitOps ownership 경계

  • Namespace 생성·label·Pod Security·tirosh.ai/ownership=bootstrap·argocd.argoproj.io/sync-options=Prune=false,Delete=false는 GitOps bootstrap 소유다.
  • Infra는 이 Namespace를 create/apply/adopt하지 않는다. 없으면 fail-closed.
  • signed bloodbag-cysec Application, 22-Secret lease, signed StorageClass bloodbag-cysec-local-retain, bloodbag-dev registry 계약은 이 lane의 대상이 아니다.
  • ImageUpdater per-image writeback을 이 Secret에 연결하지 않는다. 이미지 8개는 GitOps 단일 커밋 경로(#150)가 담당한다.

canonical GitOps Namespace metadata (origin/main):

  • labels: app.kubernetes.io/name=bloodbag, app.kubernetes.io/part-of=bloodbag, tirosh.ai/environment=cysec-latest, tirosh.ai/ownership=bootstrap, tirosh.ai/regulatory-evidence-eligible=false, tirosh.ai/clinical-use=false, Pod Security restricted enforce/audit/warn
  • annotations: argocd.argoproj.io/sync-options=Prune=false,Delete=false, tirosh.ai/temporary-non-clinical=true

RKE2 / K3s split

RKE2 GPU (tirosh-home/rke2/gpu)가 소유하는 것:

  • latest24 Secret (namespace bloodbag-cysec-latest)
  • StorageClass bloodbag-cysec-latest-local-retain

K3s (tirosh-home/k3s)가 향후 소유하는 것 (Phase 1 비범위, GPU에 적용 금지):

  • argocd/argocd-image-updater-gitops-writeback-cysec-latest
  • GitOps readiness status=ready/signing/dispatch

StorageClass

signed analog bloodbag-cysec-local-retain과 같은 형태이며 이름은 latest 전용이다.

  • provisioner rancher.io/local-path
  • reclaimPolicy: Retain
  • volumeBindingMode: WaitForFirstConsumer
  • allowVolumeExpansion: false
  • non-default (storageclass.kubernetes.io/is-default-class=false)
  • cluster-scoped, PVC 생성 없음

no-apply / 비범위 (Phase 1)

이 문서는 클러스터 apply를 강제하지 않는다. GitOps overlay는 blocked-awaiting-external-provisioning 상태다. 구현은 존재하지만 운영 apply는 GitOps Namespace가 있고 운영자 dotenv가 완전할 때만 수행한다.

명시적 비범위:

  • GitOps workload 합성 또는 GitOps 저장소 수정
  • CySec 개발 URL 전환
  • signed bloodbag-cysec 22-Secret 자동 갱신
  • Product API route TLS
  • K3s writeback 적용
  • readiness status=ready/signing/dispatch
  • 규제 증거 또는 PASS/E2 주장
  • .env를 git/CI artifact로 저장

실제 apply는 운영자가 MacBook에서 kubeconfig와 bootstrap 출력 dotenv를 준비한 뒤에만 수행한다.

rollback / recovery

prior Secret byte backup은 제공하지 않는다. 잘못된 적용을 되돌리려면:

  1. make rke2/cysec-latest/verify SITE=tirosh-home으로 metadata drift를 확인한다.
  2. 승인된 latest dotenv(BLOODBAG_CYSEC_LATEST_*)를 다시 준비한다.
  3. make rke2/cysec-latest/apply ...를 재실행한다. 동일 material이면 skip이고, 변경된 Secret만 replace한다.

signed bloodbag-cysec rollback lane과 섞지 않는다.

검증

make interface/check SITE=tirosh-home
UV_CACHE_DIR=.uv-cache uv run ruff check packages/infra-tools/src packages/infra-tools/tests
UV_CACHE_DIR=.uv-cache uv run pytest packages/infra-tools/tests
make docs/build