mirror of
https://github.com/linka-cloud/coredns-split.git
synced 2024-11-16 01:36:24 +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
|
.idea
|
||||||
|
tests
|
||||||
|
29
README.md
29
README.md
@ -6,9 +6,15 @@
|
|||||||
|
|
||||||
## Description
|
## 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.
|
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
|
## 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).
|
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
|
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:
|
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
|
## Examples
|
||||||
|
|
||||||
In this configuration, we forward all queries to 9.9.9.9 and print "example" whenever we receive
|
In this configuration, we forward all queries to 9.9.9.9 and filter out A records pointing to an IP address
|
||||||
a query.
|
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
|
~~~ corefile
|
||||||
. {
|
. {
|
||||||
|
example {
|
||||||
|
10.10.10.0/24 {
|
||||||
|
net 192.168.0.0/24 192.168.1.0/24
|
||||||
|
}
|
||||||
|
}
|
||||||
forward . 9.9.9.9
|
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
|
// Ready implements the ready.Readiness interface, once this flips to true CoreDNS
|
||||||
// assumes this plugin is ready for queries; it is not checked again.
|
// 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
|
package split
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
"github.com/coredns/caddy"
|
"github.com/coredns/caddy"
|
||||||
"github.com/coredns/coredns/core/dnsserver"
|
"github.com/coredns/coredns/core/dnsserver"
|
||||||
"github.com/coredns/coredns/plugin"
|
"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
|
// 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".
|
// for parsing any extra options the example plugin may have. The first token this function sees is "example".
|
||||||
func setup(c *caddy.Controller) error {
|
func setup(c *caddy.Controller) error {
|
||||||
c.Next() // Ignore "example" and give us the next token.
|
s := Split{}
|
||||||
if c.NextArg() {
|
for c.Next() {
|
||||||
// If there was another token, return an error, because we don't have any configuration.
|
r := rule{}
|
||||||
// Any errors returned from this setup function should be wrapped with plugin.Error, so we
|
args := c.RemainingArgs()
|
||||||
// can present a slightly nicer error message to the user.
|
r.zones = plugin.OriginsFromArgsOrServerBlock(args, c.ServerBlockKeys)
|
||||||
return plugin.Error("split", c.ArgErr())
|
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.
|
// Add the Plugin to CoreDNS, so Servers can use it in their plugin chain.
|
||||||
dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler {
|
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.
|
// All OK, return a nil error.
|
||||||
|
@ -13,9 +13,4 @@ func TestSetup(t *testing.T) {
|
|||||||
if err := setup(c); err != nil {
|
if err := setup(c); err != nil {
|
||||||
t.Fatalf("Expected no errors, but got: %v", err)
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"net"
|
||||||
|
|
||||||
"github.com/coredns/coredns/plugin"
|
"github.com/coredns/coredns/plugin"
|
||||||
"github.com/coredns/coredns/plugin/metrics"
|
"github.com/coredns/coredns/plugin/metrics"
|
||||||
clog "github.com/coredns/coredns/plugin/pkg/log"
|
clog "github.com/coredns/coredns/plugin/pkg/log"
|
||||||
|
"github.com/coredns/coredns/request"
|
||||||
"github.com/miekg/dns"
|
"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.
|
// Split is an example plugin to show how to write a plugin.
|
||||||
type Split struct {
|
type Split struct {
|
||||||
Next plugin.Handler
|
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
|
// ServeDNS implements the plugin.Handler interface. This method gets called when example is used
|
||||||
// in a Server.
|
// 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
|
// 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.
|
// 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
|
// Here we wrap the dns.ResponseWriter in a new ResponseWriter and call the next plugin, when the
|
||||||
// answer comes back, it will print "example".
|
// 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")
|
log.Debug("Received response")
|
||||||
|
|
||||||
// Wrap.
|
// Wrap.
|
||||||
pw := NewResponsePrinter(w)
|
pw := s.NewResponsePrinter(w, r)
|
||||||
|
|
||||||
// Export metric with the server label set to the current server handling the request.
|
// Export metric with the server label set to the current server handling the request.
|
||||||
requestCount.WithLabelValues(metrics.WithServer(ctx)).Inc()
|
requestCount.WithLabelValues(metrics.WithServer(ctx)).Inc()
|
||||||
|
|
||||||
// Call next plugin (if any).
|
// 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.
|
// 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.
|
// ResponsePrinter wrap a dns.ResponseWriter and will write example to standard output when WriteMsg is called.
|
||||||
type ResponsePrinter struct {
|
type ResponsePrinter struct {
|
||||||
dns.ResponseWriter
|
dns.ResponseWriter
|
||||||
|
state request.Request
|
||||||
|
r *dns.Msg
|
||||||
|
src net.IP
|
||||||
|
rules []rule
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewResponsePrinter returns ResponseWriter.
|
// NewResponsePrinter returns ResponseWriter.
|
||||||
func NewResponsePrinter(w dns.ResponseWriter) *ResponsePrinter {
|
func (s Split) NewResponsePrinter(w dns.ResponseWriter, r *dns.Msg) *ResponsePrinter {
|
||||||
return &ResponsePrinter{ResponseWriter: w}
|
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.
|
// WriteMsg calls the underlying ResponseWriter's WriteMsg method and prints "example" to standard output.
|
||||||
func (r *ResponsePrinter) WriteMsg(res *dns.Msg) error {
|
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)
|
return r.ResponseWriter.WriteMsg(res)
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
package split
|
package split
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
golog "log"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/coredns/coredns/plugin/pkg/dnstest"
|
"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.
|
// Create a new Split Plugin. Use the test.ErrorHandler as the next plugin.
|
||||||
x := Split{Next: test.ErrorHandler()}
|
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()
|
ctx := context.TODO()
|
||||||
r := new(dns.Msg)
|
r := new(dns.Msg)
|
||||||
r.SetQuestion("example.org.", dns.TypeA)
|
r.SetQuestion("example.org.", dns.TypeA)
|
||||||
@ -31,7 +23,5 @@ func TestExample(t *testing.T) {
|
|||||||
|
|
||||||
// Call our plugin directly, and check the result.
|
// Call our plugin directly, and check the result.
|
||||||
x.ServeDNS(ctx, rec, r)
|
x.ServeDNS(ctx, rec, r)
|
||||||
if a := b.String(); !strings.Contains(a, "[INFO] plugin/split: example") {
|
t.Log(rec.Msg)
|
||||||
t.Errorf("Failed to print '%s', got %s", "[INFO] plugin/split: example", a)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user