Progress in chaos
This commit is contained in:
parent
6c50f9fae5
commit
5a413a0cb4
42
ldap.go
42
ldap.go
@ -10,14 +10,15 @@ package ldap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/coredns/coredns/plugin"
|
||||
"github.com/coredns/coredns/plugin/etcd/msg"
|
||||
"github.com/coredns/coredns/plugin/pkg/fall"
|
||||
"github.com/coredns/coredns/plugin/pkg/upstream"
|
||||
"github.com/coredns/coredns/request"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
@ -26,17 +27,34 @@ import (
|
||||
|
||||
// Ldap is an ldap plugin to serve zone entries from a ldap backend.
|
||||
type Ldap struct {
|
||||
Next plugin.Handler
|
||||
Fall fall.F
|
||||
Zones []string
|
||||
Client *ldap.Client
|
||||
clientConfig map[string]
|
||||
Next plugin.Handler
|
||||
Fall fall.F
|
||||
Zones []string
|
||||
Upstream *upstream.Upstream
|
||||
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.
|
||||
func New(zones []string) *Ldap {
|
||||
k := new(Ldap)
|
||||
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
|
||||
}
|
||||
|
||||
@ -48,17 +66,13 @@ var (
|
||||
|
||||
// InitClient initializes a Ldap client.
|
||||
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 {
|
||||
log.Fatal(err)
|
||||
return err
|
||||
}
|
||||
defer l.Client.Close()
|
||||
|
||||
// Reconnect with TLS
|
||||
err = l.Client.StartTLS(&tls.Config{InsecureSkipVerify: true})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Services implements the ServiceBackend interface.
|
||||
|
231
setup.go
231
setup.go
@ -1,20 +1,13 @@
|
||||
package ldap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/coredns/coredns/core/dnsserver"
|
||||
"github.com/coredns/coredns/plugin"
|
||||
"github.com/coredns/coredns/plugin/metrics"
|
||||
"github.com/coredns/coredns/plugin/pkg/dnsutil"
|
||||
clog "github.com/coredns/coredns/plugin/pkg/log"
|
||||
"github.com/coredns/coredns/plugin/pkg/parse"
|
||||
"github.com/coredns/coredns/plugin/pkg/upstream"
|
||||
|
||||
"github.com/caddyserver/caddy"
|
||||
@ -38,13 +31,11 @@ func setup(c *caddy.Controller) error {
|
||||
return plugin.Error(pluginName, err)
|
||||
}
|
||||
|
||||
err = l.InitLdapCache(context.Background())
|
||||
err = l.InitClient()
|
||||
if err != nil {
|
||||
return plugin.Error(pluginName, err)
|
||||
}
|
||||
|
||||
l.RegisterLdapCache(c)
|
||||
|
||||
// add prometheus metrics on startup
|
||||
c.OnStartup(func() error {
|
||||
// add plugin-global metric once
|
||||
@ -65,30 +56,6 @@ func setup(c *caddy.Controller) error {
|
||||
|
||||
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) {
|
||||
var (
|
||||
ldap *Ldap
|
||||
@ -102,7 +69,7 @@ func ldapParse(c *caddy.Controller) (*Ldap, error) {
|
||||
}
|
||||
i++
|
||||
|
||||
l, err = ParseStanza(c)
|
||||
ldap, err = ParseStanza(c)
|
||||
if err != nil {
|
||||
return ldap, err
|
||||
}
|
||||
@ -112,16 +79,7 @@ func ldapParse(c *caddy.Controller) (*Ldap, error) {
|
||||
|
||||
// ParseStanza parses a ldap stanza
|
||||
func ParseStanza(c *caddy.Controller) (*Ldap, error) {
|
||||
|
||||
ldap := New([]string{""})
|
||||
ldap.autoPathSearch = searchFromResolvConf()
|
||||
|
||||
opts := dnsControlOpts{
|
||||
initEndpointsCache: true,
|
||||
ignoreEmptyService: false,
|
||||
}
|
||||
ldap.opts = opts
|
||||
|
||||
zones := c.RemainingArgs()
|
||||
|
||||
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()
|
||||
|
||||
for c.NextBlock() {
|
||||
switch c.Val() {
|
||||
// RFC 4516 URL
|
||||
case "endpoint_pod_names":
|
||||
args := c.RemainingArgs()
|
||||
if len(args) > 0 {
|
||||
case "ldap_url":
|
||||
c.NextArg()
|
||||
ldap.ldapURL = c.Val()
|
||||
continue
|
||||
case "paging_limit":
|
||||
c.NextArg()
|
||||
pagingLimit, err := strconv.Atoi(c.Val())
|
||||
if err != nil {
|
||||
return nil, c.ArgErr()
|
||||
}
|
||||
ldap.endpointNameMode = true
|
||||
ldap.pagingLimit = pagingLimit
|
||||
continue
|
||||
case "pods":
|
||||
args := c.RemainingArgs()
|
||||
if len(args) == 1 {
|
||||
switch args[0] {
|
||||
case podModeDisabled, podModeInsecure, podModeVerified:
|
||||
ldap.podMode = args[0]
|
||||
case "search_request":
|
||||
for c.NextBlock() {
|
||||
switch c.Val() {
|
||||
case "base_dn":
|
||||
c.NextArg() // ou=ae-dir
|
||||
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:
|
||||
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
|
||||
}
|
||||
return nil, c.ArgErr()
|
||||
case "namespaces":
|
||||
args := c.RemainingArgs()
|
||||
if len(args) > 0 {
|
||||
for _, a := range args {
|
||||
ldap.Namespaces[a] = struct{}{}
|
||||
}
|
||||
continue
|
||||
}
|
||||
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()
|
||||
continue
|
||||
case "username":
|
||||
c.NextArg()
|
||||
ldap.username = c.Val()
|
||||
case "password":
|
||||
c.NextArg()
|
||||
ldap.password = c.Val()
|
||||
case "sasl":
|
||||
c.NextArg()
|
||||
ldap.sasl = true
|
||||
case "fallthrough":
|
||||
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:
|
||||
return nil, c.Errf("unknown property '%s'", c.Val())
|
||||
}
|
||||
}
|
||||
|
||||
if len(ldap.Namespaces) != 0 && ldap.opts.namespaceLabelSelector != nil {
|
||||
return nil, c.Errf("namespaces and namespace_labels cannot both be set")
|
||||
// validate non-default ldap values ...
|
||||
if ldap.ldapURL == "" || &ldap.ldapURL == nil {
|
||||
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
|
||||
|
150
sync.go
Normal file
150
sync.go
Normal 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
|
||||
}
|
Loading…
Reference in New Issue
Block a user