Disable DNS-backed key-value store

I'm disabling the key-value store because no one was using it.

There are other reasons, too:

- The removal of the `etcd` library dropped the executable size by over
  half from 17MB to 7MB
- I didn't want users who've deployed it internally to be "surprised" by
  unexpected key-value features
- Key-value-over-DNS has a seamy side to it: "data exfiltration". I know
  there are legitimate uses for it, but I've come to believe that a
  Key-value-over-HTTP solution is preferable because it's not only more
  legitimate but also because it eliminates the DNS caching problem.
This commit is contained in:
Brian Cunnie
2023-03-13 10:16:53 -04:00
parent 326b717eb7
commit a62a797fe5
10 changed files with 18 additions and 479 deletions

View File

@@ -60,18 +60,7 @@ dig @$DNS_SERVER_IP txt ip.sslip.io +short | tr -d '"'
echo 127.0.0.1
dig @$DNS_SERVER_IP txt version.status.sslip.io +short | grep $VERSION
echo "\"$VERSION\""
dig @$DNS_SERVER_IP my-key.k-v.io txt +short # returns nothing
echo " ===" # separator because the results are too similar
dig @$DNS_SERVER_IP put.MyValue.my-key.k-v.io txt +short
echo "\"MyValue\""
echo " ===" # separator because the results are too similar
dig @$DNS_SERVER_IP MY-KEY.k-v.io txt +short
echo "\"MyValue\""
echo " ===" # separator because the results are too similar
dig @$DNS_SERVER_IP delete.my-key.k-v.io txt +short
echo
echo " ===" # separator because the results are too similar
dig @$DNS_SERVER_IP my-key.k-v.io txt +short # returns nothing
dig @$DNS_SERVER_IP 1.0.0.127.in-addr.arpa ptr +short
echo "127-0-0-1.sslip.io."
dig @$DNS_SERVER_IP metrics.status.sslip.io txt +short | grep '"Queries: '

View File

@@ -36,6 +36,8 @@
<main role="main" class="container">
<div class="starter-template">
<h1>k-v.io</h1>
<p>k-v.io is under construction. I've dismantled the DNS-backed key-value store—no one was using it. Stay
tuned.</p><!--
<p>A DNS-based Key-Value Store. <a href="https://ci.nono.io/teams/main/pipelines/sslip.io"><img alt="badge" src=
"https://ci.nono.io/api/v1/teams/main/pipelines/sslip.io/badge"></a></p>
<h2>Quick Start</h2>
@@ -115,6 +117,8 @@
</li>
</ul>
</div>
-->
</div>
</main><!-- /.container -->
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity=
"sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>

View File

