feat(agent): return A2A endpoint security scheme/profile (#55)
客户端 #25/#55:A2A 直连方需 Manager 在 agent endpoint 元数据给出安全通道信息,才能在 生产强制 HTTPS。agent 对象(列表/详情/部署/状态返回)新增 security: - scheme: http | https(从 subdomain 推断) - security_profile: none | tls(显式 https→tls;http 或裸主机→none) - secure: profile != none 保守口径:无法确证 TLS 即 none,宁可客户端在生产拒绝也不回传"看似安全"的明文端点。 客户端置 HEICODE_AGENT_REQUIRE_SECURE=1 即据此强制(secure=false→拒绝)。AM 上 HTTPS/mTLS listener(azgy)后 profile 自动变 tls,客户端无需改包。元数据不含任何 secret_ref。 纯函数 agentEndpointSecurity + 单测;文档 §5/§6 更新。Manager only,无计费/审计改动。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -241,7 +241,12 @@ signature = base64( ed25519_sign( device_priv, sha256(canonical) ) )
|
||||
"status": "Pending", // Pending | running | failed | stopped …
|
||||
"runtime_id": "dep-4bb07dc1e376", // AM 侧运行时 id
|
||||
"created_at": "2026-06-04T09:04:15Z",
|
||||
"updated_at": "2026-06-04T09:04:15Z"
|
||||
"updated_at": "2026-06-04T09:04:15Z",
|
||||
"security": { // ★ #55 A2A 直连安全元数据
|
||||
"scheme": "http", // http | https
|
||||
"security_profile": "none", // none | tls | mtls
|
||||
"secure": false // = security_profile != none
|
||||
}
|
||||
}
|
||||
],
|
||||
"total": 1
|
||||
@@ -327,6 +332,8 @@ signature = base64( ed25519_sign( device_priv, sha256(canonical) ) )
|
||||
> ⚠️ **连之前先确认 agent 就绪**:新建后 `status=Pending`(还在拉起)。等 `GET /api/heicode/agents/{id}/status` 变 `running`、或 `GET {subdomain}/health` 返 200 再连。**2026-06-04 复测:数秒即 `running`,`/health` 200、`/message/send` 带令牌任务 `completed`,直连已通。**
|
||||
>
|
||||
> ⚠️ 当前 AM 侧两点(待加固,不影响调通):① **令牌校验尚未真正生效**——无 `X-Agent-Access-Token` 也被放行;客户端仍应规范地每请求都带,等 AM 开启校验即自动生效。② 子域名目前 `http://` 明文,令牌/`api_key` 会明文传输,等 AM 上 HTTPS。
|
||||
>
|
||||
> 🔒 **传输安全门(#55)**:agent 对象回传 `security`(`scheme` http/https、`security_profile` none/tls/mtls、`secure` 布尔)。客户端据此在生产强制 HTTPS(`HEICODE_AGENT_REQUIRE_SECURE=1`):`secure=false`(当前明文)→ 拒绝直连并提示。AM 上线 HTTPS/mTLS listener 后 `security_profile` 自动变 tls,客户端无需改包。HM 保守口径:无法确证 TLS 即标 `none`。
|
||||
|
||||
- **同步**:`POST {subdomain}/message/send`
|
||||
- **流式**:`POST {subdomain}/message/stream`(返回 `text/event-stream`)
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package controller
|
||||
|
||||
import "testing"
|
||||
|
||||
// #55: agent endpoint 安全级别推断 —— 明文/裸主机 = none(客户端生产可拒),显式 https = tls。
|
||||
func TestAgentEndpointSecurity(t *testing.T) {
|
||||
cases := []struct {
|
||||
in string
|
||||
scheme, prof string
|
||||
secure bool
|
||||
}{
|
||||
{"https://dep-x.agents.example", "https", "tls", true},
|
||||
{"http://dep-x.agents.example", "http", "none", false},
|
||||
{"dep-x.taijiagnet.com", "http", "none", false}, // 裸主机:AM 当前明文
|
||||
{" HTTPS://Dep.Example ", "https", "tls", true},
|
||||
{"", "http", "none", false},
|
||||
}
|
||||
for _, c := range cases {
|
||||
sc, pr, se := agentEndpointSecurity(c.in)
|
||||
if sc != c.scheme || pr != c.prof || se != c.secure {
|
||||
t.Errorf("agentEndpointSecurity(%q) = (%s,%s,%v), want (%s,%s,%v)", c.in, sc, pr, se, c.scheme, c.prof, c.secure)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -111,6 +111,7 @@ func templateAgentResponse(row model.AgentDeployment) gin.H {
|
||||
if strings.TrimSpace(row.BindingIDsJSON) != "" {
|
||||
_ = common.UnmarshalJsonStr(row.BindingIDsJSON, &bindingIDs)
|
||||
}
|
||||
scheme, profile, secure := agentEndpointSecurity(row.Subdomain)
|
||||
return gin.H{
|
||||
"agent_id": row.DeploymentID,
|
||||
"template_id": row.TemplateID,
|
||||
@@ -121,6 +122,30 @@ func templateAgentResponse(row model.AgentDeployment) gin.H {
|
||||
"runtime_id": row.RuntimeDeploymentID,
|
||||
"created_at": row.CreatedAtText,
|
||||
"updated_at": row.UpdatedAtText,
|
||||
// #55: A2A 直连安全元数据。客户端据此在生产强制 HTTPS(HEICODE_AGENT_REQUIRE_SECURE):
|
||||
// scheme=http/https,security_profile=none/tls,secure=profile!=none。AM 启用 HTTPS/
|
||||
// mTLS listener 是 AM(azgy)的事;HM 只如实回传当前子域安全级别,明文 http →
|
||||
// security_profile=none,客户端可拒绝并提示(元数据不含任何 secret_ref)。
|
||||
"security": gin.H{
|
||||
"scheme": scheme,
|
||||
"security_profile": profile,
|
||||
"secure": secure,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// agentEndpointSecurity 从子域字符串推断 A2A 直连的安全级别(#55)。纯函数,可单测。
|
||||
// 显式 https:// → (https, tls, true);显式 http:// 或裸主机(AM 当前默认明文) → (http, none, false)。
|
||||
// 保守口径:无法确证 TLS 即视为 none,宁可让客户端在生产拒绝,也不回传"看似安全"的明文端点。
|
||||
func agentEndpointSecurity(subdomain string) (scheme, profile string, secure bool) {
|
||||
s := strings.TrimSpace(strings.ToLower(subdomain))
|
||||
switch {
|
||||
case strings.HasPrefix(s, "https://"):
|
||||
return "https", "tls", true
|
||||
case strings.HasPrefix(s, "http://"):
|
||||
return "http", "none", false
|
||||
default:
|
||||
return "http", "none", false // 裸主机:AM 当前明文 HTTP,保守标 none
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user