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

RFC2136: Add support for DNS-over-TLS #3974

Merged
merged 2 commits into from
Feb 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions docs/tutorials/rfc2136.md
Original file line number Diff line number Diff line change
Expand Up @@ -403,3 +403,15 @@ However, it also determines the name of the Kerberos principal which is used dur
This means that Active Directory might only work if this is set to a specific domain name, possibly leading to errors like this:
`KDC_ERR_S_PRINCIPAL_UNKNOWN Server not found in Kerberos database`.
To fix this, try setting `--rfc2136-host` to the "actual" hostname of your DNS server.

## DNS Over TLS (RFCs 7858 and 9103)

If your DNS server does zone transfers over TLS, you can instruct `external-dns` to connect over TLS with the following flags:

* `--rfc2136-use-tls` Will enable TLS for both zone transfers and for updates.
* `--tls-ca=<cert-file>` Is the path to a file containing certificate(s) that can be used to verify the DNS server
* `--tls-client-cert=<client-cert-file>` and
* `--tls-client-cert-key=<client-key-file>` Set the client certificate and key for mutual verification
* `--rfc2136-skip-tls-verify` Disables verification of the certificate supplied by the DNS server.

It is currently not supported to do only zone transfers over TLS, but not the updates. They are enabled and disabled together.
10 changes: 9 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,15 @@ func main() {
p, err = oci.NewOCIProvider(*config, domainFilter, zoneIDFilter, cfg.OCIZoneScope, 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 @@ -189,6 +189,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 @@ -347,6 +349,8 @@ var defaultConfig = &Config{
RFC2136TAXFR: true,
RFC2136MinTTL: 0,
RFC2136BatchChangeSize: 50,
RFC2136UseTLS: false,
RFC2136SkipTLSVerify: false,
NS1Endpoint: "",
NS1IgnoreSSL: false,
TransIPAccountName: "",
Expand Down Expand Up @@ -581,6 +585,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"
"sort"
Expand All @@ -33,6 +34,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 @@ -54,6 +56,7 @@ type rfc2136Provider struct {
axfr bool
minTTL time.Duration
batchChangeSize int
tlsConfig TLSConfig

// options specific to rfc3645 gss-tsig support
gssTsig bool
Expand All @@ -67,6 +70,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 @@ -82,7 +95,7 @@ type rfc2136Actions interface {
}

// NewRfc2136Provider is a factory function for OpenStack rfc2136 providers
func NewRfc2136Provider(host string, port int, zoneNames []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, zoneNames []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 @@ -98,6 +111,10 @@ func NewRfc2136Provider(host string, port int, zoneNames []string, insecure bool
return len(strings.Split(zoneNames[i], ".")) > len(strings.Split(zoneNames[j], "."))
})

if tlsConfig.UseTLS {
tlsConfig.ServerName = host
}

r := &rfc2136Provider{
nameserver: net.JoinHostPort(host, strconv.Itoa(port)),
zoneNames: zoneNames,
Expand All @@ -111,6 +128,7 @@ func NewRfc2136Provider(host string, port int, zoneNames []string, insecure bool
axfr: axfr,
minTTL: minTTL,
batchChangeSize: batchChangeSize,
tlsConfig: tlsConfig,
}
if actions != nil {
r.actions = actions
Expand Down Expand Up @@ -207,6 +225,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: %w", err)
}
conn, err := c.Dial(a)
if err != nil {
return nil, fmt.Errorf("failed to connect for transfer: %w", err)
}
t.Conn = conn
return t.In(m, r.nameserver)
}

Expand Down Expand Up @@ -419,7 +446,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: %w", err)
}

if !r.insecure {
if r.gssTsig {
Expand All @@ -439,8 +469,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 @@ -484,3 +512,33 @@ func findMsgZone(ep *endpoint.Endpoint, zoneNames []string) string {
log.Warnf("No available zone found for %s, set it to 'root'", ep.DNSName)
return dns.Fqdn(".")
}

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
}
Loading
Loading