RKE2 BLOODBAG dev signal-source Redis Secrets

이 문서는 tirosh-gitops #257/#263과 PR #258 dest가 internal-push mode에서 기대하는 Secret registry와 additive legacy-live-v1 pair (bloodbag-dev-legacy-live-redis-v1, bloodbag-dev-legacy-live-policy-v1)를 다룹니다. 값은 Git에 두지 않으며, 유일한 소유자는 sites/tirosh-home/secrets.toml과 기존 infra-tools secret입니다. 아래 기존 five-secret 계약, 예시, rollback은 그대로 유지합니다.

하지 않는 것: Secret apply, cluster 변경, pull-source Secret, CySec material bootstrap 재사용, SMC/BLOODBAG_DEV_REDIS_PASSWORD 재사용, Cilium KPR/L2.

계약

Context tirosh-home/rke2/gpu, namespace bloodbag-dev. Secret type은 Opaque입니다.

ID Name Keys and required environment source
bloodbag-dev-signal-source-redis-tls bloodbag-signal-source-redis-tls tls.crt ← BLOODBAG_DEV_SIGNAL_SOURCE_REDIS_TLS_CERT; tls.key ← BLOODBAG_DEV_SIGNAL_SOURCE_REDIS_TLS_KEY; ca.crt ← BLOODBAG_DEV_SIGNAL_SOURCE_REDIS_TLS_CA
bloodbag-dev-signal-source-redis-vital-file-streamer bloodbag-signal-source-redis-vital-file-streamer password ← BLOODBAG_DEV_SIGNAL_SOURCE_REDIS_VITAL_FILE_STREAMER_PASSWORD
bloodbag-dev-signal-source-redis-external-producer bloodbag-signal-source-redis-external-producer password ← BLOODBAG_DEV_SIGNAL_SOURCE_REDIS_EXTERNAL_PRODUCER_PASSWORD
bloodbag-dev-signal-source-redis-signal-relay bloodbag-signal-source-redis-signal-relay password ← BLOODBAG_DEV_SIGNAL_SOURCE_REDIS_SIGNAL_RELAY_PASSWORD
bloodbag-dev-signal-source-redis-health bloodbag-signal-source-redis-health password ← BLOODBAG_DEV_SIGNAL_SOURCE_REDIS_HEALTH_PASSWORD

네 ACL password 입력은 서로 달라야 합니다. GitOps runtime이 users.acl을 만들 때 이 불변 조건을 강제합니다. TLS 서버 인증서 SAN은 정확히 다음 두 이름입니다.

source.dev.bloodbag.internal.tirosh.ai
bloodbag-signal-source-redis.bloodbag-dev.svc

PEM은 기존 env stringData 경로로만 넣습니다. 별도 secret tool이나 file source를 추가하지 않습니다. --from-literal과 --show-values는 사용하지 않습니다.

describe, redacted render, dummy dry-run, apply

로컬 미커밋 .env(mode 0600) 또는 export VAR="$(cat file)"로 값을 주입합니다. 값 자체는 출력하지 않습니다.

uv run --package infra-tools infra-tools secret describe \
  --site tirosh-home \
  --secret bloodbag-dev-signal-source-redis-tls

BLOODBAG_DEV_SIGNAL_SOURCE_REDIS_TLS_CERT=dummy \
BLOODBAG_DEV_SIGNAL_SOURCE_REDIS_TLS_KEY=dummy \
BLOODBAG_DEV_SIGNAL_SOURCE_REDIS_TLS_CA=dummy \
uv run --package infra-tools infra-tools secret render \
  --site tirosh-home \
  --secret bloodbag-dev-signal-source-redis-tls

make kubernetes/secret/plan SITE=tirosh-home \
  KUBERNETES_SECRET=bloodbag-dev-signal-source-redis-tls

describe와 plan/render는 redacted입니다. apply --dry-run은 kubectl이 dummy 값을 보여 주므로 실제 PEM/password를 넣지 않습니다. 각 password ID도 같은 순서로 확인한 뒤 ID마다 apply합니다.

export BLOODBAG_DEV_SIGNAL_SOURCE_REDIS_TLS_CERT="$(cat "$TLS_DIR/tls.crt")"
export BLOODBAG_DEV_SIGNAL_SOURCE_REDIS_TLS_KEY="$(cat "$TLS_DIR/tls.key")"
export BLOODBAG_DEV_SIGNAL_SOURCE_REDIS_TLS_CA="$(cat "$TLS_DIR/ca.crt")"

