Progress in chaos

This commit is contained in:
David Arnold 2020-06-07 23:12:18 -05:00
parent 6c50f9fae5
commit 5a413a0cb4
No known key found for this signature in database
GPG Key ID: 6D6A936E69C59D08
3 changed files with 235 additions and 188 deletions

36
ldap.go
View File

@ -10,14 +10,15 @@ package ldap
import ( import (
"context" "context"
"crypto/tls"
"errors" "errors"
"fmt"
"strings" "strings"
"sync"
"time"
"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/pkg/fall" "github.com/coredns/coredns/plugin/pkg/fall"
"github.com/coredns/coredns/plugin/pkg/upstream"
"github.com/coredns/coredns/request" "github.com/coredns/coredns/request"
"github.com/miekg/dns" "github.com/miekg/dns"
@ -29,14 +30,31 @@ type Ldap struct {
Next plugin.Handler Next plugin.Handler
Fall fall.F Fall fall.F
Zones []string Zones []string
Client *ldap.Client Upstream *upstream.Upstream
clientConfig map[string] Client ldap.Client
searchRequest *ldap.SearchRequest
ldapURL string
pagingLimit uint32
syncInterval time.Duration
username string
password string
sasl bool
zMu sync.RWMutex
} }
// New returns an initialized Ldap with defaults. // New returns an initialized Ldap with defaults.
func New(zones []string) *Ldap { func New(zones []string) *Ldap {
k := new(Ldap) k := new(Ldap)
k.Zones = zones k.Zones = zones
k.pagingLimit = 0
// SearchRequest defaults
k.searchRequest = new(ldap.SearchRequest)
k.searchRequest.DerefAliases = ldap.NeverDerefAliases // TODO: Reason
k.searchRequest.Scope = ldap.ScopeWholeSubtree // search whole subtree
k.searchRequest.SizeLimit = 500 // TODO: Reason
k.searchRequest.TimeLimit = 500 // TODO: Reason
k.searchRequest.TypesOnly = false // TODO: Reason
return k return k
} }
@ -48,17 +66,13 @@ var (
// InitClient initializes a Ldap client. // InitClient initializes a Ldap client.
func (l *Ldap) InitClient() (err error) { func (l *Ldap) InitClient() (err error) {
l.Client, err = ldap.Dial("tcp", fmt.Sprintf("%s:%d", "ldap.example.com", 389)) l.Client, err = ldap.DialURL(l.ldapURL)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
return err
} }
defer l.Client.Close() defer l.Client.Close()
return nil
// Reconnect with TLS
err = l.Client.StartTLS(&tls.Config{InsecureSkipVerify: true})
if err != nil {
log.Fatal(err)
}
} }
// Services implements the ServiceBackend interface. // Services implements the ServiceBackend interface.

231
setup.go
View File

