1
0
mirror of https://github.com/StackExchange/dnscontrol.git synced 2024-05-11 05:55:12 +00:00

AZURE_DNS: Add support for Alias: AZURE_ALIAS() (#675)

* Add support for Alias in Azure

* Actioned comments and added parse tests

* GetTargetDebug

* Go formatting
This commit is contained in:
Vatsalya Goel
2020-03-02 20:25:42 +04:00
committed by GitHub
parent 56b448f329
commit 884118f6dd
16 changed files with 384 additions and 130 deletions

View File

@@ -39,6 +39,7 @@ func generateFeatureMatrix() error {
{"TLSA", "Provider can manage TLSA records"}, {"TLSA", "Provider can manage TLSA records"},
{"TXTMulti", "Provider can manage TXT records with multiple strings"}, {"TXTMulti", "Provider can manage TXT records with multiple strings"},
{"R53_ALIAS", "Provider supports Route 53 limited ALIAS"}, {"R53_ALIAS", "Provider supports Route 53 limited ALIAS"},
{"AZURE_ALIAS", "Provider supports Azure DNS limited ALIAS"},
{"dual host", "This provider is recommended for use in 'dual hosting' scenarios. Usually this means the provider allows full control over the apex NS records"}, {"dual host", "This provider is recommended for use in 'dual hosting' scenarios. Usually this means the provider allows full control over the apex NS records"},
{"create-domains", "This means the provider can automatically create domains that do not currently exist on your account. The 'dnscontrol create-domains' command will initialize any missing domains"}, {"create-domains", "This means the provider can automatically create domains that do not currently exist on your account. The 'dnscontrol create-domains' command will initialize any missing domains"},
@@ -80,6 +81,7 @@ func generateFeatureMatrix() error {
setCap("NAPTR", providers.CanUseNAPTR) setCap("NAPTR", providers.CanUseNAPTR)
setCap("PTR", providers.CanUsePTR) setCap("PTR", providers.CanUsePTR)
setCap("R53_ALIAS", providers.CanUseRoute53Alias) setCap("R53_ALIAS", providers.CanUseRoute53Alias)
setCap("AZURE_ALIAS", providers.CanUseAzureAlias)
setCap("SRV", providers.CanUseSRV) setCap("SRV", providers.CanUseSRV)
setCap("SSHFP", providers.CanUseSSHFP) setCap("SSHFP", providers.CanUseSSHFP)
setCap("TLSA", providers.CanUseTLSA) setCap("TLSA", providers.CanUseTLSA)

View File

@@ -0,0 +1,54 @@
---
name: AZURE_ALIAS
parameters:
- name
- type
- target
- modifiers ...
---
AZURE_ALIAS is a Azure specific virtual record type that points a record at either another record or an Azure entity.
It is analogous to a CNAME, but is usually resolved at request-time and served as an A record.
Unlike CNAMEs, ALIAS records can be used at the zone apex (`@`)
Unlike the regular ALIAS directive, AZURE_ALIAS is only supported on AZURE.
Attempting to use AZURE_ALIAS on another provider than Azure will result in an error.
The name should be the relative label for the domain.
The type can be any of the following:
* A
* AAAA
* CNAME
Target should be the Azure Id representing the target. It starts `/subscription/`. The resource id can be found in https://resources.azure.com/.
The Target can :
* Point to a public IP resource from a DNS `A/AAAA` record set.
You can create an A/AAAA record set and make it an alias record set to point to a public IP resource (standard or basic).
The DNS record set changes automatically if the public IP address changes or is deleted.
Dangling DNS records that point to incorrect IP addresses are avoided.
There is a current limit of 20 alias records sets per resource.
* Point to a Traffic Manager profile from a DNS `A/AAAA/CNAME` record set.
You can create an A/AAAA or CNAME record set and use alias records to point it to a Traffic Manager profile.
It's especially useful when you need to route traffic at a zone apex, as traditional CNAME records aren't supported for a zone apex.
For example, say your Traffic Manager profile is myprofile.trafficmanager.net and your business DNS zone is contoso.com.
You can create an alias record set of type A/AAAA for contoso.com (the zone apex) and point to myprofile.trafficmanager.net.
* Point to an Azure Content Delivery Network (CDN) endpoint.
This is useful when you create static websites using Azure storage and Azure CDN.
* Point to another DNS record set within the same zone.
Alias records can reference other record sets of the same type.
For example, a DNS CNAME record set can be an alias to another CNAME record set.
This arrangement is useful if you want some record sets to be aliases and some non-aliases.
{% include startExample.html %}
{% highlight js %}
D("example.com", REGISTRAR, DnsProvider("AZURE_DNS"),
AZURE_ALIAS("foo", "A", "/subscriptions/726f8cd6-6459-4db4-8e6d-2cd2716904e2/resourceGroups/test/providers/Microsoft.Network/trafficManagerProfiles/testpp2"), // record for traffic manager
AZURE_ALIAS("foo", "CNAME", "/subscriptions/726f8cd6-6459-4db4-8e6d-2cd2716904e2/resourceGroups/test/providers/Microsoft.Network/dnszones/example.com/A/quux."), // record in the same zone
);
{%endhighlight%}
{% include endExample.html %}

View File

@@ -263,7 +263,7 @@
<td class="danger"> <td class="danger">
<i class="fa fa-times text-danger" aria-hidden="true"></i> <i class="fa fa-times text-danger" aria-hidden="true"></i>
</td> </td>
<td class="danger" data-toggle="tooltip" data-container="body" data-placement="top" title="Only supported for Azure Resources. Not yet implemented"> <td class="danger" data-toggle="tooltip" data-container="body" data-placement="top" title="Azure DNS does not provide a generic ALIAS functionality. Use AZURE_ALIAS instead.">
<i class="fa has-tooltip fa-times text-danger" aria-hidden="true"></i> <i class="fa has-tooltip fa-times text-danger" aria-hidden="true"></i>
</td> </td>
<td><i class="fa fa-minus dim"></i></td> <td><i class="fa fa-minus dim"></i></td>
@@ -706,9 +706,7 @@
<tr> <tr>
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Provider supports Route 53 limited ALIAS">R53_ALIAS</th> <th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Provider supports Route 53 limited ALIAS">R53_ALIAS</th>
<td><i class="fa fa-minus dim"></i></td> <td><i class="fa fa-minus dim"></i></td>
<td class="danger"> <td><i class="fa fa-minus dim"></i></td>
<i class="fa fa-times text-danger" aria-hidden="true"></i>
</td>
<td><i class="fa fa-minus dim"></i></td> <td><i class="fa fa-minus dim"></i></td>
<td><i class="fa fa-minus dim"></i></td> <td><i class="fa fa-minus dim"></i></td>
<td><i class="fa fa-minus dim"></i></td> <td><i class="fa fa-minus dim"></i></td>
@@ -736,6 +734,35 @@
<td><i class="fa fa-minus dim"></i></td> <td><i class="fa fa-minus dim"></i></td>
<td><i class="fa fa-minus dim"></i></td> <td><i class="fa fa-minus dim"></i></td>
</tr> </tr>
<tr>
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Provider supports Azure DNS limited ALIAS">AZURE_ALIAS</th>
<td><i class="fa fa-minus dim"></i></td>
<td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i>
</td>
<td><i class="fa fa-minus dim"></i></td>
<td><i class="fa fa-minus dim"></i></td>
<td><i class="fa fa-minus dim"></i></td>
<td><i class="fa fa-minus dim"></i></td>
<td><i class="fa fa-minus dim"></i></td>
<td><i class="fa fa-minus dim"></i></td>
<td><i class="fa fa-minus dim"></i></td>
<td><i class="fa fa-minus dim"></i></td>
<td><i class="fa fa-minus dim"></i></td>
<td><i class="fa fa-minus dim"></i></td>
<td><i class="fa fa-minus dim"></i></td>
<td><i class="fa fa-minus dim"></i></td>
<td><i class="fa fa-minus dim"></i></td>
<td><i class="fa fa-minus dim"></i></td>
<td><i class="fa fa-minus dim"></i></td>
<td><i class="fa fa-minus dim"></i></td>
<td><i class="fa fa-minus dim"></i></td>
<td><i class="fa fa-minus dim"></i></td>
<td><i class="fa fa-minus dim"></i></td>
<td><i class="fa fa-minus dim"></i></td>
<td><i class="fa fa-minus dim"></i></td>
<td><i class="fa fa-minus dim"></i></td>
</tr>
<tr> <tr>
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="This provider is recommended for use in &#39;dual hosting&#39; scenarios. Usually this means the provider allows full control over the apex NS records">dual host</th> <th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="This provider is recommended for use in &#39;dual hosting&#39; scenarios. Usually this means the provider allows full control over the apex NS records">dual host</th>
<td class="danger" data-toggle="tooltip" data-container="body" data-placement="top" title="This driver does not manage NS records, so should not be used for dual-host scenarios"> <td class="danger" data-toggle="tooltip" data-container="body" data-placement="top" title="This driver does not manage NS records, so should not be used for dual-host scenarios">

View File

@@ -24,4 +24,4 @@ func init() {
3. CNAMEs at `@` are disallowed, but ALIAS is allowed. 3. CNAMEs at `@` are disallowed, but ALIAS is allowed.
4. Cloudflare does not have a native ALIAS type, but CNAMEs behave similarly. The Cloudflare provider "rewrites" ALIAS records to CNAME as it sees them. Other providers may not need this step. 4. Cloudflare does not have a native ALIAS type, but CNAMEs behave similarly. The Cloudflare provider "rewrites" ALIAS records to CNAME as it sees them. Other providers may not need this step.
5. Route 53 requires the use of R53_ALIAS instead of ALIAS. 5. Route 53 requires the use of R53_ALIAS instead of ALIAS.
6. Azure DNS requires the use of AZURE_ALIAS instead of ALIAS.

View File

@@ -26,10 +26,10 @@ func init() {
flag.Parse() flag.Parse()
} }
func getProvider(t *testing.T) (providers.DNSServiceProvider, string, map[int]bool) { func getProvider(t *testing.T) (providers.DNSServiceProvider, string, map[int]bool, map[string]string) {
if *providerToRun == "" { if *providerToRun == "" {
t.Log("No provider specified with -provider") t.Log("No provider specified with -provider")
return nil, "", nil return nil, "", nil, nil
} }
jsons, err := config.LoadProviderConfigs("providers.json") jsons, err := config.LoadProviderConfigs("providers.json")
if err != nil { if err != nil {
@@ -53,19 +53,19 @@ func getProvider(t *testing.T) (providers.DNSServiceProvider, string, map[int]bo
fails[i] = true fails[i] = true
} }
} }
return provider, cfg["domain"], fails return provider, cfg["domain"], fails, cfg
} }
t.Fatalf("Provider %s not found", *providerToRun) t.Fatalf("Provider %s not found", *providerToRun)
return nil, "", nil return nil, "", nil, nil
} }
func TestDNSProviders(t *testing.T) { func TestDNSProviders(t *testing.T) {
provider, domain, fails := getProvider(t) provider, domain, fails, cfg := getProvider(t)
if provider == nil { if provider == nil {
return return
} }
t.Run(fmt.Sprintf("%s", domain), func(t *testing.T) { t.Run(fmt.Sprintf("%s", domain), func(t *testing.T) {
runTests(t, provider, domain, fails) runTests(t, provider, domain, fails, cfg)
}) })
} }
@@ -84,7 +84,7 @@ func getDomainConfigWithNameservers(t *testing.T, prv providers.DNSServiceProvid
return dc return dc
} }
func runTests(t *testing.T, prv providers.DNSServiceProvider, domainName string, knownFailures map[int]bool) { func runTests(t *testing.T, prv providers.DNSServiceProvider, domainName string, knownFailures map[int]bool, origConfig map[string]string) {
dc := getDomainConfigWithNameservers(t, prv, domainName) dc := getDomainConfigWithNameservers(t, prv, domainName)
// run tests one at a time // run tests one at a time
end := *endIdx end := *endIdx
@@ -107,11 +107,22 @@ func runTests(t *testing.T, prv providers.DNSServiceProvider, domainName string,
for _, r := range tst.Records { for _, r := range tst.Records {
rc := models.RecordConfig(*r) rc := models.RecordConfig(*r)
if strings.Contains(rc.GetTargetField(), "**current-domain**") { if strings.Contains(rc.GetTargetField(), "**current-domain**") {
rc.SetTarget(strings.Replace(rc.GetTargetField(), "**current-domain**", domainName, 1) + ".") _ = rc.SetTarget(strings.Replace(rc.GetTargetField(), "**current-domain**", domainName, 1) + ".")
}
if strings.Contains(rc.GetTargetField(), "**current-domain-no-trailing**") {
_ = rc.SetTarget(strings.Replace(rc.GetTargetField(), "**current-domain-no-trailing**", domainName, 1))
} }
if strings.Contains(rc.GetLabelFQDN(), "**current-domain**") { if strings.Contains(rc.GetLabelFQDN(), "**current-domain**") {
rc.SetLabelFromFQDN(strings.Replace(rc.GetLabelFQDN(), "**current-domain**", domainName, 1), domainName) rc.SetLabelFromFQDN(strings.Replace(rc.GetLabelFQDN(), "**current-domain**", domainName, 1), domainName)
} }
if providers.ProviderHasCapability(*providerToRun, providers.CanUseAzureAlias) {
if strings.Contains(rc.GetTargetField(), "**subscription-id**") {
_ = rc.SetTarget(strings.Replace(rc.GetTargetField(), "**subscription-id**", origConfig["SubscriptionID"], 1))
}
if strings.Contains(rc.GetTargetField(), "**resource-group**") {
_ = rc.SetTarget(strings.Replace(rc.GetTargetField(), "**resource-group**", origConfig["ResourceGroup"], 1))
}
}
dom.Records = append(dom.Records, &rc) dom.Records = append(dom.Records, &rc)
} }
dom.IgnoredLabels = tst.IgnoredLabels dom.IgnoredLabels = tst.IgnoredLabels
@@ -156,7 +167,7 @@ func runTests(t *testing.T, prv providers.DNSServiceProvider, domainName string,
} }
func TestDualProviders(t *testing.T) { func TestDualProviders(t *testing.T) {
p, domain, _ := getProvider(t) p, domain, _, _ := getProvider(t)
if p == nil { if p == nil {
return return
} }
@@ -239,6 +250,14 @@ func r53alias(name, aliasType, target string) *rec {
return r return r
} }
func azureAlias(name, aliasType, target string) *rec {
r := makeRec(name, target, "AZURE_ALIAS")
r.AzureAlias = map[string]string{
"type": aliasType,
}
return r
}
func ns(name, target string) *rec { func ns(name, target string) *rec {
return makeRec(name, target, "NS") return makeRec(name, target, "NS")
} }
@@ -642,6 +661,20 @@ func makeTests(t *testing.T) []*TestCase {
tc("Update 1200 records", manyA("rec%04d", "1.2.3.5", 1200)...), tc("Update 1200 records", manyA("rec%04d", "1.2.3.5", 1200)...),
) )
} }
// AZURE_ALIAS
if !providers.ProviderHasCapability(*providerToRun, providers.CanUseAzureAlias) {
t.Log("Skipping AZURE_ALIAS Tests because provider does not support them")
} else {
t.Log("SubscriptionID: ")
tests = append(tests, tc("Empty"),
tc("create dependent A records", a("foo.a", "1.2.3.4"), a("quux.a", "2.3.4.5")),
tc("ALIAS to A record in same zone", a("foo.a", "1.2.3.4"), a("quux.a", "2.3.4.5"), azureAlias("bar.a", "A", "/subscriptions/**subscription-id**/resourceGroups/**resource-group**/providers/Microsoft.Network/dnszones/**current-domain-no-trailing**/A/foo.a")),
tc("change it", a("foo.a", "1.2.3.4"), a("quux.a", "2.3.4.5"), azureAlias("bar.a", "A", "/subscriptions/**subscription-id**/resourceGroups/**resource-group**/providers/Microsoft.Network/dnszones/**current-domain-no-trailing**/A/quux.a")),
tc("create dependent CNAME records", cname("foo.cname", "google.com"), cname("quux.cname", "google2.com")),
tc("ALIAS to CNAME record in same zone", cname("foo.cname", "google.com"), cname("quux.cname", "google2.com"), azureAlias("bar", "CNAME", "/subscriptions/**subscription-id**/resourceGroups/**resource-group**/providers/Microsoft.Network/dnszones/**current-domain-no-trailing**/CNAME/foo.cname")),
tc("change it", cname("foo.cname", "google.com"), cname("quux.cname", "google2.com"), azureAlias("bar.cname", "CNAME", "/subscriptions/**subscription-id**/resourceGroups/**resource-group**/providers/Microsoft.Network/dnszones/**current-domain-no-trailing**/CNAME/quux.cname")),
)
}
// Empty last // Empty last
tests = append(tests, tc("Empty")) tests = append(tests, tc("Empty"))

