Files
nezha_domains/cmd/dashboard/controller/mcp_capability_test.go
T
naibaandcloudcode e8dabf5bc6 feat(auth): add PAT auth, scoped REST/MCP access, CSRF, and tenant isolation
Introduce Personal Access Tokens (nzp_*) as a stateless auth path alongside
JWT, gated per-endpoint by a scope middleware (nezha:{resource}:{verb}) with
fail-closed empty-scope defaults and a server-id whitelist. Self-management
endpoints (profile, api-tokens, oauth2 bind, refresh-token) explicitly reject
PATs to block privilege-escalation chains. A revoke registry tears down active
long-lived connections (terminal, fm, ws, transfer, mcp) the moment a PAT is
deleted, with a tombstone closing the revoke->register race.

Add an MCP endpoint that proxies tool calls (exec, fs read/write/delete,
transfer) to agents over gRPC, guarded by origin/DNS-rebinding checks, a
per-token rate limiter, audit logging, and a kill switch. Serialize all
sends through the IOStream wrapper to honour grpc-go's concurrency contract.

Add CSRF double-submit protection on unsafe cookie-authenticated methods,
exempting authenticated PAT requests by context identity (not a forgeable
Authorization header). Apply visibility/whitelist filtering consistently
across list, get-by-id, and mutate paths to enforce tenant isolation.

Migrate legacy mcp:* scopes: rewrite read/exec to nezha:* equivalents and
drop dangerous write/delete/wildcard grants.

Co-authored-by: cloudcode <cloudcode@users.noreply.github.com>
2026-05-30 15:56:44 +00:00

53 lines
2.6 KiB
Go
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.
package controller
import (
"errors"
"testing"
"github.com/stretchr/testify/require"
"github.com/nezhahq/nezha/model"
)
// 旧 agent 不识别 TaskTypeExec/TaskTypeFs* 等新 task type,会走 default
// 分支不回 TaskResultdashboard 必须在调 CallAgent 之前依据 Host.Version
// 快速失败,否则用户要等到 30s/24h timeout 才知道 agent 不支持。
// MinServerTransferAgentVersion 已为同类问题在 transfer 路径上确立了
// release-time 必填的版本下限——这里把 MCP 也纳入同一不变量。
func TestMCPMinAgentVersionIsPinnedToRelease(t *testing.T) {
require.NotEmpty(t, MCPMinAgentVersion,
"MCPMinAgentVersion must be set to the lowest agent build that ships MCP handlers; an empty string disables the gate and lets old agents hang dashboard requests until timeout")
}
func TestRequireAgentSupportsMCPRejectsBelowMinVersion(t *testing.T) {
old := &model.Server{Host: &model.Host{Version: "v0.0.1"}}
err := requireAgentSupportsMCP(old)
require.Error(t, err, "agents older than MCPMinAgentVersion must be rejected before CallAgent")
require.True(t, errors.Is(err, errMCPUnsupported) || err.Error() == errMCPUnsupported.Error(),
"expected the errMCPUnsupported sentinel, got %v", err)
}
func TestRequireAgentSupportsMCPAcceptsCurrentVersion(t *testing.T) {
current := &model.Server{Host: &model.Host{Version: MCPMinAgentVersion}}
require.NoError(t, requireAgentSupportsMCP(current),
"server reporting exactly MCPMinAgentVersion must be accepted")
}
// 钉住「最近一个不带 MCP handler 的已发布 agent tag (v2.0.4) 必须被拒绝」。
// v2.0.4 的 model/task.go 还没有 TaskTypeExec/TaskTypeFs* 常量,cmd/agent/
// mcp_handlers.go 也不存在;如果版本门槛把它放行,dashboard 调 MCP tool
// 后 agent 会走 default 分支不回 TaskResultCallAgent 必须等 30s 超时。
func TestRequireAgentSupportsMCPRejectsLastReleaseWithoutMCP(t *testing.T) {
noMCP := &model.Server{Host: &model.Host{Version: "v2.0.4"}}
err := requireAgentSupportsMCP(noMCP)
require.Error(t, err,
"v2.0.4 is the latest released agent tag that ships *without* MCP handlers; bumping MCPMinAgentVersion below the first MCP release re-introduces the silent-timeout bug")
require.True(t, errors.Is(err, errMCPUnsupported) || err.Error() == errMCPUnsupported.Error(),
"expected the errMCPUnsupported sentinel, got %v", err)
}
func TestRequireAgentSupportsMCPDefersWhenAgentNeverReported(t *testing.T) {
require.NoError(t, requireAgentSupportsMCP(&model.Server{Host: nil}),
"Host==nil means agent never reported its build; defer the version decision to the CallAgent timeout layer")
}