@@ -21,14 +21,6 @@
"TXTSslipIoSPF"
]
},
"k-v.io.": {
"A": [
"104.155.144.4"
]
},
"_acme-challenge.k-v.io.": {
"TXT": []
},
"ns.sslip.io.": {
"A": [
"52.0.56.137",

View File

@@ -1,33 +1,22 @@
module xip
go 1.19
go 1.20
require (
github.com/onsi/ginkgo/v2 v2.8.4
github.com/onsi/gomega v1.27.2
go.etcd.io/etcd/client/v3 v3.5.7
golang.org/x/net v0.7.0
)
require (
github.com/coreos/go-semver v0.3.1 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/go-logr/logr v1.2.3 // indirect
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10 // indirect
go.etcd.io/etcd/api/v3 v3.5.7 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.7 // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
go.uber.org/zap v1.24.0 // indirect
github.com/stretchr/testify v1.8.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
golang.org/x/tools v0.6.0 // indirect
google.golang.org/genproto v0.0.0-20230227214838-9b19f0bdc514 // indirect
google.golang.org/grpc v1.53.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View File

@@ -1,8 +1,3 @@
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -10,87 +5,40 @@ github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10 h1:CqYfpuYIjnlNxM3msdyPRKabhXZWbKjf3Q8BWROFBso=
github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/onsi/ginkgo/v2 v2.8.4 h1:gf5mIQ8cLFieruNLAdgijHF1PYfLphKm2dxxcUtcqK0=
github.com/onsi/ginkgo/v2 v2.8.4/go.mod h1:427dEDQZkDKsBvCjc2A/ZPefhKxsTTrsQegMlayL730=
github.com/onsi/gomega v1.27.2 h1:SKU0CXeKE/WVgIV1T61kSa3+IRE8Ekrv9rdXDwwTqnY=
github.com/onsi/gomega v1.27.2/go.mod h1:5mR3phAHpkAVIDkHEUBY6HGVsU+cpcEscrGPB4oPlZI=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.etcd.io/etcd/api/v3 v3.5.7 h1:sbcmosSVesNrWOJ58ZQFitHMdncusIifYcrBfwrlJSY=
go.etcd.io/etcd/api/v3 v3.5.7/go.mod h1:9qew1gCdDDLu+VwmeG+iFpL+QlpHTo7iubavdVDgCAA=
go.etcd.io/etcd/client/pkg/v3 v3.5.7 h1:y3kf5Gbp4e4q7egZdn5T7W9TSHUvkClN6u+Rq9mEOmg=
go.etcd.io/etcd/client/pkg/v3 v3.5.7/go.mod h1:o0Abi1MK86iad3YrWhgUsbGx1pmTS+hrORWc2CamuhY=
go.etcd.io/etcd/client/v3 v3.5.7 h1:u/OhpiuCgYY8awOHlhIhmGIGpxfBU/GZBUP3m/3/Iz4=
go.etcd.io/etcd/client/v3 v3.5.7/go.mod h1:sOWmj9DZUMyAngS7QQwCyAXXAL6WhgTOPLNS/NabQgw=
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto v0.0.0-20230227214838-9b19f0bdc514 h1:rtNKfB++wz5mtDY2t5C8TXlU5y52ojSu7tZo0z7u8eQ=
google.golang.org/genproto v0.0.0-20230227214838-9b19f0bdc514/go.mod h1:TvhZT5f700eVlTNwND1xoEZQeWTB2RY/65kplwl/bFA=
google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc=
google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -123,30 +123,6 @@ var _ = Describe("IntegrationMetrics", func() {
actualMetrics = digAndGetMetrics("@localhost version.status.sslip.io txt +short -p "+strconv.Itoa(port), port)
Expect(expectedMetrics.MostlyEquals(actualMetrics)).To(BeTrue())
// TXT put.value.key.k-v.io updates .Queries, .AnsweredQueries, .AnsweredTXTPutKvQueries
expectedMetrics.Queries++
expectedMetrics.AnsweredQueries++
expectedMetrics.AnsweredTXTPutKvQueries++
expectedMetrics = bumpExpectedToAccountForMetricsQuery(expectedMetrics)
actualMetrics = digAndGetMetrics("@localhost put.value.key.k-v.io txt +short -p "+strconv.Itoa(port), port)
Expect(expectedMetrics.MostlyEquals(actualMetrics)).To(BeTrue())
// TXT key.k-v.io updates .Queries, .AnsweredQueries, .AnsweredTXTGetKvQueries
expectedMetrics.Queries++
expectedMetrics.AnsweredQueries++
expectedMetrics.AnsweredTXTGetKvQueries++
expectedMetrics = bumpExpectedToAccountForMetricsQuery(expectedMetrics)
actualMetrics = digAndGetMetrics("@localhost key.k-v.io txt +short -p "+strconv.Itoa(port), port)
Expect(expectedMetrics.MostlyEquals(actualMetrics)).To(BeTrue())
// TXT delete.key.k-v.io updates .Queries, .AnsweredTXTDelKvQueries
// It doesn't count as an "answered" query because it returns no record
expectedMetrics.Queries++
expectedMetrics.AnsweredTXTDelKvQueries++
expectedMetrics = bumpExpectedToAccountForMetricsQuery(expectedMetrics)
actualMetrics = digAndGetMetrics("@localhost delete.key.k-v.io txt +short -p "+strconv.Itoa(port), port)
Expect(expectedMetrics.MostlyEquals(actualMetrics)).To(BeTrue())
// PTR version.sslip.io updates .Queries, .AnsweredQueries, .AnsweredPTRQueriesIPv4
expectedMetrics.Queries++
expectedMetrics.AnsweredQueries++
@@ -203,7 +179,6 @@ func getMetrics(port int) (m xip.Metrics) {
var junk string
_, err = fmt.Sscanf(string(stdout),
"\"Uptime: %d\"\n"+
"\"KV Store: %s\n"+ // %s "swallows" the double-quote at the end
"\"Blocklist: %s %s %s\n"+
"\"Queries: %d (%s\n"+ // %s "swallows" the `/s"` at the end
"\"Answered Queries: %d (%s\n"+ // %s "swallows" the `/s"` at the end
@@ -211,12 +186,10 @@ func getMetrics(port int) (m xip.Metrics) {
"\"AAAA: %d\"\n"+
"\"TXT Source: %d\"\n"+
"\"TXT Version: %d\"\n"+
"\"TXT KV GET/PUT/DEL: %d/%d/%d\"\n"+
"\"PTR IPv4/IPv6: %d/%d\"\n"+
"\"NS DNS-01: %d\"\n"+
"\"Blocked: %d\"\n",
&uptime,
&junk,
&junk, &junk, &junk,
&m.Queries, &junk,
&m.AnsweredQueries, &junk,
@@ -224,7 +197,6 @@ func getMetrics(port int) (m xip.Metrics) {
&m.AnsweredAAAAQueries,
&m.AnsweredTXTSrcIPQueries,
&m.AnsweredTXTVersionQueries,
&m.AnsweredTXTGetKvQueries, &m.AnsweredTXTPutKvQueries, &m.AnsweredTXTDelKvQueries,
&m.AnsweredPTRQueriesIPv4, &m.AnsweredPTRQueriesIPv6,
&m.AnsweredNSDNS01ChallengeQueries,
&m.AnsweredBlockedQueries,

View File

@@ -58,10 +58,6 @@ var _ = Describe("sslip.io-dns-server", func() {
Eventually(string(digSession.Out.Contents())).Should(MatchRegexp(digResults))
Eventually(string(serverSession.Err.Contents())).Should(MatchRegexp(serverLogMessage))
},
Entry("A (customized) for k-v.io",
"@localhost k-v.io +short",
`\A104.155.144.4\n\z`,
`TypeA k-v.io. \? 104.155.144.4\n`),
Entry("A (customized) for sslip.io",
"@localhost sslip.io +short",
`\A78.46.204.247\n\z`,
@@ -131,26 +127,6 @@ var _ = Describe("sslip.io-dns-server", func() {
"@127.0.0.1 example.com txt +short",
`\A\z`,
`TypeTXT example.com. \? nil, SOA example.com. briancunnie.gmail.com. 2022112600 900 900 1800 180\n`),
Entry(`getting a non-existent value: TXT for non-existent.k-v.io"`,
"@127.0.0.1 non-existent.k-v.io txt +short",
`\A\z`,
`TypeTXT non-existent.k-v.io. \? nil, SOA non-existent.k-v.io. briancunnie.gmail.com. 2022112600 900 900 1800 180\n`),
Entry(`putting a value: TXT for put.MyValue.MY-KEY.k-v.io"`,
"@127.0.0.1 put.MyValue.MY-KEY.k-v.io txt +short",
`"MyValue"`,
`TypeTXT put.MyValue.MY-KEY.k-v.io. \? \["MyValue"\]`),
Entry(`deleting a value: TXT for delete.my-key.k-v.io"`,
"@127.0.0.1 delete.my-key.k-v.io txt +short",
`\A\z`,
`TypeTXT delete.my-key.k-v.io. \? nil, SOA delete.my-key.k-v.io. briancunnie.gmail.com. 2022112600 900 900 1800 180\n`),
Entry(`setting a TXT for _acme-challenge.k-v.io appears to work (spoiler: it doesn't)'"`,
"@127.0.0.1 put.sneaky-boy._acme-challenge.k-v.io txt +short",
`sneaky-boy`,
`TypeTXT put.sneaky-boy._acme-challenge.k-v.io. \? \["sneaky-boy"\]`),
Entry(`get a TXT for _acme-challenge.k-v.io is blocked to foil Let's Encrypt ACME DNS-01 challenge"`,
"@127.0.0.1 _acme-challenge.k-v.io txt +short",
`Please don't try to procure a k-v.io cert via DNS-01 challenge`,
`TypeTXT _acme-challenge.k-v.io. \? \["Please don't try to procure a k-v.io cert via DNS-01 challenge"\]`),
Entry(`get a PTR for 1.0.168.192.in-addr.arpa returns 192-168-0-1.sslip.io`,
"@127.0.0.1 1.0.168.192.in-addr.arpa ptr +short",
`\A192-168-0-1.sslip.io.\n\z`,
@@ -267,79 +243,6 @@ var _ = Describe("sslip.io-dns-server", func() {
Eventually(string(serverSession.Err.Contents())).Should(MatchRegexp(`TypeTXT sslip.io. \? \["protonmail-verification=ce0ca3f5010aa7a2cf8bcc693778338ffde73e26"\], \["v=spf1 include:_spf.protonmail.ch mx ~all"\]\n`))
})
})
When(`a TXT record for a host under the "k-v.io" domain is queried`, func() {
It(`the PUT has a three-minute TTL`, func() {
digArgs = "@localhost put.a.a.k-v.io txt -p " + strconv.Itoa(port)
digCmd = exec.Command("dig", strings.Split(digArgs, " ")...)
digSession, err = Start(digCmd, GinkgoWriter, GinkgoWriter)
Expect(err).ToNot(HaveOccurred())
Eventually(digSession, 1).Should(Exit(0))
Eventually(string(digSession.Out.Contents())).Should(MatchRegexp(`put.a.a.k-v.io. 180 IN TXT "a"`))
Eventually(string(serverSession.Err.Contents())).Should(MatchRegexp(`TypeTXT put.a.a.k-v.io. \? \["a"\]`))
})
It(`the GET has a three-minute TTL`, func() {
// create (PUT) the key
digArgs = "@localhost put.a.b.k-v.io txt -p " + strconv.Itoa(port)
digCmd = exec.Command("dig", strings.Split(digArgs, " ")...)
digSession, err = Start(digCmd, GinkgoWriter, GinkgoWriter)
Expect(err).ToNot(HaveOccurred())
Eventually(digSession, 1).Should(Exit(0))
// retrieve (GET) the key
digArgs = "@localhost b.k-v.io txt -p " + strconv.Itoa(port)
digCmd = exec.Command("dig", strings.Split(digArgs, " ")...)
digSession, err = Start(digCmd, GinkgoWriter, GinkgoWriter)
Expect(err).ToNot(HaveOccurred())
Eventually(digSession, 1).Should(Exit(0))
Eventually(string(digSession.Out.Contents()), 3).Should(MatchRegexp(`b.k-v.io. 180 IN TXT "a"`))
Eventually(string(serverSession.Err.Contents())).Should(MatchRegexp(`TypeTXT b.k-v.io. \? \["a"\]`))
})
It(`the DELETE returns no records so that value cached in downstream DNS servers expires more quickly`, func() {
// create (PUT) the key
digArgs = "@localhost put.a.c.k-v.io txt -p " + strconv.Itoa(port)
digCmd = exec.Command("dig", strings.Split(digArgs, " ")...)
digSession, err = Start(digCmd, GinkgoWriter, GinkgoWriter)
Expect(err).ToNot(HaveOccurred())
Eventually(digSession, 1).Should(Exit(0))
// DELETE the key
digArgs = "@localhost delete.c.k-v.io txt -p " + strconv.Itoa(port)
digCmd = exec.Command("dig", strings.Split(digArgs, " ")...)
digSession, err = Start(digCmd, GinkgoWriter, GinkgoWriter)
Expect(err).ToNot(HaveOccurred())
Eventually(digSession, 1).Should(Exit(0))
Eventually(string(serverSession.Err.Contents())).Should(MatchRegexp(`TypeTXT delete.c.k-v.io. \? nil, SOA delete.c.k-v.io. briancunnie.gmail.com. 2022112600 900 900 1800 180`))
})
It(`the DELETE on a non-existent key behaves the same as the DELETE on an existing key`, func() {
// DELETE the key (make sure it's gone)
digArgs = "@localhost delete.d.k-v.io txt -p " + strconv.Itoa(port)
digCmd = exec.Command("dig", strings.Split(digArgs, " ")...)
digSession, err = Start(digCmd, GinkgoWriter, GinkgoWriter)
Expect(err).ToNot(HaveOccurred())
Eventually(digSession, 1).Should(Exit(0))
// DELETE again to test the non-existent behavior
digArgs = "@localhost delete.d.k-v.io txt -p " + strconv.Itoa(port)
digCmd = exec.Command("dig", strings.Split(digArgs, " ")...)
digSession, err = Start(digCmd, GinkgoWriter, GinkgoWriter)
Expect(err).ToNot(HaveOccurred())
Eventually(digSession, 1).Should(Exit(0))
Eventually(string(serverSession.Err.Contents())).Should(MatchRegexp(`TypeTXT delete.d.k-v.io. \? nil, SOA delete.d.k-v.io. briancunnie.gmail.com. 2022112600 900 900 1800 180`))
})
It(`setting a TXT for _acme-challenge.subdomain-key.k-v.io doesn't expose DNS-01 vulnerability`, func() {
// set (PUT) the key
digArgs = "@localhost put.baffled-boy._acme-challenge.subdomain-key.k-v.io txt +short -p " + strconv.Itoa(port)
digCmd = exec.Command("dig", strings.Split(digArgs, " ")...)
digSession, err = Start(digCmd, GinkgoWriter, GinkgoWriter)
Expect(err).ToNot(HaveOccurred())
Eventually(digSession, 1).Should(Exit(0))
// GET the key
digArgs = "@localhost get.subdomain-key.k-v.io txt +short -p " + strconv.Itoa(port)
digCmd = exec.Command("dig", strings.Split(digArgs, " ")...)
digSession, err = Start(digCmd, GinkgoWriter, GinkgoWriter)
Expect(err).ToNot(HaveOccurred())
Eventually(digSession, 1).Should(Exit(0))
Eventually(string(digSession.Out.Contents()), 3).Should(MatchRegexp(`"baffled-boy._acme-challenge"`))
Eventually(string(serverSession.Err.Contents())).Should(MatchRegexp(`TypeTXT get.subdomain-key.k-v.io. \? \["baffled-boy._acme-challenge"\]`))
})
})
When(`a record for an "_acme-challenge" domain is queried`, func() {
When(`it's an NS record`, func() {
It(`returns the NS record of the query with the "_acme-challenge." stripped`, func() {

View File

@@ -15,13 +15,11 @@ import (
func main() {
var wg sync.WaitGroup
var etcdEndpoint = flag.String("etcdHost", "localhost:2379", "etcd client endpoint; falls back to builtin key-value store if unable to connect")
var blocklistURL = flag.String("blocklistURL", "https://raw.githubusercontent.com/cunnie/sslip.io/main/etc/blocklist.txt", `URL containing a list of "forbidden" names/CIDRs`)
var nameservers = flag.String("nameservers", "ns-aws.sslip.io.,ns-azure.sslip.io.,ns-gce.sslip.io.", "comma-separated list of nameservers")
var addresses = flag.String("addresses",
"sslip.io=78.46.204.247,"+
"sslip.io=2a01:4f8:c17:b8f::2,"+
"k-v.io=104.155.144.4,"+
"ns.sslip.io=52.0.56.137,"+
"ns.sslip.io=52.187.42.158,"+
"ns.sslip.io=104.155.144.4,"+
@@ -34,10 +32,10 @@ func main() {
var quiet = flag.Bool("quiet", false, "suppresses logging of each DNS response")
flag.Parse()
log.Printf("%s version %s starting", os.Args[0], xip.VersionSemantic)
log.Printf("etcd endpoint: %s, blocklist URL: %s, name servers: %s, bind port: %d, quiet: %t",
*etcdEndpoint, *blocklistURL, *nameservers, *bindPort, *quiet)
log.Printf("blocklist URL: %s, name servers: %s, bind port: %d, quiet: %t",
*blocklistURL, *nameservers, *bindPort, *quiet)
x, logmessages := xip.NewXip(*etcdEndpoint, *blocklistURL, strings.Split(*nameservers, ","), strings.Split(*addresses, ","))
x, logmessages := xip.NewXip(*blocklistURL, strings.Split(*nameservers, ","), strings.Split(*addresses, ","))
for _, logmessage := range logmessages {
log.Println(logmessage)
}

View File

@@ -5,7 +5,6 @@ package xip
import (
"bufio"
"context"
"errors"
"fmt"
"io"
@@ -14,29 +13,18 @@ import (
"net/http"
"net/netip"
"os"
"reflect"
"regexp"
"strconv"
"strings"
"time"
clientv3 "go.etcd.io/etcd/client/v3"
"golang.org/x/net/dns/dnsmessage"
)
//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -generate
//counterfeiter:generate . V3client
type V3client interface {
Get(context.Context, string, ...clientv3.OpOption) (*clientv3.GetResponse, error)
Put(context.Context, string, string, ...clientv3.OpOption) (*clientv3.PutResponse, error)
Delete(context.Context, string, ...clientv3.OpOption) (*clientv3.DeleteResponse, error)
Close() error
}
// Xip is meant to be a singleton that holds global state for the DNS server
type Xip struct {
Etcd V3client // etcd client for `k-v.io`
DnsAmplificationAttackDelay chan struct{} // for throttling metrics.status.sslip.io
Metrics Metrics // DNS server metrics
BlocklistStrings []string // list of blacklisted strings that shouldn't appear in public hostnames
@@ -54,9 +42,6 @@ type Metrics struct {
AnsweredAAAAQueries int
AnsweredTXTSrcIPQueries int
AnsweredTXTVersionQueries int
AnsweredTXTGetKvQueries int
AnsweredTXTPutKvQueries int
AnsweredTXTDelKvQueries int
AnsweredNSDNS01ChallengeQueries int
AnsweredBlockedQueries int
AnsweredPTRQueriesIPv4 int
@@ -88,12 +73,6 @@ type DomainCustomization struct {
// DNS hostnames are technically case-insensitive
type DomainCustomizations map[string]DomainCustomization
// KvCustomizations is a lookup table for custom TXT records
// e.g. KvCustomizations["my-key"] = []dnsmessage.TXTResource{ TXT: { "my-value" } }
// The key should NOT include ".k-v.io."
// It's used when there's no etcd server running
type KvCustomizations map[string][]dnsmessage.TXTResource
// There's nothing like global variables to make my heart pound with joy.
// Some of these are global because they are, in essence, constants which
// I don't want to waste time recreating with every function call.
@@ -105,7 +84,6 @@ var (
ipv4ReverseRE = regexp.MustCompile(`^(.*)\.in-addr\.arpa\.$`)
ipv6ReverseRE = regexp.MustCompile(`^(([[:xdigit:]]\.){32})ip6\.arpa\.`)
dns01ChallengeRE = regexp.MustCompile(`(?i)_acme-challenge\.`) // (?i) → non-capturing case insensitive
kvRE = regexp.MustCompile(`\.k-v\.io\.$`)
mbox, _ = dnsmessage.NewName("briancunnie.gmail.com.")
mx1, _ = dnsmessage.NewName("mail.protonmail.ch.")
@@ -120,16 +98,7 @@ var (
MetricsBufferSize = 200 // big enough to run our tests, and small enough to prevent DNS amplification attacks
// etcdContextTimeout — the duration (context) that we wait for etcd to get back to us
// - etcd queries on the nameserver take as long as 482 milliseconds on the "slow" server, 247 on the "fast"
// - round-trip time from my house in San Francisco to ns-azure in Singapore is 190 milliseconds
// - time between queries with `dig` on my macOS Monterey is 5000 milliseconds, three queries before giving up
// - quadruple the headroom for queries 4 x 482 = 1928, should still leave enough room to get answer back
// within the 5000 milliseconds
etcdContextTimeout = 1928 * time.Millisecond
TxtKvCustomizations = KvCustomizations{}
Customizations = DomainCustomizations{
Customizations = DomainCustomizations{
"sslip.io.": {
MX: []dnsmessage.MXResource{
{
@@ -143,12 +112,6 @@ var (
},
TXT: TXTSslipIoSPF,
},
// don't let people procure *.k-v.io TLS certs via ACME DNS-01 challenge
"_acme-challenge.k-v.io.": {
TXT: func(_ *Xip, _ net.IP) ([]dnsmessage.TXTResource, error) {
return []dnsmessage.TXTResource{{TXT: []string{"Please don't try to procure a k-v.io cert via DNS-01 challenge"}}}, nil
},
},
// nameserver addresses; we get queries for those every once in a while
// CNAMEs for sslip.io for DKIM signing
"protonmail._domainkey.sslip.io.": {
@@ -201,19 +164,8 @@ type Response struct {
}
// NewXip follows convention for constructors: https://go.dev/doc/effective_go#allocation_new
func NewXip(etcdEndpoint, blocklistURL string, nameservers []string, addresses []string) (x *Xip, logmessages []string) {
var err error
func NewXip(blocklistURL string, nameservers []string, addresses []string) (x *Xip, logmessages []string) {
x = &Xip{Metrics: Metrics{Start: time.Now()}}
// connect to `etcd`; if there's an error, set etcdCli to `nil` and that to
// determine whether to use a local key-value store instead
x.Etcd, err = clientv3New(etcdEndpoint)
if err != nil {
logmessages = append(logmessages, fmt.Sprintf("failed to connect to etcd at %s, using local key-value store instead: %s", etcdEndpoint, err.Error()))
} else {
logmessages = append(logmessages, fmt.Sprintf("Successfully connected to etcd at %s", etcdEndpoint))
}
// don't `defer etcdCli.Close()`: "The Client has internal state (watchers and leases), so
// Clients should be reused instead of created as needed"
// Download the blocklist
logmessages = append(logmessages, x.downloadBlockList(blocklistURL))
@@ -819,7 +771,7 @@ func (x *Xip) NSResources(fqdnString string) []dnsmessage.NSResource {
return x.NameServers
}
// TXTResources returns TXT records from Customizations or KvCustomizations
// TXTResources returns TXT records from Customizations
func (x *Xip) TXTResources(fqdn string, ip net.IP) ([]dnsmessage.TXTResource, error) {
if domain, ok := Customizations[strings.ToLower(fqdn)]; ok {
// Customizations[strings.ToLower(fqdn)] returns a _function_,
@@ -828,9 +780,6 @@ func (x *Xip) TXTResources(fqdn string, ip net.IP) ([]dnsmessage.TXTResource, er
return domain.TXT(x, ip)
}
}
if kvRE.MatchString(fqdn) {
return x.kvTXTResources(fqdn)
}
return nil, nil
}
@@ -938,11 +887,6 @@ func TXTMetrics(x *Xip, _ net.IP) (txtResources []dnsmessage.TXTResource, err er
var metrics []string
uptime := time.Since(x.Metrics.Start)
metrics = append(metrics, fmt.Sprintf("Uptime: %.0f", uptime.Seconds()))
keyValueStore := "etcd"
if x.isEtcdNil() {
keyValueStore = "builtin"
}
metrics = append(metrics, "KV Store: "+keyValueStore)
metrics = append(metrics, fmt.Sprintf("Blocklist: %s %d,%d",
x.BlocklistUpdated.Format("2006-01-02 15:04:05-07"),
len(x.BlocklistStrings),
@@ -953,7 +897,6 @@ func TXTMetrics(x *Xip, _ net.IP) (txtResources []dnsmessage.TXTResource, err er
metrics = append(metrics, fmt.Sprintf("AAAA: %d", x.Metrics.AnsweredAAAAQueries))
metrics = append(metrics, fmt.Sprintf("TXT Source: %d", x.Metrics.AnsweredTXTSrcIPQueries))
metrics = append(metrics, fmt.Sprintf("TXT Version: %d", x.Metrics.AnsweredTXTVersionQueries))
metrics = append(metrics, fmt.Sprintf("TXT KV GET/PUT/DEL: %d/%d/%d", x.Metrics.AnsweredTXTGetKvQueries, x.Metrics.AnsweredTXTPutKvQueries, x.Metrics.AnsweredTXTDelKvQueries))
metrics = append(metrics, fmt.Sprintf("PTR IPv4/IPv6: %d/%d", x.Metrics.AnsweredPTRQueriesIPv4, x.Metrics.AnsweredPTRQueriesIPv6))
metrics = append(metrics, fmt.Sprintf("NS DNS-01: %d", x.Metrics.AnsweredNSDNS01ChallengeQueries))
metrics = append(metrics, fmt.Sprintf("Blocked: %d", x.Metrics.AnsweredBlockedQueries))
@@ -963,106 +906,6 @@ func TXTMetrics(x *Xip, _ net.IP) (txtResources []dnsmessage.TXTResource, err er
return txtResources, nil
}
// when TXT for "k-v.io" is queried, return the key-value pair
func (x *Xip) kvTXTResources(fqdn string) ([]dnsmessage.TXTResource, error) {
// "labels" => official RFC 1035 term
// k-v.io. => ["k-v", "io"] are labels
var (
verb string // i.e. "get", "put", "delete"
key string // e.g. "my-key" as in "my-key.k-v.io"
value string // e.g. "my-value" as in "put.my-value.my-key.k-v.io"
)
labels := strings.Split(fqdn, ".")
labels = labels[:len(labels)-3] // strip ".k-v.io"
// key is always present, always first subdomain of "k-v.io"
key = strings.ToLower(labels[len(labels)-1])
switch {
case len(labels) == 1:
verb = "get" // default action if only key, not verb, is not present
case len(labels) == 2:
verb = strings.ToLower(labels[0]) // verb, if present, is leftmost, "put.value.key.k-v.io"
case len(labels) > 2:
verb = strings.ToLower(labels[0])
// concatenate multiple labels to create value, especially useful for version numbers
value = strings.Join(labels[1:len(labels)-1], ".") // e.g. "put.94.0.2.firefox-version.k-v.io"
}
// prepare to query etcd:
switch verb {
case "get":
return x.getKv(key)
case "put":
if len(labels) == 2 {
return []dnsmessage.TXTResource{{[]string{"422: missing a value: put.value.key.k-v.io"}}}, nil
}
return x.putKv(key, value)
case "delete":
return x.deleteKv(key)
}
return []dnsmessage.TXTResource{{[]string{"422: valid verbs are get, put, delete"}}}, nil
}
func (x *Xip) getKv(key string) ([]dnsmessage.TXTResource, error) {
if x.isEtcdNil() {
if txtRecord, ok := TxtKvCustomizations[key]; ok {
x.Metrics.AnsweredTXTGetKvQueries++
return txtRecord, nil
}
return nil, nil
}
ctx, cancel := context.WithTimeout(context.Background(), etcdContextTimeout)
defer cancel()
resp, err := x.Etcd.Get(ctx, key)
if err != nil {
return nil, fmt.Errorf(`couldn't GET "%s": %w`, key, err)
}
if len(resp.Kvs) > 0 {
x.Metrics.AnsweredTXTGetKvQueries++
return []dnsmessage.TXTResource{{[]string{string(resp.Kvs[0].Value)}}}, nil
}
return []dnsmessage.TXTResource{}, nil
}
func (x *Xip) putKv(key, value string) ([]dnsmessage.TXTResource, error) {
if len(value) > 63 { // too-long TXT records can be used in DNS amplification attacks; Truncate!
value = value[:63]
}
if x.isEtcdNil() {
TxtKvCustomizations[key] = []dnsmessage.TXTResource{
{
[]string{value},
},
}
x.Metrics.AnsweredTXTPutKvQueries++
return TxtKvCustomizations[key], nil
}
ctx, cancel := context.WithTimeout(context.Background(), etcdContextTimeout)
defer cancel()
_, err := x.Etcd.Put(ctx, key, value)
if err != nil {
return nil, fmt.Errorf("couldn't PUT (%s: %s): %w", key, value, err)
}
x.Metrics.AnsweredTXTPutKvQueries++
return []dnsmessage.TXTResource{{[]string{value}}}, nil
}
func (x *Xip) deleteKv(key string) ([]dnsmessage.TXTResource, error) {
if x.isEtcdNil() {
if _, ok := TxtKvCustomizations[key]; ok {
x.Metrics.AnsweredTXTDelKvQueries++
delete(TxtKvCustomizations, key)
}
return nil, nil
}
ctx, cancel := context.WithTimeout(context.Background(), etcdContextTimeout)
defer cancel()
_, err := x.Etcd.Delete(ctx, key)
if err != nil {
return nil, fmt.Errorf("couldn't DELETE (key %s): %w", key, err)
}
x.Metrics.AnsweredTXTDelKvQueries++
return nil, nil
}
// soaLogMessage returns an easy-to-read string for logging SOA Answers/Authorities
func soaLogMessage(soaResource dnsmessage.SOAResource) string {
return soaResource.NS.String() + " " +
@@ -1082,9 +925,6 @@ func (a Metrics) MostlyEquals(b Metrics) bool {
a.AnsweredAAAAQueries == b.AnsweredAAAAQueries &&
a.AnsweredTXTSrcIPQueries == b.AnsweredTXTSrcIPQueries &&
a.AnsweredTXTVersionQueries == b.AnsweredTXTVersionQueries &&
a.AnsweredTXTGetKvQueries == b.AnsweredTXTGetKvQueries &&
a.AnsweredTXTPutKvQueries == b.AnsweredTXTPutKvQueries &&
a.AnsweredTXTDelKvQueries == b.AnsweredTXTDelKvQueries &&
a.AnsweredPTRQueriesIPv4 == b.AnsweredPTRQueriesIPv4 &&
a.AnsweredPTRQueriesIPv6 == b.AnsweredPTRQueriesIPv6 &&
a.AnsweredNSDNS01ChallengeQueries == b.AnsweredNSDNS01ChallengeQueries &&
@@ -1161,15 +1001,6 @@ func ReadBlocklist(blocklist io.Reader) (stringBlocklists []string, cidrBlocklis
return stringBlocklists, cidrBlocklists, nil
}
func (x *Xip) isEtcdNil() bool {
// comparing interfaces to nil are tricky: interfaces contain both a type
// and a value, and although the value is nil the type isn't, so we need the following
if x.Etcd == nil || reflect.ValueOf(x.Etcd).IsNil() {
return true
}
return false
}
func (x *Xip) blocklist(hostname string) bool {
aResources := NameToA(hostname)
aaaaResources := NameToAAAA(hostname)
@@ -1322,25 +1153,3 @@ func (x *Xip) nameToAAAAwithBlocklist(q dnsmessage.Question, response Response,
}
return response, logMessage + strings.Join(logMessages, ", "), nil
}
// clientv3New attempts to connect to local etcd and retrieve a key to make
// sure the connection works. If for any reason it fails it returns nil +
// error
func clientv3New(etcdEndpoint string) (*clientv3.Client, error) {
etcdEndpoints := []string{etcdEndpoint}
etcdCli, err := clientv3.New(clientv3.Config{
Endpoints: etcdEndpoints,
DialTimeout: 250 * time.Millisecond,
})
if err != nil {
return nil, err
}
// Let's do a query to determine if etcd is really, truly there
ctx, cancel := context.WithTimeout(context.Background(), etcdContextTimeout)
defer cancel()
_, err = etcdCli.Get(ctx, "some-silly-key, doesn't matter if it exists")
if err != nil {
return nil, err
}
return etcdCli, nil
}

View File

@@ -5,12 +5,10 @@ import (
"math/rand"
"net"
"strings"
"time"
"xip/xip"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
clientv3 "go.etcd.io/etcd/client/v3"
"golang.org/x/net/dns/dnsmessage"
)
@@ -81,7 +79,7 @@ var _ = Describe("Xip", func() {
Describe("NSResources()", func() {
When("we use the default nameservers", func() {
var x, _ = xip.NewXip("localhost:2379", "file:///", []string{"ns-aws.sslip.io.", "ns-azure.sslip.io.", "ns-gce.sslip.io."}, []string{})
var x, _ = xip.NewXip("file:///", []string{"ns-aws.sslip.io.", "ns-azure.sslip.io.", "ns-gce.sslip.io."}, []string{})
It("returns the name servers", func() {
randomDomain := random8ByteString() + ".com."
ns := x.NSResources(randomDomain)
@@ -113,7 +111,7 @@ var _ = Describe("Xip", func() {
})
})
When("we override the default nameservers", func() {
var x, _ = xip.NewXip("localhost:2379", "file:///", []string{"mickey", "minn.ie.", "goo.fy"}, []string{})
var x, _ = xip.NewXip("file:///", []string{"mickey", "minn.ie.", "goo.fy"}, []string{})
It("returns the configured servers", func() {
randomDomain := random8ByteString() + ".com."
ns := x.NSResources(randomDomain)
@@ -178,69 +176,6 @@ var _ = Describe("Xip", func() {
Expect(len(txts)).To(Equal(0))
})
})
When(`the domain "k-v.io is queried"`, Ordered, func() {
txtTests := func() {
DescribeTable(`the domain "k-v.io" is queried for TXT records`,
func(fqdn string, txts []string) {
txtResources, err := x.TXTResources(fqdn, nil)
Expect(err).ToNot(HaveOccurred())
Expect(len(txtResources)).To(Equal(len(txts)))
for i, txtResource := range txtResources {
Expect(len(txtResource.TXT)).To(Equal(1)) // each TXT record has 1 & only 1 string
Expect(txtResource.TXT[0]).To(Equal(txts[i]))
}
},
// simple tests: get, put, delete with single label value
Entry("no arguments → empty array", "k-v.io.", []string{}),
Entry("putting a value → that value", "PUT.MyValue.my-key.k-v.io.", []string{"MyValue"}),
Entry("getting that value → that value", "my-key.k-v.io.", []string{"MyValue"}),
Entry("getting that value with an UPPERCASE key → that value", "MY-KEY.k-v.io.", []string{"MyValue"}),
Entry("explicitly getting that value → that value", "GeT.my-key.k-v.io.", []string{"MyValue"}),
Entry("deleting that value → empty array", "DelETe.my-key.k-v.io.", []string{}),
Entry("getting that deleted value → empty array", "my-key.k-v.io.", []string{}),
// errors
Entry("getting a non-existent key → empty array", "nonexistent.k-v.io.", []string{}),
Entry("putting but skipping the value → error txt", "put.my-key.k-v.io.", []string{"422: missing a value: put.value.key.k-v.io"}),
Entry("deleting a non-existent key → silently succeeds", "delete.non-existent.k-v.io.", []string{}),
Entry("using a garbage verb → error txt", "post.my-key.k-v.io.", []string{"422: valid verbs are get, put, delete"}),
// others
Entry("putting a multi-label value", "put.96.0.4664.55.chrome-version.k-v.io.", []string{"96.0.4664.55"}),
Entry("putting a super-long multi-label value to use in a DNS amplification attack gets truncated to 63 characters",
"put"+
".IReturnedAndSawUnderTheSunThatTheRaceIsNotToTheSwiftNotThe"+
".BattleToTheStrongNeitherYetBreadToTheWiseNorYetRichesToMenOf"+
".amplify.k-v.io.",
[]string{"IReturnedAndSawUnderTheSunThatTheRaceIsNotToTheSwiftNotThe.Batt"},
),
)
}
When("there's no etcd, just local, in-memory key-value", func() {
txtTests()
})
etcdURI := "localhost:2379"
// make sure there's an etcd listening before we run our tests
conn, err := net.DialTimeout("tcp", etcdURI, 250*time.Millisecond)
if err == nil {
err = conn.Close()
Expect(err).ToNot(HaveOccurred())
When(`etcd is backing the kv store`, func() {
BeforeEach(func() {
etcdCli, err := clientv3.New(clientv3.Config{
Endpoints: []string{etcdURI},
DialTimeout: 250 * time.Millisecond,
})
Expect(err).ToNot(HaveOccurred())
x.Etcd = etcdCli
})
AfterEach(func() {
err = x.Etcd.Close()
Expect(err).ToNot(HaveOccurred())
})
txtTests()
})
}
})
})
Describe("NameToA()", func() {