Files
HarborForge.Backend/docs/examples/monitor_heartbeat_secure.py
zhi 929a722c66 docs: add OpenClaw Plugin development plan
- docs/OPENCLAW_PLUGIN_DEV_PLAN.md: Complete development plan
  * Backend capability assessment
  * Security analysis (current HTTP heartbeat lacks validation)
  * Three implementation options (enhanced HTTP / API Key / encrypted payload)
  * Phased development plan (Phase 1-3)
  * API specifications
  * Data models
  * Sequence diagrams

- docs/examples/monitor_heartbeat_secure.py: Reference implementation
  for secure HTTP heartbeat with challenge validation
2026-03-19 14:19:46 +00:00

109 lines
3.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Backend 监控接口需要补充的安全验证代码
添加到 app/api/routers/monitor.py
"""
from fastapi import Header
class ServerHeartbeatSecure(BaseModel):
identifier: str
challenge_uuid: str # 新增:必须提供 challenge
openclaw_version: str | None = None
agents: List[dict] = []
cpu_pct: float | None = None
mem_pct: float | None = None
disk_pct: float | None = None
swap_pct: float | None = None
@router.post('/server/heartbeat')
def server_heartbeat(
payload: ServerHeartbeatSecure,
x_challenge_uuid: str = Header(..., description='Challenge UUID from registration'),
db: Session = Depends(get_db)
):
"""
安全版本的心跳接口,验证 challenge_uuid
"""
# 1. 验证服务器存在且启用
server = db.query(MonitoredServer).filter(
MonitoredServer.identifier == payload.identifier,
MonitoredServer.is_enabled == True
).first()
if not server:
raise HTTPException(status_code=404, detail='unknown server identifier')
# 2. 验证 challenge_uuid 存在且有效
ch = db.query(ServerChallenge).filter(
ServerChallenge.challenge_uuid == x_challenge_uuid,
ServerChallenge.server_id == server.id
).first()
if not ch:
raise HTTPException(status_code=401, detail='invalid challenge')
if ch.expires_at < datetime.now(timezone.utc):
raise HTTPException(status_code=401, detail='challenge expired')
# 3. 可选:检查 challenge 是否已被使用过
# 如果是首次验证,标记为已使用
if ch.used_at is None:
ch.used_at = datetime.now(timezone.utc)
# 4. 存储状态
st = db.query(ServerState).filter(ServerState.server_id == server.id).first()
if not st:
st = ServerState(server_id=server.id)
db.add(st)
st.openclaw_version = payload.openclaw_version
st.agents_json = json.dumps(payload.agents, ensure_ascii=False)
st.cpu_pct = payload.cpu_pct
st.mem_pct = payload.mem_pct
st.disk_pct = payload.disk_pct
st.swap_pct = payload.swap_pct
st.last_seen_at = datetime.now(timezone.utc)
db.commit()
return {
'ok': True,
'server_id': server.id,
'last_seen_at': st.last_seen_at,
'challenge_valid_until': ch.expires_at.isoformat()
}
# 或者,如果需要长期有效的 API Key 方式:
class ServerHeartbeatApiKey(BaseModel):
identifier: str
openclaw_version: str | None = None
agents: List[dict] = []
cpu_pct: float | None = None
mem_pct: float | None = None
disk_pct: float | None = None
swap_pct: float | None = None
@router.post('/server/heartbeat-v2')
def server_heartbeat_v2(
payload: ServerHeartbeatApiKey,
x_api_key: str = Header(..., description='Server API Key'),
db: Session = Depends(get_db)
):
"""
使用 API Key 的心跳接口(长期有效,不需要 challenge
需要在 MonitoredServer 模型中添加 api_key 字段
"""
server = db.query(MonitoredServer).filter(
MonitoredServer.identifier == payload.identifier,
MonitoredServer.is_enabled == True,
MonitoredServer.api_key == x_api_key # 需要添加 api_key 字段
).first()
if not server:
raise HTTPException(status_code=401, detail='invalid identifier or api key')
# ... 存储状态 ...