107 lines
3.6 KiB
Go
107 lines
3.6 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"net/url"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
DefaultSiteTCPFlowLimit = 2048
|
|
DefaultSiteUDPFlowLimit = 4096
|
|
DefaultSiteUDPIdleSeconds = 60
|
|
)
|
|
|
|
// EngineerConfig contains portable Engineer settings. JoinToken is an optional
|
|
// plaintext first-registration convenience; durable Node secrets stay in DPAPI.
|
|
type EngineerConfig struct {
|
|
Server string `yaml:"server"`
|
|
NodeName string `yaml:"node_name"`
|
|
JoinToken string `yaml:"join_token,omitempty"`
|
|
}
|
|
|
|
// SiteConfig contains portable Site settings. JoinToken has the same
|
|
// first-registration-only semantics as EngineerConfig.JoinToken.
|
|
type SiteConfig struct {
|
|
Server string `yaml:"server"`
|
|
NodeName string `yaml:"node_name"`
|
|
JoinToken string `yaml:"join_token,omitempty"`
|
|
Netstack SiteNetstackConfig `yaml:"netstack"`
|
|
}
|
|
|
|
type SiteNetstackConfig struct {
|
|
TCPFlowLimit int `yaml:"tcp_flow_limit"`
|
|
UDPFlowLimit int `yaml:"udp_flow_limit"`
|
|
UDPIdleSeconds int `yaml:"udp_idle_seconds"`
|
|
}
|
|
|
|
// LoadEngineer loads and validates a strict Engineer YAML file.
|
|
func LoadEngineer(path string) (EngineerConfig, error) {
|
|
var config EngineerConfig
|
|
if err := decodeStrict(path, &config); err != nil {
|
|
return EngineerConfig{}, err
|
|
}
|
|
if err := validateClient(config.Server, config.NodeName); err != nil {
|
|
return EngineerConfig{}, fmt.Errorf("validate engineer config %q: %w", path, err)
|
|
}
|
|
config.JoinToken = strings.TrimSpace(config.JoinToken)
|
|
return config, nil
|
|
}
|
|
|
|
// LoadSite loads and validates a strict Site YAML file.
|
|
func LoadSite(path string) (SiteConfig, error) {
|
|
config := SiteConfig{Netstack: SiteNetstackConfig{
|
|
TCPFlowLimit: DefaultSiteTCPFlowLimit, UDPFlowLimit: DefaultSiteUDPFlowLimit,
|
|
UDPIdleSeconds: DefaultSiteUDPIdleSeconds,
|
|
}}
|
|
if err := decodeStrict(path, &config); err != nil {
|
|
return SiteConfig{}, err
|
|
}
|
|
if err := validateClient(config.Server, config.NodeName); err != nil {
|
|
return SiteConfig{}, fmt.Errorf("validate site config %q: %w", path, err)
|
|
}
|
|
config.JoinToken = strings.TrimSpace(config.JoinToken)
|
|
if config.Netstack.TCPFlowLimit < 1 || config.Netstack.UDPFlowLimit < 1 || config.Netstack.UDPIdleSeconds < 1 || config.Netstack.UDPIdleSeconds > 86400 {
|
|
return SiteConfig{}, fmt.Errorf("validate site config %q: netstack limits must be positive and udp_idle_seconds must be within 1..86400", path)
|
|
}
|
|
return config, nil
|
|
}
|
|
|
|
// ResolveJoinToken applies the documented precedence. override is populated by
|
|
// the -join-token flag, whose default is REMLINK_JOIN_TOKEN.
|
|
func ResolveJoinToken(override, configured string) string {
|
|
if value := strings.TrimSpace(override); value != "" {
|
|
return value
|
|
}
|
|
return strings.TrimSpace(configured)
|
|
}
|
|
|
|
func validateClient(server, nodeName string) error {
|
|
if strings.TrimSpace(nodeName) == "" {
|
|
return fmt.Errorf("node_name must not be empty")
|
|
}
|
|
return ValidateServerURL(server)
|
|
}
|
|
|
|
// ValidateServerURL applies the single public Server URL trust boundary used
|
|
// by YAML loading, persisted Node identities, and the Bootstrap client.
|
|
func ValidateServerURL(server string) error {
|
|
parsed, err := url.Parse(server)
|
|
if err != nil || parsed.Host == "" || parsed.Opaque != "" {
|
|
return fmt.Errorf("server must be an absolute HTTP URL")
|
|
}
|
|
if parsed.Scheme != "http" && parsed.Scheme != "https" {
|
|
return fmt.Errorf("server URL scheme must be http or https")
|
|
}
|
|
if parsed.User != nil {
|
|
return fmt.Errorf("server URL must not contain credentials")
|
|
}
|
|
if parsed.RawQuery != "" || parsed.ForceQuery || parsed.Fragment != "" {
|
|
return fmt.Errorf("server URL must not contain a query or fragment")
|
|
}
|
|
if parsed.Path != "" && parsed.Path != "/" {
|
|
return fmt.Errorf("server URL must not contain a path")
|
|
}
|
|
return nil
|
|
}
|