diff --git a/internal/configmgr/clients.go b/internal/configmgr/clients.go new file mode 100644 index 00000000000..5e61e5d900f --- /dev/null +++ b/internal/configmgr/clients.go @@ -0,0 +1,121 @@ +package configmgr + +import ( + "github.com/AdguardTeam/AdGuardHome/internal/client" + "github.com/AdguardTeam/golibs/errors" + "github.com/AdguardTeam/golibs/validate" + "github.com/c2h5oh/datasize" +) + +// ClientsConfig is the on-disk persistent client configuration. +type ClientsConfig struct { + // Sources defines the set of sources to fetch the runtime clients from. + Sources *ClientSourcesConfig `yaml:"runtime_sources"` + + // Persistent are the configured clients. + Persistent []*Client `yaml:"persistent"` +} + +// ClientSourcesConfig is used to configure where the runtime clients will be +// obtained from. +type ClientSourcesConfig struct { + // ARP enables the ARP source of runtime clients. + ARP bool `yaml:"arp"` + + // DHCP enables the DHCP source of runtime clients. + DHCP bool `yaml:"dhcp"` + + // HostsFile enables the system hosts file source of runtime clients. + HostsFile bool `yaml:"hosts"` + + // RDNS enables the reverse DNS source of runtime clients. + RDNS bool `yaml:"rdns"` + + // WHOIS enables the WHOIS source of runtime clients. + WHOIS bool `yaml:"whois"` +} + +// Client represents the on-disk persistent client. +type Client struct { + // BlockedServices is the configuration of blocked services of a client. + BlockedServices *BlockedServices `yaml:"blocked_services"` + + // SafeSearchConf is the safe search configuration of a client. + SafeSearchConf *SafeSearch `yaml:"safe_search"` + + // Name is the human-readable name of the client. + Name string `yaml:"name"` + + // IDs are the identifiers of the client, such as IP addresses, CIDRs, MAC + // addresses, or ClientIDs. + IDs []string `yaml:"ids"` + + // Tags are the tags of the client. + Tags []string `yaml:"tags"` + + // Upstreams are the custom upstream DNS servers of the client. + Upstreams []string `yaml:"upstreams"` + + // UID is the unique identifier of the persistent client. + UID client.UID `yaml:"uid"` + + // UpstreamsCacheSize is the DNS cache size. + UpstreamsCacheSize datasize.ByteSize `yaml:"upstreams_cache_size"` + + // FilteringEnabled indicates if filtering is enabled for the client. + FilteringEnabled bool `yaml:"filtering_enabled"` + + // IgnoreQueryLog indicates if the client's queries are excluded from the + // query log. + IgnoreQueryLog bool `yaml:"ignore_querylog"` + + // IgnoreStatistics indicates if the client's queries are excluded from the + // statistics. + IgnoreStatistics bool `yaml:"ignore_statistics"` + + // ParentalEnabled indicates if parental control is enabled for the client. + ParentalEnabled bool `yaml:"parental_enabled"` + + // SafeBrowsingEnabled indicates if safe browsing is enabled for the client. + SafeBrowsingEnabled bool `yaml:"safebrowsing_enabled"` + + // UpstreamsCacheEnabled indicates if the DNS cache is enabled. + UpstreamsCacheEnabled bool `yaml:"upstreams_cache_enabled"` + + // UseGlobalBlockedServices indicates if the client uses the global blocked + // services configuration. + UseGlobalBlockedServices bool `yaml:"use_global_blocked_services"` + + // UseGlobalSettings indicates if the client uses the global filtering + // settings. + UseGlobalSettings bool `yaml:"use_global_settings"` +} + +// type check +var _ validate.Interface = (*ClientsConfig)(nil) + +// Validate implements the [validate.Interface] interface for *ClientsConfig. +func (c *ClientsConfig) Validate() (err error) { + if c == nil { + return errors.ErrNoValue + } + + return errors.Join( + validate.NotNil("runtime_sources", c.Sources), + validate.Slice("persistent", c.Persistent), + ) +} + +// type check +var _ validate.Interface = (*Client)(nil) + +// Validate implements the [validate.Interface] interface for *Client. +func (c *Client) Validate() (err error) { + if c == nil { + return errors.ErrNoValue + } + + // TODO(d.kolyshev): Add more validations. + + return nil +} diff --git a/internal/configmgr/config.go b/internal/configmgr/config.go index 2eeef21ed82..c9150a9303b 100644 --- a/internal/configmgr/config.go +++ b/internal/configmgr/config.go @@ -13,11 +13,17 @@ import ( // TODO(d.kolyshev): Use. // TODO(d.kolyshev): Add tests and contracts. type Config struct { + // Clients is a block with persistent clients configuration settings. + Clients *ClientsConfig `yaml:"clients"` + // DHCP is a block with DHCP configuration params. DHCP *DHCPConfig `yaml:"dhcp"` - // DNSConfig is a block with DNS configuration params. - DNSConfig *DNSConfig `yaml:"dns"` + // DNS is a block with DNS configuration params. + DNS *DNSConfig `yaml:"dns"` + + // Filtering is a block with DNS filtering configuration settings. + Filtering *FilteringConfig `yaml:"filtering"` // HTTP is a block with web API configuration settings. HTTP *HTTPConfig `yaml:"http"` @@ -28,6 +34,9 @@ type Config struct { // QueryLog is a block with query log configuration settings. QueryLog *QueryLogConfig `yaml:"querylog"` + // OS is a block with OS-related configuration settings. + OS *OSConfig `yaml:"os"` + // Stats is a block with statistics configuration settings. Stats *StatsConfig `yaml:"statistics"` @@ -35,6 +44,8 @@ type Config struct { TLS *TLSConfig `yaml:"tls"` // ProxyURL is the address of proxy server for the internal HTTP client. + // + // TODO(d.kolyshev): Use [url.URL]. ProxyURL string `yaml:"http_proxy"` // Language is a two-letter ISO 639-1 language code. @@ -45,9 +56,22 @@ type Config struct { // Theme is a web UI theme for current user. Theme string `yaml:"theme"` + // Filters is a slice of the blocking filter lists. + // + // TODO(e.burkov): Move all the filtering configuration fields into the + // only configuration subsection covering the changes with a single + // migration. Also keep the blocked services in mind. + Filters []*Filter `yaml:"filters"` + + // UserRules is a list of custom rules. + UserRules []string `yaml:"user_rules"` + // Users are the clients capable for accessing the web interface. Users []*WebUser `yaml:"users"` + // WhitelistFilters is a slice of the allowing filter lists. + WhitelistFilters []*Filter `yaml:"whitelist_filters"` + // AuthAttempts is the maximum number of failed login attempts a user can do // before being blocked. AuthAttempts uint `yaml:"auth_attempts"` @@ -78,17 +102,26 @@ func (c *Config) Validate() (err error) { // Keep this in the same order as the fields in the config. validators := container.KeyValues[string, validate.Interface]{{ + Key: "clients", + Value: c.Clients, + }, { Key: "dhcp", Value: c.DHCP, }, { Key: "dns", - Value: c.DNSConfig, + Value: c.DNS, + }, { + Key: "filtering", + Value: c.Filtering, }, { Key: "http", Value: c.HTTP, }, { Key: "log", Value: c.Log, + }, { + Key: "os", + Value: c.OS, }, { Key: "querylog", Value: c.QueryLog, @@ -112,7 +145,9 @@ func (c *Config) Validate() (err error) { } } + errs = validate.AppendSlice(errs, "filters", c.Filters) errs = validate.AppendSlice(errs, "users", c.Users) + errs = validate.AppendSlice(errs, "whitelist_filters", c.WhitelistFilters) return errors.Join(errs...) } diff --git a/internal/configmgr/filter.go b/internal/configmgr/filter.go new file mode 100644 index 00000000000..adf19296bb2 --- /dev/null +++ b/internal/configmgr/filter.go @@ -0,0 +1,38 @@ +package configmgr + +import ( + "github.com/AdguardTeam/golibs/errors" + "github.com/AdguardTeam/golibs/netutil/urlutil" + "github.com/AdguardTeam/golibs/validate" + "github.com/AdguardTeam/urlfilter/rules" +) + +// Filter represents the on-disk filter list. +type Filter struct { + // URL is the location of the filter list, which can be a URL or an absolute + // file path. + URL *urlutil.URL `yaml:"url"` + + // Name is the human-readable name of the filter list. + Name string `yaml:"name"` + + // ID is automatically assigned when filter is added. + ID rules.ListID `yaml:"id"` + + // Enabled indicates whether the filter list is used. + Enabled bool `yaml:"enabled"` +} + +// type check +var _ validate.Interface = (*Filter)(nil) + +// Validate implements the [validate.Interface] interface for *Filter. +func (f *Filter) Validate() (err error) { + if f == nil { + return errors.ErrNoValue + } + + // TODO(d.kolyshev): Add more validations. + + return nil +} diff --git a/internal/configmgr/filtering.go b/internal/configmgr/filtering.go new file mode 100644 index 00000000000..be1a7060bef --- /dev/null +++ b/internal/configmgr/filtering.go @@ -0,0 +1,142 @@ +package configmgr + +import ( + "net/netip" + "time" + + "github.com/AdguardTeam/AdGuardHome/internal/schedule" + "github.com/AdguardTeam/golibs/errors" + "github.com/AdguardTeam/golibs/timeutil" + "github.com/AdguardTeam/golibs/validate" + "github.com/c2h5oh/datasize" +) + +// FilteringConfig is the on-disk filtering configuration. +type FilteringConfig struct { + // BlockingIPv4 is the IP address to be returned for a blocked A request. + BlockingIPv4 netip.Addr `yaml:"blocking_ipv4"` + + // BlockingIPv6 is the IP address to be returned for a blocked AAAA request. + BlockingIPv6 netip.Addr `yaml:"blocking_ipv6"` + + // BlockedServices is the configuration of blocked services. Per-client + // settings can override this configuration. + BlockedServices *BlockedServices `yaml:"blocked_services"` + + // ProtectionDisabledUntil is the timestamp until when the protection is + // disabled. + ProtectionDisabledUntil *time.Time `yaml:"protection_disabled_until"` + + // SafeSearchConf is the safe search configuration. + SafeSearchConf *SafeSearch `yaml:"safe_search"` + + // BlockingMode defines the way how blocked responses are constructed. + BlockingMode string `yaml:"blocking_mode"` + + // ParentalBlockHost is the IP (or domain name) which is used to respond to + // DNS requests blocked by parental control. + ParentalBlockHost string `yaml:"parental_block_host"` + + // SafeBrowsingBlockHost is the IP (or domain name) which is used to respond + // to DNS requests blocked by safe-browsing. + SafeBrowsingBlockHost string `yaml:"safebrowsing_block_host"` + + // Rewrites is a list of legacy DNS rewrite records. + Rewrites []*LegacyRewrite `yaml:"rewrites"` + + // SafeFSPatterns are the patterns for matching which local filtering-rule + // files can be added. + SafeFSPatterns []string `yaml:"safe_fs_patterns"` + + // MaxHTTPSize defines the maximum size of the HTTP body. It must be + // positive. + MaxHTTPSize datasize.ByteSize `yaml:"max_http_size"` + + // ParentalCacheSize is the size of the parental control cache, in bytes. + ParentalCacheSize uint `yaml:"parental_cache_size"` + + // SafeBrowsingCacheSize is the size of the safe browsing cache, in bytes. + SafeBrowsingCacheSize uint `yaml:"safebrowsing_cache_size"` + + // SafeSearchCacheSize is the size of the safe search cache, in bytes. + SafeSearchCacheSize uint `yaml:"safesearch_cache_size"` + + // CacheTime is the TTL of a cache element. + CacheTime timeutil.Duration `yaml:"cache_time"` + + // BlockedResponseTTL is the time-to-live value for blocked responses. If + // 0, then default value is used (3600). + BlockedResponseTTL uint32 `yaml:"blocked_response_ttl"` + + // FiltersUpdateIntervalHours is the time period to update filters + // (in hours). + FiltersUpdateIntervalHours uint32 `yaml:"filters_update_interval"` + + // FilteringEnabled indicates whether or not use filter lists. + FilteringEnabled bool `yaml:"filtering_enabled"` + + // ParentalEnabled indicates whether parental control is enabled. + ParentalEnabled bool `yaml:"parental_enabled"` + + // ProtectionEnabled defines whether or not use any of filtering features. + ProtectionEnabled bool `yaml:"protection_enabled"` + + // RewritesEnabled indicates whether legacy rewrites are applied. + RewritesEnabled bool `yaml:"rewrites_enabled"` + + // SafeBrowsingEnabled indicates whether safe browsing is enabled. + SafeBrowsingEnabled bool `yaml:"safebrowsing_enabled"` +} + +// BlockedServices is the configuration of blocked services. +type BlockedServices struct { + // Schedule is blocked services schedule for every day of the week. + Schedule *schedule.Weekly `yaml:"schedule"` + + // IDs is the names of blocked services. + IDs []string `yaml:"ids"` +} + +// SafeSearch is a struct with safe search related settings. +type SafeSearch struct { + // Enabled indicates if safe search is enabled entirely. + Enabled bool `yaml:"enabled"` + + // Services flags. Each flag indicates if the corresponding service is + // enabled or disabled. + + Bing bool `yaml:"bing"` + DuckDuckGo bool `yaml:"duckduckgo"` + Ecosia bool `yaml:"ecosia"` + Google bool `yaml:"google"` + Pixabay bool `yaml:"pixabay"` + Yandex bool `yaml:"yandex"` + YouTube bool `yaml:"youtube"` +} + +// LegacyRewrite is a single legacy DNS rewrite record. +type LegacyRewrite struct { + // Answer is the IP address, canonical name, or one of the special values: + // "A" or "AAAA". + Answer string `yaml:"answer"` + + // Domain is the pattern to which this rewrite applies. + Domain string `yaml:"domain"` + + // Enabled indicates whether this rewrite is active. + Enabled bool `yaml:"enabled"` +} + +// type check +var _ validate.Interface = (*FilteringConfig)(nil) + +// Validate implements the [validate.Interface] interface for *FilteringConfig. +func (c *FilteringConfig) Validate() (err error) { + if c == nil { + return errors.ErrNoValue + } + + // TODO(d.kolyshev): Add more validations. + + return nil +} diff --git a/internal/configmgr/os.go b/internal/configmgr/os.go new file mode 100644 index 00000000000..a5534c0f5a0 --- /dev/null +++ b/internal/configmgr/os.go @@ -0,0 +1,30 @@ +package configmgr + +import ( + "github.com/AdguardTeam/golibs/validate" +) + +// OSConfig is the on-disk OS-related configuration. +type OSConfig struct { + // Group is the name of the group which AdGuard Home must switch to on + // startup. Empty string means no switching. + Group string `yaml:"group"` + + // User is the name of the user which AdGuard Home must switch to on + // startup. Empty string means no switching. + User string `yaml:"user"` + + // RlimitNoFile is the maximum number of opened fd's per process. Zero + // means to use the default value. + RlimitNoFile uint64 `yaml:"rlimit_nofile"` +} + +// type check +var _ validate.Interface = (*OSConfig)(nil) + +// Validate implements the [validate.Interface] interface for *OSConfig. +func (c *OSConfig) Validate() (err error) { + // TODO(d.kolyshev): Validate. + + return nil +}