mirror of
https://github.com/linka-cloud/coredns-split.git
synced 2024-12-21 17:00:44 +00:00
naive implementation
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
This commit is contained in:
parent
5b45e22631
commit
99971d8b4c
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +1,2 @@
|
||||
.idea
|
||||
tests
|
||||
|
29
README.md
29
README.md
@ -6,9 +6,15 @@
|
||||
|
||||
## Description
|
||||
|
||||
The split plugin allows filtering DNS Server response Records based on network definitions. That way
|
||||
The split plugin allows filtering DNS Server responses Records based on network definitions. That way
|
||||
you do not need to run multiple DNS servers to handle split DNS.
|
||||
|
||||
If there are multiple A Records in the response, only the records matching the defined network will be returned
|
||||
to a matching querier, and the records not matching the network to the other sources.
|
||||
|
||||
This plugin is not about security, it is design only to give a better answer to the incoming source IP,
|
||||
if you need to apply security filtering rules, please consider using the [**coredns** *acl*](https://coredns.io/plugins/acl/) plugin.
|
||||
|
||||
## Compilation
|
||||
|
||||
This package will always be compiled as part of CoreDNS and not in a standalone way. It will require you to use `go get` or as a dependency on [plugin.cfg](https://github.com/coredns/coredns/blob/master/plugin.cfg).
|
||||
@ -21,7 +27,7 @@ A simple way to consume this plugin, is by adding the following on [plugin.cfg](
|
||||
split:go.linka.cloud/coredns-split
|
||||
~~~
|
||||
|
||||
Put this lower in the plugin list, so that *split* is executed after any of the other plugins.
|
||||
Put this higher in the plugin list, so that *split* is before after any of the other plugins.
|
||||
|
||||
After this you can compile coredns by:
|
||||
|
||||
@ -57,22 +63,17 @@ This plugin reports readiness to the ready plugin. It will be immediately ready.
|
||||
|
||||
## Examples
|
||||
|
||||
In this configuration, we forward all queries to 9.9.9.9 and print "example" whenever we receive
|
||||
a query.
|
||||
In this configuration, we forward all queries to 9.9.9.9 and filter out A records pointing to an IP address
|
||||
in the 10.10.10.0/24 network except for queries coming from the 192.168.0.0/24 and 192.168.1.0/24 networks.
|
||||
|
||||
~~~ corefile
|
||||
. {
|
||||
example {
|
||||
10.10.10.0/24 {
|
||||
net 192.168.0.0/24 192.168.1.0/24
|
||||
}
|
||||
}
|
||||
forward . 9.9.9.9
|
||||
example
|
||||
}
|
||||
~~~
|
||||
|
||||
Or without any external connectivity:
|
||||
|
||||
~~~ corefile
|
||||
. {
|
||||
whoami
|
||||
example
|
||||
}
|
||||
~~~
|
||||
|
||||
|
4
ready.go
4
ready.go
@ -2,4 +2,6 @@ package split
|
||||
|
||||
// Ready implements the ready.Readiness interface, once this flips to true CoreDNS
|
||||
// assumes this plugin is ready for queries; it is not checked again.
|
||||
func (e Split) Ready() bool { return true }
|
||||
func (s Split) Ready() bool {
|
||||
return true
|
||||
}
|
||||
|
45
setup.go
45
setup.go
@ -1,6 +1,8 @@
|
||||
package split
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/coredns/caddy"
|
||||
"github.com/coredns/coredns/core/dnsserver"
|
||||
"github.com/coredns/coredns/plugin"
|
||||
@ -12,17 +14,46 @@ func init() { plugin.Register("split", setup) }
|
||||
// setup is the function that gets called when the config parser see the token "example". Setup is responsible
|
||||
// for parsing any extra options the example plugin may have. The first token this function sees is "example".
|
||||
func setup(c *caddy.Controller) error {
|
||||
c.Next() // Ignore "example" and give us the next token.
|
||||
if c.NextArg() {
|
||||
// If there was another token, return an error, because we don't have any configuration.
|
||||
// Any errors returned from this setup function should be wrapped with plugin.Error, so we
|
||||
// can present a slightly nicer error message to the user.
|
||||
return plugin.Error("split", c.ArgErr())
|
||||
s := Split{}
|
||||
for c.Next() {
|
||||
r := rule{}
|
||||
args := c.RemainingArgs()
|
||||
r.zones = plugin.OriginsFromArgsOrServerBlock(args, c.ServerBlockKeys)
|
||||
if c.NextBlock() {
|
||||
n := network{}
|
||||
_, ipnet, err := net.ParseCIDR(c.Val())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n.record = ipnet
|
||||
for c.NextBlock() {
|
||||
for c.NextLine() {
|
||||
a := c.Val()
|
||||
_ = a
|
||||
argsLoop:
|
||||
for _, v := range c.RemainingArgs() {
|
||||
_, ipnet, err := net.ParseCIDR(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, vv := range n.allowed {
|
||||
if vv.Contains(ipnet.IP) {
|
||||
continue argsLoop
|
||||
}
|
||||
}
|
||||
n.allowed = append(n.allowed, ipnet)
|
||||
}
|
||||
}
|
||||
r.networks = append(r.networks, n)
|
||||
}
|
||||
s.Rule = append(s.Rule, r)
|
||||
}
|
||||
}
|
||||
|
||||
// Add the Plugin to CoreDNS, so Servers can use it in their plugin chain.
|
||||
dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler {
|
||||
return Split{Next: next}
|
||||
s.Next = next
|
||||
return s
|
||||
})
|
||||
|
||||
// All OK, return a nil error.
|
||||
|
@ -13,9 +13,4 @@ func TestSetup(t *testing.T) {
|
||||
if err := setup(c); err != nil {
|
||||
t.Fatalf("Expected no errors, but got: %v", err)
|
||||
}
|
||||
|
||||
c = caddy.NewTestController("dns", `split more`)
|
||||
if err := setup(c); err == nil {
|
||||
t.Fatalf("Expected errors, but got: %v", err)
|
||||
}
|
||||
}
|
||||
|
83
split.go
83
split.go
@ -5,11 +5,12 @@ package split
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
"github.com/coredns/coredns/plugin"
|
||||
"github.com/coredns/coredns/plugin/metrics"
|
||||
clog "github.com/coredns/coredns/plugin/pkg/log"
|
||||
|
||||
"github.com/coredns/coredns/request"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
@ -20,44 +21,108 @@ var log = clog.NewWithPlugin("split")
|
||||
// Split is an example plugin to show how to write a plugin.
|
||||
type Split struct {
|
||||
Next plugin.Handler
|
||||
|
||||
Rule []rule
|
||||
}
|
||||
|
||||
type rule struct {
|
||||
zones []string
|
||||
networks []network
|
||||
}
|
||||
|
||||
type network struct {
|
||||
record *net.IPNet
|
||||
allowed []*net.IPNet
|
||||
}
|
||||
|
||||
// ServeDNS implements the plugin.Handler interface. This method gets called when example is used
|
||||
// in a Server.
|
||||
func (e Split) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
|
||||
func (s Split) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
|
||||
// This function could be simpler. I.e. just fmt.Println("example") here, but we want to show
|
||||
// a slightly more complex example as to make this more interesting.
|
||||
// Here we wrap the dns.ResponseWriter in a new ResponseWriter and call the next plugin, when the
|
||||
// answer comes back, it will print "example".
|
||||
|
||||
// Debug log that we've have seen the query. This will only be shown when the debug plugin is loaded.
|
||||
// Debug log that we've seen the query. This will only be shown when the debug plugin is loaded.
|
||||
log.Debug("Received response")
|
||||
|
||||
// Wrap.
|
||||
pw := NewResponsePrinter(w)
|
||||
pw := s.NewResponsePrinter(w, r)
|
||||
|
||||
// Export metric with the server label set to the current server handling the request.
|
||||
requestCount.WithLabelValues(metrics.WithServer(ctx)).Inc()
|
||||
|
||||
// Call next plugin (if any).
|
||||
return plugin.NextOrFailure(e.Name(), e.Next, ctx, pw, r)
|
||||
return plugin.NextOrFailure(s.Name(), s.Next, ctx, pw, r)
|
||||
}
|
||||
|
||||
// Name implements the Handler interface.
|
||||
func (e Split) Name() string { return "split" }
|
||||
func (s Split) Name() string { return "split" }
|
||||
|
||||
// ResponsePrinter wrap a dns.ResponseWriter and will write example to standard output when WriteMsg is called.
|
||||
type ResponsePrinter struct {
|
||||
dns.ResponseWriter
|
||||
state request.Request
|
||||
r *dns.Msg
|
||||
src net.IP
|
||||
rules []rule
|
||||
}
|
||||
|
||||
// NewResponsePrinter returns ResponseWriter.
|
||||
func NewResponsePrinter(w dns.ResponseWriter) *ResponsePrinter {
|
||||
return &ResponsePrinter{ResponseWriter: w}
|
||||
func (s Split) NewResponsePrinter(w dns.ResponseWriter, r *dns.Msg) *ResponsePrinter {
|
||||
state := request.Request{W: w, Req: r}
|
||||
ip := net.ParseIP(state.IP())
|
||||
return &ResponsePrinter{ResponseWriter: w, r: r, src: ip, rules: s.Rule, state: state}
|
||||
}
|
||||
|
||||
// WriteMsg calls the underlying ResponseWriter's WriteMsg method and prints "example" to standard output.
|
||||
func (r *ResponsePrinter) WriteMsg(res *dns.Msg) error {
|
||||
log.Info("example")
|
||||
var rule rule
|
||||
for _, v := range r.rules {
|
||||
zone := plugin.Zones(v.zones).Matches(r.state.Name())
|
||||
if zone == "" {
|
||||
continue
|
||||
}
|
||||
rule = v
|
||||
break
|
||||
}
|
||||
var answers []dns.RR
|
||||
var netAnswers []dns.RR
|
||||
for _, v := range res.Answer {
|
||||
rec, ok := v.(*dns.A)
|
||||
if !ok {
|
||||
answers = append(answers, v)
|
||||
continue
|
||||
}
|
||||
var net *network
|
||||
for _, vv := range rule.networks {
|
||||
if vv.record.Contains(rec.A) {
|
||||
net = &vv
|
||||
break
|
||||
}
|
||||
}
|
||||
if net == nil {
|
||||
answers = append(answers, v)
|
||||
continue
|
||||
}
|
||||
allowed := false
|
||||
for _, vv := range net.allowed {
|
||||
if vv.Contains(r.src) {
|
||||
allowed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if allowed {
|
||||
answers = append(answers, v)
|
||||
netAnswers = append(netAnswers, v)
|
||||
continue
|
||||
}
|
||||
log.Infof("request source %s: %s: filtering %s", r.src.String(), rec.Hdr.Name, rec.A)
|
||||
}
|
||||
if len(netAnswers) != 0 {
|
||||
res.Answer = netAnswers
|
||||
} else {
|
||||
res.Answer = answers
|
||||
}
|
||||
return r.ResponseWriter.WriteMsg(res)
|
||||
}
|
||||
|
@ -1,10 +1,7 @@
|
||||
package split
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
golog "log"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/coredns/coredns/plugin/pkg/dnstest"
|
||||
@ -17,11 +14,6 @@ func TestExample(t *testing.T) {
|
||||
// Create a new Split Plugin. Use the test.ErrorHandler as the next plugin.
|
||||
x := Split{Next: test.ErrorHandler()}
|
||||
|
||||
// Setup a new output buffer that is *not* standard output, so we can check if
|
||||
// example is really being printed.
|
||||
b := &bytes.Buffer{}
|
||||
golog.SetOutput(b)
|
||||
|
||||
ctx := context.TODO()
|
||||
r := new(dns.Msg)
|
||||
r.SetQuestion("example.org.", dns.TypeA)
|
||||
@ -31,7 +23,5 @@ func TestExample(t *testing.T) {
|
||||
|
||||
// Call our plugin directly, and check the result.
|
||||
x.ServeDNS(ctx, rec, r)
|
||||
if a := b.String(); !strings.Contains(a, "[INFO] plugin/split: example") {
|
||||
t.Errorf("Failed to print '%s', got %s", "[INFO] plugin/split: example", a)
|
||||
}
|
||||
t.Log(rec.Msg)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user