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

NEW PROVIDER: MSDNS (#1005)

* New provider
* Add support for SRV records
* Modify ACTIVEDIRECTORY_PS provider to warn that it is deprecated.
This commit is contained in:
Tom Limoncelli
2020-12-28 16:07:33 -05:00
committed by GitHub
parent 38e3e706cd
commit 50db086278
19 changed files with 1206 additions and 24 deletions

1
.gitattributes vendored
View File

@ -3,6 +3,7 @@
# Git itself
.gitattributes eol=lf
.gitigore eol=lf
OWNERS eol=lf
# dnscontrol files are unix line-endings:
*.md text eol=lf

1
OWNERS
View File

@ -15,6 +15,7 @@ providers/hetzner @das7pad
providers/hexonet @papakai
providers/internetbs @pragmaton
providers/inwx @svenpeter42
providers/msdns @tlimoncelli
providers/linode @koesie10
providers/namecheap @captncraig
# providers/namedotcom

View File

@ -17,7 +17,7 @@ Windows). The provider model is extensible, so more providers can be added.
Currently supported DNS providers:
- AWS Route 53
- AXFR+DDNS
- Active Directory
- Active Directory (Deprecated, see Microsoft DNS)
- Azure DNS
- BIND
- ClouDNS
@ -34,6 +34,7 @@ Currently supported DNS providers:
- INWX
- Internet.bs
- Linode
- Microsoft Windows Server DNS Server
- NS1
- Name.com
- Namecheap

View File