View File

@@ -79,7 +79,7 @@ func (dc *DomainConfig) Punycode() error {
if err != nil { if err != nil {
return err return err
} }
case "A", "AAAA", "CAA", "NAPTR", "SOA", "SSHFP", "TXT", "TLSA": case "A", "AAAA", "CAA", "NAPTR", "SOA", "SSHFP", "TXT", "TLSA", "AZURE_ALIAS":
// Nothing to do. // Nothing to do.
default: default:
msg := fmt.Sprintf("Punycode rtype %v unimplemented", rec.Type) msg := fmt.Sprintf("Punycode rtype %v unimplemented", rec.Type)

View File

@@ -95,6 +95,7 @@ type RecordConfig struct {
TlsaMatchingType uint8 `json:"tlsamatchingtype,omitempty"` TlsaMatchingType uint8 `json:"tlsamatchingtype,omitempty"`
TxtStrings []string `json:"txtstrings,omitempty"` // TxtStrings stores all strings (including the first). Target stores only the first one. TxtStrings []string `json:"txtstrings,omitempty"` // TxtStrings stores all strings (including the first). Target stores only the first one.
R53Alias map[string]string `json:"r53_alias,omitempty"` R53Alias map[string]string `json:"r53_alias,omitempty"`
AzureAlias map[string]string `json:"azure_alias,omitempty"`
Original interface{} `json:"-"` // Store pointer to provider-specific record object. Used in diffing. Original interface{} `json:"-"` // Store pointer to provider-specific record object. Used in diffing.
} }
@@ -307,6 +308,12 @@ func (rc *RecordConfig) Key() RecordKey {
// label with different alias types are considered separate. // label with different alias types are considered separate.
t = fmt.Sprintf("%s_%s", t, v) t = fmt.Sprintf("%s_%s", t, v)
} }
} else if rc.AzureAlias != nil {
if v, ok := rc.AzureAlias["type"]; ok {
// Azure aliases append their alias type, so that records for the same
// label with different alias types are considered separate.
t = fmt.Sprintf("%s_%s", t, v)
}
} }
return RecordKey{rc.NameFQDN, t} return RecordKey{rc.NameFQDN, t}
} }

