RKE2 CySec Secret/PKI 공급 경계¶
이 변경은 bloodbag-cysec namespace의 실제 Secret/PKI를 반복 가능하고 감사 가능하게 공급하기 위한 lane을 등록한다. 기준은 GitOps tirosh-chain/tirosh-gitops source-revision b8408ca2e1db0cea8db8a9fe804fef824b41a2b2(canonical activation overlay merge)의 소비 계약과, 기존 registration-only preflight다. 계약은 SHA-256 digest(2bcc26f6...)로 고정해 source contract drift를 fail-closed로 감지한다.
핵심 원칙은 하나다. 비밀값(secret/cert/password byte)은 Git·로그·명령 출력·테스트 fixture 어디에도 평문으로 쓰지 않는다. 공급은 환경 변수를 통한 external-encrypted material에서만 이뤄지고, 그 material이 실제로 없으면 생성자를 임의로 만들지 않고 fail-closed로 막는다.
계약 요약 (source-bound)¶
registration(sites/tirosh-home/rke2/bloodbag-cysec-secret-pki-registration.yaml)은 다음을 고정한다.
namespace: bloodbag-cysecsource: tirosh-chain/tirosh-gitops,source-revision: b8408ca2e1db0cea8db8a9fe804fef824b41a2b2contract-state: registration-only-blocked,activation-ready: falserotation-owner-state: blocked-unassigned,rotation-runbook-state: blocked-unregistered,issuer-state: blocked-unassignedsecret-git-state: absent-by-design,pki-git-state: absent-by-design
Secret 22개와 principal 16개는 exact match로 검증된다.
- registry pull secret 1:
bloodbag-cysec-registry(kubernetes.io/dockerconfigjson,.dockerconfigjson) - PostgreSQL TLS 1:
bloodbag-postgres-tls(SANbloodbag-postgres.bloodbag-cysec.svc) - Redis TLS 2:
bloodbag-signal-ingress-redis-tls,bloodbag-internal-processing-redis-tls(각 SAN은 해당 headless service FQDN) - nginx Ingress TLS 1:
bloodbag-cysec-product-api-tls(SANcysec.bloodbag.internal.tirosh.ai) - PostgreSQL bootstrap 1:
bloodbag-postgres-bootstrap(admin-password) - DB principal 4:
bloodbag_product_api,bloodbag_inference_context,bloodbag_super_admin_bootstrap,bloodbag_migration(blocked,bloodbag-database-migration의database-password) - Redis principal 12: signal-ingress 4 + internal-processing 8 (각각
passwordkey 1개, ACL user와 1:1)
실제 infra 조사 결과¶
- rke2/gpu 클러스터에는 SOPS/age/Vault/ExternalSecrets/cert-manager/sealed-secrets/ClusterIssuer가 설치되어 있지 않다. 확인된 Secret/PKI 관련 CRD는 없다.
bloodbag-cysecnamespace의 workload(Deployment/StatefulSet/Service/SA)는 GitOps로 이미 배포되어0/0상태로 Secret을 기다리고 있다. Secret은 아직 하나도 없다.- 기존 dev/stg 공급은
sites/tirosh-home/secrets.toml+ 환경변수 +kubectlapply 방식이다. 이 매커니즘은 cysec에도 재사용 가능하지만, 22개 lease Secret은 별도external-encrypted계약을 따르므로 전용 material registry로 분리한다. 기존 dev/stg entry와 StorageClass, cluster context는 불변이다. Demo Console adjunct Secret(token·Product API User)만secrets.toml에 추가하며, 절차는 RKE2 CySec Demo 운영자 Secret을 본다.
material registry¶
sites/tirosh-home/rke2/bloodbag-cysec-secret-pki-material.toml은 22개 Secret의 환경변수 참조만 선언한다. 값은 절대 없고, BLOODBAG_CYSEC_* 환경변수 이름만 있다. 실값은 apply 시점에 외부(운영자의 승인된 vault/secret manager)에서 주입한다.
- TLS
kubernetes.io/tls4개:BLOODBAG_CYSEC_{POSTGRES,SIGNAL_INGRESS_REDIS,INTERNAL_PROCESSING_REDIS,PRODUCT_API}_TLS_{CRT,KEY,CA} - registry 1개:
BLOODBAG_CYSEC_REGISTRY_USERNAME,BLOODBAG_CYSEC_REGISTRY_TOKEN(serverghcr.io) - PostgreSQL bootstrap/DB/Redis password 17개:
BLOODBAG_CYSEC_*_PASSWORD(각 binding 1개)
공개 Make Interface¶
make rke2/cysec-secret-pki/preflight SITE=tirosh-home
make rke2/cysec-secret-pki/plan SITE=tirosh-home
make rke2/cysec-secret-pki/apply SITE=tirosh-home
make rke2/cysec-secret-pki/verify SITE=tirosh-home
make rke2/cysec-secret-pki/rollback SITE=tirosh-home \
RKE2_BLOODBAG_CYSEC_SECRET_PKI_ROLLBACK_CONFIRMATION=bloodbag-cysec
preflight: registration만 검증. source/schema/keys/SANs/principals/blocked 상태 exact match, YAML alias·merge·duplicate key 거부, symlink·2MiB 경계·TOCTOU 방지.plan: material registry가 계약과 정확히 일치하는지(strict closed schema) 검증하고, 값은 모두REDACTED로 렌더링한다.materialReady가 false면 빠진 환경변수 이름만 보고한다.apply: 정확한 context(tirosh-home/rke2/gpu)·pinned API server(https://172.31.0.20:6443)·검증된 kubeconfig descriptor를 요구하고, 모든 kubectl 호출에--kubeconfig/--context를 명시한다.O_EXCLmode0400 operation lock을 획득한 뒤 material/TLS/docker semantics를 모두 preflight하고, 기존 Secret은 resourceVersion CAS(kubectl replace), 부재 Secret은 create-only(kubectl create)로 단일 writer를 보장한다(AlreadyExists race는 overwrite 없이 실패). 22개 전체에--dry-run=server를 통과시킨 뒤 하나씩 적용하며, pre-applyuid/resourceVersion을 metadata-only receipt(JSON, mode0400, 값 없음)에 기록한다. partial failure는 terminalfailedreceipt에 적용된 이름만 남기고 자동 rollback은 하지 않는다.verify: 기본은 metadata-only다. 클러스터 Secret의 name/type/key names/managed-by label만 조회한다(go-template,-o json으로 value를 읽지 않는다). value equivalence는--verify-values로 명시할 때만 정확히 22개 name을 하나씩(개별 GET) 로컬 digest로 비교한다.rollback: 비활성화되어 있다. prior Secret byte를 안전한 authorized backup에 보관하지 않으므로, 현재 값을 재-materialize해 "rollback"으로 위장하지 않는다.--confirm이후에도 항상 fail-closed로 종료하며 수동 복구를 안내한다. 복구 절차는 아래 "수동 복구"를 참고한다.
fail-closed 경계¶
- 외부 material(환경변수/파일)이 하나라도 없으면
apply는 non-zero로 종료하고 빠진 이름만 출력한다. 임의 생성은 하지 않는다. - TLS 인증서를 발급해 줄 PKI issuer(cert-manager 등)가 설치·승인되어 있지 않으므로, 인증서 material은 외부에서 주입돼야 한다. issuer는 만들지 않는다.
- TLS material은
cryptography로 PEM parse, key/cert match, CA chain(CABasicConstraints ca=true+keyCertSign+ validity), SAN exact equality(추가 SAN 금지), EKU exact[serverAuth], ECDSA P-256, validity/rotation overlap을 검증한다. password/registry는 nonempty·entropy bound·ghcr.ioexact endpoint를 검증한다. - material registry는 closed schema다. root/secret/dockerconfigjson/환경변수 inline table의 모든 depth에서 임의 field, annotation, inline material, registry
email, 환경변수 cross-principal 재사용, 32 refs 불일치를 거부한다. 파일 입력은 hardlink/parent symlink/FIFO/device/TOCTOU를 거부한다. - rotation owner/runbook/issuer assignment는 저장소 근거가 없어
blocked로 유지한다. 이는 외부 blocker이며, 결정 없이 추측하지 않는다. plan/apply/verify/rollback모두 값 평문을 stdout/stderr/log에 쓰지 않는다. stringData 값의 C0 control(0x00-0x1f)은 LF/TAB을 제외하고 전부 거부한다.
수동 복구 (rollback 비활성화)¶
prior Secret byte를 안전한 authorized backup에 보관하지 않으므로, 자동 rollback은 제공하지 않는다. 잘못된 적용을 되돌리려면:
- 문제가 된 Secret 22개 중 어떤 name이 오염됐는지
make rke2/cysec-secret-pki/verify로 확인한다. - 승인된 material source(offline CA/secret manager)에서 원하는 버전의 값을 다시 준비한다.
make rke2/cysec-secret-pki/apply로 재적용한다(기존 Secret은 resourceVersion CAS를 통과해야 한다).
자동 롤백을 원하면 prior byte의 authorized backup 체계를 별도로 승인·구현한 뒤 rollback lane을 다시 활성화해야 한다.
nginx Ingress 경로¶
bloodbag-cysec의 product-api는 nginx Ingress를 통해 내부 hostname cysec.bloodbag.internal.tirosh.ai로 노출된다. 이 hostname 전용 TLS leaf가 새 계약에 추가됐다.
- Ingress hostname:
cysec.bloodbag.internal.tirosh.ai(SAN exact, 추가 SAN 금지) - TLS Secret:
bloodbag-cysec-product-api-tls(kubernetes.io/tls,tls.crt/tls.key/ca.crt) - leaf: ECDSA P-256 / SHA-256, EKU exact
[serverAuth], full-chain(intermediate + root), validity 90일, rotation overlap 30일(T-30 갱신) - Ingress manifest는 tirosh-gitops가 관리한다. 여기서는 TLS Secret의 exact material만 공급하며, hostname↔Secret 바인딩은
bloodbag-cysec-product-api-tls의 SAN으로 고정된다.
# tirosh-gitops가 렌더링하는 Ingress (참고용, 이 repo에서 관리하지 않음)
spec:
tls:
- hosts: [cysec.bloodbag.internal.tirosh.ai]
secretName: bloodbag-cysec-product-api-tls
rules:
- host: cysec.bloodbag.internal.tirosh.ai
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: bloodbag-product-api
port:
name: http
이 leaf도 나머지 3개 leaf와 동일한 plan/apply/verify/rotate 경계를 통과하며, SAN exact·serverAuth·P-256/SHA256·full-chain·rotation/overlap·descriptor-safe material 경계를 재사용한다.
검증¶
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
테스트는 exact schema, duplicate/unknown/symlink/path/TOCTOU, wrong namespace/context, dry-run server 검증, no shell/env secret leakage를 Red→Green으로 고정한다.
현재 상태와 다음 단계¶
- registration preflight와 공급 lane(plan/apply/verify/rollback)은 모두 구현·검증되어 있다.
- 실제 배포는 외부 material(인증서·password·registry token)과 PKI issuer 정책이 결정된 뒤에만 진행한다. material 없이
apply를 실행하면 fail-closed가 정상이다.