* mw/federaration This PR add the federation back as a middleware to keep it more contained from the main kubernetes code. It also makes parseRequest less import and pushes this functionlity down in the k.Entries. This minimizes (or tries to) the importance for the qtype in the query. In the end the qtype checking should only happen in ServeDNS - but for k8s this might proof difficult. Numerous other cleanup in code and kubernetes tests. * up test coverage
140 lines
4.3 KiB
Go
140 lines
4.3 KiB
Go
/*
|
|
Package federation implements kubernetes federation. It checks if the qname matches
|
|
a possible federation. If this is the case and the captured answer is an NXDOMAIN,
|
|
federation is performed. If this is not the case the original answer is returned.
|
|
|
|
The federation label is always the 2nd to last once the zone is chopped of. For
|
|
instance "nginx.mynamespace.myfederation.svc.example.com" has "myfederation" as
|
|
the federation label. For federation to work we do a normal k8s lookup
|
|
*without* that label, if that comes back with NXDOMAIN or NODATA(??) we create
|
|
a federation record and return that.
|
|
|
|
Federation is only useful in conjunction with the kubernetes middleware, without it is a noop.
|
|
*/
|
|
package federation
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"github.com/coredns/coredns/middleware"
|
|
"github.com/coredns/coredns/middleware/etcd/msg"
|
|
"github.com/coredns/coredns/middleware/pkg/dnsutil"
|
|
"github.com/coredns/coredns/request"
|
|
|
|
"github.com/miekg/dns"
|
|
"golang.org/x/net/context"
|
|
)
|
|
|
|
// Federation contains the name to zone mapping used for federation in kubernetes.
|
|
type Federation struct {
|
|
f map[string]string
|
|
zones []string
|
|
|
|
Next middleware.Handler
|
|
Federations Func
|
|
}
|
|
|
|
// Func needs to be implemented by any middleware that implements
|
|
// federation. Right now this is only the kubernetes middleware.
|
|
type Func func(state request.Request, fname, fzone string) (msg.Service, error)
|
|
|
|
// New returns a new federation.
|
|
func New() *Federation {
|
|
return &Federation{f: make(map[string]string)}
|
|
}
|
|
|
|
// ServeDNS implements the middleware.Handle interface.
|
|
func (f *Federation) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
|
|
if f.Federations == nil {
|
|
return middleware.NextOrFailure(f.Name(), f.Next, ctx, w, r)
|
|
}
|
|
|
|
state := request.Request{W: w, Req: r}
|
|
zone := middleware.Zones(f.zones).Matches(state.Name())
|
|
if zone == "" {
|
|
return middleware.NextOrFailure(f.Name(), f.Next, ctx, w, r)
|
|
}
|
|
|
|
state.Zone = zone
|
|
|
|
// Remove the federation label from the qname to see if something exists.
|
|
without, label := f.isNameFederation(state.Name(), state.Zone)
|
|
if without == "" {
|
|
return middleware.NextOrFailure(f.Name(), f.Next, ctx, w, r)
|
|
}
|
|
|
|
qname := r.Question[0].Name
|
|
r.Question[0].Name = without
|
|
state.Clear()
|
|
|
|
// Start the next middleware, but with a nowriter, capture the result, if NXDOMAIN
|
|
// perform federation, otherwise just write the result.
|
|
nw := NewNonWriter(w)
|
|
ret, err := middleware.NextOrFailure(f.Name(), f.Next, ctx, nw, r)
|
|
|
|
if !middleware.ClientWrite(ret) {
|
|
// something went wrong
|
|
return ret, err
|
|
}
|
|
|
|
if m := nw.Msg; m.Rcode != dns.RcodeNameError {
|
|
// If positive answer we need to substitute the orinal qname in question and answer.
|
|
r.Question[0].Name = qname
|
|
for _, a := range m.Answer {
|
|
a.Header().Name = qname
|
|
}
|
|
|
|
state.SizeAndDo(m)
|
|
m, _ = state.Scrub(m)
|
|
w.WriteMsg(m)
|
|
|
|
return dns.RcodeSuccess, nil
|
|
}
|
|
|
|
// Still here, we've seen NXDOMAIN and need to perform federation.
|
|
service, err := f.Federations(state, label, f.f[label]) // state references Req which has updated qname
|
|
if err != nil {
|
|
return dns.RcodeServerFailure, err
|
|
}
|
|
|
|
r.Question[0].Name = qname
|
|
|
|
m := new(dns.Msg)
|
|
m.SetReply(r)
|
|
m.Authoritative, m.RecursionAvailable, m.Compress = true, true, true
|
|
|
|
m.Answer = []dns.RR{service.NewCNAME(state.QName(), service.Host)}
|
|
|
|
state.SizeAndDo(m)
|
|
m, _ = state.Scrub(m)
|
|
w.WriteMsg(m)
|
|
|
|
return dns.RcodeSuccess, nil
|
|
}
|
|
|
|
// Name implements the middleware.Handle interface.
|
|
func (f *Federation) Name() string { return "federation" }
|
|
|
|
// IsNameFederation checks the qname to see if it is a potential federation. The federation
|
|
// label is always the 2nd to last once the zone is chopped of. For instance
|
|
// "nginx.mynamespace.myfederation.svc.example.com" has "myfederation" as the federation label.
|
|
// IsNameFederation returns a new qname with the federation label and the label itself or two
|
|
// emtpy strings if there wasn't a hit.
|
|
func (f *Federation) isNameFederation(name, zone string) (string, string) {
|
|
base, _ := dnsutil.TrimZone(name, zone)
|
|
|
|
// TODO(miek): dns.PrevLabel is better for memory, or dns.Split.
|
|
labels := dns.SplitDomainName(base)
|
|
ll := len(labels)
|
|
if ll < 2 {
|
|
return "", ""
|
|
}
|
|
|
|
fed := labels[ll-2]
|
|
|
|
if _, ok := f.f[fed]; ok {
|
|
without := strings.Join(labels[:ll-2], ".") + "." + labels[ll-1] + "." + zone
|
|
return without, fed
|
|
}
|
|
return "", ""
|
|
}
|