feat(security): 新增主機服務配置只讀清冊
This commit is contained in:
@@ -119,14 +119,16 @@ CONTROL_STATUS_BY_CATEGORY = {
|
||||
"next_owner_action": "補 rule diff、receiver diff、reload owner、failure-only notification policy 與 route smoke。",
|
||||
},
|
||||
"docker_compose_systemd_host_config": {
|
||||
"coverage_status": "inventory_needed",
|
||||
"coverage_percent": 42,
|
||||
"coverage_status": "repo_only_inventory_ready_needs_live_owner_evidence",
|
||||
"coverage_percent": 50,
|
||||
"evidence_refs": [
|
||||
"docs/security/IWOOOS-CONFIG-CONTROL-INVENTORY.md",
|
||||
"docs/security/HOST-SERVICE-CONFIG-INVENTORY.md",
|
||||
"docs/security/host-service-config-inventory.snapshot.json",
|
||||
"docs/security/DEV-HOSTS-112-111-168-OBSERVE-ONLY-MAPPING.md",
|
||||
],
|
||||
"current_gap": "110 / 188 Docker Compose、systemd、port / volume / env 差異仍需只讀 inventory。",
|
||||
"next_owner_action": "補 compose / systemd owner、restart window、rollback owner 與 post-check 指標。",
|
||||
"current_gap": "repo-only 清冊已納入 9 個 surface;仍缺 110 / 188 live hash、restart window、rollback owner 與 post-check 指標。",
|
||||
"next_owner_action": "補 owner-provided live hash / disposition、compose / systemd owner、restart window、rollback owner 與 post-check 指標。",
|
||||
},
|
||||
"ssh_firewall_network_access": {
|
||||
"coverage_status": "policy_ready_needs_network_matrix",
|
||||
@@ -254,6 +256,7 @@ def build_report(root: Path, generated_at: str | None) -> dict[str, Any]:
|
||||
"policy_defined_needs_restore_drill_owner",
|
||||
"policy_ready_needs_drift_evidence",
|
||||
"inventory_needed",
|
||||
"repo_only_inventory_ready_needs_live_owner_evidence",
|
||||
"policy_ready_needs_network_matrix",
|
||||
"policy_ready_needs_dry_run_pack",
|
||||
}
|
||||
|
||||
310
scripts/security/host-service-config-inventory.py
Normal file
310
scripts/security/host-service-config-inventory.py
Normal file
@@ -0,0 +1,310 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
IwoooS Docker / systemd / host service repo-only 清冊。
|
||||
|
||||
本工具只讀取已提交的 repo 檔案,整理 Docker Compose、systemd/repair
|
||||
白名單、Ansible service role 與 config backup coverage。它不 SSH、不讀
|
||||
live host、不執行 docker compose、不執行 systemctl、不重啟任何服務。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
TAIPEI = timezone(timedelta(hours=8))
|
||||
|
||||
|
||||
SURFACES: list[dict[str, Any]] = [
|
||||
{
|
||||
"surface_id": "local_dev_compose",
|
||||
"label": "AWOOOI local development compose",
|
||||
"source_path": "docker-compose.yml",
|
||||
"expected_host_scope": "local_dev_only",
|
||||
"config_kind": "docker_compose_source",
|
||||
"service_scope": ["web", "api", "postgres", "redis"],
|
||||
"control_tier": "C1",
|
||||
"current_state": "repo_source_visible",
|
||||
"requires_live_evidence": False,
|
||||
"requires_owner_response": True,
|
||||
"next_owner_action": "確認本檔僅供 local dev,不得作為 production compose;補 dev secret placeholder policy。",
|
||||
},
|
||||
{
|
||||
"surface_id": "monitoring_110_compose",
|
||||
"label": "110 monitoring docker compose",
|
||||
"source_path": "k8s/monitoring/docker-compose-110.yml",
|
||||
"expected_host_scope": "192.168.0.110",
|
||||
"config_kind": "docker_compose_source",
|
||||
"service_scope": ["cadvisor", "prometheus", "grafana", "blackbox-exporter", "alertmanager", "github-exporter"],
|
||||
"control_tier": "C1",
|
||||
"current_state": "repo_source_visible_with_live_drift_warning",
|
||||
"requires_live_evidence": True,
|
||||
"requires_owner_response": True,
|
||||
"next_owner_action": "補 110 live compose hash、restart window、rollback owner、post-check 指標與 drift disposition。",
|
||||
},
|
||||
{
|
||||
"surface_id": "monitoring_exporters_188_compose",
|
||||
"label": "188 database exporters compose",
|
||||
"source_path": "ops/monitoring/docker-compose.exporters.yaml",
|
||||
"expected_host_scope": "192.168.0.188",
|
||||
"config_kind": "docker_compose_source",
|
||||
"service_scope": ["postgres-exporter", "redis-exporter"],
|
||||
"control_tier": "C1",
|
||||
"current_state": "repo_source_visible_needs_live_hash",
|
||||
"requires_live_evidence": True,
|
||||
"requires_owner_response": True,
|
||||
"next_owner_action": "補 188 exporter compose live hash、env source policy、restart window 與 rollback owner。",
|
||||
},
|
||||
{
|
||||
"surface_id": "sentry_110_reference_compose",
|
||||
"label": "110 Sentry self-hosted reference compose",
|
||||
"source_path": "ops/sentry-self-hosted/docker-compose.yml",
|
||||
"expected_host_scope": "192.168.0.110",
|
||||
"config_kind": "docker_compose_reference",
|
||||
"service_scope": ["sentry-placeholder-reference"],
|
||||
"control_tier": "C1",
|
||||
"current_state": "reference_only_not_runtime_source",
|
||||
"requires_live_evidence": True,
|
||||
"requires_owner_response": True,
|
||||
"next_owner_action": "確認 110 Sentry 實際 source-of-truth、official self-hosted revision、backup path 與 rollback owner。",
|
||||
},
|
||||
{
|
||||
"surface_id": "langfuse_110_compose",
|
||||
"label": "110 Langfuse compose",
|
||||
"source_path": "infra/langfuse/docker-compose.yml",
|
||||
"expected_host_scope": "192.168.0.110",
|
||||
"config_kind": "docker_compose_source",
|
||||
"service_scope": ["langfuse", "langfuse-db"],
|
||||
"control_tier": "C1",
|
||||
"current_state": "repo_source_visible_needs_secret_policy_review",
|
||||
"requires_live_evidence": True,
|
||||
"requires_owner_response": True,
|
||||
"next_owner_action": "補 110 live compose hash、secret placeholder disposition、restart window 與 rollback owner。",
|
||||
},
|
||||
{
|
||||
"surface_id": "ansible_docker_compose_service_role",
|
||||
"label": "Ansible docker-compose-service role",
|
||||
"source_path": "infra/ansible/roles/docker-compose-service/tasks/main.yml",
|
||||
"expected_host_scope": "multi_host",
|
||||
"config_kind": "ansible_service_executor",
|
||||
"service_scope": ["docker compose up -d"],
|
||||
"control_tier": "C1",
|
||||
"current_state": "executor_role_visible_needs_gate_mapping",
|
||||
"requires_live_evidence": True,
|
||||
"requires_owner_response": True,
|
||||
"next_owner_action": "補 role 使用範圍、allowed service_dir、check-mode plan、rollback owner 與人工批准 gate。",
|
||||
},
|
||||
{
|
||||
"surface_id": "repair_bot_110_whitelist",
|
||||
"label": "110 repair-bot compose whitelist",
|
||||
"source_path": "scripts/repair-bot/repair-bot-110.sh",
|
||||
"expected_host_scope": "192.168.0.110",
|
||||
"config_kind": "host_repair_whitelist",
|
||||
"service_scope": ["sentry", "harbor", "gitea", "gitea-runner", "langfuse", "alertmanager", "signoz"],
|
||||
"control_tier": "C1",
|
||||
"current_state": "write_capable_whitelist_visible_gate_closed",
|
||||
"requires_live_evidence": True,
|
||||
"requires_owner_response": True,
|
||||
"next_owner_action": "補 authorized_keys command binding、disable switch、audit log path、rollback owner 與 post-check 指標。",
|
||||
},
|
||||
{
|
||||
"surface_id": "repair_bot_188_whitelist",
|
||||
"label": "188 repair-bot compose/systemd whitelist",
|
||||
"source_path": "scripts/repair-bot/repair-bot-188.sh",
|
||||
"expected_host_scope": "192.168.0.188",
|
||||
"config_kind": "host_repair_whitelist",
|
||||
"service_scope": ["openclaw", "minio", "signoz", "redis", "nginx", "ollama"],
|
||||
"control_tier": "C1",
|
||||
"current_state": "write_capable_whitelist_visible_gate_closed",
|
||||
"requires_live_evidence": True,
|
||||
"requires_owner_response": True,
|
||||
"next_owner_action": "補 systemd restart approval gate、sudoers boundary、disable switch、rollback owner 與 route smoke。",
|
||||
},
|
||||
{
|
||||
"surface_id": "config_backup_host_capture",
|
||||
"label": "host config backup capture contract",
|
||||
"source_path": "scripts/backup/backup-configs.sh",
|
||||
"expected_host_scope": "110_188_120_121_cluster",
|
||||
"config_kind": "backup_capture_contract",
|
||||
"service_scope": ["systemd", "docker", "nginx", "cron", "k8s", "host-configs"],
|
||||
"control_tier": "C1",
|
||||
"current_state": "capture_script_visible_not_executed_by_this_inventory",
|
||||
"requires_live_evidence": True,
|
||||
"requires_owner_response": True,
|
||||
"next_owner_action": "補 latest backup status、restore drill owner、secret handling proof、retention owner 與 restore validation plan。",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
FALSE_BOUNDARIES = {
|
||||
"runtime_execution_authorized": False,
|
||||
"host_write_authorized": False,
|
||||
"ssh_read_authorized": False,
|
||||
"ssh_write_authorized": False,
|
||||
"docker_compose_action_authorized": False,
|
||||
"systemctl_action_authorized": False,
|
||||
"service_restart_authorized": False,
|
||||
"sudo_action_authorized": False,
|
||||
"live_host_read_authorized": False,
|
||||
"secret_value_collection_allowed": False,
|
||||
"active_scan_authorized": False,
|
||||
"repair_bot_execution_authorized": False,
|
||||
"ansible_apply_authorized": False,
|
||||
"action_buttons_allowed": False,
|
||||
}
|
||||
|
||||
|
||||
def git_short_sha(root: Path) -> str:
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["git", "rev-parse", "--short", "HEAD"],
|
||||
cwd=root,
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
return result.stdout.strip()
|
||||
except Exception:
|
||||
return "unknown"
|
||||
|
||||
|
||||
def file_metadata(root: Path, source_path: str) -> dict[str, Any]:
|
||||
path = root / source_path
|
||||
exists = path.exists()
|
||||
if not exists:
|
||||
return {"source_exists": False, "line_count": 0, "sha256": None}
|
||||
content = path.read_bytes()
|
||||
return {
|
||||
"source_exists": True,
|
||||
"line_count": len(content.decode("utf-8", errors="replace").splitlines()),
|
||||
"sha256": hashlib.sha256(content).hexdigest(),
|
||||
}
|
||||
|
||||
|
||||
def build_surface(root: Path, surface: dict[str, Any]) -> dict[str, Any]:
|
||||
metadata = file_metadata(root, surface["source_path"])
|
||||
return {
|
||||
**surface,
|
||||
**metadata,
|
||||
"owner_response_received": False,
|
||||
"owner_response_accepted": False,
|
||||
"live_evidence_received": False,
|
||||
"restart_window_accepted": False,
|
||||
"rollback_owner_accepted": False,
|
||||
"runtime_gate_open": False,
|
||||
"action_buttons_allowed": False,
|
||||
}
|
||||
|
||||
|
||||
def build_report(root: Path, generated_at: str | None) -> dict[str, Any]:
|
||||
report_time = generated_at or datetime.now(TAIPEI).isoformat(timespec="seconds")
|
||||
surfaces = [build_surface(root, surface) for surface in SURFACES]
|
||||
expected_hosts = sorted({surface["expected_host_scope"] for surface in surfaces})
|
||||
write_capable = [
|
||||
surface
|
||||
for surface in surfaces
|
||||
if surface["config_kind"] in {"host_repair_whitelist", "ansible_service_executor"}
|
||||
]
|
||||
live_evidence = [surface for surface in surfaces if surface["requires_live_evidence"]]
|
||||
|
||||
return {
|
||||
"schema_version": "host_service_config_inventory_v1",
|
||||
"generated_at": report_time,
|
||||
"git_commit": git_short_sha(root),
|
||||
"status": "repo_only_inventory_ready",
|
||||
"source_scope": "committed_repo_files_only",
|
||||
"summary": {
|
||||
"surface_count": len(surfaces),
|
||||
"source_exists_count": sum(1 for surface in surfaces if surface["source_exists"]),
|
||||
"expected_host_scope_count": len(expected_hosts),
|
||||
"docker_compose_source_count": sum(
|
||||
1 for surface in surfaces if surface["config_kind"] in {"docker_compose_source", "docker_compose_reference"}
|
||||
),
|
||||
"host_repair_whitelist_count": sum(1 for surface in surfaces if surface["config_kind"] == "host_repair_whitelist"),
|
||||
"systemd_restart_surface_count": 1,
|
||||
"write_capable_surface_count": len(write_capable),
|
||||
"surfaces_requiring_owner_response_count": sum(1 for surface in surfaces if surface["requires_owner_response"]),
|
||||
"surfaces_requiring_live_evidence_count": len(live_evidence),
|
||||
"owner_response_received_count": 0,
|
||||
"owner_response_accepted_count": 0,
|
||||
"live_evidence_received_count": 0,
|
||||
"restart_window_accepted_count": 0,
|
||||
"rollback_owner_accepted_count": 0,
|
||||
"runtime_gate_count": 0,
|
||||
"action_button_count": 0,
|
||||
"coverage_percent_after_inventory": 50,
|
||||
"coverage_percent_before_inventory": 42,
|
||||
},
|
||||
"execution_boundaries": FALSE_BOUNDARIES,
|
||||
"expected_host_scopes": expected_hosts,
|
||||
"config_surfaces": surfaces,
|
||||
"write_capable_surfaces": [
|
||||
{
|
||||
"surface_id": surface["surface_id"],
|
||||
"label": surface["label"],
|
||||
"config_kind": surface["config_kind"],
|
||||
"expected_host_scope": surface["expected_host_scope"],
|
||||
"service_scope": surface["service_scope"],
|
||||
"required_gate": "owner_response_plus_maintenance_window_plus_rollback_owner",
|
||||
}
|
||||
for surface in write_capable
|
||||
],
|
||||
"next_collection_order": [
|
||||
"repair_bot_110_whitelist",
|
||||
"repair_bot_188_whitelist",
|
||||
"monitoring_110_compose",
|
||||
"monitoring_exporters_188_compose",
|
||||
"langfuse_110_compose",
|
||||
"config_backup_host_capture",
|
||||
"ansible_docker_compose_service_role",
|
||||
"sentry_110_reference_compose",
|
||||
"local_dev_compose",
|
||||
],
|
||||
"operator_interpretation": [
|
||||
"這是 repo-only 主機服務配置清冊,不是 live host 盤點。",
|
||||
"write-capable 白名單與 Ansible role 可見,不代表 repair-bot、docker compose、systemctl 或 sudo 已授權。",
|
||||
"所有 live hash、restart window、rollback owner、post-check 指標都仍需 owner response。",
|
||||
"本清冊讓 Docker/systemd 類別從 inventory_needed 進到 repo_only_inventory_ready,但 runtime gate 仍為 0。",
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="IwoooS host service repo-only config inventory")
|
||||
parser.add_argument("--root", default=".", help="repo 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(root, args.generated_at)
|
||||
payload = json.dumps(report, ensure_ascii=False, indent=2, sort_keys=True)
|
||||
|
||||
if args.output:
|
||||
output = Path(args.output)
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
output.write_text(payload + "\n", encoding="utf-8")
|
||||
else:
|
||||
print(payload)
|
||||
|
||||
summary = report["summary"]
|
||||
print(
|
||||
"HOST_SERVICE_CONFIG_INVENTORY_OK "
|
||||
f"surfaces={summary['surface_count']} "
|
||||
f"hosts={summary['expected_host_scope_count']} "
|
||||
f"write_capable={summary['write_capable_surface_count']} "
|
||||
f"runtime_gate={summary['runtime_gate_count']}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -88,6 +88,7 @@ def validate(root: Path) -> None:
|
||||
rollout_policy = load_json(security_dir / "security-rollout-policy.snapshot.json")
|
||||
iwooos_projection = load_json(security_dir / "iwooos-posture-projection.snapshot.json")
|
||||
high_value_config_coverage = load_json(security_dir / "high-value-config-control-coverage.snapshot.json")
|
||||
host_service_config_inventory = load_json(security_dir / "host-service-config-inventory.snapshot.json")
|
||||
domain_tls_inventory = load_json(security_dir / "domain-tls-certbot-inventory.snapshot.json")
|
||||
s49_request_draft = load_json(security_dir / "gitea-inventory-owner-attestation-request-draft.snapshot.json")
|
||||
kali_status = load_json(security_dir / "kali-integration-status.snapshot.json")
|
||||
@@ -2452,6 +2453,142 @@ def validate(root: Path) -> None:
|
||||
[item["category_id"] for item in high_value_config_coverage["lowest_coverage_categories"]],
|
||||
"docker_compose_systemd_host_config",
|
||||
)
|
||||
docker_systemd_category = next(
|
||||
item
|
||||
for item in high_value_config_coverage["coverage_categories"]
|
||||
if item["category_id"] == "docker_compose_systemd_host_config"
|
||||
)
|
||||
assert_equal(
|
||||
"high_value_config_coverage.coverage_categories.docker.coverage_percent",
|
||||
docker_systemd_category["coverage_percent"],
|
||||
50,
|
||||
)
|
||||
assert_equal(
|
||||
"high_value_config_coverage.coverage_categories.docker.coverage_status",
|
||||
docker_systemd_category["coverage_status"],
|
||||
"repo_only_inventory_ready_needs_live_owner_evidence",
|
||||
)
|
||||
for evidence_ref in [
|
||||
"docs/security/HOST-SERVICE-CONFIG-INVENTORY.md",
|
||||
"docs/security/host-service-config-inventory.snapshot.json",
|
||||
]:
|
||||
assert_contains(
|
||||
"high_value_config_coverage.coverage_categories.docker.evidence_refs",
|
||||
docker_systemd_category["evidence_refs"],
|
||||
evidence_ref,
|
||||
)
|
||||
assert_equal(
|
||||
"host_service_config_inventory.schema",
|
||||
host_service_config_inventory["schema_version"],
|
||||
"host_service_config_inventory_v1",
|
||||
)
|
||||
assert_equal(
|
||||
"host_service_config_inventory.status",
|
||||
host_service_config_inventory["status"],
|
||||
"repo_only_inventory_ready",
|
||||
)
|
||||
assert_equal(
|
||||
"host_service_config_inventory.source_scope",
|
||||
host_service_config_inventory["source_scope"],
|
||||
"committed_repo_files_only",
|
||||
)
|
||||
expected_host_service_config_summary = {
|
||||
"surface_count": 9,
|
||||
"source_exists_count": 9,
|
||||
"expected_host_scope_count": 5,
|
||||
"docker_compose_source_count": 5,
|
||||
"host_repair_whitelist_count": 2,
|
||||
"systemd_restart_surface_count": 1,
|
||||
"write_capable_surface_count": 3,
|
||||
"surfaces_requiring_owner_response_count": 9,
|
||||
"surfaces_requiring_live_evidence_count": 8,
|
||||
"owner_response_received_count": 0,
|
||||
"owner_response_accepted_count": 0,
|
||||
"live_evidence_received_count": 0,
|
||||
"restart_window_accepted_count": 0,
|
||||
"rollback_owner_accepted_count": 0,
|
||||
"runtime_gate_count": 0,
|
||||
"action_button_count": 0,
|
||||
"coverage_percent_before_inventory": 42,
|
||||
"coverage_percent_after_inventory": 50,
|
||||
}
|
||||
for key, expected in expected_host_service_config_summary.items():
|
||||
assert_equal(
|
||||
f"host_service_config_inventory.summary.{key}",
|
||||
host_service_config_inventory["summary"][key],
|
||||
expected,
|
||||
)
|
||||
for key, value in host_service_config_inventory["execution_boundaries"].items():
|
||||
assert_false(f"host_service_config_inventory.execution_boundaries.{key}", value)
|
||||
assert_equal(
|
||||
"host_service_config_inventory.config_surfaces.count",
|
||||
len(host_service_config_inventory["config_surfaces"]),
|
||||
9,
|
||||
)
|
||||
host_service_surface_ids = [item["surface_id"] for item in host_service_config_inventory["config_surfaces"]]
|
||||
for surface_id in [
|
||||
"repair_bot_110_whitelist",
|
||||
"repair_bot_188_whitelist",
|
||||
"monitoring_110_compose",
|
||||
"config_backup_host_capture",
|
||||
]:
|
||||
assert_contains(
|
||||
"host_service_config_inventory.config_surfaces",
|
||||
host_service_surface_ids,
|
||||
surface_id,
|
||||
)
|
||||
for surface in host_service_config_inventory["config_surfaces"]:
|
||||
assert_true(
|
||||
f"host_service_config_inventory.config_surfaces.{surface['surface_id']}.source_exists",
|
||||
surface["source_exists"],
|
||||
)
|
||||
assert_equal(
|
||||
f"host_service_config_inventory.config_surfaces.{surface['surface_id']}.sha256_length",
|
||||
len(surface["sha256"]),
|
||||
64,
|
||||
)
|
||||
assert_true(
|
||||
f"host_service_config_inventory.config_surfaces.{surface['surface_id']}.requires_owner_response",
|
||||
surface["requires_owner_response"],
|
||||
)
|
||||
for key in [
|
||||
"owner_response_received",
|
||||
"owner_response_accepted",
|
||||
"live_evidence_received",
|
||||
"restart_window_accepted",
|
||||
"rollback_owner_accepted",
|
||||
"runtime_gate_open",
|
||||
"action_buttons_allowed",
|
||||
]:
|
||||
assert_false(
|
||||
f"host_service_config_inventory.config_surfaces.{surface['surface_id']}.{key}",
|
||||
surface[key],
|
||||
)
|
||||
assert_equal(
|
||||
"host_service_config_inventory.write_capable_surfaces.count",
|
||||
len(host_service_config_inventory["write_capable_surfaces"]),
|
||||
3,
|
||||
)
|
||||
for surface_id in [
|
||||
"ansible_docker_compose_service_role",
|
||||
"repair_bot_110_whitelist",
|
||||
"repair_bot_188_whitelist",
|
||||
]:
|
||||
assert_contains(
|
||||
"host_service_config_inventory.write_capable_surfaces",
|
||||
[item["surface_id"] for item in host_service_config_inventory["write_capable_surfaces"]],
|
||||
surface_id,
|
||||
)
|
||||
for source_path in [
|
||||
"docs/security/host-service-config-inventory.snapshot.json",
|
||||
"docs/security/HOST-SERVICE-CONFIG-INVENTORY.md",
|
||||
"docs/schemas/host_service_config_inventory_v1.schema.json",
|
||||
]:
|
||||
assert_contains(
|
||||
"iwooos_projection.source_paths.host_service_config_inventory",
|
||||
iwooos_projection["source_paths"],
|
||||
source_path,
|
||||
)
|
||||
assert_true(
|
||||
"iwooos_projection.summary.high_value_config_control_coverage_first_layer",
|
||||
iwooos_projection["summary"]["high_value_config_control_coverage_first_layer"],
|
||||
@@ -2485,6 +2622,33 @@ def validate(root: Path) -> None:
|
||||
iwooos_projection["summary"][key],
|
||||
expected,
|
||||
)
|
||||
expected_host_service_projection_summary = {
|
||||
"host_service_config_inventory_first_layer": True,
|
||||
"host_service_config_inventory_surface_count": 9,
|
||||
"host_service_config_inventory_source_exists_count": 9,
|
||||
"host_service_config_inventory_expected_host_scope_count": 5,
|
||||
"host_service_config_inventory_docker_compose_source_count": 5,
|
||||
"host_service_config_inventory_host_repair_whitelist_count": 2,
|
||||
"host_service_config_inventory_systemd_restart_surface_count": 1,
|
||||
"host_service_config_inventory_write_capable_surface_count": 3,
|
||||
"host_service_config_inventory_owner_response_required_count": 9,
|
||||
"host_service_config_inventory_owner_response_received_count": 0,
|
||||
"host_service_config_inventory_owner_response_accepted_count": 0,
|
||||
"host_service_config_inventory_live_evidence_required_count": 8,
|
||||
"host_service_config_inventory_live_evidence_received_count": 0,
|
||||
"host_service_config_inventory_restart_window_accepted_count": 0,
|
||||
"host_service_config_inventory_rollback_owner_accepted_count": 0,
|
||||
"host_service_config_inventory_runtime_gate_count": 0,
|
||||
"host_service_config_inventory_action_button_count": 0,
|
||||
"host_service_config_inventory_coverage_percent_before_inventory": 42,
|
||||
"host_service_config_inventory_coverage_percent_after_inventory": 50,
|
||||
}
|
||||
for key, expected in expected_host_service_projection_summary.items():
|
||||
assert_equal(
|
||||
f"iwooos_projection.summary.{key}",
|
||||
iwooos_projection["summary"][key],
|
||||
expected,
|
||||
)
|
||||
assert_true(
|
||||
"iwooos_projection.summary.high_value_config_owner_packet_first_layer",
|
||||
iwooos_projection["summary"]["high_value_config_owner_packet_first_layer"],
|
||||
@@ -11594,6 +11758,11 @@ def validate(root: Path) -> None:
|
||||
iwooos_projection_page,
|
||||
"IwoooSHighValueConfigControlCoverageBoard",
|
||||
)
|
||||
assert_text_contains(
|
||||
"iwooos_page.high_value_config_control_coverage_docker_systemd_percent",
|
||||
iwooos_projection_page,
|
||||
"{ key: 'dockerSystemd', rank: 'P1-1', value: '50%'",
|
||||
)
|
||||
assert_text_before(
|
||||
"iwooos_page.high_value_config_control_coverage_before_owner_packet",
|
||||
iwooos_projection_page,
|
||||
@@ -11613,6 +11782,13 @@ def validate(root: Path) -> None:
|
||||
"high_value_config_control_coverage_owner_response_accepted_count=0",
|
||||
"high_value_config_control_coverage_runtime_gate_count=0",
|
||||
"high_value_config_control_coverage_action_button_count=0",
|
||||
"host_service_config_inventory_surface_count=9",
|
||||
"host_service_config_inventory_write_capable_surface_count=3",
|
||||
"host_service_config_inventory_runtime_gate_count=0",
|
||||
"docker_compose_action_authorized=false",
|
||||
"systemctl_action_authorized=false",
|
||||
"repair_bot_execution_authorized=false",
|
||||
"ansible_apply_authorized=false",
|
||||
"runtime_execution_authorized=false",
|
||||
"host_write_authorized=false",
|
||||
"nginx_reload_authorized=false",
|
||||
|
||||
Reference in New Issue
Block a user