View File

@@ -52,6 +52,9 @@ func (rc *RecordConfig) GetTargetCombined() string {
case "R53_ALIAS": case "R53_ALIAS":
// Differentiate between multiple R53_ALIASs on the same label. // Differentiate between multiple R53_ALIASs on the same label.
return fmt.Sprintf("%s atype=%s zone_id=%s", rc.Target, rc.R53Alias["type"], rc.R53Alias["zone_id"]) return fmt.Sprintf("%s atype=%s zone_id=%s", rc.Target, rc.R53Alias["type"], rc.R53Alias["zone_id"])
case "AZURE_ALIAS":
// Differentiate between multiple AZURE_ALIASs on the same label.
return fmt.Sprintf("%s atype=%s", rc.Target, rc.AzureAlias["type"])
case "SOA": case "SOA":
return fmt.Sprintf("%s %v %d %d %d %d %d", rc.Target, rc.SoaMbox, rc.SoaSerial, rc.SoaRefresh, rc.SoaRetry, rc.SoaExpire, rc.SoaMinttl) return fmt.Sprintf("%s %v %d %d %d %d %d", rc.Target, rc.SoaMbox, rc.SoaSerial, rc.SoaRefresh, rc.SoaRetry, rc.SoaExpire, rc.SoaMinttl)
default: default:
@@ -101,6 +104,8 @@ func (rc *RecordConfig) GetTargetDebug() string {
content += fmt.Sprintf(" caatag=%s caaflag=%d", rc.CaaTag, rc.CaaFlag) content += fmt.Sprintf(" caatag=%s caaflag=%d", rc.CaaTag, rc.CaaFlag)
case "R53_ALIAS": case "R53_ALIAS":
content += fmt.Sprintf(" type=%s zone_id=%s", rc.R53Alias["type"], rc.R53Alias["zone_id"]) content += fmt.Sprintf(" type=%s zone_id=%s", rc.R53Alias["type"], rc.R53Alias["zone_id"])
case "AZURE_ALIAS":
content += fmt.Sprintf(" type=%s", rc.AzureAlias["type"])
default: default:
panic(fmt.Errorf("rc.String rtype %v unimplemented", rc.Type)) panic(fmt.Errorf("rc.String rtype %v unimplemented", rc.Type))
// We panic so that we quickly find any switch statements // We panic so that we quickly find any switch statements

11
package-lock.json generated Normal file
View File

@@ -0,0 +1,11 @@
{
"requires": true,
"lockfileVersion": 1,
"dependencies": {
"prettier": {
"version": "1.19.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz",
"integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew=="
}
}
}

View File

@@ -165,6 +165,31 @@ var AAAA = recordBuilder('AAAA');
// ALIAS(name,target, recordModifiers...) // ALIAS(name,target, recordModifiers...)
var ALIAS = recordBuilder('ALIAS'); var ALIAS = recordBuilder('ALIAS');
// AZURE_ALIAS(name, type, target, recordModifiers...)
var AZURE_ALIAS = recordBuilder('AZURE_ALIAS', {
args: [
['name', _.isString],
['type', validateAzureAliasType],
['target', _.isString],
],
transform: function(record, args, modifier) {
record.name = args.name;
record.target = args.target;
if (_.isObject(record.azure_alias)) {
record.azure_alias['type'] = args.type;
} else {
record.azure_alias = { type: args.type };
}
},
});
function validateAzureAliasType(value) {
if (!_.isString(value)) {
return false;
}
return ['A', 'AAAA', 'CNAME'].indexOf(value) !== -1;
}
// R53_ALIAS(name, target, type, recordModifiers...) // R53_ALIAS(name, target, type, recordModifiers...)
var R53_ALIAS = recordBuilder('R53_ALIAS', { var R53_ALIAS = recordBuilder('R53_ALIAS', {
args: [ args: [
@@ -210,7 +235,7 @@ function validateR53AliasType(value) {
'SPF', 'SPF',
'SRV', 'SRV',
'NAPTR', 'NAPTR',
].indexOf(value) != -1 ].indexOf(value) !== -1
); );
} }
@@ -741,7 +766,8 @@ function CAA_BUILDER(value) {
(!value.issue && !value.issuewild) || (!value.issue && !value.issuewild) ||
(value.issue && (value.issue &&
value.issue.length == 0 && value.issue.length == 0 &&
value.issuewild && value.issuewild.length == 0) value.issuewild &&
value.issuewild.length == 0)
) { ) {
throw 'CAA_BUILDER requires at least one entry at issue or issuewild'; throw 'CAA_BUILDER requires at least one entry at issue or issuewild';
} }

View File

@@ -0,0 +1,5 @@
D("foo.com", "none",
AZURE_ALIAS("atest", "A", "foo.com."),
AZURE_ALIAS("aaaatest", "AAAA", "foo.com."),
AZURE_ALIAS("cnametest", "CNAME", "foo.com.")
);

View File

@@ -0,0 +1,37 @@
{
"registrars": [],
"dns_providers": [],
"domains": [
{
"name": "foo.com",
"registrar": "none",
"dnsProviders": {},
"records": [
{
"type": "AZURE_ALIAS",
"name": "atest",
"target": "foo.com.",
"azure_alias": {
"type": "A"
}
},
{
"type": "AZURE_ALIAS",
"name": "aaaatest",
"target": "foo.com.",
"azure_alias": {
"type": "AAAA"
}
},
{
"type": "AZURE_ALIAS",
"name": "cnametest",
"target": "foo.com.",
"azure_alias": {
"type": "CNAME"
}
}
]
}
]
}

View File

@@ -212,103 +212,105 @@ var _escData = map[string]*_escFile{
"/helpers.js": { "/helpers.js": {
name: "helpers.js", name: "helpers.js",
local: "pkg/js/helpers.js", local: "pkg/js/helpers.js",
size: 22107, size: 22817,
modtime: 0, modtime: 0,
compressed: ` compressed: `
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
Ef83AAD//yJmQYUhWQAA
`, `,
}, },
} }

View File

@@ -465,6 +465,7 @@ func init() {
{"SSHFP", providers.CanUseSSHFP}, {"SSHFP", providers.CanUseSSHFP},
{"SRV", providers.CanUseSRV}, {"SRV", providers.CanUseSRV},
{"TLSA", providers.CanUseTLSA}, {"TLSA", providers.CanUseTLSA},
{"AZURE_ALIAS", providers.CanUseAzureAlias},
} }
} }

