feat(api): expose gitea runner attestation request
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 27s
CD Pipeline / build-and-deploy (push) Has been cancelled
CD Pipeline / post-deploy-checks (push) Has been cancelled

This commit is contained in:
Your Name
2026-06-30 07:39:05 +08:00
parent 481a946d1b
commit 43396f5603
8 changed files with 419 additions and 0 deletions

View File

@@ -307,6 +307,8 @@ jobs:
;;
apps/api/src/services/gitea_private_inventory_p0_scorecard.py)
;;
apps/api/src/services/gitea_workflow_runner_owner_attestation_request.py)
;;
apps/api/src/services/reboot_auto_recovery_slo_scorecard.py)
;;
apps/api/src/services/reboot_auto_recovery_drill_preflight.py)
@@ -405,6 +407,8 @@ jobs:
;;
apps/api/tests/test_gitea_private_inventory_p0_scorecard_api.py)
;;
apps/api/tests/test_gitea_workflow_runner_owner_attestation_request_api.py)
;;
apps/api/tests/test_reboot_auto_recovery_slo_scorecard_api.py)
;;
apps/api/tests/test_iwooos_security_operating_system.py)
@@ -583,6 +587,7 @@ jobs:
src/services/gitea_owner_coverage_attestation_validation.py \
src/services/gitea_private_inventory_closeout_validation.py \
src/services/gitea_private_inventory_p0_scorecard.py \
src/services/gitea_workflow_runner_owner_attestation_request.py \
src/services/reboot_auto_recovery_slo_scorecard.py \
src/services/reboot_auto_recovery_drill_preflight.py \
src/services/iwooos_security_operating_system.py \
@@ -635,6 +640,7 @@ jobs:
tests/test_backup_dr_readiness_matrix_api.py \
tests/test_credential_escrow_evidence_intake_readiness_api.py \
tests/test_gitea_private_inventory_p0_scorecard_api.py \
tests/test_gitea_workflow_runner_owner_attestation_request_api.py \
tests/test_reboot_auto_recovery_slo_scorecard_api.py \
tests/test_iwooos_security_operating_system.py \
tests/e2e_network_test.py::TestHMACVerification::test_valid_hmac_signature \

View File

