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
This commit is contained in:
108
docs/examples/monitor_heartbeat_secure.py
Normal file
108
docs/examples/monitor_heartbeat_secure.py
Normal file
@@ -0,0 +1,108 @@
|
||||
"""
|
||||
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')
|
||||
|
||||
# ... 存储状态 ...
|
||||
Reference in New Issue
Block a user