Bind protected Sub2API manual accounts to proxy

This commit is contained in:
Codex
2026-06-14 12:41:10 +00:00
parent 2e2536576f
commit c00f22eab5
2 changed files with 239 additions and 8 deletions
+235 -8
View File
@@ -81,6 +81,8 @@ interface CodexPoolRuntimeTarget {
egressProxy: {
enabled: boolean;
applyToSentinel: boolean;
serviceName: string;
listenPort: number;
httpProxy: string;
noProxy: string;
} | null;
@@ -156,9 +158,16 @@ interface CodexPoolManualAccountsConfig {
protected: CodexPoolManualAccountProtection[];
}
interface CodexPoolManualAccountProxyBinding {
enabled: boolean;
source: "target-egress-proxy";
proxyName: string;
}
interface CodexPoolManualAccountProtection {
accountName: string;
reason: string | null;
proxyBinding: CodexPoolManualAccountProxyBinding | null;
}
interface CodexPoolProfileConfig {
@@ -631,6 +640,8 @@ function codexPoolRuntimeTarget(targetId?: string): CodexPoolRuntimeTarget {
egressProxy = {
enabled: true,
applyToSentinel: raw.egressProxy.applyToSentinel === undefined ? true : raw.egressProxy.applyToSentinel === true,
serviceName: proxyServiceName,
listenPort,
httpProxy: `http://${proxyServiceName}.${targetNamespace}.svc.cluster.local:${listenPort}`,
noProxy,
};
@@ -1508,11 +1519,28 @@ function readManualAccountsConfig(value: unknown, defaults: CodexPoolManualAccou
if (seen.has(normalized)) throw new Error(`${codexPoolConfigPath}.${key}.accountName is duplicated in manualAccounts.protected`);
seen.add(normalized);
const reason = isRecord(entry) ? readManualAccountReason(entry.reason, `${key}.reason`) : null;
return { accountName, reason };
const proxyBinding = isRecord(entry) ? readManualAccountProxyBinding(entry.proxyBinding, `${key}.proxyBinding`) : null;
return { accountName, reason, proxyBinding };
});
return { protected: protectedAccounts };
}
function readManualAccountProxyBinding(value: unknown, key: string): CodexPoolManualAccountProxyBinding | null {
if (value === undefined || value === null) return null;
if (!isRecord(value)) throw new Error(`${codexPoolConfigPath}.${key} must be a YAML object`);
const enabled = value.enabled === undefined ? true : value.enabled === true;
const source = stringValue(value.source) ?? "target-egress-proxy";
if (source !== "target-egress-proxy") throw new Error(`${codexPoolConfigPath}.${key}.source must be target-egress-proxy`);
const proxyName = stringValue(value.proxyName);
if (proxyName === null || proxyName.trim().length === 0) throw new Error(`${codexPoolConfigPath}.${key}.proxyName is required`);
validateProxyName(proxyName, `${key}.proxyName`);
return {
enabled,
source,
proxyName,
};
}
function readManualAccountName(value: unknown, key: string): string | null {
const text = stringValue(value)?.trim() ?? null;
if (text === null || text.length === 0) return null;
@@ -2122,18 +2150,42 @@ function compactManualAccounts(block: unknown): Record<string, unknown> | null {
const items = recordArray(block.items).map((item) => pickSummaryFields(item, [
"accountName",
"reason",
"ok",
"exists",
"accountId",
"status",
"schedulable",
"inYamlProfiles",
"runtimeMarkedUnideskManaged",
"proxyBinding",
"controlPolicy",
]));
const proxySync = isRecord(block.proxySync)
? {
ok: block.proxySync.ok,
itemCount: block.proxySync.itemCount,
items: recordArray(block.proxySync.items).map((item) => pickSummaryFields(item, [
"accountName",
"accountId",
"enabled",
"ok",
"action",
"proxyAction",
"expectedProxyName",
"proxyId",
"runtimeProxyId",
"runtimeProxyName",
"bindingAligned",
"controlPolicy",
])),
valuesPrinted: false,
}
: undefined;
return {
ok: block.ok,
protectedCount: block.protectedCount,
items,
proxySync,
valuesPrinted: false,
};
}
@@ -2946,6 +2998,8 @@ function poolTarget(pool = readCodexPoolConfig(), target = codexPoolRuntimeTarge
egressProxy: target.egressProxy === null ? null : {
enabled: target.egressProxy.enabled,
applyToSentinel: target.egressProxy.applyToSentinel,
serviceName: target.egressProxy.serviceName,
listenPort: target.egressProxy.listenPort,
httpProxy: target.egressProxy.httpProxy,
noProxy: target.egressProxy.noProxy,
},
@@ -4152,6 +4206,7 @@ EXPECTED_ACCOUNT_WS_MODES = json.loads(${JSON.stringify(JSON.stringify(desiredAc
EXPECTED_ACCOUNT_TEMP_UNSCHEDULABLE = json.loads(${JSON.stringify(JSON.stringify(desiredAccountTempUnschedulableMap(pool)))})
MANUAL_ACCOUNT_PROTECTIONS = json.loads(${JSON.stringify(JSON.stringify(pool.manualAccounts.protected))})
SENTINEL_CONFIG = json.loads(${JSON.stringify(JSON.stringify(pool.sentinel))})
TARGET_EGRESS_PROXY = json.loads(${JSON.stringify(JSON.stringify(target.egressProxy))})
MODE = "${mode}"
PAYLOAD_B64 = "${encodedPayload}"
@@ -4459,13 +4514,181 @@ def list_accounts(token):
return extract_items(data)
def find_account_by_name(token, name):
path = "/api/v1/admin/accounts?page=1&page_size=20&platform=openai&type=apikey&search=" + quote(str(name))
path = "/api/v1/admin/accounts?page=1&page_size=50&platform=openai&search=" + quote(str(name))
data = ensure_success(curl_api("GET", path, bearer=token), "find account " + str(name))
for item in extract_items(data):
if isinstance(item, dict) and item.get("name") == name:
return item
return None
def list_proxy_candidates(token, search=""):
path = "/api/v1/admin/proxies?page=1&page_size=200"
if search:
path += "&search=" + quote(str(search))
data = ensure_success(curl_api("GET", path, bearer=token), "list proxies")
return extract_items(data)
def find_proxy_by_name(token, name):
for item in list_proxy_candidates(token, name):
if isinstance(item, dict) and item.get("name") == name:
return item
return None
def desired_manual_proxy_payload(protection):
binding = protection.get("proxyBinding") if isinstance(protection, dict) else None
if not isinstance(binding, dict) or binding.get("enabled") is not True:
return None
if binding.get("source") != "target-egress-proxy":
raise RuntimeError("manual account proxyBinding source must be target-egress-proxy")
if not isinstance(TARGET_EGRESS_PROXY, dict) or TARGET_EGRESS_PROXY.get("enabled") is not True:
raise RuntimeError("manual account proxyBinding requires an enabled target egressProxy")
service_name = TARGET_EGRESS_PROXY.get("serviceName")
listen_port = TARGET_EGRESS_PROXY.get("listenPort")
proxy_name = binding.get("proxyName")
if not isinstance(service_name, str) or not service_name:
raise RuntimeError("target egressProxy serviceName is missing")
if not isinstance(listen_port, int) or listen_port <= 0:
raise RuntimeError("target egressProxy listenPort is missing")
if not isinstance(proxy_name, str) or not proxy_name:
raise RuntimeError("manual account proxyBinding proxyName is missing")
return {
"name": proxy_name,
"protocol": "http",
"host": f"{service_name}.{NAMESPACE}.svc.cluster.local",
"port": listen_port,
"fallback_mode": "none",
"expiry_warn_days": 0,
}
def proxy_needs_update(proxy, payload):
if not isinstance(proxy, dict):
return True
for key in ("name", "protocol", "host", "port", "fallback_mode", "expiry_warn_days"):
if proxy.get(key) != payload.get(key):
return True
return proxy.get("status") != "active"
def ensure_manual_proxy(token, payload):
current = find_proxy_by_name(token, payload["name"])
if current is None:
created = ensure_success(curl_api("POST", "/api/v1/admin/proxies", bearer=token, payload=payload), f"create proxy {payload['name']}")
return created, "created"
if proxy_needs_update(current, payload):
update_payload = dict(payload)
update_payload["status"] = "active"
updated = ensure_success(curl_api("PUT", f"/api/v1/admin/proxies/{current['id']}", bearer=token, payload=update_payload), f"update proxy {payload['name']}")
return updated if isinstance(updated, dict) else current, "updated"
return current, "unchanged"
def manual_proxy_status(token, account, protection):
payload = desired_manual_proxy_payload(protection)
if payload is None:
return {
"enabled": False,
"ok": True,
"action": "not-configured",
"valuesPrinted": False,
}
proxy = find_proxy_by_name(token, payload["name"])
runtime_proxy = account.get("proxy") if isinstance(account, dict) and isinstance(account.get("proxy"), dict) else None
binding_aligned = (
isinstance(account, dict)
and isinstance(proxy, dict)
and account.get("proxy_id") == proxy.get("id")
)
return {
"enabled": True,
"ok": binding_aligned,
"action": "validate",
"source": "target-egress-proxy",
"expectedProxyName": payload["name"],
"expectedProtocol": payload["protocol"],
"expectedHost": payload["host"],
"expectedPort": payload["port"],
"proxyRecordExists": isinstance(proxy, dict),
"proxyId": proxy.get("id") if isinstance(proxy, dict) else None,
"runtimeProxyId": account.get("proxy_id") if isinstance(account, dict) else None,
"runtimeProxyName": runtime_proxy.get("name") if isinstance(runtime_proxy, dict) else None,
"bindingAligned": binding_aligned,
"valuesPrinted": False,
}
def ensure_manual_account_proxy_bindings(token):
items = []
for protection in MANUAL_ACCOUNT_PROTECTIONS:
if not isinstance(protection, dict):
continue
name = protection.get("accountName")
if not isinstance(name, str) or not name:
continue
account = find_account_by_name(token, name)
payload = desired_manual_proxy_payload(protection)
if payload is None:
items.append({
"accountName": name,
"enabled": False,
"action": "not-configured",
"ok": True,
"valuesPrinted": False,
})
continue
if not isinstance(account, dict):
items.append({
"accountName": name,
"enabled": True,
"action": "account-missing",
"ok": False,
"expectedProxyName": payload["name"],
"valuesPrinted": False,
})
continue
extra = account.get("extra") if isinstance(account.get("extra"), dict) else {}
if extra.get("unidesk_managed") is True:
items.append({
"accountName": name,
"accountId": account.get("id"),
"enabled": True,
"action": "refused-managed-runtime-account",
"ok": False,
"expectedProxyName": payload["name"],
"valuesPrinted": False,
})
continue
proxy, proxy_action = ensure_manual_proxy(token, payload)
proxy_id = proxy.get("id") if isinstance(proxy, dict) else None
if proxy_id is None:
raise RuntimeError(f"proxy {payload['name']} has no id")
action = "unchanged"
if account.get("proxy_id") != proxy_id:
updated = ensure_success(curl_api("PUT", f"/api/v1/admin/accounts/{account['id']}", bearer=token, payload={"proxy_id": proxy_id}), f"bind manual account proxy {name}")
account = updated if isinstance(updated, dict) else account
action = "bound"
runtime_proxy = account.get("proxy") if isinstance(account.get("proxy"), dict) else None
items.append({
"accountName": name,
"accountId": account.get("id"),
"enabled": True,
"action": action,
"proxyAction": proxy_action,
"ok": account.get("proxy_id") == proxy_id,
"expectedProxyName": payload["name"],
"expectedProtocol": payload["protocol"],
"expectedHost": payload["host"],
"expectedPort": payload["port"],
"proxyId": proxy_id,
"runtimeProxyId": account.get("proxy_id"),
"runtimeProxyName": runtime_proxy.get("name") if isinstance(runtime_proxy, dict) else None,
"bindingAligned": account.get("proxy_id") == proxy_id,
"controlPolicy": "manual-protected: only proxy_id binding is YAML-controlled; credentials/status/schedulable are untouched",
"valuesPrinted": False,
})
return {
"ok": all(item.get("ok") is True for item in items),
"itemCount": len(items),
"items": items,
"valuesPrinted": False,
}
def manual_account_protection_status(token):
items = []
desired_names = set(EXPECTED_ACCOUNT_CAPACITIES.keys())
@@ -4477,6 +4700,7 @@ def manual_account_protection_status(token):
continue
account = find_account_by_name(token, name)
extra = account.get("extra") if isinstance(account, dict) and isinstance(account.get("extra"), dict) else {}
proxy_status = manual_proxy_status(token, account, protection)
items.append({
"accountName": name,
"reason": protection.get("reason") if isinstance(protection.get("reason"), str) else None,
@@ -4486,11 +4710,13 @@ def manual_account_protection_status(token):
"schedulable": account.get("schedulable") if isinstance(account, dict) else None,
"inYamlProfiles": name in desired_names,
"runtimeMarkedUnideskManaged": extra.get("unidesk_managed") is True,
"controlPolicy": "manual-protected: no create/update/prune/probe/freeze",
"proxyBinding": proxy_status,
"ok": proxy_status.get("ok") is True,
"controlPolicy": "manual-protected: no create/update/prune/probe/freeze; optional proxy_id binding only when proxyBinding is configured",
"valuesPrinted": False,
})
return {
"ok": True,
"ok": all(item.get("ok") is True for item in items),
"protectedCount": len(items),
"items": items,
"valuesPrinted": False,
@@ -6101,13 +6327,14 @@ def run_sync():
if group_id is None:
raise RuntimeError("pool group id missing after ensure")
existing_accounts = list_accounts(token)
manual_account_protections = manual_account_protection_status(token)
planned_account_results = planned_sentinel_account_results(profiles, existing_accounts)
sentinel_quality_prepare = ensure_sentinel_state_for_sync(planned_account_results, True)
if sentinel_quality_prepare.get("ok") is not True:
raise RuntimeError("prepare sentinel pending probe failed: " + json.dumps(sentinel_quality_prepare, ensure_ascii=False))
protected_frozen_names = active_sentinel_quarantine_names()
account_results, pruned_account_results = ensure_accounts(token, profiles, group_id, prune_removed, protected_frozen_names, existing_accounts)
manual_account_proxy_bindings = ensure_manual_account_proxy_bindings(token)
manual_account_protections = manual_account_protection_status(token)
capacity_status = account_capacity_status(token)
load_factor_status = account_load_factor_status(token)
ws_v2_status = account_ws_v2_status(token)
@@ -6125,7 +6352,7 @@ def run_sync():
sentinel_quality = ensure_sentinel_state_for_sync(account_results)
sentinel_reassert = reassert_sentinel_freezes_after_sync(token)
return {
"ok": gateway["ok"] is True and responses_smoke["ok"] is True and owner_concurrency["ok"] is True and capacity_status["ok"] is True and load_factor_status["ok"] is True and ws_v2_status["ok"] is True and temp_unschedulable_status["ok"] is True and sentinel.get("ok") is True and sentinel_quality_prepare.get("ok") is True and sentinel_quality.get("ok") is True and sentinel_reassert.get("ok") is True and runtime_capabilities.get("ok") is True,
"ok": gateway["ok"] is True and responses_smoke["ok"] is True and owner_concurrency["ok"] is True and capacity_status["ok"] is True and load_factor_status["ok"] is True and ws_v2_status["ok"] is True and temp_unschedulable_status["ok"] is True and manual_account_proxy_bindings.get("ok") is True and manual_account_protections.get("ok") is True and sentinel.get("ok") is True and sentinel_quality_prepare.get("ok") is True and sentinel_quality.get("ok") is True and sentinel_reassert.get("ok") is True and runtime_capabilities.get("ok") is True,
"degraded": bool(responses_smoke.get("degraded")) or bool(compact_evidence.get("degraded")) or bool(responses_evidence.get("degraded")) or runtime_capabilities.get("ok") is not True,
"mode": "sync",
"namespace": NAMESPACE,
@@ -6144,7 +6371,7 @@ def run_sync():
"processControl": {"schedulableRestore": "sentinel marker probe only; sync does not restore schedulable for existing accounts", "durableConfig": False},
"valuesPrinted": False,
},
"manualAccounts": manual_account_protections,
"manualAccounts": {**manual_account_protections, "proxySync": manual_account_proxy_bindings},
"capacity": capacity_status,
"loadFactor": load_factor_status,
"webSocketsV2": ws_v2_status,
@@ -6191,7 +6418,7 @@ def run_validate():
runtime_capabilities = validate_runtime_capabilities(token)
sentinel = sentinel_runtime_status()
return {
"ok": gateway["ok"] is True and responses_smoke["ok"] is True and (owner_concurrency is None or owner_concurrency["ok"] is True) and capacity_status["ok"] is True and load_factor_status["ok"] is True and ws_v2_status["ok"] is True and temp_unschedulable_status["ok"] is True and sentinel.get("ok") is True and runtime_capabilities.get("ok") is True,
"ok": gateway["ok"] is True and responses_smoke["ok"] is True and (owner_concurrency is None or owner_concurrency["ok"] is True) and capacity_status["ok"] is True and load_factor_status["ok"] is True and ws_v2_status["ok"] is True and temp_unschedulable_status["ok"] is True and manual_account_protections.get("ok") is True and sentinel.get("ok") is True and runtime_capabilities.get("ok") is True,
"degraded": bool(responses_smoke.get("degraded")) or bool(compact_evidence.get("degraded")) or bool(responses_evidence.get("degraded")) or runtime_capabilities.get("ok") is not True,
"mode": "validate",
"namespace": NAMESPACE,