Files
nezha_domains/model/cron.go
T
naibaandcloudcode ab25662ddd 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

90 lines
3.1 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 model
import (
"time"
"github.com/gin-gonic/gin"
"github.com/goccy/go-json"
"github.com/robfig/cron/v3"
"gorm.io/gorm"
)
const (
CronCoverIgnoreAll = iota
CronCoverAll
CronCoverAlertTrigger
CronTypeCronTask = 0
CronTypeTriggerTask = 1
)
type Cron struct {
Common
Name string `json:"name"`
TaskType uint8 `gorm:"default:0" json:"task_type"` // 0:计划任务 1:触发任务
Scheduler string `json:"scheduler"` // 分钟 小时 天 月 星期
Command string `json:"command,omitempty"`
Servers []uint64 `gorm:"-" json:"servers"`
PushSuccessful bool `json:"push_successful,omitempty"` // 推送成功的通知
NotificationGroupID uint64 `json:"notification_group_id"` // 指定通知方式的分组
LastExecutedAt time.Time `json:"last_executed_at,omitempty"` // 最后一次执行时间
LastResult bool `json:"last_result,omitempty"` // 最后一次执行结果
Cover uint8 `json:"cover"` // 计划任务覆盖范围 (0:仅覆盖特定服务器 1:仅忽略特定服务器 2:由触发该计划任务的服务器执行)
CronJobID cron.EntryID `gorm:"-" json:"cron_job_id,omitempty"`
ServersRaw string `json:"-"`
}
func (c *Cron) BeforeSave(tx *gorm.DB) error {
if data, err := json.Marshal(c.Servers); err != nil {
return err
} else {
c.ServersRaw = string(data)
}
return nil
}
func (c *Cron) AfterFind(tx *gorm.DB) error {
return json.Unmarshal([]byte(c.ServersRaw), &c.Servers)
}
// HasPermission 扩展默认的 owner/admin 检查,使得 PAT 的 server_ids 白名单
// 同样能收窄 cron 的列出、触发、删除路径。
//
// 语义按 Cover 字段分流,与 dispatch 入口(CronTrigger)的 fan-out 规则严格
// 对齐——Servers 字段在不同 Cover 下含义完全相反:
//
// - CronCoverIgnoreAllServers 是 allow-list;必须每个 server 都落在 PAT
// 白名单内。空 allow-list 是「matches nothing」的退化形态,安全。
// - CronCoverAlertTriggerServers 是触发服务器 allow-list;与上同。
// - CronCoverAllServers 是 deny-list。dispatch 时 fan out 到 owner 的
// 全部 server 再减去这个 deny-list。受限 PAT 必须保证 deny-list 已经覆
// 盖 owner 在白名单外的所有 servers——否则 CronTrigger 会把任务发到
// PAT 没权限的 server 上。本方法和 controller 写侧 guard
// rejectImplicitCoverForLimitedPAT* / 运行时 guard
// enforcePATCronDispatchScope 共用 DenyListSafeForLimitedPAT,避免列表
// 视图把越界历史/旁路写入行漏给受限 PAT。
func (c *Cron) HasPermission(ctx *gin.Context) bool {
if !c.Common.HasPermission(ctx) {
return false
}
v, ok := ctx.Get(CtxKeyAPIToken)
if !ok {
return true
}
tok, _ := v.(APITokenAccessor)
if tok == nil {
return true
}
switch c.Cover {
case CronCoverAll:
return DenyListSafeForLimitedPAT(tok, c.GetUserID(), c.Servers)
default:
for _, id := range c.Servers {
if !tok.CanAccessServer(id) {
return false
}
}
return true
}
}