mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2025-02-24 15:43:08 +08:00
NEW PROVIDER: Azure Private DNS Zones (#2626)
Co-authored-by: Tom Limoncelli <tlimoncelli@stackoverflow.com>
This commit is contained in:
parent
51104ce775
commit
37e21030a2
9 changed files with 636 additions and 0 deletions
1
OWNERS
1
OWNERS
|
@ -2,6 +2,7 @@ providers/akamaiedgedns @svernick
|
|||
providers/autodns @arnoschoon
|
||||
providers/axfrddns @hnrgrgr
|
||||
providers/azuredns @vatsalyagoel
|
||||
providers/azure_private_dns @matthewmgamble
|
||||
providers/bind @tlimoncelli
|
||||
providers/cloudflare @tresni
|
||||
providers/cloudns @pragmaton
|
||||
|
|
|
@ -21,6 +21,7 @@ Currently supported DNS providers:
|
|||
- AWS Route 53
|
||||
- AXFR+DDNS
|
||||
- Azure DNS
|
||||
- Azure Private DNS
|
||||
- BIND
|
||||
- Cloudflare
|
||||
- ClouDNS
|
||||
|
|
|
@ -100,6 +100,7 @@
|
|||
* [AutoDNS](providers/autodns.md)
|
||||
* [AXFR+DDNS](providers/axfrddns.md)
|
||||
* [Azure DNS](providers/azure_dns.md)
|
||||
* [Azure Private DNS](providers/azure_private_dns.md)
|
||||
* [BIND](providers/bind.md)
|
||||
* [Cloudflare](providers/cloudflareapi.md)
|
||||
* [ClouDNS](providers/cloudns.md)
|
||||
|
|
|
@ -76,6 +76,7 @@ Providers in this category and their maintainers are:
|
|||
|Name|Maintainer|
|
||||
|---|---|
|
||||
|[`AZURE_DNS`](providers/azure_dns.md)|@vatsalyagoel|
|
||||
[[`AZURE_PRIVATE_DNS`](providers/azure_private_dns.md)|@matthewmgamble]
|
||||
|[`BIND`](providers/bind.md)|@tlimoncelli|
|
||||
|[`CLOUDFLAREAPI`](providers/cloudflareapi.md)|@tresni|
|
||||
|[`CSCGLOBAL`](providers/cscglobal.md)|@mikenz|
|
||||
|
|
74
documentation/providers/azure_private_dns.md
Normal file
74
documentation/providers/azure_private_dns.md
Normal file
|
@ -0,0 +1,74 @@
|
|||
## Configuration
|
||||
|
||||
This provider is for the [Azure Private DNS Service](https://learn.microsoft.com/en-us/azure/dns/private-dns-overview). This provider can only manage Azure Private DNS zones and will not manage public Azure DNS zones. To use this provider, add an entry to `creds.json` with `TYPE` set to `AZURE_PRIVATE_DNS`
|
||||
along with the API credentials.
|
||||
|
||||
Example:
|
||||
|
||||
{% code title="creds.json" %}
|
||||
```json
|
||||
{
|
||||
"azure_private_dns_main": {
|
||||
"TYPE": "AZURE_PRIVATE_DNS",
|
||||
"SubscriptionID": "AZURE_PRIVATE_SUBSCRIPTION_ID",
|
||||
"ResourceGroup": "AZURE_PRIVATE_RESOURCE_GROUP",
|
||||
"TenantID": "AZURE_PRIVATE_TENANT_ID",
|
||||
"ClientID": "AZURE_PRIVATE_CLIENT_ID",
|
||||
"ClientSecret": "AZURE_PRIVATE_CLIENT_SECRET"
|
||||
}
|
||||
}
|
||||
```
|
||||
{% endcode %}
|
||||
|
||||
You can also use environment variables:
|
||||
|
||||
```shell
|
||||
export AZURE_SUBSCRIPTION_ID=XXXXXXXXX
|
||||
export AZURE_RESOURCE_GROUP=YYYYYYYYY
|
||||
export AZURE_TENANT_ID=ZZZZZZZZ
|
||||
export AZURE_CLIENT_ID=AAAAAAAAA
|
||||
export AZURE_CLIENT_SECRET=BBBBBBBBB
|
||||
```
|
||||
|
||||
{% code title="creds.json" %}
|
||||
```json
|
||||
{
|
||||
"azure_private_dns_main": {
|
||||
"TYPE": "AZURE_PRIVATE_DNS",
|
||||
"SubscriptionID": "$AZURE_PRIVATE_SUBSCRIPTION_ID",
|
||||
"ResourceGroup": "$AZURE_PRIVATE_RESOURCE_GROUP",
|
||||
"ClientID": "$AZURE_PRIVATE_CLIENT_ID",
|
||||
"TenantID": "$AZURE_PRIVATE_TENANT_ID",
|
||||
"ClientSecret": "$AZURE_PRIVATE_CLIENT_SECRET"
|
||||
}
|
||||
}
|
||||
```
|
||||
{% endcode %}
|
||||
|
||||
## Metadata
|
||||
This provider does not recognize any special metadata fields unique to Azure Private DNS.
|
||||
|
||||
## Usage
|
||||
An example configuration:
|
||||
|
||||
{% code title="dnsconfig.js" %}
|
||||
```javascript
|
||||
var REG_NONE = NewRegistrar("none");
|
||||
var DSP_AZURE_PRIVATE_MAIN = NewDnsProvider("azure_private_dns_main");
|
||||
|
||||
D("example.com", REG_NONE, DnsProvider(DSP_AZURE_PRIVATE_MAIN),
|
||||
A("test", "1.2.3.4")
|
||||
);
|
||||
```
|
||||
{% endcode %}
|
||||
|
||||
## Activation
|
||||
DNSControl depends on a standard [Client credentials Authentication](https://docs.microsoft.com/en-us/cli/azure/create-an-azure-service-principal-azure-cli?view=azure-cli-latest) with permission to list, create and update private zones.
|
||||
|
||||
## New domains
|
||||
|
||||
If a domain does not exist in your Azure account, DNSControl will *not* automatically add it with the `push` command. You can do that manually via the control panel.
|
||||
|
||||
## Caveats
|
||||
|
||||
The ResourceGroup is case sensitive.
|
1
go.mod
1
go.mod
|
@ -7,6 +7,7 @@ toolchain go1.21.1
|
|||
require (
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.1.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.1.0
|
||||
github.com/Azure/go-autorest/autorest/to v0.4.0
|
||||
github.com/DisposaBoy/JsonConfigReader v0.0.0-20201129172854-99cf318d67e7
|
||||
github.com/PuerkitoBio/goquery v1.8.1
|
||||
|
|
6
go.sum
6
go.sum
|
@ -11,6 +11,12 @@ github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.0 h1:d81/ng9rET2YqdVkVwkb6EX
|
|||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.0/go.mod h1:s4kgfzA0covAXNicZHDMN58jExvcng2mC/DepXiF1EI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.1.0 h1:8iR6OLffWWorFdzL2JFCab5xpD8VKEE2DUBBl+HNTDY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.1.0/go.mod h1:copqlcjMWc/wgQ1N2fzsJFQxDdqKGg1EQt8T5wJMOGE=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal v1.1.2 h1:mLY+pNLjCUeKhgnAJWAKhEUQM+RJQo2H1fuGSw1Ky1E=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal v1.1.2/go.mod h1:FbdwsQ2EzwvXxOPcMFYO8ogEc9uMMIj3YkmCdXdAFmk=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.1.0 h1:rR8ZW79lE/ppfXTfiYSnMFv5EzmVuY4pfZWIkscIJ64=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.1.0/go.mod h1:y2zXtLSMM/X5Mfawq0lOftpWn3f4V6OCsRdINsvWBPI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0 h1:ECsQtyERDVz3NP3kvDOTLvbQhqWp/x9EsGKtb4ogUr8=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0/go.mod h1:s1tW/At+xHqjNFvWU4G0c0Qv33KOhvbGNj0RCTQDV8s=
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||
github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk=
|
||||
|
|
17
providers/azure_private_dns/auditrecords.go
Normal file
17
providers/azure_private_dns/auditrecords.go
Normal file
|
@ -0,0 +1,17 @@
|
|||
package azure_private_dns
|
||||
|
||||
import (
|
||||
"github.com/StackExchange/dnscontrol/v4/models"
|
||||
"github.com/StackExchange/dnscontrol/v4/pkg/rejectif"
|
||||
)
|
||||
|
||||
// AuditRecords returns a list of errors corresponding to the records
|
||||
// that aren't supported by this provider. If all records are
|
||||
// supported, an empty list is returned.
|
||||
func AuditRecords(records []*models.RecordConfig) []error {
|
||||
a := rejectif.Auditor{}
|
||||
|
||||
a.Add("MX", rejectif.MxNull) // Last verified 2020-12-28
|
||||
|
||||
return a.Audit(records)
|
||||
}
|
534
providers/azure_private_dns/azurePrivateDnsProvider.go
Normal file
534
providers/azure_private_dns/azurePrivateDnsProvider.go
Normal file
|
@ -0,0 +1,534 @@
|
|||
package azure_private_dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
|
||||
aauth "github.com/Azure/azure-sdk-for-go/sdk/azidentity"
|
||||
adns "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns"
|
||||
"github.com/Azure/go-autorest/autorest/to"
|
||||
"github.com/StackExchange/dnscontrol/v4/models"
|
||||
"github.com/StackExchange/dnscontrol/v4/pkg/diff2"
|
||||
"github.com/StackExchange/dnscontrol/v4/pkg/printer"
|
||||
"github.com/StackExchange/dnscontrol/v4/pkg/txtutil"
|
||||
"github.com/StackExchange/dnscontrol/v4/providers"
|
||||
)
|
||||
|
||||
type azurednsProvider struct {
|
||||
zonesClient *adns.PrivateZonesClient
|
||||
recordsClient *adns.RecordSetsClient
|
||||
zones map[string]*adns.PrivateZone
|
||||
resourceGroup *string
|
||||
subscriptionID *string
|
||||
rawRecords map[string][]*adns.RecordSet
|
||||
zoneName map[string]string
|
||||
}
|
||||
|
||||
func newAzureDNSDsp(conf map[string]string, metadata json.RawMessage) (providers.DNSServiceProvider, error) {
|
||||
return newAzureDNS(conf, metadata)
|
||||
}
|
||||
|
||||
func newAzureDNS(m map[string]string, metadata json.RawMessage) (*azurednsProvider, error) {
|
||||
subID, rg := m["SubscriptionID"], m["ResourceGroup"]
|
||||
clientID, clientSecret, tenantID := m["ClientID"], m["ClientSecret"], m["TenantID"]
|
||||
credential, authErr := aauth.NewClientSecretCredential(tenantID, clientID, clientSecret, nil)
|
||||
if authErr != nil {
|
||||
return nil, authErr
|
||||
}
|
||||
zonesClient, zoneErr := adns.NewPrivateZonesClient(subID, credential, nil)
|
||||
if zoneErr != nil {
|
||||
return nil, zoneErr
|
||||
}
|
||||
recordsClient, recordErr := adns.NewRecordSetsClient(subID, credential, nil)
|
||||
if recordErr != nil {
|
||||
return nil, recordErr
|
||||
}
|
||||
|
||||
api := &azurednsProvider{
|
||||
zonesClient: zonesClient,
|
||||
recordsClient: recordsClient,
|
||||
resourceGroup: to.StringPtr(rg),
|
||||
subscriptionID: to.StringPtr(subID),
|
||||
rawRecords: map[string][]*adns.RecordSet{},
|
||||
zoneName: map[string]string{},
|
||||
}
|
||||
err := api.getZones()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return api, nil
|
||||
}
|
||||
|
||||
var features = providers.DocumentationNotes{
|
||||
providers.CanGetZones: providers.Can(),
|
||||
providers.CanUseAlias: providers.Cannot("Azure DNS does not provide a generic ALIAS functionality. Use AZURE_ALIAS instead."),
|
||||
providers.CanUseAzureAlias: providers.Can(),
|
||||
providers.CanUseCAA: providers.Cannot("Azure Private DNS does not support CAA records"),
|
||||
providers.CanUseLOC: providers.Cannot(),
|
||||
providers.CanUseNAPTR: providers.Cannot(),
|
||||
providers.CanUsePTR: providers.Can(),
|
||||
providers.CanUseSRV: providers.Can(),
|
||||
providers.CanUseSSHFP: providers.Cannot(),
|
||||
providers.CanUseTLSA: providers.Cannot(),
|
||||
providers.DocCreateDomains: providers.Can(),
|
||||
providers.DocDualHost: providers.Can("Azure does not permit modifying the existing NS records, only adding/removing additional records."),
|
||||
providers.DocOfficiallySupported: providers.Can(),
|
||||
}
|
||||
|
||||
func init() {
|
||||
fns := providers.DspFuncs{
|
||||
Initializer: newAzureDNSDsp,
|
||||
RecordAuditor: AuditRecords,
|
||||
}
|
||||
providers.RegisterDomainServiceProviderType("AZURE_PRIVATE_DNS", fns, features)
|
||||
providers.RegisterCustomRecordType("AZURE_ALIAS", "AZURE_PRIVATE_DNS", "")
|
||||
}
|
||||
|
||||
func (a *azurednsProvider) getExistingZones() ([]*adns.PrivateZone, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 6000*time.Second)
|
||||
defer cancel()
|
||||
zonesPager := a.zonesClient.NewListByResourceGroupPager(*a.resourceGroup, nil)
|
||||
var zones []*adns.PrivateZone
|
||||
for zonesPager.More() {
|
||||
nextResult, zonesErr := zonesPager.NextPage(ctx)
|
||||
if zonesErr != nil {
|
||||
return nil, zonesErr
|
||||
}
|
||||
zones = append(zones, nextResult.Value...)
|
||||
}
|
||||
return zones, nil
|
||||
}
|
||||
|
||||
func (a *azurednsProvider) getZones() error {
|
||||
a.zones = make(map[string]*adns.PrivateZone)
|
||||
|
||||
zones, err := a.getExistingZones()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, z := range zones {
|
||||
zone := z
|
||||
domain := strings.TrimSuffix(*z.Name, ".")
|
||||
a.zones[domain] = zone
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type errNoExist struct {
|
||||
domain string
|
||||
}
|
||||
|
||||
func (e errNoExist) Error() string {
|
||||
return fmt.Sprintf("Private Domain %s not found in you Azure account", e.domain)
|
||||
}
|
||||
|
||||
func (a *azurednsProvider) GetNameservers(domain string) ([]*models.Nameserver, error) {
|
||||
// Azure Private DNS does not have the concept of "Name Servers" since these are local, private views of zones unique to the Azure environment
|
||||
var nss []string
|
||||
return models.ToNameserversStripTD(nss)
|
||||
}
|
||||
|
||||
func (a *azurednsProvider) ListZones() ([]string, error) {
|
||||
zonesResult, err := a.getExistingZones()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var zones []string
|
||||
|
||||
for _, z := range zonesResult {
|
||||
domain := strings.TrimSuffix(*z.Name, ".")
|
||||
zones = append(zones, domain)
|
||||
}
|
||||
|
||||
return zones, nil
|
||||
}
|
||||
|
||||
// GetZoneRecords gets the records of a zone and returns them in RecordConfig format.
|
||||
func (a *azurednsProvider) GetZoneRecords(domain string, meta map[string]string) (models.Records, error) {
|
||||
existingRecords, _, _, err := a.getExistingRecords(domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return existingRecords, nil
|
||||
}
|
||||
|
||||
func (a *azurednsProvider) getExistingRecords(domain string) (models.Records, []*adns.RecordSet, string, error) {
|
||||
zone, ok := a.zones[domain]
|
||||
if !ok {
|
||||
return nil, nil, "", errNoExist{domain}
|
||||
}
|
||||
zoneName := *zone.Name
|
||||
rawRecords, err := a.fetchRecordSets(zoneName)
|
||||
if err != nil {
|
||||
return nil, nil, "", err
|
||||
}
|
||||
|
||||
var existingRecords models.Records
|
||||
for _, set := range rawRecords {
|
||||
existingRecords = append(existingRecords, nativeToRecords(set, zoneName)...)
|
||||
}
|
||||
|
||||
a.rawRecords[domain] = rawRecords
|
||||
a.zoneName[domain] = zoneName
|
||||
|
||||
return existingRecords, rawRecords, zoneName, nil
|
||||
}
|
||||
|
||||
// GetZoneRecordsCorrections returns a list of corrections that will turn existing records into dc.Records.
|
||||
func (a *azurednsProvider) GetZoneRecordsCorrections(dc *models.DomainConfig, existingRecords models.Records) ([]*models.Correction, error) {
|
||||
txtutil.SplitSingleLongTxt(existingRecords) // Autosplit long TXT records
|
||||
|
||||
var corrections []*models.Correction
|
||||
|
||||
changes, err := diff2.ByRecordSet(existingRecords, dc, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, change := range changes {
|
||||
|
||||
// Copy all param values to local variables to avoid overwrites
|
||||
msgs := change.MsgsJoined
|
||||
dcn := dc.Name
|
||||
chaKey := change.Key
|
||||
|
||||
switch change.Type {
|
||||
case diff2.REPORT:
|
||||
corrections = append(corrections, &models.Correction{Msg: change.MsgsJoined})
|
||||
case diff2.CHANGE, diff2.CREATE:
|
||||
changeNew := change.New
|
||||
corrections = append(corrections, &models.Correction{
|
||||
Msg: msgs,
|
||||
F: func() error {
|
||||
return a.recordCreate(dcn, chaKey, changeNew)
|
||||
},
|
||||
})
|
||||
case diff2.DELETE:
|
||||
corrections = append(corrections, &models.Correction{
|
||||
Msg: msgs,
|
||||
F: func() error {
|
||||
return a.recordDelete(dcn, chaKey, change.Old)
|
||||
},
|
||||
})
|
||||
default:
|
||||
panic(fmt.Sprintf("unhandled change.Type %s", change.Type))
|
||||
}
|
||||
}
|
||||
|
||||
return corrections, nil
|
||||
}
|
||||
|
||||
func (a *azurednsProvider) recordCreate(zoneName string, reckey models.RecordKey, recs models.Records) error {
|
||||
|
||||
rrset, azRecType, err := a.recordToNativeDiff2(reckey, recs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var recordName string
|
||||
var i int64
|
||||
for _, r := range recs {
|
||||
i = int64(r.TTL)
|
||||
recordName = r.Name
|
||||
}
|
||||
rrset.Properties.TTL = &i
|
||||
|
||||
waitTime := 1
|
||||
retry:
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 6000*time.Second)
|
||||
defer cancel()
|
||||
_, err = a.recordsClient.CreateOrUpdate(ctx, *a.resourceGroup, zoneName, azRecType, recordName, *rrset, nil)
|
||||
|
||||
if e, ok := err.(*azcore.ResponseError); ok {
|
||||
if e.StatusCode == 429 {
|
||||
waitTime = waitTime * 2
|
||||
if waitTime > 300 {
|
||||
return err
|
||||
}
|
||||
printer.Printf("AZURE_PRIVATE_DNS: rate-limit paused for %v.\n", waitTime)
|
||||
time.Sleep(time.Duration(waitTime+1) * time.Second)
|
||||
goto retry
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (a *azurednsProvider) recordDelete(zoneName string, reckey models.RecordKey, recs models.Records) error {
|
||||
|
||||
shortName := strings.TrimSuffix(reckey.NameFQDN, "."+zoneName)
|
||||
if shortName == zoneName {
|
||||
shortName = "@"
|
||||
}
|
||||
|
||||
azRecType, err := nativeToRecordTypeDiff(to.StringPtr(reckey.Type))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
waitTime := 1
|
||||
retry:
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 6000*time.Second)
|
||||
defer cancel()
|
||||
_, err = a.recordsClient.Delete(ctx, *a.resourceGroup, zoneName, azRecType, shortName, nil)
|
||||
|
||||
if e, ok := err.(*azcore.ResponseError); ok {
|
||||
if e.StatusCode == 429 {
|
||||
waitTime = waitTime * 2
|
||||
if waitTime > 300 {
|
||||
return err
|
||||
}
|
||||
printer.Printf("AZURE_PRIVATE_DNS: rate-limit paused for %v.\n", waitTime)
|
||||
time.Sleep(time.Duration(waitTime+1) * time.Second)
|
||||
goto retry
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func nativeToRecordTypeDiff(recordType *string) (adns.RecordType, error) {
|
||||
recordTypeStripped := strings.TrimPrefix(*recordType, "Microsoft.Network/dnszones/")
|
||||
switch recordTypeStripped {
|
||||
case "A", "AZURE_ALIAS_A":
|
||||
return adns.RecordTypeA, nil
|
||||
case "AAAA", "AZURE_ALIAS_AAAA":
|
||||
return adns.RecordTypeAAAA, nil
|
||||
case "CAA":
|
||||
// CAA doesn't make any senese in a private dns zone in azure
|
||||
return adns.RecordTypeA, fmt.Errorf("nativeToRecordTypeDiff RTYPE %v UNIMPLEMENTED", *recordType)
|
||||
case "CNAME", "AZURE_ALIAS_CNAME":
|
||||
return adns.RecordTypeCNAME, nil
|
||||
case "MX":
|
||||
return adns.RecordTypeMX, nil
|
||||
case "NS":
|
||||
// NS record types don't make any sense in a private azure dns zone
|
||||
return adns.RecordTypeA, fmt.Errorf("nativeToRecordTypeDiff RTYPE %v UNIMPLEMENTED", *recordType)
|
||||
case "PTR":
|
||||
return adns.RecordTypePTR, nil
|
||||
case "SRV":
|
||||
return adns.RecordTypeSRV, nil
|
||||
case "TXT":
|
||||
return adns.RecordTypeTXT, nil
|
||||
case "SOA":
|
||||
return adns.RecordTypeSOA, nil
|
||||
default:
|
||||
// Unimplemented type. Return adns.A as a decoy, but send an error.
|
||||
return adns.RecordTypeA, fmt.Errorf("nativeToRecordTypeDiff RTYPE %v UNIMPLEMENTED", *recordType)
|
||||
}
|
||||
}
|
||||
|
||||
func nativeToRecords(set *adns.RecordSet, origin string) []*models.RecordConfig {
|
||||
var results []*models.RecordConfig
|
||||
switch rtype := *set.Type; rtype {
|
||||
case "Microsoft.Network/privateDnsZones/A":
|
||||
if set.Properties.ARecords != nil {
|
||||
// This is an A recordset. Process all the targets there.
|
||||
for _, rec := range set.Properties.ARecords {
|
||||
rc := &models.RecordConfig{TTL: uint32(*set.Properties.TTL), Original: set}
|
||||
rc.SetLabelFromFQDN(*set.Properties.Fqdn, origin)
|
||||
rc.Type = "A"
|
||||
_ = rc.SetTarget(*rec.IPv4Address)
|
||||
results = append(results, rc)
|
||||
}
|
||||
} else {
|
||||
panic(fmt.Errorf("nativeToRecords rtype %v unimplemented", *set.Type))
|
||||
|
||||
}
|
||||
case "Microsoft.Network/privateDnsZones/AAAA":
|
||||
if set.Properties.AaaaRecords != nil {
|
||||
// This is an AAAA recordset. Process all the targets there.
|
||||
for _, rec := range set.Properties.AaaaRecords {
|
||||
rc := &models.RecordConfig{TTL: uint32(*set.Properties.TTL), Original: set}
|
||||
rc.SetLabelFromFQDN(*set.Properties.Fqdn, origin)
|
||||
rc.Type = "AAAA"
|
||||
_ = rc.SetTarget(*rec.IPv6Address)
|
||||
results = append(results, rc)
|
||||
}
|
||||
} else {
|
||||
panic(fmt.Errorf("nativeToRecords rtype %v unimplemented", *set.Type))
|
||||
}
|
||||
case "Microsoft.Network/privateDnsZones/CNAME":
|
||||
if set.Properties.CnameRecord != nil {
|
||||
// This is a CNAME recordset. Process the targets. (there can only be one)
|
||||
rc := &models.RecordConfig{TTL: uint32(*set.Properties.TTL), Original: set}
|
||||
rc.SetLabelFromFQDN(*set.Properties.Fqdn, origin)
|
||||
rc.Type = "CNAME"
|
||||
_ = rc.SetTarget(*set.Properties.CnameRecord.Cname)
|
||||
results = append(results, rc)
|
||||
} else {
|
||||
panic(fmt.Errorf("nativeToRecords rtype %v unimplemented", *set.Type))
|
||||
}
|
||||
case "Microsoft.Network/privateDnsZones/PTR":
|
||||
for _, rec := range set.Properties.PtrRecords {
|
||||
rc := &models.RecordConfig{TTL: uint32(*set.Properties.TTL), Original: set}
|
||||
rc.SetLabelFromFQDN(*set.Properties.Fqdn, origin)
|
||||
rc.Type = "PTR"
|
||||
_ = rc.SetTarget(*rec.Ptrdname)
|
||||
results = append(results, rc)
|
||||
}
|
||||
case "Microsoft.Network/privateDnsZones/TXT":
|
||||
if len(set.Properties.TxtRecords) == 0 { // Empty String Record Parsing
|
||||
// This is a null TXT record.
|
||||
rc := &models.RecordConfig{TTL: uint32(*set.Properties.TTL), Original: set}
|
||||
rc.SetLabelFromFQDN(*set.Properties.Fqdn, origin)
|
||||
rc.Type = "TXT"
|
||||
_ = rc.SetTargetTXT("")
|
||||
results = append(results, rc)
|
||||
} else {
|
||||
// This is a normal TXT record. Collect all its segments.
|
||||
for _, rec := range set.Properties.TxtRecords {
|
||||
rc := &models.RecordConfig{TTL: uint32(*set.Properties.TTL), Original: set}
|
||||
rc.SetLabelFromFQDN(*set.Properties.Fqdn, origin)
|
||||
rc.Type = "TXT"
|
||||
var txts []string
|
||||
for _, txt := range rec.Value {
|
||||
txts = append(txts, *txt)
|
||||
}
|
||||
_ = rc.SetTargetTXTs(txts)
|
||||
results = append(results, rc)
|
||||
}
|
||||
}
|
||||
case "Microsoft.Network/privateDnsZones/MX":
|
||||
for _, rec := range set.Properties.MxRecords {
|
||||
rc := &models.RecordConfig{TTL: uint32(*set.Properties.TTL), Original: set}
|
||||
rc.SetLabelFromFQDN(*set.Properties.Fqdn, origin)
|
||||
rc.Type = "MX"
|
||||
_ = rc.SetTargetMX(uint16(*rec.Preference), *rec.Exchange)
|
||||
results = append(results, rc)
|
||||
}
|
||||
case "Microsoft.Network/privateDnsZones/SRV":
|
||||
for _, rec := range set.Properties.SrvRecords {
|
||||
rc := &models.RecordConfig{TTL: uint32(*set.Properties.TTL), Original: set}
|
||||
rc.SetLabelFromFQDN(*set.Properties.Fqdn, origin)
|
||||
rc.Type = "SRV"
|
||||
_ = rc.SetTargetSRV(uint16(*rec.Priority), uint16(*rec.Weight), uint16(*rec.Port), *rec.Target)
|
||||
results = append(results, rc)
|
||||
}
|
||||
case "Microsoft.Network/privateDnsZones/SOA":
|
||||
default:
|
||||
panic(fmt.Errorf("nativeToRecords rtype %v unimplemented", *set.Type))
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
// NOTE recordToNativeDiff2 is really "convert []RecordConfig to rrset".
|
||||
|
||||
func (a *azurednsProvider) recordToNativeDiff2(recordKey models.RecordKey, recordConfig []*models.RecordConfig) (*adns.RecordSet, adns.RecordType, error) {
|
||||
|
||||
recordKeyType := recordKey.Type
|
||||
// if recordKeyType == "AZURE_ALIAS" {
|
||||
// fmt.Fprintf(os.Stderr, "DEBUG: XXXXXXXXXXXXXXXXXXXXXXX %v\n", recordKeyType)
|
||||
// }
|
||||
|
||||
recordSet := &adns.RecordSet{Type: to.StringPtr(recordKeyType), Properties: &adns.RecordSetProperties{}}
|
||||
for _, rec := range recordConfig {
|
||||
switch recordKeyType {
|
||||
case "A":
|
||||
if recordSet.Properties.ARecords == nil {
|
||||
recordSet.Properties.ARecords = []*adns.ARecord{}
|
||||
}
|
||||
recordSet.Properties.ARecords = append(recordSet.Properties.ARecords, &adns.ARecord{IPv4Address: to.StringPtr(rec.GetTargetField())})
|
||||
case "AAAA":
|
||||
if recordSet.Properties.AaaaRecords == nil {
|
||||
recordSet.Properties.AaaaRecords = []*adns.AaaaRecord{}
|
||||
}
|
||||
recordSet.Properties.AaaaRecords = append(recordSet.Properties.AaaaRecords, &adns.AaaaRecord{IPv6Address: to.StringPtr(rec.GetTargetField())})
|
||||
case "CNAME":
|
||||
recordSet.Properties.CnameRecord = &adns.CnameRecord{Cname: to.StringPtr(rec.GetTargetField())}
|
||||
case "PTR":
|
||||
if recordSet.Properties.PtrRecords == nil {
|
||||
recordSet.Properties.PtrRecords = []*adns.PtrRecord{}
|
||||
}
|
||||
recordSet.Properties.PtrRecords = append(recordSet.Properties.PtrRecords, &adns.PtrRecord{Ptrdname: to.StringPtr(rec.GetTargetField())})
|
||||
case "TXT":
|
||||
if recordSet.Properties.TxtRecords == nil {
|
||||
recordSet.Properties.TxtRecords = []*adns.TxtRecord{}
|
||||
}
|
||||
// Empty TXT record needs to have no value set in it's properties
|
||||
if rec.GetTargetTXTJoined() == "" {
|
||||
var txts []*string
|
||||
for _, txt := range rec.GetTargetTXTSegmented() {
|
||||
txts = append(txts, to.StringPtr(txt))
|
||||
}
|
||||
recordSet.Properties.TxtRecords = append(recordSet.Properties.TxtRecords, &adns.TxtRecord{Value: txts})
|
||||
}
|
||||
case "MX":
|
||||
if recordSet.Properties.MxRecords == nil {
|
||||
recordSet.Properties.MxRecords = []*adns.MxRecord{}
|
||||
}
|
||||
recordSet.Properties.MxRecords = append(recordSet.Properties.MxRecords, &adns.MxRecord{Exchange: to.StringPtr(rec.GetTargetField()), Preference: to.Int32Ptr(int32(rec.MxPreference))})
|
||||
case "SRV":
|
||||
if recordSet.Properties.SrvRecords == nil {
|
||||
recordSet.Properties.SrvRecords = []*adns.SrvRecord{}
|
||||
}
|
||||
recordSet.Properties.SrvRecords = append(recordSet.Properties.SrvRecords, &adns.SrvRecord{Target: to.StringPtr(rec.GetTargetField()), Port: to.Int32Ptr(int32(rec.SrvPort)), Weight: to.Int32Ptr(int32(rec.SrvWeight)), Priority: to.Int32Ptr(int32(rec.SrvPriority))})
|
||||
/* CAA records don't work in a private zone */
|
||||
case "AZURE_ALIAS_A", "AZURE_ALIAS_AAAA", "AZURE_ALIAS_CNAME":
|
||||
return nil, adns.RecordTypeA, fmt.Errorf("recordToNativeDiff2 RTYPE %v UNIMPLEMENTED", recordKeyType) // ands.A is a placeholder
|
||||
default:
|
||||
return nil, adns.RecordTypeA, fmt.Errorf("recordToNativeDiff2 RTYPE %v UNIMPLEMENTED", recordKeyType) // ands.A is a placeholder
|
||||
}
|
||||
}
|
||||
|
||||
rt, err := nativeToRecordTypeDiff(to.StringPtr(*recordSet.Type))
|
||||
if err != nil {
|
||||
return nil, adns.RecordTypeA, err // adns.A is a placeholder
|
||||
}
|
||||
return recordSet, rt, nil
|
||||
}
|
||||
|
||||
func (a *azurednsProvider) fetchRecordSets(zoneName string) ([]*adns.RecordSet, error) {
|
||||
if zoneName == "" {
|
||||
return nil, nil
|
||||
}
|
||||
var records []*adns.RecordSet
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 6000*time.Second)
|
||||
defer cancel()
|
||||
|
||||
recordsPager := a.recordsClient.NewListPager(*a.resourceGroup, zoneName, nil)
|
||||
|
||||
for recordsPager.More() {
|
||||
|
||||
waitTime := 1
|
||||
retry:
|
||||
|
||||
nextResult, recordsErr := recordsPager.NextPage(ctx)
|
||||
|
||||
if recordsErr != nil {
|
||||
err := recordsErr
|
||||
if e, ok := err.(*azcore.ResponseError); ok {
|
||||
|
||||
if e.StatusCode == 429 {
|
||||
waitTime = waitTime * 2
|
||||
if waitTime > 300 {
|
||||
return nil, err
|
||||
}
|
||||
printer.Printf("AZURE_PRIVATE_DNS: rate-limit paused for %v.\n", waitTime)
|
||||
time.Sleep(time.Duration(waitTime+1) * time.Second)
|
||||
goto retry
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
records = append(records, nextResult.Value...)
|
||||
}
|
||||
|
||||
return records, nil
|
||||
}
|
||||
|
||||
func (a *azurednsProvider) EnsureZoneExists(domain string) error {
|
||||
if _, ok := a.zones[domain]; ok {
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
Loading…
Reference in a new issue