diff options
author | 2019-08-09 12:40:28 +0530 | |
---|---|---|
committer | 2019-08-09 08:10:28 +0100 | |
commit | 879466b0288a2d11e278950375c7593b60ea0677 (patch) | |
tree | 8f277238fd0cd9b458d63fbcc156267ad2a04006 /plugin | |
parent | 5b74d0f957a2565c8ee168ad55283b0914cac9d7 (diff) | |
download | coredns-879466b0288a2d11e278950375c7593b60ea0677.tar.gz coredns-879466b0288a2d11e278950375c7593b60ea0677.tar.zst coredns-879466b0288a2d11e278950375c7593b60ea0677.zip |
Add plugin for Azure DNS (#2945)
* Add plugin for Azure DNS
Signed-off-by: darshanime <deathbullet@gmail.com>
* Rename AzureDNS plugin to Azure
Signed-off-by: darshanime <deathbullet@gmail.com>
* remove upstream from azure syntax
Signed-off-by: darshanime <deathbullet@gmail.com>
* Rename azure plugin block keynames
Signed-off-by: darshanime <deathbullet@gmail.com>
* Normalize zone name before lookup in zones
Signed-off-by: darshanime <deathbullet@gmail.com>
* Update import path for caddy
Signed-off-by: darshanime <deathbullet@gmail.com>
* normalize azure zone name only if required
Signed-off-by: darshanime <deathbullet@gmail.com>
* Add support for MX, SRV, TXT, records
Signed-off-by: darshanime <deathbullet@gmail.com>
* Add specs for new record types
Signed-off-by: darshanime <deathbullet@gmail.com>
* Use sequential updates for zones
Signed-off-by: darshanime <deathbullet@gmail.com>
* Add OWNERS file for azure plugin
Signed-off-by: darshanime <deathbullet@gmail.com>
* Rename imports for third party packages
Signed-off-by: darshanime <deathbullet@gmail.com>
* Capitalize values in README
Signed-off-by: darshanime <deathbullet@gmail.com>
* Shorten keys for azure plugin config
Signed-off-by: darshanime <deathbullet@gmail.com>
* Fixup readme for azure plugin
Signed-off-by: darshanime <deathbullet@gmail.com>
Diffstat (limited to 'plugin')
-rw-r--r-- | plugin/azure/OWNERS | 8 | ||||
-rw-r--r-- | plugin/azure/README.md | 52 | ||||
-rw-r--r-- | plugin/azure/azure.go | 293 | ||||
-rw-r--r-- | plugin/azure/azure_test.go | 180 | ||||
-rw-r--r-- | plugin/azure/setup.go | 123 | ||||
-rw-r--r-- | plugin/azure/setup_test.go | 72 |
6 files changed, 728 insertions, 0 deletions
diff --git a/plugin/azure/OWNERS b/plugin/azure/OWNERS new file mode 100644 index 000000000..c30e67ae3 --- /dev/null +++ b/plugin/azure/OWNERS @@ -0,0 +1,8 @@ +reviewers: + - miekg + - yongtang + - darshanime +approvers: + - miekg + - yongtang + - darshanime diff --git a/plugin/azure/README.md b/plugin/azure/README.md new file mode 100644 index 000000000..5c7be25a8 --- /dev/null +++ b/plugin/azure/README.md @@ -0,0 +1,52 @@ +# azure + +## Name + +*azure* - enables serving zone data from Microsoft Azure DNS service. + +## Description + +The azure plugin is useful for serving zones from Microsoft Azure DNS. +Thi *azure* plugin supports all the DNS records supported by Azure, viz. A, AAAA, CAA, CNAME, MX, NS, PTR, SOA, SRV, and TXT record types. For a non-existing resource record, zone's SOA response will returned. + + +## Syntax + +~~~ txt +azure RESOURCE_GROUP:ZONE... { + tenant TENANT_ID + client CLIENT_ID + secret CLIENT_SECRET + subscription SUBSCRIPTION_ID +} +~~~ + +* **`RESOURCE_GROUP`** The resource group to which the dns hosted zones belong on Azure + +* **`ZONE`** the zone that contains the resource record sets to be + accessed. + +* `fallthrough` If zone matches and no record can be generated, pass request to the next plugin. + If **ZONES** is omitted, then fallthrough happens for all zones for which the plugin is + authoritative. If specific zones are listed (for example `in-addr.arpa` and `ip6.arpa`), then + only queries for those zones will be subject to fallthrough. + +* `environment` the azure environment to use. Defaults to `AzurePublicCloud`. Possible values: `AzureChinaCloud`, `AzureGermanCloud`, `AzurePublicCloud`, `AzureUSGovernmentCloud`. + +## Examples + +Enable the *azure* plugin with Azure credentials: + +~~~ txt +. { + azure resource_group_foo:foo.com { + tenant 123abc-123abc-123abc-123abc + client 123abc-123abc-123abc-123abc + secret 123abc-123abc-123abc-123abc + subscription 123abc-123abc-123abc-123abc + } +} +~~~ + +## Also See +- [Azure DNS Overview](https://docs.microsoft.com/en-us/azure/dns/dns-overview) diff --git a/plugin/azure/azure.go b/plugin/azure/azure.go new file mode 100644 index 000000000..f3b2fad40 --- /dev/null +++ b/plugin/azure/azure.go @@ -0,0 +1,293 @@ +package azure + +import ( + "context" + "fmt" + "net" + "sync" + "time" + + "github.com/coredns/coredns/plugin" + "github.com/coredns/coredns/plugin/file" + "github.com/coredns/coredns/plugin/pkg/fall" + "github.com/coredns/coredns/plugin/pkg/upstream" + "github.com/coredns/coredns/request" + + azuredns "github.com/Azure/azure-sdk-for-go/profiles/latest/dns/mgmt/dns" + "github.com/miekg/dns" +) + +type zone struct { + id string + z *file.Zone + zone string +} + +type zones map[string][]*zone + +// Azure is the core struct of the azure plugin. +type Azure struct { + Next plugin.Handler + Fall fall.F + zoneNames []string + client azuredns.RecordSetsClient + upstream *upstream.Upstream + zMu sync.RWMutex + zones zones +} + +// New validates the input DNS zones and initializes the Azure struct. +func New(ctx context.Context, dnsClient azuredns.RecordSetsClient, keys map[string][]string, up *upstream.Upstream) (*Azure, error) { + zones := make(map[string][]*zone, len(keys)) + zoneNames := make([]string, 0, len(keys)) + for resourceGroup, inputZoneNames := range keys { + for _, zoneName := range inputZoneNames { + _, err := dnsClient.ListAllByDNSZone(context.Background(), resourceGroup, zoneName, nil, "") + if err != nil { + return nil, err + } + // Normalizing zoneName to make it fqdn if required. + zoneNameFQDN := dns.Fqdn(zoneName) + if _, ok := zones[zoneNameFQDN]; !ok { + zoneNames = append(zoneNames, zoneNameFQDN) + } + zones[zoneNameFQDN] = append(zones[zoneNameFQDN], &zone{id: resourceGroup, zone: zoneName, z: file.NewZone(zoneName, "")}) + } + } + return &Azure{ + client: dnsClient, + zones: zones, + zoneNames: zoneNames, + upstream: up, + }, nil +} + +// Run updates the zone from azure. +func (h *Azure) Run(ctx context.Context) error { + if err := h.updateZones(ctx); err != nil { + return err + } + go func() { + for { + select { + case <-ctx.Done(): + log.Infof("Breaking out of Azure update loop: %v", ctx.Err()) + return + case <-time.After(1 * time.Minute): + if err := h.updateZones(ctx); err != nil && ctx.Err() == nil { + log.Errorf("Failed to update zones: %v", err) + } + } + } + }() + return nil +} + +func (h *Azure) updateZones(ctx context.Context) error { + errs := make([]string, 0) + for zName, z := range h.zones { + for i, hostedZone := range z { + recordSet, err := h.client.ListByDNSZone(ctx, hostedZone.id, hostedZone.zone, nil, "") + if err != nil { + errs = append(errs, fmt.Sprintf("failed to list resource records for %v from azure: %v", hostedZone.zone, err)) + } + newZ := updateZoneFromResourceSet(recordSet, zName) + newZ.Upstream = h.upstream + h.zMu.Lock() + (*z[i]).z = newZ + h.zMu.Unlock() + } + } + + if len(errs) != 0 { + return fmt.Errorf("errors updating zones: %v", errs) + } + return nil + +} + +func updateZoneFromResourceSet(recordSet azuredns.RecordSetListResultPage, zName string) *file.Zone { + newZ := file.NewZone(zName, "") + + for _, result := range *(recordSet.Response().Value) { + resultFqdn := *(result.RecordSetProperties.Fqdn) + resultTTL := uint32(*(result.RecordSetProperties.TTL)) + if result.RecordSetProperties.ARecords != nil { + for _, A := range *(result.RecordSetProperties.ARecords) { + a := dns.A{ + Hdr: dns.RR_Header{ + Name: resultFqdn, + Rrtype: dns.TypeA, + Class: dns.ClassINET, + Ttl: resultTTL}, + A: net.ParseIP(*(A.Ipv4Address))} + newZ.Insert(&a) + } + } + + if result.RecordSetProperties.AaaaRecords != nil { + for _, AAAA := range *(result.RecordSetProperties.AaaaRecords) { + aaaa := dns.AAAA{ + Hdr: dns.RR_Header{ + Name: resultFqdn, + Rrtype: dns.TypeAAAA, + Class: dns.ClassINET, + Ttl: resultTTL}, + AAAA: net.ParseIP(*(AAAA.Ipv6Address))} + newZ.Insert(&aaaa) + } + } + + if result.RecordSetProperties.MxRecords != nil { + for _, MX := range *(result.RecordSetProperties.MxRecords) { + mx := dns.MX{ + Hdr: dns.RR_Header{ + Name: resultFqdn, + Rrtype: dns.TypeMX, + Class: dns.ClassINET, + Ttl: resultTTL}, + Preference: uint16(*(MX.Preference)), + Mx: dns.Fqdn(*(MX.Exchange))} + newZ.Insert(&mx) + } + } + + if result.RecordSetProperties.PtrRecords != nil { + for _, PTR := range *(result.RecordSetProperties.PtrRecords) { + ptr := dns.PTR{ + Hdr: dns.RR_Header{ + Name: resultFqdn, + Rrtype: dns.TypePTR, + Class: dns.ClassINET, + Ttl: resultTTL}, + Ptr: dns.Fqdn(*(PTR.Ptrdname))} + newZ.Insert(&ptr) + } + } + + if result.RecordSetProperties.SrvRecords != nil { + for _, SRV := range *(result.RecordSetProperties.SrvRecords) { + srv := dns.SRV{ + Hdr: dns.RR_Header{ + Name: resultFqdn, + Rrtype: dns.TypeSRV, + Class: dns.ClassINET, + Ttl: resultTTL}, + Priority: uint16(*(SRV.Priority)), + Weight: uint16(*(SRV.Weight)), + Port: uint16(*(SRV.Port)), + Target: dns.Fqdn(*(SRV.Target))} + newZ.Insert(&srv) + } + } + + if result.RecordSetProperties.TxtRecords != nil { + for _, TXT := range *(result.RecordSetProperties.TxtRecords) { + txt := dns.TXT{ + Hdr: dns.RR_Header{ + Name: resultFqdn, + Rrtype: dns.TypeTXT, + Class: dns.ClassINET, + Ttl: resultTTL}, + Txt: *(TXT.Value)} + newZ.Insert(&txt) + } + } + + if result.RecordSetProperties.NsRecords != nil { + for _, NS := range *(result.RecordSetProperties.NsRecords) { + ns := dns.NS{ + Hdr: dns.RR_Header{ + Name: resultFqdn, + Rrtype: dns.TypeNS, + Class: dns.ClassINET, + Ttl: resultTTL}, + Ns: *(NS.Nsdname)} + newZ.Insert(&ns) + } + } + + if result.RecordSetProperties.SoaRecord != nil { + SOA := result.RecordSetProperties.SoaRecord + soa := dns.SOA{ + Hdr: dns.RR_Header{ + Name: resultFqdn, + Rrtype: dns.TypeSOA, + Class: dns.ClassINET, + Ttl: resultTTL}, + Minttl: uint32(*(SOA.MinimumTTL)), + Expire: uint32(*(SOA.ExpireTime)), + Retry: uint32(*(SOA.RetryTime)), + Refresh: uint32(*(SOA.RefreshTime)), + Serial: uint32(*(SOA.SerialNumber)), + Mbox: dns.Fqdn(*(SOA.Email)), + Ns: *(SOA.Host)} + newZ.Insert(&soa) + } + + if result.RecordSetProperties.CnameRecord != nil { + CNAME := result.RecordSetProperties.CnameRecord.Cname + cname := dns.CNAME{ + Hdr: dns.RR_Header{ + Name: resultFqdn, + Rrtype: dns.TypeCNAME, + Class: dns.ClassINET, + Ttl: resultTTL}, + Target: dns.Fqdn(*CNAME)} + newZ.Insert(&cname) + } + } + return newZ +} + +// ServeDNS implements the plugin.Handler interface. +func (h *Azure) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { + state := request.Request{W: w, Req: r} + qname := state.Name() + + zName := plugin.Zones(h.zoneNames).Matches(qname) + if zName == "" { + return plugin.NextOrFailure(h.Name(), h.Next, ctx, w, r) + } + + z, ok := h.zones[zName] // ok true if we are authoritive for the zone. + if !ok || z == nil { + return dns.RcodeServerFailure, nil + } + + m := new(dns.Msg) + m.SetReply(r) + m.Authoritative = true + var result file.Result + for _, hostedZone := range z { + h.zMu.RLock() + m.Answer, m.Ns, m.Extra, result = hostedZone.z.Lookup(ctx, state, qname) + h.zMu.RUnlock() + + // record type exists for this name (NODATA). + if len(m.Answer) != 0 || result == file.NoData { + break + } + } + + if len(m.Answer) == 0 && result != file.NoData && h.Fall.Through(qname) { + return plugin.NextOrFailure(h.Name(), h.Next, ctx, w, r) + } + + switch result { + case file.Success: + case file.NoData: + case file.NameError: + m.Rcode = dns.RcodeNameError + case file.Delegation: + m.Authoritative = false + case file.ServerFailure: + return dns.RcodeServerFailure, nil + } + + w.WriteMsg(m) + return dns.RcodeSuccess, nil +} + +// Name implements plugin.Handler.Name. +func (h *Azure) Name() string { return "azure" } diff --git a/plugin/azure/azure_test.go b/plugin/azure/azure_test.go new file mode 100644 index 000000000..d006f196b --- /dev/null +++ b/plugin/azure/azure_test.go @@ -0,0 +1,180 @@ +package azure + +import ( + "context" + "reflect" + "testing" + + "github.com/coredns/coredns/plugin/file" + "github.com/coredns/coredns/plugin/pkg/dnstest" + "github.com/coredns/coredns/plugin/pkg/fall" + "github.com/coredns/coredns/plugin/test" + "github.com/coredns/coredns/request" + + "github.com/miekg/dns" +) + +var demoAzure = Azure{ + Next: testHandler(), + Fall: fall.Zero, + zoneNames: []string{"example.org.", "www.example.org.", "example.org.", "sample.example.org."}, + zones: testZones(), +} + +func testZones() zones { + zones := make(map[string][]*zone) + zones["example.org."] = append(zones["example.org."], &zone{zone: "example.org."}) + newZ := file.NewZone("example.org.", "") + + for _, rr := range []string{ + "example.org. 300 IN A 1.2.3.4", + "example.org. 300 IN AAAA 2001:db8:85a3::8a2e:370:7334", + "www.example.org. 300 IN A 1.2.3.4", + "www.example.org. 300 IN A 1.2.3.4", + "org. 172800 IN NS ns3-06.azure-dns.org.", + "org. 300 IN SOA ns1-06.azure-dns.com. azuredns-hostmaster.microsoft.com. 1 3600 300 2419200 300", + "cname.example.org. 300 IN CNAME example.org", + "mail.example.org. 300 IN MX 10 mailserver.example.com", + "ptr.example.org. 300 IN PTR www.ptr-example.com", + "example.org. 300 IN SRV 1 10 5269 srv-1.example.com.", + "example.org. 300 IN SRV 1 10 5269 srv-2.example.com.", + "txt.example.org. 300 IN TXT \"TXT for example.org\"", + } { + r, _ := dns.NewRR(rr) + newZ.Insert(r) + } + zones["example.org."][0].z = newZ + return zones +} + +func testHandler() test.HandlerFunc { + return func(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { + state := request.Request{W: w, Req: r} + qname := state.Name() + m := new(dns.Msg) + rcode := dns.RcodeServerFailure + if qname == "example.gov." { // No records match, test fallthrough. + m.SetReply(r) + rr := test.A("example.gov. 300 IN A 2.4.6.8") + m.Answer = []dns.RR{rr} + m.Authoritative = true + rcode = dns.RcodeSuccess + } + m.SetRcode(r, rcode) + w.WriteMsg(m) + return rcode, nil + } +} + +func TestAzure(t *testing.T) { + tests := []struct { + qname string + qtype uint16 + wantRetCode int + wantAnswer []string + wantMsgRCode int + wantNS []string + expectedErr error + }{ + { + qname: "example.org.", + qtype: dns.TypeA, + wantAnswer: []string{"example.org. 300 IN A 1.2.3.4"}, + }, + { + qname: "example.org", + qtype: dns.TypeAAAA, + wantAnswer: []string{"example.org. 300 IN AAAA 2001:db8:85a3::8a2e:370:7334"}, + }, + { + qname: "example.org", + qtype: dns.TypeSOA, + wantAnswer: []string{"org. 300 IN SOA ns1-06.azure-dns.com. azuredns-hostmaster.microsoft.com. 1 3600 300 2419200 300"}, + }, + { + qname: "badexample.com", + qtype: dns.TypeA, + wantRetCode: dns.RcodeServerFailure, + wantMsgRCode: dns.RcodeServerFailure, + }, + { + qname: "example.gov", + qtype: dns.TypeA, + wantAnswer: []string{"example.gov. 300 IN A 2.4.6.8"}, + }, + { + qname: "example.org", + qtype: dns.TypeSRV, + wantAnswer: []string{"example.org. 300 IN SRV 1 10 5269 srv-1.example.com.", "example.org. 300 IN SRV 1 10 5269 srv-2.example.com."}, + }, + { + qname: "cname.example.org.", + qtype: dns.TypeCNAME, + wantAnswer: []string{"cname.example.org. 300 IN CNAME example.org."}, + }, + { + qname: "cname.example.org.", + qtype: dns.TypeA, + wantAnswer: []string{"cname.example.org. 300 IN CNAME example.org.", "example.org. 300 IN A 1.2.3.4"}, + }, + { + qname: "mail.example.org.", + qtype: dns.TypeMX, + wantAnswer: []string{"mail.example.org. 300 IN MX 10 mailserver.example.com."}, + }, + { + qname: "ptr.example.org.", + qtype: dns.TypePTR, + wantAnswer: []string{"ptr.example.org. 300 IN PTR www.ptr-example.com."}, + }, + { + qname: "txt.example.org.", + qtype: dns.TypeTXT, + wantAnswer: []string{"txt.example.org. 300 IN TXT \"TXT for example.org\""}, + }, + } + + for ti, tc := range tests { + req := new(dns.Msg) + req.SetQuestion(dns.Fqdn(tc.qname), tc.qtype) + + rec := dnstest.NewRecorder(&test.ResponseWriter{}) + code, err := demoAzure.ServeDNS(context.Background(), rec, req) + + if err != tc.expectedErr { + t.Fatalf("Test %d: Expected error %v, but got %v", ti, tc.expectedErr, err) + } + + if code != int(tc.wantRetCode) { + t.Fatalf("Test %d: Expected returned status code %s, but got %s", ti, dns.RcodeToString[tc.wantRetCode], dns.RcodeToString[code]) + } + + if tc.wantMsgRCode != rec.Msg.Rcode { + t.Errorf("Test %d: Unexpected msg status code. Want: %s, got: %s", ti, dns.RcodeToString[tc.wantMsgRCode], dns.RcodeToString[rec.Msg.Rcode]) + } + + if len(tc.wantAnswer) != len(rec.Msg.Answer) { + t.Errorf("Test %d: Unexpected number of Answers. Want: %d, got: %d", ti, len(tc.wantAnswer), len(rec.Msg.Answer)) + } else { + for i, gotAnswer := range rec.Msg.Answer { + if gotAnswer.String() != tc.wantAnswer[i] { + t.Errorf("Test %d: Unexpected answer.\nWant:\n\t%s\nGot:\n\t%s", ti, tc.wantAnswer[i], gotAnswer) + } + } + } + + if len(tc.wantNS) != len(rec.Msg.Ns) { + t.Errorf("Test %d: Unexpected NS number. Want: %d, got: %d", ti, len(tc.wantNS), len(rec.Msg.Ns)) + } else { + for i, ns := range rec.Msg.Ns { + got, ok := ns.(*dns.SOA) + if !ok { + t.Errorf("Test %d: Unexpected NS type. Want: SOA, got: %v", ti, reflect.TypeOf(got)) + } + if got.String() != tc.wantNS[i] { + t.Errorf("Test %d: Unexpected NS.\nWant: %v\nGot: %v", ti, tc.wantNS[i], got) + } + } + } + } +} diff --git a/plugin/azure/setup.go b/plugin/azure/setup.go new file mode 100644 index 000000000..1ac0cc723 --- /dev/null +++ b/plugin/azure/setup.go @@ -0,0 +1,123 @@ +package azure + +import ( + "context" + "strings" + + "github.com/coredns/coredns/core/dnsserver" + "github.com/coredns/coredns/plugin" + "github.com/coredns/coredns/plugin/pkg/fall" + clog "github.com/coredns/coredns/plugin/pkg/log" + "github.com/coredns/coredns/plugin/pkg/upstream" + + azuredns "github.com/Azure/azure-sdk-for-go/profiles/latest/dns/mgmt/dns" + azurerest "github.com/Azure/go-autorest/autorest/azure" + "github.com/Azure/go-autorest/autorest/azure/auth" + "github.com/caddyserver/caddy" +) + +var log = clog.NewWithPlugin("azure") + +func init() { + caddy.RegisterPlugin("azure", caddy.Plugin{ + ServerType: "dns", + Action: setup, + }) +} + +func setup(c *caddy.Controller) error { + env, keys, fall, err := parse(c) + if err != nil { + return plugin.Error("azure", err) + } + ctx := context.Background() + dnsClient := azuredns.NewRecordSetsClient(env.Values[auth.SubscriptionID]) + dnsClient.Authorizer, err = env.GetAuthorizer() + if err != nil { + return c.Errf("failed to create azure plugin: %v", err) + } + h, err := New(ctx, dnsClient, keys, upstream.New()) + if err != nil { + return c.Errf("failed to initialize azure plugin: %v", err) + } + h.Fall = fall + if err := h.Run(ctx); err != nil { + return c.Errf("failed to run azure plugin: %v", err) + } + dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler { + h.Next = next + return h + }) + return nil +} + +func parse(c *caddy.Controller) (auth.EnvironmentSettings, map[string][]string, fall.F, error) { + resourceGroupMapping := map[string][]string{} + resourceGroupSet := map[string]struct{}{} + var err error + var fall fall.F + + azureEnv := azurerest.PublicCloud + env := auth.EnvironmentSettings{Values: map[string]string{}} + + for c.Next() { + args := c.RemainingArgs() + + for i := 0; i < len(args); i++ { + parts := strings.SplitN(args[i], ":", 2) + if len(parts) != 2 { + return env, resourceGroupMapping, fall, c.Errf("invalid resource group / zone '%s'", args[i]) + } + resourceGroup, zoneName := parts[0], parts[1] + if resourceGroup == "" || zoneName == "" { + return env, resourceGroupMapping, fall, c.Errf("invalid resource group / zone '%s'", args[i]) + } + if _, ok := resourceGroupSet[args[i]]; ok { + return env, resourceGroupMapping, fall, c.Errf("conflict zone '%s'", args[i]) + } + + resourceGroupSet[args[i]] = struct{}{} + resourceGroupMapping[resourceGroup] = append(resourceGroupMapping[resourceGroup], zoneName) + } + for c.NextBlock() { + switch c.Val() { + case "subscription": + if !c.NextArg() { + return env, resourceGroupMapping, fall, c.ArgErr() + } + env.Values[auth.SubscriptionID] = c.Val() + case "tenant": + if !c.NextArg() { + return env, resourceGroupMapping, fall, c.ArgErr() + } + env.Values[auth.TenantID] = c.Val() + case "client": + if !c.NextArg() { + return env, resourceGroupMapping, fall, c.ArgErr() + } + env.Values[auth.ClientID] = c.Val() + case "secret": + if !c.NextArg() { + return env, resourceGroupMapping, fall, c.ArgErr() + } + env.Values[auth.ClientSecret] = c.Val() + case "environment": + if !c.NextArg() { + return env, resourceGroupMapping, fall, c.ArgErr() + } + env.Values[auth.ClientSecret] = c.Val() + azureEnv, err = azurerest.EnvironmentFromName(c.Val()) + if err != nil { + return env, resourceGroupMapping, fall, c.Errf("cannot set azure environment: %s", err.Error()) + } + case "fallthrough": + fall.SetZonesFromArgs(c.RemainingArgs()) + default: + return env, resourceGroupMapping, fall, c.Errf("unknown property '%s'", c.Val()) + } + } + } + env.Values[auth.Resource] = azureEnv.ResourceManagerEndpoint + env.Environment = azureEnv + return env, resourceGroupMapping, fall, nil +} diff --git a/plugin/azure/setup_test.go b/plugin/azure/setup_test.go new file mode 100644 index 000000000..c0b22d581 --- /dev/null +++ b/plugin/azure/setup_test.go @@ -0,0 +1,72 @@ +package azure + +import ( + "testing" + + "github.com/caddyserver/caddy" +) + +func TestSetup(t *testing.T) { + tests := []struct { + body string + expectedError bool + }{ + {`azure`, false}, + {`azure :`, true}, + {`azure resource_set:zone`, false}, + {`azure resource_set:zone { + tenant +}`, true}, + {`azure resource_set:zone { + tenant +}`, true}, + {`azure resource_set:zone { + client +}`, true}, + {`azure resource_set:zone { + secret +}`, true}, + {`azure resource_set:zone { + subscription +}`, true}, + {`azure resource_set:zone { + upstream 10.0.0.1 +}`, true}, + + {`azure resource_set:zone { + upstream +}`, true}, + {`azure resource_set:zone { + foobar +}`, true}, + {`azure resource_set:zone { + tenant tenant_id + client client_id + secret client_secret + subscription subscription_id +}`, false}, + + {`azure resource_set:zone { + fallthrough +}`, false}, + {`azure resource_set:zone { + environment AZUREPUBLICCLOUD + }`, false}, + {`azure resource_set:zone resource_set:zone { + fallthrough + }`, true}, + {`azure resource_set:zone,zone2 { + fallthrough + }`, false}, + {`azure resource-set { + fallthrough + }`, true}, + } + + for i, test := range tests { + c := caddy.NewTestController("dns", test.body) + if _, _, _, err := parse(c); (err == nil) == test.expectedError { + t.Fatalf("Unexpected errors: %v in test: %d\n\t%s", err, i, test.body) + } + } +} |