uv run --package infra-tools infra-tools secret apply \
  --site tirosh-home \
  --secret bloodbag-dev-signal-source-redis-tls

unset BLOODBAG_DEV_SIGNAL_SOURCE_REDIS_TLS_CERT \
  BLOODBAG_DEV_SIGNAL_SOURCE_REDIS_TLS_KEY \
  BLOODBAG_DEV_SIGNAL_SOURCE_REDIS_TLS_CA

password는 자식 infra-tools 프로세스가 읽도록 먼저 export한 뒤 read로 넣습니다. read -r -s VAR만 하면 변수는 현재 shell에만 남고 apply가 missing environment variable로 실패합니다. 값을 echo하거나 Git에 쓰지 않습니다. 적용 직후 unset합니다.

uv run --package infra-tools infra-tools secret describe \
  --site tirosh-home \
  --secret bloodbag-dev-signal-source-redis-vital-file-streamer

BLOODBAG_DEV_SIGNAL_SOURCE_REDIS_VITAL_FILE_STREAMER_PASSWORD=dummy \
uv run --package infra-tools infra-tools secret render \
  --site tirosh-home \
  --secret bloodbag-dev-signal-source-redis-vital-file-streamer

make kubernetes/secret/plan SITE=tirosh-home \
  KUBERNETES_SECRET=bloodbag-dev-signal-source-redis-vital-file-streamer

export BLOODBAG_DEV_SIGNAL_SOURCE_REDIS_VITAL_FILE_STREAMER_PASSWORD
IFS= read -r -s BLOODBAG_DEV_SIGNAL_SOURCE_REDIS_VITAL_FILE_STREAMER_PASSWORD

uv run --package infra-tools infra-tools secret apply \
  --site tirosh-home \
  --secret bloodbag-dev-signal-source-redis-vital-file-streamer

unset BLOODBAG_DEV_SIGNAL_SOURCE_REDIS_VITAL_FILE_STREAMER_PASSWORD
uv run --package infra-tools infra-tools secret describe \
  --site tirosh-home \
  --secret bloodbag-dev-signal-source-redis-external-producer

BLOODBAG_DEV_SIGNAL_SOURCE_REDIS_EXTERNAL_PRODUCER_PASSWORD=dummy \
uv run --package infra-tools infra-tools secret render \
  --site tirosh-home \
  --secret bloodbag-dev-signal-source-redis-external-producer

make kubernetes/secret/plan SITE=tirosh-home \
  KUBERNETES_SECRET=bloodbag-dev-signal-source-redis-external-producer

export BLOODBAG_DEV_SIGNAL_SOURCE_REDIS_EXTERNAL_PRODUCER_PASSWORD
IFS= read -r -s BLOODBAG_DEV_SIGNAL_SOURCE_REDIS_EXTERNAL_PRODUCER_PASSWORD

uv run --package infra-tools infra-tools secret apply \
  --site tirosh-home \
  --secret bloodbag-dev-signal-source-redis-external-producer

unset BLOODBAG_DEV_SIGNAL_SOURCE_REDIS_EXTERNAL_PRODUCER_PASSWORD
uv run --package infra-tools infra-tools secret describe \
  --site tirosh-home \
  --secret bloodbag-dev-signal-source-redis-signal-relay

BLOODBAG_DEV_SIGNAL_SOURCE_REDIS_SIGNAL_RELAY_PASSWORD=dummy \
uv run --package infra-tools infra-tools secret render \
  --site tirosh-home \
  --secret bloodbag-dev-signal-source-redis-signal-relay

make kubernetes/secret/plan SITE=tirosh-home \
  KUBERNETES_SECRET=bloodbag-dev-signal-source-redis-signal-relay

export BLOODBAG_DEV_SIGNAL_SOURCE_REDIS_SIGNAL_RELAY_PASSWORD
IFS= read -r -s BLOODBAG_DEV_SIGNAL_SOURCE_REDIS_SIGNAL_RELAY_PASSWORD

uv run --package infra-tools infra-tools secret apply \
  --site tirosh-home \
  --secret bloodbag-dev-signal-source-redis-signal-relay

unset BLOODBAG_DEV_SIGNAL_SOURCE_REDIS_SIGNAL_RELAY_PASSWORD
uv run --package infra-tools infra-tools secret describe \
  --site tirosh-home \
  --secret bloodbag-dev-signal-source-redis-health

BLOODBAG_DEV_SIGNAL_SOURCE_REDIS_HEALTH_PASSWORD=dummy \
uv run --package infra-tools infra-tools secret render \
  --site tirosh-home \
  --secret bloodbag-dev-signal-source-redis-health

make kubernetes/secret/plan SITE=tirosh-home \
  KUBERNETES_SECRET=bloodbag-dev-signal-source-redis-health

export BLOODBAG_DEV_SIGNAL_SOURCE_REDIS_HEALTH_PASSWORD
IFS= read -r -s BLOODBAG_DEV_SIGNAL_SOURCE_REDIS_HEALTH_PASSWORD

uv run --package infra-tools infra-tools secret apply \
  --site tirosh-home \
  --secret bloodbag-dev-signal-source-redis-health

unset BLOODBAG_DEV_SIGNAL_SOURCE_REDIS_HEALTH_PASSWORD

운영자 소유와 rollback

사람 운영자(human operator)가 generation, apply, verification, rotation, 그리고 secure handoff를 소유합니다. infra-tools secret apply is direct kubectl apply and does not snapshot/rollback.

consumer activation 전에는 non-secret metadata만 기록합니다. 이미 있는 Secret을 교체할 때는 Git에 값을 쓰지 말고, secure out-of-Git backup을 두거나 같은 계약을 regenerate할 수 있어야 합니다.

rollback은 previous known-good material을 다시 apply하거나, 일관된 five-secret set을 새로 만들어 apply하는 것입니다. 적용 후 keys, SAN, four distinct passwords를 확인하고, PR #258 consumer activation은 상황에 맞게 keep하거나 revert합니다. deleting these Secrets while consumers run is forbidden.

Bounded legacy-live-v1 appendix

This appendix is the bounded operational record for the additive generation-v1 pair only. The five-secret contracts, examples, and rollback assertions above stay in force for those five Secrets. Do not use that old five-secret rollback procedure as a mechanism for this pair.

ID Name Namespace Context Type Keys and required environment source
bloodbag-dev-legacy-live-redis-v1 bloodbag-dev-legacy-live-redis-v1 bloodbag-dev tirosh-home/rke2/gpu Opaque password ← BLOODBAG_DEV_LEGACY_LIVE_REDIS_V1_PASSWORD; users.acl ← BLOODBAG_DEV_LEGACY_LIVE_REDIS_V1_USERS_ACL
bloodbag-dev-legacy-live-policy-v1 bloodbag-dev-legacy-live-policy-v1 bloodbag-dev tirosh-home/rke2/gpu Opaque service-token ← BLOODBAG_DEV_LEGACY_LIVE_POLICY_V1_SERVICE_TOKEN

Annotation bloodbag.tirosh.ai/credential-generation is v1. Labels are app.kubernetes.io/managed-by=infra-tools, app.kubernetes.io/part-of=bloodbag, and tirosh.ai/environment=dev. The registry has no immutable field. The existing parser and SecretSpec do not implement it, so a TOML immutable flag is not written.

The human operator, who is the human custodian named later by Root in the private operational record, generates one high-entropy printable UTF-8 producer password and a distinct whitespace-free service token of 32..4096 UTF-8 bytes. Do not reuse SMC, source, default, or runtime credentials. The complete users.acl environment string contains exactly the preserved default semantics and the frozen named producer ACL, with no extra users or selectors. The default nonsecret semantic line may be user default reset on nopass sanitize-payload ~* &* +@all. The producer hash is derived privately from the exact password bytes. Validate matching hash, nonempty material, token grammar, and actual ACL syntax privately, and report only booleans. SecretSpec does not check those semantics.

Hold the three environment variables in an exported environment or a protected private ENV_FILE: a regular 0600 file in a private 0700 directory, trusted shell assignments only, excluded from Git, logs, and artifacts. A bare ignored.env name is not itself an exclusion. Before apply, place the exact password, the complete ACL, and the token, or exact reproducing inputs, into the human custodian's secure out-of-Git backup. A temporary copy, an ignored file, a live Secret, or an etcd snapshot is not a recovery receipt. Retrieve from that backup into an isolated private process and verify byte-for-byte identity plus password/ACL agreement, reporting booleans only. A newly regenerated random token or password is a rotation, not exact restoration.

No custody receipt exists yet. No handoff is established. Public evidence has no backup values, password hashes, token, or ACL body. Current flags: material_generated=false, secret_applied=false, recoverability_verified=false, immutable_verified=false, operator_handoff=unestablished.

