mirror of
https://github.com/AdguardTeam/AdGuardHome.git
synced 2026-10-12 02:01:26 +00:00
AGH-31 Use-confs-http
This commit is contained in:
+41
-17
@@ -2,8 +2,8 @@ package configmgr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"regexp"
|
||||
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/timeutil"
|
||||
@@ -12,10 +12,10 @@ import (
|
||||
|
||||
// HTTPConfig is the on-disk web API configuration.
|
||||
type HTTPConfig struct {
|
||||
// DoH contains DNS-over-HTTPS configuration.
|
||||
// DoH contains DNS-over-HTTPS configuration. It must not be nil.
|
||||
DoH *DoHConfig `yaml:"doh"`
|
||||
|
||||
// Pprof defines the profiling HTTP handler.
|
||||
// Pprof defines the profiling HTTP handler. It must not be nil.
|
||||
Pprof *HTTPPprofConfig `yaml:"pprof"`
|
||||
|
||||
// Address is the addresses on which to serve web API.
|
||||
@@ -28,7 +28,8 @@ type HTTPConfig struct {
|
||||
// DoHConfig is the block with DNS-over-HTTPS configuration.
|
||||
type DoHConfig struct {
|
||||
// Routes is the list of HTTP route patterns for DoH requests. Each route
|
||||
// should be in the format "METHOD /path" or "METHOD /path/{param}".
|
||||
// must be a valid [http.ServeMux] pattern, and the routes must not conflict
|
||||
// with each other.
|
||||
Routes []string `yaml:"routes"`
|
||||
|
||||
// InsecureEnabled allows DoH queries via unencrypted HTTP.
|
||||
@@ -60,38 +61,61 @@ func (c *HTTPConfig) Validate() (err error) {
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
// doHRoutePatternRegexp is a regular expression for validating DoH route
|
||||
// patterns.
|
||||
var doHRoutePatternRegexp = regexp.MustCompile(
|
||||
`^[A-Z]+ /[^\s{}]+(?:/[^\s{}]+)*(?:/\{[_A-Za-z][_A-Za-z0-9]*\})?$`,
|
||||
)
|
||||
|
||||
// type check
|
||||
var _ validate.Interface = (*DoHConfig)(nil)
|
||||
|
||||
// Validate implements the [validate.Interface] interface for *DoHConfig.
|
||||
func (c *DoHConfig) Validate() (err error) {
|
||||
if c == nil {
|
||||
return nil
|
||||
return errors.ErrNoValue
|
||||
}
|
||||
|
||||
var errs []error
|
||||
for i, route := range c.Routes {
|
||||
if doHRoutePatternRegexp.MatchString(route) {
|
||||
continue
|
||||
}
|
||||
if err = validateRoutes(c.Routes); err != nil {
|
||||
return fmt.Errorf("routes: %w", err)
|
||||
}
|
||||
|
||||
errs = append(errs, fmt.Errorf(`route %q at index %d: incorrect format`, route, i))
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateRoutes registers the given routes on a temporary [http.ServeMux] and
|
||||
// returns an error if any of them cannot be registered.
|
||||
func validateRoutes(routes []string) (err error) {
|
||||
mux := http.NewServeMux()
|
||||
|
||||
var errs []error
|
||||
for i, route := range routes {
|
||||
if err = validateRoute(mux, route); err != nil {
|
||||
errs = append(errs, fmt.Errorf("route at index %d: %q: %w", i, route, err))
|
||||
}
|
||||
}
|
||||
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
// validateRoute tries registering route on mux, converting the panic that
|
||||
// [http.ServeMux.Handle] raises for an invalid or conflicting pattern into an
|
||||
// error. mux must not be nil.
|
||||
//
|
||||
// TODO(a.garipov): Find out better solution.
|
||||
func validateRoute(mux *http.ServeMux, route string) (err error) {
|
||||
defer func() {
|
||||
err = errors.FromRecovered(recover())
|
||||
}()
|
||||
|
||||
mux.Handle(route, http.NotFoundHandler())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ validate.Interface = (*HTTPPprofConfig)(nil)
|
||||
|
||||
// Validate implements the [validate.Interface] interface for *HTTPPprofConfig.
|
||||
func (c *HTTPPprofConfig) Validate() (err error) {
|
||||
if c == nil {
|
||||
return errors.ErrNoValue
|
||||
}
|
||||
|
||||
// TODO(d.kolyshev): Validate.
|
||||
|
||||
return nil
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
package configmgr_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/configmgr"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDoHConfig_Validate_routes(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
wantMessage string
|
||||
routes []string
|
||||
}{{
|
||||
name: "valid",
|
||||
wantMessage: "",
|
||||
routes: []string{
|
||||
"GET /dns-query",
|
||||
"POST /dns-query",
|
||||
"GET /dns-query/{ClientID}",
|
||||
"POST /dns-query/{ClientID}",
|
||||
"GET /dns-query/",
|
||||
"GET /doh/{a}/{b}",
|
||||
"/dns-query",
|
||||
},
|
||||
}, {
|
||||
name: "empty",
|
||||
wantMessage: `routes: route at index 0: "": http: invalid pattern`,
|
||||
routes: []string{""},
|
||||
}, {
|
||||
name: "bad_wildcard",
|
||||
wantMessage: `routes: route at index 0: "GET /dns-query/{ClientID": parsing ` +
|
||||
`"GET /dns-query/{ClientID"`,
|
||||
routes: []string{
|
||||
"GET /dns-query/{ClientID",
|
||||
},
|
||||
}, {
|
||||
name: "duplicate",
|
||||
wantMessage: `routes: route at index 1: "GET /dns-query": pattern "GET /dns-query"`,
|
||||
routes: []string{
|
||||
"GET /dns-query",
|
||||
"GET /dns-query",
|
||||
},
|
||||
}, {
|
||||
name: "conflict",
|
||||
wantMessage: `routes: route at index 1: "GET /dns-query/{Other}": pattern ` +
|
||||
`"GET /dns-query/{Other}"`,
|
||||
routes: []string{
|
||||
"GET /dns-query/{ClientID}",
|
||||
"GET /dns-query/{Other}",
|
||||
},
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
conf := &configmgr.DoHConfig{
|
||||
Routes: tc.routes,
|
||||
}
|
||||
|
||||
err := conf.Validate()
|
||||
if tc.wantMessage == "" {
|
||||
assert.NoError(t, err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
require.Error(t, err)
|
||||
fmt.Println(err)
|
||||
assert.Contains(t, err.Error(), tc.wantMessage)
|
||||
})
|
||||
}
|
||||
}
|
||||
+14
-55
@@ -88,7 +88,8 @@ type configuration struct {
|
||||
fileData []byte
|
||||
|
||||
// HTTPConfig is the block with http conf.
|
||||
HTTPConfig httpConfig `yaml:"http"`
|
||||
HTTPConfig configmgr.HTTPConfig `yaml:"http"`
|
||||
|
||||
// Users are the clients capable for accessing the web interface.
|
||||
Users []webUser `yaml:"users"`
|
||||
// AuthAttempts is the maximum number of failed login attempts a user
|
||||
@@ -153,52 +154,6 @@ type configuration struct {
|
||||
UnsafeUseCustomUpdateIndexURL bool `yaml:"unsafe_use_custom_update_index_url,omitempty"`
|
||||
}
|
||||
|
||||
// httpConfig is a block with HTTP configuration params.
|
||||
//
|
||||
// Field ordering is important, YAML fields better not to be reordered, if it's
|
||||
// not absolutely necessary.
|
||||
type httpConfig struct {
|
||||
// Pprof defines the profiling HTTP handler. It is never nil.
|
||||
Pprof *httpPprofConfig `yaml:"pprof"`
|
||||
|
||||
// DoH contains DNS-over-HTTPS configuration. It is never nil.
|
||||
DoH *doHConfig `yaml:"doh"`
|
||||
|
||||
// Address is the address to serve the web UI on.
|
||||
Address netip.AddrPort
|
||||
|
||||
// SessionTTL for a web session.
|
||||
// An active session is automatically refreshed once a day.
|
||||
SessionTTL timeutil.Duration `yaml:"session_ttl"`
|
||||
}
|
||||
|
||||
// httpPprofConfig is the block with pprof HTTP configuration.
|
||||
type httpPprofConfig struct {
|
||||
// Port for the profiling handler.
|
||||
Port uint16 `yaml:"port"`
|
||||
|
||||
// Enabled defines if the profiling handler is enabled.
|
||||
Enabled bool `yaml:"enabled"`
|
||||
}
|
||||
|
||||
// doHConfig is the block with DNS-over-HTTPS configuration.
|
||||
type doHConfig struct {
|
||||
// Routes is the list of HTTP route patterns for DoH requests. Default
|
||||
// routes are:
|
||||
// - "GET /dns-query"
|
||||
// - "POST /dns-query"
|
||||
// - "GET /dns-query/{ClientID}"
|
||||
// - "POST /dns-query/{ClientID}"
|
||||
//
|
||||
// TODO(d.kolyshev): Validate.
|
||||
// TODO(d.kolyshev): Since we have multiple muxes now serving on one
|
||||
// address, the user can register e.g. GET /control/status.
|
||||
Routes []string `yaml:"routes"`
|
||||
|
||||
// InsecureEnabled allows DoH queries via unencrypted HTTP.
|
||||
InsecureEnabled bool `yaml:"insecure_enabled"`
|
||||
}
|
||||
|
||||
// tlsConfigSettings is the TLS configuration for DNS-over-TLS, DNS-over-QUIC,
|
||||
// and HTTPS. When adding new properties, update the conversion functions
|
||||
// [confFromTLSSettings] and [confToTLSSettings] as necessary.
|
||||
@@ -309,14 +264,8 @@ const (
|
||||
var config = &configuration{
|
||||
AuthAttempts: 5,
|
||||
AuthBlockMin: 15,
|
||||
HTTPConfig: httpConfig{
|
||||
Address: netip.AddrPortFrom(netip.IPv4Unspecified(), 3000),
|
||||
SessionTTL: timeutil.Duration(30 * timeutil.Day),
|
||||
Pprof: &httpPprofConfig{
|
||||
Enabled: false,
|
||||
Port: 6060,
|
||||
},
|
||||
DoH: &doHConfig{
|
||||
HTTPConfig: configmgr.HTTPConfig{
|
||||
DoH: &configmgr.DoHConfig{
|
||||
Routes: []string{
|
||||
"GET /dns-query",
|
||||
"POST /dns-query",
|
||||
@@ -325,6 +274,12 @@ var config = &configuration{
|
||||
},
|
||||
InsecureEnabled: false,
|
||||
},
|
||||
Pprof: &configmgr.HTTPPprofConfig{
|
||||
Port: 6060,
|
||||
Enabled: false,
|
||||
},
|
||||
Address: netip.AddrPortFrom(netip.IPv4Unspecified(), 3000),
|
||||
SessionTTL: timeutil.Duration(30 * timeutil.Day),
|
||||
},
|
||||
DNS: configmgr.DNSConfig{
|
||||
EDNSClientSubnet: &configmgr.EDNSClientSubnet{
|
||||
@@ -658,6 +613,10 @@ func validateConfig(ctx context.Context, l *slog.Logger, fileData []byte) (err e
|
||||
config.Filtering.FiltersUpdateIntervalHours = 24
|
||||
}
|
||||
|
||||
if err = config.HTTPConfig.Validate(); err != nil {
|
||||
return fmt.Errorf("validating http config: %w", err)
|
||||
}
|
||||
|
||||
if len(config.Users) == 0 {
|
||||
l.WarnContext(ctx, "no users in the configuration file; authentication is disabled")
|
||||
}
|
||||
|
||||
@@ -241,7 +241,7 @@ func ipsToUDPAddrs(ips []netip.Addr, port uint16) (udpAddrs []*net.UDPAddr) {
|
||||
func newServerConfig(
|
||||
dnsConf *configmgr.DNSConfig,
|
||||
clientSrcConf *clientSourcesConfig,
|
||||
dohConf *doHConfig,
|
||||
dohConf *configmgr.DoHConfig,
|
||||
tlsManager aghtls.Manager,
|
||||
dohAddr netip.AddrPort,
|
||||
httpReg aghhttp.Registrar,
|
||||
|
||||
Reference in New Issue
Block a user