feat(iwooos): define Wazuh release owner gate
This commit is contained in:
@@ -263,6 +263,13 @@
|
||||
- 完成度:rebase / snapshot refresh `100%`;formal release lane owner acks `0/6`、evidence `0/6`;Gitea push / production deploy / production readback `0%`。
|
||||
- 邊界:本段沒有讀 git credential、沒有推送、沒有部署、沒有 live Wazuh query、沒有 Nginx / Docker / K8s / firewall / host / Wazuh secret 變更。
|
||||
|
||||
**Release owner request / acceptance 補充,22:32 Asia/Taipei**:
|
||||
- 新增 `scripts/security/wazuh-readonly-release-owner-request.py`、`docs/security/wazuh-readonly-release-owner-request.snapshot.json`、`scripts/security/wazuh-readonly-release-owner-response-acceptance.py`、`docs/security/wazuh-readonly-release-owner-response-acceptance.snapshot.json`,並接入 `security-mirror-progress-guard.py`。
|
||||
- Owner request 草稿固定 required ack flags `6`、required evidence fields `6`、allowed release methods `3`、forbidden payloads `12`、blocked actions `11`;目前 request sent `0`、owner response accepted `0`、runtime gate `0`。
|
||||
- Owner response acceptance 帳本固定 reviewer checks `15`、outcome lanes `10`、blocked actions `13`;目前 received `0`、accepted `0`、acks `0/6`、evidence `0/6`、formal release ready `0`。
|
||||
- 完成度:release owner request / acceptance artifact 與 guard `100%`;正式 owner response / release ready / push / deploy / production readback `0%`。
|
||||
- 邊界:本段沒有發送 request、沒有收件、沒有讀 credential、沒有推送、沒有部署、沒有 Wazuh live query、沒有 runtime action;一般「批准繼續」仍不可當 release lane owner response。
|
||||
|
||||
## 2026-06-24|21:04 recovery readback 與 MOMO V10.651 雙機基準收斂
|
||||
|
||||
**背景**:前一輪 MOMO workspace readback 指到 `V10.646`,但 21:04 live health 已回 `V10.651`。因此本輪重新比對 Gitea `wooo/ewoooc` `main`、正式站 `/health`、Mac Mini / MacBook Pro Codex workspace 與 full-stack cold-start,避免「網站可用」和「版本 / 資料最新」互相混淆。
|
||||
|
||||
@@ -31,9 +31,13 @@
|
||||
- `scripts/security/wazuh-readonly-production-readback.py`
|
||||
- `scripts/security/wazuh-readonly-release-gate.py`
|
||||
- `scripts/security/wazuh-readonly-release-lane-preflight.py`
|
||||
- `scripts/security/wazuh-readonly-release-owner-request.py`
|
||||
- `scripts/security/wazuh-readonly-release-owner-response-acceptance.py`
|
||||
- `scripts/security/security-mirror-progress-guard.py`
|
||||
- `docs/security/wazuh-readonly-release-gate.snapshot.json`
|
||||
- `docs/security/wazuh-readonly-release-lane-preflight.snapshot.json`
|
||||
- `docs/security/wazuh-readonly-release-owner-request.snapshot.json`
|
||||
- `docs/security/wazuh-readonly-release-owner-response-acceptance.snapshot.json`
|
||||
- `docs/LOGBOOK.md`
|
||||
|
||||
完成內容:
|
||||
@@ -49,6 +53,7 @@
|
||||
- 新增 production readback 腳本,部署後可直接驗證 public API 不再 404、schema / status / boundary 正確,且沒有 raw payload、內網 IP、agent 原名或 secret 洩漏。
|
||||
- 新增 release gate snapshot 與 guard,固定 source-side 已完成、Gitea push / production deploy / production readback 尚未完成,避免後續把 predeploy 404 誤判成通過。
|
||||
- 新增 release lane preflight snapshot 與 guard,固定正式 release 前必須選擇 `formal_gitea_merge`、`formal_patch_apply` 或 `maintainer_local_push_with_safe_credential` 其中一條合規 lane,且 owner ack / evidence 未到齊前不得 push、deploy、force push、使用明文 token workaround 或改 runtime。
|
||||
- 新增 release owner request 草稿與 owner response acceptance 帳本,將 required ack flags、required evidence fields、allowed release methods、blocked actions、forbidden payloads 與 reviewer checks 機器可讀化;目前 request sent、response received / accepted、release ready、runtime gate 全部維持 `0`。
|
||||
|
||||
## 已完成驗證
|
||||
|
||||
@@ -59,9 +64,11 @@ pytest apps/api/tests/test_iwooos_wazuh_api.py
|
||||
python3 scripts/security/wazuh-readonly-route-boundary-guard.py --root .
|
||||
python3 scripts/security/wazuh-readonly-release-gate.py --root .
|
||||
python3 scripts/security/wazuh-readonly-release-lane-preflight.py --root .
|
||||
python3 scripts/security/wazuh-readonly-release-owner-request.py --root .
|
||||
python3 scripts/security/wazuh-readonly-release-owner-response-acceptance.py --root .
|
||||
python3 scripts/security/security-mirror-progress-guard.py --root .
|
||||
python3 scripts/ops/doc-secrets-sanity-check.py docs apps/api/src/api/v1/iwooos.py apps/web/src/app/api/iwooos/wazuh/route.ts scripts/security/wazuh-readonly-route-boundary-guard.py scripts/security/wazuh-readonly-production-readback.py scripts/security/wazuh-readonly-release-gate.py scripts/security/wazuh-readonly-release-lane-preflight.py
|
||||
python3 -m py_compile apps/api/src/api/v1/iwooos.py scripts/security/wazuh-readonly-route-boundary-guard.py scripts/security/wazuh-readonly-production-readback.py scripts/security/wazuh-readonly-release-gate.py scripts/security/wazuh-readonly-release-lane-preflight.py scripts/security/security-mirror-progress-guard.py
|
||||
python3 scripts/ops/doc-secrets-sanity-check.py docs apps/api/src/api/v1/iwooos.py apps/web/src/app/api/iwooos/wazuh/route.ts scripts/security/wazuh-readonly-route-boundary-guard.py scripts/security/wazuh-readonly-production-readback.py scripts/security/wazuh-readonly-release-gate.py scripts/security/wazuh-readonly-release-lane-preflight.py scripts/security/wazuh-readonly-release-owner-request.py scripts/security/wazuh-readonly-release-owner-response-acceptance.py
|
||||
python3 -m py_compile apps/api/src/api/v1/iwooos.py scripts/security/wazuh-readonly-route-boundary-guard.py scripts/security/wazuh-readonly-production-readback.py scripts/security/wazuh-readonly-release-gate.py scripts/security/wazuh-readonly-release-lane-preflight.py scripts/security/wazuh-readonly-release-owner-request.py scripts/security/wazuh-readonly-release-owner-response-acceptance.py scripts/security/security-mirror-progress-guard.py
|
||||
git diff --check
|
||||
```
|
||||
|
||||
@@ -71,8 +78,10 @@ git diff --check
|
||||
- `wazuh-readonly-route-boundary-guard`:`route=2 public_ui_files=1 forbidden=0 runtime_gate=0`。
|
||||
- `wazuh-readonly-release-gate`:`source=1 push=0 deploy=0 readback=0 runtime_gate=0`。
|
||||
- `wazuh-readonly-release-lane-preflight`:`ready=0 acks=0/6 evidence=0/6 runtime_gate=0`。
|
||||
- `wazuh-readonly-release-owner-request`:`drafts=1 sent=0 accepted=0 runtime_gate=0`。
|
||||
- `wazuh-readonly-release-owner-response-acceptance`:`received=0 accepted=0 acks=0/6 evidence=0/6 runtime_gate=0`。
|
||||
- `security-mirror-progress-guard`:`SECURITY_MIRROR_PROGRESS_GUARD_OK`。
|
||||
- `doc-secrets-sanity-check`:`DOC_SECRET_SANITY_OK scanned_files=970`。
|
||||
- `doc-secrets-sanity-check`:`DOC_SECRET_SANITY_OK scanned_files=972`。
|
||||
- `py_compile`:通過。
|
||||
- `git diff --check`:通過。
|
||||
|
||||
@@ -94,7 +103,7 @@ git am /private/tmp/awoooi-iwooos-wazuh-boundary-release-patch-<timestamp>/*.pat
|
||||
- `python3 scripts/security/wazuh-readonly-release-gate.py --root .`:`WAZUH_READONLY_RELEASE_GATE_OK source=1 push=0 deploy=0 readback=0 runtime_gate=0`。
|
||||
- `python3 scripts/security/wazuh-readonly-release-lane-preflight.py --root .`:`WAZUH_READONLY_RELEASE_LANE_PREFLIGHT_OK ready=0 acks=0/6 evidence=0/6 runtime_gate=0`。
|
||||
- `python3 scripts/security/security-mirror-progress-guard.py --root .`:`SECURITY_MIRROR_PROGRESS_GUARD_OK`。
|
||||
- `python3 scripts/ops/doc-secrets-sanity-check.py ...`:`DOC_SECRET_SANITY_OK scanned_files=970`。
|
||||
- `python3 scripts/ops/doc-secrets-sanity-check.py ...`:`DOC_SECRET_SANITY_OK scanned_files=972`。
|
||||
- `python3 -m py_compile ...`:通過。
|
||||
- `git diff --check`:通過。
|
||||
|
||||
@@ -169,6 +178,7 @@ python3 scripts/security/wazuh-readonly-production-readback.py --json
|
||||
| Production readback 驗收腳本 | `100%` | 已完成;正式部署後不得接受 404 |
|
||||
| Wazuh release gate snapshot / guard | `100%` | 已完成;固定 push/deploy/readback 仍 blocked |
|
||||
| Wazuh release lane preflight | `100%` | 已完成;owner acks `0/6`、evidence `0/6`、正式 release ready `0` |
|
||||
| Wazuh release owner request / acceptance | `100%` | 已完成只讀草稿與收件帳本;request sent `0`、response accepted `0` |
|
||||
| 乾淨套用 proof | `100%` | patch set 可落在最新 `gitea/main` 並通過同組 guard;最終 hash 以 release 前 readback 為準 |
|
||||
| Gitea push | `0%` | 受控 workspace HTTPS credential 缺失 |
|
||||
| Production deploy / readback | `0%` | 等待 release lane |
|
||||
@@ -178,7 +188,7 @@ python3 scripts/security/wazuh-readonly-production-readback.py --json
|
||||
|
||||
## 下一步優先序
|
||||
|
||||
1. 先補 release lane owner response:選擇 formal merge、formal patch apply 或安全 credential push,並補 6 個 ack 與 6 個 evidence 欄位。
|
||||
1. 先依 `wazuh-readonly-release-owner-request.snapshot.json` 補 release lane owner response:選擇 formal merge、formal patch apply 或安全 credential push,並補 6 個 ack 與 6 個 evidence 欄位。
|
||||
2. 解決受控 workspace Gitea HTTPS push 認證,或由正式 release lane 合併 `codex/iwooos-wazuh-boundary-guard-20260624` 分支 HEAD。
|
||||
3. 部署後先驗證 `/api/iwooos/wazuh` 不再 404,且預設 disabled 邊界正確。
|
||||
4. 另開 owner gate 決定是否啟用 server-side Wazuh read-only metadata query。
|
||||
|
||||
137
docs/security/wazuh-readonly-release-owner-request.snapshot.json
Normal file
137
docs/security/wazuh-readonly-release-owner-request.snapshot.json
Normal file
@@ -0,0 +1,137 @@
|
||||
{
|
||||
"execution_boundaries": {
|
||||
"dispatch_authorized": false,
|
||||
"force_push_allowed": false,
|
||||
"gitea_push_authorized": false,
|
||||
"host_write_authorized": false,
|
||||
"kali_active_scan_authorized": false,
|
||||
"not_authorization": true,
|
||||
"patch_apply_authorized": false,
|
||||
"plain_text_token_workaround_allowed": false,
|
||||
"production_deploy_authorized": false,
|
||||
"recipient_confirmed": false,
|
||||
"repo_write_authorized": false,
|
||||
"request_sent": false,
|
||||
"runtime_execution_authorized": false,
|
||||
"secret_value_collection_allowed": false,
|
||||
"wazuh_active_response_authorized": false,
|
||||
"wazuh_api_live_query_authorized": false
|
||||
},
|
||||
"generated_at": "2026-06-24T22:32:00+08:00",
|
||||
"handoff_envelope_fields": [
|
||||
"request_id",
|
||||
"stage_id",
|
||||
"recipient_role_or_team",
|
||||
"sender_role_or_team",
|
||||
"requested_response_window",
|
||||
"allowed_release_methods",
|
||||
"required_ack_flags",
|
||||
"required_evidence_fields",
|
||||
"target_branch_or_patch_set",
|
||||
"post_deploy_readback_command",
|
||||
"forbidden_payloads",
|
||||
"blocked_runtime_actions",
|
||||
"followup_owner",
|
||||
"not_approval"
|
||||
],
|
||||
"mode": "repo_request_draft_no_secret_no_runtime_no_push",
|
||||
"request_draft": {
|
||||
"action_buttons_allowed": false,
|
||||
"allowed_release_methods": [
|
||||
"formal_gitea_merge",
|
||||
"formal_patch_apply",
|
||||
"maintainer_local_push_with_safe_credential"
|
||||
],
|
||||
"blocked_runtime_actions": [
|
||||
"plain_text_gitea_token_in_remote_url",
|
||||
"copy_token_from_dirty_workspace",
|
||||
"force_push",
|
||||
"nginx_or_gateway_workaround_for_404",
|
||||
"docker_restart_for_wazuh_route",
|
||||
"k8s_or_argocd_manual_apply_for_wazuh_route",
|
||||
"firewall_change_for_wazuh_route",
|
||||
"wazuh_secret_or_manager_change_for_api_404",
|
||||
"enable_wazuh_live_metadata_without_owner_gate",
|
||||
"enable_wazuh_active_response",
|
||||
"host_write_or_kali_active_scan"
|
||||
],
|
||||
"followup_owner": "pending_followup_owner",
|
||||
"forbidden_payloads": [
|
||||
"token",
|
||||
"secret",
|
||||
"private_key",
|
||||
"cookie",
|
||||
"session",
|
||||
"authorization_header",
|
||||
"runner_token",
|
||||
"webhook_secret",
|
||||
"wazuh_password",
|
||||
"wazuh_raw_payload",
|
||||
"git_credential",
|
||||
"repo_archive"
|
||||
],
|
||||
"not_approval": true,
|
||||
"owner_response_accepted": false,
|
||||
"owner_response_received": false,
|
||||
"post_deploy_readback_command": "python3 scripts/security/wazuh-readonly-production-readback.py --json",
|
||||
"recipient_confirmed": false,
|
||||
"recipient_role_or_team": "pending_release_lane_owner",
|
||||
"redacted_evidence_refs": [
|
||||
"docs/security/IWOOOS-WAZUH-READONLY-API-RELEASE-HANDOFF.md",
|
||||
"docs/security/wazuh-readonly-release-gate.snapshot.json",
|
||||
"docs/security/wazuh-readonly-release-lane-preflight.snapshot.json"
|
||||
],
|
||||
"request_id": "iwooos_wazuh_readonly_release_owner_request",
|
||||
"request_sent": false,
|
||||
"requested_response_window": "not_scheduled",
|
||||
"required_ack_flags": [
|
||||
"approve_formal_release_lane",
|
||||
"confirm_no_plaintext_token_workaround",
|
||||
"confirm_no_force_push",
|
||||
"confirm_no_runtime_workaround",
|
||||
"confirm_production_readback_after_deploy",
|
||||
"confirm_wazuh_live_metadata_requires_separate_owner_gate"
|
||||
],
|
||||
"required_evidence_fields": [
|
||||
"release_lane_owner",
|
||||
"release_method",
|
||||
"target_branch_or_patch_set",
|
||||
"post_deploy_readback_command",
|
||||
"rollback_owner",
|
||||
"blocked_runtime_actions_ack"
|
||||
],
|
||||
"runtime_gate": false,
|
||||
"sender_role_or_team": "iwooos_security_reviewer",
|
||||
"stage_id": "P0-IWOOOS-WAZUH-RELEASE",
|
||||
"target_branch": "codex/iwooos-wazuh-boundary-guard-20260624",
|
||||
"target_branch_readback": "git log --oneline gitea/main..HEAD",
|
||||
"target_patch_set_readback": "git format-patch gitea/main..HEAD after final docs commit; record sha256 outside committed docs"
|
||||
},
|
||||
"schema_version": "iwooos_wazuh_readonly_release_owner_request_v1",
|
||||
"send_after_conditions": [
|
||||
"先確認 gitea/main、Wazuh 分支與另一個 AwoooP Session 基線。",
|
||||
"只送脫敏欄位與 refs;不得附 secret、raw Wazuh payload、git credential 或 runtime 操作要求。",
|
||||
"一般批准繼續不是 release owner response。",
|
||||
"收到 response 後仍需先通過 owner response acceptance ledger,不能直接 push 或 deploy。"
|
||||
],
|
||||
"status": "draft_not_dispatched_waiting_release_lane_owner",
|
||||
"summary": {
|
||||
"allowed_release_method_count": 3,
|
||||
"blocked_action_count": 11,
|
||||
"forbidden_payload_count": 12,
|
||||
"formal_release_lane_ready_count": 0,
|
||||
"gitea_push_authorized_count": 0,
|
||||
"handoff_envelope_field_count": 14,
|
||||
"owner_response_accepted_count": 0,
|
||||
"owner_response_received_count": 0,
|
||||
"patch_apply_authorized_count": 0,
|
||||
"production_deploy_authorized_count": 0,
|
||||
"production_readback_passed_count": 0,
|
||||
"recipient_confirmed_count": 0,
|
||||
"request_draft_count": 1,
|
||||
"request_sent_count": 0,
|
||||
"required_ack_flag_count": 6,
|
||||
"required_evidence_field_count": 6,
|
||||
"runtime_gate_count": 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
{
|
||||
"acceptance_candidate": {
|
||||
"acceptance_candidate_id": "iwooos_wazuh_readonly_release_owner_response",
|
||||
"ack_flags": {
|
||||
"approve_formal_release_lane": false,
|
||||
"confirm_no_force_push": false,
|
||||
"confirm_no_plaintext_token_workaround": false,
|
||||
"confirm_no_runtime_workaround": false,
|
||||
"confirm_production_readback_after_deploy": false,
|
||||
"confirm_wazuh_live_metadata_requires_separate_owner_gate": false
|
||||
},
|
||||
"blocked_actions": [
|
||||
"plain_text_gitea_token_in_remote_url",
|
||||
"copy_token_from_dirty_workspace",
|
||||
"force_push",
|
||||
"nginx_or_gateway_workaround_for_404",
|
||||
"docker_restart_for_wazuh_route",
|
||||
"k8s_or_argocd_manual_apply_for_wazuh_route",
|
||||
"firewall_change_for_wazuh_route",
|
||||
"wazuh_secret_or_manager_change_for_api_404",
|
||||
"enable_wazuh_live_metadata_without_owner_gate",
|
||||
"enable_wazuh_active_response",
|
||||
"host_write_or_kali_active_scan",
|
||||
"mark_general_approval_as_release_response",
|
||||
"mark_predeploy_404_as_passed_readback"
|
||||
],
|
||||
"decision": "pending_owner_response",
|
||||
"decision_reason": "pending_owner_response",
|
||||
"formal_release_lane_ready": false,
|
||||
"gitea_push_authorized": false,
|
||||
"not_approval": true,
|
||||
"outcome_lanes": [
|
||||
"waiting_owner_response",
|
||||
"quarantine_secret_or_raw_payload",
|
||||
"reject_execution_request",
|
||||
"request_supplement",
|
||||
"ready_for_release_reviewer_validation",
|
||||
"formal_gitea_merge_candidate",
|
||||
"formal_patch_apply_candidate",
|
||||
"safe_credential_push_candidate",
|
||||
"waiting_production_readback",
|
||||
"waiting_runtime_gate"
|
||||
],
|
||||
"owner_response_accepted": false,
|
||||
"owner_response_quarantined": false,
|
||||
"owner_response_received": false,
|
||||
"owner_response_rejected": false,
|
||||
"owner_role_or_team": "pending_owner_response",
|
||||
"patch_apply_authorized": false,
|
||||
"post_deploy_readback_command": "python3 scripts/security/wazuh-readonly-production-readback.py --json",
|
||||
"production_deploy_authorized": false,
|
||||
"production_readback_passed": false,
|
||||
"redacted_evidence_refs": [],
|
||||
"release_method": "pending_owner_response",
|
||||
"request_id": "iwooos_wazuh_readonly_release_owner_request",
|
||||
"required_ack_flags": [
|
||||
"approve_formal_release_lane",
|
||||
"confirm_no_plaintext_token_workaround",
|
||||
"confirm_no_force_push",
|
||||
"confirm_no_runtime_workaround",
|
||||
"confirm_production_readback_after_deploy",
|
||||
"confirm_wazuh_live_metadata_requires_separate_owner_gate"
|
||||
],
|
||||
"required_evidence_fields": [
|
||||
"release_lane_owner",
|
||||
"release_method",
|
||||
"target_branch_or_patch_set",
|
||||
"post_deploy_readback_command",
|
||||
"rollback_owner",
|
||||
"blocked_runtime_actions_ack"
|
||||
],
|
||||
"reviewer_checks": [
|
||||
"owner_identity_present",
|
||||
"release_method_allowed",
|
||||
"target_scope_matches_wazuh_branch_or_patch_set",
|
||||
"all_ack_flags_true",
|
||||
"all_evidence_fields_present",
|
||||
"redacted_refs_only",
|
||||
"secret_value_absent",
|
||||
"no_plaintext_token_workaround",
|
||||
"no_force_push",
|
||||
"no_runtime_workaround",
|
||||
"post_deploy_readback_required",
|
||||
"rollback_owner_present",
|
||||
"live_metadata_gate_separate",
|
||||
"active_response_stays_false",
|
||||
"counts_transition_safe"
|
||||
],
|
||||
"rollback_owner": "pending_owner_response",
|
||||
"runtime_gate": false,
|
||||
"status": "waiting_owner_response",
|
||||
"supplement_requested": false,
|
||||
"target_branch_or_patch_set": "pending_owner_response"
|
||||
},
|
||||
"execution_boundaries": {
|
||||
"force_push_allowed": false,
|
||||
"gitea_push_authorized": false,
|
||||
"host_write_authorized": false,
|
||||
"kali_active_scan_authorized": false,
|
||||
"not_authorization": true,
|
||||
"patch_apply_authorized": false,
|
||||
"plain_text_token_workaround_allowed": false,
|
||||
"production_deploy_authorized": false,
|
||||
"repo_write_authorized": false,
|
||||
"runtime_execution_authorized": false,
|
||||
"secret_value_collection_allowed": false,
|
||||
"wazuh_active_response_authorized": false,
|
||||
"wazuh_api_live_query_authorized": false
|
||||
},
|
||||
"generated_at": "2026-06-24T22:32:00+08:00",
|
||||
"mode": "metadata_only_acceptance_no_secret_no_runtime_no_push",
|
||||
"reviewer_instructions": [
|
||||
"只有具備完整欄位、脫敏 evidence refs、無 secret、無 runtime 要求的 owner response 才能進 reviewer validation。",
|
||||
"一般批准繼續、截圖、口頭同意或未列 release method 的訊息都不能增加 accepted count。",
|
||||
"即使 owner response accepted,也只代表可進正式 release lane;Wazuh live metadata 與 active response 仍是不同 gate。",
|
||||
"production readback 必須在部署後不加 --allow-predeploy-404 執行,且不得回 404。"
|
||||
],
|
||||
"schema_version": "iwooos_wazuh_readonly_release_owner_response_acceptance_v1",
|
||||
"status": "waiting_owner_response",
|
||||
"summary": {
|
||||
"acceptance_candidate_count": 1,
|
||||
"accepted_ack_flag_count": 0,
|
||||
"accepted_evidence_field_count": 0,
|
||||
"blocked_action_count": 13,
|
||||
"formal_release_lane_ready_count": 0,
|
||||
"gitea_push_authorized_count": 0,
|
||||
"outcome_lane_count": 10,
|
||||
"owner_response_accepted_count": 0,
|
||||
"owner_response_quarantined_count": 0,
|
||||
"owner_response_received_count": 0,
|
||||
"owner_response_rejected_count": 0,
|
||||
"patch_apply_authorized_count": 0,
|
||||
"production_deploy_authorized_count": 0,
|
||||
"production_readback_passed_count": 0,
|
||||
"required_ack_flag_count": 6,
|
||||
"required_evidence_field_count": 6,
|
||||
"reviewer_check_count": 15,
|
||||
"runtime_gate_count": 0,
|
||||
"supplement_requested_count": 0
|
||||
}
|
||||
}
|
||||
@@ -99,6 +99,14 @@ def validate(root: Path) -> None:
|
||||
str(root / "scripts" / "security" / "wazuh-readonly-release-lane-preflight.py")
|
||||
)
|
||||
wazuh_readonly_release_lane_preflight["validate"](root)
|
||||
wazuh_readonly_release_owner_request = runpy.run_path(
|
||||
str(root / "scripts" / "security" / "wazuh-readonly-release-owner-request.py")
|
||||
)
|
||||
wazuh_readonly_release_owner_request["validate"](root)
|
||||
wazuh_readonly_release_owner_response_acceptance = runpy.run_path(
|
||||
str(root / "scripts" / "security" / "wazuh-readonly-release-owner-response-acceptance.py")
|
||||
)
|
||||
wazuh_readonly_release_owner_response_acceptance["validate"](root)
|
||||
telegram_alert_readability_guard = runpy.run_path(
|
||||
str(root / "scripts" / "security" / "telegram-alert-readability-guard.py")
|
||||
)
|
||||
|
||||
230
scripts/security/wazuh-readonly-release-owner-request.py
Normal file
230
scripts/security/wazuh-readonly-release-owner-request.py
Normal file
@@ -0,0 +1,230 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
IwoooS Wazuh 只讀 API release owner request 草稿。
|
||||
|
||||
本工具只產生 / 驗證 repo 內 committed snapshot;不送 request、不讀
|
||||
credential、不推送、不部署、不查 Wazuh、不改 runtime。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
TAIPEI = timezone(timedelta(hours=8))
|
||||
SNAPSHOT_PATH = Path("docs/security/wazuh-readonly-release-owner-request.snapshot.json")
|
||||
|
||||
REQUIRED_ACK_FLAGS = [
|
||||
"approve_formal_release_lane",
|
||||
"confirm_no_plaintext_token_workaround",
|
||||
"confirm_no_force_push",
|
||||
"confirm_no_runtime_workaround",
|
||||
"confirm_production_readback_after_deploy",
|
||||
"confirm_wazuh_live_metadata_requires_separate_owner_gate",
|
||||
]
|
||||
|
||||
REQUIRED_EVIDENCE_FIELDS = [
|
||||
"release_lane_owner",
|
||||
"release_method",
|
||||
"target_branch_or_patch_set",
|
||||
"post_deploy_readback_command",
|
||||
"rollback_owner",
|
||||
"blocked_runtime_actions_ack",
|
||||
]
|
||||
|
||||
ALLOWED_RELEASE_METHODS = [
|
||||
"formal_gitea_merge",
|
||||
"formal_patch_apply",
|
||||
"maintainer_local_push_with_safe_credential",
|
||||
]
|
||||
|
||||
FORBIDDEN_PAYLOADS = [
|
||||
"token",
|
||||
"secret",
|
||||
"private_key",
|
||||
"cookie",
|
||||
"session",
|
||||
"authorization_header",
|
||||
"runner_token",
|
||||
"webhook_secret",
|
||||
"wazuh_password",
|
||||
"wazuh_raw_payload",
|
||||
"git_credential",
|
||||
"repo_archive",
|
||||
]
|
||||
|
||||
BLOCKED_ACTIONS = [
|
||||
"plain_text_gitea_token_in_remote_url",
|
||||
"copy_token_from_dirty_workspace",
|
||||
"force_push",
|
||||
"nginx_or_gateway_workaround_for_404",
|
||||
"docker_restart_for_wazuh_route",
|
||||
"k8s_or_argocd_manual_apply_for_wazuh_route",
|
||||
"firewall_change_for_wazuh_route",
|
||||
"wazuh_secret_or_manager_change_for_api_404",
|
||||
"enable_wazuh_live_metadata_without_owner_gate",
|
||||
"enable_wazuh_active_response",
|
||||
"host_write_or_kali_active_scan",
|
||||
]
|
||||
|
||||
HANDOFF_ENVELOPE_FIELDS = [
|
||||
"request_id",
|
||||
"stage_id",
|
||||
"recipient_role_or_team",
|
||||
"sender_role_or_team",
|
||||
"requested_response_window",
|
||||
"allowed_release_methods",
|
||||
"required_ack_flags",
|
||||
"required_evidence_fields",
|
||||
"target_branch_or_patch_set",
|
||||
"post_deploy_readback_command",
|
||||
"forbidden_payloads",
|
||||
"blocked_runtime_actions",
|
||||
"followup_owner",
|
||||
"not_approval",
|
||||
]
|
||||
|
||||
|
||||
def now_iso() -> str:
|
||||
return datetime.now(TAIPEI).replace(microsecond=0).isoformat()
|
||||
|
||||
|
||||
def build_report(generated_at: str | None = None) -> dict[str, Any]:
|
||||
return {
|
||||
"schema_version": "iwooos_wazuh_readonly_release_owner_request_v1",
|
||||
"generated_at": generated_at or now_iso(),
|
||||
"status": "draft_not_dispatched_waiting_release_lane_owner",
|
||||
"mode": "repo_request_draft_no_secret_no_runtime_no_push",
|
||||
"summary": {
|
||||
"request_draft_count": 1,
|
||||
"required_ack_flag_count": len(REQUIRED_ACK_FLAGS),
|
||||
"required_evidence_field_count": len(REQUIRED_EVIDENCE_FIELDS),
|
||||
"allowed_release_method_count": len(ALLOWED_RELEASE_METHODS),
|
||||
"forbidden_payload_count": len(FORBIDDEN_PAYLOADS),
|
||||
"blocked_action_count": len(BLOCKED_ACTIONS),
|
||||
"handoff_envelope_field_count": len(HANDOFF_ENVELOPE_FIELDS),
|
||||
"request_sent_count": 0,
|
||||
"recipient_confirmed_count": 0,
|
||||
"owner_response_received_count": 0,
|
||||
"owner_response_accepted_count": 0,
|
||||
"formal_release_lane_ready_count": 0,
|
||||
"gitea_push_authorized_count": 0,
|
||||
"patch_apply_authorized_count": 0,
|
||||
"production_deploy_authorized_count": 0,
|
||||
"production_readback_passed_count": 0,
|
||||
"runtime_gate_count": 0,
|
||||
},
|
||||
"request_draft": {
|
||||
"request_id": "iwooos_wazuh_readonly_release_owner_request",
|
||||
"stage_id": "P0-IWOOOS-WAZUH-RELEASE",
|
||||
"recipient_role_or_team": "pending_release_lane_owner",
|
||||
"sender_role_or_team": "iwooos_security_reviewer",
|
||||
"requested_response_window": "not_scheduled",
|
||||
"allowed_release_methods": ALLOWED_RELEASE_METHODS,
|
||||
"required_ack_flags": REQUIRED_ACK_FLAGS,
|
||||
"required_evidence_fields": REQUIRED_EVIDENCE_FIELDS,
|
||||
"target_branch": "codex/iwooos-wazuh-boundary-guard-20260624",
|
||||
"target_branch_readback": "git log --oneline gitea/main..HEAD",
|
||||
"target_patch_set_readback": "git format-patch gitea/main..HEAD after final docs commit; record sha256 outside committed docs",
|
||||
"post_deploy_readback_command": "python3 scripts/security/wazuh-readonly-production-readback.py --json",
|
||||
"redacted_evidence_refs": [
|
||||
"docs/security/IWOOOS-WAZUH-READONLY-API-RELEASE-HANDOFF.md",
|
||||
"docs/security/wazuh-readonly-release-gate.snapshot.json",
|
||||
"docs/security/wazuh-readonly-release-lane-preflight.snapshot.json",
|
||||
],
|
||||
"forbidden_payloads": FORBIDDEN_PAYLOADS,
|
||||
"blocked_runtime_actions": BLOCKED_ACTIONS,
|
||||
"followup_owner": "pending_followup_owner",
|
||||
"not_approval": True,
|
||||
"request_sent": False,
|
||||
"recipient_confirmed": False,
|
||||
"owner_response_received": False,
|
||||
"owner_response_accepted": False,
|
||||
"runtime_gate": False,
|
||||
"action_buttons_allowed": False,
|
||||
},
|
||||
"execution_boundaries": {
|
||||
"dispatch_authorized": False,
|
||||
"request_sent": False,
|
||||
"recipient_confirmed": False,
|
||||
"repo_write_authorized": False,
|
||||
"gitea_push_authorized": False,
|
||||
"patch_apply_authorized": False,
|
||||
"production_deploy_authorized": False,
|
||||
"runtime_execution_authorized": False,
|
||||
"secret_value_collection_allowed": False,
|
||||
"plain_text_token_workaround_allowed": False,
|
||||
"force_push_allowed": False,
|
||||
"wazuh_api_live_query_authorized": False,
|
||||
"wazuh_active_response_authorized": False,
|
||||
"host_write_authorized": False,
|
||||
"kali_active_scan_authorized": False,
|
||||
"not_authorization": True,
|
||||
},
|
||||
"handoff_envelope_fields": HANDOFF_ENVELOPE_FIELDS,
|
||||
"send_after_conditions": [
|
||||
"先確認 gitea/main、Wazuh 分支與另一個 AwoooP Session 基線。",
|
||||
"只送脫敏欄位與 refs;不得附 secret、raw Wazuh payload、git credential 或 runtime 操作要求。",
|
||||
"一般批准繼續不是 release owner response。",
|
||||
"收到 response 後仍需先通過 owner response acceptance ledger,不能直接 push 或 deploy。",
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def validate(root: Path) -> None:
|
||||
snapshot_path = root / SNAPSHOT_PATH
|
||||
if not snapshot_path.exists():
|
||||
raise SystemExit(f"BLOCKED Wazuh release owner request snapshot missing: {SNAPSHOT_PATH}")
|
||||
snapshot = json.loads(snapshot_path.read_text(encoding="utf-8"))
|
||||
expected = build_report(snapshot.get("generated_at"))
|
||||
|
||||
for key in ("schema_version", "status", "mode"):
|
||||
if snapshot.get(key) != expected[key]:
|
||||
raise SystemExit(f"BLOCKED Wazuh release owner request {key} mismatch")
|
||||
for key, expected_value in expected["summary"].items():
|
||||
actual = snapshot.get("summary", {}).get(key)
|
||||
if actual != expected_value:
|
||||
raise SystemExit(
|
||||
f"BLOCKED Wazuh release owner request summary.{key}: "
|
||||
f"expected {expected_value!r}, got {actual!r}"
|
||||
)
|
||||
for key, value in snapshot.get("execution_boundaries", {}).items():
|
||||
if key == "not_authorization":
|
||||
if value is not True:
|
||||
raise SystemExit("BLOCKED Wazuh release owner request not_authorization must be true")
|
||||
elif value is not False:
|
||||
raise SystemExit(f"BLOCKED Wazuh release owner request execution_boundaries.{key}: expected false")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="IwoooS Wazuh 只讀 API release owner request 草稿")
|
||||
parser.add_argument("--root", default=".", help="repository root")
|
||||
parser.add_argument("--output", help="寫出 JSON 報告")
|
||||
parser.add_argument("--generated-at", help="固定報告時間,供 committed snapshot 使用")
|
||||
args = parser.parse_args()
|
||||
|
||||
root = Path(args.root).resolve()
|
||||
report = build_report(args.generated_at)
|
||||
if args.output:
|
||||
output = Path(args.output)
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
output.write_text(json.dumps(report, ensure_ascii=False, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
||||
validate(root)
|
||||
summary = report["summary"]
|
||||
print(
|
||||
"WAZUH_READONLY_RELEASE_OWNER_REQUEST_OK "
|
||||
f"drafts={summary['request_draft_count']} "
|
||||
f"sent={summary['request_sent_count']} "
|
||||
f"accepted={summary['owner_response_accepted_count']} "
|
||||
f"runtime_gate={summary['runtime_gate_count']}"
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,233 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
IwoooS Wazuh 只讀 API release owner response acceptance 帳本。
|
||||
|
||||
本工具定義未來 owner response 如何被收件、補件、隔離、拒收或進入
|
||||
reviewer validation;它不讀 credential、不推送、不部署、不查 Wazuh、
|
||||
不寫 runtime,也不把一般批准繼續當 release 授權。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
TAIPEI = timezone(timedelta(hours=8))
|
||||
SNAPSHOT_PATH = Path("docs/security/wazuh-readonly-release-owner-response-acceptance.snapshot.json")
|
||||
|
||||
REQUIRED_ACK_FLAGS = [
|
||||
"approve_formal_release_lane",
|
||||
"confirm_no_plaintext_token_workaround",
|
||||
"confirm_no_force_push",
|
||||
"confirm_no_runtime_workaround",
|
||||
"confirm_production_readback_after_deploy",
|
||||
"confirm_wazuh_live_metadata_requires_separate_owner_gate",
|
||||
]
|
||||
|
||||
REQUIRED_EVIDENCE_FIELDS = [
|
||||
"release_lane_owner",
|
||||
"release_method",
|
||||
"target_branch_or_patch_set",
|
||||
"post_deploy_readback_command",
|
||||
"rollback_owner",
|
||||
"blocked_runtime_actions_ack",
|
||||
]
|
||||
|
||||
REVIEWER_CHECKS = [
|
||||
"owner_identity_present",
|
||||
"release_method_allowed",
|
||||
"target_scope_matches_wazuh_branch_or_patch_set",
|
||||
"all_ack_flags_true",
|
||||
"all_evidence_fields_present",
|
||||
"redacted_refs_only",
|
||||
"secret_value_absent",
|
||||
"no_plaintext_token_workaround",
|
||||
"no_force_push",
|
||||
"no_runtime_workaround",
|
||||
"post_deploy_readback_required",
|
||||
"rollback_owner_present",
|
||||
"live_metadata_gate_separate",
|
||||
"active_response_stays_false",
|
||||
"counts_transition_safe",
|
||||
]
|
||||
|
||||
OUTCOME_LANES = [
|
||||
"waiting_owner_response",
|
||||
"quarantine_secret_or_raw_payload",
|
||||
"reject_execution_request",
|
||||
"request_supplement",
|
||||
"ready_for_release_reviewer_validation",
|
||||
"formal_gitea_merge_candidate",
|
||||
"formal_patch_apply_candidate",
|
||||
"safe_credential_push_candidate",
|
||||
"waiting_production_readback",
|
||||
"waiting_runtime_gate",
|
||||
]
|
||||
|
||||
BLOCKED_ACTIONS = [
|
||||
"plain_text_gitea_token_in_remote_url",
|
||||
"copy_token_from_dirty_workspace",
|
||||
"force_push",
|
||||
"nginx_or_gateway_workaround_for_404",
|
||||
"docker_restart_for_wazuh_route",
|
||||
"k8s_or_argocd_manual_apply_for_wazuh_route",
|
||||
"firewall_change_for_wazuh_route",
|
||||
"wazuh_secret_or_manager_change_for_api_404",
|
||||
"enable_wazuh_live_metadata_without_owner_gate",
|
||||
"enable_wazuh_active_response",
|
||||
"host_write_or_kali_active_scan",
|
||||
"mark_general_approval_as_release_response",
|
||||
"mark_predeploy_404_as_passed_readback",
|
||||
]
|
||||
|
||||
|
||||
def now_iso() -> str:
|
||||
return datetime.now(TAIPEI).replace(microsecond=0).isoformat()
|
||||
|
||||
|
||||
def build_report(generated_at: str | None = None) -> dict[str, Any]:
|
||||
return {
|
||||
"schema_version": "iwooos_wazuh_readonly_release_owner_response_acceptance_v1",
|
||||
"generated_at": generated_at or now_iso(),
|
||||
"status": "waiting_owner_response",
|
||||
"mode": "metadata_only_acceptance_no_secret_no_runtime_no_push",
|
||||
"summary": {
|
||||
"acceptance_candidate_count": 1,
|
||||
"required_ack_flag_count": len(REQUIRED_ACK_FLAGS),
|
||||
"accepted_ack_flag_count": 0,
|
||||
"required_evidence_field_count": len(REQUIRED_EVIDENCE_FIELDS),
|
||||
"accepted_evidence_field_count": 0,
|
||||
"reviewer_check_count": len(REVIEWER_CHECKS),
|
||||
"outcome_lane_count": len(OUTCOME_LANES),
|
||||
"blocked_action_count": len(BLOCKED_ACTIONS),
|
||||
"owner_response_received_count": 0,
|
||||
"owner_response_accepted_count": 0,
|
||||
"owner_response_rejected_count": 0,
|
||||
"owner_response_quarantined_count": 0,
|
||||
"supplement_requested_count": 0,
|
||||
"formal_release_lane_ready_count": 0,
|
||||
"gitea_push_authorized_count": 0,
|
||||
"patch_apply_authorized_count": 0,
|
||||
"production_deploy_authorized_count": 0,
|
||||
"production_readback_passed_count": 0,
|
||||
"runtime_gate_count": 0,
|
||||
},
|
||||
"acceptance_candidate": {
|
||||
"acceptance_candidate_id": "iwooos_wazuh_readonly_release_owner_response",
|
||||
"request_id": "iwooos_wazuh_readonly_release_owner_request",
|
||||
"status": "waiting_owner_response",
|
||||
"owner_role_or_team": "pending_owner_response",
|
||||
"decision": "pending_owner_response",
|
||||
"decision_reason": "pending_owner_response",
|
||||
"release_method": "pending_owner_response",
|
||||
"target_branch_or_patch_set": "pending_owner_response",
|
||||
"post_deploy_readback_command": "python3 scripts/security/wazuh-readonly-production-readback.py --json",
|
||||
"rollback_owner": "pending_owner_response",
|
||||
"redacted_evidence_refs": [],
|
||||
"ack_flags": {flag: False for flag in REQUIRED_ACK_FLAGS},
|
||||
"required_ack_flags": REQUIRED_ACK_FLAGS,
|
||||
"required_evidence_fields": REQUIRED_EVIDENCE_FIELDS,
|
||||
"reviewer_checks": REVIEWER_CHECKS,
|
||||
"outcome_lanes": OUTCOME_LANES,
|
||||
"blocked_actions": BLOCKED_ACTIONS,
|
||||
"not_approval": True,
|
||||
"owner_response_received": False,
|
||||
"owner_response_accepted": False,
|
||||
"owner_response_rejected": False,
|
||||
"owner_response_quarantined": False,
|
||||
"supplement_requested": False,
|
||||
"formal_release_lane_ready": False,
|
||||
"gitea_push_authorized": False,
|
||||
"patch_apply_authorized": False,
|
||||
"production_deploy_authorized": False,
|
||||
"production_readback_passed": False,
|
||||
"runtime_gate": False,
|
||||
},
|
||||
"execution_boundaries": {
|
||||
"repo_write_authorized": False,
|
||||
"gitea_push_authorized": False,
|
||||
"patch_apply_authorized": False,
|
||||
"production_deploy_authorized": False,
|
||||
"runtime_execution_authorized": False,
|
||||
"secret_value_collection_allowed": False,
|
||||
"plain_text_token_workaround_allowed": False,
|
||||
"force_push_allowed": False,
|
||||
"wazuh_api_live_query_authorized": False,
|
||||
"wazuh_active_response_authorized": False,
|
||||
"host_write_authorized": False,
|
||||
"kali_active_scan_authorized": False,
|
||||
"not_authorization": True,
|
||||
},
|
||||
"reviewer_instructions": [
|
||||
"只有具備完整欄位、脫敏 evidence refs、無 secret、無 runtime 要求的 owner response 才能進 reviewer validation。",
|
||||
"一般批准繼續、截圖、口頭同意或未列 release method 的訊息都不能增加 accepted count。",
|
||||
"即使 owner response accepted,也只代表可進正式 release lane;Wazuh live metadata 與 active response 仍是不同 gate。",
|
||||
"production readback 必須在部署後不加 --allow-predeploy-404 執行,且不得回 404。",
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def validate(root: Path) -> None:
|
||||
snapshot_path = root / SNAPSHOT_PATH
|
||||
if not snapshot_path.exists():
|
||||
raise SystemExit(
|
||||
f"BLOCKED Wazuh release owner response acceptance snapshot missing: {SNAPSHOT_PATH}"
|
||||
)
|
||||
snapshot = json.loads(snapshot_path.read_text(encoding="utf-8"))
|
||||
expected = build_report(snapshot.get("generated_at"))
|
||||
|
||||
for key in ("schema_version", "status", "mode"):
|
||||
if snapshot.get(key) != expected[key]:
|
||||
raise SystemExit(f"BLOCKED Wazuh release owner response acceptance {key} mismatch")
|
||||
for key, expected_value in expected["summary"].items():
|
||||
actual = snapshot.get("summary", {}).get(key)
|
||||
if actual != expected_value:
|
||||
raise SystemExit(
|
||||
f"BLOCKED Wazuh release owner response acceptance summary.{key}: "
|
||||
f"expected {expected_value!r}, got {actual!r}"
|
||||
)
|
||||
for key, value in snapshot.get("execution_boundaries", {}).items():
|
||||
if key == "not_authorization":
|
||||
if value is not True:
|
||||
raise SystemExit(
|
||||
"BLOCKED Wazuh release owner response acceptance not_authorization must be true"
|
||||
)
|
||||
elif value is not False:
|
||||
raise SystemExit(
|
||||
f"BLOCKED Wazuh release owner response acceptance execution_boundaries.{key}: expected false"
|
||||
)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="IwoooS Wazuh 只讀 API release owner response acceptance")
|
||||
parser.add_argument("--root", default=".", help="repository root")
|
||||
parser.add_argument("--output", help="寫出 JSON 報告")
|
||||
parser.add_argument("--generated-at", help="固定報告時間,供 committed snapshot 使用")
|
||||
args = parser.parse_args()
|
||||
|
||||
root = Path(args.root).resolve()
|
||||
report = build_report(args.generated_at)
|
||||
if args.output:
|
||||
output = Path(args.output)
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
output.write_text(json.dumps(report, ensure_ascii=False, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
||||
validate(root)
|
||||
summary = report["summary"]
|
||||
print(
|
||||
"WAZUH_READONLY_RELEASE_OWNER_RESPONSE_ACCEPTANCE_OK "
|
||||
f"received={summary['owner_response_received_count']} "
|
||||
f"accepted={summary['owner_response_accepted_count']} "
|
||||
f"acks={summary['accepted_ack_flag_count']}/{summary['required_ack_flag_count']} "
|
||||
f"evidence={summary['accepted_evidence_field_count']}/{summary['required_evidence_field_count']} "
|
||||
f"runtime_gate={summary['runtime_gate_count']}"
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user