Progress in chaos (more)

This commit is contained in:
David Arnold 2020-06-08 22:01:53 -05:00
parent a64423ce00
commit a290a448ef
No known key found for this signature in database
GPG Key ID: 6D6A936E69C59D08
5 changed files with 161 additions and 238 deletions

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"github.com/coredns/coredns/plugin" "github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/plugin/file"
"github.com/coredns/coredns/request" "github.com/coredns/coredns/request"
"github.com/miekg/dns" "github.com/miekg/dns"
@ -11,69 +12,39 @@ import (
// ServeDNS implements the plugin.Handler interface. // ServeDNS implements the plugin.Handler interface.
func (l Ldap) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { func (l Ldap) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
opt := plugin.Options{} // opt := plugin.Options{}
state := request.Request{W: w, Req: r} state := request.Request{W: w, Req: r}
zone := plugin.Zones(l.Zones).Matches(state.Name()) zone := plugin.Zones(l.Zones.Names).Matches(state.Name())
if zone == "" { if zone == "" {
return plugin.NextOrFailure(l.Name(), l.Next, ctx, w, r) return plugin.NextOrFailure(l.Name(), l.Next, ctx, w, r)
} }
var ( Zone, ok := l.Zones.Z[zone]
records []dns.RR if !ok || Zone == nil {
extra []dns.RR return dns.RcodeServerFailure, nil
err error
)
switch state.QType() {
case dns.TypeA:
records, err = plugin.A(ctx, l, zone, state, nil, opt)
case dns.TypeAAAA:
records, err = plugin.AAAA(ctx, l, zone, state, nil, opt)
case dns.TypeTXT:
records, err = plugin.TXT(ctx, l, zone, state, nil, opt)
case dns.TypeCNAME:
records, err = plugin.CNAME(ctx, l, zone, state, opt)
case dns.TypePTR:
records, err = plugin.PTR(ctx, l, zone, state, opt)
case dns.TypeMX:
records, extra, err = plugin.MX(ctx, l, zone, state, opt)
case dns.TypeSRV:
records, extra, err = plugin.SRV(ctx, l, zone, state, opt)
case dns.TypeSOA:
records, err = plugin.SOA(ctx, l, zone, state, opt)
case dns.TypeNS:
if state.Name() == zone {
records, extra, err = plugin.NS(ctx, l, zone, state, opt)
break
}
fallthrough
default:
// Do a fake A lookup, so we can distinguish between NODATA and NXDOMAIN
_, err = plugin.A(ctx, l, zone, state, nil, opt)
} }
var result file.Result
if l.IsNameError(err) {
if l.Fall.Through(state.Name()) {
return plugin.NextOrFailure(l.Name(), l.Next, ctx, w, r)
}
// Make err nil when returning here, so we don't log spam for NXDOMAIN.
return plugin.BackendError(ctx, &l, zone, dns.RcodeNameError, state, nil /* err */, opt)
}
if err != nil {
return plugin.BackendError(ctx, &l, zone, dns.RcodeServerFailure, state, err, opt)
}
if len(records) == 0 {
return plugin.BackendError(ctx, &l, zone, dns.RcodeSuccess, state, err, opt)
}
m := new(dns.Msg) m := new(dns.Msg)
m.SetReply(r) m.SetReply(r)
m.Authoritative = true m.Authoritative = true
m.Answer = append(m.Answer, records...) l.zMu.RLock()
m.Extra = append(m.Extra, extra...) m.Answer, m.Ns, m.Extra, result = Zone.Lookup(ctx, state, state.Name())
l.zMu.RUnlock()
if len(m.Answer) == 0 && result != file.NoData && l.Fall.Through(state.Name()) {
return plugin.NextOrFailure(l.Name(), l.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) w.WriteMsg(m)
return dns.RcodeSuccess, nil return dns.RcodeSuccess, nil
} }

87
ldap.go
View File

@ -14,9 +14,11 @@ import (
"strings" "strings"
"sync" "sync"
"time" "time"
"net"
"github.com/coredns/coredns/plugin" "github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/plugin/etcd/msg" "github.com/coredns/coredns/plugin/etcd/msg"
"github.com/coredns/coredns/plugin/file"
"github.com/coredns/coredns/plugin/pkg/fall" "github.com/coredns/coredns/plugin/pkg/fall"
"github.com/coredns/coredns/plugin/pkg/upstream" "github.com/coredns/coredns/plugin/pkg/upstream"
"github.com/coredns/coredns/request" "github.com/coredns/coredns/request"
@ -25,13 +27,22 @@ import (
"gopkg.in/ldap.v3" "gopkg.in/ldap.v3"
) )
type ldapRecord struct {
fqdn string
ip net.IP
}
func (r *ldapRecord) A() (A *dns.A) {
return &dns.A{Hdr: dns.RR_Header{Name: r.fqdn, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 0}, A: r.ip}
}
// Ldap is an ldap plugin to serve zone entries from a ldap backend. // Ldap is an ldap plugin to serve zone entries from a ldap backend.
type Ldap struct { type Ldap struct {
Next plugin.Handler Next plugin.Handler
Fall fall.F Fall fall.F
Zones []string Upstream *upstream.Upstream
Upstream *upstream.Upstream Client ldap.Client
Client ldap.Client Zones file.Zones
searchRequest *ldap.SearchRequest searchRequest *ldap.SearchRequest
ldapURL string ldapURL string
@ -40,22 +51,25 @@ type Ldap struct {
username string username string
password string password string
sasl bool sasl bool
zMu sync.RWMutex fqdnAttr string
ip4Attr string
zMu sync.RWMutex
ttl time.Duration
} }
// New returns an initialized Ldap with defaults. // New returns an initialized Ldap with defaults.
func New(zones []string) *Ldap { func New(zoneNames []string) *Ldap {
k := new(Ldap) l := new(Ldap)
k.Zones = zones l.Zones.Names = zoneNames
k.pagingLimit = 0 l.pagingLimit = 0
// SearchRequest defaults // SearchRequest defaults
k.searchRequest = new(ldap.SearchRequest) l.searchRequest = new(ldap.SearchRequest)
k.searchRequest.DerefAliases = ldap.NeverDerefAliases // TODO: Reason l.searchRequest.DerefAliases = ldap.NeverDerefAliases // TODO: Reason
k.searchRequest.Scope = ldap.ScopeWholeSubtree // search whole subtree l.searchRequest.Scope = ldap.ScopeWholeSubtree // search whole subtree
k.searchRequest.SizeLimit = 500 // TODO: Reason l.searchRequest.SizeLimit = 500 // TODO: Reason
k.searchRequest.TimeLimit = 500 // TODO: Reason l.searchRequest.TimeLimit = 500 // TODO: Reason
k.searchRequest.TypesOnly = false // TODO: Reason l.searchRequest.TypesOnly = false // TODO: Reason
return k return l
} }
var ( var (
@ -75,42 +89,3 @@ func (l *Ldap) InitClient() (err error) {
return nil return nil
} }
// Services implements the ServiceBackend interface.
func (l *Ldap) Services(ctx context.Context, state request.Request, exact bool, opt plugin.Options) (services []msg.Service, err error) {
services, err = l.Records(ctx, state, exact)
if err != nil {
return
}
services = msg.Group(services)
return
}
// Reverse implements the ServiceBackend interface.
func (l *Ldap) Reverse(ctx context.Context, state request.Request, exact bool, opt plugin.Options) (services []msg.Service, err error) {
return l.Services(ctx, state, exact, opt)
}
// Lookup implements the ServiceBackend interface.
func (l *Ldap) Lookup(ctx context.Context, state request.Request, name string, typ uint16) (*dns.Msg, error) {
return l.Upstream.Lookup(ctx, state, name, typ)
}
// IsNameError implements the ServiceBackend interface.
func (l *Ldap) IsNameError(err error) bool {
return err == errNoItems || err == errNsNotExposed || err == errInvalidRequest
}
// Records looks up records in ldap. If exact is true, it will lookup just this
// name. This is used when find matches when completing SRV lookups for instance.
func (l *Ldap) Records(ctx context.Context, state request.Request, exact bool) ([]msg.Service, error) {
name := state.Name()
path, star := msg.PathWithWildcard(name, l.PathPrefix)
r, err := l.get(ctx, path, !exact)
if err != nil {
return nil, err
}
segments := strings.Split(msg.Path(name, l.PathPrefix), "/")
return l.loopNodes(r.Kvs, segments, star, state.QType())
}

View File

@ -3,6 +3,7 @@ package ldap
import ( import (
"strconv" "strconv"
"sync" "sync"
"time"
"github.com/coredns/coredns/core/dnsserver" "github.com/coredns/coredns/core/dnsserver"
"github.com/coredns/coredns/plugin" "github.com/coredns/coredns/plugin"
@ -79,21 +80,19 @@ func ldapParse(c *caddy.Controller) (*Ldap, error) {
// ParseStanza parses a ldap stanza // ParseStanza parses a ldap stanza
func ParseStanza(c *caddy.Controller) (*Ldap, error) { func ParseStanza(c *caddy.Controller) (*Ldap, error) {
ldap := New([]string{""}) zoneNames := c.RemainingArgs()
zones := c.RemainingArgs() if len(zoneNames) != 0 {
for i := 0; i < len(zoneNames); i++ {
if len(zones) != 0 { zoneNames[i] = plugin.Host(zoneNames[i]).Normalize()
ldap.Zones = zones
for i := 0; i < len(ldap.Zones); i++ {
ldap.Zones[i] = plugin.Host(ldap.Zones[i]).Normalize()
} }
} else { } else {
ldap.Zones = make([]string, len(c.ServerBlockKeys)) zoneNames = make([]string, len(c.ServerBlockKeys))
for i := 0; i < len(c.ServerBlockKeys); i++ { for i := 0; i < len(zoneNames); i++ {
ldap.Zones[i] = plugin.Host(c.ServerBlockKeys[i]).Normalize() zoneNames[i] = plugin.Host(c.ServerBlockKeys[i]).Normalize()
} }
} }
ldap := New(zoneNames)
ldap.Upstream = upstream.New() ldap.Upstream = upstream.New()
for c.NextBlock() { for c.NextBlock() {
@ -105,39 +104,69 @@ func ParseStanza(c *caddy.Controller) (*Ldap, error) {
continue continue
case "paging_limit": case "paging_limit":
c.NextArg() c.NextArg()
pagingLimit, err := strconv.Atoi(c.Val()) pagingLimit, err := strconv.ParseUint(c.Val(), 10, 0)
if err != nil { if err != nil {
return nil, c.ArgErr() return nil, c.ArgErr()
} }
ldap.pagingLimit = pagingLimit ldap.pagingLimit = uint32(pagingLimit)
continue continue
case "search_request": case "base_dn":
c.NextArg() // ou=ae-dir
ldap.searchRequest.BaseDN = c.Val()
continue
case "filter":
c.NextArg() // (objectClass=aeNwDevice)
ldap.searchRequest.Filter = c.Val()
continue
case "attributes":
for c.NextBlock() { for c.NextBlock() {
switch c.Val() { switch c.Val() {
case "base_dn": case "fqdn":
c.NextArg() // ou=ae-dir c.NextArg() // aeFqdn
ldap.searchRequest.BaseDN = c.Val() ldap.searchRequest.Attributes = append(ldap.searchRequest.Attributes, c.Val())
case "filter": ldap.fqdnAttr = c.Val()
c.NextArg() // (objectClass=aeNwDevice) continue
ldap.searchRequest.Filter = c.Val() case "ip4":
case "attributes": c.NextArg() // ipHostNumber
ldap.searchRequest.Attributes = c.RemainingArgs() // aeFqdn ipHostNumber ldap.searchRequest.Attributes = append(ldap.searchRequest.Attributes, c.Val())
ldap.ip4Attr = c.Val()
continue
default: default:
return nil, c.Errf("unknown search request property '%s'", c.Val()) return nil, c.Errf("unknown attributes property '%s'", c.Val())
} }
} }
continue continue
case "username": case "username":
c.NextArg() c.NextArg()
ldap.username = c.Val() ldap.username = c.Val()
continue
case "password": case "password":
c.NextArg() c.NextArg()
ldap.password = c.Val() ldap.password = c.Val()
continue
case "sasl": case "sasl":
c.NextArg() c.NextArg()
ldap.sasl = true ldap.sasl = true
continue
case "ttl":
c.NextArg()
ttl, err := time.ParseDuration(c.Val())
if err != nil {
return nil, c.ArgErr()
}
ldap.ttl = ttl
continue
case "sync_interval":
c.NextArg()
syncInterval, err := time.ParseDuration(c.Val())
if err != nil {
return nil, c.ArgErr()
}
ldap.syncInterval = syncInterval
continue
case "fallthrough": case "fallthrough":
ldap.Fall.SetZonesFromArgs(c.RemainingArgs()) ldap.Fall.SetZonesFromArgs(c.RemainingArgs())
continue
default: default:
return nil, c.Errf("unknown property '%s'", c.Val()) return nil, c.Errf("unknown property '%s'", c.Val())
} }

158
sync.go
View File

@ -3,15 +3,14 @@ package ldap
import ( import (
"context" "context"
"fmt" "fmt"
"net"
"time" "time"
"github.com/coredns/coredns/plugin" "github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/plugin/file" "github.com/coredns/coredns/plugin/file"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
) )
// Run updates the zone from ldap. // Run updates the zone from ldap.
func (l *Ldap) Run(ctx context.Context) error { func (l *Ldap) Run(ctx context.Context) error {
if err := l.updateZones(ctx); err != nil { if err := l.updateZones(ctx); err != nil {
@ -23,7 +22,7 @@ func (l *Ldap) Run(ctx context.Context) error {
case <-ctx.Done(): case <-ctx.Done():
log.Infof("Breaking out of Ldap update loop: %v", ctx.Err()) log.Infof("Breaking out of Ldap update loop: %v", ctx.Err())
return return
case <-time.After(l.syncInterval * time.Second): case <-time.After(l.syncInterval):
if err := l.updateZones(ctx); err != nil && ctx.Err() == nil { if err := l.updateZones(ctx); err != nil && ctx.Err() == nil {
log.Errorf("Failed to update zones: %v", err) log.Errorf("Failed to update zones: %v", err)
} }
@ -34,117 +33,62 @@ func (l *Ldap) Run(ctx context.Context) error {
} }
func (l *Ldap) updateZones(ctx context.Context) error { func (l *Ldap) updateZones(ctx context.Context) error {
var err error zoneFileMap := make(map[string]*file.Zone, len(l.Zones.Names))
var zoneFile file.Zone for _, zn := range l.Zones.Names {
zoneFileMap[zn] = nil
valuePairs, err := getValuePairs()
for _, z := range l.Zones {
zoneFile = file.NewZone(z, "")
zoneFile.Upstream = l.Upstream
l.zMu.Lock()
(*z[i]).z = zoneFile
l.zMu.Unlock()
}
if err != nil {
return fmt.Errorf("error updating zones: %v", err)
} }
ldapRecords, err := l.fetchLdapRecords()
if err != nil {
return fmt.Errorf("updating zones: %w", err)
}
for zn, lrpz := range l.mapLdapRecordsToZone(ldapRecords) {
if lrpz == nil {
continue
}
if zoneFileMap[zn] == nil {
zoneFileMap[zn] = file.NewZone(zn, "")
zoneFileMap[zn].Upstream = l.Upstream
}
for _, lr := range lrpz {
zoneFileMap[zn].Insert(lr.A())
}
}
l.zMu.Lock()
for zn, zf := range zoneFileMap {
(*l.Zones.Z[zn]) = *zf
}
l.zMu.Unlock()
return nil return nil
} }
func (l *Ldap) getValuePairs() (valuePairs *[][]string, err error) { func (l *Ldap) mapLdapRecordsToZone(ldapRecords []ldapRecord) (ldapRecordsPerZone map[string][]ldapRecord) {
lrpz := make(map[string][]ldapRecord, len(l.Zones.Names))
for _, zn := range l.Zones.Names {
lrpz[zn] = nil
}
for _, lr := range ldapRecords {
zone := plugin.Zones(l.Zones.Names).Matches(lr.fqdn)
if zone != "" {
lrpz[zone] = append(lrpz[zone], lr)
}
}
return lrpz
}
func (l *Ldap) fetchLdapRecords() (ldapRecords []ldapRecord, err error) {
searchResult, err := l.Client.SearchWithPaging(l.searchRequest, l.pagingLimit) searchResult, err := l.Client.SearchWithPaging(l.searchRequest, l.pagingLimit)
if err != nil { if err != nil {
return nil, fmt.Errorf("error fetching data from ldap server: %w", err) return nil, fmt.Errorf("fetching data from server: %w", err)
} }
ldapRecords = make([]ldapRecord, len(searchResult.Entries))
} for i, _ := range ldapRecords {
ldapRecords[i] = ldapRecord{
func updateZoneFromPublicResourceSet(recordSet publicdns.RecordSetListResultPage, zName string) *file.Zone { fqdn: searchResult.Entries[i].GetAttributeValue(l.fqdnAttr),
zoneFile := file.NewZone(zName, "") ip: net.ParseIP(searchResult.Entries[i].GetAttributeValue(l.ip4Attr)),
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))}
zoneFile.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))}
zoneFile.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))}
zoneFile.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))}
zoneFile.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))}
zoneFile.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)}
zoneFile.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)}
zoneFile.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)}
zoneFile.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)}
zoneFile.Insert(cname)
} }
} }
return zoneFile return ldapRecords, nil
} }

