aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar An Xiao <hac@zju.edu.cn> 2019-09-04 23:43:45 +0800
committerGravatar Yong Tang <yong.tang.github@outlook.com> 2019-09-04 08:43:45 -0700
commit79f37a1460cc52ce6c63110f4df33316a36af3a5 (patch)
tree35b3ddcc68ba82eabb9393cb166b5de76a42bb29
parent7894154bfd2f1960c6842318d8ee99c194a04179 (diff)
downloadcoredns-79f37a1460cc52ce6c63110f4df33316a36af3a5.tar.gz
coredns-79f37a1460cc52ce6c63110f4df33316a36af3a5.tar.zst
coredns-79f37a1460cc52ce6c63110f4df33316a36af3a5.zip
Add plugin ACL for source ip filtering (#3103)
* Add plugin ACL for source ip filtering Signed-off-by: An Xiao <hac@zju.edu.cn> * Allow all arguments to be optional and support multiple qtypes in a single policy Signed-off-by: An Xiao <hac@zju.edu.cn> * Add newline before third party imports Signed-off-by: An Xiao <hac@zju.edu.cn> * Use camel instead of underscore in method name Signed-off-by: An Xiao <hac@zju.edu.cn> * Start with an upper case letter in t.Errorf() Signed-off-by: An Xiao <hac@zju.edu.cn> * Use the qtype parse logic in miekg/dns Signed-off-by: An Xiao <hac@zju.edu.cn> * Use third party trie implementation as the ip filter Signed-off-by: An Xiao <hac@zju.edu.cn> * Update based on rdrozhdzh's comment Signed-off-by: An Xiao <hac@zju.edu.cn> * Change the type of action to int Signed-off-by: An Xiao <hac@zju.edu.cn> * Add IPv6 support Signed-off-by: An Xiao <hac@zju.edu.cn> * Update plugin.cfg Signed-off-by: An Xiao <hac@zju.edu.cn> * Remove file functionality Signed-off-by: An Xiao <hac@zju.edu.cn> * Update Signed-off-by: Xiao An <hac@zju.edu.cn> * Update README Signed-off-by: Xiao An <hac@zju.edu.cn> * remove comments Signed-off-by: Xiao An <hac@zju.edu.cn> * update Signed-off-by: Xiao An <hac@zju.edu.cn> * Update dependency Signed-off-by: Xiao An <hac@zju.edu.cn> * Update Signed-off-by: Xiao An <hac@zju.edu.cn> * Update test Signed-off-by: Xiao An <hac@zju.edu.cn> * Add OWNERS Signed-off-by: Xiao An <hac@zju.edu.cn> * Refactor shouldBlock and skip useless check Signed-off-by: Xiao An <hac@zju.edu.cn> * Introduce ActionNone Signed-off-by: Xiao An <hac@zju.edu.cn> * Update label name Signed-off-by: Xiao An <hac@zju.edu.cn> * Avoid capitalizing private types Signed-off-by: Xiao An <hac@zju.edu.cn>
-rw-r--r--core/dnsserver/zdirectives.go1
-rw-r--r--core/plugin/zplugin.go1
-rw-r--r--go.mod3
-rw-r--r--go.sum30
-rw-r--r--plugin.cfg1
-rw-r--r--plugin/acl/OWNERS7
-rw-r--r--plugin/acl/README.md68
-rw-r--r--plugin/acl/acl.go115
-rw-r--r--plugin/acl/acl_test.go396
-rw-r--r--plugin/acl/metrics.go24
-rw-r--r--plugin/acl/setup.go166
-rw-r--r--plugin/acl/setup_test.go245
12 files changed, 1031 insertions, 26 deletions
diff --git a/core/dnsserver/zdirectives.go b/core/dnsserver/zdirectives.go
index 6dfb99226..d1310a7f1 100644
--- a/core/dnsserver/zdirectives.go
+++ b/core/dnsserver/zdirectives.go
@@ -26,6 +26,7 @@ var Directives = []string{
"errors",
"log",
"dnstap",
+ "acl",
"any",
"chaos",
"loadbalance",
diff --git a/core/plugin/zplugin.go b/core/plugin/zplugin.go
index d522ed294..3a31f4127 100644
--- a/core/plugin/zplugin.go
+++ b/core/plugin/zplugin.go
@@ -5,6 +5,7 @@ package plugin
import (
// Include all plugins.
_ "github.com/caddyserver/caddy/onevent"
+ _ "github.com/coredns/coredns/plugin/acl"
_ "github.com/coredns/coredns/plugin/any"
_ "github.com/coredns/coredns/plugin/auto"
_ "github.com/coredns/coredns/plugin/autopath"
diff --git a/go.mod b/go.mod
index 28224630f..bfa344576 100644
--- a/go.mod
+++ b/go.mod
@@ -17,6 +17,7 @@ require (
github.com/coreos/bbolt v1.3.2 // indirect
github.com/coreos/go-systemd v0.0.0-20190212144455-93d5ec2c7f76 // indirect
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect
+ github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc // indirect
github.com/dnstap/golang-dnstap v0.0.0-20170829151710-2cf77a2b5e11
github.com/evanphx/json-patch v4.1.0+incompatible // indirect
github.com/farsightsec/golang-framestream v0.0.0-20181102145529-8a0cb8ba8710
@@ -26,6 +27,7 @@ require (
github.com/gophercloud/gophercloud v0.0.0-20190307220656-fe1ba5ce12dd // indirect
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645
github.com/imdario/mergo v0.3.7 // indirect
+ github.com/infobloxopen/go-trees v0.0.0-20190313150506-2af4e13f9062
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1
github.com/miekg/dns v1.1.16
@@ -37,6 +39,7 @@ require (
github.com/prometheus/client_golang v1.1.0
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90
github.com/prometheus/common v0.6.0
+ github.com/seiflotfy/cuckoofilter v0.0.0-20190302225222-764cb5258d9b
github.com/sirupsen/logrus v1.4.2 // indirect
github.com/spf13/cobra v0.0.5 // indirect
github.com/tinylib/msgp v1.1.0 // indirect
diff --git a/go.sum b/go.sum
index 81c952daa..155e15813 100644
--- a/go.sum
+++ b/go.sum
@@ -37,7 +37,6 @@ github.com/DataDog/zstd v1.3.5 h1:DtpNbljikUepEPD16hD4LvIcmhnhdLTiW/5pHgbmp14=
github.com/DataDog/zstd v1.3.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
github.com/Shopify/sarama v1.21.0 h1:0GKs+e8mn1RRUzfg9oUXv3v7ZieQLmOZF/bfnmmGhM8=
github.com/Shopify/sarama v1.21.0/go.mod h1:yuqtN/pe8cXRWG5zPaO7hCfNJp5MwmkoJEoLjkm5tCQ=
-github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
@@ -49,7 +48,6 @@ github.com/aws/aws-sdk-go v1.23.8/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN
github.com/aws/aws-sdk-go v1.23.13 h1:l/NG+mgQFRGG3dsFzEj0jw9JIs/zYdtU6MXhY1WIDmM=
github.com/aws/aws-sdk-go v1.23.13/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
-github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
@@ -64,7 +62,6 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/coredns/federation v0.0.0-20190818181423-e032b096babe h1:ND08lR/TclI9W4dScCwdRESOacCCdF3FkuB5pBIOv1U=
github.com/coredns/federation v0.0.0-20190818181423-e032b096babe/go.mod h1:MoqTEFX8GlnKkyq8eBCF94VzkNAOgjdlCJ+Pz/oCLPk=
-github.com/coreos/bbolt v1.3.2 h1:wZwiHHUieZCquLkDL0B8UhzreNWsPHooDAG3q34zk0s=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/etcd v3.3.15+incompatible h1:+9RjdC18gMxNQVvSiXvObLu29mOFmkgdsB4cRTlV+EE=
@@ -86,6 +83,7 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/dgrijalva/jwt-go v0.0.0-20160705203006-01aeca54ebda/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
+github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TRo4=
github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
github.com/dnstap/golang-dnstap v0.0.0-20170829151710-2cf77a2b5e11 h1:m8nX8hsUghn853BJ5qB0lX+VvS6LTJPksWyILFZRYN4=
@@ -109,9 +107,7 @@ github.com/farsightsec/golang-framestream v0.0.0-20181102145529-8a0cb8ba8710/go.
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
-github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
-github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-acme/lego v2.5.0+incompatible h1:5fNN9yRQfv8ymH3DSsxla+4aYeQt2IgfZqHKVnK8f0s=
github.com/go-acme/lego v2.5.0+incompatible/go.mod h1:yzMNe9CasVUhkquNvti5nAtPmG94USbYxYrZfTkIn0M=
@@ -125,7 +121,6 @@ github.com/gogo/protobuf v0.0.0-20171007142547-342cbe0a0415/go.mod h1:r8qH/GZQm5
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
-github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef h1:veQD95Isof8w9/WXiA+pa3tz3fJXkt5B7QaRBrM62gk=
@@ -134,7 +129,6 @@ github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -142,12 +136,10 @@ github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pO
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20160524151835-7d79101e329e/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
-github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
-github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck=
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@@ -178,7 +170,6 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92Bcuy
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.8.5 h1:2+KSC78XiO6Qy0hIjfc1OD9H+hsaJdJlb8Kqsd41CTE=
github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
-github.com/grpc-ecosystem/grpc-gateway v1.9.5 h1:UImYN5qQ8tuGpGE16ZmjvcTtTw24zw1QAp/SlnNrZhI=
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU=
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw=
@@ -194,10 +185,11 @@ github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJ
github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI=
github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/infobloxopen/go-trees v0.0.0-20190313150506-2af4e13f9062 h1:d3VSuNcgTCn21dNMm8g412Fck/XWFmMj4nJhhHT7ZZ0=
+github.com/infobloxopen/go-trees v0.0.0-20190313150506-2af4e13f9062/go.mod h1:PcNJqIlcX/dj3DTG/+QQnRvSgTMG6CLpRMjWcv4+J6w=
github.com/jimstudt/http-authentication v0.0.0-20140401203705-3eca13d6893a/go.mod h1:wK6yTYYcgjHE1Z1QtXACPDjcFJyBskHEdagmnq3vsP8=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
-github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs=
@@ -211,14 +203,10 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
github.com/klauspost/cpuid v1.2.0 h1:NMpwD2G9JSFOE1/TJjGSo5zG7Yb2bTe7eq1jH+irmeE=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
-github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
-github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
-github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
github.com/lucas-clemente/aes12 v0.0.0-20171027163421-cd47fb39b79f/go.mod h1:JpH9J1c9oX6otFSgdUHwUBUizmKlrMjxWnIAjff4m04=
@@ -269,9 +257,7 @@ github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG
github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0 h1:vrDKnkGzuGvhNAL56c7DBz29ZL+KxnoR0x7enabFceM=
@@ -295,10 +281,9 @@ github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqn
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/russross/blackfriday v0.0.0-20170610170232-067529f716f4/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
+github.com/seiflotfy/cuckoofilter v0.0.0-20190302225222-764cb5258d9b/go.mod h1:ET5mVvNjwaGXRgZxO9UZr7X+8eAf87AfIYNwRSp9s4Y=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
-github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
-github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
@@ -314,7 +299,6 @@ github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DM
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
-github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/tinylib/msgp v1.1.0 h1:9fQd+ICuRIu/ue4vxJZu6/LzxN0HwMds2nq/0cFvxHU=
github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
@@ -383,7 +367,6 @@ golang.org/x/net v0.0.0-20190328230028-74de082e2cca/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190603091049-60506f45cf65 h1:+rhAzEzT3f4JtomfC371qB+0Ola2caSKcY69NUBZrRQ=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -452,14 +435,12 @@ golang.org/x/tools v0.0.0-20190624190245-7f2218787638/go.mod h1:/rFqwRUd4F7ZHNgw
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
-google.golang.org/api v0.7.0 h1:9sdfJOzWlkqPltHAuzT2Cp+yrBeY1KRVYgms8soxMwM=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.9.0 h1:jbyannxz0XFD3zdjgrSUsaJbgpH4eTrkdhRChkHPfO8=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@@ -473,7 +454,6 @@ google.golang.org/genproto v0.0.0-20190701230453-710ae3a149df h1:k3DT34vxk64+4bD
google.golang.org/genproto v0.0.0-20190701230453-710ae3a149df/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
-google.golang.org/grpc v1.21.1 h1:j6XxA85m/6txkUCHvzlV5f+HBNl/1r5cZ2A/3IEFOO8=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0 h1:AzbTB6ux+okLTzP8Ru1Xs41C303zdcfEht7MQnYJt5A=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
@@ -481,7 +461,6 @@ gopkg.in/DataDog/dd-trace-go.v1 v1.16.1 h1:Dngw1zun6yTYFHNdzEWBlrJzFA2QJMjSA2sZ4
gopkg.in/DataDog/dd-trace-go.v1 v1.16.1/go.mod h1:DVp8HmDh8PuTu2Z0fVVlBsyWaC++fzwVCaGWylTe3tg=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
@@ -494,7 +473,6 @@ gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/square/go-jose.v2 v2.2.2 h1:orlkJ3myw8CN1nVQHBFfloD+L3egixIa4FvUP6RosSA=
gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
-gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
diff --git a/plugin.cfg b/plugin.cfg
index ff856f724..66552cd80 100644
--- a/plugin.cfg
+++ b/plugin.cfg
@@ -35,6 +35,7 @@ prometheus:metrics
errors:errors
log:log
dnstap:dnstap
+acl:acl
any:any
chaos:chaos
loadbalance:loadbalance
diff --git a/plugin/acl/OWNERS b/plugin/acl/OWNERS
new file mode 100644
index 000000000..5921fce19
--- /dev/null
+++ b/plugin/acl/OWNERS
@@ -0,0 +1,7 @@
+reviewers:
+ - miekg
+ - ihac
+approvers:
+ - miekg
+ - ihac
+
diff --git a/plugin/acl/README.md b/plugin/acl/README.md
new file mode 100644
index 000000000..49b6895a9
--- /dev/null
+++ b/plugin/acl/README.md
@@ -0,0 +1,68 @@
+# acl
+
+*acl* - enforces access control policies on source ip and prevents unauthorized access to DNS servers.
+
+## Description
+
+With `acl` enabled, users are able to block suspicous DNS queries by configuring IP filter rule sets, i.e. allowing authorized queries to recurse or blocking unauthorized queries.
+
+This plugin can be used multiple times per Server Block.
+
+## Syntax
+
+```
+acl [ZONES...] {
+ ACTION [type QTYPE...] [net SOURCE...]
+}
+```
+
+- **ZONES** zones it should be authoritative for. If empty, the zones from the configuration block are used.
+- **ACTION** (*allow* or *block*) defines the way to deal with DNS queries matched by this rule. The default action is *allow*, which means a DNS query not matched by any rules will be allowed to recurse.
+- **QTYPE** is the query type to match for the requests to be allowed or blocked. Common resource record types are supported. `*` stands for all record types. The default behavior for an omitted `type QTYPE...` is to match all kinds of DNS queries (same as `type *`).
+- **SOURCE** is the source IP address to match for the requests to be allowed or blocked. Typical CIDR notation and single IP address are supported. `*` stands for all possible source IP addresses.
+
+## Examples
+
+To demonstrate the usage of plugin acl, here we provide some typical examples.
+
+Block all DNS queries with record type A from 192.168.0.0/16:
+
+~~~ Corefile
+. {
+ acl {
+ block type A net 192.168.0.0/16
+ }
+}
+~~~
+
+Block all DNS queries from 192.168.0.0/16 except for 192.168.1.0/24:
+
+~~~ Corefile
+. {
+ acl {
+ allow net 192.168.1.0/24
+ block net 192.168.0.0/16
+ }
+}
+```
+
+Allow only DNS queries from 192.168.0.0/24 and 192.168.1.0/24:
+
+~~~ Corefile
+. {
+ acl {
+ allow net 192.168.0.0/16 192.168.1.0/24
+ block
+ }
+}
+~~~
+
+Block all DNS queries from 192.168.1.0/24 towards a.example.org:
+
+~~~ Corefile
+example.org {
+ acl a.example.org {
+ block net 192.168.1.0/24
+ }
+}
+~~~
diff --git a/plugin/acl/acl.go b/plugin/acl/acl.go
new file mode 100644
index 000000000..b25138a30
--- /dev/null
+++ b/plugin/acl/acl.go
@@ -0,0 +1,115 @@
+package acl
+
+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/infobloxopen/go-trees/iptree"
+ "github.com/miekg/dns"
+)
+
+var log = clog.NewWithPlugin("acl")
+
+// ACL enforces access control policies on DNS queries.
+type ACL struct {
+ Next plugin.Handler
+
+ Rules []rule
+}
+
+// rule defines a list of Zones and some ACL policies which will be
+// enforced on them.
+type rule struct {
+ zones []string
+ policies []policy
+}
+
+// action defines the action against queries.
+type action int
+
+// policy defines the ACL policy for DNS queries.
+// A policy performs the specified action (block/allow) on all DNS queries
+// matched by source IP or QTYPE.
+type policy struct {
+ action action
+ qtypes map[uint16]struct{}
+ filter *iptree.Tree
+}
+
+const (
+ // actionNone does nothing on the queries.
+ actionNone = iota
+ // actionAllow allows authorized queries to recurse.
+ actionAllow
+ // actionBlock blocks unauthorized queries towards protected DNS zones.
+ actionBlock
+)
+
+// ServeDNS implements the plugin.Handler interface.
+func (a ACL) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
+ state := request.Request{W: w, Req: r}
+
+RulesCheckLoop:
+ for _, rule := range a.Rules {
+ // check zone.
+ zone := plugin.Zones(rule.zones).Matches(state.Name())
+ if zone == "" {
+ continue
+ }
+
+ action := matchWithPolicies(rule.policies, w, r)
+ switch action {
+ case actionBlock:
+ {
+ m := new(dns.Msg)
+ m.SetRcode(r, dns.RcodeRefused)
+ w.WriteMsg(m)
+ RequestBlockCount.WithLabelValues(metrics.WithServer(ctx), zone).Inc()
+ return dns.RcodeSuccess, nil
+ }
+ case actionAllow:
+ {
+ break RulesCheckLoop
+ }
+ }
+ }
+
+ RequestAllowCount.WithLabelValues(metrics.WithServer(ctx)).Inc()
+ return plugin.NextOrFailure(state.Name(), a.Next, ctx, w, r)
+}
+
+// matchWithPolicies matches the DNS query with a list of ACL polices and returns suitable
+// action agains the query.
+func matchWithPolicies(policies []policy, w dns.ResponseWriter, r *dns.Msg) action {
+ state := request.Request{W: w, Req: r}
+
+ ip := net.ParseIP(state.IP())
+ qtype := state.QType()
+ for _, policy := range policies {
+ // dns.TypeNone matches all query types.
+ _, matchAll := policy.qtypes[dns.TypeNone]
+ _, match := policy.qtypes[qtype]
+ if !matchAll && !match {
+ continue
+ }
+
+ _, contained := policy.filter.GetByIP(ip)
+ if !contained {
+ continue
+ }
+
+ // matched.
+ return policy.action
+ }
+ return actionNone
+}
+
+// Name implements the plugin.Handler interface.
+func (a ACL) Name() string {
+ return "acl"
+}
diff --git a/plugin/acl/acl_test.go b/plugin/acl/acl_test.go
new file mode 100644
index 000000000..9b23edc53
--- /dev/null
+++ b/plugin/acl/acl_test.go
@@ -0,0 +1,396 @@
+package acl
+
+import (
+ "context"
+ "testing"
+
+ "github.com/coredns/coredns/plugin/test"
+
+ "github.com/caddyserver/caddy"
+ "github.com/miekg/dns"
+)
+
+type testResponseWriter struct {
+ test.ResponseWriter
+ Rcode int
+}
+
+func (t *testResponseWriter) setRemoteIP(ip string) {
+ t.RemoteIP = ip
+}
+
+// WriteMsg implement dns.ResponseWriter interface.
+func (t *testResponseWriter) WriteMsg(m *dns.Msg) error {
+ t.Rcode = m.Rcode
+ return nil
+}
+
+func NewTestControllerWithZones(input string, zones []string) *caddy.Controller {
+ ctr := caddy.NewTestController("dns", input)
+ for _, zone := range zones {
+ ctr.ServerBlockKeys = append(ctr.ServerBlockKeys, zone)
+ }
+ return ctr
+}
+
+func TestACLServeDNS(t *testing.T) {
+ type args struct {
+ domain string
+ sourceIP string
+ qtype uint16
+ }
+ tests := []struct {
+ name string
+ config string
+ zones []string
+ args args
+ wantRcode int
+ wantErr bool
+ }{
+ // IPv4 tests.
+ {
+ "Blacklist 1 BLOCKED",
+ `acl example.org {
+ block type A net 192.168.0.0/16
+ }`,
+ []string{},
+ args{
+ "www.example.org.",
+ "192.168.0.2",
+ dns.TypeA,
+ },
+ dns.RcodeRefused,
+ false,
+ },
+ {
+ "Blacklist 1 ALLOWED",
+ `acl example.org {
+ block type A net 192.168.0.0/16
+ }`,
+ []string{},
+ args{
+ "www.example.org.",
+ "192.167.0.2",
+ dns.TypeA,
+ },
+ dns.RcodeSuccess,
+ false,
+ },
+ {
+ "Blacklist 2 BLOCKED",
+ `
+ acl example.org {
+ block type * net 192.168.0.0/16
+ }`,
+ []string{},
+ args{
+ "www.example.org.",
+ "192.168.0.2",
+ dns.TypeAAAA,
+ },
+ dns.RcodeRefused,
+ false,
+ },
+ {
+ "Blacklist 3 BLOCKED",
+ `acl example.org {
+ block type A
+ }`,
+ []string{},
+ args{
+ "www.example.org.",
+ "10.1.0.2",
+ dns.TypeA,
+ },
+ dns.RcodeRefused,
+ false,
+ },
+ {
+ "Blacklist 3 ALLOWED",
+ `acl example.org {
+ block type A
+ }`,
+ []string{},
+ args{
+ "www.example.org.",
+ "10.1.0.2",
+ dns.TypeAAAA,
+ },
+ dns.RcodeSuccess,
+ false,
+ },
+ {
+ "Blacklist 4 Single IP BLOCKED",
+ `acl example.org {
+ block type A net 192.168.1.2
+ }`,
+ []string{},
+ args{
+ "www.example.org.",
+ "192.168.1.2",
+ dns.TypeA,
+ },
+ dns.RcodeRefused,
+ false,
+ },
+ {
+ "Blacklist 4 Single IP ALLOWED",
+ `acl example.org {
+ block type A net 192.168.1.2
+ }`,
+ []string{},
+ args{
+ "www.example.org.",
+ "192.168.1.3",
+ dns.TypeA,
+ },
+ dns.RcodeSuccess,
+ false,
+ },
+ {
+ "Whitelist 1 ALLOWED",
+ `acl example.org {
+ allow net 192.168.0.0/16
+ block
+ }`,
+ []string{},
+ args{
+ "www.example.org.",
+ "192.168.0.2",
+ dns.TypeA,
+ },
+ dns.RcodeSuccess,
+ false,
+ },
+ {
+ "Whitelist 1 REFUSED",
+ `acl example.org {
+ allow type * net 192.168.0.0/16
+ block
+ }`,
+ []string{},
+ args{
+ "www.example.org.",
+ "10.1.0.2",
+ dns.TypeA,
+ },
+ dns.RcodeRefused,
+ false,
+ },
+ {
+ "Fine-Grained 1 REFUSED",
+ `acl a.example.org {
+ block type * net 192.168.1.0/24
+ }`,
+ []string{"example.org"},
+ args{
+ "a.example.org.",
+ "192.168.1.2",
+ dns.TypeA,
+ },
+ dns.RcodeRefused,
+ false,
+ },
+ {
+ "Fine-Grained 1 ALLOWED",
+ `acl a.example.org {
+ block net 192.168.1.0/24
+ }`,
+ []string{"example.org"},
+ args{
+ "www.example.org.",
+ "192.168.1.2",
+ dns.TypeA,
+ },
+ dns.RcodeSuccess,
+ false,
+ },
+ {
+ "Fine-Grained 2 REFUSED",
+ `acl {
+ block net 192.168.1.0/24
+ }`,
+ []string{"example.org"},
+ args{
+ "a.example.org.",
+ "192.168.1.2",
+ dns.TypeA,
+ },
+ dns.RcodeRefused,
+ false,
+ },
+ {
+ "Fine-Grained 2 ALLOWED",
+ `acl {
+ block net 192.168.1.0/24
+ }`,
+ []string{"example.org"},
+ args{
+ "a.example.com.",
+ "192.168.1.2",
+ dns.TypeA,
+ },
+ dns.RcodeSuccess,
+ false,
+ },
+ {
+ "Fine-Grained 3 REFUSED",
+ `acl a.example.org {
+ block net 192.168.1.0/24
+ }
+ acl b.example.org {
+ block type * net 192.168.2.0/24
+ }`,
+ []string{"example.org"},
+ args{
+ "b.example.org.",
+ "192.168.2.2",
+ dns.TypeA,
+ },
+ dns.RcodeRefused,
+ false,
+ },
+ {
+ "Fine-Grained 3 ALLOWED",
+ `acl a.example.org {
+ block net 192.168.1.0/24
+ }
+ acl b.example.org {
+ block net 192.168.2.0/24
+ }`,
+ []string{"example.org"},
+ args{
+ "b.example.org.",
+ "192.168.1.2",
+ dns.TypeA,
+ },
+ dns.RcodeSuccess,
+ false,
+ },
+ // IPv6 tests.
+ {
+ "Blacklist 1 BLOCKED IPv6",
+ `acl example.org {
+ block type A net 2001:db8:abcd:0012::0/64
+ }`,
+ []string{},
+ args{
+ "www.example.org.",
+ "2001:db8:abcd:0012::1230",
+ dns.TypeA,
+ },
+ dns.RcodeRefused,
+ false,
+ },
+ {
+ "Blacklist 1 ALLOWED IPv6",
+ `acl example.org {
+ block type A net 2001:db8:abcd:0012::0/64
+ }`,
+ []string{},
+ args{
+ "www.example.org.",
+ "2001:db8:abcd:0013::0",
+ dns.TypeA,
+ },
+ dns.RcodeSuccess,
+ false,
+ },
+ {
+ "Blacklist 2 BLOCKED IPv6",
+ `acl example.org {
+ block type A
+ }`,
+ []string{},
+ args{
+ "www.example.org.",
+ "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
+ dns.TypeA,
+ },
+ dns.RcodeRefused,
+ false,
+ },
+ {
+ "Blacklist 3 Single IP BLOCKED IPv6",
+ `acl example.org {
+ block type A net 2001:0db8:85a3:0000:0000:8a2e:0370:7334
+ }`,
+ []string{},
+ args{
+ "www.example.org.",
+ "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
+ dns.TypeA,
+ },
+ dns.RcodeRefused,
+ false,
+ },
+ {
+ "Blacklist 3 Single IP ALLOWED IPv6",
+ `acl example.org {
+ block type A net 2001:0db8:85a3:0000:0000:8a2e:0370:7334
+ }`,
+ []string{},
+ args{
+ "www.example.org.",
+ "2001:0db8:85a3:0000:0000:8a2e:0370:7335",
+ dns.TypeA,
+ },
+ dns.RcodeSuccess,
+ false,
+ },
+ {
+ "Fine-Grained 1 REFUSED IPv6",
+ `acl a.example.org {
+ block type * net 2001:db8:abcd:0012::0/64
+ }`,
+ []string{"example.org"},
+ args{
+ "a.example.org.",
+ "2001:db8:abcd:0012:2019::0",
+ dns.TypeA,
+ },
+ dns.RcodeRefused,
+ false,
+ },
+ {
+ "Fine-Grained 1 ALLOWED IPv6",
+ `acl a.example.org {
+ block net 2001:db8:abcd:0012::0/64
+ }`,
+ []string{"example.org"},
+ args{
+ "www.example.org.",
+ "2001:db8:abcd:0012:2019::0",
+ dns.TypeA,
+ },
+ dns.RcodeSuccess,
+ false,
+ },
+ }
+
+ ctx := context.Background()
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ ctr := NewTestControllerWithZones(tt.config, tt.zones)
+ a, err := parse(ctr)
+ a.Next = test.NextHandler(dns.RcodeSuccess, nil)
+ if err != nil {
+ t.Errorf("Error: Cannot parse acl from config: %v", err)
+ return
+ }
+
+ w := &testResponseWriter{}
+ m := new(dns.Msg)
+ w.setRemoteIP(tt.args.sourceIP)
+ m.SetQuestion(tt.args.domain, tt.args.qtype)
+ _, err = a.ServeDNS(ctx, w, m)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("Error: acl.ServeDNS() error = %v, wantErr %v", err, tt.wantErr)
+ return
+ }
+ if w.Rcode != tt.wantRcode {
+ t.Errorf("Error: acl.ServeDNS() Rcode = %v, want %v", w.Rcode, tt.wantRcode)
+ }
+ })
+ }
+}
diff --git a/plugin/acl/metrics.go b/plugin/acl/metrics.go
new file mode 100644
index 000000000..442ea2374
--- /dev/null
+++ b/plugin/acl/metrics.go
@@ -0,0 +1,24 @@
+package acl
+
+import (
+ "github.com/coredns/coredns/plugin"
+
+ "github.com/prometheus/client_golang/prometheus"
+)
+
+var (
+ // RequestBlockCount is the number of DNS requests being blocked.
+ RequestBlockCount = prometheus.NewCounterVec(prometheus.CounterOpts{
+ Namespace: plugin.Namespace,
+ Subsystem: "dns",
+ Name: "request_block_count_total",
+ Help: "Counter of DNS requests being blocked.",
+ }, []string{"server", "zone"})
+ // RequestAllowCount is the number of DNS requests being Allowed.
+ RequestAllowCount = prometheus.NewCounterVec(prometheus.CounterOpts{
+ Namespace: plugin.Namespace,
+ Subsystem: "dns",
+ Name: "request_allow_count_total",
+ Help: "Counter of DNS requests being allowed.",
+ }, []string{"server"})
+)
diff --git a/plugin/acl/setup.go b/plugin/acl/setup.go
new file mode 100644
index 000000000..1179175dd
--- /dev/null
+++ b/plugin/acl/setup.go
@@ -0,0 +1,166 @@
+package acl
+
+import (
+ "net"
+ "strings"
+
+ "github.com/coredns/coredns/core/dnsserver"
+ "github.com/coredns/coredns/plugin"
+ "github.com/coredns/coredns/plugin/metrics"
+
+ "github.com/caddyserver/caddy"
+ "github.com/infobloxopen/go-trees/iptree"
+ "github.com/miekg/dns"
+)
+
+func init() {
+ caddy.RegisterPlugin("acl", caddy.Plugin{
+ ServerType: "dns",
+ Action: setup,
+ })
+}
+
+func newDefaultFilter() *iptree.Tree {
+ defaultFilter := iptree.NewTree()
+ _, IPv4All, _ := net.ParseCIDR("0.0.0.0/0")
+ _, IPv6All, _ := net.ParseCIDR("::/0")
+ defaultFilter.InplaceInsertNet(IPv4All, struct{}{})
+ defaultFilter.InplaceInsertNet(IPv6All, struct{}{})
+ return defaultFilter
+}
+
+func setup(c *caddy.Controller) error {
+ a, err := parse(c)
+ if err != nil {
+ return plugin.Error("acl", err)
+ }
+
+ dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler {
+ a.Next = next
+ return a
+ })
+
+ // Register all metrics.
+ c.OnStartup(func() error {
+ metrics.MustRegister(c, RequestBlockCount, RequestAllowCount)
+ return nil
+ })
+ return nil
+}
+
+func parse(c *caddy.Controller) (ACL, error) {
+ a := ACL{}
+ for c.Next() {
+ r := rule{}
+ r.zones = c.RemainingArgs()
+ if len(r.zones) == 0 {
+ // if empty, the zones from the configuration block are used.
+ r.zones = make([]string, len(c.ServerBlockKeys))
+ copy(r.zones, c.ServerBlockKeys)
+ }
+ for i := range r.zones {
+ r.zones[i] = plugin.Host(r.zones[i]).Normalize()
+ }
+
+ for c.NextBlock() {
+ p := policy{}
+
+ action := strings.ToLower(c.Val())
+ if action == "allow" {
+ p.action = actionAllow
+ } else if action == "block" {
+ p.action = actionBlock
+ } else {
+ return a, c.Errf("unexpected token %q; expect 'allow' or 'block'", c.Val())
+ }
+
+ p.qtypes = make(map[uint16]struct{})
+ p.filter = iptree.NewTree()
+
+ hasTypeSection := false
+ hasNetSection := false
+
+ remainingTokens := c.RemainingArgs()
+ for len(remainingTokens) > 0 {
+ if !isPreservedIdentifier(remainingTokens[0]) {
+ return a, c.Errf("unexpected token %q; expect 'type | net'", remainingTokens[0])
+ }
+ section := strings.ToLower(remainingTokens[0])
+
+ i := 1
+ var tokens []string
+ for ; i < len(remainingTokens) && !isPreservedIdentifier(remainingTokens[i]); i++ {
+ tokens = append(tokens, remainingTokens[i])
+ }
+ remainingTokens = remainingTokens[i:]
+
+ if len(tokens) == 0 {
+ return a, c.Errf("no token specified in %q section", section)
+ }
+
+ switch section {
+ case "type":
+ hasTypeSection = true
+ for _, token := range tokens {
+ if token == "*" {
+ p.qtypes[dns.TypeNone] = struct{}{}
+ break
+ }
+ qtype, ok := dns.StringToType[token]
+ if !ok {
+ return a, c.Errf("unexpected token %q; expect legal QTYPE", token)
+ }
+ p.qtypes[qtype] = struct{}{}
+ }
+ case "net":
+ hasNetSection = true
+ for _, token := range tokens {
+ if token == "*" {
+ p.filter = newDefaultFilter()
+ break
+ }
+ token = normalize(token)
+ _, source, err := net.ParseCIDR(token)
+ if err != nil {
+ return a, c.Errf("illegal CIDR notation %q", token)
+ }
+ p.filter.InplaceInsertNet(source, struct{}{})
+ }
+ default:
+ return a, c.Errf("unexpected token %q; expect 'type | net'", section)
+ }
+ }
+
+ // optional `type` section means all record types.
+ if !hasTypeSection {
+ p.qtypes[dns.TypeNone] = struct{}{}
+ }
+
+ // optional `net` means all ip addresses.
+ if !hasNetSection {
+ p.filter = newDefaultFilter()
+ }
+
+ r.policies = append(r.policies, p)
+ }
+ a.Rules = append(a.Rules, r)
+ }
+ return a, nil
+}
+
+func isPreservedIdentifier(token string) bool {
+ identifier := strings.ToLower(token)
+ return identifier == "type" || identifier == "net"
+}
+
+// normalize appends '/32' for any single IPv4 address and '/128' for IPv6.
+func normalize(rawNet string) string {
+ if idx := strings.IndexAny(rawNet, "/"); idx >= 0 {
+ return rawNet
+ }
+
+ if idx := strings.IndexAny(rawNet, ":"); idx >= 0 {
+ return rawNet + "/128"
+ }
+ return rawNet + "/32"
+}
diff --git a/plugin/acl/setup_test.go b/plugin/acl/setup_test.go
new file mode 100644
index 000000000..f48da3f24
--- /dev/null
+++ b/plugin/acl/setup_test.go
@@ -0,0 +1,245 @@
+package acl
+
+import (
+ "testing"
+
+ "github.com/caddyserver/caddy"
+)
+
+func TestSetup(t *testing.T) {
+ tests := []struct {
+ name string
+ config string
+ wantErr bool
+ }{
+ // IPv4 tests.
+ {
+ "Blacklist 1",
+ `acl {
+ block type A net 192.168.0.0/16
+ }`,
+ false,
+ },
+ {
+ "Blacklist 2",
+ `acl {
+ block type * net 192.168.0.0/16
+ }`,
+ false,
+ },
+ {
+ "Blacklist 3",
+ `acl {
+ block type A net *
+ }`,
+ false,
+ },
+ {
+ "Blacklist 4",
+ `acl {
+ allow type * net 192.168.1.0/24
+ block type * net 192.168.0.0/16
+ }`,
+ false,
+ },
+ {
+ "Whitelist 1",
+ `acl {
+ allow type * net 192.168.0.0/16
+ block type * net *
+ }`,
+ false,
+ },
+ {
+ "fine-grained 1",
+ `acl a.example.org {
+ block type * net 192.168.1.0/24
+ }`,
+ false,
+ },
+ {
+ "fine-grained 2",
+ `acl a.example.org {
+ block type * net 192.168.1.0/24
+ }
+ acl b.example.org {
+ block type * net 192.168.2.0/24
+ }`,
+ false,
+ },
+ {
+ "Multiple Networks 1",
+ `acl example.org {
+ block type * net 192.168.1.0/24 192.168.3.0/24
+ }`,
+ false,
+ },
+ {
+ "Multiple Qtypes 1",
+ `acl example.org {
+ block type TXT ANY CNAME net 192.168.3.0/24
+ }`,
+ false,
+ },
+ {
+ "Missing argument 1",
+ `acl {
+ block A net 192.168.0.0/16
+ }`,
+ true,
+ },
+ {
+ "Missing argument 2",
+ `acl {
+ block type net 192.168.0.0/16
+ }`,
+ true,
+ },
+ {
+ "Illegal argument 1",
+ `acl {
+ block type ABC net 192.168.0.0/16
+ }`,
+ true,
+ },
+ {
+ "Illegal argument 2",
+ `acl {
+ blck type A net 192.168.0.0/16
+ }`,
+ true,
+ },
+ {
+ "Illegal argument 3",
+ `acl {
+ block type A net 192.168.0/16
+ }`,
+ true,
+ },
+ {
+ "Illegal argument 4",
+ `acl {
+ block type A net 192.168.0.0/33
+ }`,
+ true,
+ },
+ // IPv6 tests.
+ {
+ "Blacklist 1 IPv6",
+ `acl {
+ block type A net 2001:0db8:85a3:0000:0000:8a2e:0370:7334
+ }`,
+ false,
+ },
+ {
+ "Blacklist 2 IPv6",
+ `acl {
+ block type * net 2001:db8:85a3::8a2e:370:7334
+ }`,
+ false,
+ },
+ {
+ "Blacklist 3 IPv6",
+ `acl {
+ block type A
+ }`,
+ false,
+ },
+ {
+ "Blacklist 4 IPv6",
+ `acl {
+ allow net 2001:db8:abcd:0012::0/64
+ block net 2001:db8:abcd:0012::0/48
+ }`,
+ false,
+ },
+ {
+ "Whitelist 1 IPv6",
+ `acl {
+ allow net 2001:db8:abcd:0012::0/64
+ block
+ }`,
+ false,
+ },
+ {
+ "fine-grained 1 IPv6",
+ `acl a.example.org {
+ block net 2001:db8:abcd:0012::0/64
+ }`,
+ false,
+ },
+ {
+ "fine-grained 2 IPv6",
+ `acl a.example.org {
+ block net 2001:db8:abcd:0012::0/64
+ }
+ acl b.example.org {
+ block net 2001:db8:abcd:0013::0/64
+ }`,
+ false,
+ },
+ {
+ "Multiple Networks 1 IPv6",
+ `acl example.org {
+ block net 2001:db8:abcd:0012::0/64 2001:db8:85a3::8a2e:370:7334/64
+ }`,
+ false,
+ },
+ {
+ "Illegal argument 1 IPv6",
+ `acl {
+ block type A net 2001::85a3::8a2e:370:7334
+ }`,
+ true,
+ },
+ {
+ "Illegal argument 2 IPv6",
+ `acl {
+ block type A net 2001:db8:85a3:::8a2e:370:7334
+ }`,
+ true,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ ctr := caddy.NewTestController("dns", tt.config)
+ if err := setup(ctr); (err != nil) != tt.wantErr {
+ t.Errorf("Error: setup() error = %v, wantErr %v", err, tt.wantErr)
+ }
+ })
+ }
+}
+
+func TestNormalize(t *testing.T) {
+ type args struct {
+ rawNet string
+ }
+ tests := []struct {
+ name string
+ args args
+ want string
+ }{
+ {
+ "Network range 1",
+ args{"10.218.10.8/24"},
+ "10.218.10.8/24",
+ },
+ {
+ "IP address 1",
+ args{"10.218.10.8"},
+ "10.218.10.8/32",
+ },
+ {
+ "IPv6 address 1",
+ args{"2001:0db8:85a3:0000:0000:8a2e:0370:7334"},
+ "2001:0db8:85a3:0000:0000:8a2e:0370:7334/128",
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := normalize(tt.args.rawNet); got != tt.want {
+ t.Errorf("Error: normalize() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}