added support for views
Build and Test / build-test-publish-coverage (push) Failing after 1m36s

This commit is contained in:
2025-02-10 23:09:08 -07:00
parent 3c7493c61d
commit 05c3b0ff0b
5 changed files with 232 additions and 81 deletions
+1 -1
View File
@@ -15,7 +15,7 @@ type APIRequestClient struct {
} }
type APIResultModel interface { type APIResultModel interface {
Record | Zone Record | Zone | View
} }
type APIManyResponse[T APIResultModel] struct { type APIManyResponse[T APIResultModel] struct {
+46
View File
@@ -0,0 +1,46 @@
package netbox
import (
"net/netip"
"net/url"
"strconv"
)
type Prefix struct {
ID int `json:"id"`
Prefix string `json:"prefix"`
}
type View struct {
ID int `json:"id"`
Name string `json:"name"`
Prefixes []Prefix `json:"prefixes"`
Default bool `json:"default_view`
}
func (v View) ContainsIP(IP netip.Addr) (bool, error) {
for _, prefixObj := range v.Prefixes {
prefix, err := netip.ParsePrefix(prefixObj.Prefix)
if err != nil {
return false, err
}
if prefix.Contains(IP) {
return true, nil
}
}
return false, nil
}
func urlViewID(netboxurl *url.URL, id int) *url.URL {
return netboxurl.JoinPath("views", "/", strconv.Itoa(id), "/")
}
func GetView(requestClient *APIRequestClient, id int) (View, error) {
requestUrl := urlViewID(requestClient.NetboxURL, id)
view, err := get[View](requestClient, requestUrl.String())
if err != nil {
return View{}, err
}
return view, nil
}
+4
View File
@@ -10,6 +10,10 @@ type Zone struct {
ID int `json:"id"` ID int `json:"id"`
Name string `json:"name"` Name string `json:"name"`
NameServers []SOAMName `json:"nameservers"` NameServers []SOAMName `json:"nameservers"`
View struct {
ID int `json:"id"`
Name string `json:"name"`
}
} }
type SOAMName struct { type SOAMName struct {
+145 -50
View File
@@ -1,8 +1,11 @@
package netboxdns package netboxdns
import ( import (
"fmt"
"net/netip"
"strings" "strings"
"github.com/coredns/coredns/plugin/pkg/log"
"github.com/doubleu-labs/coredns-netbox-plugin-dns/internal/netbox" "github.com/doubleu-labs/coredns-netbox-plugin-dns/internal/netbox"
"github.com/miekg/dns" "github.com/miekg/dns"
) )
@@ -24,88 +27,138 @@ type lookupResponse struct {
func (netboxdns *NetboxDNS) lookup( func (netboxdns *NetboxDNS) lookup(
name string, name string,
reqIP netip.Addr,
qtype uint16, qtype uint16,
family int, family int,
) (*lookupResponse, error) { ) (*lookupResponse, error) {
logger.Debugf("request for [%v] %v from %v\n", dns.TypeToString[qtype], name, reqIP)
nameTrimmed := strings.TrimSuffix(name, ".") nameTrimmed := strings.TrimSuffix(name, ".")
// check if zone exists on Netbox // check if zone exists on Netbox
zone, err := netboxdns.matchZone(nameTrimmed) zones, default_zone_index, err := netboxdns.matchZone(nameTrimmed, reqIP)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if zone == nil { if zones == nil {
logger.Debugf("no zone matching %q", name) logger.Debugf("no zone matching %q", name)
return &lookupResponse{LookupResult: lookupNameError}, nil return &lookupResponse{LookupResult: lookupNameError}, nil
} }
// var responses []*lookupResponse
var defaultResponse *lookupResponse
for i, zone := range zones {
is_zone_default := i == default_zone_index
// check if qname is for zone origin // check if qname is for zone origin
if nameTrimmed == zone.Name { if nameTrimmed == zone.Name {
originResponse, err := netboxdns.processOrigin(qtype, zone, family) originResponse, err := netboxdns.processOrigin(qtype, zone)
if err != nil { if err != nil {
return nil, err log.Debugf("Could not process origin for zone %v: %w", zone, err)
continue
} }
if originResponse != nil { if originResponse != nil {
logger.Debugf( logger.Debugf(
"found origin records for [%s] %q", "found origin records for [%s] %q in zone %v",
dns.TypeToString[qtype], dns.TypeToString[qtype],
name, name,
zone.Name,
) )
if is_zone_default {
return originResponse, nil return originResponse, nil
} else {
defaultResponse = originResponse
}
continue
} }
} }
// lookup exact request // lookup exact request
direct, err := netboxdns.lookupDirect(nameTrimmed, qtype, zone, family) direct, err := netboxdns.lookupDirect(nameTrimmed, qtype, zone)
if err != nil { if err != nil {
return nil, err log.Debugf("could not lookup exact request for %v in zone %v: %w", nameTrimmed, zone.Name, err)
continue
} }
if direct != nil { if direct != nil {
logger.Debugf( logger.Debugf(
"found records for [%s] %q", "found records for [%s] %q in zone %v",
dns.TypeToString[qtype], dns.TypeToString[qtype],
name, name,
zone.View.Name,
) )
if is_zone_default {
return direct, nil return direct, nil
} else {
defaultResponse = direct
}
continue
} }
// if no exact records exist for the request, check if the qname is a // if no exact records exist for the request, check if the qname is a
// delegate zone // delegate zone
delegate, err := netboxdns.lookupDelegate(nameTrimmed, zone, family) delegate, err := netboxdns.lookupDelegate(nameTrimmed, zone, qtype)
if err != nil { if err != nil {
return nil, err log.Debugf("could not lookup delegate for %v in zone %v: %w", nameTrimmed, zone.Name, err)
continue
} }
if delegate != nil { if delegate != nil {
logger.Debugf("found delegate zone records for %q", name) logger.Debugf("found delegate zone records for %q in zone %v", name, zone.Name)
// return delegate, nil
if is_zone_default {
return delegate, nil return delegate, nil
} else {
defaultResponse = delegate
} }
logger.Debugf("no records found for [%s] %q", dns.TypeToString[qtype], name) continue
return &lookupResponse{LookupResult: lookupNameError}, nil }
}
if defaultResponse != nil {
return defaultResponse, nil
} else {
log.Errorf("could not resolve any records for request %v", nameTrimmed)
return nil, fmt.Errorf("could not resolve any records for request %v ", nameTrimmed)
}
} }
func (netboxdns *NetboxDNS) matchZone(qname string) (*netbox.Zone, error) { func (netboxdns *NetboxDNS) matchZone(qname string, reqIP netip.Addr) ([]*netbox.Zone, int, error) {
managedZones, err := netbox.GetZones(netboxdns.requestClient) managedZones, err := netbox.GetZones(netboxdns.requestClient)
if err != nil { if err != nil {
return nil, err return nil, 0, err
} }
var out *netbox.Zone var out []*netbox.Zone
index_of_default := -1
for _, managedZone := range managedZones { for _, managedZone := range managedZones {
view, err := netbox.GetView(netboxdns.requestClient, managedZone.View.ID)
if err != nil {
return nil, 0, err
}
viewContainsIP, err := view.ContainsIP(reqIP)
if err != nil {
return nil, 0, err
}
if !viewContainsIP {
log.Debugf("view %v's configured prefixes don't match request IP %v", view.Name, reqIP.String())
continue
}
log.Debugf("view %v's configured prefixes match request IP %v", view.Name, reqIP.String())
if dns.IsSubDomain(managedZone.Name, qname) { if dns.IsSubDomain(managedZone.Name, qname) {
if out == nil { out = append(out, &managedZone)
out = &managedZone if view.Default {
if index_of_default != -1 {
log.Errorf("more than one default view configured for IP %v", reqIP.String())
return nil, 0, fmt.Errorf("more than one default view configured for IP %v", reqIP.String())
} }
if len(managedZone.Name) > len(out.Name) { index_of_default = len(out)
out = &managedZone
} }
} }
} }
return out, nil return out, index_of_default, nil
} }
func (netboxdns *NetboxDNS) processOrigin( func (netboxdns *NetboxDNS) processOrigin(
qtype uint16, qtype uint16,
zone *netbox.Zone, zone *netbox.Zone,
family int,
) (*lookupResponse, error) { ) (*lookupResponse, error) {
var queryType []string var queryType []string
switch qtype { switch qtype {
@@ -133,14 +186,14 @@ func (netboxdns *NetboxDNS) processOrigin(
} }
answer := filterRRByType(rrs, dns.TypeSOA) answer := filterRRByType(rrs, dns.TypeSOA)
ns := filterRRByType(rrs, dns.TypeNS) ns := filterRRByType(rrs, dns.TypeNS)
extraRecords, err := netboxdns.processExtra(ns, zone, family) extraRecords, err := netboxdns.processExtra(ns, zone, qtype)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if len(extraRecords) == 0 { if len(extraRecords) == 0 {
// if no A/AAAA records exist for the NS in the specified zone, check if // if no A/AAAA records exist for the NS in the specified zone, check if
// the server has records anywhere // the server has records anywhere
extraRecords, err = netboxdns.processExtra(ns, nil, family) extraRecords, err = netboxdns.processExtra(ns, nil, qtype)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -163,7 +216,7 @@ func (netboxdns *NetboxDNS) processOrigin(
func (netboxdns *NetboxDNS) processExtra( func (netboxdns *NetboxDNS) processExtra(
answer []dns.RR, answer []dns.RR,
zone *netbox.Zone, zone *netbox.Zone,
family int, qtype uint16,
) ([]netbox.Record, error) { ) ([]netbox.Record, error) {
var out []netbox.Record var out []netbox.Record
for _, rr := range answer { for _, rr := range answer {
@@ -181,18 +234,18 @@ func (netboxdns *NetboxDNS) processExtra(
if len(name) == 0 { if len(name) == 0 {
continue continue
} }
var reqType []string //var reqType []string
switch family { //switch qtype {
case 1: //case 1:
reqType = []string{"A"} // reqType = []string{"A"}
case 2: //case 2:
reqType = []string{"AAAA"} // reqType = []string{"AAAA"}
} //}
records, err := netbox.GetRecordsQuery( records, err := netbox.GetRecordsQuery(
netboxdns.requestClient, netboxdns.requestClient,
&netbox.RecordQuery{ &netbox.RecordQuery{
FQDN: strings.TrimSuffix(name, "."), FQDN: strings.TrimSuffix(name, "."),
Type: reqType, Type: []string{dns.TypeToString[qtype]},
Zone: zone, Zone: zone,
}, },
) )
@@ -208,12 +261,12 @@ func (netboxdns *NetboxDNS) lookupDirect(
qname string, qname string,
qtype uint16, qtype uint16,
zone *netbox.Zone, zone *netbox.Zone,
family int,
) (*lookupResponse, error) { ) (*lookupResponse, error) {
queryTypes := []string{dns.TypeToString[qtype]} queryTypes := []string{dns.TypeToString[qtype]}
if qtype == dns.TypeA || qtype == dns.TypeAAAA { if qtype == dns.TypeA || qtype == dns.TypeAAAA {
queryTypes = append(queryTypes, "CNAME") queryTypes = append(queryTypes, "CNAME")
} }
records, err := netbox.GetRecordsQuery( records, err := netbox.GetRecordsQuery(
netboxdns.requestClient, netboxdns.requestClient,
&netbox.RecordQuery{ &netbox.RecordQuery{
@@ -225,37 +278,79 @@ func (netboxdns *NetboxDNS) lookupDirect(
if err != nil { if err != nil {
return nil, err return nil, err
} }
// log.Debugf("%v", records)
if len(records) > 0 { // allRecords := append([]netbox.Record{}, records...)
answer, err := recordsToRR(records) var allRecords []netbox.Record
var newRecords []netbox.Record
for i := 0; ; i++ {
foundCNAME := false
// If record data end with ., pass as is
// If record data doesn't end with ., append the zone name
for i, record := range records {
// log.Debugf("%v", record)
if record.Type == "CNAME" {
foundCNAME = true
if record.Value[len(record.Value)-1:] != "." {
records[i].Value = strings.Join([]string{record.Value, ".", zone.Name, "."}, "")
}
// log.Debugf("%v", records[i].Value)
newRecordsForCNAME, err := netbox.GetRecordsQuery(
netboxdns.requestClient,
&netbox.RecordQuery{
FQDN: records[i].Value,
Type: queryTypes,
Zone: zone,
},
)
if err != nil { if err != nil {
return nil, err return nil, err
} }
extraRecords, err := netboxdns.processExtra(answer, zone, family) // log.Debugf("%v", newRecordsForCNAME)
if len(newRecordsForCNAME) > 0 {
newRecords = append(newRecords, newRecordsForCNAME...)
}
}
}
// log.Debugf("%v", newRecords)
allRecords = append(allRecords, records...)
records = newRecords
newRecords = []netbox.Record{}
if !foundCNAME {
break
}
if i > 20 {
return nil, fmt.Errorf("CNAME recursion depth exceeded")
}
}
//for _, record := range allRecords {
// log.Debugf("%v -> %v", record.FQDN, record.Value)
//}
answer, err := recordsToRR(allRecords)
if err != nil { if err != nil {
return nil, err return nil, err
} }
extra, err := recordsToRR(extraRecords)
if err != nil { log.Debug(answer)
return nil, err if len(answer) > 0 {
}
cnames := filterRRByType(answer, dns.TypeCNAME)
if qtype == dns.TypeCNAME || len(cnames) > 0 {
answer = append(answer, extra...)
extra = nil
}
return &lookupResponse{ return &lookupResponse{
Answer: answer, Answer: answer,
Extra: extra, Extra: []dns.RR{},
}, nil }, nil
} } else {
return nil, nil return nil, nil
}
} }
func (netboxdns *NetboxDNS) lookupDelegate( func (netboxdns *NetboxDNS) lookupDelegate(
qname string, qname string,
zone *netbox.Zone, zone *netbox.Zone,
family int, qtype uint16,
) (*lookupResponse, error) { ) (*lookupResponse, error) {
records, err := netbox.GetRecordsQuery( records, err := netbox.GetRecordsQuery(
netboxdns.requestClient, netboxdns.requestClient,
@@ -273,7 +368,7 @@ func (netboxdns *NetboxDNS) lookupDelegate(
if err != nil { if err != nil {
return nil, err return nil, err
} }
extraRecords, err := netboxdns.processExtra(ns, nil, family) extraRecords, err := netboxdns.processExtra(ns, nil, qtype)
if err != nil { if err != nil {
return nil, err return nil, err
} }
+8 -2
View File
@@ -3,6 +3,7 @@ package netboxdns
import ( import (
"context" "context"
"net/http" "net/http"
"net/netip"
"time" "time"
"github.com/coredns/coredns/plugin" "github.com/coredns/coredns/plugin"
@@ -57,8 +58,13 @@ func (netboxdns *NetboxDNS) ServeDNS(
) (int, error) { ) (int, error) {
state := request.Request{W: respWriter, Req: reqMsg} state := request.Request{W: respWriter, Req: reqMsg}
qname := state.QName() qname := state.QName()
reqIP, err := netip.ParseAddr(state.IP())
if err != nil {
return dns.RcodeServerFailure, err
}
family := state.Family() family := state.Family()
qtype := fixQType(state.QType(), family) qtype := state.QType()
// qtype := fixQType(state.QType(), family)
// check if plugin is configured to respond to the requested zone // check if plugin is configured to respond to the requested zone
respondingZone := plugin.Zones(netboxdns.zones).Matches(qname) respondingZone := plugin.Zones(netboxdns.zones).Matches(qname)
@@ -66,7 +72,7 @@ func (netboxdns *NetboxDNS) ServeDNS(
return netboxdns.nextOrFailure(reqContext, respWriter, reqMsg) return netboxdns.nextOrFailure(reqContext, respWriter, reqMsg)
} }
response, err := netboxdns.lookup(qname, qtype, family) response, err := netboxdns.lookup(qname, reqIP, qtype, family)
if err != nil { if err != nil {
return dns.RcodeServerFailure, err return dns.RcodeServerFailure, err
} }