## Phase 1-3: Control Plane + Contract System - awooop_phase1_control_plane_2026-05-04.sql: 12 張核心表 + RLS - awooop_phase1_batch1_rls_2026-05-04.sql: 全部 FORCE RLS + GRANT - packages/awooop-contracts/: 六合約 JSON Schema + golden fixtures - src/models/awooop_contracts.py: Pydantic v2 contract models(extra=forbid) - src/repositories/contract_repository.py: contract lifecycle(draft→published→active) - src/services/contract_service.py: HMAC publish sig + Redis multi-sig activate - src/services/schema_validator.py: LLM output validator(retry×3, E-SCHEMA-001) ## Phase 2: Tenant Isolation - awooop_phase2_budget_ledger_2026-05-04.sql: budget_ledger + RLS - src/services/budget_service.py: Token Budget Hard Kill 三層防線 - src/core/context.py: PROJECT_ID ContextVar(31 background loop 自動繼承) - src/db/base.py + models.py: project_id 欄位 + RLS set_config 注入 - src/hermes/nl_gateway.py: project_id Redis key 前綴(Phase A 雙寫) - src/services/anomaly_counter.py: per-project 改造(Phase A fallback) ## Phase 4: Platform Shell in Shadow Mode - awooop_phase4_run_state_2026-05-04.sql: run_state + step_journal + idempotency - src/services/run_state_machine.py: 8-state FSM + SKIP LOCKED + stale reaper - src/services/platform_runtime.py: UUID v7 + W3C trace_id + shadow_execute - src/services/audit_sink.py: PII/secret redaction 9 patterns - src/api/v1/platform/runs.py: POST/GET /v1/platform/runs(Router→Service 架構) - src/workers/platform_worker.py: SKIP LOCKED worker + heartbeat + reaper loop - src/main.py: platform router + lifespan worker start/stop ## Phase 5: MCP Gateway 五閘門 - awooop_phase5_mcp_gateway_2026-05-04.sql: 4 表 + RLS - src/plugins/mcp/gateway.py: McpGateway(Gate 1~5, E-MCP-GATE-001~009) - src/plugins/mcp/redaction_middleware.py: 雙層 redaction + 16K 截斷 - src/plugins/mcp/registry.py: __provider name mangling(ADR-116) - src/plugins/mcp/credential_resolver.py: k8s secret ref 解析 - tests/test_mcp_credential_isolation.py: 10 個迴歸測試(secret leak 防再現) ## Phase 6-8: EwoooC + Channel Hub + Approval Token - awooop_phase6_ewoooc_onboarding_2026-05-04.sql: ewoooc tenant + 4 read-only MCP tools - awooop_phase7_channel_hub_2026-05-04.sql: conversation_event + outbound_message - src/services/provider_proxy.py: ProviderProxy + PlatformEnvelope(ADR-115) - src/services/channel_hub.py: Telegram inbound mirror + Progressive Feedback(30s) - src/services/awooop_approval_token.py: HS256 + jti NX replay 防護 + suggest mode Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
109 lines
5.2 KiB
Markdown
109 lines
5.2 KiB
Markdown
# INV-4: Hardcoded Namespace & IP Inventory
|
||
|
||
**版本**:v1.0 初稿
|
||
**日期**:2026-05-03(台北)
|
||
**範圍**:`apps/api/src/` 全 codebase + `k8s/`
|
||
**用途**:Phase 2 多租戶改造 + EwoooC onboarding 前必須清理的硬碼
|
||
|
||
---
|
||
|
||
## 1. Hardcoded K8s Namespace
|
||
|
||
| 位置 | 行號 | 內容 | 影響 | 修補方式 |
|
||
|------|------|------|------|---------|
|
||
| `apps/api/src/plugins/mcp/providers/k8s_provider.py` | 40-41 | `ALLOWED_NAMESPACES = {"awoooi-prod"}` / `DEFAULT_NAMESPACE = "awoooi-prod"` | 🔴 P0-13:EwoooC K8s tool 無法操作自己的 namespace | 改為 config-driven:從 EffectivePolicy 或 project contract 讀 allowed namespaces |
|
||
| `apps/api/src/plugins/mcp/mcp_bridge.py` | 592, 602, 631, 647, 681 | `namespace = parameters.get("namespace", "awoooi-prod")` | 🔴 P0-13:5 處預設值都寫死 | 改為 `namespace = parameters.get("namespace", get_project_default_namespace(project_id))` |
|
||
| `apps/api/src/plugins/mcp/providers/signoz_provider.py` | 169 | `namespace = parameters.get("namespace", "awoooi-prod")` | 🟠 SignOz query 預設 namespace 錯誤 | 同上 |
|
||
| `apps/api/src/main.py` | 175 | `sentry_sdk.set_tag("host", "k8s-awoooi-prod")` | 🔵 Sentry tag 寫死,EwoooC 看到錯誤 host tag | 改為 `settings.SENTRY_HOST_TAG` |
|
||
| `apps/api/src/core/prompts.py` | 120, 178, 192 | 系統 prompt 中出現 `awoooi-prod` | 🔵 LLM 可能在其他 tenant 錯誤建議 awoooi-prod namespace | 改為 `{tenant_namespace}` template variable |
|
||
|
||
---
|
||
|
||
## 2. Hardcoded IP Addresses
|
||
|
||
### 內網 IP(已知用途)
|
||
|
||
| IP | 用途 | 位置 | 改法 |
|
||
|----|------|------|------|
|
||
| `192.168.0.110` | Gitea / Prometheus / Loki / MinIO | `config.py:226,413,460,813` | 已在 config 以 default 存在,OK(K8s 覆蓋)|
|
||
| `192.168.0.111` | Ollama Local Fallback | `config.py` + `feedback_ollama_111_only.md`(已更新) | ADR-110 已改為第三層 fallback,config 需更新 |
|
||
| `192.168.0.112` | ArgoCD | `config.py:396` | OK(config default)|
|
||
| `192.168.0.120` | K3s API Server | `config.py:531` | OK(config default)|
|
||
| `192.168.0.121` | K3s ingress? | `config.py:837` | 確認用途 |
|
||
| `192.168.0.188` | PostgreSQL / Redis / SigNoz / Grafana / ClickHouse | `config.py` 多處 | OK(config default,K8s 以 env 覆蓋)|
|
||
|
||
### 🔴 問題:telemetry.py IP Assertion
|
||
|
||
| 位置 | 行號 | 內容 | 問題 |
|
||
|------|------|------|------|
|
||
| `apps/api/src/core/telemetry.py` | 71 | `if "192.168.0.188" not in endpoint: raise` | 🔴 P0-08:EwoooC 啟動必失敗(EwoooC SigNoz 可能是不同 endpoint)|
|
||
| 修補方式 | | | 移除硬碼 assert,改為 `if endpoint not in settings.ALLOWED_TELEMETRY_ENDPOINTS:` |
|
||
|
||
### GCP IP(新增,ADR-110,2026-05-03 生效)
|
||
|
||
| IP | 用途 | 位置 |
|
||
|----|------|------|
|
||
| `34.143.170.20` | Ollama GCP-A Primary(SSD)| `config.py`(ADR-110 已加入 `_ALLOWED_PUBLIC_IPS`)|
|
||
| `34.21.145.224` | Ollama GCP-B Secondary(SSD)| `config.py`(ADR-110 已加入 `_ALLOWED_PUBLIC_IPS`)|
|
||
|
||
**注意**:
|
||
- K8s NetworkPolicy egress:已新增 GCP-A/GCP-B /32 出口規則(ADR-110)
|
||
- INV-4 確認:GCP IP 已在 `config.py._ALLOWED_PUBLIC_IPS` 白名單,非新增需求
|
||
- telemetry.py:71 assert:GCP IP 不影響(assert 是針對 OTEL endpoint,非 Ollama endpoint)
|
||
|
||
---
|
||
|
||
## 3. SSH Host Hardcodes
|
||
|
||
| 位置 | 內容 | 問題 | 修補 |
|
||
|------|------|------|------|
|
||
| `reference_four_hosts.md` | 110/120/121/188 四主機清單 | 文件,不是程式碼,OK | 無 |
|
||
| `apps/api/src/plugins/mcp/providers/ssh_provider.py`(若有)| SSH 目標主機 | 需 grep 確認是否硬碼 | 改為 config-driven 白名單 |
|
||
|
||
---
|
||
|
||
## 4. 其他硬碼字串
|
||
|
||
| 位置 | 內容 | 問題 | 修補 |
|
||
|------|------|------|------|
|
||
| `apps/api/src/core/config.py:625` | `default="192.168.0.188=ollama"` | Ollama-to-host mapping,ADR-110 後需更新 | 改為 `192.168.0.188=ollama_old`(僅 fallback 相關)|
|
||
| `apps/api/src/core/config.py:828` | `default="192.168.0.188,192.168.0.110,192.168.0.111"` | 主機清單 | 確認用途,若是 monitoring target 需加 GCP IP |
|
||
|
||
---
|
||
|
||
## 5. 改造策略(Phase 2)
|
||
|
||
### K8s Namespace(優先)
|
||
```python
|
||
# 方案:project contract 中定義允許的 K8s namespaces
|
||
# awooop_projects.k8s_namespaces: ["awoooi-prod"](AWOOOI)/ ["ewoooc-prod"](EwoooC)
|
||
# k8s_provider.py 從 project contract 讀,而非硬碼
|
||
|
||
def get_allowed_namespaces(project_id: str) -> set[str]:
|
||
contract = get_active_project_contract(project_id)
|
||
return set(contract.allowed_k8s_namespaces)
|
||
```
|
||
|
||
### Telemetry Endpoint Assert(P0-08,PR-01 優先)
|
||
```python
|
||
# 修改前(telemetry.py:71)
|
||
if "192.168.0.188" not in endpoint:
|
||
raise ValueError(f"Forbidden OTEL endpoint: {endpoint}")
|
||
|
||
# 修改後
|
||
allowed_endpoints = settings.ALLOWED_TELEMETRY_ENDPOINTS.split(",")
|
||
if not any(allowed in endpoint for allowed in allowed_endpoints):
|
||
raise ValueError(f"Forbidden OTEL endpoint: {endpoint}")
|
||
```
|
||
|
||
---
|
||
|
||
## 6. 驗收標準
|
||
|
||
- [ ] `grep -r "192.168.0.188" apps/api/src/` 中 `telemetry.py` 的 assert 行消失
|
||
- [ ] `grep -r '"awoooi-prod"' apps/api/src/` 中的程式碼路徑(非 prompt 文字、非 comment)結果為 0
|
||
- [ ] k8s_provider.py `ALLOWED_NAMESPACES` 改為 config-driven
|
||
- [ ] INV-4 中標記 GCP IP 已確認加入 NetworkPolicy(ADR-110 完成)
|
||
|
||
*最後更新:2026-05-03(台北)*
|