@@ -381,6 +381,9 @@ from src.services.gitea_private_inventory_closeout_validation import (
from src.services.gitea_private_inventory_p0_scorecard import (
load_latest_gitea_private_inventory_p0_scorecard,
)
from src.services.gitea_workflow_runner_owner_attestation_request import (
load_latest_gitea_workflow_runner_owner_attestation_request,
)
from src.services.gitea_workflow_runner_health import (
load_latest_gitea_workflow_runner_health,
)
@@ -4129,6 +4132,39 @@ async def get_gitea_workflow_runner_health() -> dict[str, Any]:
) from exc
@router.get(
"/gitea-workflow-runner-owner-attestation-request",
response_model=dict[str, Any],
summary="取得 Gitea workflow runner owner attestation request",
description=(
"從 Gitea workflow / runner health contract 產生 runner label owner "
"attestation request packet此端點只回傳脫敏 metadata 欄位、驗收規則與禁止事項。"
"它不送出 request、不呼叫 Gitea API、不修改 workflow、不重啟或註冊 runner、"
"不改 runner label、不讀 Secret 或 runner token、不觸發 workflow。"
),
)
async def get_gitea_workflow_runner_owner_attestation_request() -> dict[str, Any]:
"""Return the read-only Gitea runner owner attestation request packet."""
try:
return await asyncio.to_thread(
load_latest_gitea_workflow_runner_owner_attestation_request
)
except FileNotFoundError as exc:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=str(exc),
) from exc
except (json.JSONDecodeError, ValueError) as exc:
logger.error(
"gitea_workflow_runner_owner_attestation_request_invalid",
error=str(exc),
)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Gitea workflow runner owner attestation request 無效",
) from exc
@router.get(
"/observability-contract-matrix",
response_model=dict[str, Any],

View File

@@ -33,6 +33,9 @@ from src.services.credential_escrow_evidence_intake_readiness import (
from src.services.gitea_private_inventory_p0_scorecard import (
load_latest_gitea_private_inventory_p0_scorecard,
)
from src.services.gitea_workflow_runner_owner_attestation_request import (
load_latest_gitea_workflow_runner_owner_attestation_request,
)
from src.services.gitea_workflow_runner_health import (
load_latest_gitea_workflow_runner_health,
)
@@ -65,6 +68,9 @@ def load_delivery_closure_workbench() -> dict[str, Any]:
load_latest_awoooi_gitea_onboarding_warning_step_runtime_enablement_gate()
)
gitea = load_latest_gitea_workflow_runner_health()
gitea_runner_attestation_request = (
load_latest_gitea_workflow_runner_owner_attestation_request()
)
runtime = load_latest_runtime_surface_inventory()
backup = load_latest_backup_dr_readiness_matrix()
credential_escrow_intake = load_latest_credential_escrow_evidence_intake_readiness()
@@ -78,6 +84,7 @@ def load_delivery_closure_workbench() -> dict[str, Any]:
cicd_template_copy_receipt=cicd_template_copy_receipt,
cicd_runtime_enablement_gate=cicd_runtime_enablement_gate,
gitea=gitea,
gitea_runner_attestation_request=gitea_runner_attestation_request,
runtime=runtime,
backup=backup,
credential_escrow_intake=credential_escrow_intake,
@@ -95,6 +102,7 @@ def build_delivery_closure_workbench(
cicd_template_copy_receipt: dict[str, Any],
cicd_runtime_enablement_gate: dict[str, Any],
gitea: dict[str, Any],
gitea_runner_attestation_request: dict[str, Any],
runtime: dict[str, Any],
backup: dict[str, Any],
credential_escrow_intake: dict[str, Any],
@@ -145,6 +153,18 @@ def build_delivery_closure_workbench(
production_deploy_rollups = _dict(production_deploy.get("rollups"))
gitea_status = _dict(gitea.get("program_status"))
gitea_rollups = _dict(gitea.get("rollups"))
gitea_runner_request_readback = _dict(
gitea_runner_attestation_request.get("readback")
)
gitea_runner_request_packet = _dict(
gitea_runner_attestation_request.get("request_packet")
)
gitea_runner_request_rollups = _dict(
gitea_runner_attestation_request.get("rollups")
)
gitea_runner_request_boundaries = _dict(
gitea_runner_attestation_request.get("operation_boundaries")
)
runtime_status = _dict(runtime.get("program_status"))
runtime_rollups = _dict(runtime.get("rollups"))
backup_status = _dict(backup.get("program_status"))
@@ -1007,6 +1027,47 @@ def build_delivery_closure_workbench(
"metric": {
"kind": "workflow_count",
"count": _int(gitea_rollups.get("total_workflows")),
"runner_attestation_request_status": str(
gitea_runner_attestation_request.get("status") or ""
),
"runner_attestation_request_ready": (
gitea_runner_request_rollups.get("request_template_ready") is True
),
"runner_attestation_request_active_blocker_count": _int(
gitea_runner_request_rollups.get("active_blocker_count")
),
"runner_attestation_contract_id": str(
gitea_runner_request_readback.get("contract_id") or ""
),
"runner_attestation_workflow_count": _int(
gitea_runner_request_rollups.get(
"workflow_requiring_attestation_count"
)
),
"runner_attestation_required_owner_field_count": _int(
gitea_runner_request_rollups.get("required_owner_field_count")
),
"runner_attestation_owner_response_received_count": _int(
gitea_runner_request_rollups.get("owner_response_received_count")
),
"runner_attestation_owner_response_accepted_count": _int(
gitea_runner_request_rollups.get("owner_response_accepted_count")
),
"runner_attestation_request_send_performed": (
gitea_runner_request_boundaries.get("request_send_performed")
is True
),
"runner_attestation_runner_label_change_allowed": (
gitea_runner_request_boundaries.get("runner_label_change_allowed")
is True
),
"runner_attestation_runner_registration_allowed": (
gitea_runner_request_boundaries.get("runner_registration_allowed")
is True
),
"runner_attestation_secret_read_allowed": (
gitea_runner_request_boundaries.get("secret_read_allowed") is True
),
},
"href": "/deployments",
"next_action": _first_contract_action(gitea.get("runner_contracts")),
@@ -1363,6 +1424,55 @@ def build_delivery_closure_workbench(
)
is True
),
"gitea_runner_attestation_request_status": str(
gitea_runner_attestation_request.get("status") or ""
),
"gitea_runner_attestation_request_ready": (
gitea_runner_request_rollups.get("request_template_ready") is True
),
"gitea_runner_attestation_request_active_blocker_count": _int(
gitea_runner_request_rollups.get("active_blocker_count")
),
"gitea_runner_attestation_contract_id": str(
gitea_runner_request_readback.get("contract_id") or ""
),
"gitea_runner_attestation_runner_label": str(
gitea_runner_request_readback.get("runner_label") or ""
),
"gitea_runner_attestation_workflow_count": _int(
gitea_runner_request_rollups.get(
"workflow_requiring_attestation_count"
)
),
"gitea_runner_attestation_required_owner_field_count": _int(
gitea_runner_request_rollups.get("required_owner_field_count")
),
"gitea_runner_attestation_forbidden_action_count": _int(
gitea_runner_request_rollups.get("forbidden_action_count")
),
"gitea_runner_attestation_owner_response_received_count": _int(
gitea_runner_request_rollups.get("owner_response_received_count")
),
"gitea_runner_attestation_owner_response_accepted_count": _int(
gitea_runner_request_rollups.get("owner_response_accepted_count")
),
"gitea_runner_attestation_request_sent": (
gitea_runner_request_packet.get("request_sent") is True
),
"gitea_runner_attestation_request_send_performed": (
gitea_runner_request_boundaries.get("request_send_performed") is True
),
"gitea_runner_attestation_runner_label_change_allowed": (
gitea_runner_request_boundaries.get("runner_label_change_allowed")
is True
),
"gitea_runner_attestation_runner_registration_allowed": (
gitea_runner_request_boundaries.get("runner_registration_allowed")
is True
),
"gitea_runner_attestation_secret_read_allowed": (
gitea_runner_request_boundaries.get("secret_read_allowed") is True
),
"p0_cicd_baseline_status": str(cicd_baseline.get("status") or ""),
"p0_cicd_baseline_workplan_id": str(
cicd_baseline_readback.get("workplan_id") or ""

View File

@@ -0,0 +1,172 @@
"""Gitea workflow runner owner attestation request readback.
Builds a metadata-only request packet from the committed Gitea workflow /
runner health contract. This module never sends the request, calls Gitea,
changes runner labels, registers runners, triggers workflows, or reads secrets.
"""
from __future__ import annotations
from typing import Any
from src.services.gitea_workflow_runner_health import (
load_latest_gitea_workflow_runner_health,
)
_SCHEMA_VERSION = "gitea_workflow_runner_owner_attestation_request_v1"
_CONTRACT_ID = "ubuntu_latest_gitea_runner_label"
_REQUIRED_FIELDS = [
"runner_label",
"actual_runner_mapping_ref",
"runner_host_alias",
"executor_scope",
"capacity_owner",
"maintenance_window",
"evidence_ref",
"no_secret_value_attestation",
"no_runner_registration_change_attestation",
"rollback_owner",
"post_check_owner",
]
_FORBIDDEN_ACTIONS = [
"workflow_modification",
"workflow_dispatch",
"runner_label_change",
"runner_registration",
"runner_restart",
"runner_container_stop",
"secret_or_runner_token_read",
"gitea_api_write",
"github_api",
]
def load_latest_gitea_workflow_runner_owner_attestation_request() -> dict[str, Any]:
"""Return the owner attestation request packet for runner label evidence."""
health = load_latest_gitea_workflow_runner_health()
rollups = _dict(health.get("rollups"))
action_contracts = [
contract
for contract in health.get("runner_contracts", [])
if isinstance(contract, dict)
and contract.get("contract_id")
in set(_strings(rollups.get("runner_contracts_requiring_action")))
]
target_contract = next(
(
contract
for contract in action_contracts
if contract.get("contract_id") == _CONTRACT_ID
),
{},
)
workflow_ids = _strings(rollups.get("workflow_ids_requiring_runner_attestation"))
request_ready = bool(target_contract) and bool(workflow_ids)
active_blockers = []
if not request_ready:
active_blockers.append("runner_attestation_request_source_missing")
active_blockers.append("owner_attestation_response_not_received")
return {
"schema_version": _SCHEMA_VERSION,
"priority": "P1-002",
"scope": "gitea_workflow_runner_owner_attestation_request",
"status": (
"owner_attestation_request_ready_waiting_response"
if request_ready
else "blocked_owner_attestation_request_source_missing"
),
"readback": {
"workplan_id": "P1-002-GITEA-RUNNER-OWNER-ATTESTATION",
"source_health_schema_version": health.get("schema_version"),
"source_health_endpoint": "/api/v1/agents/gitea-workflow-runner-health",
"contract_id": _CONTRACT_ID,
"runner_label": _first_string(target_contract.get("runner_labels")),
"request_packet_id": "gitea_runner_owner_attestation_request::ubuntu_latest",
"safe_next_step": (
"collect_redacted_owner_attestation_response_then_validate_no_runner_mutation"
),
},
"request_packet": {
"request_template_ready": request_ready,
"request_sent": False,
"owner_response_received": False,
"owner_response_accepted": False,
"contract_id": _CONTRACT_ID,
"display_name": str(target_contract.get("display_name") or ""),
"risk_level": str(target_contract.get("risk_level") or "high"),
"runner_labels": _strings(target_contract.get("runner_labels")),
"workflow_ids_requiring_attestation": workflow_ids,
"required_owner_fields": _REQUIRED_FIELDS,
"forbidden_actions": _FORBIDDEN_ACTIONS,
"allowed_response_shape": {
"redacted_metadata_only": True,
"evidence_ref_required": True,
"secret_value_allowed": False,
"runner_token_allowed": False,
"authorization_header_allowed": False,
"raw_runner_config_allowed": False,
},
"acceptance_checks": [
"runner_label_mapping_ref_present",
"runner_host_alias_present",
"capacity_owner_present",
"maintenance_window_present",
"no_secret_value_attested",
"no_runner_registration_change_attested",
"post_check_owner_present",
],
"rejection_rules": [
"contains_secret_value",
"contains_runner_token",
"requests_runner_label_change",
"requests_runner_registration",
"requests_workflow_dispatch",
"missing_evidence_ref",
],
},
"rollups": {
"request_template_ready": request_ready,
"request_packet_count": int(request_ready),
"workflow_requiring_attestation_count": len(workflow_ids),
"runner_contracts_requiring_action_count": len(action_contracts),
"required_owner_field_count": len(_REQUIRED_FIELDS),
"forbidden_action_count": len(_FORBIDDEN_ACTIONS),
"owner_response_received_count": 0,
"owner_response_accepted_count": 0,
"active_blocker_count": len(active_blockers),
"runtime_gate_opened": False,
},
"active_blockers": active_blockers,
"operation_boundaries": {
"read_only_api_allowed": True,
"request_send_performed": False,
"workflow_modification_allowed": False,
"workflow_trigger_allowed": False,
"runner_label_change_allowed": False,
"runner_registration_allowed": False,
"runner_restart_allowed": False,
"runner_container_stop_allowed": False,
"secret_read_allowed": False,
"secret_plaintext_allowed": False,
"runner_token_read_allowed": False,
"gitea_api_write_allowed": False,
"github_api_used": False,
},
}
def _dict(value: Any) -> dict[str, Any]:
return value if isinstance(value, dict) else {}
def _strings(value: Any) -> list[str]:
if not isinstance(value, list):
return []
return [str(item) for item in value if item is not None]
def _first_string(value: Any) -> str:
values = _strings(value)
return values[0] if values else ""

View File

@@ -276,6 +276,31 @@ def _assert_delivery_workbench_shape(data: dict):
assert data["summary"]["github_global_freeze_enabled"] is True
assert data["summary"]["github_lane_status"] == "stopped_retired_do_not_use"
assert data["summary"]["github_lane_excluded_from_p0_blocker_count"] is True
assert data["summary"]["gitea_runner_attestation_request_status"] == (
"owner_attestation_request_ready_waiting_response"
)
assert data["summary"]["gitea_runner_attestation_request_ready"] is True
assert data["summary"]["gitea_runner_attestation_request_active_blocker_count"] == 1
assert data["summary"]["gitea_runner_attestation_contract_id"] == (
"ubuntu_latest_gitea_runner_label"
)
assert data["summary"]["gitea_runner_attestation_runner_label"] == "ubuntu-latest"
assert data["summary"]["gitea_runner_attestation_workflow_count"] == 8
assert data["summary"]["gitea_runner_attestation_required_owner_field_count"] >= 10
assert data["summary"]["gitea_runner_attestation_forbidden_action_count"] >= 8
assert data["summary"]["gitea_runner_attestation_owner_response_received_count"] == 0
assert data["summary"]["gitea_runner_attestation_owner_response_accepted_count"] == 0
assert data["summary"]["gitea_runner_attestation_request_sent"] is False
assert data["summary"]["gitea_runner_attestation_request_send_performed"] is False
assert (
data["summary"]["gitea_runner_attestation_runner_label_change_allowed"]
is False
)
assert (
data["summary"]["gitea_runner_attestation_runner_registration_allowed"]
is False
)
assert data["summary"]["gitea_runner_attestation_secret_read_allowed"] is False
assert data["summary"]["github_blocked_preflight_target_count"] == 0
assert data["summary"]["github_operator_unblock_required"] is False
assert data["summary"]["reboot_auto_recovery_status"] == (

View File

@@ -0,0 +1,53 @@
from __future__ import annotations
from fastapi import FastAPI
from fastapi.testclient import TestClient
from src.api.v1.agents import router
def test_gitea_workflow_runner_owner_attestation_request_endpoint():
app = FastAPI()
app.include_router(router, prefix="/api/v1")
client = TestClient(app)
response = client.get(
"/api/v1/agents/gitea-workflow-runner-owner-attestation-request"
)
assert response.status_code == 200
data = response.json()
assert (
data["schema_version"]
== "gitea_workflow_runner_owner_attestation_request_v1"
)
assert data["priority"] == "P1-002"
assert data["status"] == "owner_attestation_request_ready_waiting_response"
assert data["readback"]["contract_id"] == "ubuntu_latest_gitea_runner_label"
assert data["readback"]["runner_label"] == "ubuntu-latest"
assert data["request_packet"]["request_template_ready"] is True
assert data["request_packet"]["request_sent"] is False
assert data["request_packet"]["owner_response_received"] is False
assert data["request_packet"]["owner_response_accepted"] is False
assert (
len(data["request_packet"]["workflow_ids_requiring_attestation"])
== data["rollups"]["workflow_requiring_attestation_count"]
== 8
)
assert data["rollups"]["required_owner_field_count"] >= 10
assert data["rollups"]["owner_response_received_count"] == 0
assert data["rollups"]["owner_response_accepted_count"] == 0
assert "owner_attestation_response_not_received" in data["active_blockers"]
boundaries = data["operation_boundaries"]
assert boundaries["read_only_api_allowed"] is True
assert boundaries["request_send_performed"] is False
assert boundaries["workflow_modification_allowed"] is False
assert boundaries["workflow_trigger_allowed"] is False
assert boundaries["runner_label_change_allowed"] is False
assert boundaries["runner_registration_allowed"] is False
assert boundaries["runner_restart_allowed"] is False
assert boundaries["secret_read_allowed"] is False
assert boundaries["runner_token_read_allowed"] is False
assert boundaries["gitea_api_write_allowed"] is False
assert boundaries["github_api_used"] is False

View File

@@ -51,6 +51,19 @@
**邊界**:未重啟主機,未 restart service未 workflow_dispatch未操作 host / Docker / K8s / DB / firewall未使用 GitHub / `gh` / GitHub API未讀 secret / token / raw sessions / SQLite / `.env`
## 2026-06-30 — 07:38 P1-002 Gitea runner owner attestation request readback
**照主線 next_focus 推進的實作**
- P0-006 reboot SLO 仍只剩 `host_boot_observation_older_than_target_window`,未重啟主機;因此依 Delivery Workbench 下一個可執行主線推進 Gitea runner owner attestation request。
- 新增 `GET /api/v1/agents/gitea-workflow-runner-owner-attestation-request`,把 `ubuntu_latest_gitea_runner_label`、8 個需 owner attestation 的 workflow、11 個 required owner fields、9 個 forbidden actions 與 response acceptance / rejection rules 轉成機器可讀 request packet。
- Delivery Workbench 的 `gitea` lane 與 summary 現在顯示 request ready、workflow count、required field count、response received / accepted count 與 runner mutation/secret/Gitea write 邊界。
**驗證**
- Focused pytestGitea runner attestation request / workflow runner health / Delivery Workbench / CD profile `29 passed`
- `py_compile`、Gitea runner pressure guard、`git diff --check` 通過。
**邊界**:未送出 request未呼叫 Gitea API未修改 workflow未 workflow_dispatch未註冊 / 重啟 runner未改 runner label未操作 host / Docker / K8s / DB / firewall未讀 secret / token / raw sessions / SQLite / `.env`,未使用 GitHub / `gh` / GitHub API。
## 2026-06-30 — 00:41 P0-004 template copy receipt runtime-image readback 修正
**照優先順序完成的實作**

View File

@@ -228,7 +228,9 @@ def test_gitea_private_inventory_scorecard_stays_on_controlled_runtime_profile()
"apps/api/src/services/gitea_owner_coverage_attestation_validation.py)",
"apps/api/src/services/gitea_private_inventory_closeout_validation.py)",
"apps/api/src/services/gitea_private_inventory_p0_scorecard.py)",
"apps/api/src/services/gitea_workflow_runner_owner_attestation_request.py)",
"apps/api/tests/test_gitea_private_inventory_p0_scorecard_api.py)",
"apps/api/tests/test_gitea_workflow_runner_owner_attestation_request_api.py)",
"docs/operations/awoooi-gitea-authenticated-inventory-payload-validation.snapshot.json)",
"docs/security/GITEA-REPO-INVENTORY-SNAPSHOT.md)",
"docs/security/gitea-repo-inventory.snapshot.json)",
@@ -240,7 +242,9 @@ def test_gitea_private_inventory_scorecard_stays_on_controlled_runtime_profile()
"src/services/gitea_owner_coverage_attestation_validation.py",
"src/services/gitea_private_inventory_closeout_validation.py",
"src/services/gitea_private_inventory_p0_scorecard.py",
"src/services/gitea_workflow_runner_owner_attestation_request.py",
"tests/test_gitea_private_inventory_p0_scorecard_api.py",
"tests/test_gitea_workflow_runner_owner_attestation_request_api.py",
"scripts/security/tests/test_gitea_authenticated_inventory_payload_validator.py)",
"../../scripts/security/gitea-private-inventory-p0-scorecard.py",
"../../scripts/security/gitea-authenticated-inventory-payload-validator.py",