package main import ( "bytes" "encoding/json" "errors" "fmt" "net/netip" "os" ) type config struct { Admission admissionConfig `json:"admission"` AdmissionHelper string `json:"admission_helper"` GatewayRelayAddress string `json:"gateway_relay_address"` HomeWireGuardPublicKey string `json:"home_wireguard_public_key"` LogIngestHostname string `json:"log_ingest_hostname"` LogIngestSecretOCID string `json:"log_ingest_secret_ocid"` MailRelayAddress string `json:"mail_relay_address,omitempty"` MonitoringSourceIPv4 []string `json:"monitoring_source_ipv4"` PublicKeySecretOCID string `json:"public_key_secret_ocid"` RateLimitMbit int `json:"rate_limit_mbit"` RelayHTTPQuarantineEnabled bool `json:"relay_http_quarantine_enabled"` RelayHTTPQuarantineMinutes int `json:"relay_http_quarantine_minutes"` RelayHTTPSRecentMinutes int `json:"relay_https_recent_minutes"` RelayMetricsPort int `json:"relay_metrics_port"` RelayAuthPacketAggregateBurst int `json:"relay_auth_packet_aggregate_burst"` RelayAuthPacketAggregateRate int `json:"relay_auth_packet_aggregate_rate_per_second"` RelayAuthSYNAggregateBurst int `json:"relay_auth_syn_aggregate_burst"` RelayAuthSYNAggregateRate int `json:"relay_auth_syn_aggregate_rate_per_second"` RelayAuthSYNBurst int `json:"relay_auth_syn_burst"` RelayAuthSYNRate int `json:"relay_auth_syn_rate_per_minute"` RelaySMTPPacketAggregateBurst int `json:"relay_smtp_packet_aggregate_burst"` RelaySMTPPacketAggregateRate int `json:"relay_smtp_packet_aggregate_rate_per_second"` RelaySMTPSYNAggregateBurst int `json:"relay_smtp_syn_aggregate_burst"` RelaySMTPSYNAggregateRate int `json:"relay_smtp_syn_aggregate_rate_per_second"` RelaySMTPSYNBurst int `json:"relay_smtp_syn_burst"` RelaySMTPSYNRate int `json:"relay_smtp_syn_rate_per_minute"` RelayTrackerMaxSources int `json:"relay_tracker_max_sources"` RelayTunnelAddress string `json:"relay_tunnel_address"` RelayTunnelMTU int `json:"relay_tunnel_mtu"` RelayUnmatchedHTTPBurst int `json:"relay_unmatched_http_burst"` RelayUnmatchedHTTPRate int `json:"relay_unmatched_http_rate_per_hour"` RelayWebPacketAggregateBurst int `json:"relay_web_packet_aggregate_burst"` RelayWebPacketAggregateRate int `json:"relay_web_packet_aggregate_rate_per_second"` RelayWebSYNAggregateBurst int `json:"relay_web_syn_aggregate_burst"` RelayWebSYNAggregateRate int `json:"relay_web_syn_aggregate_rate_per_second"` RelayWebSYNBurst int `json:"relay_web_syn_burst"` RelayWebSYNRate int `json:"relay_web_syn_rate_per_minute"` SMTPDeny *smtpDenyConfig `json:"smtp_deny,omitempty"` SMTPDenyHelper string `json:"smtp_deny_helper,omitempty"` WireGuardListenPort int `json:"wireguard_listen_port"` WireGuardSeedSecretOCID string `json:"wireguard_seed_secret_ocid"` mailConfigurationError string } type admissionConfig struct { Bucket string `json:"bucket"` MaximumAddresses uint64 `json:"maximum_addresses"` MinimumAddresses uint64 `json:"minimum_addresses"` MinimumIntervals int `json:"minimum_intervals"` Namespace string `json:"namespace"` Object string `json:"object"` } type smtpDenyConfig struct { Bucket string `json:"bucket"` MaximumAddresses uint64 `json:"maximum_addresses"` MaximumIntervals int `json:"maximum_intervals"` MaximumLifetimeSeconds int `json:"maximum_lifetime_seconds"` MaximumObservationWindow int `json:"maximum_observation_window_seconds"` MinimumObservations uint64 `json:"minimum_observations"` Namespace string `json:"namespace"` Object string `json:"object"` } func loadConfig(path string) (config, error) { contents, err := os.ReadFile(path) if err != nil { return config{}, err } var value config decoder := json.NewDecoder(bytes.NewReader(contents)) decoder.DisallowUnknownFields() if err := decoder.Decode(&value); err != nil { return config{}, err } if err := value.validate(); err != nil { return config{}, err } if err := value.validateMail(); err != nil { value.MailRelayAddress = "" value.SMTPDeny = nil value.SMTPDenyHelper = "" value.mailConfigurationError = err.Error() } return value, nil } func (value config) validate() error { required := map[string]string{ "admission.bucket": value.Admission.Bucket, "admission.namespace": value.Admission.Namespace, "admission.object": value.Admission.Object, "admission_helper": value.AdmissionHelper, "gateway_relay_address": value.GatewayRelayAddress, "home_wireguard_public_key": value.HomeWireGuardPublicKey, "log_ingest_hostname": value.LogIngestHostname, "log_ingest_secret_ocid": value.LogIngestSecretOCID, "public_key_secret_ocid": value.PublicKeySecretOCID, "relay_tunnel_address": value.RelayTunnelAddress, "wireguard_seed_secret_ocid": value.WireGuardSeedSecretOCID, } for name, candidate := range required { if candidate == "" { return fmt.Errorf("%s is required", name) } } positive := map[string]int{ "admission.minimum_intervals": value.Admission.MinimumIntervals, "rate_limit_mbit": value.RateLimitMbit, "relay_http_quarantine_minutes": value.RelayHTTPQuarantineMinutes, "relay_https_recent_minutes": value.RelayHTTPSRecentMinutes, "relay_metrics_port": value.RelayMetricsPort, "relay_tracker_max_sources": value.RelayTrackerMaxSources, "relay_tunnel_mtu": value.RelayTunnelMTU, "relay_unmatched_http_burst": value.RelayUnmatchedHTTPBurst, "relay_unmatched_http_rate_per_hour": value.RelayUnmatchedHTTPRate, "relay_web_packet_aggregate_burst": value.RelayWebPacketAggregateBurst, "relay_web_packet_aggregate_rate_per_second": value.RelayWebPacketAggregateRate, "relay_web_syn_aggregate_burst": value.RelayWebSYNAggregateBurst, "relay_web_syn_aggregate_rate_per_second": value.RelayWebSYNAggregateRate, "relay_web_syn_burst": value.RelayWebSYNBurst, "relay_web_syn_rate_per_minute": value.RelayWebSYNRate, "wireguard_listen_port": value.WireGuardListenPort, } for name, candidate := range positive { if candidate <= 0 { return fmt.Errorf("%s must be positive", name) } } if value.RelayMetricsPort > 65535 { return errors.New("relay_metrics_port must not exceed 65535") } if value.Admission.MinimumAddresses == 0 || value.Admission.MaximumAddresses < value.Admission.MinimumAddresses { return errors.New("admission address bounds are invalid") } if len(value.MonitoringSourceIPv4) < 3 { return errors.New("at least three monitoring source IPv4 addresses are required") } seen := make(map[netip.Addr]bool, len(value.MonitoringSourceIPv4)) for _, candidate := range value.MonitoringSourceIPv4 { address, err := netip.ParseAddr(candidate) if err != nil || !address.Is4() || address.String() != candidate || seen[address] { return fmt.Errorf("invalid or duplicate monitoring source IPv4 address %q", candidate) } seen[address] = true } return nil } func (value config) validateMail() error { if value.MailRelayAddress == "" && value.SMTPDeny == nil && value.SMTPDenyHelper == "" { return nil } address, err := netip.ParseAddr(value.MailRelayAddress) if err != nil || !address.Is4() || address.String() != value.MailRelayAddress || value.MailRelayAddress == value.GatewayRelayAddress { return errors.New("mail_relay_address must be a distinct canonical IPv4 address") } if value.SMTPDeny == nil { return errors.New("smtp_deny is required when mail relay is configured") } required := map[string]string{ "smtp_deny.bucket": value.SMTPDeny.Bucket, "smtp_deny.namespace": value.SMTPDeny.Namespace, "smtp_deny.object": value.SMTPDeny.Object, "smtp_deny_helper": value.SMTPDenyHelper, } for name, candidate := range required { if candidate == "" { return fmt.Errorf("%s is required", name) } } if value.SMTPDeny.MinimumObservations == 0 || value.SMTPDeny.MaximumAddresses == 0 || value.SMTPDeny.MaximumIntervals <= 0 || value.SMTPDeny.MaximumLifetimeSeconds <= 0 || value.SMTPDeny.MaximumObservationWindow <= 0 || value.SMTPDeny.MaximumObservationWindow > value.SMTPDeny.MaximumLifetimeSeconds { return errors.New("smtp_deny policy bounds are invalid") } positive := map[string]int{ "relay_auth_packet_aggregate_burst": value.RelayAuthPacketAggregateBurst, "relay_auth_packet_aggregate_rate_per_second": value.RelayAuthPacketAggregateRate, "relay_auth_syn_aggregate_burst": value.RelayAuthSYNAggregateBurst, "relay_auth_syn_aggregate_rate_per_second": value.RelayAuthSYNAggregateRate, "relay_auth_syn_burst": value.RelayAuthSYNBurst, "relay_auth_syn_rate_per_minute": value.RelayAuthSYNRate, "relay_smtp_packet_aggregate_burst": value.RelaySMTPPacketAggregateBurst, "relay_smtp_packet_aggregate_rate_per_second": value.RelaySMTPPacketAggregateRate, "relay_smtp_syn_aggregate_burst": value.RelaySMTPSYNAggregateBurst, "relay_smtp_syn_aggregate_rate_per_second": value.RelaySMTPSYNAggregateRate, "relay_smtp_syn_burst": value.RelaySMTPSYNBurst, "relay_smtp_syn_rate_per_minute": value.RelaySMTPSYNRate, } for name, candidate := range positive { if candidate <= 0 { return fmt.Errorf("%s must be positive", name) } } if value.RelayAuthPacketAggregateBurst < value.RelayAuthPacketAggregateRate || value.RelaySMTPPacketAggregateBurst < value.RelaySMTPPacketAggregateRate { return errors.New("mail packet aggregate bursts must cover at least one second") } return nil }