Plans below are redacted future parent steps, not executed evidence. Actual dry-run is forbidden; only unmistakable dummy env in an isolated test may use apply --dry-run. Authorized apply is serial and must pass explicit KUBECTX=tirosh-home/rke2/gpu together with the registry context. Apply offers no automatic snapshot or rollback, and it is not atomic across the two Secrets. Unset all three variables after use. Do not print full annotations, Secret YAML/JSON, manifest stdin, shell tracing, --show-values, or --from-literal.

make kubernetes/secret/plan SITE=tirosh-home ENV_FILE=<protected-absolute-path> KUBERNETES_SECRET=bloodbag-dev-legacy-live-redis-v1
make kubernetes/secret/plan SITE=tirosh-home ENV_FILE=<protected-absolute-path> KUBERNETES_SECRET=bloodbag-dev-legacy-live-policy-v1
make kubernetes/secret/apply SITE=tirosh-home ENV_FILE=<protected-absolute-path> KUBECTX=tirosh-home/rke2/gpu KUBERNETES_SECRET=bloodbag-dev-legacy-live-redis-v1
make kubernetes/secret/apply SITE=tirosh-home ENV_FILE=<protected-absolute-path> KUBECTX=tirosh-home/rke2/gpu KUBERNETES_SECRET=bloodbag-dev-legacy-live-policy-v1

Parent management-only seal runs after each successful apply and private validation. Root privately compares exact applied data to its custody copy and returns boolean equality. Bind that private equality check to the same observed UID and resourceVersion of that same object, and put exactly those UID and resourceVersion values in the patch. A separately fetched later resourceVersion cannot substitute. Ensure no concurrent secret writer is operating. Use one JSON patch per exact object. Do not change data, stringData, or the last-applied annotation. A test conflict aborts sealing. Repeat metadata and private validation; do not retry a patch blindly. If either seal fails, keep the pair unaccepted. Consumers remain held through the mutable interval and do not resume until both objects are validated and sealed. A lone sealed Secret is not complete custody. Do not unset immutable and do not clear immutable to retry. Reobserve BOTH immutable=true together with the expected UID, type, keys, and generation. Seal is a two-step gate and is not atomic. Do not claim Kubernetes immutability means recoverability. The safe output below prints key names only.

kubectl --context tirosh-home/rke2/gpu -n bloodbag-dev patch secret bloodbag-dev-legacy-live-redis-v1 --type=json --patch='[{"op":"test","path":"/metadata/uid","value":"<observed-uid>"},{"op":"test","path":"/metadata/resourceVersion","value":"<observed-rv>"},{"op":"add","path":"/immutable","value":true}]'
kubectl --context tirosh-home/rke2/gpu -n bloodbag-dev patch secret bloodbag-dev-legacy-live-policy-v1 --type=json --patch='[{"op":"test","path":"/metadata/uid","value":"<observed-uid>"},{"op":"test","path":"/metadata/resourceVersion","value":"<observed-rv>"},{"op":"add","path":"/immutable","value":true}]'
kubectl --context tirosh-home/rke2/gpu -n bloodbag-dev get secret bloodbag-dev-legacy-live-redis-v1 bloodbag-dev-legacy-live-policy-v1 -o go-template='{{range .items}}{{.metadata.name}} uid={{.metadata.uid}} rv={{.metadata.resourceVersion}} immutable={{.immutable}} type={{.type}} keys={{range $key, $value := .data}}{{$key}} {{end}}{{"\n"}}{{end}}'

Direct kubectl here is the documented parent seal exception for a future authorized operator. Existing env-only apply remains the provisioning API. These commands are not run by this registry change.

If a Secret is genuinely missing, restore exactly the same v1 bytes through the existing env-only apply and repeat the guarded seal before its consumers resume. Never switch mounts to a temporary name, a third Secret, or a fallback password. If a present immutable Secret is wrong, or the backup is unavailable, stop. If the backup is missing, stop that custody slice. Do not delete these objects and do not replace them under this decision. Deletion or replacement requires separate explicit authorization. This decision authorizes no deletion or in-place rotation. Lost original material without a verified backup cannot be reported recovered. Private backup retrieval and byte-for-byte comparison are a prerequisite only. An isolated create/apply/seal exercise would prove the existing mechanism, not actual dev outage recovery, and must never delete the real dev objects. No custody receipt exists yet.