4
vendor/modules.txt vendored
View File

@ -14,6 +14,9 @@ github.com/coredns/coredns/coremain
github.com/coredns/coredns/pb github.com/coredns/coredns/pb
github.com/coredns/coredns/plugin github.com/coredns/coredns/plugin
github.com/coredns/coredns/plugin/etcd/msg github.com/coredns/coredns/plugin/etcd/msg
github.com/coredns/coredns/plugin/file
github.com/coredns/coredns/plugin/file/rrutil
github.com/coredns/coredns/plugin/file/tree
github.com/coredns/coredns/plugin/metrics github.com/coredns/coredns/plugin/metrics
github.com/coredns/coredns/plugin/metrics/vars github.com/coredns/coredns/plugin/metrics/vars
github.com/coredns/coredns/plugin/pkg/dnstest github.com/coredns/coredns/plugin/pkg/dnstest
@ -21,6 +24,7 @@ github.com/coredns/coredns/plugin/pkg/dnsutil
github.com/coredns/coredns/plugin/pkg/doh github.com/coredns/coredns/plugin/pkg/doh
github.com/coredns/coredns/plugin/pkg/edns github.com/coredns/coredns/plugin/pkg/edns
github.com/coredns/coredns/plugin/pkg/fall github.com/coredns/coredns/plugin/pkg/fall
github.com/coredns/coredns/plugin/pkg/fuzz
github.com/coredns/coredns/plugin/pkg/log github.com/coredns/coredns/plugin/pkg/log
github.com/coredns/coredns/plugin/pkg/nonwriter github.com/coredns/coredns/plugin/pkg/nonwriter
github.com/coredns/coredns/plugin/pkg/parse github.com/coredns/coredns/plugin/pkg/parse