Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use alternative DNS resolver for initial setup #25

Merged
merged 12 commits into from
Jan 18, 2024
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## main

FEATURES:

- The plugin now supports the setting of `client_dns_resolver` to override the DNS resolver used for DNSimple API endpoints. This is useful when running CoreDNS as the DNS resolver for the host. Format is `ADDRESS:PORT`. (#25)

## 1.0.0-rc.1

FEATURES:
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ dnsimple ZONE[:REGION ZONE ...] {
account_id DNSIMPLE_ACCOUNT_ID
base_url STRING
custom_dns_resolver ADDRESS
client_dns_resolver ADDRESS
fallthrough [ZONES...]
identifier STRING
max_retries MAX_RETRIES
Expand All @@ -28,8 +29,9 @@ dnsimple ZONE[:REGION ZONE ...] {
- `account_id`: The account ID containing the configured zones. If it's not provided, the environment variable `DNSIMPLE_ACCOUNT_ID` will be used.
- `base_url`: The base url for interacting with the DNSimple API. If no value is provided the production environment is used. See [DNSimple Sandbox API](https://support.dnsimple.com/articles/sandbox/) for sandbox environment testing.
- `custom_dns_resolver`: Optionally override the DNS resolver to use for resolving ALIAS records. By default, the system resolver is used. See the [ALIAS records](#alias-records) section for more details.
- `client_dns_resolver`: Optionally provide a DNS resolver to use for resolving DNSimple API endpoints. By default, the system resolver is used. This is useful when running CoreDNS as the DNS resolver for the host. Format is `ADDRESS:PORT`.
- `fallthrough`: If a query matches the zone(s) but no response message can be generated, the query will be passed to the next plugin in the chain. To restrict passing only for specific zones, list them here; all other zone queries will **not** "fall through".
- `identifer`: Optionally set the CoreDNS instance identifer string. This is used to report the sync status of the zone to the DNSimple application. If no identifier is provided, "default" is used.
- `identifier`: Optionally set the CoreDNS instance identifier string. This is used to report the sync status of the zone to the DNSimple application. If no identifier is provided, "default" is used.
- `max_retries`: Maximum retry attempts to fetch zones using the DNSimple API. Must be greater than zero. Defaults to 3.
- `refresh`: The interval to refresh zones at. It must be a valid duration, and defaults to `1m`.

Expand Down
16 changes: 11 additions & 5 deletions dnsimple.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,17 +83,23 @@ func (g *nameGraph) insertName(name string, value string) {

type DNSimpleApiCaller func(path string, body []byte) error

func createDNSimpleApiCaller(baseUrl string, accessToken string, userAgent string) DNSimpleApiCaller {
func createDNSimpleAPICaller(options Options, baseURL string, accessToken string, userAgent string) DNSimpleApiCaller {
return func(path string, body []byte) error {
url := fmt.Sprintf("%s%s", baseUrl, path)
url := fmt.Sprintf("%s%s", baseURL, path)
req, err := http.NewRequest("POST", url, bytes.NewBuffer(body))
if err != nil {
return err
}

client := http.Client{}
if options.clientDNSResolver != "" {
client.Transport.(*http.Transport).DialContext = options.customHTTPDialer
}

req.Header.Set("authorization", fmt.Sprintf("Bearer %s", accessToken))
req.Header.Set("content-type", "application/json")
req.Header.Set("user-agent", userAgent)
res, err := http.DefaultClient.Do(req)
res, err := client.Do(req)
if err != nil {
return err
}
Expand Down Expand Up @@ -160,14 +166,14 @@ func New(ctx context.Context, client dnsimpleService, keys map[string][]string,
}
}
dnsResolver := net.DefaultResolver
if opts.customDnsResolver != "" {
if opts.customDNSResolver != "" {
dnsResolver = &net.Resolver{
PreferGo: true,
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
d := net.Dialer{
Timeout: time.Second * 10,
}
return d.DialContext(ctx, network, opts.customDnsResolver)
return d.DialContext(ctx, network, opts.customDNSResolver)
},
}
}
Expand Down
18 changes: 8 additions & 10 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ require (
github.com/coredns/caddy v1.1.1
github.com/coredns/coredns v1.11.1
github.com/dnsimple/dnsimple-go v1.5.1
github.com/miekg/dns v1.1.57
github.com/miekg/dns v1.1.58
github.com/stretchr/testify v1.8.4
golang.org/x/oauth2 v0.14.0
)

require (
Expand All @@ -25,7 +26,6 @@ require (
github.com/kr/text v0.2.0 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/onsi/ginkgo/v2 v2.11.0 // indirect
github.com/onsi/gomega v1.27.10 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.16.0 // indirect
Expand All @@ -34,20 +34,18 @@ require (
github.com/prometheus/procfs v0.10.1 // indirect
github.com/quic-go/qtls-go1-20 v0.3.1 // indirect
github.com/quic-go/quic-go v0.37.4 // indirect
github.com/rogpeppe/go-internal v1.11.0 // indirect
github.com/shopspring/decimal v1.3.1 // indirect
github.com/stretchr/objx v0.5.0 // indirect
golang.org/x/crypto v0.17.0 // indirect
golang.org/x/crypto v0.18.0 // indirect
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/net v0.18.0 // indirect
golang.org/x/oauth2 v0.14.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/mod v0.14.0 // indirect
golang.org/x/net v0.20.0 // indirect
golang.org/x/sys v0.16.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.13.0 // indirect
golang.org/x/tools v0.17.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230807174057-1744710a1577 // indirect
google.golang.org/grpc v1.57.1 // indirect
google.golang.org/grpc v1.57.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
45 changes: 38 additions & 7 deletions setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"context"
"fmt"
"math/rand"
"net"
"net/http"
"os"
"strconv"
"strings"
Expand All @@ -15,6 +17,7 @@ import (
"github.com/coredns/coredns/plugin/pkg/fall"
clog "github.com/coredns/coredns/plugin/pkg/log"
"github.com/dnsimple/dnsimple-go/dnsimple"
"golang.org/x/oauth2"
)

var log = clog.NewWithPlugin("dnsimple")
Expand All @@ -27,15 +30,22 @@ type Options struct {
accountId string
accessToken string
apiCaller DNSimpleApiCaller
customDnsResolver string
customDNSResolver string
clientDNSResolver string
identifier string
maxRetries int
refresh time.Duration
customHTTPDialer func(ctx context.Context, network, address string) (net.Conn, error)
}

// exposed for testing
var newDnsimpleService = func(ctx context.Context, accessToken string, baseUrl string) (dnsimpleService, error) {
client := dnsimple.NewClient(dnsimple.StaticTokenHTTPClient(ctx, accessToken))
var newDnsimpleService = func(ctx context.Context, options Options, accessToken string, baseUrl string) (dnsimpleService, error) {
httpClient := dnsimple.StaticTokenHTTPClient(ctx, accessToken)

if options.clientDNSResolver != "" {
httpClient.Transport.(*oauth2.Transport).Base.(*http.Transport).DialContext = options.customHTTPDialer
}
client := dnsimple.NewClient(httpClient)
client.BaseURL = baseUrl
client.SetUserAgent(defaultUserAgent + "/" + PluginVersion)
return dnsimpleClient{client}, nil
Expand Down Expand Up @@ -75,7 +85,6 @@ func setup(c *caddy.Controller) error {
keys[zone] = append(keys[zone], region)
}

// TODO: set to warn when no zones are defined
if len(keys) == 0 {
return plugin.Error("dnsimple", c.Errf("no zone(s) specified"))
}
Expand Down Expand Up @@ -104,7 +113,12 @@ func setup(c *caddy.Controller) error {
if !c.NextArg() {
return plugin.Error("dnsimple", c.ArgErr())
}
opts.customDnsResolver = c.Val()
opts.customDNSResolver = c.Val()
case "client_dns_resolver":
if !c.NextArg() {
return plugin.Error("dnsimple", c.ArgErr())
}
opts.clientDNSResolver = c.Val()
case "fallthrough":
fall.SetZonesFromArgs(c.RemainingArgs())
case "identifier":
Expand Down Expand Up @@ -168,10 +182,27 @@ func setup(c *caddy.Controller) error {
opts.refresh = time.Duration(1) * time.Minute
}

opts.apiCaller = createDNSimpleApiCaller(baseUrl, accessToken, defaultUserAgent+"/"+PluginVersion)
if opts.clientDNSResolver == "" {
dialer := &net.Dialer{
Resolver: &net.Resolver{
PreferGo: true,
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
d := net.Dialer{
Timeout: time.Second * 5,
}
return d.DialContext(ctx, network, opts.clientDNSResolver)
},
},
}
opts.customHTTPDialer = func(ctx context.Context, network, address string) (net.Conn, error) {
return dialer.DialContext(ctx, network, address)
}
}

opts.apiCaller = createDNSimpleAPICaller(opts, baseUrl, accessToken, defaultUserAgent+"/"+PluginVersion)

ctx, cancel := context.WithCancel(context.Background())
client, err := newDnsimpleService(ctx, accessToken, baseUrl)
client, err := newDnsimpleService(ctx, opts, accessToken, baseUrl)
if err != nil {
cancel()
return err
Expand Down
2 changes: 1 addition & 1 deletion setup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (

func TestSetupDNSimple(t *testing.T) {
fakeClient := new(fakeDNSimpleClient)
newDnsimpleService = func(ctx context.Context, accessToken, baseUrl string) (dnsimpleService, error) {
newDnsimpleService = func(ctx context.Context, options Options, accessToken, baseUrl string) (dnsimpleService, error) {
return fakeClient, nil
}

Expand Down
Loading