mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-08-06 01:26:59 +02:00
322 lines
7.4 KiB
Go
322 lines
7.4 KiB
Go
/*
|
|
Copyright 2017 The Kubernetes Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package provider
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/miekg/dns"
|
|
"github.com/pkg/errors"
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
"github.com/kubernetes-incubator/external-dns/endpoint"
|
|
"github.com/kubernetes-incubator/external-dns/plan"
|
|
)
|
|
|
|
// rfc2136 provider type
|
|
type rfc2136Provider struct {
|
|
nameserver string
|
|
zoneName string
|
|
tsigKeyName string
|
|
tsigSecret string
|
|
tsigSecretAlg string
|
|
insecure bool
|
|
axfr bool
|
|
|
|
// only consider hosted zones managing domains ending in this suffix
|
|
domainFilter DomainFilter
|
|
dryRun bool
|
|
actions rfc2136Actions
|
|
}
|
|
|
|
var (
|
|
// Map of supported TSIG algorithms
|
|
tsigAlgs = map[string]string{
|
|
"hmac-md5": dns.HmacMD5,
|
|
"hmac-sha1": dns.HmacSHA1,
|
|
"hmac-sha256": dns.HmacSHA256,
|
|
"hmac-sha512": dns.HmacSHA512,
|
|
}
|
|
)
|
|
|
|
type rfc2136Actions interface {
|
|
SendMessage(msg *dns.Msg) error
|
|
IncomeTransfer(m *dns.Msg, a string) (env chan *dns.Envelope, err error)
|
|
}
|
|
|
|
// 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 DomainFilter, dryRun bool, actions rfc2136Actions) (Provider, error) {
|
|
secretAlgChecked, ok := tsigAlgs[secretAlg]
|
|
if !ok {
|
|
return nil, errors.Errorf("%s is not supported TSIG algorithm", secretAlg)
|
|
}
|
|
|
|
r := &rfc2136Provider{
|
|
nameserver: net.JoinHostPort(host, strconv.Itoa(port)),
|
|
zoneName: dns.Fqdn(zoneName),
|
|
insecure: insecure,
|
|
domainFilter: domainFilter,
|
|
dryRun: dryRun,
|
|
axfr: axfr,
|
|
}
|
|
if actions != nil {
|
|
r.actions = actions
|
|
} else {
|
|
r.actions = r
|
|
}
|
|
|
|
if !insecure {
|
|
r.tsigKeyName = dns.Fqdn(keyName)
|
|
r.tsigSecret = secret
|
|
r.tsigSecretAlg = secretAlgChecked
|
|
}
|
|
|
|
log.Infof("Configured RFC2136 with zone '%s' and nameserver '%s'", r.zoneName, r.nameserver)
|
|
return r, nil
|
|
}
|
|
|
|
// Records returns the list of records.
|
|
func (r rfc2136Provider) Records() ([]*endpoint.Endpoint, error) {
|
|
rrs, err := r.List()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var eps []*endpoint.Endpoint
|
|
|
|
OuterLoop:
|
|
for _, rr := range rrs {
|
|
log.Debugf("Record=%s", rr)
|
|
|
|
if rr.Header().Class != dns.ClassINET {
|
|
continue
|
|
}
|
|
|
|
rrFqdn := rr.Header().Name
|
|
rrTTL := endpoint.TTL(rr.Header().Ttl)
|
|
var rrType string
|
|
var rrValues []string
|
|
switch rr.Header().Rrtype {
|
|
case dns.TypeCNAME:
|
|
rrValues = []string{rr.(*dns.CNAME).Target}
|
|
rrType = "CNAME"
|
|
case dns.TypeA:
|
|
rrValues = []string{rr.(*dns.A).A.String()}
|
|
rrType = "A"
|
|
case dns.TypeAAAA:
|
|
rrValues = []string{rr.(*dns.AAAA).AAAA.String()}
|
|
rrType = "AAAA"
|
|
case dns.TypeTXT:
|
|
rrValues = (rr.(*dns.TXT).Txt)
|
|
rrType = "TXT"
|
|
default:
|
|
continue // Unhandled record type
|
|
}
|
|
|
|
for idx, existingEndpoint := range eps {
|
|
if existingEndpoint.DNSName == rrFqdn && existingEndpoint.RecordType == rrType {
|
|
eps[idx].Targets = append(eps[idx].Targets, rrValues...)
|
|
continue OuterLoop
|
|
}
|
|
}
|
|
|
|
ep := endpoint.NewEndpointWithTTL(
|
|
rrFqdn,
|
|
rrType,
|
|
rrTTL,
|
|
rrValues...,
|
|
)
|
|
|
|
eps = append(eps, ep)
|
|
}
|
|
|
|
return eps, nil
|
|
}
|
|
|
|
func (r rfc2136Provider) IncomeTransfer(m *dns.Msg, a string) (env chan *dns.Envelope, err error) {
|
|
t := new(dns.Transfer)
|
|
if !r.insecure {
|
|
t.TsigSecret = map[string]string{r.tsigKeyName: r.tsigSecret}
|
|
}
|
|
|
|
return t.In(m, r.nameserver)
|
|
}
|
|
|
|
func (r rfc2136Provider) List() ([]dns.RR, error) {
|
|
if !r.axfr {
|
|
log.Info("axfr is disabled")
|
|
return make([]dns.RR, 0), nil
|
|
}
|
|
|
|
log.Debugf("Fetching records for '%s'", r.zoneName)
|
|
|
|
m := new(dns.Msg)
|
|
m.SetAxfr(r.zoneName)
|
|
if !r.insecure {
|
|
m.SetTsig(r.tsigKeyName, r.tsigSecretAlg, 300, time.Now().Unix())
|
|
}
|
|
|
|
env, err := r.actions.IncomeTransfer(m, r.nameserver)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to fetch records via AXFR: %v", err)
|
|
}
|
|
|
|
records := make([]dns.RR, 0)
|
|
for e := range env {
|
|
if e.Error != nil {
|
|
if e.Error == dns.ErrSoa {
|
|
log.Error("AXFR error: unexpected response received from the server")
|
|
} else {
|
|
log.Errorf("AXFR error: %v", e.Error)
|
|
}
|
|
continue
|
|
}
|
|
records = append(records, e.RR...)
|
|
}
|
|
|
|
return records, nil
|
|
}
|
|
|
|
// ApplyChanges applies a given set of changes in a given zone.
|
|
func (r rfc2136Provider) ApplyChanges(changes *plan.Changes) error {
|
|
log.Debugf("ApplyChanges")
|
|
|
|
for _, ep := range changes.Create {
|
|
|
|
if !r.domainFilter.Match(ep.DNSName) {
|
|
log.Debugf("Skipping record %s because it was filtered out by the specified --domain-filter", ep.DNSName)
|
|
continue
|
|
}
|
|
|
|
r.AddRecord(ep)
|
|
}
|
|
for _, ep := range changes.UpdateNew {
|
|
|
|
if !r.domainFilter.Match(ep.DNSName) {
|
|
log.Debugf("Skipping record %s because it was filtered out by the specified --domain-filter", ep.DNSName)
|
|
continue
|
|
}
|
|
|
|
r.UpdateRecord(ep)
|
|
}
|
|
for _, ep := range changes.Delete {
|
|
|
|
if !r.domainFilter.Match(ep.DNSName) {
|
|
log.Debugf("Skipping record %s because it was filtered out by the specified --domain-filter", ep.DNSName)
|
|
continue
|
|
}
|
|
|
|
r.RemoveRecord(ep)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (r rfc2136Provider) UpdateRecord(ep *endpoint.Endpoint) error {
|
|
err := r.RemoveRecord(ep)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return r.AddRecord(ep)
|
|
}
|
|
|
|
func (r rfc2136Provider) AddRecord(ep *endpoint.Endpoint) error {
|
|
log.Debugf("AddRecord.ep=%s", ep)
|
|
for _, target := range ep.Targets {
|
|
newRR := fmt.Sprintf("%s %d %s %s", ep.DNSName, ep.RecordTTL, ep.RecordType, target)
|
|
log.Debugf("Adding RR: %s", newRR)
|
|
|
|
rr, err := dns.NewRR(newRR)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to build RR: %v", err)
|
|
}
|
|
|
|
rrs := make([]dns.RR, 1)
|
|
rrs[0] = rr
|
|
|
|
m := new(dns.Msg)
|
|
m.SetUpdate(r.zoneName)
|
|
m.Insert(rrs)
|
|
|
|
err = r.actions.SendMessage(m)
|
|
if err != nil {
|
|
return fmt.Errorf("RFC2136 query failed: %v", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (r rfc2136Provider) RemoveRecord(ep *endpoint.Endpoint) error {
|
|
log.Debugf("RemoveRecord.ep=%s", ep)
|
|
|
|
newRR := fmt.Sprintf("%s 0 %s 0.0.0.0", ep.DNSName, ep.RecordType)
|
|
log.Debugf("Adding RR: %s", newRR)
|
|
|
|
rr, err := dns.NewRR(newRR)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to build RR: %v", err)
|
|
}
|
|
|
|
rrs := make([]dns.RR, 1)
|
|
rrs[0] = rr
|
|
|
|
m := new(dns.Msg)
|
|
m.SetUpdate(r.zoneName)
|
|
m.RemoveRRset(rrs)
|
|
|
|
err = r.actions.SendMessage(m)
|
|
if err != nil {
|
|
return fmt.Errorf("RFC2136 query failed: %v", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (r rfc2136Provider) SendMessage(msg *dns.Msg) error {
|
|
if r.dryRun {
|
|
log.Debugf("SendMessage.skipped")
|
|
return nil
|
|
}
|
|
log.Debugf("SendMessage")
|
|
|
|
c := new(dns.Client)
|
|
c.SingleInflight = true
|
|
|
|
if !r.insecure {
|
|
c.TsigSecret = map[string]string{r.tsigKeyName: r.tsigSecret}
|
|
msg.SetTsig(r.tsigKeyName, r.tsigSecretAlg, 300, time.Now().Unix())
|
|
}
|
|
|
|
resp, _, err := c.Exchange(msg, r.nameserver)
|
|
if err != nil {
|
|
log.Infof("error in dns.Client.Exchange: %s", err)
|
|
return err
|
|
}
|
|
if resp != nil && resp.Rcode != dns.RcodeSuccess {
|
|
log.Infof("Bad dns.Client.Exchange response: %s", resp)
|
|
return fmt.Errorf("bad return code: %s", dns.RcodeToString[resp.Rcode])
|
|
}
|
|
|
|
log.Debugf("SendMessage.success")
|
|
return nil
|
|
}
|