Skip to content

Commit 3ea4d87

Browse files
authored
support provider defaults for google, cloudflare (#27)
* support provider defaults for google, cloudflare * allows passing of simple `-google` or `-cloudflare` flags to configure google or cloudflare DNS, respectively. Provides sane defaults for each, and avoids some unintuitive settings needed for cloudflare to work properly. * adds new CLI flags for specifying DNS headers or additional query parameters which should be sent along with the request to the DNS-over-HTTPS server * update build versions * fix help formatting issue * fix yaml treating versions as floats * use ENTRYPOINT in docker for easier container use * use 1.0.0.1 as first-listed DNS server
1 parent 18d2021 commit 3ea4d87

File tree

7 files changed

+194
-51
lines changed

7 files changed

+194
-51
lines changed

.travis.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
language: go
22
go:
3-
- 1.8
4-
- 1.9
3+
- "1.9"
4+
- "1.10"
55
- tip
66
install:
77
- go get -u -v github.com/fardog/secureoperator
@@ -24,4 +24,4 @@ deploy:
2424
on:
2525
repo: fardog/secureoperator
2626
tags: true
27-
go: 1.9
27+
go: 1.10

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@ COPY . /go/src/github.com/fardog/secureoperator
1010
WORKDIR /go/src/github.com/fardog/secureoperator/cmd/secure-operator
1111
RUN go install -v
1212

13-
CMD secure-operator --listen 0.0.0.0:53
13+
ENTRYPOINT ["secure-operator", "--listen", "0.0.0.0:53"]

README.md

Lines changed: 12 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ across the internet using HTTPS.
1010
It's known to work with the following providers:
1111

1212
* [Google][dnsoverhttps] - Well tested and configured by default
13-
* **Alpha:** [Cloudflare][] - See [release notes](#cloudflare) for configuration
14-
and caveats
13+
* [Cloudflare][] _(Beta)_ - May be used by passing the `--cloudflare` flag
1514

1615
## Installation
1716

@@ -70,34 +69,6 @@ stability, either use the tagged releases or mirror on gopkg.in:
7069
go get -u gopkg.in/fardog/secureoperator.v3
7170
```
7271

73-
## Cloudflare
74-
75-
[Cloudflare][] can be used as an DNS-over-HTTPS provider today, but requires
76-
some special configuration. Try the following:
77-
78-
```
79-
$ secure-operator \
80-
-endpoint https://cloudflare-dns.com/dns-query\?ct\=application/dns-json \
81-
-endpoint-ips 1.1.1.1,1.0.0.1
82-
```
83-
84-
Support for [Cloudflare][] as a DNS-over-HTTPS will be added as a CLI flag in
85-
the future, however the above will work with the current version of
86-
secureoperator.
87-
88-
### Known Issues
89-
90-
Cloudflare is not fully tested yet; it should work for common cases, however:
91-
92-
* Requests require a Content-Type parameter to be present in the URL, which must
93-
be provided in the CLI.
94-
* EDNS is not supported; this is an intentional choice by Cloudflare, which
95-
means any EDNS setting you provide when using Cloudflare as a provider will
96-
be silently ignored.
97-
98-
For a production environment, the Google provider (default) is your best option
99-
today. If you're brave, please test Cloudflare and [report any issues][issues]!
100-
10172
## Security
10273

10374
Note that while DNS requests are made over HTTPS, this does not imply "secure";
@@ -131,6 +102,17 @@ following areas could use work:
131102
secureoperator which enables API compatible independent DNS-over-HTTPS servers
132103
to be run
133104

105+
### Known Issues
106+
107+
Cloudflare is not fully tested yet; it should work for common cases, however:
108+
109+
* EDNS is not supported; this is an intentional choice by Cloudflare, which
110+
means any EDNS setting you provide when using Cloudflare as a provider will
111+
be silently ignored.
112+
113+
For a production environment, the Google provider (default) is your best option
114+
today. If you're brave, please test Cloudflare and [report any issues][issues]!
115+
134116
## Acknowledgments
135117

136118
This owes heavily to the following work:

cmd/secure-operator/main.go

Lines changed: 95 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"math/rand"
77
"net"
8+
"net/http"
89
"os"
910
"os/signal"
1011
"path/filepath"
@@ -18,6 +19,11 @@ import (
1819
"github.com/fardog/secureoperator/cmd"
1920
)
2021

22+
const (
23+
gdnsEndpoint = "https://dns.google.com/resolve"
24+
cloudflareEndpoint = "https://cloudflare-dns.com/dns-query"
25+
)
26+
2127
var (
2228
listenAddress = flag.String(
2329
"listen", ":53", "listen address, as `[host]:port`",
@@ -35,49 +41,72 @@ var (
3541
"Log level, one of: debug, info, warn, error, fatal, panic",
3642
)
3743

44+
// one-stop configuration flags; when used, these configure sane defaults
45+
google = flag.Bool(
46+
"google",
47+
false,
48+
fmt.Sprintf(`Use Google defaults. When set, the following options will be used unless
49+
explicitly overridden:
50+
dns-servers: 8.8.8.8,8.8.4.4
51+
endpoint: %v`, gdnsEndpoint),
52+
)
53+
cloudflare = flag.Bool(
54+
"cloudflare",
55+
false,
56+
fmt.Sprintf(`Use Cloudflare defaults. When set, the following options will be used
57+
unless explicitly overridden:
58+
dns-servers: 1.0.0.1,1.1.1.1
59+
params: ct=application/dns-json
60+
endpoint: %v`, cloudflareEndpoint),
61+
)
62+
3863
// resolution of the Google DNS endpoint; the interaction of these values is
3964
// somewhat complex, and is further explained in the help message.
4065
endpoint = flag.String(
4166
"endpoint",
42-
"https://dns.google.com/resolve",
43-
"Google DNS-over-HTTPS endpoint url",
67+
gdnsEndpoint,
68+
"DNS-over-HTTPS endpoint url",
4469
)
4570
endpointIPs = flag.String(
4671
"endpoint-ips",
4772
"",
48-
`IPs of the Google DNS-over-HTTPS endpoint; if provided, endpoint lookup is
49-
skipped, and the host value in "endpoint" is sent as the Host header. Comma
50-
separated with no spaces; e.g. "74.125.28.139,74.125.28.102". One server is
51-
randomly chosen for each request, failed requests are not retried.`,
73+
`IPs of the DNS-over-HTTPS endpoint; if provided, endpoint lookup is
74+
skipped, and the host value in "endpoint" is sent as the Host header. Comma
75+
separated with no spaces; e.g. "74.125.28.139,74.125.28.102". One server is
76+
randomly chosen for each request, failed requests are not retried.`,
5277
)
5378
dnsServers = flag.String(
5479
"dns-servers",
5580
"",
5681
`DNS Servers used to look up the endpoint; system default is used if absent.
57-
Ignored if "endpoint-ips" is set. Comma separated, e.g. "8.8.8.8,8.8.4.4:53".
58-
The port section is optional, and 53 will be used by default.`,
82+
Ignored if "endpoint-ips" is set. Comma separated, e.g. "8.8.8.8,8.8.4.4:53".
83+
The port section is optional, and 53 will be used by default.`,
5984
)
6085
autoEDNS = flag.Bool(
6186
"auto-edns-subnet",
6287
false,
6388
`By default, we use an EDNS subnet of 0.0.0.0/0 which does not reveal your
64-
IP address or subnet to authoratative DNS servers. If privacy of your IP
65-
address is not a concern and you want to take advantage of an authoratative
66-
server determining the best DNS results for you, set this flag. This flag
67-
specifies that Google should choose what subnet to send; if you'd like to
68-
specify your own subnet, use the -edns-subnet option.`,
89+
IP address or subnet to authoratative DNS servers. If privacy of your IP
90+
address is not a concern and you want to take advantage of an authoratative
91+
server determining the best DNS results for you, set this flag. This flag
92+
specifies that Google should choose what subnet to send; if you'd like to
93+
specify your own subnet, use the -edns-subnet option.`,
6994
)
7095
ednsSubnet = flag.String(
7196
"edns-subnet",
7297
secop.GoogleEDNSSentinelValue,
7398
`Specify a subnet to be sent in the edns0-client-subnet option; by default
74-
we specify that this option should not be used, for privacy. If
75-
-auto-edns-subnet is used, the value specified here is ignored.
99+
we specify that this option should not be used, for privacy. If
100+
-auto-edns-subnet is used, the value specified here is ignored.
76101
`,
77102
)
78103

79104
enableTCP = flag.Bool("tcp", true, "Listen on TCP")
80105
enableUDP = flag.Bool("udp", true, "Listen on UDP")
106+
107+
// variables set in main body
108+
headers = make(cmd.KeyValue)
109+
queryParameters = make(cmd.KeyValue)
81110
)
82111

83112
func serve(net string) {
@@ -102,6 +131,21 @@ func serve(net string) {
102131
}
103132

104133
func main() {
134+
// non-standard flag vars
135+
flag.Var(
136+
headers,
137+
"header",
138+
`Additional headers to be sent with http requests, as Key=Value; specify
139+
multiple as:
140+
-header Key-1=Value-1-1 -header Key-1=Value1-2 -header Key-2=Value-2`,
141+
)
142+
flag.Var(
143+
queryParameters,
144+
"param",
145+
`Additional query parameters to be sent with http requests, as key=value;
146+
specify multiple as:
147+
-param key1=value1-1 -param key1=value1-2 -param key2=value2`,
148+
)
105149
flag.Usage = func() {
106150
_, exe := filepath.Split(os.Args[0])
107151
fmt.Fprint(os.Stderr, "A DNS-protocol proxy for Google's DNS-over-HTTPS service.\n\n")
@@ -121,6 +165,10 @@ func main() {
121165
}
122166
log.SetLevel(level)
123167

168+
if *google && *cloudflare {
169+
log.Fatalf("you may not specify `-google` and `-cloudflare` arguments together")
170+
}
171+
124172
eips, err := cmd.CSVtoIPs(*endpointIPs)
125173
if err != nil {
126174
log.Fatalf("error parsing endpoint-ips: %v", err)
@@ -141,13 +189,43 @@ func main() {
141189
log.Warn("EDNS will be used; authoritative name servers may be able to determine your location")
142190
}
143191

144-
provider, err := secop.NewGDNSProvider(*endpoint, &secop.GDNSOptions{
192+
ep := *endpoint
193+
opts := &secop.GDNSOptions{
145194
Pad: !*noPad,
146195
EndpointIPs: eips,
147196
DNSServers: dips,
148197
UseEDNSsubnetOption: true,
149198
EDNSSubnet: edns,
150-
})
199+
QueryParameters: map[string][]string(queryParameters),
200+
Headers: http.Header(headers),
201+
}
202+
203+
// handle "sane defaults" if requested; only where settings are not explicitly
204+
// provided by the user
205+
if *google {
206+
if len(opts.DNSServers) == 0 {
207+
opts.DNSServers = []secop.Endpoint{
208+
secop.Endpoint{IP: net.ParseIP("8.8.8.8"), Port: 53},
209+
secop.Endpoint{IP: net.ParseIP("8.8.4.4"), Port: 53},
210+
}
211+
}
212+
} else if *cloudflare {
213+
// override only if it's currently the default
214+
if ep == gdnsEndpoint {
215+
ep = cloudflareEndpoint
216+
}
217+
if len(opts.DNSServers) == 0 {
218+
opts.DNSServers = []secop.Endpoint{
219+
secop.Endpoint{IP: net.ParseIP("1.0.0.1"), Port: 53},
220+
secop.Endpoint{IP: net.ParseIP("1.1.1.1"), Port: 53},
221+
}
222+
}
223+
if _, ok := opts.QueryParameters["ct"]; !ok {
224+
opts.QueryParameters["ct"] = []string{"application/dns-json"}
225+
}
226+
}
227+
228+
provider, err := secop.NewGDNSProvider(ep, opts)
151229
if err != nil {
152230
log.Fatal(err)
153231
}

cmd/util.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,32 @@ func CSVtoIPs(csv string) (ips []net.IP, err error) {
4646

4747
return
4848
}
49+
50+
type KeyValue map[string][]string
51+
52+
func (k KeyValue) Set(kv string) error {
53+
kvs := strings.SplitN(kv, "=", 2)
54+
if len(kvs) != 2 {
55+
return fmt.Errorf("invalid format for %v; expected KEY=VALUE", kv)
56+
}
57+
key, value := kvs[0], kvs[1]
58+
59+
if vs, ok := k[key]; ok {
60+
k[key] = append(vs, value)
61+
} else {
62+
k[key] = []string{value}
63+
}
64+
65+
return nil
66+
}
67+
68+
func (k KeyValue) String() string {
69+
var s []string
70+
for k, vs := range k {
71+
for _, v := range vs {
72+
s = append(s, fmt.Sprintf("%v=%v", k, v))
73+
}
74+
}
75+
76+
return strings.Join(s, " ")
77+
}

cmd/util_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,38 @@ func TestCSVtoIPs(t *testing.T) {
113113
}
114114
}
115115
}
116+
117+
func TestKeyValue(t *testing.T) {
118+
kv := make(KeyValue)
119+
120+
kv.Set("key1=value=value")
121+
kv.Set("key2=value2-1")
122+
kv.Set("key2=value2-2")
123+
124+
if vs, ok := kv["key1"]; ok {
125+
if len(vs) == 1 {
126+
if vs[0] != "value=value" {
127+
t.Errorf("unexpected value %v", vs[0])
128+
}
129+
} else {
130+
t.Errorf("expected key1 value to be length 1, saw %v", vs)
131+
}
132+
} else {
133+
t.Error("did not get value for key1")
134+
}
135+
136+
if vs, ok := kv["key2"]; ok {
137+
if len(vs) == 2 {
138+
if vs[0] != "value2-1" {
139+
t.Errorf("unexpected value %v", vs[0])
140+
}
141+
if vs[1] != "value2-2" {
142+
t.Errorf("unexpected value %v", vs[1])
143+
}
144+
} else {
145+
t.Errorf("unexpected length for %v", vs)
146+
}
147+
} else {
148+
t.Error("did not get value for key2")
149+
}
150+
}

provider_google.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,10 @@ type GDNSOptions struct {
118118
// specified, Google determines this automatically. To specify that the
119119
// option should not be set, use the value "0.0.0.0/0".
120120
EDNSSubnet string
121+
// Additional headers to be sent with requests to the DNS provider
122+
Headers http.Header
123+
// Additional query parameters to be sent with requests to the DNS provider
124+
QueryParameters map[string][]string
121125
}
122126

123127
// NewGDNSProvider creates a GDNSProvider
@@ -206,6 +210,12 @@ func (g GDNSProvider) newRequest(q DNSQuestion) (*http.Request, error) {
206210
return nil, err
207211
}
208212

213+
// set headers if provided; we don't merge these for now, as we don't set
214+
// any headers by default
215+
if g.opts.Headers != nil {
216+
httpreq.Header = g.opts.Headers
217+
}
218+
209219
qry := httpreq.URL.Query()
210220
dnsType := fmt.Sprintf("%v", q.Type)
211221

@@ -217,6 +227,15 @@ func (g GDNSProvider) newRequest(q DNSQuestion) (*http.Request, error) {
217227
qry.Add("name", q.Name)
218228
qry.Add("type", dnsType)
219229

230+
// add additional query parameters
231+
if g.opts.QueryParameters != nil {
232+
for k, vs := range g.opts.QueryParameters {
233+
for _, v := range vs {
234+
qry.Add(k, v)
235+
}
236+
}
237+
}
238+
220239
edns := GoogleEDNSSentinelValue
221240
if g.opts.UseEDNSsubnetOption {
222241
edns = g.opts.EDNSSubnet

0 commit comments

Comments
 (0)