Skip to content

Commit

Permalink
RFC2136: Add support for DNS-over-TLS
Browse files Browse the repository at this point in the history
 *  Reuse the existing TLS options.
 *  Add two new flags, one to enable DNS-over-TLS, and the second to
    disable cert checks for DNS-over-TLS.
 *  Factor out the connection code so that it can be shared between the
    zone transfer and the updates. If TLS was requested, it will be used
    for both.
 *  RFC9013 requires TLS 1.3 or later, and an ALPN negotiation of "dot".
  • Loading branch information
iteratee committed Oct 24, 2023
1 parent 95daddd commit 696d499
Show file tree
Hide file tree
Showing 4 changed files with 242 additions and 6 deletions.
10 changes: 9 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,15 @@ func main() {
p, err = oci.NewOCIProvider(*config, domainFilter, zoneIDFilter, cfg.DryRun)
}
case "rfc2136":
p, err = rfc2136.NewRfc2136Provider(cfg.RFC2136Host, cfg.RFC2136Port, cfg.RFC2136Zone, cfg.RFC2136Insecure, cfg.RFC2136TSIGKeyName, cfg.RFC2136TSIGSecret, cfg.RFC2136TSIGSecretAlg, cfg.RFC2136TAXFR, domainFilter, cfg.DryRun, cfg.RFC2136MinTTL, cfg.RFC2136GSSTSIG, cfg.RFC2136KerberosUsername, cfg.RFC2136KerberosPassword, cfg.RFC2136KerberosRealm, cfg.RFC2136BatchChangeSize, nil)
tlsConfig := rfc2136.TLSConfig{
UseTLS: cfg.RFC2136UseTls,
SkipTLSVerify: cfg.RFC2136SkipTLSVerify,
CAFilePath: cfg.TLSCA,
ClientCertFilePath: cfg.TLSClientCert,
ClientCertKeyFilePath: cfg.TLSClientCertKey,
ServerName: "",
}
p, err = rfc2136.NewRfc2136Provider(cfg.RFC2136Host, cfg.RFC2136Port, cfg.RFC2136Zone, cfg.RFC2136Insecure, cfg.RFC2136TSIGKeyName, cfg.RFC2136TSIGSecret, cfg.RFC2136TSIGSecretAlg, cfg.RFC2136TAXFR, domainFilter, cfg.DryRun, cfg.RFC2136MinTTL, cfg.RFC2136GSSTSIG, cfg.RFC2136KerberosUsername, cfg.RFC2136KerberosPassword, cfg.RFC2136KerberosRealm, cfg.RFC2136BatchChangeSize, tlsConfig, nil)
case "ns1":
p, err = ns1.NewNS1Provider(
ns1.NS1Config{
Expand Down
6 changes: 6 additions & 0 deletions pkg/apis/externaldns/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,8 @@ type Config struct {
RFC2136TAXFR bool
RFC2136MinTTL time.Duration
RFC2136BatchChangeSize int
RFC2136UseTls bool
RFC2136SkipTLSVerify bool
NS1Endpoint string
NS1IgnoreSSL bool
NS1MinTTLSeconds int
Expand Down Expand Up @@ -341,6 +343,8 @@ var defaultConfig = &Config{
RFC2136TAXFR: true,
RFC2136MinTTL: 0,
RFC2136BatchChangeSize: 50,
RFC2136UseTls: false,
RFC2136SkipTLSVerify: false,
NS1Endpoint: "",
NS1IgnoreSSL: false,
TransIPAccountName: "",
Expand Down Expand Up @@ -569,6 +573,8 @@ func (cfg *Config) ParseFlags(args []string) error {
app.Flag("rfc2136-kerberos-password", "When using the RFC2136 provider with GSS-TSIG, specify the password of the user with permissions to update DNS records (required when --rfc2136-gss-tsig=true)").Default(defaultConfig.RFC2136KerberosPassword).StringVar(&cfg.RFC2136KerberosPassword)
app.Flag("rfc2136-kerberos-realm", "When using the RFC2136 provider with GSS-TSIG, specify the realm of the user with permissions to update DNS records (required when --rfc2136-gss-tsig=true)").Default(defaultConfig.RFC2136KerberosRealm).StringVar(&cfg.RFC2136KerberosRealm)
app.Flag("rfc2136-batch-change-size", "When using the RFC2136 provider, set the maximum number of changes that will be applied in each batch.").Default(strconv.Itoa(defaultConfig.RFC2136BatchChangeSize)).IntVar(&cfg.RFC2136BatchChangeSize)
app.Flag("rfc2136-use-tls", "When using the RFC2136 provider, communicate with name server over tls").BoolVar(&cfg.RFC2136UseTls)
app.Flag("rfc2136-skip-tls-verify", "When using TLS with the RFC2136 provider, disable verification of any TLS certificates").BoolVar(&cfg.RFC2136SkipTLSVerify)

// Flags related to TransIP provider
app.Flag("transip-account", "When using the TransIP provider, specify the account name (required when --provider=transip)").Default(defaultConfig.TransIPAccountName).StringVar(&cfg.TransIPAccountName)
Expand Down
66 changes: 62 additions & 4 deletions provider/rfc2136/rfc2136.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package rfc2136

import (
"context"
"crypto/tls"
"fmt"
"net"
"strconv"
Expand All @@ -32,6 +33,7 @@ import (
log "github.com/sirupsen/logrus"

"sigs.k8s.io/external-dns/endpoint"
"sigs.k8s.io/external-dns/pkg/tlsutils"
"sigs.k8s.io/external-dns/plan"
"sigs.k8s.io/external-dns/provider"
)
Expand All @@ -53,6 +55,7 @@ type rfc2136Provider struct {
axfr bool
minTTL time.Duration
batchChangeSize int
tlsConfig TLSConfig

// options specific to rfc3645 gss-tsig support
gssTsig bool
Expand All @@ -66,6 +69,16 @@ type rfc2136Provider struct {
actions rfc2136Actions
}

// TLSConfig is comprised of the TLS-related fields necessary if we are using DNS over TLS
type TLSConfig struct {
UseTLS bool
SkipTLSVerify bool
CAFilePath string
ClientCertFilePath string
ClientCertKeyFilePath string
ServerName string
}

// Map of supported TSIG algorithms
var tsigAlgs = map[string]string{
"hmac-sha1": dns.HmacSHA1,
Expand All @@ -81,7 +94,7 @@ type rfc2136Actions interface {
}

// NewRfc2136Provider is a factory function for OpenStack rfc2136 providers
func NewRfc2136Provider(host string, port int, zoneName string, insecure bool, keyName string, secret string, secretAlg string, axfr bool, domainFilter endpoint.DomainFilter, dryRun bool, minTTL time.Duration, gssTsig bool, krb5Username string, krb5Password string, krb5Realm string, batchChangeSize int, actions rfc2136Actions) (provider.Provider, error) {
func NewRfc2136Provider(host string, port int, zoneName string, insecure bool, keyName string, secret string, secretAlg string, axfr bool, domainFilter endpoint.DomainFilter, dryRun bool, minTTL time.Duration, gssTsig bool, krb5Username string, krb5Password string, krb5Realm string, batchChangeSize int, tlsConfig TLSConfig, actions rfc2136Actions) (provider.Provider, error) {
secretAlgChecked, ok := tsigAlgs[secretAlg]
if !ok && !insecure && !gssTsig {
return nil, errors.Errorf("%s is not supported TSIG algorithm", secretAlg)
Expand All @@ -91,6 +104,10 @@ func NewRfc2136Provider(host string, port int, zoneName string, insecure bool, k
krb5Realm = strings.ToUpper(zoneName)
}

if tlsConfig.UseTLS {
tlsConfig.ServerName = host
}

r := &rfc2136Provider{
nameserver: net.JoinHostPort(host, strconv.Itoa(port)),
zoneName: dns.Fqdn(zoneName),
Expand All @@ -104,6 +121,7 @@ func NewRfc2136Provider(host string, port int, zoneName string, insecure bool, k
axfr: axfr,
minTTL: minTTL,
batchChangeSize: batchChangeSize,
tlsConfig: tlsConfig,
}
if actions != nil {
r.actions = actions
Expand Down Expand Up @@ -200,6 +218,15 @@ func (r rfc2136Provider) IncomeTransfer(m *dns.Msg, a string) (env chan *dns.Env
t.TsigSecret = map[string]string{r.tsigKeyName: r.tsigSecret}
}

c, err := makeClient(r)
if err != nil {
return nil, fmt.Errorf("error setting up TLS: %v", err)
}
conn, err := c.Dial(a)
if err != nil {
return nil, fmt.Errorf("failed to connect for transfer: %v", err)
}
t.Conn = conn
return t.In(m, r.nameserver)
}

Expand Down Expand Up @@ -385,7 +412,10 @@ func (r rfc2136Provider) SendMessage(msg *dns.Msg) error {
}
log.Debugf("SendMessage")

c := new(dns.Client)
c, err := makeClient(r)
if err != nil {
return fmt.Errorf("error setting up TLS: %v", err)
}

if !r.insecure {
if r.gssTsig {
Expand All @@ -405,8 +435,6 @@ func (r rfc2136Provider) SendMessage(msg *dns.Msg) error {
}
}

c.Net = "tcp"

resp, _, err := c.Exchange(msg, r.nameserver)
if err != nil {
if resp != nil && resp.Rcode != dns.RcodeSuccess {
Expand Down Expand Up @@ -439,3 +467,33 @@ func chunkBy(slice []*endpoint.Endpoint, chunkSize int) [][]*endpoint.Endpoint {

return chunks
}

func makeClient(r rfc2136Provider) (result *dns.Client, err error) {
c := new(dns.Client)

if r.tlsConfig.UseTLS {
log.Debug("RFC2136 Connecting via TLS")
c.Net = "tcp-tls"
tlsConfig, err := tlsutils.NewTLSConfig(
r.tlsConfig.ClientCertFilePath,
r.tlsConfig.ClientCertKeyFilePath,
r.tlsConfig.CAFilePath,
r.tlsConfig.ServerName,
r.tlsConfig.SkipTLSVerify,
// Per RFC9103
tls.VersionTLS13,
)
if err != nil {
return nil, err
}
if tlsConfig.NextProtos == nil {
// Per RFC9103
tlsConfig.NextProtos = []string{"dot"}
}
c.TLSConfig = tlsConfig
} else {
c.Net = "tcp"
}

return c, nil
}
166 changes: 165 additions & 1 deletion provider/rfc2136/rfc2136_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ package rfc2136

import (
"context"
"crypto/tls"
"fmt"
"os"
"strings"
"testing"
"time"
Expand Down Expand Up @@ -95,7 +97,18 @@ func (r *rfc2136Stub) IncomeTransfer(m *dns.Msg, a string) (env chan *dns.Envelo
}

func createRfc2136StubProvider(stub *rfc2136Stub) (provider.Provider, error) {
return NewRfc2136Provider("", 0, "", false, "key", "secret", "hmac-sha512", true, endpoint.DomainFilter{}, false, 300*time.Second, false, "", "", "", 50, stub)
tlsConfig := TLSConfig{
UseTLS: false,
SkipTLSVerify: false,
CAFilePath: "",
ClientCertFilePath: "",
ClientCertKeyFilePath: "",
}
return NewRfc2136Provider("", 0, "", false, "key", "secret", "hmac-sha512", true, endpoint.DomainFilter{}, false, 300*time.Second, false, "", "", "", 50, tlsConfig, stub)
}

func createRfc2136TLSStubProvider(stub *rfc2136Stub, tlsConfig TLSConfig) (provider.Provider, error) {
return NewRfc2136Provider("rfc2136-host", 0, "", false, "key", "secret", "hmac-sha512", true, endpoint.DomainFilter{}, false, 300*time.Second, false, "", "", "", 50, tlsConfig, stub)
}

func extractAuthoritySectionFromMessage(msg fmt.Stringer) []string {
Expand Down Expand Up @@ -130,6 +143,157 @@ func TestRfc2136GetRecordsMultipleTargets(t *testing.T) {
assert.Equal(t, 0, len(recs[0].ProviderSpecific), "expected no provider specific config")
}

func TestRfc2136TLSConfig(t *testing.T) {
stub := newStub()

caFile, err := os.CreateTemp("", "rfc2136-test-XXXXXXXX.crt")
assert.NoError(t, err)
defer os.Remove(caFile.Name())
_, err = caFile.Write([]byte(
`-----BEGIN CERTIFICATE-----
MIH+MIGxAhR2n1aQk0ONrQ8QQfa6GCzFWLmTXTAFBgMrZXAwITELMAkGA1UEBhMC
REUxEjAQBgNVBAMMCWxvY2FsaG9zdDAgFw0yMzEwMjQwNzI5NDNaGA8yMTIzMDkz
MDA3Mjk0M1owITELMAkGA1UEBhMCREUxEjAQBgNVBAMMCWxvY2FsaG9zdDAqMAUG
AytlcAMhAA1FzGJXuQdOpKv02SEl7SIA8SP8RVRI0QTi1bUFiFBLMAUGAytlcANB
ADiCKRUGDMyafSSYhl0KXoiXrFOxvhrGM5l15L4q82JM5Qb8wv0gNrnbGTZlInuv
ouB5ZN+05DzKCQhBekMnygQ=
-----END CERTIFICATE-----
`))

tlsConfig := TLSConfig{
UseTLS: true,
SkipTLSVerify: false,
CAFilePath: caFile.Name(),
ClientCertFilePath: "",
ClientCertKeyFilePath: "",
}

provider, err := createRfc2136TLSStubProvider(stub, tlsConfig)
assert.NoError(t, err)

rawProvider := provider.(*rfc2136Provider)

client, err := makeClient(*rawProvider)
assert.NoError(t, err)

assert.Equal(t, "tcp-tls", client.Net)
assert.Equal(t, false, client.TLSConfig.InsecureSkipVerify)
assert.Equal(t, "rfc2136-host", client.TLSConfig.ServerName)
assert.Equal(t, uint16(tls.VersionTLS13), client.TLSConfig.MinVersion)
assert.Equal(t, []string{"dot"}, client.TLSConfig.NextProtos)
}

func TestRfc2136TLSConfigNoVerify(t *testing.T) {
stub := newStub()

caFile, err := os.CreateTemp("", "rfc2136-test-XXXXXXXX.crt")
assert.NoError(t, err)
defer os.Remove(caFile.Name())
_, err = caFile.Write([]byte(
`-----BEGIN CERTIFICATE-----
MIH+MIGxAhR2n1aQk0ONrQ8QQfa6GCzFWLmTXTAFBgMrZXAwITELMAkGA1UEBhMC
REUxEjAQBgNVBAMMCWxvY2FsaG9zdDAgFw0yMzEwMjQwNzI5NDNaGA8yMTIzMDkz
MDA3Mjk0M1owITELMAkGA1UEBhMCREUxEjAQBgNVBAMMCWxvY2FsaG9zdDAqMAUG
AytlcAMhAA1FzGJXuQdOpKv02SEl7SIA8SP8RVRI0QTi1bUFiFBLMAUGAytlcANB
ADiCKRUGDMyafSSYhl0KXoiXrFOxvhrGM5l15L4q82JM5Qb8wv0gNrnbGTZlInuv
ouB5ZN+05DzKCQhBekMnygQ=
-----END CERTIFICATE-----
`))

tlsConfig := TLSConfig{
UseTLS: true,
SkipTLSVerify: true,
CAFilePath: caFile.Name(),
ClientCertFilePath: "",
ClientCertKeyFilePath: "",
}

provider, err := createRfc2136TLSStubProvider(stub, tlsConfig)
assert.NoError(t, err)

rawProvider := provider.(*rfc2136Provider)

client, err := makeClient(*rawProvider)
assert.NoError(t, err)

assert.Equal(t, "tcp-tls", client.Net)
assert.Equal(t, true, client.TLSConfig.InsecureSkipVerify)
assert.Equal(t, "rfc2136-host", client.TLSConfig.ServerName)
assert.Equal(t, uint16(tls.VersionTLS13), client.TLSConfig.MinVersion)
assert.Equal(t, []string{"dot"}, client.TLSConfig.NextProtos)
}

func TestRfc2136TLSConfigClientAuth(t *testing.T) {
stub := newStub()

caFile, err := os.CreateTemp("", "rfc2136-test-XXXXXXXX.crt")
assert.NoError(t, err)
defer os.Remove(caFile.Name())
_, err = caFile.Write([]byte(
`-----BEGIN CERTIFICATE-----
MIH+MIGxAhR2n1aQk0ONrQ8QQfa6GCzFWLmTXTAFBgMrZXAwITELMAkGA1UEBhMC
REUxEjAQBgNVBAMMCWxvY2FsaG9zdDAgFw0yMzEwMjQwNzI5NDNaGA8yMTIzMDkz
MDA3Mjk0M1owITELMAkGA1UEBhMCREUxEjAQBgNVBAMMCWxvY2FsaG9zdDAqMAUG
AytlcAMhAA1FzGJXuQdOpKv02SEl7SIA8SP8RVRI0QTi1bUFiFBLMAUGAytlcANB
ADiCKRUGDMyafSSYhl0KXoiXrFOxvhrGM5l15L4q82JM5Qb8wv0gNrnbGTZlInuv
ouB5ZN+05DzKCQhBekMnygQ=
-----END CERTIFICATE-----
`))

certFile, err := os.CreateTemp("", "rfc2136-test-XXXXXXXX-client.crt")
assert.NoError(t, err)
defer os.Remove(certFile.Name())
_, err = certFile.Write([]byte(
`-----BEGIN CERTIFICATE-----
MIIBfDCCAQICFANNDjPVDMTPm63C0jZ9M3H5I7GJMAoGCCqGSM49BAMCMCExCzAJ
BgNVBAYTAkRFMRIwEAYDVQQDDAlsb2NhbGhvc3QwIBcNMjMxMDI0MDcyMTU1WhgP
MjEyMzA5MzAwNzIxNTVaMCExCzAJBgNVBAYTAkRFMRIwEAYDVQQDDAlsb2NhbGhv
c3QwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQj7rjkeUEvjBT++IBMnIWgmI9VIjFx
4VUGFmzPEawOckdnKW4fBdePiItsgePDVK4Oys5bzfSDhl6aAPCe16pwvljB7yIm
xLJ+ytWk7OV/s10cmlaczrEtNeUjV1X9MTMwCgYIKoZIzj0EAwIDaAAwZQIwcZl8
TrwwsyX3A0enXB1ih+nruF8Q9f9Rmm2pNcbEv24QIW/P2HGQm9qfx4lrYa7hAjEA
goRP/fRfTTTLwLg8UBpUAmALX8A8HBSBaUlTTQcaImbcwU4DRSbv5JEA8tM1mWrA
-----END CERTIFICATE-----
`))

keyFile, err := os.CreateTemp("", "rfc2136-test-XXXXXXXX-client.key")
assert.NoError(t, err)
defer os.Remove(keyFile.Name())
_, err = keyFile.Write([]byte(
`-----BEGIN PRIVATE KEY-----
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDD5B+aPE+TuHCvW1f7L
U8jEPVXHv1fvCR8uBSsf1qdPo929XGpt5y5QfIGdW3NUeHWhZANiAAQj7rjkeUEv
jBT++IBMnIWgmI9VIjFx4VUGFmzPEawOckdnKW4fBdePiItsgePDVK4Oys5bzfSD
hl6aAPCe16pwvljB7yImxLJ+ytWk7OV/s10cmlaczrEtNeUjV1X9MTM=
-----END PRIVATE KEY-----
`))

tlsConfig := TLSConfig{
UseTLS: true,
SkipTLSVerify: false,
CAFilePath: caFile.Name(),
ClientCertFilePath: certFile.Name(),
ClientCertKeyFilePath: keyFile.Name(),
}

provider, err := createRfc2136TLSStubProvider(stub, tlsConfig)
log.Infof("provider, err is: %s", err)
assert.NoError(t, err)

rawProvider := provider.(*rfc2136Provider)

client, err := makeClient(*rawProvider)
log.Infof("client, err is: %v", client)
log.Infof("client, err is: %s", err)
assert.NoError(t, err)

assert.Equal(t, "tcp-tls", client.Net)
assert.Equal(t, false, client.TLSConfig.InsecureSkipVerify)
assert.Equal(t, "rfc2136-host", client.TLSConfig.ServerName)
assert.Equal(t, uint16(tls.VersionTLS13), client.TLSConfig.MinVersion)
assert.Equal(t, []string{"dot"}, client.TLSConfig.NextProtos)
}

func TestRfc2136GetRecords(t *testing.T) {
stub := newStub()
err := stub.setOutput([]string{
Expand Down

0 comments on commit 696d499

Please sign in to comment.