@ -4,6 +4,14 @@ layout: default
jsId: ACTIVEDIRECTORY_PS
title: ActiveDirectory_PS Provider
---
# WARNING:
WARNING: This provider is deprecated and will eventually be removed.
Please switch to MSDNS. It is more modern and reliable. The
`creds.json` fields changed names; otherwise it should be an
uneventful upgrade.
# ActiveDirectory_PS Provider
This provider updates an Microsoft Active Directory server DNS server. It interacts with AD via PowerShell commands that are generated and executed on the local machine. This means that DNSControl must be run on a Windows host. This driver automatically deactivates itself when run on non-Windows systems.
@ -17,7 +25,7 @@ To activate this mode, set `"fakeps":"true"` inside your credentials file for th
## Configuration
The `ActiveDirectory_PS` provider reads an `ADServer` setting from `creds.json` to know the name of the ActiveDirectory DNS Server to update.
The `ActiveDirectory_PS` provider reads an `ADServer` setting from `creds.json` to know the name of the ActiveDirectory DNS Server to update.
{% highlight javascript %}
{

96
docs/_providers/msdns.md Normal file
View File

@ -0,0 +1,96 @@
---
name: Microsoft DNS Server (Windows Server)
layout: default
jsId: MSDNS
title: Microsoft DNS Server on Microsoft Windows Server
---
# Microsoft DNS Server on Microsoft Windows Server
This provider updates a Microsoft DNS server.
It interacts with the server via PowerShell commands. As a result, DNSControl
must be run on Windows and will automatically disable itself when run on
non-Windows systems.
DNSControl will use `New-PSSession` to execute the commands remotely if
`computername` is set in `creds.json` (see below).
This provider will replace `ACTIVEDIRECTORY_PS` which is deprecated.
# Caveats
* Two systems updating a zone is never a good idea. If Windows Dynamic
DNS and DNSControl are both updating a zone, there will be
unhappiness. DNSControl will blindly remove the dynamic records
unless precautions such as `IGNORE*` and `NO_PURGE` are in use.
* This is a new provider and has not been tested extensively,
especially the `pssession` feature.
# Running on Non-Windows systems
Currently this driver disables itself when run on Non-Windows systems.
It should be possible for non-Windows hosts with PowerShell Core installed to
execute commands remotely via SSH. The module used to talk to PowerShell
supports this. It should be easy to implement. Volunteers requested.
## Configuration
The `ActiveDirectory_PS` provider reads an `computername` setting from
`creds.json` to know the name of the ActiveDirectory DNS Server to run the commands on.
Otherwise
{% highlight javascript %}
{
"msdns": {
"dnsserver": "ny-dc01",
"pssession": "mywindowshost"
}
}
{% endhighlight %}
An example DNS configuration:
{% highlight javascript %}
var REG_NONE = NewRegistrar('none', 'NONE')
var MSDNS = NewDnsProvider("msdns", "MSDNS");
D('example.tld', REG_NONE, DnsProvider(MSDNS),
A("test","1.2.3.4")
)
{% endhighlight %}
# Converting from `ACTIVEDIRECTORY_PS`
If you were using the `ACTIVEDIRECTORY_PS` provider and are switching to `MSDNS`, make the following changes:
1. In `dnsconfig.js`, change `ACTIVEDIRECTORY_PS` to `MSDNS` in any `NewDnsProvider()` calls.
2. In `creds.json`: Since unused fields are quietly ignored, it is
safe to list both the old and new options:
a. Add a field "dnsserver" with the DNS server's name. (OPTIONAL if dnscontrol is run on the DNS server.)
b. If the PowerShell commands need to be run on a different host using a `PSSession`, add `pssession: "remoteserver",` where `remoteserver` is the name of the server where the PowerShell commands should run.
c. The MSDNS provider will quietly ignore `fakeps`, `pslog` and `psout`. Feel free to leave them in `creds.json` until you are sure you aren't going back to the old provider.
During the transition your `creds.json` file might look like:
{% highlight javascript %}
{
"msdns": {
"ADServer": "ny-dc01", << Delete these after you have
"fakeps": "true", << verified that MSDNS works
"pslog": "log.txt", << properly.
"psout": "out.txt",
"dnsserver": "ny-dc01",
"pssession": "mywindowshost"
}
}
{% endhighlight %}
3. Run `dnscontrol preview` to make sure the provider works as expected.
4. If for any reason you need to revert, simply change `dnsconfig.js` to refer to `ACTIVEDIRECTORY_PS` again (or use `git` commands). If you are reverting because you found a bug, please [file an issue](https://github.com/StackExchange/dnscontrol/issues/new).
5. Once you are confident in the new provider, remove `ADServer`, `fakeps`, `pslog`, `psout` from `creds.json`.

7
go.mod
View File

@ -15,6 +15,7 @@ require (
github.com/andybalholm/cascadia v1.2.0 // indirect
github.com/aws/aws-sdk-go v1.36.0
github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6
github.com/bhendo/go-powershell v0.0.0-20190719160123-219e7fb4e41e
github.com/billputer/go-namecheap v0.0.0-20170915210158-0c7adb0710f8
github.com/cenkalti/backoff v2.2.1+incompatible // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
@ -38,14 +39,13 @@ require (
github.com/hashicorp/vault/api v1.0.4
github.com/hexonet/go-sdk v3.5.0+incompatible
github.com/jarcoal/httpmock v1.0.4 // indirect
github.com/juju/testing v0.0.0-20201216035041-2be42bba85f3 // indirect
github.com/kolo/xmlrpc v0.0.0-20201022064351-38db28db192b // indirect
github.com/kr/text v0.2.0 // indirect
github.com/miekg/dns v1.1.35
github.com/mitchellh/mapstructure v1.4.0 // indirect
github.com/mittwald/go-powerdns v0.5.2
github.com/mjibson/esc v0.2.0
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/nrdcg/goinwx v0.8.1
github.com/ovh/go-ovh v0.0.0-20181109152953-ba5adb4cf014
github.com/philhug/opensrs-go v0.0.0-20171126225031-9dfa7433020d
@ -66,7 +66,7 @@ require (
github.com/vultr/govultr v1.0.0
golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392 // indirect
golang.org/x/mod v0.4.0 // indirect
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb
golang.org/x/net v0.0.0-20201224014010-6772e930b67b
golang.org/x/oauth2 v0.0.0-20201203001011-0b49973bad19
golang.org/x/sys v0.0.0-20201202213521-69691e467435 // indirect
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e // indirect
@ -75,7 +75,6 @@ require (
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20201203001206-6486ece9c497 // indirect
google.golang.org/grpc v1.34.0 // indirect
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
gopkg.in/ini.v1 v1.62.0 // indirect
gopkg.in/ns1/ns1-go.v2 v2.0.0-20170502175150-c563826f4cbe
gopkg.in/sourcemap.v1 v1.0.5 // indirect

61
go.sum
View File

@ -85,6 +85,8 @@ github.com/aws/aws-sdk-go v1.36.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zK
github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6 h1:4NNbNM2Iq/k57qEu7WfL67UrbPq1uFWxW4qODCohi+0=
github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6/go.mod h1:J29hk+f9lJrblVIfiJOtTFk+OblBawmib4uz/VdKzlg=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bhendo/go-powershell v0.0.0-20190719160123-219e7fb4e41e h1:KCjb01YiNoRaJ5c+SbnPLWjVzU9vqRYHg3e5JcN50nM=
github.com/bhendo/go-powershell v0.0.0-20190719160123-219e7fb4e41e/go.mod h1:f7vw6ObmmNcyFQLhZX9eUGBJGpnwTJFDvVjqZxIxHWY=
github.com/billputer/go-namecheap v0.0.0-20170915210158-0c7adb0710f8 h1:sIv3xbwhhAG94a62Q/rrSBtrWcXiYgldNOeqifyKSgo=
github.com/billputer/go-namecheap v0.0.0-20170915210158-0c7adb0710f8/go.mod h1:bqqNsI2akL+lLWyApkYY0cxquWPKwEBU0Wd3chi3TEg=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
@ -280,6 +282,32 @@ github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfE
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/juju/ansiterm v0.0.0-20160907234532-b99631de12cf/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU=
github.com/juju/clock v0.0.0-20190205081909-9c5c9712527c/go.mod h1:nD0vlnrUjcjJhqN5WuCWZyzfd5AHZAC9/ajvbSx69xA=
github.com/juju/cmd v0.0.0-20171107070456-e74f39857ca0/go.mod h1:yWJQHl73rdSX4DHVKGqkAip+huBslxRwS8m9CrOLq18=
github.com/juju/collections v0.0.0-20200605021417-0d0ec82b7271/go.mod h1:5XgO71dV1JClcOJE+4dzdn4HrI5LiyKd7PlVG6eZYhY=
github.com/juju/errors v0.0.0-20150916125642-1b5e39b83d18/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
github.com/juju/errors v0.0.0-20200330140219-3fe23663418f h1:MCOvExGLpaSIzLYB4iQXEHP4jYVU6vmzLNQPdMVrxnM=
github.com/juju/errors v0.0.0-20200330140219-3fe23663418f/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
github.com/juju/httpprof v0.0.0-20141217160036-14bf14c30767/go.mod h1:+MaLYz4PumRkkyHYeXJ2G5g5cIW0sli2bOfpmbaMV/g=
github.com/juju/loggo v0.0.0-20170605014607-8232ab8918d9/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
github.com/juju/loggo v0.0.0-20200526014432-9ce3a2e09b5e h1:FdDd7bdI6cjq5vaoYlK1mfQYfF9sF2VZw8VEZMsl5t8=
github.com/juju/loggo v0.0.0-20200526014432-9ce3a2e09b5e/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
github.com/juju/mutex v0.0.0-20171110020013-1fe2a4bf0a3a/go.mod h1:Y3oOzHH8CQ0Ppt0oCKJ2JFO81/EsWenH5AEqigLH+yY=
github.com/juju/retry v0.0.0-20151029024821-62c620325291/go.mod h1:OohPQGsr4pnxwD5YljhQ+TZnuVRYpa5irjugL1Yuif4=
github.com/juju/retry v0.0.0-20180821225755-9058e192b216/go.mod h1:OohPQGsr4pnxwD5YljhQ+TZnuVRYpa5irjugL1Yuif4=
github.com/juju/testing v0.0.0-20180402130637-44801989f0f7/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA=
github.com/juju/testing v0.0.0-20190723135506-ce30eb24acd2/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA=
github.com/juju/testing v0.0.0-20201216035041-2be42bba85f3 h1:ram7cW6jDPTu6cv9xDMwa+tO7RsO4BdsubxrJ4EEw+E=
github.com/juju/testing v0.0.0-20201216035041-2be42bba85f3/go.mod h1:IbSKFoKW0bzmbDZ7rBwF/L3lO3b1bpmOIhTXQl/WJxw=
github.com/juju/utils v0.0.0-20180424094159-2000ea4ff043/go.mod h1:6/KLg8Wz/y2KVGWEpkK9vMNGkOnu4k/cqs8Z1fKjTOk=
github.com/juju/utils v0.0.0-20200116185830-d40c2fe10647/go.mod h1:6/KLg8Wz/y2KVGWEpkK9vMNGkOnu4k/cqs8Z1fKjTOk=
github.com/juju/utils/v2 v2.0.0-20200923005554-4646bfea2ef1/go.mod h1:fdlDtQlzundleLLz/ggoYinEt/LmnrpNKcNTABQATNI=
github.com/juju/version v0.0.0-20161031051906-1f41e27e54f2/go.mod h1:kE8gK5X0CImdr7qpSKl3xB2PmpySSmfj7zVbkZFs81U=
github.com/juju/version v0.0.0-20180108022336-b64dbd566305/go.mod h1:kE8gK5X0CImdr7qpSKl3xB2PmpySSmfj7zVbkZFs81U=
github.com/juju/version v0.0.0-20191219164919-81c1be00b9a6/go.mod h1:kE8gK5X0CImdr7qpSKl3xB2PmpySSmfj7zVbkZFs81U=
github.com/julienschmidt/httprouter v1.1.1-0.20151013225520-77a895ad01eb/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kolo/xmlrpc v0.0.0-20200310150728-e0350524596b h1:DzHy0GlWeF0KAglaTMY7Q+khIFoG8toHP+wLFBVBQJc=
github.com/kolo/xmlrpc v0.0.0-20200310150728-e0350524596b/go.mod h1:o03bZfuBwAXHetKXuInt4S7omeXUu62/A845kiycsSQ=
@ -294,7 +322,14 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lunixbochs/vtclean v0.0.0-20160125035106-4fbf7632a2c6/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/masterzen/azure-sdk-for-go v3.2.0-beta.0.20161014135628-ee4f0065d00c+incompatible/go.mod h1:mf8fjOu33zCqxUjuiU3I8S1lJMyEAlH+0F2+M5xl3hE=
github.com/masterzen/simplexml v0.0.0-20160608183007-4572e39b1ab9/go.mod h1:kCEbxUJlNDEBNbdQMkPSp6yaKcRXVI6f4ddk8Riv4bc=
github.com/masterzen/winrm v0.0.0-20161014151040-7a535cd943fc/go.mod h1:CfZSN7zwz5gJiFhZJz49Uzk7mEBHIceWmbFmYx7Hf7E=
github.com/masterzen/xmlpath v0.0.0-20140218185901-13f4951698ad/go.mod h1:A0zPC53iKKKcXYxr4ROjpQRQ5FgJXtelNdSmHHuq/tY=
github.com/mattn/go-colorable v0.0.6/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.0-20160806122752-66b8e73f3f5c/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/miekg/dns v1.1.35 h1:oTfOaDH+mZkdcgdIjH6yBajRGtIwcwcaR+rt23ZSrJs=
github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
@ -324,6 +359,7 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWb
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nrdcg/goinwx v0.8.1 h1:20EQ/JaGFnSKwiDH2JzjIpicffl3cPk6imJBDqVBVtU=
github.com/nrdcg/goinwx v0.8.1/go.mod h1:tILVc10gieBp/5PMvbcYeXM6pVQ+c9jxDZnpaR1UW7c=
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/ovh/go-ovh v0.0.0-20181109152953-ba5adb4cf014 h1:37VE5TYj2m/FLA9SNr4z0+A0JefvTmR60Zwf8XSEV7c=
github.com/ovh/go-ovh v0.0.0-20181109152953-ba5adb4cf014/go.mod h1:joRatxRJaZBsY3JAOEMcoOp05CnZzsx4scTxi95DHyQ=
@ -399,12 +435,14 @@ go.opencensus.io v0.22.4 h1:LYy1Hy3MJdrCdMwwzxA/dRok4ejH+RwNGbuoD9fCjto=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
golang.org/x/crypto v0.0.0-20180214000028-650f4a345ab4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 h1:hb9wdF1z5waM+dSIICn1l0DkLVDT3hqhhQsDNUmHPRE=
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392 h1:xYJJ3S178yv++9zXV/hnr29plCAGO9vAFG9dorqaFQc=
@ -447,6 +485,7 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0 h1:8pl+sMODzuvGJkmj2W4kZihvVb5mKm8pB/X44PIQHv8=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180406214816-61147c48b25b/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -479,12 +518,13 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb h1:eBmm0M9fYhWpKZLjQUUKka/LtIxf46G4fxeEz5KJr9U=
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
@ -547,9 +587,11 @@ golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201202213521-69691e467435 h1:25AvDqqB9PrNqj1FLf2/70I4W0L19qqoaFq3gjNwbKk=
golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -729,16 +771,22 @@ google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20160105164936-4f90aeace3a2/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v1 v1.0.0-20161222125816-442357a80af5/go.mod h1:u0ALmqvLRxLI95fkdCEWrE6mhWYZW1aMOJHp5YXLHTg=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/h2non/gock.v1 v1.0.14 h1:fTeu9fcUvSnLNacYvYI54h+1/XEteDyHvrVCZEEEYNM=
gopkg.in/h2non/gock.v1 v1.0.14/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE=
gopkg.in/httprequest.v1 v1.1.1/go.mod h1:/CkavNL+g3qLOrpFHVrEx4NKepeqR4XTZWNj4sGGjz0=
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/mgo.v2 v2.0.0-20160818015218-f2b6f6c918c4/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 h1:VpOs+IwYnYBaFnrNAeB8UUWtL3vEUnzSCL1nVjPhqrw=
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
gopkg.in/ns1/ns1-go.v2 v2.0.0-20170502175150-c563826f4cbe h1:fuu3vZ8C6O8mk8Ich8YfkDv/Zpnx1HUotQk8JocBcSw=
gopkg.in/ns1/ns1-go.v2 v2.0.0-20170502175150-c563826f4cbe/go.mod h1:VV+3haRsgDiVLxyifmMBrBIuCWFBPYKbRssXB9z67Hw=
gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI=
@ -747,11 +795,14 @@ gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w=
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.0.0-20170712054546-1be3d31502d6/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
@ -767,6 +818,8 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt
honnef.co/go/tools v0.0.1-2020.1.3 h1:sXmLre5bzIR6ypkjXCDI3jHPssRhc8KD/Ome589sc3U=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
launchpad.net/gocheck v0.0.0-20140225173054-000000000087/go.mod h1:hj7XX3B/0A+80Vse0e+BUHsHMTEhd0O4cpUHr/e/BUM=
launchpad.net/xmlpath v0.0.0-20130614043138-000000000004/go.mod h1:vqyExLOM3qBx7mvYRkoxjSCF945s0mbe7YynlKYXtsA=
rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=

View File

@ -652,7 +652,20 @@ func makeTests(t *testing.T) []*TestGroup {
),
testgroup("Null MX",
not("AZURE_DNS", "GANDI_V5", "INWX", "NAMEDOTCOM", "DIGITALOCEAN", "NETCUP", "DNSIMPLE", "HEDNS", "VULTR", "OVH"), // These providers don't support RFC 7505
// These providers don't support RFC 7505
not(
"AZURE_DNS",
"DIGITALOCEAN",
"DNSIMPLE",
"GANDI_V5",
"HEDNS",
"INWX",
"MSDNS",
"NAMEDOTCOM",
"NETCUP",
"OVH",
"VULTR",
),
tc("Null MX", mx("@", 0, ".")),
),
@ -689,11 +702,22 @@ func makeTests(t *testing.T) []*TestGroup {
tc("Create a TXT with spaces", txt("foo", "with spaces")),
tc("Create 1 TXT as array", txtmulti("foo", []string{"simple"})), // Same as non-TXTMulti
clear(),
tc("Create a 254-byte TXT", txt("foo", strings.Repeat("A", 254))),
),
testgroup("max-sized TXT",
not(
"INWX", // INWX does not support
"MSDNS", // Supports 254-byte strings, not 255. Seems like a bug.
),
tc("Create a 255-byte TXT", txt("foo", strings.Repeat("A", 255))),
),
testgroup("single TXT with single-quote",
not("INWX"),
not(
"INWX", // Bug in the API prevents this.
"MSDNS", // TODO(tlim): Should be easy to implement.
),
tc("Create TXT with single-quote", txt("foo", "blah`blah")),
),
@ -705,7 +729,13 @@ func makeTests(t *testing.T) []*TestGroup {
),
testgroup("empty TXT",
not("HETZNER", "HEXONET", "INWX", "NETCUP"),
not(
"HETZNER", // Not supported.
"HEXONET", // Not supported.
"INWX", // Not supported.
"MSDNS", // Not supported.
"NETCUP", // Not supported.
),
tc("TXT with empty str", txt("foo1", "")),
// https://github.com/StackExchange/dnscontrol/issues/598
// We decided that permitting the TXT target to be an empty
@ -754,28 +784,36 @@ func makeTests(t *testing.T) []*TestGroup {
// Tests the paging code of providers. Many providers page at 100.
// Notes:
// - Gandi: page size is 100, therefore we test with 99, 100, and 101
// - NS1: free acct only allows 50 records, therefore we skip
// - DIGITALOCEAN: page size is 100 (default: 20)
// - CLOUDFLAREAPI: Infinite pagesize but due to slow speed, skipping.
not("NS1", "CLOUDFLAREAPI"),
not(
"NS1", // Free acct only allows 50 records, therefore we skip
"CLOUDFLAREAPI", // Infinite pagesize but due to slow speed, skipping.
"MSDNS", // No paging done. No need to test.
),
tc("99 records", manyA("rec%04d", "1.2.3.4", 99)...),
tc("100 records", manyA("rec%04d", "1.2.3.4", 100)...),
tc("101 records", manyA("rec%04d", "1.2.3.4", 101)...),
),
testgroup("pager601",
// AWS: https://github.com/StackExchange/dnscontrol/issues/493
//only("AZURE_DNS", "HEXONET", "ROUTE53"),
only("HEXONET", "ROUTE53", "GCLOUD"), // AZURE_DNS is failing.
only(
//"MSDNS", // No paging done. No need to test.
//"AZURE_DNS", // Currently failing.
"HEXONET",
"GCLOUD",
"ROUTE53", // See https://github.com/StackExchange/dnscontrol/issues/493
),
tc("601 records", manyA("rec%04d", "1.2.3.4", 600)...),
tc("Update 601 records", manyA("rec%04d", "1.2.3.5", 600)...),
),
testgroup("pager1201",
// AWS: https://github.com/StackExchange/dnscontrol/issues/493
// Azure: https://github.com/StackExchange/dnscontrol/issues/770
//only("AZURE_DNS", "HEXONET", "ROUTE53"),
only("HEXONET", "ROUTE53"), // AZURE_DNS is failing.
only(
//"MSDNS", // No paging done. No need to test.
//"AZURE_DNS", // Currently failing. See https://github.com/StackExchange/dnscontrol/issues/770
"HEXONET",
"ROUTE53", // https://github.com/StackExchange/dnscontrol/issues/493
),
tc("1200 records", manyA("rec%04d", "1.2.3.4", 1200)...),
tc("Update 1200 records", manyA("rec%04d", "1.2.3.5", 1200)...),
),
@ -830,7 +868,14 @@ func makeTests(t *testing.T) []*TestGroup {
tc("Change Weight", srv("_sip._tcp", 52, 62, 7, "foo.com."), srv("_sip._tcp", 15, 65, 75, "foo4.com.")),
tc("Change Port", srv("_sip._tcp", 52, 62, 72, "foo.com."), srv("_sip._tcp", 15, 65, 75, "foo4.com.")),
),
testgroup("SRV w/ null target", requires(providers.CanUseSRV), not("EXOSCALE", "HEXONET", "INWX", "NAMEDOTCOM"),
testgroup("SRV w/ null target", requires(providers.CanUseSRV),
not(
"EXOSCALE", // Not supported.
"HEXONET", // Not supported.
"INWX", // Not supported.
"MSDNS", // Not supported.
"NAMEDOTCOM", // Not supported.
),
tc("Null Target", srv("_sip._tcp", 52, 62, 72, "foo.com."), srv("_sip._tcp", 15, 65, 75, ".")),
),

View File

@ -96,6 +96,11 @@
"domain": "$LINODE_DOMAIN",
"token": "$LINODE_TOKEN"
},
"MSDNS": {
"domain": "$MSDNS_DOMAIN",
"dnsserver": "$MSDNS_DNSSERVER",
"pssession": "$MSDNS_PSSESSION"
},
"NAMECHEAP": {
"apikey": "$NAMECHEAP_KEY",
"apiuser": "$NAMECHEAP_USER",

View File

@ -23,6 +23,7 @@ import (
_ "github.com/StackExchange/dnscontrol/v3/providers/internetbs"
_ "github.com/StackExchange/dnscontrol/v3/providers/inwx"
_ "github.com/StackExchange/dnscontrol/v3/providers/linode"
_ "github.com/StackExchange/dnscontrol/v3/providers/msdns"
_ "github.com/StackExchange/dnscontrol/v3/providers/namecheap"
_ "github.com/StackExchange/dnscontrol/v3/providers/namedotcom"
_ "github.com/StackExchange/dnscontrol/v3/providers/netcup"

View File

@ -34,6 +34,7 @@ func init() {
}
func newDNS(config map[string]string, metadata json.RawMessage) (providers.DNSServiceProvider, error) {
fmt.Printf("WARNING: ACTIVEDIRECTORY_PS provider is being replaced by MSDNS. Please convert. Details in https://stackexchange.github.io/dnscontrol/providers/msdns\n")
fake := false
if fVal := config["fakeps"]; fVal == "true" {

122
providers/msdns/convert.go Normal file
View File

@ -0,0 +1,122 @@
package msdns
// Convert the provider's native record description to models.RecordConfig.
import (
"encoding/json"
"fmt"
"net"
"github.com/StackExchange/dnscontrol/v3/models"
)
// extractProps and collects Name/Value pairs into maps for easier access.
func extractProps(cip []ciProperty) (map[string]string, map[string]uint32, error) {
// Sadly this structure is dynamic JSON i.e. .Value could be an int, string,
// or a map. We peek at the first byte to guess at the contents.
// We store strings in sprops, numbers in uprops. Maps are special: Currently
// the only map we decode is a map with the same duration in many units. We
// simply pick the Seconds unit and store it as a number.
sprops := map[string]string{}
uprops := map[string]uint32{}
for _, p := range cip {
name := p.Name
if len(p.Value) == 0 {
// Empty string? Skip it.
} else if p.Value[0] == '"' {
// First byte is a quote. Must be a string.
var svalue string
err := json.Unmarshal(p.Value, &svalue)
if err != nil {
return nil, nil, fmt.Errorf("could not unmarshal string value=%q: %w", p.Value, err)
}
sprops[name] = svalue
} else if p.Value[0] == '{' {
// First byte is {. Must be a map.
var dvalue ciValueDuration
err := json.Unmarshal(p.Value, &dvalue)
if err != nil {
return nil, nil, fmt.Errorf("could not unmarshal duration value=%q: %w", p.Value, err)
}
uprops[name] = uint32(dvalue.TotalSeconds)
} else {
// Assume it is a number.
var uvalue uint32
err := json.Unmarshal(p.Value, &uvalue)
if err != nil {
return nil, nil, fmt.Errorf("could not unmarshal uint value=%q: %w", p.Value, err)
}
uprops[name] = uvalue
}
}
return sprops, uprops, nil
}
// nativeToRecord takes a DNS record from DNS and returns a native RecordConfig struct.
func nativeToRecords(nr nativeRecord, origin string) (*models.RecordConfig, error) {
rc := &models.RecordConfig{
Type: nr.RecordType,
Original: nr,
}
rc.SetLabel(nr.HostName, origin)
rc.TTL = uint32(nr.TimeToLive.TotalSeconds)
sprops, uprops, err := extractProps(nr.RecordData.CimInstanceProperties)
if err != nil {
return nil, err
}
switch rtype := nr.RecordType; rtype {
case "A":
contents := sprops["IPv4Address"]
ip := net.ParseIP(contents)
if ip == nil || ip.To4() == nil {
return nil, fmt.Errorf("invalid IP in A record: %q", contents)
}
rc.SetTargetIP(ip)
case "AAAA":
contents := sprops["IPv6Address"]
ip := net.ParseIP(contents)
if ip == nil || ip.To16() == nil {
return nil, fmt.Errorf("invalid IPv6 in AAAA record: %q", contents)
}
rc.SetTargetIP(ip)
case "CNAME":
rc.SetTarget(sprops["HostNameAlias"])
case "MX":
rc.SetTargetMX(uint16(uprops["Preference"]), sprops["MailExchange"])
case "NS":
rc.SetTarget(sprops["NameServer"])
case "PTR":
rc.SetTarget(sprops["PtrDomainName"])
case "SRV":
rc.SetTargetSRV(
uint16(uprops["Priority"]),
uint16(uprops["Weight"]),
uint16(uprops["Port"]),
sprops["DomainName"],
)
case "SOA":
// We discard SOA records for now. Windows DNS doesn't let us delete
// them and they get in the way of integration tests. In the future,
// we should support SOA records by (1) ignoring them in the
// integration tests. (2) generatePSModify will have to special-case
// updates.
return nil, nil
// If we weren't ignoring them, the code would look like this:
//rc.SetTargetSOA(sprops["PrimaryServer"], sprops["ResponsiblePerson"],
// uprops["SerialNumber"], uprops["RefreshInterval"], uprops["RetryDelay"],
// uprops["ExpireLimit"], uprops["MinimumTimeToLive"])
case "TXT":
rc.SetTargetTXTString(sprops["DescriptiveText"])
default:
return nil, fmt.Errorf(
"msdns/convert.go:nativeToRecord rtype=%q unknown: props=%+v and %+v",
rtype, sprops, uprops)
}
return rc, nil
}

View File

@ -0,0 +1,72 @@
package msdns
import (
"fmt"
"github.com/StackExchange/dnscontrol/v3/models"
"github.com/StackExchange/dnscontrol/v3/pkg/diff"
)
// GetDomainCorrections gets existing records, diffs them against existing, and returns corrections.
func (c *msdnsProvider) GenerateDomainCorrections(dc *models.DomainConfig, existing models.Records) ([]*models.Correction, error) {
// Read foundRecords:
foundRecords, err := c.GetZoneRecords(dc.Name)
if err != nil {
return nil, fmt.Errorf("c.GetDNSZoneRecords(%v) failed: %v", dc.Name, err)
}
// Normalize
models.PostProcessRecords(foundRecords)
differ := diff.New(dc)
_, creates, dels, modifications, err := differ.IncrementalDiff(foundRecords)
if err != nil {
return nil, err
}
// Generate changes.
corrections := []*models.Correction{}
for _, del := range dels {
corrections = append(corrections, c.deleteRec(c.dnsserver, dc.Name, del))
}
for _, cre := range creates {
corrections = append(corrections, c.createRec(c.dnsserver, dc.Name, cre)...)
}
for _, m := range modifications {
corrections = append(corrections, c.modifyRec(c.dnsserver, dc.Name, m))
}
return corrections, nil
}
func (c *msdnsProvider) deleteRec(dnsserver, domainname string, cor diff.Correlation) *models.Correction {
rec := cor.Existing
return &models.Correction{
Msg: cor.String(),
F: func() error {
return c.shell.RecordDelete(dnsserver, domainname, rec)
},
}
}
func (c *msdnsProvider) createRec(dnsserver, domainname string, cre diff.Correlation) []*models.Correction {
rec := cre.Desired
arr := []*models.Correction{{
Msg: cre.String(),
F: func() error {
return c.shell.RecordCreate(dnsserver, domainname, rec)
},
}}
return arr
}
func (c *msdnsProvider) modifyRec(dnsserver, domainname string, m diff.Correlation) *models.Correction {
old, rec := m.Existing, m.Desired
return &models.Correction{
Msg: m.String(),
F: func() error {
return c.shell.RecordModify(dnsserver, domainname, old, rec)
},
}
}

View File

@ -0,0 +1,8 @@
package msdns
import "github.com/StackExchange/dnscontrol/v3/models"
func (c *msdnsProvider) GetNameservers(string) ([]*models.Nameserver, error) {
// TODO: If using AD for publicly hosted zones, probably pull these from config.
return nil, nil
}

View File

@ -0,0 +1,9 @@
package msdns
func (c *msdnsProvider) ListZones() ([]string, error) {
zones, err := c.shell.GetDNSServerZoneAll(c.dnsserver)
if err != nil {
return nil, err
}
return zones, err
}

View File

@ -0,0 +1,129 @@
package msdns
import (
"encoding/json"
"fmt"
"runtime"
"github.com/StackExchange/dnscontrol/v3/models"
"github.com/StackExchange/dnscontrol/v3/providers"
)
// This is the struct that matches either (or both) of the Registrar and/or DNSProvider interfaces:
type msdnsProvider struct {
dnsserver string // Which DNS Server to update
pssession string // Remote machine to PSSession to
shell DNSAccessor // Handle for
}
var features = providers.DocumentationNotes{
providers.CanGetZones: providers.Can(),
providers.CanUseAlias: providers.Cannot(),
providers.CanUseCAA: providers.Cannot(),
providers.CanUseDS: providers.Unimplemented(),
providers.CanUsePTR: providers.Can(),
providers.CanUseSRV: providers.Can(),
providers.CanUseTLSA: providers.Unimplemented(),
providers.CanUseTXTMulti: providers.Unimplemented(),
providers.DocCreateDomains: providers.Cannot("This provider assumes the zone already existing on the dns server"),
providers.DocDualHost: providers.Cannot("This driver does not manage NS records, so should not be used for dual-host scenarios"),
providers.DocOfficiallySupported: providers.Can(),
}
// Register with the dnscontrol system.
// This establishes the name (all caps), and the function to call to initialize it.
func init() {
providers.RegisterDomainServiceProviderType("MSDNS", newDNS, features)
}
func newDNS(config map[string]string, metadata json.RawMessage) (providers.DNSServiceProvider, error) {
if runtime.GOOS != "windows" {
fmt.Println("INFO: PowerShell not available. Disabling Active Directory provider.")
return providers.None{}, nil
}
var err error
p := &msdnsProvider{
dnsserver: config["dnsserver"],
}
p.shell, err = newPowerShell(config)
if err != nil {
return nil, err
}
return p, nil
}
// Section 3: Domain Service Provider (DSP) related functions
// NB(tal): To future-proof your code, all new providers should
// implement GetDomainCorrections exactly as you see here
// (byte-for-byte the same). In 3.0
// we plan on using just the individual calls to GetZoneRecords,
// PostProcessRecords, and so on.
//
// Currently every provider does things differently, which prevents
// us from doing things like using GetZoneRecords() of a provider
// to make convertzone work with all providers.
// GetDomainCorrections get the current and existing records,
// post-process them, and generate corrections.
func (client *msdnsProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
existing, err := client.GetZoneRecords(dc.Name)
if err != nil {
return nil, err
}
models.PostProcessRecords(existing)
clean := PrepFoundRecords(existing)
PrepDesiredRecords(dc)
return client.GenerateDomainCorrections(dc, clean)
}
// GetZoneRecords gathers the DNS records and converts them to
// dnscontrol's format.
func (client *msdnsProvider) GetZoneRecords(domain string) (models.Records, error) {
// Get the existing DNS records in native format.
nativeExistingRecords, err := client.shell.GetDNSZoneRecords(client.dnsserver, domain)
if err != nil {
return nil, err
}
// Convert them to DNScontrol's native format:
existingRecords := make([]*models.RecordConfig, 0, len(nativeExistingRecords))
for _, rr := range nativeExistingRecords {
rc, err := nativeToRecords(rr, domain)
if err != nil {
return nil, err
}
if rc != nil {
existingRecords = append(existingRecords, rc)
}
}
return existingRecords, nil
}
// PrepFoundRecords munges any records to make them compatible with
// this provider. Usually this is a no-op.
func PrepFoundRecords(recs models.Records) models.Records {
// If there are records that need to be modified, removed, etc. we
// do it here. Usually this is a no-op.
return recs
}
// PrepDesiredRecords munges any records to best suit this provider.
func PrepDesiredRecords(dc *models.DomainConfig) {
// Sort through the dc.Records, eliminate any that can't be
// supported; modify any that need adjustments to work with the
// provider. We try to do minimal changes otherwise it gets
// confusing.
dc.Punycode()
}
// NB(tlim): If we want to implement a registrar, refer to
// http://go.microsoft.com/fwlink/?LinkId=288158
// (Get-DnsServerZoneDelegation) for hints about which PowerShell
// commands to use.

View File

@ -0,0 +1,414 @@
package msdns
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"os"
"github.com/StackExchange/dnscontrol/v3/models"
"github.com/TomOnTime/utfutil"
ps "github.com/bhendo/go-powershell"
"github.com/bhendo/go-powershell/backend"
"github.com/bhendo/go-powershell/middleware"
)
type psHandle struct {
shell ps.Shell
}
func newPowerShell(config map[string]string) (*psHandle, error) {
back := &backend.Local{}
sh, err := ps.New(back)
if err != nil {
return nil, err
}
shell := sh
pssession := config["pssession"]
if pssession != "" {
fmt.Printf("INFO: PowerShell commands will run on %q\n", pssession)
// create a remote shell by wrapping the existing one in the session middleware
mconfig := middleware.NewSessionConfig()
mconfig.ComputerName = pssession
session, err := middleware.NewSession(sh, mconfig)
if err != nil {
panic(err)
}
shell = session
}
psh := &psHandle{
shell: shell,
}
return psh, nil
}
func (psh *psHandle) Exit() {
psh.shell.Exit()
}
type dnsZone map[string]interface{}
func (psh *psHandle) GetDNSServerZoneAll(dnsserver string) ([]string, error) {
stdout, stderr, err := psh.shell.Execute(generatePSZoneAll(dnsserver))
if err != nil {
return nil, err
}
if stderr != "" {
fmt.Printf("STDERROR = %q\n", stderr)
return nil, fmt.Errorf("unexpected stderr from Get-DnsServerZones: %q", stderr)
}
var zones []dnsZone
json.Unmarshal([]byte(stdout), &zones)
var result []string
for _, z := range zones {
zonename := z["ZoneName"].(string)
result = append(result, zonename)
}
return result, nil
}
// powerShellDump runs a PowerShell command to get a dump of all records in a DNS zone.
func generatePSZoneAll(dnsserver string) string {
var b bytes.Buffer
fmt.Fprintf(&b, `Get-DnsServerZone`)
if dnsserver != "" {
fmt.Fprintf(&b, ` -ComputerName "%v"`, dnsserver)
}
fmt.Fprintf(&b, ` | `)
fmt.Fprintf(&b, `ConvertTo-Json`)
return b.String()
}
func (psh *psHandle) GetDNSZoneRecords(dnsserver, domain string) ([]nativeRecord, error) {
tmpfile, err := ioutil.TempFile("", "zonerecords.*.json")
if err != nil {
log.Fatal(err)
}
tmpfile.Close()
stdout, stderr, err := psh.shell.Execute(generatePSZoneDump(dnsserver, domain, tmpfile.Name()))
if err != nil {
return nil, err
}
if stdout != "" {
fmt.Printf("STDOUT = %q\n", stderr)
}
if stderr != "" {
fmt.Printf("STDERROR = %q\n", stderr)
return nil, fmt.Errorf("unexpected stderr from PSZoneDump: %q", stderr)
}
contents, err := utfutil.ReadFile(tmpfile.Name(), utfutil.WINDOWS)
if err != nil {
return nil, err
}
os.Remove(tmpfile.Name())
var records []nativeRecord
json.Unmarshal([]byte(contents), &records)
return records, nil
}
// powerShellDump runs a PowerShell command to get a dump of all records in a DNS zone.
func generatePSZoneDump(dnsserver, domainname string, filename string) string {
var b bytes.Buffer
fmt.Fprintf(&b, `Get-DnsServerResourceRecord`)
if dnsserver != "" {
fmt.Fprintf(&b, ` -ComputerName "%v"`, dnsserver)
}
fmt.Fprintf(&b, ` -ZoneName "%v"`, domainname)
fmt.Fprintf(&b, ` | `)
fmt.Fprintf(&b, `ConvertTo-Json -depth 4`) // Tested with 3 (causes errors). 4 and larger work.
fmt.Fprintf(&b, ` > %s`, filename)
//fmt.Printf("DEBUG PSZoneDump CMD = (\n%s\n)\n", b.String())
return b.String()
}
// Functions for record manipulation
func (psh *psHandle) RecordDelete(dnsserver, domain string, rec *models.RecordConfig) error {
_, stderr, err := psh.shell.Execute(generatePSDelete(dnsserver, domain, rec))
if err != nil {
return err
}
if stderr != "" {
fmt.Printf("STDERROR = %q\n", stderr)
return fmt.Errorf("unexpected stderr from PSDelete: %q", stderr)
}
return nil
}
func generatePSDelete(dnsserver, domain string, rec *models.RecordConfig) string {
var b bytes.Buffer
fmt.Fprintf(&b, `echo DELETE "%s" "%s" "%s"`, rec.Type, rec.Name, rec.GetTargetCombined())
fmt.Fprintf(&b, " ; ")
fmt.Fprintf(&b, `Remove-DnsServerResourceRecord`)
if dnsserver != "" {
fmt.Fprintf(&b, ` -ComputerName "%s"`, dnsserver)
}
fmt.Fprintf(&b, ` -Force`)
fmt.Fprintf(&b, ` -ZoneName "%s"`, domain)
fmt.Fprintf(&b, ` -Name "%s"`, rec.Name)
fmt.Fprintf(&b, ` -RRType "%s"`, rec.Type)
if rec.Type == "MX" {
fmt.Fprintf(&b, ` -RecordData %d,"%s"`, rec.MxPreference, rec.GetTargetField())
} else if rec.Type == "SRV" {
// https://www.gitmemory.com/issue/MicrosoftDocs/windows-powershell-docs/1149/511916884
fmt.Fprintf(&b, ` -RecordData %d,%d,%d,"%s"`, rec.SrvPriority, rec.SrvWeight, rec.SrvPort, rec.GetTargetField())
} else {
fmt.Fprintf(&b, ` -RecordData "%s"`, rec.GetTargetField())
}
//fmt.Printf("DEBUG PSDelete CMD = (\n%s\n)\n", b.String())
return b.String()
}
func (psh *psHandle) RecordCreate(dnsserver, domain string, rec *models.RecordConfig) error {
_, stderr, err := psh.shell.Execute(generatePSCreate(dnsserver, domain, rec))
if err != nil {
return err
}
if stderr != "" {
fmt.Printf("STDERROR = %q\n", stderr)
return fmt.Errorf("unexpected stderr from PSCreate: %q", stderr)
}
return nil
}
func generatePSCreate(dnsserver, domain string, rec *models.RecordConfig) string {
var b bytes.Buffer
fmt.Fprintf(&b, `echo CREATE "%s" "%s" "%s"`, rec.Type, rec.Name, rec.GetTargetCombined())
fmt.Fprintf(&b, " ; ")
fmt.Fprint(&b, `Add-DnsServerResourceRecord`)
if dnsserver != "" {
fmt.Fprintf(&b, ` -ComputerName "%s"`, dnsserver)
}
fmt.Fprintf(&b, ` -ZoneName "%s"`, domain)
fmt.Fprintf(&b, ` -Name "%s"`, rec.GetLabel())
fmt.Fprintf(&b, ` -TimeToLive $(New-TimeSpan -Seconds %d)`, rec.TTL)
switch rec.Type {
case "A":
fmt.Fprintf(&b, ` -A -IPv4Address "%s"`, rec.GetTargetIP())
case "AAAA":
fmt.Fprintf(&b, ` -AAAA -IPv6Address "%s"`, rec.GetTargetIP())
//case "ATMA":
// fmt.Fprintf(&b, ` -Atma -Address <String> -AddressType {E164 | AESA}`, rec.GetTargetField())
//case "AFSDB":
// fmt.Fprintf(&b, ` -Afsdb -ServerName <String> -SubType <UInt16>`, rec.GetTargetField())
case "SRV":
fmt.Fprintf(&b, ` -Srv -DomainName "%s" -Port %d -Priority %d -Weight %d`, rec.GetTargetField(), rec.SrvPort, rec.SrvPriority, rec.SrvWeight)
case "CNAME":
fmt.Fprintf(&b, ` -CName -HostNameAlias "%s"`, rec.GetTargetField())
//case "X25":
// fmt.Fprintf(&b, ` -X25 -PsdnAddress <String>`, rec.GetTargetField())
//case "WKS":
// fmt.Fprintf(&b, ` -Wks -InternetAddress <IPAddress> -InternetProtocol {UDP | TCP} -Service <String[]>`, rec.GetTargetField())
case "TXT":
fmt.Fprintf(&b, ` -Txt -DescriptiveText "%s"`, rec.GetTargetField())
//case "RT":
// fmt.Fprintf(&b, ` -RT -IntermediateHost <String> -Preference <UInt16>`, rec.GetTargetField())
//case "RP":
// fmt.Fprintf(&b, ` -RP -Description <String> -ResponsiblePerson <String>`, rec.GetTargetField())
case "PTR":
fmt.Fprintf(&b, ` -Ptr -PtrDomainName "%s"`, rec.GetTargetField())
case "NS":
fmt.Fprintf(&b, ` -NS -NameServer "%s"`, rec.GetTargetField())
case "MX":
fmt.Fprintf(&b, ` -MX -MailExchange "%s" -Preference %d`, rec.GetTargetField(), rec.MxPreference)
//case "ISDN":
// fmt.Fprintf(&b, ` -Isdn -IsdnNumber <String> -IsdnSubAddress <String>`, rec.GetTargetField())
//case "HINFO":
// fmt.Fprintf(&b, ` -HInfo -Cpu <String> -OperatingSystem <String>`, rec.GetTargetField())
//case "DNAME":
// fmt.Fprintf(&b, ` -DName -DomainNameAlias <String>`, rec.GetTargetField())
//case "DHCID":
// fmt.Fprintf(&b, ` -DhcId -DhcpIdentifier <String>`, rec.GetTargetField())
//case "TLSA":
// fmt.Fprintf(&b, ` -TLSA -CertificateAssociationData <System.String> -CertificateUsage {CAConstraint | ServiceCertificateConstraint | TrustAnchorAssertion | DomainIssuedCertificate} -MatchingType {ExactMatch | Sha256Hash | Sha512Hash} -Selector {FullCertificate | SubjectPublicKeyInfo}`, rec.GetTargetField())
default:
panic(fmt.Errorf("generatePSCreate() has not implemented recType=%s recName=%#v content=%#v)",
rec.Type, rec.GetLabel(), rec.GetTargetField()))
// We panic so that we quickly find any switch statements
// that have not been updated for a new RR type.
}
//fmt.Printf("DEBUG PSCreate CMD = (\n%s\n)\n", b.String())
return b.String()
}
func (psh *psHandle) RecordModify(dnsserver, domain string, old, rec *models.RecordConfig) error {
_, stderr, err := psh.shell.Execute(generatePSModify(dnsserver, domain, old, rec))
if err != nil {
return err
}
if stderr != "" {
fmt.Printf("STDERROR = %q\n", stderr)
return fmt.Errorf("unexpected stderr from PSModify: %q", stderr)
}
return nil
}
func generatePSModify(dnsserver, domain string, old, rec *models.RecordConfig) string {
// The simple way is to just remove the old record and insert the new record.
return generatePSDelete(dnsserver, domain, old) + ` ; ` + generatePSCreate(dnsserver, domain, rec)
// NB: SOA records can't be deleted. When we implement them, we'll
// need to special case them and generate an in-place modification
// command.
}
// Note about the old generatePSModify:
//
// The old method is to generate PowerShell code that extracts the resource
// record, clones it, makes modifications to the clone, and replaces the old
// object with the modified clone. In theory this is cleaner.
//
// Sadly that technique is considerably slower (PowerShell seems to take a
// long time doing it) and it is more brittle (each new rType seems to be a
// new adventure).
//
// The other benefit of the Delete/Create method is that it more heavily
// exercises existing code that is known to work.
//
// Sadly I can't bring myself to erase the code yet. I still hope this can
// be fixed. Deep down I know we should just accept that Del/Create is better.
// if old.GetLabel() != rec.GetLabel() {
// panic(fmt.Sprintf("generatePSModify assertion failed: %q != %q", old.GetLabel(), rec.GetLabel()))
// }
//
// var b bytes.Buffer
// fmt.Fprintf(&b, `echo "MODIFY %s %s %s old=(%s) new=(%s):"`, rec.GetLabel(), domain, rec.Type, old.GetTargetCombined(), rec.GetTargetCombined())
// fmt.Fprintf(&b, " ; ")
// fmt.Fprintf(&b, "$OldObj = Get-DnsServerResourceRecord")
// fmt.Fprintf(&b, ` -ZoneName "%s"`, domain)
// fmt.Fprintf(&b, ` -Name "%s"`, old.GetLabel())
// fmt.Fprintf(&b, ` -RRType "%s"`, old.Type)
// fmt.Fprintf(&b, ` | Where-Object {$_.HostName eq "%s" -and -RRType -eq "%s" -and `, old.GetLabel(), rec.Type)
// switch old.Type {
// case "A":
// fmt.Fprintf(&b, `$_.RecordData.IPv4Address -eq "%s"`, old.GetTargetIP())
// case "AAAA":
// fmt.Fprintf(&b, `$_.RecordData.IPv6Address -eq "%s"`, old.GetTargetIP())
// //case "ATMA":
// // fmt.Fprintf(&b, ` -Atma -Address <String> -AddressType {E164 | AESA}`, old.GetTargetField())
// //case "AFSDB":
// // fmt.Fprintf(&b, ` -Afsdb -ServerName <String> -SubType <UInt16>`, old.GetTargetField())
// case "SRV":
// fmt.Fprintf(&b, `$_.RecordData.DomainName -eq "%s" -and $_.RecordData.Port -eq %d -and $_.RecordData.Priority -eq %d -and $_.RecordData.Weight -eq %d`, old.GetTargetField(), old.SrvPort, old.SrvPriority, old.SrvWeight)
// case "CNAME":
// fmt.Fprintf(&b, `$_.RecordData.HostNameAlias -eq "%s"`, old.GetTargetField())
// //case "X25":
// // fmt.Fprintf(&b, ` -X25 -PsdnAddress <String>`, old.GetTargetField())
// //case "WKS":
// // fmt.Fprintf(&b, ` -Wks -InternetAddress <IPAddress> -InternetProtocol {UDP | TCP} -Service <String[]>`, old.GetTargetField())
// case "TXT":
// fmt.Fprintf(&b, `$_.RecordData.DescriptiveText -eq "%s"`, old.GetTargetField())
// //case "RT":
// // fmt.Fprintf(&b, ` -RT -IntermediateHost <String> -Preference <UInt16>`, old.GetTargetField())
// //case "RP":
// // fmt.Fprintf(&b, ` -RP -Description <String> -ResponsiblePerson <String>`, old.GetTargetField())
// case "PTR":
// fmt.Fprintf(&b, `$_.RecordData.PtrDomainName -eq "%s"`, old.GetTargetField())
// case "NS":
// fmt.Fprintf(&b, `$_.RecordData.NameServer -eq "%s"`, old.GetTargetField())
// case "MX":
// fmt.Fprintf(&b, `$_.RecordData.MailExchange -eq "%s" -and $_.RecordData.Preference -eq %d`, old.GetTargetField(), old.MxPreference)
// //case "ISDN":
// // fmt.Fprintf(&b, ` -Isdn -IsdnNumber <String> -IsdnSubAddress <String>`, old.GetTargetField())
// //case "HINFO":
// // fmt.Fprintf(&b, ` -HInfo -Cpu <String> -OperatingSystem <String>`, old.GetTargetField())
// //case "DNAME":
// // fmt.Fprintf(&b, ` -DName -DomainNameAlias <String>`, old.GetTargetField())
// //case "DHCID":
// // fmt.Fprintf(&b, ` -DhcId -DhcpIdentifier <String>`, old.GetTargetField())
// //case "TLSA":
// // fmt.Fprintf(&b, ` -TLSA -CertificateAssociationData <System.String> -CertificateUsage {CAConstraint | ServiceCertificateConstraint | TrustAnchorAssertion | DomainIssuedCertificate} -MatchingType {ExactMatch | Sha256Hash | Sha512Hash} -Selector {FullCertificate | SubjectPublicKeyInfo}`, rec.GetTargetField())
// default:
// panic(fmt.Errorf("generatePSModify() has not implemented recType=%q recName=%q content=(%s))",
// rec.Type, rec.GetLabel(), rec.GetTargetCombined()))
// // We panic so that we quickly find any switch statements
// // that have not been updated for a new RR type.
// }
// fmt.Fprintf(&b, "}")
// fmt.Fprintf(&b, " ; ")
// fmt.Fprintf(&b, `if($OldObj.Length -ne 1){ throw "Error, multiple results for Get-DnsServerResourceRecord" }`)
// fmt.Fprintf(&b, " ; ")
// fmt.Fprintf(&b, "$NewObj = $OldObj.Clone()")
// fmt.Fprintf(&b, " ; ")
//
// if old.TTL != rec.TTL {
// fmt.Fprintf(&b, `$NewObj.TimeToLive = New-TimeSpan -Seconds %d`, rec.TTL)
// fmt.Fprintf(&b, " ; ")
// }
// switch rec.Type {
// case "A":
// fmt.Fprintf(&b, `$NewObj.RecordData.IPv4Address = "%s"`, rec.GetTargetIP())
// case "AAAA":
// fmt.Fprintf(&b, `$NewObj.RecordData.IPv6Address = "%s"`, rec.GetTargetIP())
// //case "ATMA":
// // fmt.Fprintf(&b, ` -Atma -Address <String> -AddressType {E164 | AESA}`, rec.GetTargetField())
// //case "AFSDB":
// // fmt.Fprintf(&b, ` -Afsdb -ServerName <String> -SubType <UInt16>`, rec.GetTargetField())
// case "SRV":
// fmt.Fprintf(&b, ` -Srv -DomainName "%s" -Port %d -Priority %d -Weight %d`, rec.GetTargetField(), rec.SrvPort, rec.SrvPriority, rec.SrvWeight)
// fmt.Fprintf(&b, `$NewObj.RecordData.DomainName = "%s"`, rec.GetTargetField())
// fmt.Fprintf(&b, " ; ")
// fmt.Fprintf(&b, `$NewObj.RecordData.Port = %d`, rec.SrvPort)
// fmt.Fprintf(&b, " ; ")
// fmt.Fprintf(&b, `$NewObj.RecordData.Priority = %d`, rec.SrvPriority)
// fmt.Fprintf(&b, " ; ")
// fmt.Fprintf(&b, `$NewObj.RecordData.Weight = "%d"`, rec.SrvWeight)
// case "CNAME":
// fmt.Fprintf(&b, `$NewObj.RecordData.HostNameAlias = "%s"`, rec.GetTargetField())
// //case "X25":
// // fmt.Fprintf(&b, ` -X25 -PsdnAddress <String>`, rec.GetTargetField())
// //case "WKS":
// // fmt.Fprintf(&b, ` -Wks -InternetAddress <IPAddress> -InternetProtocol {UDP | TCP} -Service <String[]>`, rec.GetTargetField())
// case "TXT":
// fmt.Fprintf(&b, `$NewObj.RecordData.DescriptiveText = "%s"`, rec.GetTargetField())
// //case "RT":
// // fmt.Fprintf(&b, ` -RT -IntermediateHost <String> -Preference <UInt16>`, rec.GetTargetField())
// //case "RP":
// // fmt.Fprintf(&b, ` -RP -Description <String> -ResponsiblePerson <String>`, rec.GetTargetField())
// case "PTR":
// fmt.Fprintf(&b, `$NewObj.RecordData.PtrDomainName = "%s"`, rec.GetTargetField())
// case "NS":
// fmt.Fprintf(&b, `$NewObj.RecordData.NameServer = "%s"`, rec.GetTargetField())
// case "MX":
// fmt.Fprintf(&b, `$NewObj.RecordData.MailExchange = "%s"`, rec.GetTargetField())
// fmt.Fprintf(&b, " ; ")
// fmt.Fprintf(&b, `$NewObj.RecordData.Preference = "%d"`, rec.MxPreference)
// //case "ISDN":
// // fmt.Fprintf(&b, ` -Isdn -IsdnNumber <String> -IsdnSubAddress <String>`, rec.GetTargetField())
// //case "HINFO":
// // fmt.Fprintf(&b, ` -HInfo -Cpu <String> -OperatingSystem <String>`, rec.GetTargetField())
// //case "DNAME":
// // fmt.Fprintf(&b, ` -DName -DomainNameAlias <String>`, rec.GetTargetField())
// //case "DHCID":
// // fmt.Fprintf(&b, ` -DhcId -DhcpIdentifier <String>`, rec.GetTargetField())
// //case "TLSA":
// // fmt.Fprintf(&b, ` -TLSA -CertificateAssociationData <System.String> -CertificateUsage {CAConstraint | ServiceCertificateConstraint | TrustAnchorAssertion | DomainIssuedCertificate} -MatchingType {ExactMatch | Sha256Hash | Sha512Hash} -Selector {FullCertificate | SubjectPublicKeyInfo}`, rec.GetTargetField())
// default:
// panic(fmt.Errorf("generatePSModify() update has not implemented recType=%q recName=%q content=(%s))",
// rec.Type, rec.GetLabel(), rec.GetTargetCombined()))
// // We panic so that we quickly find any switch statements
// // that have not been updated for a new RR type.
// }
// fmt.Fprintf(&b, " ; ")
// //fmt.Printf("DEBUG CCMD: %s\n", b.String())
//
// fmt.Fprintf(&b, "Set-DnsServerResourceRecord")
// fmt.Fprintf(&b, ` -ZoneName "%s"`, domain)
// fmt.Fprintf(&b, ` -NewInputObject $NewObj -OldInputObject $OldObj`)
//
// fmt.Printf("DEBUG MCMD: %s", b.String())
// return b.String()

View File

@ -0,0 +1,169 @@
package msdns
import (
"strings"
"testing"
"github.com/StackExchange/dnscontrol/v3/models"
)
func Test_generatePSZoneAll(t *testing.T) {
type args struct {
dnsserver string
domain string
}
tests := []struct {
name string
args args
want string
}{
{
name: "local",
args: args{},
want: `Get-DnsServerZone | ConvertTo-Json`,
},
{
name: "remote",
args: args{dnsserver: "mydnsserver"},
want: `Get-DnsServerZone -ComputerName "mydnsserver" | ConvertTo-Json`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := generatePSZoneAll(tt.args.dnsserver); got != tt.want {
t.Errorf("generatePSZoneAll() = got=(\n%s\n) want=(\n%s\n)", got, tt.want)
}
})
}
}
func Test_generatePSZoneDump(t *testing.T) {
type args struct {
domainname string
dnsserver string
}
tests := []struct {
name string
args args
want string
}{
{
name: "local",
args: args{domainname: "example.com"},
want: `Get-DnsServerResourceRecord -ZoneName "example.com" | ConvertTo-Json -depth 4 > mytemp.json`,
},
{
name: "remote",
args: args{domainname: "example.com", dnsserver: "mydnsserver"},
want: `Get-DnsServerResourceRecord -ComputerName "mydnsserver" -ZoneName "example.com" | ConvertTo-Json -depth 4 > mytemp.json`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := generatePSZoneDump(tt.args.dnsserver, tt.args.domainname, "mytemp.json"); got != tt.want {
t.Errorf("generatePSZoneDump() = got=(\n%s\n) want=(\n%s\n)", got, tt.want)
}
})
}
}
//func Test_generatePSDelete(t *testing.T) {
// type args struct {
// domain string
// rec *models.RecordConfig
// }
// tests := []struct {
// name string
// args args
// want string
// }{
// // TODO: Add test cases.
// }
// for _, tt := range tests {
// t.Run(tt.name, func(t *testing.T) {
// if got := generatePSDelete(tt.args.domain, tt.args.rec); got != tt.want {
// t.Errorf("generatePSDelete() = %v, want %v", got, tt.want)
// }
// })
// }
//}
// func Test_generatePSCreate(t *testing.T) {
// type args struct {
// domain string
// rec *models.RecordConfig
// }
// tests := []struct {
// name string
// args args
// want string
// }{
// // TODO: Add test cases.
// }
// for _, tt := range tests {
// t.Run(tt.name, func(t *testing.T) {
// if got := generatePSCreate(tt.args.domain, tt.args.rec); got != tt.want {
// t.Errorf("generatePSCreate() = %v, want %v", got, tt.want)
// }
// })
// }
// }
func Test_generatePSModify(t *testing.T) {
recA1 := &models.RecordConfig{
Type: "A",
Name: "@",
Target: "1.2.3.4",
}
recA2 := &models.RecordConfig{
Type: "A",
Name: "@",
Target: "10.20.30.40",
}
recMX1 := &models.RecordConfig{
Type: "MX",
Name: "@",
Target: "foo.com.",
MxPreference: 5,
}
recMX2 := &models.RecordConfig{
Type: "MX",
Name: "@",
Target: "foo2.com.",
MxPreference: 50,
}
type args struct {
domain string
dnsserver string
old *models.RecordConfig
rec *models.RecordConfig
}
tests := []struct {
name string
args args
want string
}{
{name: "A", args: args{domain: "example.com", dnsserver: "", old: recA1, rec: recA2},
want: `echo DELETE "A" "@" "1.2.3.4" ; Remove-DnsServerResourceRecord -Force -ZoneName "example.com" -Name "@" -RRType "A" -RecordData "1.2.3.4" ; echo CREATE "A" "@" "10.20.30.40" ; Add-DnsServerResourceRecord -ZoneName "example.com" -Name "@" -TimeToLive $(New-TimeSpan -Seconds 0) -A -IPv4Address "10.20.30.40"`,
},
{name: "MX1", args: args{domain: "example.com", dnsserver: "", old: recMX1, rec: recMX2},
want: `echo DELETE "MX" "@" "5 foo.com." ; Remove-DnsServerResourceRecord -Force -ZoneName "example.com" -Name "@" -RRType "MX" -RecordData 5,"foo.com." ; echo CREATE "MX" "@" "50 foo2.com." ; Add-DnsServerResourceRecord -ZoneName "example.com" -Name "@" -TimeToLive $(New-TimeSpan -Seconds 0) -MX -MailExchange "foo2.com." -Preference 50`,
},
{name: "A-remote", args: args{domain: "example.com", dnsserver: "myremote", old: recA1, rec: recA2},
want: `echo DELETE "A" "@" "1.2.3.4" ; Remove-DnsServerResourceRecord -ComputerName "myremote" -Force -ZoneName "example.com" -Name "@" -RRType "A" -RecordData "1.2.3.4" ; echo CREATE "A" "@" "10.20.30.40" ; Add-DnsServerResourceRecord -ComputerName "myremote" -ZoneName "example.com" -Name "@" -TimeToLive $(New-TimeSpan -Seconds 0) -A -IPv4Address "10.20.30.40"`,
},
{name: "MX1-remote", args: args{domain: "example.com", dnsserver: "yourremote", old: recMX1, rec: recMX2},
want: `echo DELETE "MX" "@" "5 foo.com." ; Remove-DnsServerResourceRecord -ComputerName "yourremote" -Force -ZoneName "example.com" -Name "@" -RRType "MX" -RecordData 5,"foo.com." ; echo CREATE "MX" "@" "50 foo2.com." ; Add-DnsServerResourceRecord -ComputerName "yourremote" -ZoneName "example.com" -Name "@" -TimeToLive $(New-TimeSpan -Seconds 0) -MX -MailExchange "foo2.com." -Preference 50`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := generatePSModify(tt.args.dnsserver, tt.args.domain, tt.args.old, tt.args.rec); strings.TrimSpace(got) != strings.TrimSpace(tt.want) {
t.Errorf("generatePSModify() = got=(\n%s\n) want=(\n%s\n)", got, tt.want)
}
})
}
}

48
providers/msdns/types.go Normal file
View File

@ -0,0 +1,48 @@
package msdns
import (
"encoding/json"
"github.com/StackExchange/dnscontrol/v3/models"
)
// DNSAccessor describes a system that can access Microsoft DNS.
type DNSAccessor interface {
Exit()
GetDNSServerZoneAll(dnsserver string) ([]string, error)
GetDNSZoneRecords(dnsserver, domain string) ([]nativeRecord, error)
RecordCreate(dnsserver, domain string, rec *models.RecordConfig) error
RecordDelete(dnsserver, domain string, rec *models.RecordConfig) error
RecordModify(dnsserver, domain string, old, rec *models.RecordConfig) error
}
// nativeRecord the JSON received from PowerShell when listing all DNS
// records in a zone.
type nativeRecord struct {
//CimClass interface{} `json:"CimClass"`
//CimInstanceProperties interface{} `json:"CimInstanceProperties"`
//CimSystemProperties interface{} `json:"CimSystemProperties"`
//DistinguishedName string `json:"DistinguishedName"`
//RecordClass string `json:"RecordClass"`
RecordType string `json:"RecordType"`
HostName string `json:"HostName"`
RecordData struct {
CimInstanceProperties []ciProperty `json:"CimInstanceProperties"`
} `json:"RecordData"`
TimeToLive struct {
TotalSeconds float64 `json:"TotalSeconds"`
} `json:"TimeToLive"`
}
type ciProperty struct {
Name string `json:"Name"`
Value json.RawMessage `json:"Value,omitempty"`
}
type ciValueDuration struct {
TotalSeconds float64 `json:"TotalSeconds"`
}
// NB(tlim): The above structs were created using the help of:
// Get-DnsServerResourceRecord -ZoneName example.com | where { $_.RecordType -eq "SOA" } | select $_.RecordData | ConvertTo-Json -depth 10
// and pass it to https://mholt.github.io/json-to-go/