aboutsummaryrefslogtreecommitdiff
path: root/plugin/azure/azure.go
diff options
context:
space:
mode:
Diffstat (limited to 'plugin/azure/azure.go')
-rw-r--r--plugin/azure/azure.go293
1 files changed, 293 insertions, 0 deletions
diff --git a/plugin/azure/azure.go b/plugin/azure/azure.go
new file mode 100644
index 000000000..f3b2fad40
--- /dev/null
+++ b/plugin/azure/azure.go
@@ -0,0 +1,293 @@
+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 {
+ Next plugin.Handler
+ Fall fall.F
+ zoneNames []string
+ client azuredns.RecordSetsClient
+ upstream *upstream.Upstream
+ zMu sync.RWMutex
+ zones zones
+}
+
+// New validates the input DNS zones and initializes the Azure struct.
+func New(ctx context.Context, dnsClient azuredns.RecordSetsClient, keys map[string][]string, up *upstream.Upstream) (*Azure, error) {
+ zones := make(map[string][]*zone, len(keys))
+ zoneNames := make([]string, 0, len(keys))
+ for resourceGroup, inputZoneNames := range keys {
+ for _, zoneName := range inputZoneNames {
+ _, err := dnsClient.ListAllByDNSZone(context.Background(), resourceGroup, zoneName, nil, "")
+ if err != nil {
+ return nil, err
+ }
+ // Normalizing zoneName to make it fqdn if required.
+ zoneNameFQDN := dns.Fqdn(zoneName)
+ if _, ok := zones[zoneNameFQDN]; !ok {
+ zoneNames = append(zoneNames, zoneNameFQDN)
+ }
+ zones[zoneNameFQDN] = append(zones[zoneNameFQDN], &zone{id: resourceGroup, zone: zoneName, z: file.NewZone(zoneName, "")})
+ }
+ }
+ return &Azure{
+ client: dnsClient,
+ zones: zones,
+ zoneNames: zoneNames,
+ upstream: up,
+ }, 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) {
+ a := dns.A{
+ Hdr: dns.RR_Header{
+ Name: resultFqdn,
+ Rrtype: dns.TypeA,
+ Class: dns.ClassINET,
+ Ttl: resultTTL},
+ A: net.ParseIP(*(A.Ipv4Address))}
+ newZ.Insert(&a)
+ }
+ }
+
+ if result.RecordSetProperties.AaaaRecords != nil {
+ for _, AAAA := range *(result.RecordSetProperties.AaaaRecords) {
+ aaaa := dns.AAAA{
+ Hdr: dns.RR_Header{
+ Name: resultFqdn,
+ Rrtype: dns.TypeAAAA,
+ Class: dns.ClassINET,
+ Ttl: resultTTL},
+ AAAA: net.ParseIP(*(AAAA.Ipv6Address))}
+ newZ.Insert(&aaaa)
+ }
+ }
+
+ if result.RecordSetProperties.MxRecords != nil {
+ for _, MX := range *(result.RecordSetProperties.MxRecords) {
+ mx := dns.MX{
+ Hdr: dns.RR_Header{
+ Name: resultFqdn,
+ Rrtype: dns.TypeMX,
+ Class: dns.ClassINET,
+ Ttl: resultTTL},
+ Preference: uint16(*(MX.Preference)),
+ Mx: dns.Fqdn(*(MX.Exchange))}
+ newZ.Insert(&mx)
+ }
+ }
+
+ if result.RecordSetProperties.PtrRecords != nil {
+ for _, PTR := range *(result.RecordSetProperties.PtrRecords) {
+ ptr := dns.PTR{
+ Hdr: dns.RR_Header{
+ Name: resultFqdn,
+ Rrtype: dns.TypePTR,
+ Class: dns.ClassINET,
+ Ttl: resultTTL},
+ Ptr: dns.Fqdn(*(PTR.Ptrdname))}
+ newZ.Insert(&ptr)
+ }
+ }
+
+ if result.RecordSetProperties.SrvRecords != nil {
+ for _, SRV := range *(result.RecordSetProperties.SrvRecords) {
+ srv := dns.SRV{
+ Hdr: dns.RR_Header{
+ Name: resultFqdn,
+ Rrtype: dns.TypeSRV,
+ Class: dns.ClassINET,
+ Ttl: resultTTL},
+ Priority: uint16(*(SRV.Priority)),
+ Weight: uint16(*(SRV.Weight)),
+ Port: uint16(*(SRV.Port)),
+ Target: dns.Fqdn(*(SRV.Target))}
+ newZ.Insert(&srv)
+ }
+ }
+
+ if result.RecordSetProperties.TxtRecords != nil {
+ for _, TXT := range *(result.RecordSetProperties.TxtRecords) {
+ txt := dns.TXT{
+ Hdr: dns.RR_Header{
+ Name: resultFqdn,
+ Rrtype: dns.TypeTXT,
+ Class: dns.ClassINET,
+ Ttl: resultTTL},
+ Txt: *(TXT.Value)}
+ newZ.Insert(&txt)
+ }
+ }
+
+ if result.RecordSetProperties.NsRecords != nil {
+ for _, NS := range *(result.RecordSetProperties.NsRecords) {
+ ns := dns.NS{
+ Hdr: dns.RR_Header{
+ Name: resultFqdn,
+ Rrtype: dns.TypeNS,
+ Class: dns.ClassINET,
+ Ttl: resultTTL},
+ Ns: *(NS.Nsdname)}
+ newZ.Insert(&ns)
+ }
+ }
+
+ if result.RecordSetProperties.SoaRecord != nil {
+ SOA := result.RecordSetProperties.SoaRecord
+ soa := dns.SOA{
+ Hdr: dns.RR_Header{
+ Name: resultFqdn,
+ Rrtype: dns.TypeSOA,
+ Class: dns.ClassINET,
+ Ttl: resultTTL},
+ 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)}
+ newZ.Insert(&soa)
+ }
+
+ if result.RecordSetProperties.CnameRecord != nil {
+ CNAME := result.RecordSetProperties.CnameRecord.Cname
+ cname := dns.CNAME{
+ Hdr: dns.RR_Header{
+ Name: resultFqdn,
+ Rrtype: dns.TypeCNAME,
+ Class: dns.ClassINET,
+ Ttl: resultTTL},
+ Target: dns.Fqdn(*CNAME)}
+ newZ.Insert(&cname)
+ }
+ }
+ 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()
+
+ zName := plugin.Zones(h.zoneNames).Matches(qname)
+ if zName == "" {
+ return plugin.NextOrFailure(h.Name(), h.Next, ctx, w, r)
+ }
+
+ z, ok := h.zones[zName] // ok true if we are authoritive for the zone.
+ if !ok || z == nil {
+ return dns.RcodeServerFailure, nil
+ }
+
+ m := new(dns.Msg)
+ m.SetReply(r)
+ m.Authoritative = true
+ var result file.Result
+ for _, hostedZone := range z {
+ h.zMu.RLock()
+ m.Answer, m.Ns, m.Extra, result = hostedZone.z.Lookup(ctx, state, qname)
+ 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" }