mirror of
https://github.com/NotAShelf/watchdog.git
synced 2026-03-07 05:46:00 +00:00
config: add security and performance sections to sample config; validate
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: Ieda42bcbd09014c45fb14bee579f829c6a6a6964
This commit is contained in:
parent
c3b77696aa
commit
b2256183e1
2 changed files with 121 additions and 16 deletions
|
|
@ -8,13 +8,18 @@ site:
|
||||||
# Options: "daily", "hourly"
|
# Options: "daily", "hourly"
|
||||||
salt_rotation: "daily"
|
salt_rotation: "daily"
|
||||||
|
|
||||||
|
# Sampling rate (0.0 to 1.0, default: 1.0 = 100%)
|
||||||
|
# Use 0.1 to track 10% of traffic for high-volume sites
|
||||||
|
sampling: 1.0
|
||||||
|
|
||||||
# Which dimensions to collect
|
# Which dimensions to collect
|
||||||
collect:
|
collect:
|
||||||
pageviews: true
|
pageviews: true
|
||||||
country: true
|
country: true
|
||||||
device: true
|
device: true
|
||||||
# Referrer collection mode: "off", "domain"
|
# Referrer collection mode: "off", "domain", "url"
|
||||||
# "domain" - collect only the domain part (e.g., "google.com")
|
# "domain" - collect only the domain part (e.g., "google.com")
|
||||||
|
# "url" - collect full URL (not recommended - high cardinality)
|
||||||
# "off" - don't collect referrer data
|
# "off" - don't collect referrer data
|
||||||
referrer: "domain"
|
referrer: "domain"
|
||||||
|
|
||||||
|
|
@ -32,7 +37,7 @@ site:
|
||||||
strip_fragment: true
|
strip_fragment: true
|
||||||
# Collapse numeric segments to :id (e.g., /user/123 -> /user/:id)
|
# Collapse numeric segments to :id (e.g., /user/123 -> /user/:id)
|
||||||
collapse_numeric_segments: true
|
collapse_numeric_segments: true
|
||||||
# Maximum number of path segments to keep
|
# Maximum number of path segments to keep (0 = unlimited)
|
||||||
max_segments: 5
|
max_segments: 5
|
||||||
# Normalize trailing slashes (e.g., /page/ -> /page)
|
# Normalize trailing slashes (e.g., /page/ -> /page)
|
||||||
normalize_trailing_slash: true
|
normalize_trailing_slash: true
|
||||||
|
|
@ -43,8 +48,36 @@ limits:
|
||||||
max_paths: 10000
|
max_paths: 10000
|
||||||
# Maximum number of unique referrer sources to track
|
# Maximum number of unique referrer sources to track
|
||||||
max_sources: 500
|
max_sources: 500
|
||||||
# Maximum events per minute (for rate limiting, not yet implemented)
|
# Maximum number of unique custom event names to track
|
||||||
max_events_per_minute: 1000
|
max_custom_events: 100
|
||||||
|
# Maximum events per minute (rate limiting, 0 = unlimited)
|
||||||
|
max_events_per_minute: 10000
|
||||||
|
|
||||||
|
# Device classification breakpoints (screen width in pixels)
|
||||||
|
device_breakpoints:
|
||||||
|
mobile: 768 # < 768px = mobile
|
||||||
|
tablet: 1024 # < 1024px = tablet, >= 1024px = desktop
|
||||||
|
|
||||||
|
# Security settings
|
||||||
|
security:
|
||||||
|
# Trusted proxy IPs/CIDRs - only trust X-Forwarded-For from these IPs
|
||||||
|
# Leave empty to never trust proxy headers
|
||||||
|
trusted_proxies:
|
||||||
|
- "127.0.0.1"
|
||||||
|
- "10.0.0.0/8"
|
||||||
|
# - "your-load-balancer-ip"
|
||||||
|
|
||||||
|
# CORS configuration for cross-origin tracking
|
||||||
|
cors:
|
||||||
|
enabled: false
|
||||||
|
allowed_origins:
|
||||||
|
- "*" # Or specific domains: ["https://example.com", "https://www.example.com"]
|
||||||
|
|
||||||
|
# Basic authentication for /metrics endpoint
|
||||||
|
metrics_auth:
|
||||||
|
enabled: false
|
||||||
|
username: "admin"
|
||||||
|
password: "changeme"
|
||||||
|
|
||||||
# Server configuration
|
# Server configuration
|
||||||
server:
|
server:
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ type Config struct {
|
||||||
Site SiteConfig `yaml:"site"`
|
Site SiteConfig `yaml:"site"`
|
||||||
Limits LimitsConfig `yaml:"limits"`
|
Limits LimitsConfig `yaml:"limits"`
|
||||||
Server ServerConfig `yaml:"server"`
|
Server ServerConfig `yaml:"server"`
|
||||||
|
Security SecurityConfig `yaml:"security"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Site-specific settings
|
// Site-specific settings
|
||||||
|
|
@ -21,6 +22,7 @@ type SiteConfig struct {
|
||||||
Collect CollectConfig `yaml:"collect"`
|
Collect CollectConfig `yaml:"collect"`
|
||||||
CustomEvents []string `yaml:"custom_events"`
|
CustomEvents []string `yaml:"custom_events"`
|
||||||
Path PathConfig `yaml:"path"`
|
Path PathConfig `yaml:"path"`
|
||||||
|
Sampling float64 `yaml:"sampling"` // 0.0-1.0, 1.0 = track all traffic
|
||||||
}
|
}
|
||||||
|
|
||||||
// Which dimensions to collect
|
// Which dimensions to collect
|
||||||
|
|
@ -45,6 +47,34 @@ type LimitsConfig struct {
|
||||||
MaxPaths int `yaml:"max_paths"`
|
MaxPaths int `yaml:"max_paths"`
|
||||||
MaxEventsPerMinute int `yaml:"max_events_per_minute"`
|
MaxEventsPerMinute int `yaml:"max_events_per_minute"`
|
||||||
MaxSources int `yaml:"max_sources"`
|
MaxSources int `yaml:"max_sources"`
|
||||||
|
MaxCustomEvents int `yaml:"max_custom_events"`
|
||||||
|
DeviceBreakpoints DeviceBreaks `yaml:"device_breakpoints"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Device classification breakpoints
|
||||||
|
type DeviceBreaks struct {
|
||||||
|
Mobile int `yaml:"mobile"` // < mobile = mobile
|
||||||
|
Tablet int `yaml:"tablet"` // < tablet = tablet, else desktop
|
||||||
|
}
|
||||||
|
|
||||||
|
// Security settings
|
||||||
|
type SecurityConfig struct {
|
||||||
|
TrustedProxies []string `yaml:"trusted_proxies"` // IPs/CIDRs to trust X-Forwarded-For from
|
||||||
|
CORS CORSConfig `yaml:"cors"`
|
||||||
|
MetricsAuth AuthConfig `yaml:"metrics_auth"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CORS configuration
|
||||||
|
type CORSConfig struct {
|
||||||
|
Enabled bool `yaml:"enabled"`
|
||||||
|
AllowedOrigins []string `yaml:"allowed_origins"` // ["*"] or specific domains
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authentication for metrics endpoint
|
||||||
|
type AuthConfig struct {
|
||||||
|
Enabled bool `yaml:"enabled"`
|
||||||
|
Username string `yaml:"username"`
|
||||||
|
Password string `yaml:"password"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Server endpoints
|
// Server endpoints
|
||||||
|
|
@ -74,29 +104,61 @@ func Load(path string) (*Config, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check required fields and sets defaults
|
// Check required fields and sets defaults
|
||||||
// FIXME: in the future we need to validate in the config parser
|
|
||||||
func (c *Config) Validate() error {
|
func (c *Config) Validate() error {
|
||||||
// Validate site domain is required
|
// Site validation
|
||||||
if c.Site.Domain == "" {
|
if c.Site.Domain == "" {
|
||||||
return fmt.Errorf("site.domain is required")
|
return fmt.Errorf("site.domain is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate salt_rotation if provided
|
// Validate salt_rotation
|
||||||
if c.Site.SaltRotation != "" && c.Site.SaltRotation != "daily" && c.Site.SaltRotation != "hourly" {
|
if c.Site.SaltRotation != "" && c.Site.SaltRotation != "daily" && c.Site.SaltRotation != "hourly" {
|
||||||
return fmt.Errorf("site.salt_rotation must be 'daily' or 'hourly'")
|
return fmt.Errorf("site.salt_rotation must be 'daily' or 'hourly'")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate max_paths is positive
|
// Validate referrer setting
|
||||||
|
if c.Site.Collect.Referrer != "" && c.Site.Collect.Referrer != "off" &&
|
||||||
|
c.Site.Collect.Referrer != "domain" && c.Site.Collect.Referrer != "url" {
|
||||||
|
return fmt.Errorf("site.collect.referrer must be 'off', 'domain', or 'url'")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate sampling rate
|
||||||
|
if c.Site.Sampling < 0.0 || c.Site.Sampling > 1.0 {
|
||||||
|
return fmt.Errorf("site.sampling must be between 0.0 and 1.0")
|
||||||
|
}
|
||||||
|
if c.Site.Sampling == 0.0 {
|
||||||
|
c.Site.Sampling = 1.0 // default to 100%
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limits validation
|
||||||
if c.Limits.MaxPaths <= 0 {
|
if c.Limits.MaxPaths <= 0 {
|
||||||
return fmt.Errorf("limits.max_paths must be greater than 0")
|
return fmt.Errorf("limits.max_paths must be greater than 0")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate max_sources is positive
|
|
||||||
if c.Limits.MaxSources <= 0 {
|
if c.Limits.MaxSources <= 0 {
|
||||||
return fmt.Errorf("limits.max_sources must be greater than 0")
|
return fmt.Errorf("limits.max_sources must be greater than 0")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set server defaults if not provided
|
if c.Limits.MaxEventsPerMinute < 0 {
|
||||||
|
return fmt.Errorf("limits.max_events_per_minute cannot be negative")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Limits.MaxCustomEvents <= 0 {
|
||||||
|
c.Limits.MaxCustomEvents = 100 // Default
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Site.Path.MaxSegments < 0 {
|
||||||
|
return fmt.Errorf("site.path.max_segments cannot be negative")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set device breakpoint defaults
|
||||||
|
if c.Limits.DeviceBreakpoints.Mobile == 0 {
|
||||||
|
c.Limits.DeviceBreakpoints.Mobile = 768
|
||||||
|
}
|
||||||
|
if c.Limits.DeviceBreakpoints.Tablet == 0 {
|
||||||
|
c.Limits.DeviceBreakpoints.Tablet = 1024
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server defaults
|
||||||
if c.Server.ListenAddr == "" {
|
if c.Server.ListenAddr == "" {
|
||||||
c.Server.ListenAddr = ":8080"
|
c.Server.ListenAddr = ":8080"
|
||||||
}
|
}
|
||||||
|
|
@ -107,5 +169,15 @@ func (c *Config) Validate() error {
|
||||||
c.Server.IngestionPath = "/api/event"
|
c.Server.IngestionPath = "/api/event"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.Security.MetricsAuth.Enabled {
|
||||||
|
if c.Security.MetricsAuth.Username == "" || c.Security.MetricsAuth.Password == "" {
|
||||||
|
return fmt.Errorf("security.metrics_auth: username and password required when enabled")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Security.CORS.Enabled && len(c.Security.CORS.AllowedOrigins) == 0 {
|
||||||
|
return fmt.Errorf("security.cors: allowed_origins required when enabled")
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue