sigs.k8s.io/external-dns@v0.14.1/provider/rfc2136/rfc2136.go (about) 1 /* 2 Copyright 2017 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package rfc2136 18 19 import ( 20 "context" 21 "crypto/tls" 22 "fmt" 23 "net" 24 "sort" 25 "strconv" 26 "strings" 27 "time" 28 29 "github.com/bodgit/tsig" 30 "github.com/bodgit/tsig/gss" 31 "github.com/miekg/dns" 32 33 "github.com/pkg/errors" 34 log "github.com/sirupsen/logrus" 35 36 "sigs.k8s.io/external-dns/endpoint" 37 "sigs.k8s.io/external-dns/pkg/tlsutils" 38 "sigs.k8s.io/external-dns/plan" 39 "sigs.k8s.io/external-dns/provider" 40 ) 41 42 const ( 43 // maximum time DNS client can be off from server for an update to succeed 44 clockSkew = 300 45 ) 46 47 // rfc2136 provider type 48 type rfc2136Provider struct { 49 provider.BaseProvider 50 nameserver string 51 zoneNames []string 52 tsigKeyName string 53 tsigSecret string 54 tsigSecretAlg string 55 insecure bool 56 axfr bool 57 minTTL time.Duration 58 batchChangeSize int 59 tlsConfig TLSConfig 60 61 // options specific to rfc3645 gss-tsig support 62 gssTsig bool 63 krb5Username string 64 krb5Password string 65 krb5Realm string 66 67 // only consider hosted zones managing domains ending in this suffix 68 domainFilter endpoint.DomainFilter 69 dryRun bool 70 actions rfc2136Actions 71 } 72 73 // TLSConfig is comprised of the TLS-related fields necessary if we are using DNS over TLS 74 type TLSConfig struct { 75 UseTLS bool 76 SkipTLSVerify bool 77 CAFilePath string 78 ClientCertFilePath string 79 ClientCertKeyFilePath string 80 ServerName string 81 } 82 83 // Map of supported TSIG algorithms 84 var tsigAlgs = map[string]string{ 85 "hmac-sha1": dns.HmacSHA1, 86 "hmac-sha224": dns.HmacSHA224, 87 "hmac-sha256": dns.HmacSHA256, 88 "hmac-sha384": dns.HmacSHA384, 89 "hmac-sha512": dns.HmacSHA512, 90 } 91 92 type rfc2136Actions interface { 93 SendMessage(msg *dns.Msg) error 94 IncomeTransfer(m *dns.Msg, a string) (env chan *dns.Envelope, err error) 95 } 96 97 // NewRfc2136Provider is a factory function for OpenStack rfc2136 providers 98 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) { 99 secretAlgChecked, ok := tsigAlgs[secretAlg] 100 if !ok && !insecure && !gssTsig { 101 return nil, errors.Errorf("%s is not supported TSIG algorithm", secretAlg) 102 } 103 104 // Set zone to root if no set 105 if len(zoneNames) == 0 { 106 zoneNames = append(zoneNames, ".") 107 } 108 109 // Sort zones 110 sort.Slice(zoneNames, func(i, j int) bool { 111 return len(strings.Split(zoneNames[i], ".")) > len(strings.Split(zoneNames[j], ".")) 112 }) 113 114 if tlsConfig.UseTLS { 115 tlsConfig.ServerName = host 116 } 117 118 r := &rfc2136Provider{ 119 nameserver: net.JoinHostPort(host, strconv.Itoa(port)), 120 zoneNames: zoneNames, 121 insecure: insecure, 122 gssTsig: gssTsig, 123 krb5Username: krb5Username, 124 krb5Password: krb5Password, 125 krb5Realm: strings.ToUpper(krb5Realm), 126 domainFilter: domainFilter, 127 dryRun: dryRun, 128 axfr: axfr, 129 minTTL: minTTL, 130 batchChangeSize: batchChangeSize, 131 tlsConfig: tlsConfig, 132 } 133 if actions != nil { 134 r.actions = actions 135 } else { 136 r.actions = r 137 } 138 139 if !insecure { 140 r.tsigKeyName = dns.Fqdn(keyName) 141 r.tsigSecret = secret 142 r.tsigSecretAlg = secretAlgChecked 143 } 144 145 log.Infof("Configured RFC2136 with zone '%s' and nameserver '%s'", r.zoneNames, r.nameserver) 146 return r, nil 147 } 148 149 // KeyName will return TKEY name and TSIG handle to use for followon actions with a secure connection 150 func (r rfc2136Provider) KeyData() (keyName string, handle *gss.Client, err error) { 151 handle, err = gss.NewClient(new(dns.Client)) 152 if err != nil { 153 return keyName, handle, err 154 } 155 156 keyName, _, err = handle.NegotiateContextWithCredentials(r.nameserver, r.krb5Realm, r.krb5Username, r.krb5Password) 157 158 return keyName, handle, err 159 } 160 161 // Records returns the list of records. 162 func (r rfc2136Provider) Records(ctx context.Context) ([]*endpoint.Endpoint, error) { 163 rrs, err := r.List() 164 if err != nil { 165 return nil, err 166 } 167 168 var eps []*endpoint.Endpoint 169 170 OuterLoop: 171 for _, rr := range rrs { 172 log.Debugf("Record=%s", rr) 173 174 if rr.Header().Class != dns.ClassINET { 175 continue 176 } 177 178 rrFqdn := rr.Header().Name 179 rrTTL := endpoint.TTL(rr.Header().Ttl) 180 var rrType string 181 var rrValues []string 182 switch rr.Header().Rrtype { 183 case dns.TypeCNAME: 184 rrValues = []string{rr.(*dns.CNAME).Target} 185 rrType = "CNAME" 186 case dns.TypeA: 187 rrValues = []string{rr.(*dns.A).A.String()} 188 rrType = "A" 189 case dns.TypeAAAA: 190 rrValues = []string{rr.(*dns.AAAA).AAAA.String()} 191 rrType = "AAAA" 192 case dns.TypeTXT: 193 rrValues = (rr.(*dns.TXT).Txt) 194 rrType = "TXT" 195 case dns.TypeNS: 196 rrValues = []string{rr.(*dns.NS).Ns} 197 rrType = "NS" 198 default: 199 continue // Unhandled record type 200 } 201 202 for idx, existingEndpoint := range eps { 203 if existingEndpoint.DNSName == strings.TrimSuffix(rrFqdn, ".") && existingEndpoint.RecordType == rrType { 204 eps[idx].Targets = append(eps[idx].Targets, rrValues...) 205 continue OuterLoop 206 } 207 } 208 209 ep := endpoint.NewEndpointWithTTL( 210 rrFqdn, 211 rrType, 212 rrTTL, 213 rrValues..., 214 ) 215 216 eps = append(eps, ep) 217 } 218 219 return eps, nil 220 } 221 222 func (r rfc2136Provider) IncomeTransfer(m *dns.Msg, a string) (env chan *dns.Envelope, err error) { 223 t := new(dns.Transfer) 224 if !r.insecure && !r.gssTsig { 225 t.TsigSecret = map[string]string{r.tsigKeyName: r.tsigSecret} 226 } 227 228 c, err := makeClient(r) 229 if err != nil { 230 return nil, fmt.Errorf("error setting up TLS: %w", err) 231 } 232 conn, err := c.Dial(a) 233 if err != nil { 234 return nil, fmt.Errorf("failed to connect for transfer: %w", err) 235 } 236 t.Conn = conn 237 return t.In(m, r.nameserver) 238 } 239 240 func (r rfc2136Provider) List() ([]dns.RR, error) { 241 if !r.axfr { 242 log.Debug("axfr is disabled") 243 return make([]dns.RR, 0), nil 244 } 245 246 records := make([]dns.RR, 0) 247 for _, zone := range r.zoneNames { 248 log.Debugf("Fetching records for '%q'", zone) 249 250 m := new(dns.Msg) 251 m.SetAxfr(dns.Fqdn(zone)) 252 if !r.insecure && !r.gssTsig { 253 m.SetTsig(r.tsigKeyName, r.tsigSecretAlg, clockSkew, time.Now().Unix()) 254 } 255 256 env, err := r.actions.IncomeTransfer(m, r.nameserver) 257 if err != nil { 258 return nil, fmt.Errorf("failed to fetch records via AXFR: %w", err) 259 } 260 261 for e := range env { 262 if e.Error != nil { 263 if e.Error == dns.ErrSoa { 264 log.Error("AXFR error: unexpected response received from the server") 265 } else { 266 log.Errorf("AXFR error: %v", e.Error) 267 } 268 continue 269 } 270 records = append(records, e.RR...) 271 } 272 } 273 274 return records, nil 275 } 276 277 // ApplyChanges applies a given set of changes in a given zone. 278 func (r rfc2136Provider) ApplyChanges(ctx context.Context, changes *plan.Changes) error { 279 log.Debugf("ApplyChanges (Create: %d, UpdateOld: %d, UpdateNew: %d, Delete: %d)", len(changes.Create), len(changes.UpdateOld), len(changes.UpdateNew), len(changes.Delete)) 280 281 var errors []error 282 283 for c, chunk := range chunkBy(changes.Create, r.batchChangeSize) { 284 log.Debugf("Processing batch %d of create changes", c) 285 286 m := make(map[string]*dns.Msg) 287 m["."] = new(dns.Msg) // Add the root zone 288 for _, z := range r.zoneNames { 289 z = dns.Fqdn(z) 290 m[z] = new(dns.Msg) 291 } 292 for _, ep := range chunk { 293 if !r.domainFilter.Match(ep.DNSName) { 294 log.Debugf("Skipping record %s because it was filtered out by the specified --domain-filter", ep.DNSName) 295 continue 296 } 297 298 zone := findMsgZone(ep, r.zoneNames) 299 r.krb5Realm = strings.ToUpper(zone) 300 m[zone].SetUpdate(zone) 301 302 r.AddRecord(m[zone], ep) 303 } 304 305 // only send if there are records available 306 for _, z := range m { 307 if len(z.Ns) > 0 { 308 if err := r.actions.SendMessage(z); err != nil { 309 log.Errorf("RFC2136 create record failed: %v", err) 310 errors = append(errors, err) 311 continue 312 } 313 } 314 } 315 } 316 317 for c, chunk := range chunkBy(changes.UpdateNew, r.batchChangeSize) { 318 log.Debugf("Processing batch %d of update changes", c) 319 320 m := make(map[string]*dns.Msg) 321 m["."] = new(dns.Msg) // Add the root zone 322 for _, z := range r.zoneNames { 323 z = dns.Fqdn(z) 324 m[z] = new(dns.Msg) 325 } 326 327 for i, ep := range chunk { 328 if !r.domainFilter.Match(ep.DNSName) { 329 log.Debugf("Skipping record %s because it was filtered out by the specified --domain-filter", ep.DNSName) 330 continue 331 } 332 333 zone := findMsgZone(ep, r.zoneNames) 334 r.krb5Realm = strings.ToUpper(zone) 335 m[zone].SetUpdate(zone) 336 337 r.UpdateRecord(m[zone], changes.UpdateOld[i], ep) 338 } 339 340 // only send if there are records available 341 for _, z := range m { 342 if len(z.Ns) > 0 { 343 if err := r.actions.SendMessage(z); err != nil { 344 log.Errorf("RFC2136 update record failed: %v", err) 345 errors = append(errors, err) 346 continue 347 } 348 } 349 } 350 } 351 352 for c, chunk := range chunkBy(changes.Delete, r.batchChangeSize) { 353 log.Debugf("Processing batch %d of delete changes", c) 354 355 m := make(map[string]*dns.Msg) 356 m["."] = new(dns.Msg) // Add the root zone 357 for _, z := range r.zoneNames { 358 z = dns.Fqdn(z) 359 m[z] = new(dns.Msg) 360 } 361 for _, ep := range chunk { 362 if !r.domainFilter.Match(ep.DNSName) { 363 log.Debugf("Skipping record %s because it was filtered out by the specified --domain-filter", ep.DNSName) 364 continue 365 } 366 367 zone := findMsgZone(ep, r.zoneNames) 368 r.krb5Realm = strings.ToUpper(zone) 369 m[zone].SetUpdate(zone) 370 371 r.RemoveRecord(m[zone], ep) 372 } 373 374 // only send if there are records available 375 for _, z := range m { 376 if len(z.Ns) > 0 { 377 if err := r.actions.SendMessage(z); err != nil { 378 log.Errorf("RFC2136 delete record failed: %v", err) 379 errors = append(errors, err) 380 continue 381 } 382 } 383 } 384 } 385 386 if len(errors) > 0 { 387 return fmt.Errorf("RFC2136 had errors in one or more of its batches: %v", errors) 388 } 389 390 return nil 391 } 392 393 func (r rfc2136Provider) UpdateRecord(m *dns.Msg, oldEp *endpoint.Endpoint, newEp *endpoint.Endpoint) error { 394 err := r.RemoveRecord(m, oldEp) 395 if err != nil { 396 return err 397 } 398 399 return r.AddRecord(m, newEp) 400 } 401 402 func (r rfc2136Provider) AddRecord(m *dns.Msg, ep *endpoint.Endpoint) error { 403 log.Debugf("AddRecord.ep=%s", ep) 404 405 ttl := int64(r.minTTL.Seconds()) 406 if ep.RecordTTL.IsConfigured() && int64(ep.RecordTTL) > ttl { 407 ttl = int64(ep.RecordTTL) 408 } 409 410 for _, target := range ep.Targets { 411 newRR := fmt.Sprintf("%s %d %s %s", ep.DNSName, ttl, ep.RecordType, target) 412 log.Infof("Adding RR: %s", newRR) 413 414 rr, err := dns.NewRR(newRR) 415 if err != nil { 416 return fmt.Errorf("failed to build RR: %v", err) 417 } 418 419 m.Insert([]dns.RR{rr}) 420 } 421 422 return nil 423 } 424 425 func (r rfc2136Provider) RemoveRecord(m *dns.Msg, ep *endpoint.Endpoint) error { 426 log.Debugf("RemoveRecord.ep=%s", ep) 427 for _, target := range ep.Targets { 428 newRR := fmt.Sprintf("%s %d %s %s", ep.DNSName, ep.RecordTTL, ep.RecordType, target) 429 log.Infof("Removing RR: %s", newRR) 430 431 rr, err := dns.NewRR(newRR) 432 if err != nil { 433 return fmt.Errorf("failed to build RR: %v", err) 434 } 435 436 m.Remove([]dns.RR{rr}) 437 } 438 439 return nil 440 } 441 442 func (r rfc2136Provider) SendMessage(msg *dns.Msg) error { 443 if r.dryRun { 444 log.Debugf("SendMessage.skipped") 445 return nil 446 } 447 log.Debugf("SendMessage") 448 449 c, err := makeClient(r) 450 if err != nil { 451 return fmt.Errorf("error setting up TLS: %w", err) 452 } 453 454 if !r.insecure { 455 if r.gssTsig { 456 keyName, handle, err := r.KeyData() 457 if err != nil { 458 return err 459 } 460 defer handle.Close() 461 defer handle.DeleteContext(keyName) 462 463 c.TsigProvider = handle 464 465 msg.SetTsig(keyName, tsig.GSS, clockSkew, time.Now().Unix()) 466 } else { 467 c.TsigProvider = tsig.HMAC{r.tsigKeyName: r.tsigSecret} 468 msg.SetTsig(r.tsigKeyName, r.tsigSecretAlg, clockSkew, time.Now().Unix()) 469 } 470 } 471 472 resp, _, err := c.Exchange(msg, r.nameserver) 473 if err != nil { 474 if resp != nil && resp.Rcode != dns.RcodeSuccess { 475 log.Infof("error in dns.Client.Exchange: %s", err) 476 return err 477 } 478 log.Warnf("warn in dns.Client.Exchange: %s", err) 479 } 480 if resp != nil && resp.Rcode != dns.RcodeSuccess { 481 log.Infof("Bad dns.Client.Exchange response: %s", resp) 482 return fmt.Errorf("bad return code: %s", dns.RcodeToString[resp.Rcode]) 483 } 484 485 log.Debugf("SendMessage.success") 486 return nil 487 } 488 489 func chunkBy(slice []*endpoint.Endpoint, chunkSize int) [][]*endpoint.Endpoint { 490 var chunks [][]*endpoint.Endpoint 491 492 for i := 0; i < len(slice); i += chunkSize { 493 end := i + chunkSize 494 495 if end > len(slice) { 496 end = len(slice) 497 } 498 499 chunks = append(chunks, slice[i:end]) 500 } 501 502 return chunks 503 } 504 505 func findMsgZone(ep *endpoint.Endpoint, zoneNames []string) string { 506 for _, zone := range zoneNames { 507 if strings.HasSuffix(ep.DNSName, zone) { 508 return dns.Fqdn(zone) 509 } 510 } 511 512 log.Warnf("No available zone found for %s, set it to 'root'", ep.DNSName) 513 return dns.Fqdn(".") 514 } 515 516 func makeClient(r rfc2136Provider) (result *dns.Client, err error) { 517 c := new(dns.Client) 518 519 if r.tlsConfig.UseTLS { 520 log.Debug("RFC2136 Connecting via TLS") 521 c.Net = "tcp-tls" 522 tlsConfig, err := tlsutils.NewTLSConfig( 523 r.tlsConfig.ClientCertFilePath, 524 r.tlsConfig.ClientCertKeyFilePath, 525 r.tlsConfig.CAFilePath, 526 r.tlsConfig.ServerName, 527 r.tlsConfig.SkipTLSVerify, 528 // Per RFC9103 529 tls.VersionTLS13, 530 ) 531 if err != nil { 532 return nil, err 533 } 534 if tlsConfig.NextProtos == nil { 535 // Per RFC9103 536 tlsConfig.NextProtos = []string{"dot"} 537 } 538 c.TLSConfig = tlsConfig 539 } else { 540 c.Net = "tcp" 541 } 542 543 return c, nil 544 }