From 36ffcf7d22af56af0803ada6bff8718f37b9eab2 Mon Sep 17 00:00:00 2001 From: Simon Zolin Date: Fri, 24 May 2019 14:49:26 +0300 Subject: [PATCH 1/5] + dnsforward: add access settings for blocking DNS requests Block by client IP or target domain name. --- dnsforward/dnsforward.go | 114 +++++++++++++++++++++++++++++++++- dnsforward/dnsforward_test.go | 83 ++++++++++++++++++++++--- 2 files changed, 186 insertions(+), 11 deletions(-) diff --git a/dnsforward/dnsforward.go b/dnsforward/dnsforward.go index bc8ed460..6db87c7a 100644 --- a/dnsforward/dnsforward.go +++ b/dnsforward/dnsforward.go @@ -43,6 +43,12 @@ type Server struct { stats *stats // General server statistics once sync.Once + AllowedClients map[string]bool // IP addresses of whitelist clients + DisallowedClients map[string]bool // IP addresses of clients that should be blocked + AllowedClientsIPNet []net.IPNet // CIDRs of whitelist clients + DisallowedClientsIPNet []net.IPNet // CIDRs of clients that should be blocked + BlockedHosts map[string]bool // hosts that should be blocked + sync.RWMutex conf ServerConfig } @@ -70,6 +76,10 @@ type FilteringConfig struct { BootstrapDNS []string `yaml:"bootstrap_dns"` // a list of bootstrap DNS for DoH and DoT (plain DNS only) AllServers bool `yaml:"all_servers"` // if true, parallel queries to all configured upstream servers are enabled + AllowedClients []string `yaml:"allowed_clients"` // IP addresses of whitelist clients + DisallowedClients []string `yaml:"disallowed_clients"` // IP addresses of clients that should be blocked + BlockedHosts []string `yaml:"blocked_hosts"` // hosts that should be blocked + dnsfilter.Config `yaml:",inline"` } @@ -121,6 +131,34 @@ func (s *Server) Start(config *ServerConfig) error { return s.startInternal(config) } +func convertArrayToMap(dst *map[string]bool, src []string) { + *dst = make(map[string]bool) + for _, s := range src { + (*dst)[s] = true + } +} + +// Split array of IP or CIDR into 2 containers for fast search +func processIPCIDRArray(dst *map[string]bool, dstIPNet *[]net.IPNet, src []string) error { + *dst = make(map[string]bool) + + for _, s := range src { + ip := net.ParseIP(s) + if ip != nil { + (*dst)[s] = true + continue + } + + _, ipnet, err := net.ParseCIDR(s) + if err != nil { + return err + } + *dstIPNet = append(*dstIPNet, *ipnet) + } + + return nil +} + // startInternal starts without locking func (s *Server) startInternal(config *ServerConfig) error { if config != nil { @@ -167,10 +205,23 @@ func (s *Server) startInternal(config *ServerConfig) error { CacheEnabled: true, Upstreams: s.conf.Upstreams, DomainsReservedUpstreams: s.conf.DomainsReservedUpstreams, - Handler: s.handleDNSRequest, + BeforeRequestHandler: s.beforeRequestHandler, + RequestHandler: s.handleDNSRequest, AllServers: s.conf.AllServers, } + err = processIPCIDRArray(&s.AllowedClients, &s.AllowedClientsIPNet, s.conf.AllowedClients) + if err != nil { + return err + } + + err = processIPCIDRArray(&s.DisallowedClients, &s.DisallowedClientsIPNet, s.conf.DisallowedClients) + if err != nil { + return err + } + + convertArrayToMap(&s.BlockedHosts, s.conf.BlockedHosts) + if s.conf.TLSListenAddr != nil && s.conf.CertificateChain != "" && s.conf.PrivateKey != "" { proxyConfig.TLSListenAddr = s.conf.TLSListenAddr keypair, err := tls.X509KeyPair([]byte(s.conf.CertificateChain), []byte(s.conf.PrivateKey)) @@ -321,6 +372,67 @@ func (s *Server) GetStatsHistory(timeUnit time.Duration, startTime time.Time, en return s.stats.getStatsHistory(timeUnit, startTime, endTime) } +// Return TRUE if this client should be blocked +func (s *Server) isBlockedIP(ip string) bool { + if len(s.AllowedClients) != 0 || len(s.AllowedClientsIPNet) != 0 { + _, ok := s.AllowedClients[ip] + if ok { + return false + } + + if len(s.AllowedClientsIPNet) != 0 { + ipAddr := net.ParseIP(ip) + for _, ipnet := range s.AllowedClientsIPNet { + if ipnet.Contains(ipAddr) { + return false + } + } + } + + return true + } + + _, ok := s.DisallowedClients[ip] + if ok { + return true + } + + if len(s.DisallowedClientsIPNet) != 0 { + ipAddr := net.ParseIP(ip) + for _, ipnet := range s.DisallowedClientsIPNet { + if ipnet.Contains(ipAddr) { + return true + } + } + } + + return false +} + +// Return TRUE if this domain should be blocked +func (s *Server) isBlockedDomain(host string) bool { + _, ok := s.BlockedHosts[host] + return ok +} + +func (s *Server) beforeRequestHandler(p *proxy.Proxy, d *proxy.DNSContext) (bool, error) { + ip, _, _ := net.SplitHostPort(d.Addr.String()) + if s.isBlockedIP(ip) { + log.Tracef("Client IP %s is blocked by settings", ip) + return false, nil + } + + if len(d.Req.Question) == 1 { + host := strings.TrimSuffix(d.Req.Question[0].Name, ".") + if s.isBlockedDomain(host) { + log.Tracef("Domain %s is blocked by settings", host) + return false, nil + } + } + + return true, nil +} + // handleDNSRequest filters the incoming DNS requests and writes them to the query log func (s *Server) handleDNSRequest(p *proxy.Proxy, d *proxy.DNSContext) error { start := time.Now() diff --git a/dnsforward/dnsforward_test.go b/dnsforward/dnsforward_test.go index 1a38e96b..d643bf30 100644 --- a/dnsforward/dnsforward_test.go +++ b/dnsforward/dnsforward_test.go @@ -15,12 +15,10 @@ import ( "testing" "time" - "github.com/AdguardTeam/dnsproxy/proxy" - - "github.com/stretchr/testify/assert" - "github.com/AdguardTeam/AdGuardHome/dnsfilter" + "github.com/AdguardTeam/dnsproxy/proxy" "github.com/miekg/dns" + "github.com/stretchr/testify/assert" ) const ( @@ -460,12 +458,8 @@ func createTestServer(t *testing.T) *Server { s.conf.FilteringConfig.SafeBrowsingEnabled = true s.conf.Filters = make([]dnsfilter.Filter, 0) - rules := []string{ - "||nxdomain.example.org^", - "||null.example.org^", - "127.0.0.1 host.example.org", - } - filter := dnsfilter.Filter{ID: 1, Rules: rules} + rules := "||nxdomain.example.org^\n||null.example.org^\n127.0.0.1 host.example.org\n" + filter := dnsfilter.Filter{ID: 1, Data: []byte(rules)} s.conf.Filters = append(s.conf.Filters, filter) return s } @@ -628,3 +622,72 @@ func publicKey(priv interface{}) interface{} { return nil } } + +func TestIsBlockedIPAllowed(t *testing.T) { + s := createTestServer(t) + s.conf.AllowedClients = []string{"1.1.1.1", "2.2.0.0/16"} + + err := s.Start(nil) + defer removeDataDir(t) + if err != nil { + t.Fatalf("Failed to start server: %s", err) + } + + if s.isBlockedIP("1.1.1.1") { + t.Fatalf("isBlockedIP") + } + if !s.isBlockedIP("1.1.1.2") { + t.Fatalf("isBlockedIP") + } + if s.isBlockedIP("2.2.1.1") { + t.Fatalf("isBlockedIP") + } + if !s.isBlockedIP("2.3.1.1") { + t.Fatalf("isBlockedIP") + } +} + +func TestIsBlockedIPDisallowed(t *testing.T) { + s := createTestServer(t) + s.conf.DisallowedClients = []string{"1.1.1.1", "2.2.0.0/16"} + + err := s.Start(nil) + defer removeDataDir(t) + if err != nil { + t.Fatalf("Failed to start server: %s", err) + } + + if !s.isBlockedIP("1.1.1.1") { + t.Fatalf("isBlockedIP") + } + if s.isBlockedIP("1.1.1.2") { + t.Fatalf("isBlockedIP") + } + if !s.isBlockedIP("2.2.1.1") { + t.Fatalf("isBlockedIP") + } + if s.isBlockedIP("2.3.1.1") { + t.Fatalf("isBlockedIP") + } +} + +func TestIsBlockedIPBlockedDomain(t *testing.T) { + s := createTestServer(t) + s.conf.BlockedHosts = []string{"host1", "host2"} + + err := s.Start(nil) + defer removeDataDir(t) + if err != nil { + t.Fatalf("Failed to start server: %s", err) + } + + if !s.isBlockedDomain("host1") { + t.Fatalf("isBlockedDomain") + } + if !s.isBlockedDomain("host2") { + t.Fatalf("isBlockedDomain") + } + if s.isBlockedDomain("host3") { + t.Fatalf("isBlockedDomain") + } +} From d18c222b1aef8cb991211ae78ba187b588712091 Mon Sep 17 00:00:00 2001 From: Simon Zolin Date: Fri, 24 May 2019 17:15:19 +0300 Subject: [PATCH 2/5] + control: /access/list, /access/set handlers --- AGHTechDoc.md | 47 +++++++++++++++++++++++++ control.go | 3 ++ control_access.go | 87 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 137 insertions(+) create mode 100644 control_access.go diff --git a/AGHTechDoc.md b/AGHTechDoc.md index c6f615f3..f4e4d4c7 100644 --- a/AGHTechDoc.md +++ b/AGHTechDoc.md @@ -24,6 +24,9 @@ Contents: * "Enable DHCP" command * Static IP check/set * Add a static lease +* DNS access settings + * List access settings + * Set access settings ## First startup @@ -626,3 +629,47 @@ Response: Error response (Client not found): 400 + + +## DNS access settings + +There are low-level settings that can block undesired DNS requests. "Blocking" means not responding to request. + +There are 3 types of access settings: +* allowed_clients: Only these clients are allowed to make DNS requests. +* disallowed_clients: These clients are not allowed to make DNS requests. +* blocked_hosts: These hosts are not allowed to be resolved by a DNS request. + + +### List access settings + +Request: + + GET /control/access/list + +Response: + + 200 OK + + { + allowed_clients: ["127.0.0.1", ...] + disallowed_clients: ["127.0.0.1", ...] + blocked_hosts: ["host.com", ...] + } + + +### Set access settings + +Request: + + POST /control/access/set + + { + allowed_clients: ["127.0.0.1", ...] + disallowed_clients: ["127.0.0.1", ...] + blocked_hosts: ["host.com", ...] + } + +Response: + + 200 OK diff --git a/control.go b/control.go index 77970de2..e1113968 100644 --- a/control.go +++ b/control.go @@ -996,6 +996,9 @@ func registerControlHandlers() { http.HandleFunc("/control/dhcp/add_static_lease", postInstall(optionalAuth(ensurePOST(handleDHCPAddStaticLease)))) http.HandleFunc("/control/dhcp/remove_static_lease", postInstall(optionalAuth(ensurePOST(handleDHCPRemoveStaticLease)))) + http.HandleFunc("/control/access/list", postInstall(optionalAuth(ensureGET(handleAccessList)))) + http.HandleFunc("/control/access/set", postInstall(optionalAuth(ensurePOST(handleAccessSet)))) + RegisterTLSHandlers() RegisterClientsHandlers() diff --git a/control_access.go b/control_access.go new file mode 100644 index 00000000..0c6fd800 --- /dev/null +++ b/control_access.go @@ -0,0 +1,87 @@ +package main + +import ( + "encoding/json" + "net" + "net/http" + + "github.com/AdguardTeam/golibs/log" +) + +type accessListJSON struct { + AllowedClients []string `json:"allowed_clients"` + DisallowedClients []string `json:"disallowed_clients"` + BlockedHosts []string `json:"blocked_hosts"` +} + +func handleAccessList(w http.ResponseWriter, r *http.Request) { + log.Tracef("%s %v", r.Method, r.URL) + + controlLock.Lock() + j := accessListJSON{ + AllowedClients: config.DNS.AllowedClients, + DisallowedClients: config.DNS.DisallowedClients, + BlockedHosts: config.DNS.BlockedHosts, + } + controlLock.Unlock() + + w.Header().Set("Content-Type", "application/json") + err := json.NewEncoder(w).Encode(j) + if err != nil { + httpError(w, http.StatusInternalServerError, "json.Encode: %s", err) + return + } +} + +func checkIPCIDRArray(src []string) error { + for _, s := range src { + ip := net.ParseIP(s) + if ip != nil { + continue + } + + _, _, err := net.ParseCIDR(s) + if err != nil { + return err + } + } + + return nil +} + +func handleAccessSet(w http.ResponseWriter, r *http.Request) { + log.Tracef("%s %v", r.Method, r.URL) + + j := accessListJSON{} + err := json.NewDecoder(r.Body).Decode(&j) + if err != nil { + httpError(w, http.StatusBadRequest, "json.Decode: %s", err) + return + } + + err = checkIPCIDRArray(j.AllowedClients) + if err == nil { + err = checkIPCIDRArray(j.DisallowedClients) + } + if err != nil { + httpError(w, http.StatusBadRequest, "%s", err) + return + } + + config.Lock() + config.DNS.AllowedClients = j.AllowedClients + config.DNS.DisallowedClients = j.DisallowedClients + config.DNS.BlockedHosts = j.BlockedHosts + config.Unlock() + + log.Tracef("Update access lists: %d, %d, %d", + len(j.AllowedClients), len(j.DisallowedClients), len(j.BlockedHosts)) + + err = writeAllConfigsAndReloadDNS() + if err != nil { + httpError(w, http.StatusBadRequest, "%s", err) + return + } + + returnOK(w) +} From 821ad3edd90f9b998fa5d6918b30007cd9b91a25 Mon Sep 17 00:00:00 2001 From: Simon Zolin Date: Fri, 24 May 2019 17:51:13 +0300 Subject: [PATCH 3/5] * go.mod: update dnsproxy --- go.mod | 8 ++++---- go.sum | 34 ++++++++++++++++++++++++---------- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/go.mod b/go.mod index bb650707..a34a3ce6 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/AdguardTeam/AdGuardHome go 1.12 require ( - github.com/AdguardTeam/dnsproxy v0.12.0 + github.com/AdguardTeam/dnsproxy v0.14.0 github.com/AdguardTeam/golibs v0.1.3 github.com/AdguardTeam/urlfilter v0.3.0 github.com/NYTimes/gziphandler v1.1.1 @@ -14,11 +14,11 @@ require ( github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1 // indirect github.com/kardianos/service v0.0.0-20181115005516-4c239ee84e7b github.com/krolaw/dhcp4 v0.0.0-20180925202202-7cead472c414 - github.com/miekg/dns v1.1.1 + github.com/miekg/dns v1.1.8 github.com/sparrc/go-ping v0.0.0-20181106165434-ef3ab45e41b0 github.com/stretchr/testify v1.3.0 - golang.org/x/net v0.0.0-20190313220215-9f648a60d977 - golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a + golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6 + golang.org/x/sys v0.0.0-20190424160641-4347357a82bc gopkg.in/asaskevich/govalidator.v4 v4.0.0-20160518190739-766470278477 gopkg.in/yaml.v2 v2.2.1 ) diff --git a/go.sum b/go.sum index 5b625f87..4aeb833a 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/AdguardTeam/dnsproxy v0.12.0 h1:BPgv2PlH2u4xakFcaW4EqU3Visk1BNidrqGSgxe5Qzg= -github.com/AdguardTeam/dnsproxy v0.12.0/go.mod h1:lcZM2QPwcWGEL3pz8RYy06nQdbjj4pr+94H45jnVSHg= +github.com/AdguardTeam/dnsproxy v0.14.0 h1:ubB5031Oc8TfOWxRpYYDx0Lt181jiNGOfiOgEN5VJys= +github.com/AdguardTeam/dnsproxy v0.14.0/go.mod h1:50//JYIOMRnQnq0GQhvg516seqb5vjjyMIk+Z3RYy/s= github.com/AdguardTeam/golibs v0.1.2/go.mod h1:b0XkhgIcn2TxwX6C5AQMtpIFAgjPehNgxJErWkwA3ko= github.com/AdguardTeam/golibs v0.1.3 h1:hmapdTtMtIk3T8eQDwTOLdqZLGDKNKk9325uC8z12xg= github.com/AdguardTeam/golibs v0.1.3/go.mod h1:b0XkhgIcn2TxwX6C5AQMtpIFAgjPehNgxJErWkwA3ko= @@ -13,8 +13,8 @@ github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmH github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw= github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us= -github.com/ameshkov/dnscrypt v1.0.6 h1:55wfnNF8c4E3JXDNlwPl2Pbs7UPPIh+kI6KK3THqYS0= -github.com/ameshkov/dnscrypt v1.0.6/go.mod h1:ZvT9LaNaJfDNXKIbkYFf24HUgHuQR6MNT6nwVvN4jMQ= +github.com/ameshkov/dnscrypt v1.0.7 h1:7LS9wiC/6c00H3ZdZOlwQSYGTJvs12g5ui9D1VSZ2aQ= +github.com/ameshkov/dnscrypt v1.0.7/go.mod h1:rA74ASZ0j4JqPWaiN64hN97QXJ/zu5Kb2xgn295VzWQ= github.com/ameshkov/dnsstamps v1.0.1 h1:LhGvgWDzhNJh+kBQd/AfUlq1vfVe109huiXw4JhnPug= github.com/ameshkov/dnsstamps v1.0.1/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A= github.com/ameshkov/goproxy v0.0.0-20190328085534-e9f6fabc24d4/go.mod h1:tKA6C/1BQYejT7L6ZX0klDrqloYenYETv3BCk8xCbrQ= @@ -55,8 +55,8 @@ github.com/krolaw/dhcp4 v0.0.0-20180925202202-7cead472c414 h1:6wnYc2S/lVM7BvR32B github.com/krolaw/dhcp4 v0.0.0-20180925202202-7cead472c414/go.mod h1:0AqAH3ZogsCrvrtUpvc6EtVKbc3w6xwZhkvGLuqyi3o= github.com/markbates/oncer v0.0.0-20181014194634-05fccaae8fc4 h1:Mlji5gkcpzkqTROyE4ZxZ8hN7osunMb2RuGVrbvMvCc= github.com/markbates/oncer v0.0.0-20181014194634-05fccaae8fc4/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= -github.com/miekg/dns v1.1.1 h1:DVkblRdiScEnEr0LR9nTnEQqHYycjkXW9bOjd+2EL2o= -github.com/miekg/dns v1.1.1/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.8 h1:1QYRAKU3lN5cRfLCkPU08hwvLJFhvjP6MqNMmQz6ZVI= +github.com/miekg/dns v1.1.8/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= @@ -82,27 +82,41 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV golang.org/x/arch v0.0.0-20190312162104-788fe5ffcd8c/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190122013713-64072686203f h1:u1CmMhe3a44hy8VIgpInORnI01UVaUYheqR7x9BxT3c= -golang.org/x/crypto v0.0.0-20190122013713-64072686203f/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190422183909-d864b10871cd h1:sMHc2rZHuzQmrbVoSpt9HgerkXPyIeCSO6k0zUMGfFk= +golang.org/x/crypto v0.0.0-20190422183909-d864b10871cd/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190516052701-61b8692d9a5c/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/mobile v0.0.0-20190509164839-32b2708ab171/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/net v0.0.0-20181102091132-c10e9556a7bc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190119204137-ed066c81e75e h1:MDa3fSUp6MdYHouVmCCNz/zaH2a6CRcxY3VhT/K3C5Q= golang.org/x/net v0.0.0-20190119204137-ed066c81e75e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190313220215-9f648a60d977 h1:actzWV6iWn3GLqN8dZjzsB+CLt+gaV2+wsxroxiQI8I= golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6 h1:FP8hkuE6yUEaJnK7O2eTuejKWwW+Rhfj80dQ2JcKxCU= +golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190122071731-054c452bb702 h1:Lk4tbZFnlyPgV+sLgTw5yGfzrlOn9kx4vSombi2FFlY= golang.org/x/sys v0.0.0-20190122071731-054c452bb702/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190424160641-4347357a82bc h1:ULV59IIHLrmESQT7EqC104GKra36T4CqHvPeEqR6v8M= +golang.org/x/sys v0.0.0-20190424160641-4347357a82bc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1 h1:nsUiJHvm6yOoRozW9Tz0siNk9sHieLzR+w814Ihse3A= +golang.org/x/text v0.3.1/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/asaskevich/govalidator.v4 v4.0.0-20160518190739-766470278477 h1:5xUJw+lg4zao9W4HIDzlFbMYgSgtvNVHh00MEHvbGpQ= gopkg.in/asaskevich/govalidator.v4 v4.0.0-20160518190739-766470278477/go.mod h1:QDV1vrFSrowdoOba0UM8VJPUZONT7dnfdLsM+GG53Z8= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= From 02db488b30e57a6ffe79a00dd0e9d410d8fec888 Mon Sep 17 00:00:00 2001 From: Ildar Kamalov Date: Thu, 30 May 2019 17:26:19 +0300 Subject: [PATCH 4/5] + client: handle access settings --- client/src/__locales/en.json | 13 ++- client/src/actions/access.js | 45 +++++++++++ client/src/api/Api.js | 18 +++++ client/src/components/Settings/Access/Form.js | 80 +++++++++++++++++++ .../src/components/Settings/Access/index.js | 43 ++++++++++ client/src/components/Settings/Settings.css | 4 + .../src/components/Settings/Upstream/Form.js | 2 +- client/src/components/Settings/index.js | 5 +- client/src/containers/Settings.js | 8 ++ client/src/reducers/access.js | 43 ++++++++++ client/src/reducers/index.js | 2 + 11 files changed, 259 insertions(+), 4 deletions(-) create mode 100644 client/src/actions/access.js create mode 100644 client/src/components/Settings/Access/Form.js create mode 100644 client/src/components/Settings/Access/index.js create mode 100644 client/src/reducers/access.js diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json index 697dbbcd..d53593c5 100644 --- a/client/src/__locales/en.json +++ b/client/src/__locales/en.json @@ -298,5 +298,14 @@ "clients_not_found": "No clients found", "client_confirm_delete": "Are you sure you want to delete client \"{{key}}\"?", "auto_clients_title": "Clients (runtime)", - "auto_clients_desc": "Data on the clients that use AdGuard Home, but not stored in the configuration" -} \ No newline at end of file + "auto_clients_desc": "Data on the clients that use AdGuard Home, but not stored in the configuration", + "access_title": "Access settings", + "access_desc": "Here you can configure access rules for the AdGuard Home DNS server.", + "access_allowed_title": "Allowed clients", + "access_allowed_desc": "A list of CIDR or IP addresses. If configured, AdGuard Home will accept requests from these IP addresses only.", + "access_disallowed_title": "Disallowed clients", + "access_disallowed_desc": "A list of CIDR or IP addresses. If configured, AdGuard Home will drop requests from these IP addresses.", + "access_blocked_title": "Blocked domains", + "access_blocked_desc": "Don't confuse this with filters. AdGuard Home will drop DNS queries with these domains in query's question.", + "access_settings_saved": "Access settings successfully saved" +} diff --git a/client/src/actions/access.js b/client/src/actions/access.js new file mode 100644 index 00000000..b10062cb --- /dev/null +++ b/client/src/actions/access.js @@ -0,0 +1,45 @@ +import { createAction } from 'redux-actions'; +import Api from '../api/Api'; +import { addErrorToast, addSuccessToast } from './index'; +import { normalizeTextarea } from '../helpers/helpers'; + +const apiClient = new Api(); + +export const getAccessListRequest = createAction('GET_ACCESS_LIST_REQUEST'); +export const getAccessListFailure = createAction('GET_ACCESS_LIST_FAILURE'); +export const getAccessListSuccess = createAction('GET_ACCESS_LIST_SUCCESS'); + +export const getAccessList = () => async (dispatch) => { + dispatch(getAccessListRequest()); + try { + const data = await apiClient.getAccessList(); + dispatch(getAccessListSuccess(data)); + } catch (error) { + dispatch(addErrorToast({ error })); + dispatch(getAccessListFailure()); + } +}; + +export const setAccessListRequest = createAction('SET_ACCESS_LIST_REQUEST'); +export const setAccessListFailure = createAction('SET_ACCESS_LIST_FAILURE'); +export const setAccessListSuccess = createAction('SET_ACCESS_LIST_SUCCESS'); + +export const setAccessList = config => async (dispatch) => { + dispatch(setAccessListRequest()); + try { + const { allowed_clients, disallowed_clients, blocked_hosts } = config; + + const values = { + allowed_clients: (allowed_clients && normalizeTextarea(allowed_clients)) || [], + disallowed_clients: (disallowed_clients && normalizeTextarea(disallowed_clients)) || [], + blocked_hosts: (blocked_hosts && normalizeTextarea(blocked_hosts)) || [], + }; + + await apiClient.setAccessList(values); + dispatch(setAccessListSuccess()); + dispatch(addSuccessToast('access_settings_saved')); + } catch (error) { + dispatch(addErrorToast({ error })); + dispatch(setAccessListFailure()); + } +}; diff --git a/client/src/api/Api.js b/client/src/api/Api.js index 81bce7cf..1fa852f2 100644 --- a/client/src/api/Api.js +++ b/client/src/api/Api.js @@ -460,4 +460,22 @@ export default class Api { }; return this.makeRequest(path, method, parameters); } + + // DNS access settings + ACCESS_LIST = { path: 'access/list', method: 'GET' }; + ACCESS_SET = { path: 'access/set', method: 'POST' }; + + getAccessList() { + const { path, method } = this.ACCESS_LIST; + return this.makeRequest(path, method); + } + + setAccessList(config) { + const { path, method } = this.ACCESS_SET; + const parameters = { + data: config, + headers: { 'Content-Type': 'application/json' }, + }; + return this.makeRequest(path, method, parameters); + } } diff --git a/client/src/components/Settings/Access/Form.js b/client/src/components/Settings/Access/Form.js new file mode 100644 index 00000000..9096102d --- /dev/null +++ b/client/src/components/Settings/Access/Form.js @@ -0,0 +1,80 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Field, reduxForm } from 'redux-form'; +import { Trans, withNamespaces } from 'react-i18next'; +import flow from 'lodash/flow'; + +const Form = (props) => { + const { handleSubmit, submitting, invalid } = props; + + return ( +
+
+ +
+ access_allowed_desc +
+ +
+
+ +
+ access_disallowed_desc +
+ +
+
+ +
+ access_blocked_desc +
+ +
+
+
+ +
+
+
+ ); +}; + +Form.propTypes = { + handleSubmit: PropTypes.func, + submitting: PropTypes.bool, + invalid: PropTypes.bool, + initialValues: PropTypes.object, + t: PropTypes.func, +}; + +export default flow([withNamespaces(), reduxForm({ form: 'accessForm' })])(Form); diff --git a/client/src/components/Settings/Access/index.js b/client/src/components/Settings/Access/index.js new file mode 100644 index 00000000..77ccc265 --- /dev/null +++ b/client/src/components/Settings/Access/index.js @@ -0,0 +1,43 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { withNamespaces } from 'react-i18next'; + +import Form from './Form'; +import Card from '../../ui/Card'; + +class Access extends Component { + handleFormSubmit = (values) => { + this.props.setAccessList(values); + }; + + render() { + const { t, access } = this.props; + + const { + processing, + processingSet, + ...values + } = access; + + return ( + +
+ + ); + } +} + +Access.propTypes = { + access: PropTypes.object.isRequired, + setAccessList: PropTypes.func.isRequired, + t: PropTypes.func.isRequired, +}; + +export default withNamespaces()(Access); diff --git a/client/src/components/Settings/Settings.css b/client/src/components/Settings/Settings.css index 281330df..7e410a0c 100644 --- a/client/src/components/Settings/Settings.css +++ b/client/src/components/Settings/Settings.css @@ -63,6 +63,10 @@ font-weight: 700; } +.form__label--with-desc { + margin-bottom: 0; +} + .form__status { margin-top: 10px; font-size: 14px; diff --git a/client/src/components/Settings/Upstream/Form.js b/client/src/components/Settings/Upstream/Form.js index 8ef916f5..37990e42 100644 --- a/client/src/components/Settings/Upstream/Form.js +++ b/client/src/components/Settings/Upstream/Form.js @@ -62,7 +62,7 @@ let Form = (props) => {
-