feat(ops): verify release provenance evidence pairs
This commit is contained in:
+37
-14
@@ -47,12 +47,26 @@ Release tag의 clean checkout에서 `./scripts/build_release_image.sh trex-core:
|
||||
non-root user 및 container `--version`을 검증한다. 전체 명령과 registry 사용법은
|
||||
[`docs/ops/release-image-build.md`](ops/release-image-build.md)를 따른다.
|
||||
|
||||
### 배포/복구/수동 재기동 provenance gate (issue #87)
|
||||
### 배포/복구/수동 재기동 provenance 검증 (issue #87)
|
||||
|
||||
배포, 복구, 수동 stop/start/recreate 전에 아래 gate를 실행해 **검증 가능한 release record**를 남긴다.
|
||||
이 저장소는 provenance **검증 contract**만 제공한다. 실행 중 target의 inspect, observed evidence 생성, 대상 선택, 승인, operation lock, stop/start/recreate/deploy, checkout 복구와 non-zero exit enforcement는 운영 계층이 소유한다. Core gate는 runtime을 직접 inspect하거나 lifecycle command를 실행하지 않으며, caller evidence를 `operator_observed`로 명시한다.
|
||||
|
||||
운영 계층은 각 PRE/POST 시점에 exact schema의 JSON을 생성한다. Unknown/duplicate key, 형식 오류, timezone 없는 시각과 secret-like material은 record를 만들기 전에 거부된다.
|
||||
|
||||
```json
|
||||
{
|
||||
"schema_version": 1,
|
||||
"target_id": "<canonical-runtime-target>",
|
||||
"target_kind": "<operations-owned-kind>",
|
||||
"image_id": "sha256:<64-lowercase-hex>",
|
||||
"image_digest": "sha256:<64-lowercase-hex>",
|
||||
"oci_revision": "<40-lowercase-hex>",
|
||||
"observed_at": "2026-09-20T02:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
# PRE (대상 인스턴스 1개만 지정; POST도 같은 operation-id로 별도 실행)
|
||||
# PRE: 대상 인스턴스 1개만 지정한다. POST도 같은 operation-id로 별도 실행한다.
|
||||
python scripts/release_provenance_gate.py \
|
||||
--actor "<operator>" \
|
||||
--reason "<ticket-or-incident>" \
|
||||
@@ -60,8 +74,7 @@ python scripts/release_provenance_gate.py \
|
||||
--operation-id "<deployment-or-incident-id>" \
|
||||
--phase "pre" \
|
||||
--command-class "manual-stop" \
|
||||
--expected-oci-revision "<40-character-running-image-oci-revision>" \
|
||||
--image-digest "sha256:<64-hex-running-image-digest>" \
|
||||
--observed-evidence-file "/absolute/path/pre-observed-evidence.json" \
|
||||
--runtime-file "trex_core/runtime/daemon.py" \
|
||||
--runtime-file "trex_core/runtime/cli.py" \
|
||||
--capture-command "pending_slots=.venv/bin/trex-core slots --status pending --instance <instance_key> --json" \
|
||||
@@ -69,16 +82,26 @@ python scripts/release_provenance_gate.py \
|
||||
--capture-command "queue_consumers=rabbitmqctl list_consumers"
|
||||
```
|
||||
|
||||
- gate는 audit checkout이 detached HEAD인지 확인하고 NUL 구분 `git status --porcelain=v1`로 clean 여부를 검사한다. branch checkout 또는 dirty checkout이면 운영 상태 조회 명령도 실행하지 않고 종료 코드 `2`로 중단하며, dirty 변경 파일은 audit checkout 밖의 `<checkout>-release-provenance-isolation/`에 작업별 디렉터리로 복사한다. 우회 옵션은 없다.
|
||||
- `checkout.head_revision`과 필수 `--expected-oci-revision` 일치 여부를 검사해 audit checkout HEAD가 실행 이미지 OCI revision과 같은지 검증한다.
|
||||
- 필수 `--image-digest` 형식과 하나 이상의 `--runtime-file` 검증 결과(sha256 + revision 일치 여부)가 JSON record에 저장된다.
|
||||
- actor/timestamp/instance/operation-id/phase/command-class/reason이 JSON record에 함께 저장되어 PRE/POST 및 stop/start/recreate 감사 추적이 가능하다. actor/reason에 credential 형태의 key/value가 포함되면 기록 전에 거부한다.
|
||||
- PRE/POST 모두 실행해 재기동 전후의 `pending_slots`, `reconciliation_slots`, `queue_consumers`, broker-side-effect 로그(`order_submit/fill/reject/cancel`)를 자동 캡처한다. 필수 조회 또는 로그 캡처가 누락·실패하면 gate는 실패한다.
|
||||
- capture command는 위의 인스턴스 지정 `trex-core slots` 두 형식과 `rabbitmqctl list_consumers`만 허용한다. 주문 제출·취소, DB 복구, 자동 sync/verify live 실행은 거부한다.
|
||||
- broker-side-effect 로그 입력은 기본 `/tmp/trex-core.log`를 사용하며, 로그 파일 위치가 다르면 `--broker-log-path <path>`로 명시한다. record에는 원문 대신 파일 sha256·스캔 행 수와 event 종류·행 번호·행 sha256만 남긴다. 파일을 정상 스캔했지만 일치 이벤트가 없으면 `captured_empty`, 파일 누락·읽기 실패는 각각 `unavailable`·`error`로 기록하며 후자는 gate를 차단한다.
|
||||
- record와 격리 디렉터리는 audit checkout 밖에 둔다. 기본 record 위치는 `<checkout>-release-provenance/`이며, 충돌 없는 파일명을 exclusive-create하고 read-only mode로 전환해 기존 record를 덮어쓰지 않는다.
|
||||
- Gate stdout은 `record_path`, raw record bytes의 `record_sha256`, `gate_passed`를 포함한 v1 JSON receipt다. 운영 계층은 PRE/POST receipt를 신뢰 경계 밖에 보존하고 non-zero이면 lifecycle을 실행하지 않는다.
|
||||
- Gate는 audit checkout이 detached HEAD인지 확인하고 NUL 구분 `git status --porcelain=v1`로 clean 여부를 검사한다. branch checkout 또는 dirty checkout이면 운영 상태 조회 명령도 실행하지 않고 종료 코드 `2`로 중단한다.
|
||||
- Observed `oci_revision`, audit checkout HEAD와 revision별 runtime-file bytes가 모두 일치해야 한다. Target/image 값은 caller 관측값이며 core가 직접 관측했다고 주장하지 않는다.
|
||||
- PRE/POST에서 `pending_slots`, `reconciliation_slots`, `queue_consumers`, broker-side-effect 로그를 read-only로 캡처한다. 필수 조회 또는 로그 캡처가 누락·실패하면 gate는 실패한다.
|
||||
- Capture command는 위의 인스턴스 지정 `trex-core slots` 두 형식과 `rabbitmqctl list_consumers`만 허용한다. 주문 제출·취소, DB 복구 또는 lifecycle 명령은 실행하지 않는다.
|
||||
- Dirty checkout의 regular file과 symlink는 checkout 밖 `<checkout>-release-provenance-isolation/`에 원본을 변경하지 않고 복사한다. v1 isolation manifest는 path, type, size, source/copy SHA-256과 일치 verdict를 기록하며 unsupported type·escape·copy mismatch는 실패한다.
|
||||
- Record와 manifest는 audit checkout 밖에 exclusive-create하고 read-only mode로 전환한다. 기존 파일 충돌은 덮어쓰지 않고 실패한다.
|
||||
|
||||
재기동 후에는 같은 `--operation-id`, `--phase "post"`, 해당 command class(예: `manual-start`)로 POST record를 추가 저장한다.
|
||||
재기동 후 운영 계층은 새 observed evidence로 POST gate를 실행한다. 두 receipt의 exact path와 trusted digest를 명시해 pair manifest를 생성한다. Validator는 디렉터리를 scan하거나 latest file을 추정하지 않는다. PRE/POST의 image identity와 command class는 서로 달라도 되지만 operation ID, instance, target kind/ID와 각 record의 success evidence는 일치해야 한다.
|
||||
|
||||
```bash
|
||||
python scripts/verify_release_provenance_pair.py \
|
||||
--pre-record "<pre-receipt.record_path>" \
|
||||
--pre-sha256 "<pre-receipt.record_sha256>" \
|
||||
--post-record "<post-receipt.record_path>" \
|
||||
--post-sha256 "<post-receipt.record_sha256>" \
|
||||
--output "/absolute/path/<operation-id>-pair-manifest.json"
|
||||
```
|
||||
|
||||
Pair manifest는 input raw bytes를 trusted digest와 다시 비교한 뒤 exclusive-create/read-only로 저장한다. Digest mismatch, malformed/legacy/failure record, identity mismatch와 output collision은 manifest 없이 non-zero로 종료한다. 이 성공은 provenance record pair의 일관성만 증명하며 실제 lifecycle 실행 여부나 운영 정책 승인을 증명하지 않는다.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user