mirror of
https://github.com/Buriburizaem0n/nezha_domains.git
synced 2026-09-21 18:50:13 +00:00
fix(security): restrict service monitors to probe tasks
This commit is contained in:
+6
-1
@@ -43,7 +43,12 @@ type ConfigForGuests struct {
|
||||
|
||||
type ConfigDashboard struct {
|
||||
InstallHost string `koanf:"install_host" json:"install_host,omitempty"`
|
||||
AgentTLS bool `koanf:"tls" json:"tls,omitempty"` // 用于前端判断生成的安装命令是否启用 TLS
|
||||
// AgentTLS controls the transport emitted by Agent installation commands.
|
||||
// false intentionally supports trusted private networks and does not provide
|
||||
// Dashboard peer authentication; Internet-facing control planes must use
|
||||
// verified TLS. Changing this compatibility default belongs in the installer
|
||||
// migration path, not in the gRPC task authorization model.
|
||||
AgentTLS bool `koanf:"tls" json:"tls,omitempty"`
|
||||
|
||||
// DashboardHost 是 dashboard 对外访问的主机名,专用于 OAuth2 回调地址。
|
||||
// 它与 InstallHost(agent 连接用主机名)解耦:两者可以是不同域名。
|
||||
|
||||
+6
-2
@@ -21,8 +21,12 @@ type StreamServerData struct {
|
||||
}
|
||||
|
||||
type ServerForm struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Note string `json:"note,omitempty" validate:"optional"` // 管理员可见备注
|
||||
Name string `json:"name,omitempty"`
|
||||
Note string `json:"note,omitempty" validate:"optional"` // 管理员可见备注
|
||||
// PublicNote is opaque public metadata consumed by independently maintained
|
||||
// user themes. The Dashboard stores/transports it but never renders it as
|
||||
// HTML or navigates URL-like fields. Themes must validate schemes before
|
||||
// using nested values such as customData.orderLink in href/window.open.
|
||||
PublicNote string `json:"public_note,omitempty" validate:"optional"` // 公开备注
|
||||
DisplayIndex int `json:"display_index,omitempty" default:"0"` // 展示排序,越大越靠前
|
||||
HideForGuest bool `json:"hide_for_guest,omitempty" validate:"optional"` // 对游客隐藏
|
||||
|
||||
+34
-10
@@ -39,6 +39,30 @@ const (
|
||||
TaskTypeFsTransfer
|
||||
)
|
||||
|
||||
// IsServiceMonitorType reports whether t is a passive service probe. Service
|
||||
// monitors and privileged Agent-control tasks share the protobuf Task.Type
|
||||
// namespace, so every path that persists, schedules, or dispatches a Service
|
||||
// must use this allowlist instead of accepting an arbitrary task integer.
|
||||
func IsServiceMonitorType(t uint64) bool {
|
||||
switch t {
|
||||
case TaskTypeHTTPGet, TaskTypeICMPPing, TaskTypeTCPPing:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateServiceMonitorType returns an actionable error at API, model, and
|
||||
// scheduler boundaries. Keeping the check in model avoids a future caller
|
||||
// accidentally turning a monitor-only capability into Agent command/config
|
||||
// execution by copying Service.Type into pb.Task.Type.
|
||||
func ValidateServiceMonitorType(t uint64) error {
|
||||
if !IsServiceMonitorType(t) {
|
||||
return fmt.Errorf("invalid service monitor type %d: allowed types are 1 (HTTP GET), 2 (ICMP ping), and 3 (TCP ping)", t)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsMCPRPCResult 判定一个 TaskResult.Type 是否属于 MCP 走 RequestTask 通道的
|
||||
// 一次性 RPC 类型。dashboard 的 RequestTask 接收循环用它把这些回包路由到
|
||||
// Server.inflightRPC 等待方,而不是走 ServiceSentinel。
|
||||
@@ -232,6 +256,9 @@ type Service struct {
|
||||
}
|
||||
|
||||
func (m *Service) PB() *pb.Task {
|
||||
if m == nil || !IsServiceMonitorType(uint64(m.Type)) {
|
||||
return nil
|
||||
}
|
||||
return &pb.Task{
|
||||
Id: m.ID,
|
||||
Type: uint64(m.Type),
|
||||
@@ -300,6 +327,9 @@ func (m *Service) CronSpec() string {
|
||||
}
|
||||
|
||||
func (m *Service) BeforeSave(tx *gorm.DB) error {
|
||||
if err := ValidateServiceMonitorType(uint64(m.Type)); err != nil {
|
||||
return err
|
||||
}
|
||||
if data, err := json.Marshal(m.SkipServers); err != nil {
|
||||
return err
|
||||
} else {
|
||||
@@ -336,15 +366,9 @@ func (m *Service) AfterFind(tx *gorm.DB) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsServiceSentinelNeeded 判断该任务类型是否需要进行服务监控 需要则返回true
|
||||
// IsServiceSentinelNeeded accepts results only for the three probe types. An
|
||||
// unknown or privileged task type must never enter ServiceSentinel merely
|
||||
// because it was not listed in a denylist.
|
||||
func IsServiceSentinelNeeded(t uint64) bool {
|
||||
switch t {
|
||||
case TaskTypeCommand, TaskTypeTerminalGRPC, TaskTypeUpgrade,
|
||||
TaskTypeKeepalive, TaskTypeNAT, TaskTypeFM,
|
||||
TaskTypeReportConfig, TaskTypeApplyConfig,
|
||||
TaskTypeServerTransferApply:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
return IsServiceMonitorType(t)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestServiceMonitorTypeAllowlist(t *testing.T) {
|
||||
for _, taskType := range []uint64{TaskTypeHTTPGet, TaskTypeICMPPing, TaskTypeTCPPing} {
|
||||
require.True(t, IsServiceMonitorType(taskType), "probe type %d must remain allowed", taskType)
|
||||
require.NoError(t, ValidateServiceMonitorType(taskType))
|
||||
require.True(t, IsServiceSentinelNeeded(taskType))
|
||||
}
|
||||
|
||||
for _, taskType := range []uint64{
|
||||
0,
|
||||
TaskTypeCommand,
|
||||
TaskTypeApplyConfig,
|
||||
TaskTypeServerTransferApply,
|
||||
TaskTypeExec,
|
||||
TaskTypeFsTransfer,
|
||||
255,
|
||||
} {
|
||||
require.False(t, IsServiceMonitorType(taskType), "privileged/unknown type %d must be rejected", taskType)
|
||||
require.Error(t, ValidateServiceMonitorType(taskType))
|
||||
require.False(t, IsServiceSentinelNeeded(taskType))
|
||||
}
|
||||
}
|
||||
|
||||
func TestServicePersistenceAndPBRejectPrivilegedTaskTypes(t *testing.T) {
|
||||
for _, taskType := range []uint8{0, TaskTypeCommand, TaskTypeApplyConfig, TaskTypeExec, 255} {
|
||||
service := &Service{Type: taskType}
|
||||
require.Error(t, service.BeforeSave(nil), "type %d must not be persisted", taskType)
|
||||
require.Nil(t, service.PB(), "type %d must not become an Agent task", taskType)
|
||||
}
|
||||
|
||||
service := &Service{Common: Common{ID: 7}, Type: TaskTypeTCPPing, Target: "example.invalid:443"}
|
||||
require.NoError(t, service.BeforeSave(nil))
|
||||
task := service.PB()
|
||||
require.NotNil(t, task)
|
||||
require.Equal(t, uint64(7), task.GetId())
|
||||
require.Equal(t, uint64(TaskTypeTCPPing), task.GetType())
|
||||
require.Equal(t, service.Target, task.GetData())
|
||||
}
|
||||
Reference in New Issue
Block a user