diff --git a/README.md b/README.md index 52538073257d9848e3b1c3b91f95ef039a39af28..6594fbe95a714d477c0cfbfd94a33a3d195731b6 100644 --- a/README.md +++ b/README.md @@ -251,6 +251,11 @@ log configuration "enable": false, "protocol": "udp", "address": "localhost:514" + }, + "kafka": { + "enable": false, + "brokers": ["127.0.0.1:9092"], + "topic": "redins" } } ~~~ @@ -263,6 +268,7 @@ log configuration * path : log output file path * sentry : sentry hook configurations * syslog : syslog hook configurations +* kafka : kafka hook configurations ### rate limit rate limit connfiguration diff --git a/VERSION b/VERSION index 3eefcb9dd5b38e2c1dc061052455dd97bcd51e6c..ee90284c27f187a315f1267b063fa81b5b84f613 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.0.0 +1.0.4 diff --git a/config.json b/config.json index 7cdfabef499d01e5dbe9629e536f96d1e174b735..76796fb9eff23638e48103e8e6e5b8b866ba9404 100644 --- a/config.json +++ b/config.json @@ -7,8 +7,14 @@ }, { "ip": "127.0.0.1", - "port": 2053, - "protocol": "udp" + "port": 10853, + "protocol": "tcp", + "tls": { + "enable": true, + "cert_path": "", + "key_path": "", + "ca_path": "" + } } ], "handler": { @@ -32,7 +38,12 @@ "target": "file", "level": "info", "path": "/tmp/redins.log", - "time_format": "2006-01-02 15:04:05" + "time_format": "2006-01-02 15:04:05", + "kafka": { + "enable": true, + "topic": "redins", + "brokers": ["127.0.0.1:9092"] + } }, "upstream": [{ "ip": "1.1.1.1", @@ -51,7 +62,7 @@ "asn_db": "geoIsp.mmdb" }, "healthcheck": { - "enable": true, + "enable": false, "max_requests": 5, "max_pending_requests": 100, "update_interval": 600, diff --git a/handler/dns_types.go b/handler/dns_types.go index 3d25bf52bb54c65ace2a556c6ff113c2b3d41b35..2326cee424ecd6de606142dc161a97b1dad8bea5 100644 --- a/handler/dns_types.go +++ b/handler/dns_types.go @@ -29,9 +29,10 @@ type Record struct { } type ZoneKey struct { - PublicKey string `json:"public_key,omitmpty"` - PrivateKey string `json:"private_key,omitempty"` - Algorithm uint8 `json:"algorithm,omitempty"` + DnsKey *dns.DNSKEY + PrivateKey crypto.PrivateKey + KeyInception uint32 + KeyExpiration uint32 } type ZoneConfig struct { @@ -45,11 +46,9 @@ type Zone struct { Name string Config ZoneConfig Locations map[string]struct{} - DnsKey *dns.DNSKEY + ZSK *ZoneKey + KSK *ZoneKey DnsKeySig dns.RR - PrivateKey crypto.PrivateKey - KeyInception uint32 - KeyExpiration uint32 } type IP_RRSet struct { diff --git a/handler/dnssec.go b/handler/dnssec.go index 0a88c6ba0faf69fde318e54d43e769bdcf76e47e..275d5886e82d220969a1cc90b9127414521453b4 100644 --- a/handler/dnssec.go +++ b/handler/dnssec.go @@ -13,23 +13,72 @@ var ( NSecTypes = []uint16{dns.TypeRRSIG, dns.TypeNSEC} ) -func Sign(rrs []dns.RR, name string, zone *Zone, ttl uint32) (*dns.RRSIG, error) { +type rrset struct { + qname string + qtype uint16 +} + +func splitSets(rrs []dns.RR) map[rrset][]dns.RR { + m := make(map[rrset][]dns.RR) + + for _, r := range rrs { + if r.Header().Rrtype == dns.TypeRRSIG || r.Header().Rrtype == dns.TypeOPT { + continue + } + + if s, ok := m[rrset{r.Header().Name, r.Header().Rrtype}]; ok { + s = append(s, r) + m[rrset{r.Header().Name, r.Header().Rrtype}] = s + continue + } + + s := make([]dns.RR, 1, 3) + s[0] = r + m[rrset{r.Header().Name, r.Header().Rrtype}] = s + } + + if len(m) > 0 { + return m + } + return nil +} + +func Sign(rrs []dns.RR, qname string, record *Record) []dns.RR { + var res []dns.RR + sets := splitSets(rrs) + for _, set := range sets { + res = append(res, set...) + switch set[0].Header().Rrtype { + case dns.TypeRRSIG, dns.TypeOPT: + continue + case dns.TypeDNSKEY: + res = append(res, record.Zone.DnsKeySig) + default: + if rrsig, err := sign(set, qname, record.Zone.ZSK, set[0].Header().Ttl); err == nil { + res = append(res, rrsig) + } + } + } + return res +} + +func sign(rrs []dns.RR, name string, key *ZoneKey, ttl uint32) (*dns.RRSIG, error) { rrsig := &dns.RRSIG{ Hdr: dns.RR_Header{name, dns.TypeRRSIG, dns.ClassINET, ttl, 0}, - Inception: zone.KeyInception, - Expiration: zone.KeyExpiration, - KeyTag: zone.DnsKey.KeyTag(), - SignerName: zone.DnsKey.Hdr.Name, - Algorithm: zone.DnsKey.Algorithm, + Inception: key.KeyInception, + Expiration: key.KeyExpiration, + KeyTag: key.DnsKey.KeyTag(), + SignerName: key.DnsKey.Hdr.Name, + Algorithm: key.DnsKey.Algorithm, } switch rrsig.Algorithm { case dns.RSAMD5, dns.RSASHA1, dns.RSASHA1NSEC3SHA1, dns.RSASHA256, dns.RSASHA512: - if err := rrsig.Sign(zone.PrivateKey.(*rsa.PrivateKey), rrs); err != nil { + if err := rrsig.Sign(key.PrivateKey.(*rsa.PrivateKey), rrs); err != nil { logger.Default.Errorf("sign failed : %s", err) return nil, err } case dns.ECDSAP256SHA256, dns.ECDSAP384SHA384: - if err := rrsig.Sign(zone.PrivateKey.(*ecdsa.PrivateKey), rrs); err != nil { + if err := rrsig.Sign(key.PrivateKey.(*ecdsa.PrivateKey), rrs); err != nil { logger.Default.Errorf("sign failed : %s", err) return nil, err } @@ -42,16 +91,12 @@ func Sign(rrs []dns.RR, name string, zone *Zone, ttl uint32) (*dns.RRSIG, error) return rrsig, nil } -func NSec(name string, zone *Zone) ([]dns.RR, error) { +func NSec(name string, zone *Zone) dns.RR { nsec := &dns.NSEC{ Hdr: dns.RR_Header{name, dns.TypeNSEC, dns.ClassINET, zone.Config.SOA.MinTtl, 0}, NextDomain: "\\000." + name, TypeBitMap: NSecTypes, } - sigs, err := Sign([]dns.RR{nsec}, name, zone, zone.Config.SOA.MinTtl) - if err != nil { - return nil, err - } - return []dns.RR{nsec, sigs}, nil + return nsec } diff --git a/handler/dnssec_test.go b/handler/dnssec_test.go index 07b4782279a88d81820b944b363d2ff6193d6146..f879af0f7ed796cef35d727cbe87ee0ea3bf8a7d 100644 --- a/handler/dnssec_test.go +++ b/handler/dnssec_test.go @@ -61,7 +61,7 @@ var dnssecEntries = [][]string{ }, } -var dnssecKeyPriv = string( +var zskPriv = string( `Private-key-format: v1.3 Algorithm: 5 (RSASHA1) Modulus: oqwXm/EF8q6p5Rrj66Bbft+0Vk7Kj6TuvZp4nNl0htiT/8/92kIcri5gbxnV2v+p6jXYQI1Vx/vqP5cB0kPzjUQuJFVpm14fxOp89D6N0fPXR7xJ+SHs5nigHBIJdaP5 @@ -77,14 +77,40 @@ Publish: 20180717134704 Activate: 20180717134704 `) -var dnssecKeyPub = string("dnssec_test.com. IN DNSKEY 256 3 5 AwEAAaKsF5vxBfKuqeUa4+ugW37ftFZOyo+k7r2aeJzZdIbYk//P/dpC HK4uYG8Z1dr/qeo12ECNVcf76j+XAdJD841ELiRVaZteH8TqfPQ+jdHz 10e8Sfkh7OZ4oBwSCXWj+Q==") +var zskPub = string("dnssec_test.com. IN DNSKEY 256 3 5 AwEAAaKsF5vxBfKuqeUa4+ugW37ftFZOyo+k7r2aeJzZdIbYk//P/dpC HK4uYG8Z1dr/qeo12ECNVcf76j+XAdJD841ELiRVaZteH8TqfPQ+jdHz 10e8Sfkh7OZ4oBwSCXWj+Q==") var dnskeyQuery = test.Case{ Do: true, Qname: "dnssec_test.com", Qtype: dns.TypeDNSKEY, } +var kskPriv = string( + `Private-key-format: v1.3 +Algorithm: 5 (RSASHA1) +Modulus: 5WuOIP3GHID5Qmed6L+2ehBCkusTAXNv9uUfpzzTJHsA+bBesZSFsRNzMAV2drM7fApcL5IgNqrhb5twxu1/+cZj2Ld3PALbkENzn/erTl4A4uQdSWdkj8KnaLiJQPaT +PublicExponent: AQAB +PrivateExponent: BxiDhduzg/AtRXOE+8zqLO5R0M96gAH9BYripr6H3Un8prxgwWdRlz99wY95sYQrlNWr+4hhvikuOc9FjpXGg8E63iCNaZsVd/l8RvLGCtRPMtOEWhOecKe3kktHMUxp +Prime1: 9EWCZ3wwK2q7nsts12QuFGBTH/SOgHiaw9ieAn+mOA679BlIWXjeUoA5Hlj+ob31 +Prime2: 8G9/lMOO+xgwjU7lQ5teFGmmNb2JXB/nP3pWQURdy+Chnb8wrcHALJGW1G7DAMVn +Exponent1: jroSoQ7iQmwh5n3sQcpqVkOWLmTB4vUVUPvAD6uwXq7VSaKAMK88EC6VsVLErZMF +Exponent2: qIlPwgTOzf3n0rXSCXD4IpDoHFWO2o/Wdm2X1spIgWglgcEKK1JcFiG7u48ki/7T +Coefficient: QCGY0yr+kkmOZfUoL9YCCgau/xjyEPRZgiGTfIy0PtGGMDKfUswJ+1KWI9Jue3E5 +Created: 20190518113600 +Publish: 20190518113600 +Activate: 20190518113600 +`) + +var kskPub = string("dnssec_test.com. IN DNSKEY 257 3 5 AwEAAeVrjiD9xhyA+UJnnei/tnoQQpLrEwFzb/blH6c80yR7APmwXrGU hbETczAFdnazO3wKXC+SIDaq4W+bcMbtf/nGY9i3dzwC25BDc5/3q05e AOLkHUlnZI/Cp2i4iUD2kw==") + var dnssecTestCases = []test.Case{ + { + Qname: "dnssec_test.com.", Qtype: dns.TypeDNSKEY, + Answer: []dns.RR{ + test.DNSKEY("dnssec_test.com. 3600 IN DNSKEY 256 3 5 AwEAAaKsF5vxBfKuqeUa4+ugW37ftFZOyo+k7r2aeJzZdIbYk//P/dpCHK4uYG8Z1dr/qeo12ECNVcf76j+XAdJD841ELiRVaZteH8TqfPQ+jdHz10e8Sfkh7OZ4oBwSCXWj+Q=="), + test.DNSKEY("dnssec_test.com. 3600 IN DNSKEY 257 3 5 AwEAAeVrjiD9xhyA+UJnnei/tnoQQpLrEwFzb/blH6c80yR7APmwXrGUhbETczAFdnazO3wKXC+SIDaq4W+bcMbtf/nGY9i3dzwC25BDc5/3q05eAOLkHUlnZI/Cp2i4iUD2kw=="), + test.RRSIG("dnssec_test.com. 3600 IN RRSIG DNSKEY 5 2 3600 20190527081109 20190519051109 37456 dnssec_test.com. oVwtVEf9eOkcuSJlsH0OSBUvLOxgKM1pIAe7v717oRyCoyC+FIG5uGsdrZWhgklh/fpEmRdJQ+nHXKWT/son8zvxAoskuIIp49wwgvcS400IoHiyjIY0BHNTFPvsPdy0"), + }, + }, { Qname: "x.dnssec_test.com.", Qtype: dns.TypeA, Answer: []dns.RR{ @@ -280,12 +306,15 @@ func TestDNSSEC(t *testing.T) { } } h.Redis.Set("redins:zones:"+dnssecZone+":config", dnssecConfig) - h.Redis.Set("redins:zones:"+dnssecZone+":pub", dnssecKeyPub) - h.Redis.Set("redins:zones:"+dnssecZone+":priv", dnssecKeyPriv) + h.Redis.Set("redins:zones:"+dnssecZone+":zsk:pub", zskPub) + h.Redis.Set("redins:zones:"+dnssecZone+":zsk:priv", zskPriv) + h.Redis.Set("redins:zones:"+dnssecZone+":ksk:pub", kskPub) + h.Redis.Set("redins:zones:"+dnssecZone+":ksk:priv", kskPriv) h.Redis.SAdd("redins:zones", dnssecZone) h.LoadZones() - var dnskey dns.RR + var zsk dns.RR + var ksk dns.RR { r := dnskeyQuery.Msg() w := test.NewRecorder(&test.ResponseWriter{}) @@ -293,8 +322,18 @@ func TestDNSSEC(t *testing.T) { h.HandleRequest(&state) resp := w.Msg fmt.Println(resp.Answer) - dnskey = resp.Answer[0] + for _, answer := range resp.Answer { + if key, ok := answer.(*dns.DNSKEY); ok { + if key.Flags == 256 { + zsk = answer + } else if key.Flags == 257 { + ksk = answer + } + } + } } + // fmt.Println("zsk is ", zsk.String()) + // fmt.Println("ksk is ", ksk.String()) for i, tc0 := range dnssecTestCases { tc := test.Case{ @@ -316,11 +355,7 @@ func TestDNSSEC(t *testing.T) { state := request.Request{W: w, Req: r} h.HandleRequest(&state) resp := w.Msg - if i == 7 { - fmt.Println("here") - } - for zz, rrs := range [][]dns.RR{tc0.Answer, tc0.Ns, resp.Answer, resp.Ns} { - fmt.Println(zz) + for _, rrs := range [][]dns.RR{tc0.Answer, tc0.Ns, resp.Answer, resp.Ns} { s := 0 e := 1 for { @@ -328,10 +363,17 @@ func TestDNSSEC(t *testing.T) { break } if rrsig, ok := rrs[e].(*dns.RRSIG); ok { - fmt.Printf("s = %d, e = %d\n", s, e) - if rrsig.Verify(dnskey.(*dns.DNSKEY), rrs[s:e]) != nil { - fmt.Println("fail") - t.Fail() + //fmt.Printf("s = %d, e = %d\n", s, e) + if tc.Qtype == dns.TypeDNSKEY { + if rrsig.Verify(ksk.(*dns.DNSKEY), rrs[s:e]) != nil { + fmt.Println("fail") + t.Fail() + } + } else { + if rrsig.Verify(zsk.(*dns.DNSKEY), rrs[s:e]) != nil { + fmt.Println("fail") + t.Fail() + } } s = e + 1 e = s + 1 @@ -340,10 +382,11 @@ func TestDNSSEC(t *testing.T) { } } } - fmt.Println("dddd") - if test.SortAndCheck(resp, tc) != nil { + //fmt.Println("dddd") + if err := test.SortAndCheck(resp, tc); err != nil { + fmt.Println(err, resp.Answer, tc.Answer) t.Fail() } - fmt.Println("xxxx") + //fmt.Println("xxxx") } } diff --git a/handler/geoip.go b/handler/geoip.go index ded9041fc19b984c5754e41f8139bc0e69fb26ac..78ed37a3ce7ac6975a976ce3a69fd0f4afdf9a7f 100644 --- a/handler/geoip.go +++ b/handler/geoip.go @@ -48,7 +48,7 @@ func (g *GeoIp) GetSameCountry(sourceIp net.IP, ips []IP_RR, logData map[string] logger.Default.Error("getSameCountry failed") return ips } - logData["SourceCountry"] = sourceCountry + logData["source_country"] = sourceCountry var result []IP_RR if sourceCountry != "" { @@ -93,7 +93,7 @@ func (g *GeoIp) GetSameASN(sourceIp net.IP, ips []IP_RR, logData map[string]inte logger.Default.Error("getSameASN failed") return ips } - logData["SourceASN"] = sourceASN + logData["source_asn"] = sourceASN var result []IP_RR if sourceASN != 0 { diff --git a/handler/handler.go b/handler/handler.go index 48b6bad1803fd0b415fff1490b57aaf161c5ec7a..c6371303f5af580a18042a5af51b082f8cb15f0e 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -109,18 +109,18 @@ func (h *DnsRequestHandler) HandleRequest(state *request.Request) { requestStartTime := time.Now() logData := map[string]interface{}{ - "SourceIP": state.IP(), - "Record": state.Name(), - "Type": state.Type(), + "source_ip": state.IP(), + "record": state.Name(), + "type": state.Type(), } - logData["ClientSubnet"] = GetSourceSubnet(state) + logData["client_subnet"] = GetSourceSubnet(state) if h.Config.LogSourceLocation { sourceIP := GetSourceIp(state) _, _, sourceCountry, _ := h.geoip.GetGeoLocation(sourceIP) - logData["SourceCountry"] = sourceCountry + logData["source_country"] = sourceCountry sourceASN, _ := h.geoip.GetASN(sourceIP) - logData["SourceASN"] = sourceASN + logData["source_asn"] = sourceASN } auth := true @@ -132,12 +132,16 @@ func (h *DnsRequestHandler) HandleRequest(state *request.Request) { var authority []dns.RR record, localRes = h.FetchRecord(qname, logData) originalRecord := record - secured := state.Do() && record != nil && record.Zone.Config.DnsSec if record != nil { - logData["DomainId"] = record.Zone.Config.DomainId + logData["domain_uuid"] = record.Zone.Config.DomainId if qtype != dns.TypeCNAME { - // TODO: check for cname loop + count := 0 for { + if count >= 10 { + answers = []dns.RR{} + localRes = dns.RcodeServerFailure + break + } if localRes != dns.RcodeSuccess { break } @@ -145,10 +149,14 @@ func (h *DnsRequestHandler) HandleRequest(state *request.Request) { break } if !record.Zone.Config.CnameFlattening { - answers = AppendRR(answers, h.CNAME(qname, record), qname, record, secured) + answers = append(answers, h.CNAME(qname, record)...) + if h.Matches(record.CNAME.Host) != originalRecord.Zone.Name { + break + } qname = record.CNAME.Host } record, localRes = h.FetchRecord(record.CNAME.Host, logData) + count++ } } } @@ -162,7 +170,7 @@ func (h *DnsRequestHandler) HandleRequest(state *request.Request) { anameAnswer, anameRes := h.FetchRecord(record.ANAME.Location, logData) if anameRes == dns.RcodeSuccess { ips := h.Filter(state, &anameAnswer.A, logData) - answers = AppendRR(answers, h.A(qname, anameAnswer, ips), qname, record, secured) + answers = append(answers, h.A(qname, anameAnswer, ips)...) } else { upstreamAnswers, upstreamRes := h.upstream.Query(record.ANAME.Location, dns.TypeA) if upstreamRes == dns.RcodeSuccess { @@ -173,14 +181,14 @@ func (h *DnsRequestHandler) HandleRequest(state *request.Request) { anameRecord = append(anameRecord, &dns.A{A: a.A, Hdr: dns.RR_Header{Rrtype: dns.TypeA, Name: qname, Ttl: a.Hdr.Ttl, Class: dns.ClassINET, Rdlength: 0}}) } } - answers = AppendRR(answers, anameRecord, qname, record, secured) + answers = append(answers, anameRecord...) } res = upstreamRes } } } else { ips := h.Filter(state, &record.A, logData) - answers = AppendRR(answers, h.A(qname, record, ips), qname, record, secured) + answers = append(answers, h.A(qname, record, ips)...) } case dns.TypeAAAA: if len(record.AAAA.Data) == 0 { @@ -188,7 +196,7 @@ func (h *DnsRequestHandler) HandleRequest(state *request.Request) { anameAnswer, anameRes := h.FetchRecord(record.ANAME.Location, logData) if anameRes == dns.RcodeSuccess { ips := h.Filter(state, &anameAnswer.AAAA, logData) - answers = AppendRR(answers, h.AAAA(qname, anameAnswer, ips), qname, record, secured) + answers = append(answers, h.AAAA(qname, anameAnswer, ips)...) } else { upstreamAnswers, upstreamRes := h.upstream.Query(record.ANAME.Location, dns.TypeAAAA) if upstreamRes == dns.RcodeSuccess { @@ -199,39 +207,39 @@ func (h *DnsRequestHandler) HandleRequest(state *request.Request) { anameRecord = append(anameRecord, &dns.AAAA{AAAA: a.AAAA, Hdr: dns.RR_Header{Rrtype: dns.TypeAAAA, Name: qname, Ttl: a.Hdr.Ttl, Class: dns.ClassINET, Rdlength: 0}}) } } - answers = AppendRR(answers, anameRecord, qname, record, secured) + answers = append(answers, anameRecord...) } res = upstreamRes } } } else { ips := h.Filter(state, &record.AAAA, logData) - answers = AppendRR(answers, h.AAAA(qname, record, ips), qname, record, secured) + answers = append(answers, h.AAAA(qname, record, ips)...) } case dns.TypeCNAME: - answers = AppendRR(answers, h.CNAME(qname, record), qname, record, secured) + answers = append(answers, h.CNAME(qname, record)...) case dns.TypeTXT: - answers = AppendRR(answers, h.TXT(qname, record), qname, record, secured) + answers = append(answers, h.TXT(qname, record)...) case dns.TypeNS: - answers = AppendRR(answers, h.NS(qname, record), qname, record, secured) + answers = append(answers, h.NS(qname, record)...) case dns.TypeMX: - answers = AppendRR(answers, h.MX(qname, record), qname, record, secured) + answers = append(answers, h.MX(qname, record)...) case dns.TypeSRV: - answers = AppendRR(answers, h.SRV(qname, record), qname, record, secured) + answers = append(answers, h.SRV(qname, record)...) case dns.TypeCAA: caaRecord := h.FindCAA(record) if caaRecord != nil { - answers = AppendRR(answers, h.CAA(qname, caaRecord), qname, caaRecord, secured) + answers = append(answers, h.CAA(qname, caaRecord)...) } case dns.TypePTR: - answers = AppendRR(answers, h.PTR(qname, record), qname, record, secured) + answers = append(answers, h.PTR(qname, record)...) case dns.TypeTLSA: - answers = AppendRR(answers, h.TLSA(qname, record), qname, record, secured) + answers = append(answers, h.TLSA(qname, record)...) case dns.TypeSOA: - answers = AppendSOA(answers, record.Zone, secured) + answers = append(answers, record.Zone.Config.SOA.Data) case dns.TypeDNSKEY: - if secured { - answers = []dns.RR{record.Zone.DnsKey, record.Zone.DnsKeySig} + if record.Zone.Config.DnsSec { + answers = []dns.RR{record.Zone.ZSK.DnsKey, record.Zone.KSK.DnsKey} } default: answers = []dns.RR{} @@ -240,19 +248,14 @@ func (h *DnsRequestHandler) HandleRequest(state *request.Request) { } if len(answers) == 0 { if originalRecord.CNAME != nil { - answers = AppendRR(answers, h.CNAME(qname, record), qname, record, secured) + answers = append(answers, h.CNAME(qname, record)...) } else { - authority = AppendSOA(authority, originalRecord.Zone, secured) - authority = AppendNSEC(authority, originalRecord.Zone, qname, secured) + authority = append(authority, originalRecord.Zone.Config.SOA.Data) } } } else if localRes == dns.RcodeNameError { answers = []dns.RR{} - authority = AppendSOA(authority, originalRecord.Zone, secured) - if secured { - authority = AppendNSEC(authority, record.Zone, qname, secured) - res = dns.RcodeSuccess - } + authority = append(authority, originalRecord.Zone.Config.SOA.Data) } else if localRes == dns.RcodeNotAuth { if h.Config.UpstreamFallback { upstreamAnswers, upstreamRes := h.upstream.Query(dns.Fqdn(qname), qtype) @@ -263,12 +266,27 @@ func (h *DnsRequestHandler) HandleRequest(state *request.Request) { res = upstreamRes } else if originalRecord != nil && originalRecord.CNAME != nil { if len(answers) == 0 { - answers = AppendRR(answers, h.CNAME(qname, record), qname, record, secured) + answers = append(answers, h.CNAME(qname, originalRecord)...) + } + res = dns.RcodeSuccess + } + } + + if auth && state.Do() && originalRecord != nil && originalRecord.Zone.Config.DnsSec { + switch res { + case dns.RcodeSuccess: + if len(answers) == 0 { + authority = append(authority, NSec(qname, originalRecord.Zone)) } + case dns.RcodeNameError: + authority = append(authority, NSec(qname, originalRecord.Zone)) res = dns.RcodeSuccess } + answers = Sign(answers, qname, originalRecord) + authority = Sign(authority, qname, originalRecord) } + h.LogRequest(logData, requestStartTime, res) m := new(dns.Msg) m.SetReply(state.Req) @@ -311,8 +329,8 @@ func (h *DnsRequestHandler) Filter(request *request.Request, rrset *IP_RRSet, lo default: index = 0 } - logData["DestinationIp"] = ips[index].Ip.String() - logData["DestinationCountry"] = ips[index].Country + logData["destination_ip"] = ips[index].Ip.String() + logData["destination_country"] = ips[index].Country return []IP_RR{ips[index]} case "multi": @@ -332,9 +350,10 @@ func (h *DnsRequestHandler) Filter(request *request.Request, rrset *IP_RRSet, lo } func (h *DnsRequestHandler) LogRequest(data map[string]interface{}, startTime time.Time, responseCode int) { - data["ProcessTime"] = time.Since(startTime).Nanoseconds() / 1000000 - data["ResponseCode"] = responseCode - h.Logger.Log(data, "ar_dns_request") + data["process_time"] = time.Since(startTime).Nanoseconds() / 1000000 + data["response_code"] = responseCode + data["log_type"] = "request" + h.Logger.Log(data, "dns request") } func GetSourceIp(request *request.Request) net.IP { @@ -366,10 +385,9 @@ func GetSourceSubnet(request *request.Request) string { func reverseZone(zone string) string { x := strings.Split(zone, ".") var y string - for i := len(x) - 1; i > 0; i-- { + for i := len(x) - 1; i >= 0; i-- { y += x[i] + "." } - y += x[0] return y } @@ -390,10 +408,10 @@ func (h *DnsRequestHandler) FetchRecord(qname string, logData map[string]interfa cachedRecord, found := h.RecordCache.Get(qname) if found { logger.Default.Debug("cached") - logData["Cache"] = "HIT" + logData["cache"] = "HIT" return cachedRecord.(*Record), dns.RcodeSuccess } else { - logData["Cache"] = "MISS" + logData["cache"] = "MISS" record, res := h.GetRecord(qname) if res == dns.RcodeSuccess { h.RecordCache.Set(qname, record, time.Duration(h.Config.CacheTimeout)*time.Second) @@ -645,9 +663,8 @@ func split255(s string) []string { } func (h *DnsRequestHandler) Matches(qname string) string { - rzname := reverseZone(qname) - _, zname, ok := h.Zones.Root().LongestPrefix([]byte(rzname)) - if ok { + rname := reverseZone(qname) + if _, zname, ok := h.Zones.Root().LongestPrefix([]byte(rname)); ok { return zname.(string) } return "" @@ -671,7 +688,6 @@ func (h *DnsRequestHandler) GetRecord(qname string) (record *Record, rcode int) location := h.findLocation(qname, z) if len(location) == 0 { // empty, no results - logger.Default.Errorf("location not exists : %s", qname) return &Record{Name: qname, Zone: z}, dns.RcodeNameError } logger.Default.Debugf("location : %s", location) @@ -684,6 +700,37 @@ func (h *DnsRequestHandler) GetRecord(qname string) (record *Record, rcode int) return record, dns.RcodeSuccess } +func (h *DnsRequestHandler) loadKey(pub string, priv string) *ZoneKey { + pubStr, _ := h.Redis.Get(pub) + if pubStr == "" { + logger.Default.Errorf("key is not set : %s", pub) + return nil + } + privStr, _ := h.Redis.Get(priv) + if privStr == "" { + logger.Default.Errorf("key is not set : %s", priv) + return nil + } + privStr = strings.Replace(privStr, "\\n", "\n", -1) + zoneKey := new(ZoneKey) + if rr, err := dns.NewRR(pubStr); err == nil { + zoneKey.DnsKey = rr.(*dns.DNSKEY) + } else { + logger.Default.Errorf("cannot parse zone key : %s", err) + return nil + } + if pk, err := zoneKey.DnsKey.NewPrivateKey(privStr); err == nil { + zoneKey.PrivateKey = pk + } else { + logger.Default.Errorf("cannot create private key : %s", err) + return nil + } + now := time.Now() + zoneKey.KeyInception = uint32(now.Add(-3 * time.Hour).Unix()) + zoneKey.KeyExpiration = uint32(now.Add(8 * 24 * time.Hour).Unix()) + return zoneKey +} + func (h *DnsRequestHandler) LoadZone(zone string) *Zone { cachedZone, found := h.ZoneCache.Get(zone) if found { @@ -739,32 +786,24 @@ func (h *DnsRequestHandler) LoadZone(zone string) *Zone { z = func() *Zone { if z.Config.DnsSec { - pubStr, _ := h.Redis.Get("redins:zones:" + z.Name + ":pub") - privStr, _ := h.Redis.Get("redins:zones:" + z.Name + ":priv") - privStr = strings.Replace(privStr, "\\n", "\n", -1) - if pubStr == "" || privStr == "" { - logger.Default.Errorf("key is not set for zone %s", z.Name) + z.ZSK = h.loadKey("redins:zones:" + z.Name + ":zsk:pub", "redins:zones:" + z.Name + ":zsk:priv") + if z.ZSK == nil { z.Config.DnsSec = false return z } - if rr, err := dns.NewRR(pubStr); err == nil { - z.DnsKey = rr.(*dns.DNSKEY) - } else { - logger.Default.Errorf("cannot parse zone key : %s", err) + z.KSK = h.loadKey("redins:zones:" + z.Name + ":ksk:pub", "redins:zones:" + z.Name + ":ksk:priv") + if z.KSK == nil { z.Config.DnsSec = false return z } - if pk, err := z.DnsKey.NewPrivateKey(privStr); err == nil { - z.PrivateKey = pk - } else { - logger.Default.Errorf("cannot create private key : %s", err) - z.Config.DnsSec = false - return z + + z.ZSK.DnsKey.Flags = 256 + z.KSK.DnsKey.Flags = 257 + if z.ZSK.DnsKey.Hdr.Ttl != z.KSK.DnsKey.Hdr.Ttl { + z.ZSK.DnsKey.Hdr.Ttl = z.KSK.DnsKey.Hdr.Ttl } - now := time.Now() - z.KeyInception = uint32(now.Add(-3 * time.Hour).Unix()) - z.KeyExpiration = uint32(now.Add(8 * 24 * time.Hour).Unix()) - if rrsig, err := Sign([]dns.RR{z.DnsKey}, z.Name, z, 300); err == nil { + + if rrsig, err := sign([]dns.RR{z.ZSK.DnsKey, z.KSK.DnsKey}, z.Name, z.KSK, z.KSK.DnsKey.Hdr.Ttl); err == nil { z.DnsKeySig = rrsig } else { logger.Default.Errorf("cannot create RRSIG for DNSKEY : %s", err) @@ -863,39 +902,6 @@ func ChooseIp(ips []IP_RR, weighted bool) int { return index } -func AppendRR(answers []dns.RR, rrs []dns.RR, qname string, record *Record, secured bool) []dns.RR { - if len(rrs) == 0 { - return answers - } - answers = append(answers, rrs...) - if secured { - if rrsig, err := Sign(rrs, qname, record.Zone, rrs[0].Header().Ttl); err == nil { - answers = append(answers, rrsig) - } - } - return answers -} - -func AppendSOA(target []dns.RR, zone *Zone, secured bool) []dns.RR { - target = append(target, zone.Config.SOA.Data) - if secured { - if rrsig, err := Sign([]dns.RR{zone.Config.SOA.Data}, zone.Name, zone, zone.Config.SOA.Ttl); err == nil { - target = append(target, rrsig) - } - } - return target -} - -func AppendNSEC(target []dns.RR, zone *Zone, qname string, secured bool) []dns.RR { - if !secured { - return target - } - if nsec, err := NSec(qname, zone); err == nil { - target = append(target, nsec...) - } - return target -} - func (h *DnsRequestHandler) FindCAA(record *Record) *Record { zone := record.Zone currentRecord := record diff --git a/handler/handler_test.go b/handler/handler_test.go index 35d78d2ff195480e665b8019840d3e4a71f6cdb1..e5390103fd023995437524555ee485e245323907 100644 --- a/handler/handler_test.go +++ b/handler/handler_test.go @@ -1538,18 +1538,211 @@ func TestUpstreamCNAME(t *testing.T) { log.Println("3") t.Fail() } - hasCNAME := false - hasA := false - for _, rr := range resp.Answer { - switch rr.(type) { - case *dns.CNAME: - hasCNAME = true - case *dns.A: - hasA = true + cname := resp.Answer[0].(*dns.CNAME) + if cname.Target != "www.google.com." { + log.Println("4 ", cname) + t.Fail() + } + } +} + +var cnameOutsideZones = []string{"inside.cnm.", "outside.cnm."} +var cnameOutsideConfig = []string{ + `{"soa":{"ttl":300, "minttl":100, "mbox":"hostmaster.inside.cnm.","ns":"ns1.inside.cnm.","refresh":44,"retry":55,"expire":66}}`, + `{"soa":{"ttl":300, "minttl":100, "mbox":"hostmaster.outside.cnm.","ns":"ns1.outside.cnm.","refresh":44,"retry":55,"expire":66}}`, +} +var cnameOutsideEntries = [][][]string{ + { + {"upstream", + `{"cname":{"ttl":300, "host":"outside.cnm."}}`, + }, + }, + { + {"@", + `{"a":{"ttl":300, "records":[{"ip":"127.0.0.6"}]}}`, + }, + }, +} + +var cnameOutsideTests = []test.Case{ + { + Qname: "upstream.inside.cnm.", Qtype: dns.TypeA, + Answer: []dns.RR{ + test.CNAME("upstream.inside.cnm. 300 IN CNAME outside.cnm."), + }, + }, +} + +func TestCNameOutsideZone(t *testing.T) { + logger.Default = logger.NewLogger(&logger.LogConfig{}) + + h := NewHandler(&handlerTestConfig) + h.Redis.Del("*") + for i, zone := range cnameOutsideZones { + h.Redis.SAdd("redins:zones", zone) + for _, cmd := range cnameOutsideEntries[i] { + err := h.Redis.HSet("redins:zones:"+zone, cmd[0], cmd[1]) + if err != nil { + log.Printf("[ERROR] cannot connect to redis: %s", err) + t.Fail() } } - if !hasCNAME || !hasA { - log.Println("4") + } + h.LoadZones() + for j, tc := range cnameOutsideTests { + + r := tc.Msg() + w := test.NewRecorder(&test.ResponseWriter{}) + state := request.Request{W: w, Req: r} + h.HandleRequest(&state) + + resp := w.Msg + + fmt.Println(j, tc.Qname, tc.Answer, resp.Answer) + if err := test.SortAndCheck(resp, tc); err != nil { + t.Fail() + fmt.Println(err) + } + } +} + +var cnameLoopZone = "loop.cnm." + +var cnameLoopEntries = [][]string{ + {"w1", + `{"cname":{"ttl":300, "host":"w2.loop.cnm."}}`, + }, + {"w2", + `{"cname":{"ttl":300, "host":"w1.loop.cnm."}}`, + }, +} + +var cnameLoopTests = []test.Case{ + { + Qname: "w1.loop.cnm.", Qtype: dns.TypeA, + Rcode: dns.RcodeServerFailure, + }, +} + +func TestCNameLoop(t *testing.T) { + logger.Default = logger.NewLogger(&logger.LogConfig{}) + + h := NewHandler(&handlerTestConfig) + h.Redis.Del("*") + h.Redis.SAdd("redins:zones", cnameLoopZone) + for _, cmd := range cnameLoopEntries { + err := h.Redis.HSet("redins:zones:"+cnameLoopZone, cmd[0], cmd[1]) + if err != nil { + log.Printf("[ERROR] cannot connect to redis: %s", err) + t.Fail() + } + } + + h.LoadZones() + for j, tc := range cnameLoopTests { + + r := tc.Msg() + w := test.NewRecorder(&test.ResponseWriter{}) + state := request.Request{W: w, Req: r} + h.HandleRequest(&state) + + resp := w.Msg + + fmt.Println(j, tc.Qname, tc.Answer, resp.Answer) + if err := test.SortAndCheck(resp, tc); err != nil { + t.Fail() + fmt.Println(err) + } + } +} + +var findZoneZones = []string{"zone.zon.", "sub1.zone.zon."} + +var findZoneEntries = [][][]string{ + { + {"sub3.sub2", + `{"a":{"ttl":300, "records":[{"ip":"1.1.1.1"}]}}`, + }, + {"sub1", + `{"a":{"ttl":300, "records":[{"ip":"2.2.2.2"}]}}`, + }, + {"sub10", + `{"a":{"ttl":300, "records":[{"ip":"5.5.5.5"}]}}`, + }, + {"ub1", + `{"a":{"ttl":300, "records":[{"ip":"6.6.6.6"}]}}`, + }, + }, + { + {"@", + `{"a":{"ttl":300, "records":[{"ip":"3.3.3.3"}]}}`, + }, + {"sub2", + `{"a":{"ttl":300, "records":[{"ip":"4.4.4.4"}]}}`, + }, + }, +} + +var findZoneTests = []test.Case{ + { + Qname: "sub1.zone.zon.", Qtype: dns.TypeA, + Answer: []dns.RR{ + test.A("sub1.zone.zon. 300 IN A 3.3.3.3"), + }, + }, + { + Qname: "sub10.zone.zon.", Qtype: dns.TypeA, + Answer: []dns.RR{ + test.A("sub10.zone.zon. 300 IN A 5.5.5.5"), + }, + }, + { + Qname: "sub3.sub2.zone.zon.", Qtype: dns.TypeA, + Answer: []dns.RR{ + test.A("sub3.sub2.zone.zon. 300 IN A 1.1.1.1"), + }, + }, + { + Qname: "sub2.sub1.zone.zon.", Qtype: dns.TypeA, + Answer: []dns.RR{ + test.A("sub2.sub1.zone.zon. 300 IN A 4.4.4.4"), + }, + }, + { + Qname: "ub1.zone.zon.", Qtype: dns.TypeA, + Answer: []dns.RR{ + test.A("ub1.zone.zon. 300 IN A 6.6.6.6"), + }, + }, +} + +func TestFindZone(t *testing.T) { + logger.Default = logger.NewLogger(&logger.LogConfig{}) + + h := NewHandler(&handlerTestConfig) + h.Redis.Del("*") + for i, zone := range findZoneZones { + h.Redis.SAdd("redins:zones", zone) + for _, cmd := range findZoneEntries[i] { + err := h.Redis.HSet("redins:zones:"+zone, cmd[0], cmd[1]) + if err != nil { + log.Printf("[ERROR] cannot connect to redis: %s", err) + t.Fail() + } + } + h.Redis.Set("redins:zones:"+zone+":config", lookupConfig[i]) + h.LoadZones() + } + for _, tc := range findZoneTests { + r := tc.Msg() + w := test.NewRecorder(&test.ResponseWriter{}) + state := request.Request{W: w, Req: r} + h.HandleRequest(&state) + + resp := w.Msg + + if err := test.SortAndCheck(resp, tc); err != nil { + fmt.Println(tc.Qname, tc.Answer, resp.Answer) t.Fail() } } @@ -1605,6 +1798,10 @@ func TestSubscribeZones(t *testing.T) { }, } + rd := uperdis.NewRedis(&handlerTestConfig.Redis) + rd.SetConfig("notify-keyspace-events", "AK") + time.Sleep(time.Second) + h := NewHandler(&handlerTestConfig) h.Redis.Del("*") for _, cmd := range subsEntries { @@ -1644,3 +1841,58 @@ func TestSubscribeZones(t *testing.T) { t.Fail() } } + +var cnameNoAuthZone = "auth.zon." + +var cnameNoAuthEntries = [][]string{ + {"w1", + `{"cname":{"ttl":300, "host":"w2.auth.zon."}}`, + }, + {"w2", + `{"cname":{"ttl":300, "host":"noauth.zon."}}`, + }, +} + +var cnameNoAuthTests = []test.Case{ + { + Qname: "w1.auth.zon.", Qtype: dns.TypeA, + Answer: []dns.RR { + test.CNAME("w1.auth.zon. 300 IN CNAME w2.auth.zon."), + test.CNAME("w2.auth.zon. 300 IN CNAME noauth.zon."), + }, + Rcode: dns.RcodeSuccess, + }, +} + +func TestCNameNoAuth(t *testing.T) { + logger.Default = logger.NewLogger(&logger.LogConfig{}) + + h := NewHandler(&handlerTestConfig) + h.Redis.Del("*") + h.Redis.SAdd("redins:zones", cnameNoAuthZone) + h.Redis.Set("redins:zones:" + cnameNoAuthZone + ":config", "{\"cname_flattening\": false}") + for _, cmd := range cnameNoAuthEntries { + err := h.Redis.HSet("redins:zones:"+cnameNoAuthZone, cmd[0], cmd[1]) + if err != nil { + log.Printf("[ERROR] cannot connect to redis: %s", err) + t.Fail() + } + } + + h.LoadZones() + for j, tc := range cnameNoAuthTests { + + r := tc.Msg() + w := test.NewRecorder(&test.ResponseWriter{}) + state := request.Request{W: w, Req: r} + h.HandleRequest(&state) + + resp := w.Msg + + fmt.Println(j, tc.Qname, tc.Answer, resp.Answer) + if err := test.SortAndCheck(resp, tc); err != nil { + t.Fail() + fmt.Println(err) + } + } +} diff --git a/handler/healthcheck.go b/handler/healthcheck.go index 936da73bca756b2bf149b818763d26e4a81f0143..0b461c0871c6ff2a3cd32b33c97f453a911dea28 100644 --- a/handler/healthcheck.go +++ b/handler/healthcheck.go @@ -30,7 +30,7 @@ type HealthCheckItem struct { UpCount int `json:"up_count,omitempty"` DownCount int `json:"down_count,omitempty"` Enable bool `json:"enable,omitempty"` - DomainId string `json:"domain_uuid,omitempty"` + DomainId string `json:"domain_uuid, omitempty"` Host string `json:"-"` Ip string `json:"-"` Error error `json:"-"` @@ -309,10 +309,11 @@ func (h *Healthcheck) logHealthcheck(item *HealthCheckItem) { data := map[string]interface{}{ "ip": item.Ip, "port": item.Port, - "domain_name": item.Host, + "host": item.Host, "domain_uuid": item.DomainId, "uri": item.Uri, "status": item.Status, + "log_type": "healthcheck", } if item.Error == nil { data["error"] = "" @@ -320,7 +321,7 @@ func (h *Healthcheck) logHealthcheck(item *HealthCheckItem) { data["error"] = item.Error.Error() } - h.logger.Log(data, "ar_dns_healthcheck") + h.logger.Log(data, "dns healthcheck") } func statusDown(item *HealthCheckItem) { diff --git a/handler/server.go b/handler/server.go index 74419f52d50464648d06becb060b8fb00dc6ede6..9ed50e3c1cf3e019ee583402263e01b1473a98e2 100644 --- a/handler/server.go +++ b/handler/server.go @@ -4,12 +4,52 @@ import ( "strconv" "github.com/miekg/dns" + "crypto/tls" + "crypto/x509" + "io/ioutil" ) +type TlsConfig struct { + Enable bool `json:"enable"` + CertPath string `json:"cert_path"` + KeyPath string `json:"key_path"` + CaPath string `json:"ca_path"` +} + type ServerConfig struct { Ip string `json:"ip,omitempty"` Port int `json:"port,omitempty"` Protocol string `json:"protocol,omitempty"` + Tls TlsConfig `json:"tls,omitempty"` +} + +func loadRoots(caPath string) *x509.CertPool { + if caPath == "" { + return nil + } + + roots := x509.NewCertPool() + pem, err := ioutil.ReadFile(caPath) + if err != nil { + return nil + } + ok := roots.AppendCertsFromPEM(pem) + if !ok { + return nil + } + return roots +} + +func loadTlsConfig(cfg TlsConfig) *tls.Config { + root := loadRoots(cfg.CaPath) + if cfg.KeyPath == "" || cfg.CertPath == "" { + return &tls.Config{RootCAs: root} + } + cert, err := tls.LoadX509KeyPair(cfg.CertPath, cfg.KeyPath) + if err != nil { + return nil + } + return &tls.Config{Certificates: []tls.Certificate{cert}, RootCAs: root} } func NewServer(config []ServerConfig) []dns.Server { @@ -19,6 +59,9 @@ func NewServer(config []ServerConfig) []dns.Server { Addr: cfg.Ip + ":" + strconv.Itoa(cfg.Port), Net: cfg.Protocol, } + if cfg.Tls.Enable { + server.TLSConfig = loadTlsConfig(cfg.Tls) + } servers = append(servers, server) } return servers