Pull request: 4927-ddr-ip-san
Merge in DNS/adguard-home from 4927-ddr-ip-san to master
Updates #4927.
Squashed commit of the following:
commit 92e7498a7a9101648c4cfdf719adf4eb135fc903
Merge: f4770abf fa0fd90d
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Wed Nov 2 14:29:08 2022 +0300
Merge branch 'master' into 4927-ddr-ip-san
commit f4770abf98ea2c0db2f0c2ddb9509a29a06c9509
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Wed Nov 2 13:50:40 2022 +0300
dnsforward: imp logs
commit 8d71371365070e221e104ae20acc8312e840eff9
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Tue Nov 1 20:57:43 2022 +0300
all: imp code, docs
commit 9793820f2c581e0ffcb28a59677be5c8df0c43f3
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Tue Nov 1 19:37:39 2022 +0300
all: remember the cert props
This commit is contained in:
parent
fa0fd90ddd
commit
c139287787
|
@ -39,6 +39,8 @@ and this project adheres to
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
- DNS-over-TLS resolvers aren't returned anymore when the configured TLS
|
||||||
|
certificate contains no IP addresses ([#4927]).
|
||||||
- Responses with `SERVFAIL` code are now cached for at least 30 seconds.
|
- Responses with `SERVFAIL` code are now cached for at least 30 seconds.
|
||||||
|
|
||||||
### Deprecated
|
### Deprecated
|
||||||
|
@ -63,6 +65,7 @@ and this project adheres to
|
||||||
[#4898]: https://github.com/AdguardTeam/AdGuardHome/issues/4898
|
[#4898]: https://github.com/AdguardTeam/AdGuardHome/issues/4898
|
||||||
[#4916]: https://github.com/AdguardTeam/AdGuardHome/issues/4916
|
[#4916]: https://github.com/AdguardTeam/AdGuardHome/issues/4916
|
||||||
[#4925]: https://github.com/AdguardTeam/AdGuardHome/issues/4925
|
[#4925]: https://github.com/AdguardTeam/AdGuardHome/issues/4925
|
||||||
|
[#4927]: https://github.com/AdguardTeam/AdGuardHome/issues/4927
|
||||||
[#4942]: https://github.com/AdguardTeam/AdGuardHome/issues/4942
|
[#4942]: https://github.com/AdguardTeam/AdGuardHome/issues/4942
|
||||||
[#4986]: https://github.com/AdguardTeam/AdGuardHome/issues/4986
|
[#4986]: https://github.com/AdguardTeam/AdGuardHome/issues/4986
|
||||||
[#4990]: https://github.com/AdguardTeam/AdGuardHome/issues/4990
|
[#4990]: https://github.com/AdguardTeam/AdGuardHome/issues/4990
|
||||||
|
|
|
@ -3,7 +3,9 @@ package aghtls
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
)
|
)
|
||||||
|
@ -69,3 +71,19 @@ func SaferCipherSuites() (safe []uint16) {
|
||||||
|
|
||||||
return safe
|
return safe
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CertificateHasIP returns true if cert has at least a single IP address among
|
||||||
|
// its subjectAltNames.
|
||||||
|
func CertificateHasIP(cert *x509.Certificate) (ok bool) {
|
||||||
|
if len(cert.IPAddresses) > 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, name := range cert.DNSNames {
|
||||||
|
if _, err := netip.ParseAddr(name); err == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/aghtls"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||||
|
@ -146,13 +147,12 @@ type FilteringConfig struct {
|
||||||
|
|
||||||
// TLSConfig is the TLS configuration for HTTPS, DNS-over-HTTPS, and DNS-over-TLS
|
// TLSConfig is the TLS configuration for HTTPS, DNS-over-HTTPS, and DNS-over-TLS
|
||||||
type TLSConfig struct {
|
type TLSConfig struct {
|
||||||
|
cert tls.Certificate
|
||||||
|
|
||||||
TLSListenAddrs []*net.TCPAddr `yaml:"-" json:"-"`
|
TLSListenAddrs []*net.TCPAddr `yaml:"-" json:"-"`
|
||||||
QUICListenAddrs []*net.UDPAddr `yaml:"-" json:"-"`
|
QUICListenAddrs []*net.UDPAddr `yaml:"-" json:"-"`
|
||||||
HTTPSListenAddrs []*net.TCPAddr `yaml:"-" json:"-"`
|
HTTPSListenAddrs []*net.TCPAddr `yaml:"-" json:"-"`
|
||||||
|
|
||||||
// Reject connection if the client uses server name (in SNI) that doesn't match the certificate
|
|
||||||
StrictSNICheck bool `yaml:"strict_sni_check" json:"-"`
|
|
||||||
|
|
||||||
// PEM-encoded certificates chain
|
// PEM-encoded certificates chain
|
||||||
CertificateChain string `yaml:"certificate_chain" json:"certificate_chain"`
|
CertificateChain string `yaml:"certificate_chain" json:"certificate_chain"`
|
||||||
// PEM-encoded private key
|
// PEM-encoded private key
|
||||||
|
@ -168,13 +168,20 @@ type TLSConfig struct {
|
||||||
// used for ClientID checking and Discovery of Designated Resolvers (DDR).
|
// used for ClientID checking and Discovery of Designated Resolvers (DDR).
|
||||||
ServerName string `yaml:"-" json:"-"`
|
ServerName string `yaml:"-" json:"-"`
|
||||||
|
|
||||||
cert tls.Certificate
|
|
||||||
// DNS names from certificate (SAN) or CN value from Subject
|
// DNS names from certificate (SAN) or CN value from Subject
|
||||||
dnsNames []string
|
dnsNames []string
|
||||||
|
|
||||||
// OverrideTLSCiphers, when set, contains the names of the cipher suites to
|
// OverrideTLSCiphers, when set, contains the names of the cipher suites to
|
||||||
// use. If the slice is empty, the default safe suites are used.
|
// use. If the slice is empty, the default safe suites are used.
|
||||||
OverrideTLSCiphers []string `yaml:"override_tls_ciphers,omitempty" json:"-"`
|
OverrideTLSCiphers []string `yaml:"override_tls_ciphers,omitempty" json:"-"`
|
||||||
|
|
||||||
|
// StrictSNICheck controls if the connections with SNI mismatching the
|
||||||
|
// certificate's ones should be rejected.
|
||||||
|
StrictSNICheck bool `yaml:"strict_sni_check" json:"-"`
|
||||||
|
|
||||||
|
// hasIPAddrs is set during the certificate parsing and is true if the
|
||||||
|
// configured certificate contains at least a single IP address.
|
||||||
|
hasIPAddrs bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// DNSCryptConfig is the DNSCrypt server configuration struct.
|
// DNSCryptConfig is the DNSCrypt server configuration struct.
|
||||||
|
@ -459,7 +466,7 @@ func (s *Server) prepareIpsetListSettings() (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// prepareTLS - prepares TLS configuration for the DNS proxy
|
// prepareTLS - prepares TLS configuration for the DNS proxy
|
||||||
func (s *Server) prepareTLS(proxyConfig *proxy.Config) error {
|
func (s *Server) prepareTLS(proxyConfig *proxy.Config) (err error) {
|
||||||
if len(s.conf.CertificateChainData) == 0 || len(s.conf.PrivateKeyData) == 0 {
|
if len(s.conf.CertificateChainData) == 0 || len(s.conf.PrivateKeyData) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -478,25 +485,26 @@ func (s *Server) prepareTLS(proxyConfig *proxy.Config) error {
|
||||||
proxyConfig.QUICListenAddr,
|
proxyConfig.QUICListenAddr,
|
||||||
)
|
)
|
||||||
|
|
||||||
var err error
|
|
||||||
s.conf.cert, err = tls.X509KeyPair(s.conf.CertificateChainData, s.conf.PrivateKeyData)
|
s.conf.cert, err = tls.X509KeyPair(s.conf.CertificateChainData, s.conf.PrivateKeyData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to parse TLS keypair: %w", err)
|
return fmt.Errorf("failed to parse TLS keypair: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.conf.StrictSNICheck {
|
cert, err := x509.ParseCertificate(s.conf.cert.Certificate[0])
|
||||||
var x *x509.Certificate
|
|
||||||
x, err = x509.ParseCertificate(s.conf.cert.Certificate[0])
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("x509.ParseCertificate(): %w", err)
|
return fmt.Errorf("x509.ParseCertificate(): %w", err)
|
||||||
}
|
}
|
||||||
if len(x.DNSNames) != 0 {
|
|
||||||
s.conf.dnsNames = x.DNSNames
|
s.conf.hasIPAddrs = aghtls.CertificateHasIP(cert)
|
||||||
log.Debug("dns: using DNS names from certificate's SAN: %v", x.DNSNames)
|
|
||||||
|
if s.conf.StrictSNICheck {
|
||||||
|
if len(cert.DNSNames) != 0 {
|
||||||
|
s.conf.dnsNames = cert.DNSNames
|
||||||
|
log.Debug("dnsforward: using certificate's SAN as DNS names: %v", cert.DNSNames)
|
||||||
sort.Strings(s.conf.dnsNames)
|
sort.Strings(s.conf.dnsNames)
|
||||||
} else {
|
} else {
|
||||||
s.conf.dnsNames = append(s.conf.dnsNames, x.Subject.CommonName)
|
s.conf.dnsNames = append(s.conf.dnsNames, cert.Subject.CommonName)
|
||||||
log.Debug("dns: using DNS name from certificate's CN: %s", x.Subject.CommonName)
|
log.Debug("dnsforward: using certificate's CN as DNS name: %s", cert.Subject.CommonName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -259,21 +259,13 @@ func (s *Server) onDHCPLeaseChanged(flags int) {
|
||||||
//
|
//
|
||||||
// See https://www.ietf.org/archive/id/draft-ietf-add-ddr-10.html.
|
// See https://www.ietf.org/archive/id/draft-ietf-add-ddr-10.html.
|
||||||
func (s *Server) processDDRQuery(dctx *dnsContext) (rc resultCode) {
|
func (s *Server) processDDRQuery(dctx *dnsContext) (rc resultCode) {
|
||||||
pctx := dctx.proxyCtx
|
|
||||||
q := pctx.Req.Question[0]
|
|
||||||
|
|
||||||
if !s.conf.HandleDDR {
|
if !s.conf.HandleDDR {
|
||||||
return resultCodeSuccess
|
return resultCodeSuccess
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pctx := dctx.proxyCtx
|
||||||
|
q := pctx.Req.Question[0]
|
||||||
if q.Name == ddrHostFQDN {
|
if q.Name == ddrHostFQDN {
|
||||||
if s.dnsProxy.TLSListenAddr == nil && s.conf.HTTPSListenAddrs == nil &&
|
|
||||||
s.dnsProxy.QUICListenAddr == nil || q.Qtype != dns.TypeSVCB {
|
|
||||||
pctx.Res = s.makeResponse(pctx.Req)
|
|
||||||
|
|
||||||
return resultCodeFinish
|
|
||||||
}
|
|
||||||
|
|
||||||
pctx.Res = s.makeDDRResponse(pctx.Req)
|
pctx.Res = s.makeDDRResponse(pctx.Req)
|
||||||
|
|
||||||
return resultCodeFinish
|
return resultCodeFinish
|
||||||
|
@ -291,6 +283,10 @@ func (s *Server) processDDRQuery(dctx *dnsContext) (rc resultCode) {
|
||||||
// [draft standard]: https://www.ietf.org/archive/id/draft-ietf-add-ddr-10.html.
|
// [draft standard]: https://www.ietf.org/archive/id/draft-ietf-add-ddr-10.html.
|
||||||
func (s *Server) makeDDRResponse(req *dns.Msg) (resp *dns.Msg) {
|
func (s *Server) makeDDRResponse(req *dns.Msg) (resp *dns.Msg) {
|
||||||
resp = s.makeResponse(req)
|
resp = s.makeResponse(req)
|
||||||
|
if req.Question[0].Qtype != dns.TypeSVCB {
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
// TODO(e.burkov): Think about storing the FQDN version of the server's
|
// TODO(e.burkov): Think about storing the FQDN version of the server's
|
||||||
// name somewhere.
|
// name somewhere.
|
||||||
domainName := dns.Fqdn(s.conf.ServerName)
|
domainName := dns.Fqdn(s.conf.ServerName)
|
||||||
|
@ -312,6 +308,11 @@ func (s *Server) makeDDRResponse(req *dns.Msg) (resp *dns.Msg) {
|
||||||
resp.Answer = append(resp.Answer, ans)
|
resp.Answer = append(resp.Answer, ans)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.conf.hasIPAddrs {
|
||||||
|
// Only add DNS-over-TLS resolvers in case the certificate contains IP
|
||||||
|
// addresses.
|
||||||
|
//
|
||||||
|
// See https://github.com/AdguardTeam/AdGuardHome/issues/4927.
|
||||||
for _, addr := range s.dnsProxy.TLSListenAddr {
|
for _, addr := range s.dnsProxy.TLSListenAddr {
|
||||||
values := []dns.SVCBKeyValue{
|
values := []dns.SVCBKeyValue{
|
||||||
&dns.SVCBAlpn{Alpn: []string{"dot"}},
|
&dns.SVCBAlpn{Alpn: []string{"dot"}},
|
||||||
|
@ -327,6 +328,7 @@ func (s *Server) makeDDRResponse(req *dns.Msg) (resp *dns.Msg) {
|
||||||
|
|
||||||
resp.Answer = append(resp.Answer, ans)
|
resp.Answer = append(resp.Answer, ans)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, addr := range s.dnsProxy.QUICListenAddr {
|
for _, addr := range s.dnsProxy.QUICListenAddr {
|
||||||
values := []dns.SVCBKeyValue{
|
values := []dns.SVCBKeyValue{
|
||||||
|
|
|
@ -157,19 +157,9 @@ func TestServer_ProcessDDRQuery(t *testing.T) {
|
||||||
func prepareTestServer(t *testing.T, portDoH, portDoT, portDoQ int, ddrEnabled bool) (s *Server) {
|
func prepareTestServer(t *testing.T, portDoH, portDoT, portDoQ int, ddrEnabled bool) (s *Server) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
proxyConf := proxy.Config{}
|
|
||||||
|
|
||||||
if portDoT > 0 {
|
|
||||||
proxyConf.TLSListenAddr = []*net.TCPAddr{{Port: portDoT}}
|
|
||||||
}
|
|
||||||
|
|
||||||
if portDoQ > 0 {
|
|
||||||
proxyConf.QUICListenAddr = []*net.UDPAddr{{Port: portDoQ}}
|
|
||||||
}
|
|
||||||
|
|
||||||
s = &Server{
|
s = &Server{
|
||||||
dnsProxy: &proxy.Proxy{
|
dnsProxy: &proxy.Proxy{
|
||||||
Config: proxyConf,
|
Config: proxy.Config{},
|
||||||
},
|
},
|
||||||
conf: ServerConfig{
|
conf: ServerConfig{
|
||||||
FilteringConfig: FilteringConfig{
|
FilteringConfig: FilteringConfig{
|
||||||
|
@ -181,8 +171,17 @@ func prepareTestServer(t *testing.T, portDoH, portDoT, portDoQ int, ddrEnabled b
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if portDoT > 0 {
|
||||||
|
s.dnsProxy.TLSListenAddr = []*net.TCPAddr{{Port: portDoT}}
|
||||||
|
s.conf.hasIPAddrs = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if portDoQ > 0 {
|
||||||
|
s.dnsProxy.QUICListenAddr = []*net.UDPAddr{{Port: portDoQ}}
|
||||||
|
}
|
||||||
|
|
||||||
if portDoH > 0 {
|
if portDoH > 0 {
|
||||||
s.conf.TLSConfig.HTTPSListenAddrs = []*net.TCPAddr{{Port: portDoH}}
|
s.conf.HTTPSListenAddrs = []*net.TCPAddr{{Port: portDoH}}
|
||||||
}
|
}
|
||||||
|
|
||||||
return s
|
return s
|
||||||
|
|
|
@ -13,7 +13,6 @@ import (
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/netip"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -21,6 +20,7 @@ import (
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/aghtls"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
|
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
|
||||||
"github.com/AdguardTeam/golibs/errors"
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
|
@ -513,22 +513,6 @@ func validateCertChain(certs []*x509.Certificate, srvName string) (err error) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// certHasIP returns true if cert has at least a single IP address either in its
|
|
||||||
// DNS names or in the IP addresses section.
|
|
||||||
func certHasIP(cert *x509.Certificate) (ok bool) {
|
|
||||||
if len(cert.IPAddresses) > 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, name := range cert.DNSNames {
|
|
||||||
if _, err := netip.ParseAddr(name); err == nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseCertChain parses the certificate chain from raw data, and returns it.
|
// parseCertChain parses the certificate chain from raw data, and returns it.
|
||||||
// If ok is true, the returned error, if any, is not critical.
|
// If ok is true, the returned error, if any, is not critical.
|
||||||
func parseCertChain(chain []byte) (parsedCerts []*x509.Certificate, ok bool, err error) {
|
func parseCertChain(chain []byte) (parsedCerts []*x509.Certificate, ok bool, err error) {
|
||||||
|
@ -550,7 +534,7 @@ func parseCertChain(chain []byte) (parsedCerts []*x509.Certificate, ok bool, err
|
||||||
|
|
||||||
log.Info("tls: number of certs: %d", len(parsedCerts))
|
log.Info("tls: number of certs: %d", len(parsedCerts))
|
||||||
|
|
||||||
if !certHasIP(parsedCerts[0]) {
|
if !aghtls.CertificateHasIP(parsedCerts[0]) {
|
||||||
err = errors.Error(`certificate has no IP addresses` +
|
err = errors.Error(`certificate has no IP addresses` +
|
||||||
`, this may cause issues with DNS-over-TLS clients`)
|
`, this may cause issues with DNS-over-TLS clients`)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue