mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2024-09-20 14:56:20 +08:00
Activedirectory: Full support for managing NS records. (#450)
* condense AD 'unsupported record type' warnings * active directory provider can create/modify/delete NS records! * ad passes more of the tests now * skip root NS records for safety * clarifying comment
This commit is contained in:
parent
82d4660816
commit
9a6af836dc
|
@ -2,7 +2,7 @@
|
|||
"ACTIVEDIRECTORY_PS": {
|
||||
"ADServer": "$AD_SERVER",
|
||||
"domain": "$AD_DOMAIN",
|
||||
"knownFailures": "20,21,22,29,30,31,32,33,34,35,38,39,40,41,48,49,51,52,53"
|
||||
"knownFailures": "29,30,31,32,33,34,35,38,39,40,41,48,49,51,52,53"
|
||||
},
|
||||
"BIND": {
|
||||
"domain": "example.com"
|
||||
|
|
|
@ -29,12 +29,24 @@ func (c *adProvider) GetNameservers(string) ([]*models.Nameserver, error) {
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
// list of types this provider supports.
|
||||
// until it is up to speed with all the built-in types.
|
||||
var supportedTypes = map[string]bool{
|
||||
"A": true,
|
||||
"AAAA": true,
|
||||
"CNAME": true,
|
||||
"NS": true,
|
||||
}
|
||||
|
||||
// GetDomainCorrections gets existing records, diffs them against existing, and returns corrections.
|
||||
func (c *adProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
|
||||
|
||||
dc.Filter(func(r *models.RecordConfig) bool {
|
||||
if r.Type != "A" && r.Type != "CNAME" {
|
||||
printer.Warnf("Active Directory only manages A and CNAME records. Won't consider %s %s\n", r.Type, r.GetLabelFQDN())
|
||||
if r.Type == "NS" && r.Name == "@" {
|
||||
return false
|
||||
}
|
||||
if !supportedTypes[r.Type] {
|
||||
printer.Warnf("Active Directory only manages certain record types. Won't consider %s %s\n", r.Type, r.GetLabelFQDN())
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
@ -142,24 +154,38 @@ func (c *adProvider) getExistingRecords(domainname string) ([]*models.RecordConf
|
|||
}
|
||||
|
||||
var recs []*RecordConfigJson
|
||||
jdata := string(data)
|
||||
// when there is only a single record, AD powershell does not
|
||||
// wrap it in an array as our types expect. This makes sure it is always an array.
|
||||
if strings.HasPrefix(strings.TrimSpace(jdata), "{") {
|
||||
jdata = "[" + jdata + "]"
|
||||
data = []byte(jdata)
|
||||
}
|
||||
err = json.Unmarshal(data, &recs)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("json.Unmarshal failed on %#v: %v", domainname, err)
|
||||
}
|
||||
|
||||
result := make([]*models.RecordConfig, 0, len(recs))
|
||||
for i := range recs {
|
||||
t := recs[i].unpackRecord(domainname)
|
||||
unsupportedCounts := map[string]int{}
|
||||
for _, rec := range recs {
|
||||
t, supportedType := rec.unpackRecord(domainname)
|
||||
if !supportedType {
|
||||
unsupportedCounts[rec.Type]++
|
||||
}
|
||||
if t != nil {
|
||||
result = append(result, t)
|
||||
}
|
||||
}
|
||||
for t, count := range unsupportedCounts {
|
||||
printer.Warnf("%d records of type %s found in AD zone. These will be ignored.\n", count, t)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *RecordConfigJson) unpackRecord(origin string) *models.RecordConfig {
|
||||
rc := models.RecordConfig{
|
||||
func (r *RecordConfigJson) unpackRecord(origin string) (rc *models.RecordConfig, supported bool) {
|
||||
rc = &models.RecordConfig{
|
||||
Type: r.Type,
|
||||
TTL: r.TTL,
|
||||
}
|
||||
|
@ -169,19 +195,24 @@ func (r *RecordConfigJson) unpackRecord(origin string) *models.RecordConfig {
|
|||
rc.SetTarget(r.Data)
|
||||
case "CNAME":
|
||||
rc.SetTarget(strings.ToLower(r.Data))
|
||||
case "NS", "SOA":
|
||||
return nil
|
||||
case "NS":
|
||||
// skip root NS
|
||||
if rc.Name == "@" {
|
||||
return nil, true
|
||||
}
|
||||
rc.SetTarget(strings.ToLower(r.Data))
|
||||
case "SOA":
|
||||
return nil, true
|
||||
default:
|
||||
printer.Warnf("Record of type %s found in AD zone. Will be ignored.\n", rc.Type)
|
||||
return nil
|
||||
return nil, false
|
||||
}
|
||||
return &rc
|
||||
return rc, true
|
||||
}
|
||||
|
||||
// powerShellDump runs a PowerShell command to get a dump of all records in a DNS zone.
|
||||
func (c *adProvider) generatePowerShellZoneDump(domainname string) string {
|
||||
cmdTxt := `@("REPLACE_WITH_ZONE") | %{
|
||||
Get-DnsServerResourceRecord -ComputerName REPLACE_WITH_COMPUTER_NAME -ZoneName $_ | select hostname,recordtype,@{n="timestamp";e={$_.timestamp.tostring()}},@{n="timetolive";e={$_.timetolive.totalseconds}},@{n="recorddata";e={($_.recorddata.ipv4address,$_.recorddata.ipv6address,$_.recorddata.HostNameAlias,"other_record" -ne $null)[0]-as [string]}} | ConvertTo-Json > REPLACE_WITH_FILENAMEPREFIX.REPLACE_WITH_ZONE.json
|
||||
Get-DnsServerResourceRecord -ComputerName REPLACE_WITH_COMPUTER_NAME -ZoneName $_ | select hostname,recordtype,@{n="timestamp";e={$_.timestamp.tostring()}},@{n="timetolive";e={$_.timetolive.totalseconds}},@{n="recorddata";e={($_.recorddata.ipv4address,$_.recorddata.ipv6address,$_.recorddata.HostNameAlias,$_.recorddata.NameServer,"unsupported_record_type" -ne $null)[0]-as [string]}} | ConvertTo-Json > REPLACE_WITH_FILENAMEPREFIX.REPLACE_WITH_ZONE.json
|
||||
}`
|
||||
cmdTxt = strings.Replace(cmdTxt, "REPLACE_WITH_ZONE", domainname, -1)
|
||||
cmdTxt = strings.Replace(cmdTxt, "REPLACE_WITH_COMPUTER_NAME", c.adServer, -1)
|
||||
|
@ -193,7 +224,11 @@ Get-DnsServerResourceRecord -ComputerName REPLACE_WITH_COMPUTER_NAME -ZoneName $
|
|||
func (c *adProvider) generatePowerShellCreate(domainname string, rec *models.RecordConfig) string {
|
||||
content := rec.GetTargetField()
|
||||
text := "\r\n" // Skip a line.
|
||||
text += fmt.Sprintf("Add-DnsServerResourceRecord%s", rec.Type)
|
||||
funcSuffix := rec.Type
|
||||
if rec.Type == "NS" {
|
||||
funcSuffix = ""
|
||||
}
|
||||
text += fmt.Sprintf("Add-DnsServerResourceRecord%s", funcSuffix)
|
||||
text += fmt.Sprintf(` -ComputerName "%s"`, c.adServer)
|
||||
text += fmt.Sprintf(` -ZoneName "%s"`, domainname)
|
||||
text += fmt.Sprintf(` -Name "%s"`, rec.GetLabel())
|
||||
|
@ -204,7 +239,7 @@ func (c *adProvider) generatePowerShellCreate(domainname string, rec *models.Rec
|
|||
case "A":
|
||||
text += fmt.Sprintf(` -IPv4Address "%s"`, content)
|
||||
case "NS":
|
||||
text = fmt.Sprintf("\r\n"+`echo "Skipping NS update (%v %v)"`+"\r\n", rec.GetLabel(), rec.GetTargetDebug())
|
||||
text += fmt.Sprintf(` -NS -NameServer "%s"`, content)
|
||||
default:
|
||||
panic(errors.Errorf("generatePowerShellCreate() does not yet handle recType=%s recName=%#v content=%#v)",
|
||||
rec.Type, rec.GetLabel(), content))
|
||||
|
@ -220,14 +255,15 @@ func (c *adProvider) generatePowerShellCreate(domainname string, rec *models.Rec
|
|||
func (c *adProvider) generatePowerShellModify(domainname, recName, recType, oldContent, newContent string, oldTTL, newTTL uint32) string {
|
||||
|
||||
var queryField, queryContent string
|
||||
queryContent = `"` + oldContent + `"`
|
||||
|
||||
switch recType { // #rtype_variations
|
||||
case "A":
|
||||
queryField = "IPv4address"
|
||||
queryContent = `"` + oldContent + `"`
|
||||
case "CNAME":
|
||||
queryField = "HostNameAlias"
|
||||
queryContent = `"` + oldContent + `"`
|
||||
case "NS":
|
||||
queryField = "NameServer"
|
||||
default:
|
||||
panic(errors.Errorf("generatePowerShellModify() does not yet handle recType=%s recName=%#v content=(%#v, %#v)", recType, recName, oldContent, newContent))
|
||||
// We panic so that we quickly find any switch statements
|
||||
|
|
Loading…
Reference in a new issue