@ -1,20 +1,13 @@
package ldap package ldap
import ( import (
"context"
"errors"
"fmt"
"strconv" "strconv"
"strings"
"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"
"github.com/coredns/coredns/plugin/metrics" "github.com/coredns/coredns/plugin/metrics"
"github.com/coredns/coredns/plugin/pkg/dnsutil"
clog "github.com/coredns/coredns/plugin/pkg/log" clog "github.com/coredns/coredns/plugin/pkg/log"
"github.com/coredns/coredns/plugin/pkg/parse"
"github.com/coredns/coredns/plugin/pkg/upstream" "github.com/coredns/coredns/plugin/pkg/upstream"
"github.com/caddyserver/caddy" "github.com/caddyserver/caddy"
@ -38,13 +31,11 @@ func setup(c *caddy.Controller) error {
return plugin.Error(pluginName, err) return plugin.Error(pluginName, err)
} }
err = l.InitLdapCache(context.Background()) err = l.InitClient()
if err != nil { if err != nil {
return plugin.Error(pluginName, err) return plugin.Error(pluginName, err)
} }
l.RegisterLdapCache(c)
// add prometheus metrics on startup // add prometheus metrics on startup
c.OnStartup(func() error { c.OnStartup(func() error {
// add plugin-global metric once // add plugin-global metric once
@ -65,30 +56,6 @@ func setup(c *caddy.Controller) error {
var once sync.Once var once sync.Once
// RegisterLdapCache registers LdapCache start and stop functions with Caddy
func (l *Ldap) RegisterLdapCache(c *caddy.Controller) {
c.OnStartup(func() error {
go l.APIConn.Run()
timeout := time.After(5 * time.Second)
ticker := time.NewTicker(100 * time.Millisecond)
for {
select {
case <-ticker.C:
if k.APIConn.HasSynced() {
return nil
}
case <-timeout:
return nil
}
}
})
c.OnShutdown(func() error {
return l.APIConn.Stop()
})
}
func ldapParse(c *caddy.Controller) (*Ldap, error) { func ldapParse(c *caddy.Controller) (*Ldap, error) {
var ( var (
ldap *Ldap ldap *Ldap
@ -102,7 +69,7 @@ func ldapParse(c *caddy.Controller) (*Ldap, error) {
} }
i++ i++
l, err = ParseStanza(c) ldap, err = ParseStanza(c)
if err != nil { if err != nil {
return ldap, err return ldap, err
} }
@ -112,16 +79,7 @@ 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{""}) ldap := New([]string{""})
ldap.autoPathSearch = searchFromResolvConf()
opts := dnsControlOpts{
initEndpointsCache: true,
ignoreEmptyService: false,
}
ldap.opts = opts
zones := c.RemainingArgs() zones := c.RemainingArgs()
if len(zones) != 0 { if len(zones) != 0 {
@ -136,153 +94,78 @@ func ParseStanza(c *caddy.Controller) (*Ldap, error) {
} }
} }
ldap.primaryZoneIndex = -1
for i, z := range ldap.Zones {
if dnsutil.IsReverse(z) > 0 {
continue
}
ldap.primaryZoneIndex = i
break
}
if ldap.primaryZoneIndex == -1 {
return nil, errors.New("non-reverse zone name must be used")
}
ldap.Upstream = upstream.New() ldap.Upstream = upstream.New()
for c.NextBlock() { for c.NextBlock() {
switch c.Val() { switch c.Val() {
// RFC 4516 URL // RFC 4516 URL
case "endpoint_pod_names": case "ldap_url":
args := c.RemainingArgs() c.NextArg()
if len(args) > 0 { ldap.ldapURL = c.Val()
continue
case "paging_limit":
c.NextArg()
pagingLimit, err := strconv.Atoi(c.Val())
if err != nil {
return nil, c.ArgErr() return nil, c.ArgErr()
} }
ldap.endpointNameMode = true ldap.pagingLimit = pagingLimit
continue continue
case "pods": case "search_request":
args := c.RemainingArgs() for c.NextBlock() {
if len(args) == 1 { switch c.Val() {
switch args[0] { case "base_dn":
case podModeDisabled, podModeInsecure, podModeVerified: c.NextArg() // ou=ae-dir
ldap.podMode = args[0] ldap.searchRequest.BaseDN = c.Val()
case "filter":
c.NextArg() // (objectClass=aeNwDevice)
ldap.searchRequest.Filter = c.Val()
case "attributes":
ldap.searchRequest.Attributes = c.RemainingArgs() // aeFqdn ipHostNumber
default: default:
return nil, fmt.Errorf("wrong value for pods: %s, must be one of: disabled, verified, insecure", args[0]) return nil, c.Errf("unknown search request property '%s'", c.Val())
}
} }
continue continue
} case "username":
return nil, c.ArgErr() c.NextArg()
case "namespaces": ldap.username = c.Val()
args := c.RemainingArgs() case "password":
if len(args) > 0 { c.NextArg()
for _, a := range args { ldap.password = c.Val()
ldap.Namespaces[a] = struct{}{} case "sasl":
} c.NextArg()
continue ldap.sasl = true
}
return nil, c.ArgErr()
case "endpoint":
args := c.RemainingArgs()
if len(args) > 0 {
// Multiple endpoints are deprecated but still could be specified,
// only the first one be used, though
ldap.APIServerList = args
if len(args) > 1 {
log.Warningf("Multiple endpoints have been deprecated, only the first specified endpoint '%s' is used", args[0])
}
continue
}
return nil, c.ArgErr()
case "tls": // cert key cacertfile
args := c.RemainingArgs()
if len(args) == 3 {
ldap.APIClientCert, ldap.APIClientKey, ldap.APICertAuth = args[0], args[1], args[2]
continue
}
return nil, c.ArgErr()
case "labels":
args := c.RemainingArgs()
if len(args) > 0 {
labelSelectorString := strings.Join(args, " ")
ls, err := meta.ParseToLabelSelector(labelSelectorString)
if err != nil {
return nil, fmt.Errorf("unable to parse label selector value: '%v': %v", labelSelectorString, err)
}
ldap.opts.labelSelector = ls
continue
}
return nil, c.ArgErr()
case "namespace_labels":
args := c.RemainingArgs()
if len(args) > 0 {
namespaceLabelSelectorString := strings.Join(args, " ")
nls, err := meta.ParseToLabelSelector(namespaceLabelSelectorString)
if err != nil {
return nil, fmt.Errorf("unable to parse namespace_label selector value: '%v': %v", namespaceLabelSelectorString, err)
}
ldap.opts.namespaceLabelSelector = nls
continue
}
return nil, c.ArgErr()
case "fallthrough": case "fallthrough":
ldap.Fall.SetZonesFromArgs(c.RemainingArgs()) ldap.Fall.SetZonesFromArgs(c.RemainingArgs())
case "ttl":
args := c.RemainingArgs()
if len(args) == 0 {
return nil, c.ArgErr()
}
t, err := strconv.Atoi(args[0])
if err != nil {
return nil, err
}
if t < 0 || t > 3600 {
return nil, c.Errf("ttl must be in range [0, 3600]: %d", t)
}
ldap.ttl = uint32(t)
case "transfer":
tos, froms, err := parse.Transfer(c, false)
if err != nil {
return nil, err
}
if len(froms) != 0 {
return nil, c.Errf("transfer from is not supported with this plugin")
}
ldap.TransferTo = tos
case "noendpoints":
if len(c.RemainingArgs()) != 0 {
return nil, c.ArgErr()
}
ldap.opts.initEndpointsCache = false
case "ignore":
args := c.RemainingArgs()
if len(args) > 0 {
ignore := args[0]
if ignore == "empty_service" {
ldap.opts.ignoreEmptyService = true
continue
} else {
return nil, fmt.Errorf("unable to parse ignore value: '%v'", ignore)
}
}
case "kubeconfig":
args := c.RemainingArgs()
if len(args) == 2 {
config := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
&clientcmd.ClientConfigLoadingRules{ExplicitPath: args[0]},
&clientcmd.ConfigOverrides{CurrentContext: args[1]},
)
ldap.ClientConfig = config
continue
}
return nil, c.ArgErr()
default: default:
return nil, c.Errf("unknown property '%s'", c.Val()) return nil, c.Errf("unknown property '%s'", c.Val())
} }
} }
// validate non-default ldap values ...
if len(ldap.Namespaces) != 0 && ldap.opts.namespaceLabelSelector != nil { if ldap.ldapURL == "" || &ldap.ldapURL == nil {
return nil, c.Errf("namespaces and namespace_labels cannot both be set") return nil, c.ArgErr()
}
if ldap.searchRequest.BaseDN == "" {
return nil, c.ArgErr()
}
if ldap.searchRequest.Filter == "" {
return nil, c.ArgErr()
}
if len(ldap.searchRequest.Attributes) != 2 {
return nil, c.ArgErr()
}
// if only one of password and username set
if (&ldap.username == nil) != (&ldap.password == nil) {
return nil, c.ArgErr()
}
// if both username/password and sasl are set
if &ldap.username != nil && &ldap.sasl != nil {
return nil, c.ArgErr()
}
// if neither username/password nor sasl are set
if &ldap.username == nil && &ldap.sasl == nil {
return nil, c.ArgErr()
} }
return ldap, nil return ldap, nil

150
sync.go Normal file
View File

@ -0,0 +1,150 @@
package ldap
import (
"context"
"fmt"
"time"
"github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/plugin/file"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
)
// Run updates the zone from ldap.
func (l *Ldap) Run(ctx context.Context) error {
if err := l.updateZones(ctx); err != nil {
return err
}
go func() {
for {
select {
case <-ctx.Done():
log.Infof("Breaking out of Ldap update loop: %v", ctx.Err())
return
case <-time.After(l.syncInterval * time.Second):
if err := l.updateZones(ctx); err != nil && ctx.Err() == nil {
log.Errorf("Failed to update zones: %v", err)
}
}
}
}()
return nil
}
func (l *Ldap) updateZones(ctx context.Context) error {
var err error
var zoneFile file.Zone
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)
}
return nil
}
func (l *Ldap) getValuePairs() (valuePairs *[][]string, err error) {
searchResult, err := l.Client.SearchWithPaging(l.searchRequest, l.pagingLimit)
if err != nil {
return nil, fmt.Errorf("error fetching data from ldap server: %w", err)
}
}
func updateZoneFromPublicResourceSet(recordSet publicdns.RecordSetListResultPage, zName string) *file.Zone {
zoneFile := 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))}
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
}