| 
									
										
										
										
											2019-08-09 12:40:28 +05:30
										 |  |  | package azure | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"context" | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							|  |  |  | 	"net" | 
					
						
							|  |  |  | 	"sync" | 
					
						
							|  |  |  | 	"time" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/coredns/coredns/plugin" | 
					
						
							|  |  |  | 	"github.com/coredns/coredns/plugin/file" | 
					
						
							|  |  |  | 	"github.com/coredns/coredns/plugin/pkg/fall" | 
					
						
							|  |  |  | 	"github.com/coredns/coredns/plugin/pkg/upstream" | 
					
						
							|  |  |  | 	"github.com/coredns/coredns/request" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	azuredns "github.com/Azure/azure-sdk-for-go/profiles/latest/dns/mgmt/dns" | 
					
						
							|  |  |  | 	"github.com/miekg/dns" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type zone struct { | 
					
						
							|  |  |  | 	id   string | 
					
						
							|  |  |  | 	z    *file.Zone | 
					
						
							|  |  |  | 	zone string | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type zones map[string][]*zone | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Azure is the core struct of the azure plugin. | 
					
						
							|  |  |  | type Azure struct { | 
					
						
							|  |  |  | 	zoneNames []string | 
					
						
							|  |  |  | 	client    azuredns.RecordSetsClient | 
					
						
							|  |  |  | 	upstream  *upstream.Upstream | 
					
						
							|  |  |  | 	zMu       sync.RWMutex | 
					
						
							|  |  |  | 	zones     zones | 
					
						
							| 
									
										
										
										
											2019-08-09 16:10:26 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	Next plugin.Handler | 
					
						
							|  |  |  | 	Fall fall.F | 
					
						
							| 
									
										
										
										
											2019-08-09 12:40:28 +05:30
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // New validates the input DNS zones and initializes the Azure struct. | 
					
						
							| 
									
										
										
										
											2019-08-09 16:10:26 +01:00
										 |  |  | func New(ctx context.Context, dnsClient azuredns.RecordSetsClient, keys map[string][]string) (*Azure, error) { | 
					
						
							| 
									
										
										
										
											2019-08-09 12:40:28 +05:30
										 |  |  | 	zones := make(map[string][]*zone, len(keys)) | 
					
						
							| 
									
										
										
										
											2019-08-09 16:10:26 +01:00
										 |  |  | 	names := make([]string, len(keys)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for resourceGroup, znames := range keys { | 
					
						
							|  |  |  | 		for _, name := range znames { | 
					
						
							|  |  |  | 			if _, err := dnsClient.ListAllByDNSZone(context.Background(), resourceGroup, name, nil, ""); err != nil { | 
					
						
							| 
									
										
										
										
											2019-08-09 12:40:28 +05:30
										 |  |  | 				return nil, err | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2019-08-09 16:10:26 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			fqdn := dns.Fqdn(name) | 
					
						
							|  |  |  | 			if _, ok := zones[fqdn]; !ok { | 
					
						
							|  |  |  | 				names = append(names, fqdn) | 
					
						
							| 
									
										
										
										
											2019-08-09 12:40:28 +05:30
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2019-08-09 16:10:26 +01:00
										 |  |  | 			zones[fqdn] = append(zones[fqdn], &zone{id: resourceGroup, zone: fqdn, z: file.NewZone(fqdn, "")}) | 
					
						
							| 
									
										
										
										
											2019-08-09 12:40:28 +05:30
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return &Azure{ | 
					
						
							|  |  |  | 		client:    dnsClient, | 
					
						
							|  |  |  | 		zones:     zones, | 
					
						
							| 
									
										
										
										
											2019-08-09 16:10:26 +01:00
										 |  |  | 		zoneNames: names, | 
					
						
							|  |  |  | 		upstream:  upstream.New(), | 
					
						
							| 
									
										
										
										
											2019-08-09 12:40:28 +05:30
										 |  |  | 	}, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Run updates the zone from azure. | 
					
						
							|  |  |  | func (h *Azure) Run(ctx context.Context) error { | 
					
						
							|  |  |  | 	if err := h.updateZones(ctx); err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	go func() { | 
					
						
							|  |  |  | 		for { | 
					
						
							|  |  |  | 			select { | 
					
						
							|  |  |  | 			case <-ctx.Done(): | 
					
						
							|  |  |  | 				log.Infof("Breaking out of Azure update loop: %v", ctx.Err()) | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			case <-time.After(1 * time.Minute): | 
					
						
							|  |  |  | 				if err := h.updateZones(ctx); err != nil && ctx.Err() == nil { | 
					
						
							|  |  |  | 					log.Errorf("Failed to update zones: %v", err) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	}() | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (h *Azure) updateZones(ctx context.Context) error { | 
					
						
							|  |  |  | 	errs := make([]string, 0) | 
					
						
							|  |  |  | 	for zName, z := range h.zones { | 
					
						
							|  |  |  | 		for i, hostedZone := range z { | 
					
						
							|  |  |  | 			recordSet, err := h.client.ListByDNSZone(ctx, hostedZone.id, hostedZone.zone, nil, "") | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				errs = append(errs, fmt.Sprintf("failed to list resource records for %v from azure: %v", hostedZone.zone, err)) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			newZ := updateZoneFromResourceSet(recordSet, zName) | 
					
						
							|  |  |  | 			newZ.Upstream = h.upstream | 
					
						
							|  |  |  | 			h.zMu.Lock() | 
					
						
							|  |  |  | 			(*z[i]).z = newZ | 
					
						
							|  |  |  | 			h.zMu.Unlock() | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if len(errs) != 0 { | 
					
						
							|  |  |  | 		return fmt.Errorf("errors updating zones: %v", errs) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func updateZoneFromResourceSet(recordSet azuredns.RecordSetListResultPage, zName string) *file.Zone { | 
					
						
							|  |  |  | 	newZ := file.NewZone(zName, "") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, result := range *(recordSet.Response().Value) { | 
					
						
							|  |  |  | 		resultFqdn := *(result.RecordSetProperties.Fqdn) | 
					
						
							|  |  |  | 		resultTTL := uint32(*(result.RecordSetProperties.TTL)) | 
					
						
							|  |  |  | 		if result.RecordSetProperties.ARecords != nil { | 
					
						
							|  |  |  | 			for _, A := range *(result.RecordSetProperties.ARecords) { | 
					
						
							| 
									
										
										
										
											2019-08-09 16:10:26 +01:00
										 |  |  | 				a := &dns.A{Hdr: dns.RR_Header{Name: resultFqdn, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: resultTTL}, | 
					
						
							| 
									
										
										
										
											2019-08-09 12:40:28 +05:30
										 |  |  | 					A: net.ParseIP(*(A.Ipv4Address))} | 
					
						
							| 
									
										
										
										
											2019-08-09 16:10:26 +01:00
										 |  |  | 				newZ.Insert(a) | 
					
						
							| 
									
										
										
										
											2019-08-09 12:40:28 +05:30
										 |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if result.RecordSetProperties.AaaaRecords != nil { | 
					
						
							|  |  |  | 			for _, AAAA := range *(result.RecordSetProperties.AaaaRecords) { | 
					
						
							| 
									
										
										
										
											2019-08-09 16:10:26 +01:00
										 |  |  | 				aaaa := &dns.AAAA{Hdr: dns.RR_Header{Name: resultFqdn, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: resultTTL}, | 
					
						
							| 
									
										
										
										
											2019-08-09 12:40:28 +05:30
										 |  |  | 					AAAA: net.ParseIP(*(AAAA.Ipv6Address))} | 
					
						
							| 
									
										
										
										
											2019-08-09 16:10:26 +01:00
										 |  |  | 				newZ.Insert(aaaa) | 
					
						
							| 
									
										
										
										
											2019-08-09 12:40:28 +05:30
										 |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if result.RecordSetProperties.MxRecords != nil { | 
					
						
							|  |  |  | 			for _, MX := range *(result.RecordSetProperties.MxRecords) { | 
					
						
							| 
									
										
										
										
											2019-08-09 16:10:26 +01:00
										 |  |  | 				mx := &dns.MX{Hdr: dns.RR_Header{Name: resultFqdn, Rrtype: dns.TypeMX, Class: dns.ClassINET, Ttl: resultTTL}, | 
					
						
							| 
									
										
										
										
											2019-08-09 12:40:28 +05:30
										 |  |  | 					Preference: uint16(*(MX.Preference)), | 
					
						
							|  |  |  | 					Mx:         dns.Fqdn(*(MX.Exchange))} | 
					
						
							| 
									
										
										
										
											2019-08-09 16:10:26 +01:00
										 |  |  | 				newZ.Insert(mx) | 
					
						
							| 
									
										
										
										
											2019-08-09 12:40:28 +05:30
										 |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if result.RecordSetProperties.PtrRecords != nil { | 
					
						
							|  |  |  | 			for _, PTR := range *(result.RecordSetProperties.PtrRecords) { | 
					
						
							| 
									
										
										
										
											2019-08-09 16:10:26 +01:00
										 |  |  | 				ptr := &dns.PTR{Hdr: dns.RR_Header{Name: resultFqdn, Rrtype: dns.TypePTR, Class: dns.ClassINET, Ttl: resultTTL}, | 
					
						
							| 
									
										
										
										
											2019-08-09 12:40:28 +05:30
										 |  |  | 					Ptr: dns.Fqdn(*(PTR.Ptrdname))} | 
					
						
							| 
									
										
										
										
											2019-08-09 16:10:26 +01:00
										 |  |  | 				newZ.Insert(ptr) | 
					
						
							| 
									
										
										
										
											2019-08-09 12:40:28 +05:30
										 |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if result.RecordSetProperties.SrvRecords != nil { | 
					
						
							|  |  |  | 			for _, SRV := range *(result.RecordSetProperties.SrvRecords) { | 
					
						
							| 
									
										
										
										
											2019-08-09 16:10:26 +01:00
										 |  |  | 				srv := &dns.SRV{Hdr: dns.RR_Header{Name: resultFqdn, Rrtype: dns.TypeSRV, Class: dns.ClassINET, Ttl: resultTTL}, | 
					
						
							| 
									
										
										
										
											2019-08-09 12:40:28 +05:30
										 |  |  | 					Priority: uint16(*(SRV.Priority)), | 
					
						
							|  |  |  | 					Weight:   uint16(*(SRV.Weight)), | 
					
						
							|  |  |  | 					Port:     uint16(*(SRV.Port)), | 
					
						
							|  |  |  | 					Target:   dns.Fqdn(*(SRV.Target))} | 
					
						
							| 
									
										
										
										
											2019-08-09 16:10:26 +01:00
										 |  |  | 				newZ.Insert(srv) | 
					
						
							| 
									
										
										
										
											2019-08-09 12:40:28 +05:30
										 |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if result.RecordSetProperties.TxtRecords != nil { | 
					
						
							|  |  |  | 			for _, TXT := range *(result.RecordSetProperties.TxtRecords) { | 
					
						
							| 
									
										
										
										
											2019-08-09 16:10:26 +01:00
										 |  |  | 				txt := &dns.TXT{Hdr: dns.RR_Header{Name: resultFqdn, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: resultTTL}, | 
					
						
							| 
									
										
										
										
											2019-08-09 12:40:28 +05:30
										 |  |  | 					Txt: *(TXT.Value)} | 
					
						
							| 
									
										
										
										
											2019-08-09 16:10:26 +01:00
										 |  |  | 				newZ.Insert(txt) | 
					
						
							| 
									
										
										
										
											2019-08-09 12:40:28 +05:30
										 |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if result.RecordSetProperties.NsRecords != nil { | 
					
						
							|  |  |  | 			for _, NS := range *(result.RecordSetProperties.NsRecords) { | 
					
						
							| 
									
										
										
										
											2019-08-09 16:10:26 +01:00
										 |  |  | 				ns := &dns.NS{Hdr: dns.RR_Header{Name: resultFqdn, Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: resultTTL}, | 
					
						
							| 
									
										
										
										
											2019-08-09 12:40:28 +05:30
										 |  |  | 					Ns: *(NS.Nsdname)} | 
					
						
							| 
									
										
										
										
											2019-08-09 16:10:26 +01:00
										 |  |  | 				newZ.Insert(ns) | 
					
						
							| 
									
										
										
										
											2019-08-09 12:40:28 +05:30
										 |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if result.RecordSetProperties.SoaRecord != nil { | 
					
						
							|  |  |  | 			SOA := result.RecordSetProperties.SoaRecord | 
					
						
							| 
									
										
										
										
											2019-08-09 16:10:26 +01:00
										 |  |  | 			soa := &dns.SOA{Hdr: dns.RR_Header{Name: resultFqdn, Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: resultTTL}, | 
					
						
							| 
									
										
										
										
											2019-08-09 12:40:28 +05:30
										 |  |  | 				Minttl:  uint32(*(SOA.MinimumTTL)), | 
					
						
							|  |  |  | 				Expire:  uint32(*(SOA.ExpireTime)), | 
					
						
							|  |  |  | 				Retry:   uint32(*(SOA.RetryTime)), | 
					
						
							|  |  |  | 				Refresh: uint32(*(SOA.RefreshTime)), | 
					
						
							|  |  |  | 				Serial:  uint32(*(SOA.SerialNumber)), | 
					
						
							|  |  |  | 				Mbox:    dns.Fqdn(*(SOA.Email)), | 
					
						
							|  |  |  | 				Ns:      *(SOA.Host)} | 
					
						
							| 
									
										
										
										
											2019-08-09 16:10:26 +01:00
										 |  |  | 			newZ.Insert(soa) | 
					
						
							| 
									
										
										
										
											2019-08-09 12:40:28 +05:30
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if result.RecordSetProperties.CnameRecord != nil { | 
					
						
							|  |  |  | 			CNAME := result.RecordSetProperties.CnameRecord.Cname | 
					
						
							| 
									
										
										
										
											2019-08-09 16:10:26 +01:00
										 |  |  | 			cname := &dns.CNAME{Hdr: dns.RR_Header{Name: resultFqdn, Rrtype: dns.TypeCNAME, Class: dns.ClassINET, Ttl: resultTTL}, | 
					
						
							| 
									
										
										
										
											2019-08-09 12:40:28 +05:30
										 |  |  | 				Target: dns.Fqdn(*CNAME)} | 
					
						
							| 
									
										
										
										
											2019-08-09 16:10:26 +01:00
										 |  |  | 			newZ.Insert(cname) | 
					
						
							| 
									
										
										
										
											2019-08-09 12:40:28 +05:30
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return newZ | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ServeDNS implements the plugin.Handler interface. | 
					
						
							|  |  |  | func (h *Azure) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { | 
					
						
							|  |  |  | 	state := request.Request{W: w, Req: r} | 
					
						
							|  |  |  | 	qname := state.Name() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-09 16:10:26 +01:00
										 |  |  | 	zone := plugin.Zones(h.zoneNames).Matches(qname) | 
					
						
							|  |  |  | 	if zone == "" { | 
					
						
							| 
									
										
										
										
											2019-08-09 12:40:28 +05:30
										 |  |  | 		return plugin.NextOrFailure(h.Name(), h.Next, ctx, w, r) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-09 16:10:26 +01:00
										 |  |  | 	zones, ok := h.zones[zone] // ok true if we are authoritive for the zone. | 
					
						
							|  |  |  | 	if !ok || zones == nil { | 
					
						
							| 
									
										
										
										
											2019-08-09 12:40:28 +05:30
										 |  |  | 		return dns.RcodeServerFailure, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	m := new(dns.Msg) | 
					
						
							|  |  |  | 	m.SetReply(r) | 
					
						
							|  |  |  | 	m.Authoritative = true | 
					
						
							|  |  |  | 	var result file.Result | 
					
						
							| 
									
										
										
										
											2019-08-09 16:10:26 +01:00
										 |  |  | 	for _, z := range zones { | 
					
						
							| 
									
										
										
										
											2019-08-09 12:40:28 +05:30
										 |  |  | 		h.zMu.RLock() | 
					
						
							| 
									
										
										
										
											2019-08-09 16:10:26 +01:00
										 |  |  | 		m.Answer, m.Ns, m.Extra, result = z.z.Lookup(ctx, state, qname) | 
					
						
							| 
									
										
										
										
											2019-08-09 12:40:28 +05:30
										 |  |  | 		h.zMu.RUnlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// record type exists for this name (NODATA). | 
					
						
							|  |  |  | 		if len(m.Answer) != 0 || result == file.NoData { | 
					
						
							|  |  |  | 			break | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if len(m.Answer) == 0 && result != file.NoData && h.Fall.Through(qname) { | 
					
						
							|  |  |  | 		return plugin.NextOrFailure(h.Name(), h.Next, ctx, w, r) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	switch result { | 
					
						
							|  |  |  | 	case file.Success: | 
					
						
							|  |  |  | 	case file.NoData: | 
					
						
							|  |  |  | 	case file.NameError: | 
					
						
							|  |  |  | 		m.Rcode = dns.RcodeNameError | 
					
						
							|  |  |  | 	case file.Delegation: | 
					
						
							|  |  |  | 		m.Authoritative = false | 
					
						
							|  |  |  | 	case file.ServerFailure: | 
					
						
							|  |  |  | 		return dns.RcodeServerFailure, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	w.WriteMsg(m) | 
					
						
							|  |  |  | 	return dns.RcodeSuccess, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Name implements plugin.Handler.Name. | 
					
						
							|  |  |  | func (h *Azure) Name() string { return "azure" } |