mirror of
https://github.com/UNITRONIX/BetterDesk.git
synced 2026-09-10 17:45:42 +00:00
43e0b6f40a
Expose global connection strategy in the web panel with systemd/Docker persistence, extend server health diagnostics, enforce org network policy in the signal handler, and document when relay fallback is expected vs misconfiguration.
145 lines
3.4 KiB
Go
145 lines
3.4 KiB
Go
// Package policy resolves organization network policies for signal routing.
|
|
package policy
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/unitronix/betterdesk-server/db"
|
|
)
|
|
|
|
const networkPolicyKey = "policy_network"
|
|
|
|
// NetworkSettings is stored JSON under org_settings policy_network.
|
|
type NetworkSettings struct {
|
|
BlockDirectP2P bool `json:"block_direct_p2p"`
|
|
AllowedRelayServers []string `json:"allowed_relay_servers"`
|
|
IPAllowlist []string `json:"ip_allowlist"`
|
|
}
|
|
|
|
type cachedEntry struct {
|
|
settings NetworkSettings
|
|
expires time.Time
|
|
}
|
|
|
|
// NetworkResolver loads and caches org network policy for signal decisions.
|
|
type NetworkResolver struct {
|
|
db db.Database
|
|
mu sync.Mutex
|
|
org map[string]cachedEntry
|
|
dev map[string]cachedEntry
|
|
ttl time.Duration
|
|
}
|
|
|
|
// NewNetworkResolver creates a resolver with a 60s TTL cache.
|
|
func NewNetworkResolver(database db.Database) *NetworkResolver {
|
|
return &NetworkResolver{
|
|
db: database,
|
|
org: make(map[string]cachedEntry),
|
|
dev: make(map[string]cachedEntry),
|
|
ttl: 60 * time.Second,
|
|
}
|
|
}
|
|
|
|
func (r *NetworkResolver) settingsForOrg(orgID string) NetworkSettings {
|
|
if orgID == "" {
|
|
return NetworkSettings{}
|
|
}
|
|
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
|
|
if ce, ok := r.org[orgID]; ok && time.Now().Before(ce.expires) {
|
|
return ce.settings
|
|
}
|
|
|
|
settings := NetworkSettings{}
|
|
raw, err := r.db.GetOrgSetting(orgID, networkPolicyKey)
|
|
if err == nil && raw != "" {
|
|
_ = json.Unmarshal([]byte(raw), &settings)
|
|
}
|
|
r.org[orgID] = cachedEntry{settings: settings, expires: time.Now().Add(r.ttl)}
|
|
return settings
|
|
}
|
|
|
|
// SettingsForDevice returns effective network policy for a registered device.
|
|
func (r *NetworkResolver) SettingsForDevice(deviceID string) NetworkSettings {
|
|
if deviceID == "" {
|
|
return NetworkSettings{}
|
|
}
|
|
|
|
r.mu.Lock()
|
|
if ce, ok := r.dev[deviceID]; ok && time.Now().Before(ce.expires) {
|
|
s := ce.settings
|
|
r.mu.Unlock()
|
|
return s
|
|
}
|
|
r.mu.Unlock()
|
|
|
|
orgID, _ := r.db.GetDeviceOrgID(deviceID)
|
|
settings := r.settingsForOrg(orgID)
|
|
|
|
r.mu.Lock()
|
|
r.dev[deviceID] = cachedEntry{settings: settings, expires: time.Now().Add(r.ttl)}
|
|
r.mu.Unlock()
|
|
return settings
|
|
}
|
|
|
|
// ShouldForceRelay returns true when any listed device belongs to an org with block_direct_p2p.
|
|
func (r *NetworkResolver) ShouldForceRelay(deviceIDs ...string) bool {
|
|
for _, id := range deviceIDs {
|
|
if id == "" {
|
|
continue
|
|
}
|
|
if r.SettingsForDevice(id).BlockDirectP2P {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// ResolveRelay picks a relay address allowed by org policy when an allowlist is configured.
|
|
func (r *NetworkResolver) ResolveRelay(defaultRelay string, deviceIDs ...string) string {
|
|
var allowed []string
|
|
for _, id := range deviceIDs {
|
|
if id == "" {
|
|
continue
|
|
}
|
|
s := r.SettingsForDevice(id)
|
|
if len(s.AllowedRelayServers) > 0 {
|
|
allowed = s.AllowedRelayServers
|
|
break
|
|
}
|
|
}
|
|
if len(allowed) == 0 {
|
|
return defaultRelay
|
|
}
|
|
|
|
normalizedDefault := strings.TrimSpace(defaultRelay)
|
|
for _, entry := range allowed {
|
|
entry = strings.TrimSpace(entry)
|
|
if entry == "" {
|
|
continue
|
|
}
|
|
if entry == normalizedDefault {
|
|
return defaultRelay
|
|
}
|
|
if normalizedDefault != "" && strings.HasPrefix(normalizedDefault, entry) {
|
|
return defaultRelay
|
|
}
|
|
}
|
|
return strings.TrimSpace(allowed[0])
|
|
}
|
|
|
|
// InvalidateOrg drops cached org/device entries after policy updates.
|
|
func (r *NetworkResolver) InvalidateOrg(orgID string) {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
delete(r.org, orgID)
|
|
for devID, _ := range r.dev {
|
|
delete(r.dev, devID)
|
|
}
|
|
}
|