View File

@@ -17,10 +17,11 @@ import (
) )
type azureDnsProvider struct { type azureDnsProvider struct {
zonesClient *adns.ZonesClient zonesClient *adns.ZonesClient
recordsClient *adns.RecordSetsClient recordsClient *adns.RecordSetsClient
zones map[string]*adns.Zone zones map[string]*adns.Zone
resourceGroup *string resourceGroup *string
subscriptionId *string
} }
func newAzureDnsDsp(conf map[string]string, metadata json.RawMessage) (providers.DNSServiceProvider, error) { func newAzureDnsDsp(conf map[string]string, metadata json.RawMessage) (providers.DNSServiceProvider, error) {
@@ -41,7 +42,7 @@ func newAzureDns(m map[string]string, metadata json.RawMessage) (*azureDnsProvid
zonesClient.Authorizer = authorizer zonesClient.Authorizer = authorizer
recordsClient.Authorizer = authorizer recordsClient.Authorizer = authorizer
api := &azureDnsProvider{zonesClient: &zonesClient, recordsClient: &recordsClient, resourceGroup: to.StringPtr(rg)} api := &azureDnsProvider{zonesClient: &zonesClient, recordsClient: &recordsClient, resourceGroup: to.StringPtr(rg), subscriptionId: to.StringPtr(subId)}
err := api.getZones() err := api.getZones()
if err != nil { if err != nil {
return nil, err return nil, err
@@ -50,7 +51,7 @@ func newAzureDns(m map[string]string, metadata json.RawMessage) (*azureDnsProvid
} }
var features = providers.DocumentationNotes{ var features = providers.DocumentationNotes{
providers.CanUseAlias: providers.Cannot("Only supported for Azure Resources. Not yet implemented"), providers.CanUseAlias: providers.Cannot("Azure DNS does not provide a generic ALIAS functionality. Use AZURE_ALIAS instead."),
providers.DocCreateDomains: providers.Can(), providers.DocCreateDomains: providers.Can(),
providers.DocDualHost: providers.Can("Azure does not permit modifying the existing NS records, only adding/removing additional records."), providers.DocDualHost: providers.Can("Azure does not permit modifying the existing NS records, only adding/removing additional records."),
providers.DocOfficiallySupported: providers.Can(), providers.DocOfficiallySupported: providers.Can(),
@@ -58,15 +59,16 @@ var features = providers.DocumentationNotes{
providers.CanUseSRV: providers.Can(), providers.CanUseSRV: providers.Can(),
providers.CanUseTXTMulti: providers.Can(), providers.CanUseTXTMulti: providers.Can(),
providers.CanUseCAA: providers.Can(), providers.CanUseCAA: providers.Can(),
providers.CanUseRoute53Alias: providers.Cannot(),
providers.CanUseNAPTR: providers.Cannot(), providers.CanUseNAPTR: providers.Cannot(),
providers.CanUseSSHFP: providers.Cannot(), providers.CanUseSSHFP: providers.Cannot(),
providers.CanUseTLSA: providers.Cannot(), providers.CanUseTLSA: providers.Cannot(),
providers.CanGetZones: providers.Can(), providers.CanGetZones: providers.Can(),
providers.CanUseAzureAlias: providers.Can(),
} }
func init() { func init() {
providers.RegisterDomainServiceProviderType("AZURE_DNS", newAzureDnsDsp, features) providers.RegisterDomainServiceProviderType("AZURE_DNS", newAzureDnsDsp, features)
providers.RegisterCustomRecordType("AZURE_ALIAS", "AZURE_DNS", "")
} }
func (a *azureDnsProvider) getExistingZones() (*adns.ZoneListResult, error) { func (a *azureDnsProvider) getExistingZones() (*adns.ZoneListResult, error) {
@@ -229,7 +231,7 @@ func (a *azureDnsProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*mod
return nil, fmt.Errorf("no record set found to delete. Name: '%s'. Type: '%s'", k.NameFQDN, k.Type) return nil, fmt.Errorf("no record set found to delete. Name: '%s'. Type: '%s'", k.NameFQDN, k.Type)
} }
} else { } else {
rrset, recordType := recordToNative(k, recs) rrset, recordType := a.recordToNative(k, recs)
var recordName string var recordName string
for _, r := range recs { for _, r := range recs {
i := int64(r.TTL) i := int64(r.TTL)
@@ -284,13 +286,13 @@ func (a *azureDnsProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*mod
func nativeToRecordType(recordType *string) adns.RecordType { func nativeToRecordType(recordType *string) adns.RecordType {
recordTypeStripped := strings.TrimPrefix(*recordType, "Microsoft.Network/dnszones/") recordTypeStripped := strings.TrimPrefix(*recordType, "Microsoft.Network/dnszones/")
switch recordTypeStripped { switch recordTypeStripped {
case "A": case "A", "AZURE_ALIAS_A":
return adns.A return adns.A
case "AAAA": case "AAAA", "AZURE_ALIAS_AAAA":
return adns.AAAA return adns.AAAA
case "CAA": case "CAA":
return adns.CAA return adns.CAA
case "CNAME": case "CNAME", "AZURE_ALIAS_CNAME":
return adns.CNAME return adns.CNAME
case "MX": case "MX":
return adns.MX return adns.MX
@@ -321,6 +323,17 @@ func nativeToRecords(set *adns.RecordSet, origin string) []*models.RecordConfig
_ = rc.SetTarget(*rec.Ipv4Address) _ = rc.SetTarget(*rec.Ipv4Address)
results = append(results, rc) results = append(results, rc)
} }
} else {
rc := &models.RecordConfig{
Type: "AZURE_ALIAS",
TTL: uint32(*set.TTL),
AzureAlias: map[string]string{
"type": "A",
},
}
rc.SetLabelFromFQDN(*set.Fqdn, origin)
_ = rc.SetTarget(*set.TargetResource.ID)
results = append(results, rc)
} }
case "Microsoft.Network/dnszones/AAAA": case "Microsoft.Network/dnszones/AAAA":
if set.AaaaRecords != nil { if set.AaaaRecords != nil {
@@ -331,13 +344,37 @@ func nativeToRecords(set *adns.RecordSet, origin string) []*models.RecordConfig
_ = rc.SetTarget(*rec.Ipv6Address) _ = rc.SetTarget(*rec.Ipv6Address)
results = append(results, rc) results = append(results, rc)
} }
} else {
rc := &models.RecordConfig{
Type: "AZURE_ALIAS",
TTL: uint32(*set.TTL),
AzureAlias: map[string]string{
"type": "AAAA",
},
}
rc.SetLabelFromFQDN(*set.Fqdn, origin)
_ = rc.SetTarget(*set.TargetResource.ID)
results = append(results, rc)
} }
case "Microsoft.Network/dnszones/CNAME": case "Microsoft.Network/dnszones/CNAME":
rc := &models.RecordConfig{TTL: uint32(*set.TTL)} if set.CnameRecord != nil {
rc.SetLabelFromFQDN(*set.Fqdn, origin) rc := &models.RecordConfig{TTL: uint32(*set.TTL)}
rc.Type = "CNAME" rc.SetLabelFromFQDN(*set.Fqdn, origin)
_ = rc.SetTarget(*set.CnameRecord.Cname) rc.Type = "CNAME"
results = append(results, rc) _ = rc.SetTarget(*set.CnameRecord.Cname)
results = append(results, rc)
} else {
rc := &models.RecordConfig{
Type: "AZURE_ALIAS",
TTL: uint32(*set.TTL),
AzureAlias: map[string]string{
"type": "CNAME",
},
}
rc.SetLabelFromFQDN(*set.Fqdn, origin)
_ = rc.SetTarget(*set.TargetResource.ID)
results = append(results, rc)
}
case "Microsoft.Network/dnszones/NS": case "Microsoft.Network/dnszones/NS":
for _, rec := range *set.NsRecords { for _, rec := range *set.NsRecords {
rc := &models.RecordConfig{TTL: uint32(*set.TTL)} rc := &models.RecordConfig{TTL: uint32(*set.TTL)}
@@ -401,7 +438,7 @@ func nativeToRecords(set *adns.RecordSet, origin string) []*models.RecordConfig
return results return results
} }
func recordToNative(recordKey models.RecordKey, recordConfig []*models.RecordConfig) (*adns.RecordSet, adns.RecordType) { func (a *azureDnsProvider) recordToNative(recordKey models.RecordKey, recordConfig []*models.RecordConfig) (*adns.RecordSet, adns.RecordType) {
recordSet := &adns.RecordSet{Type: to.StringPtr(recordKey.Type), RecordSetProperties: &adns.RecordSetProperties{}} recordSet := &adns.RecordSet{Type: to.StringPtr(recordKey.Type), RecordSetProperties: &adns.RecordSetProperties{}}
for _, rec := range recordConfig { for _, rec := range recordConfig {
switch recordKey.Type { switch recordKey.Type {
@@ -450,11 +487,15 @@ func recordToNative(recordKey models.RecordKey, recordConfig []*models.RecordCon
recordSet.CaaRecords = &[]adns.CaaRecord{} recordSet.CaaRecords = &[]adns.CaaRecord{}
} }
*recordSet.CaaRecords = append(*recordSet.CaaRecords, adns.CaaRecord{Value: to.StringPtr(rec.Target), Tag: to.StringPtr(rec.CaaTag), Flags: to.Int32Ptr(int32(rec.CaaFlag))}) *recordSet.CaaRecords = append(*recordSet.CaaRecords, adns.CaaRecord{Value: to.StringPtr(rec.Target), Tag: to.StringPtr(rec.CaaTag), Flags: to.Int32Ptr(int32(rec.CaaFlag))})
case "AZURE_ALIAS_A", "AZURE_ALIAS_AAAA", "AZURE_ALIAS_CNAME":
*recordSet.Type = rec.AzureAlias["type"]
recordSet.TargetResource = &adns.SubResource{ID: to.StringPtr(rec.Target)}
default: default:
panic(fmt.Errorf("rc.String rtype %v unimplemented", recordKey.Type)) panic(fmt.Errorf("rc.String rtype %v unimplemented", recordKey.Type))
} }
} }
return recordSet, nativeToRecordType(to.StringPtr(recordKey.Type))
return recordSet, nativeToRecordType(to.StringPtr(*recordSet.Type))
} }
func (a *azureDnsProvider) fetchRecordSets(zoneName string) ([]*adns.RecordSet, error) { func (a *azureDnsProvider) fetchRecordSets(zoneName string) ([]*adns.RecordSet, error) {

View File

@@ -56,6 +56,9 @@ const (
// CanGetZoe indicates the provider supports the get-zones subcommand. // CanGetZoe indicates the provider supports the get-zones subcommand.
CanGetZones CanGetZones
// CanUseAzureAlias indicates the provider support the specific Azure_ALIAS records that only the Azure provider supports
CanUseAzureAlias
) )
var providerCapabilities = map[string]map[Capability]bool{} var providerCapabilities = map[string]map[Capability]bool{}