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

Merge branch 'master' into fix/cci

This commit is contained in:
Chris Hunt
2022-08-12 10:46:57 -04:00
75 changed files with 527 additions and 1446 deletions

3
OWNERS
View File

@ -1,4 +1,3 @@
# providers/activedir DEPRECATED
providers/akamaiedgedns @svernick
providers/autodns @arnoschoon
providers/axfrddns @hnrgrgr
@ -22,7 +21,7 @@ providers/hetzner @das7pad
providers/hexonet @papakai
providers/hostingde @juliusrickert
providers/internetbs @pragmaton
providers/inwx @svenpeter42
providers/inwx @patschi
providers/msdns @tlimoncelli
providers/linode @koesie10
providers/namecheap @captncraig

View File

@ -78,7 +78,7 @@ You can think of it as a DNS compiler. The configuration files are
written in a DSL that looks a lot like JavaScript. It is compiled
to an intermediate representation (IR). Compiler back-ends use the
IR to update your DNS zones on services such as Route53, Cloudflare,
and Gandi, or systems such as BIND and Active Directory.
and Gandi, or systems such as BIND.
## An Example

16
SECURITY.md Normal file
View File

@ -0,0 +1,16 @@
# Security Policy
DNSControl is a command-line tool and therefore has a different (limited) attack surface as compared to a web app or other system.
## Supported Versions
Only the most recent release is supported with security updates.
When a major version is incremented, we'll support the previous major version for 6 months. For example, when v4.0 is released, we will support the most recent v3.x release for 6 months.
## Reporting a Vulnerability
To report a vulnerability please [create a new Github "issue"](https://github.com/StackExchange/dnscontrol/issues/new/choose).
We will respond in a best-effort manner, usually within 1 week. We will communciate via the Github issue unless we need to communicate privately, in which case we'll arrange a way to communicate directly.

View File

@ -5,7 +5,6 @@
<thead>
<tr>
<th></th>
<th class="rotate"><div><span>ACTIVEDIRECTORY_PS</span></div></th>
<th class="rotate"><div><span>AKAMAIEDGEDNS</span></div></th>
<th class="rotate"><div><span>AUTODNS</span></div></th>
<th class="rotate"><div><span>AXFRDDNS</span></div></th>
@ -52,9 +51,6 @@
<tbody>
<tr>
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="This means the provider is actively used at Stack Exchange, bugs are more likely to be fixed, and failing integration tests will block a release. See below for details">Official Support</th>
<td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i>
</td>
<td class="danger">
<i class="fa fa-times text-danger" aria-hidden="true"></i>
</td>
@ -217,9 +213,6 @@
<td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i>
</td>
<td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i>
</td>
<td class="danger">
<i class="fa fa-times text-danger" aria-hidden="true"></i>
</td>
@ -331,9 +324,6 @@
<td class="danger">
<i class="fa fa-times text-danger" aria-hidden="true"></i>
</td>
<td class="danger">
<i class="fa fa-times text-danger" aria-hidden="true"></i>
</td>
<td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i>
</td>
@ -442,9 +432,6 @@
<td class="danger">
<i class="fa fa-times text-danger" aria-hidden="true"></i>
</td>
<td class="danger">
<i class="fa fa-times text-danger" aria-hidden="true"></i>
</td>
<td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i>
</td>
@ -531,8 +518,8 @@
<i class="fa fa-times text-danger" aria-hidden="true"></i>
</td>
<td><i class="fa fa-minus dim"></i></td>
<td class="danger">
<i class="fa fa-times text-danger" aria-hidden="true"></i>
<td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i>
</td>
<td class="danger">
<i class="fa fa-times text-danger" aria-hidden="true"></i>
@ -540,7 +527,6 @@
</tr>
<tr>
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Provider can automatically manage DNSSEC">AUTODNSSEC</th>
<td><i class="fa fa-minus dim"></i></td>
<td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i>
</td>
@ -611,9 +597,6 @@
</tr>
<tr>
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Provider can manage CAA records">CAA</th>
<td class="danger">
<i class="fa fa-times text-danger" aria-hidden="true"></i>
</td>
<td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i>
</td>
@ -724,9 +707,6 @@
</tr>
<tr>
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Provider supports adding PTR records for reverse lookup zones">PTR</th>
<td class="danger">
<i class="fa fa-times text-danger" aria-hidden="true"></i>
</td>
<td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i>
</td>
@ -835,7 +815,6 @@
</tr>
<tr>
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Provider can manage NAPTR records">NAPTR</th>
<td><i class="fa fa-minus dim"></i></td>
<td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i>
</td>
@ -912,7 +891,6 @@
</tr>
<tr>
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Provider can manage SOA records">SOA</th>
<td><i class="fa fa-minus dim"></i></td>
<td class="danger">
<i class="fa fa-times text-danger" aria-hidden="true"></i>
</td>
@ -963,9 +941,6 @@
</tr>
<tr>
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Driver has explicitly implemented SRV record management">SRV</th>
<td class="danger">
<i class="fa fa-times text-danger" aria-hidden="true"></i>
</td>
<td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i>
</td>
@ -1082,7 +1057,6 @@
</tr>
<tr>
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Provider can manage SSHFP records">SSHFP</th>
<td><i class="fa fa-minus dim"></i></td>
<td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i>
</td>
@ -1173,7 +1147,6 @@
</tr>
<tr>
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Provider can manage TLSA records">TLSA</th>
<td><i class="fa fa-minus dim"></i></td>
<td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i>
</td>
@ -1336,7 +1309,6 @@
<td><i class="fa fa-minus dim"></i></td>
<td><i class="fa fa-minus dim"></i></td>
<td><i class="fa fa-minus dim"></i></td>
<td><i class="fa fa-minus dim"></i></td>
<td class="danger" data-toggle="tooltip" data-container="body" data-placement="top" title="Using ALIAS is possible through our extended DNS (X-DNS) service. Feel free to get in touch with us.">
<i class="fa has-tooltip fa-times text-danger" aria-hidden="true"></i>
</td>
@ -1370,7 +1342,6 @@
<td><i class="fa fa-minus dim"></i></td>
<td><i class="fa fa-minus dim"></i></td>
<td><i class="fa fa-minus dim"></i></td>
<td><i class="fa fa-minus dim"></i></td>
<td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i>
</td>
@ -1420,7 +1391,6 @@
</tr>
<tr>
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Provider supports adding DS records">DS</th>
<td><i class="fa fa-minus dim"></i></td>
<td class="danger">
<i class="fa fa-times text-danger" aria-hidden="true"></i>
</td>
@ -1501,7 +1471,6 @@
</tr>
<tr>
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Provider supports adding AKAMAICDN records">AKAMAICDN</th>
<td><i class="fa fa-minus dim"></i></td>
<td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i>
</td>
@ -1544,12 +1513,10 @@
<td><i class="fa fa-minus dim"></i></td>
<td><i class="fa fa-minus dim"></i></td>
<td><i class="fa fa-minus dim"></i></td>
<td><i class="fa fa-minus dim"></i></td>
</tr>
<tr>
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="This provider is recommended for use in &#39;dual hosting&#39; scenarios. Usually this means the provider allows full control over the apex NS records">dual host</th>
<td class="danger" data-toggle="tooltip" data-container="body" data-placement="top" title="This driver does not manage NS records, so should not be used for dual-host scenarios">
<i class="fa has-tooltip fa-times text-danger" aria-hidden="true"></i>
</td>
<td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i>
</td>
@ -1656,9 +1623,6 @@
</tr>
<tr>
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="This means the provider can automatically create domains that do not currently exist on your account. The &#39;dnscontrol create-domains&#39; command will initialize any missing domains">create-domains</th>
<td class="danger" data-toggle="tooltip" data-container="body" data-placement="top" title="AD depends on the zone already existing on the dns server">
<i class="fa has-tooltip fa-times text-danger" aria-hidden="true"></i>
</td>
<td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i>
</td>
@ -1785,9 +1749,6 @@
</tr>
<tr>
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="indicates you can use NO_PURGE macro to prevent deleting records not managed by dnscontrol. A few providers that generate the entire zone from scratch have a problem implementing this.">no_purge</th>
<td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i>
</td>
<td class="danger">
<i class="fa fa-times text-danger" aria-hidden="true"></i>
</td>
@ -1914,9 +1875,6 @@
</tr>
<tr>
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="indicates the dnscontrol get-zones subcommand is implemented.">get-zones</th>
<td class="info">
<i class="fa fa-circle-o text-info" aria-hidden="true"></i>
</td>
<td class="success">
<i class="fa fa-check text-success" aria-hidden="true"></i>
</td>

View File

@ -1,72 +0,0 @@
---
name: ActiveDirectory_PS
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.
# Running on Non-Windows systems
For debugging and testing on non-Windows systems, a "fake PowerShell" mode can be used, which will activate the driver and simulate PowerShell as follows:
- **Zone Input**: Normally when DNSControl needs to know the contents of an existing DNS zone, it generates a PowerShell command to gather such information and saves a copy in a file called `adzonedump.ZONE.json` (where "ZONE" is replaced with the zone name). When "fake PowerShell" mode is enabled, the PowerShell command is not run, but the `adzonedump.ZONE.json` file is read. You must generate this file ahead of time (often on a different machine, one that runs PowerShell).
- **Zone Changes**: Normally when DNSControl needs to change DNS records, it executes PowerShell commands as required. When "fake PowerShell" mode is enabled, these commands are simply logged to a file `dns_update_commands.ps1` and the system assumes they executed.
To activate this mode, set `"fakeps":"true"` inside your credentials file for the provider.
## Configuration
The `ActiveDirectory_PS` provider reads an `ADServer` setting from `creds.json` to know the name of the ActiveDirectory DNS Server to update.
```js
{
"activedir": {
"TYPE": "ACTIVEDIRECTORY_PS",
"ADServer": "ny-dc01"
}
}
```
If you want to modify the "fake powershell" mode details, you can set them in the credentials file:
```js
{
"activedir": {
"TYPE": "ACTIVEDIRECTORY_PS",
"ADServer": "ny-dc01",
"fakeps": "true",
"pslog": "powershell.log",
"psout": "commandsToRun.ps1"
}
}
```
An example DNS configuration:
```js
var REG_NONE = NewRegistrar("none", "NONE")
var DSP_ACTIVEDIRECTORY = NewDnsProvider("activedir", "ACTIVEDIRECTORY_PS");
D("example.tld", REG_NONE, DnsProvider(DSP_ACTIVEDIRECTORY),
A("test", "1.2.3.4")
)
```
To generate a `adzonedump.ZONE.json` file, run `dnscontrol preview` on a Windows system then copy the appropriate file to the system you'll use in "fake powershell" mode.
The `adzonedump.ZONE.json` files should be UTF-16LE encoded. If you hand-craft such a file on a non-Windows system, you may need to convert it from UTF-8 to UTF-16LE using:
iconv -f UTF8 -t UTF-16LE <adzonedump.FOO.json.utf0 > adzonedump.FOO.json
If you check these files into Git, you should mark them as "binary" in `.gitattributes`.

View File

@ -16,8 +16,6 @@ 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
@ -65,37 +63,3 @@ D("example.tld", REG_NONE, DnsProvider(DSP_MSDNS),
A("test", "1.2.3.4")
)
```
# 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:
```json
{
"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"
}
}
```
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`.

View File

@ -47,7 +47,6 @@ Official support means:
Providers in this category and their maintainers are:
* `ACTIVEDIRECTORY_PS` @tlimoncelli
* `AZURE_DNS` @vatsalyagoel
* `BIND` @tlimoncelli
* `GCLOUD` @riyadhalnur

View File

@ -92,7 +92,7 @@ Pick a similar provider as your base. Providers basically fall
into three general categories:
* **zone:** The API requires you to upload the entire zone every time. (BIND, NAMECHEAP).
* **incremental-record:** The API lets you add/change/delete individual DNS records. (ACTIVEDIR, CLOUDFLARE, DNSIMPLE, NAMEDOTCOM, GCLOUD, HEXONET)
* **incremental-record:** The API lets you add/change/delete individual DNS records. (CLOUDFLARE, DNSIMPLE, NAMEDOTCOM, GCLOUD, HEXONET)
* **incremental-label:** Like incremental-record, but if there are
multiple records on a label (for example, example www.example.com
has A and MX records), you have to replace all the records at that

View File

@ -749,7 +749,6 @@ func makeTests(t *testing.T) []*TestGroup {
),
testgroup("MX",
not("ACTIVEDIRECTORY_PS"), // Not implemented.
tc("MX record", mx("@", 5, "foo.com.")),
tc("Second MX record, same prio", mx("@", 5, "foo.com."), mx("@", 5, "foo2.com.")),
tc("3 MX", mx("@", 5, "foo.com."), mx("@", 5, "foo2.com."), mx("@", 15, "foo3.com.")),
@ -1048,7 +1047,7 @@ func makeTests(t *testing.T) []*TestGroup {
tc("CAA change flag", caa("@", "issuewild", 128, "example.com")),
),
testgroup("CAA with ;",
requires(providers.CanUseCAA), not("DIGITALOCEAN"),
requires(providers.CanUseCAA),
// Test support of ";" as a value
tc("CAA many records", caa("@", "issuewild", 0, ";")),
),
@ -1077,7 +1076,7 @@ func makeTests(t *testing.T) []*TestGroup {
),
// ClouDNS provider can work with PTR records, but you need to create special type of zone
testgroup("PTR", requires(providers.CanUsePTR), not("ACTIVEDIRECTORY_PS", "CLOUDNS"),
testgroup("PTR", requires(providers.CanUsePTR), not("CLOUDNS"),
tc("Create PTR record", ptr("4", "foo.com.")),
tc("Modify PTR record", ptr("4", "bar.com.")),
),
@ -1094,7 +1093,7 @@ func makeTests(t *testing.T) []*TestGroup {
tc("Modify SOA minttl", soa("@", "mmm.ns.cloudflare.com.", "eee.cloudflare.com.", 2037190000, 10001, 2401, 604801, 3601)),
),
testgroup("SRV", requires(providers.CanUseSRV), not("ACTIVEDIRECTORY_PS"),
testgroup("SRV", requires(providers.CanUseSRV),
tc("SRV record", srv("_sip._tcp", 5, 6, 7, "foo.com.")),
tc("Second SRV record, same prio", srv("_sip._tcp", 5, 6, 7, "foo.com."), srv("_sip._tcp", 5, 60, 70, "foo2.com.")),
tc("3 SRV", srv("_sip._tcp", 5, 6, 7, "foo.com."), srv("_sip._tcp", 5, 60, 70, "foo2.com."), srv("_sip._tcp", 15, 65, 75, "foo3.com.")),

View File

@ -1,8 +1,4 @@
{
"ACTIVEDIRECTORY_PS": {
"ADServer": "$AD_SERVER",
"domain": "$AD_DOMAIN"
},
"AUTODNS": {
"username": "$AUTODNS_USERNAME",
"password": "$AUTODNS_PASSWORD",

View File

@ -53,36 +53,15 @@ type differ struct {
// get normalized content for record. target, ttl, mxprio, and specified metadata
func (d *differ) content(r *models.RecordConfig) string {
// NB(tlim): This function will eventually be replaced by calling
// r.GetTargetDiffable(). In the meanwhile, this function compares
// its output with r.GetTargetDiffable() to make sure the same
// results are generated. Once we have confidence, this function will go away.
content := fmt.Sprintf("%v ttl=%d", r.GetTargetCombined(), r.TTL)
if r.Type == "SOA" {
content = fmt.Sprintf("%s %v %d %d %d %d ttl=%d", r.GetTargetField(), r.SoaMbox, r.SoaRefresh, r.SoaRetry, r.SoaExpire, r.SoaMinttl, r.TTL) // SoaSerial is not used in comparison
}
// get the extra values maps to add to the comparison.
var allMaps []map[string]string
for _, f := range d.extraValues {
// sort the extra values map keys to perform a deterministic
// comparison since Golang maps iteration order is not guaranteed
valueMap := f(r)
allMaps = append(allMaps, valueMap)
keys := make([]string, 0)
for k := range valueMap {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
v := valueMap[k]
content += fmt.Sprintf(" %s=%s", k, v)
}
}
control := r.ToDiffable(allMaps...)
if control != content {
fmt.Printf("CONTROL=%q CONTENT=%q\n", control, content)
panic("OOPS! control != content")
}
return content
return r.ToDiffable(allMaps...)
}
func apexException(rec *models.RecordConfig) bool {

View File

@ -485,8 +485,10 @@ func ValidateAndNormalizeConfig(config *models.DNSConfig) (errs []error) {
// be performed.
continue
}
if err := providers.AuditRecords(provider.ProviderBase.ProviderType, domain.Records); err != nil {
errs = append(errs, fmt.Errorf("%s rejects domain %s: %w", provider.ProviderBase.ProviderType, domain.Name, err))
if es := providers.AuditRecords(provider.ProviderBase.ProviderType, domain.Records); len(es) != 0 {
for _, e := range es {
errs = append(errs, fmt.Errorf("%s rejects domain %s: %w", provider.ProviderBase.ProviderType, domain.Name, e))
}
}
}
}

View File

@ -153,6 +153,7 @@ func (z *ZoneGenData) generateZoneFileHelper(w io.Writer) error {
return nil
}
// FormatLine formats a zonefile line.
func FormatLine(lengths []int, fields []string) string {
c := 0
result := ""

View File

@ -411,7 +411,7 @@ zt.mup IN A 1.2.3.14
zap IN A 1.2.3.15
`
// func formatLine
// func FormatLine
func TestFormatLine(t *testing.T) {
tests := []struct {

View File

@ -1,159 +0,0 @@
package recordaudit
import (
"fmt"
"strings"
"github.com/StackExchange/dnscontrol/v3/models"
)
// Keep these in alphabetical order.
// TxtNoBackticks audits TXT records for strings that contain backticks.
func TxtNoBackticks(records []*models.RecordConfig) error {
for _, rc := range records {
if rc.HasFormatIdenticalToTXT() {
for _, txt := range rc.TxtStrings {
if strings.Contains(txt, "`") {
return fmt.Errorf("txtstring contains backtick")
}
}
}
}
return nil
}
// TxtNoSingleQuotes audits TXT records for strings that contain single-quotes.
func TxtNoSingleQuotes(records []*models.RecordConfig) error {
for _, rc := range records {
if rc.HasFormatIdenticalToTXT() {
for _, txt := range rc.TxtStrings {
if strings.Contains(txt, "'") {
return fmt.Errorf("txtstring contains single-quotes")
}
}
}
}
return nil
}
// TxtNoDoubleQuotes audits TXT records for strings that contain doublequotes.
func TxtNoDoubleQuotes(records []*models.RecordConfig) error {
for _, rc := range records {
if rc.HasFormatIdenticalToTXT() {
for _, txt := range rc.TxtStrings {
if strings.Contains(txt, `"`) {
return fmt.Errorf("txtstring contains doublequotes")
}
}
}
}
return nil
}
// TxtNoStringsExactlyLen255 audits TXT records for strings exactly 255 octets long.
// This is rare; you probably want to use TxtNoLongStrings() instead.
func TxtNoStringsExactlyLen255(records []*models.RecordConfig) error {
for _, rc := range records {
if rc.HasFormatIdenticalToTXT() { // TXT and similar:
for _, txt := range rc.TxtStrings {
if len(txt) == 255 {
return fmt.Errorf("txtstring length is 255")
}
}
}
}
return nil
}
// TxtNoStringsLen256orLonger audits TXT records for strings that are >255 octets.
func TxtNoStringsLen256orLonger(records []*models.RecordConfig) error {
for _, rc := range records {
if rc.HasFormatIdenticalToTXT() { // TXT and similar:
for _, txt := range rc.TxtStrings {
if len(txt) > 255 {
return fmt.Errorf("%q txtstring length > 255", rc.GetLabel())
}
}
}
}
return nil
}
// TxtNoMultipleStrings audits TXT records for multiple strings
func TxtNoMultipleStrings(records []*models.RecordConfig) error {
for _, rc := range records {
if rc.HasFormatIdenticalToTXT() { // TXT and similar:
if len(rc.TxtStrings) > 1 {
return fmt.Errorf("multiple strings in one txt")
}
}
}
return nil
}
// TxtNoTrailingSpace audits TXT records for strings that end with space.
func TxtNoTrailingSpace(records []*models.RecordConfig) error {
for _, rc := range records {
if rc.HasFormatIdenticalToTXT() { // TXT and similar:
for _, txt := range rc.TxtStrings {
if txt != "" && txt[ultimate(txt)] == ' ' {
return fmt.Errorf("txtstring ends with space")
}
}
}
}
return nil
}
// TxtNotEmpty audits TXT records for empty strings.
func TxtNotEmpty(records []*models.RecordConfig) error {
for _, rc := range records {
if rc.HasFormatIdenticalToTXT() { // TXT and similar:
// There must be strings.
if len(rc.TxtStrings) == 0 {
return fmt.Errorf("txt with no strings")
}
// Each string must be non-empty.
for _, txt := range rc.TxtStrings {
if len(txt) == 0 {
return fmt.Errorf("txtstring is empty")
}
}
}
}
return nil
}
// TxtNoUnpairedDoubleQuotes audits TXT records for strings that contain unpaired doublequotes.
func TxtNoUnpairedDoubleQuotes(records []*models.RecordConfig) error {
for _, rc := range records {
if rc.HasFormatIdenticalToTXT() {
for _, txt := range rc.TxtStrings {
if strings.Count(txt, `"`)%2 == 1 {
return fmt.Errorf("txtstring contains unpaired doublequotes")
}
}
}
}
return nil
}

46
pkg/rejectif/audit.go Normal file
View File

@ -0,0 +1,46 @@
package rejectif
import (
"github.com/StackExchange/dnscontrol/v3/models"
)
// Auditor stores a list of checks to be executed during Audit().
type Auditor struct {
checksFor map[string][]checker
}
type checker = func(*models.RecordConfig) error
// Add registers a function to call on each record of a given type.
func (aud *Auditor) Add(rtype string, fn checker) {
if aud.checksFor == nil {
aud.checksFor = map[string][]checker{}
}
aud.checksFor[rtype] = append(aud.checksFor[rtype], fn)
// SPF records get any checkers that TXT records do.
if rtype == "TXT" {
aud.Add("SPF", fn)
}
}
// Audit performs the audit. For each record it calls each function in
// the list of checks.
func (aud *Auditor) Audit(records models.Records) (errs []error) {
// No checks? Exit early.
if aud.checksFor == nil {
return nil
}
// For each record, call the checks for that type, gather errors.
for _, rc := range records {
for _, f := range aud.checksFor[rc.Type] {
e := f(rc)
if e != nil {
errs = append(errs, e)
}
}
}
return errs
}

18
pkg/rejectif/caa.go Normal file
View File

@ -0,0 +1,18 @@
package rejectif
import (
"fmt"
"strings"
"github.com/StackExchange/dnscontrol/v3/models"
)
// Keep these in alphabetical order.
// CaaTargetHasSemicolon audits CAA records for issues that contain semicolons.
func CaaTargetHasSemicolon(rc *models.RecordConfig) error {
if strings.Contains(rc.GetTargetField(), ";") {
return fmt.Errorf("caa target contains semicolon")
}
return nil
}

104
pkg/rejectif/txt.go Normal file
View File

@ -0,0 +1,104 @@
package rejectif
import (
"fmt"
"strings"
"github.com/StackExchange/dnscontrol/v3/models"
)
// Keep these in alphabetical order.
// TxtHasBackticks audits TXT records for strings that contain backticks.
func TxtHasBackticks(rc *models.RecordConfig) error {
for _, txt := range rc.TxtStrings {
if strings.Contains(txt, "`") {
return fmt.Errorf("txtstring contains backtick")
}
}
return nil
}
// TxtHasSingleQuotes audits TXT records for strings that contain single-quotes.
func TxtHasSingleQuotes(rc *models.RecordConfig) error {
for _, txt := range rc.TxtStrings {
if strings.Contains(txt, "'") {
return fmt.Errorf("txtstring contains single-quotes")
}
}
return nil
}
// TxtHasDoubleQuotes audits TXT records for strings that contain doublequotes.
func TxtHasDoubleQuotes(rc *models.RecordConfig) error {
for _, txt := range rc.TxtStrings {
if strings.Contains(txt, `"`) {
return fmt.Errorf("txtstring contains doublequotes")
}
}
return nil
}
// TxtIsExactlyLen255 audits TXT records for strings exactly 255 octets long.
// This is rare; you probably want to use TxtNoStringsLen256orLonger() instead.
func TxtIsExactlyLen255(rc *models.RecordConfig) error {
for _, txt := range rc.TxtStrings {
if len(txt) == 255 {
return fmt.Errorf("txtstring length is 255")
}
}
return nil
}
// TxtHasSegmentLen256orLonger audits TXT records for strings that are >255 octets.
func TxtHasSegmentLen256orLonger(rc *models.RecordConfig) error {
for _, txt := range rc.TxtStrings {
if len(txt) > 255 {
return fmt.Errorf("%q txtstring length > 255", rc.GetLabel())
}
}
return nil
}
// TxtHasMultipleSegments audits TXT records for multiple strings
func TxtHasMultipleSegments(rc *models.RecordConfig) error {
if len(rc.TxtStrings) > 1 {
return fmt.Errorf("multiple strings in one txt")
}
return nil
}
// TxtHasTrailingSpace audits TXT records for strings that end with space.
func TxtHasTrailingSpace(rc *models.RecordConfig) error {
for _, txt := range rc.TxtStrings {
if txt != "" && txt[ultimate(txt)] == ' ' {
return fmt.Errorf("txtstring ends with space")
}
}
return nil
}
// TxtIsEmpty audits TXT records for empty strings.
func TxtIsEmpty(rc *models.RecordConfig) error {
// There must be strings.
if len(rc.TxtStrings) == 0 {
return fmt.Errorf("txt with no strings")
}
// Each string must be non-empty.
for _, txt := range rc.TxtStrings {
if len(txt) == 0 {
return fmt.Errorf("txtstring is empty")
}
}
return nil
}
// TxtHasUnpairedDoubleQuotes audits TXT records for strings that contain unpaired doublequotes.
func TxtHasUnpairedDoubleQuotes(rc *models.RecordConfig) error {
for _, txt := range rc.TxtStrings {
if strings.Count(txt, `"`)%2 == 1 {
return fmt.Errorf("txtstring contains unpaired doublequotes")
}
}
return nil
}

View File

@ -1,4 +1,4 @@
package recordaudit
package rejectif
/*
I proposed that Go add something like "len()" that returns the highest

View File

@ -3,7 +3,6 @@ package all
import (
// Define all known providers here. They should each register themselves with the providers package via init function.
_ "github.com/StackExchange/dnscontrol/v3/providers/activedir"
_ "github.com/StackExchange/dnscontrol/v3/providers/akamaiedgedns"
_ "github.com/StackExchange/dnscontrol/v3/providers/autodns"
_ "github.com/StackExchange/dnscontrol/v3/providers/axfrddns"

View File

@ -1,73 +0,0 @@
package activedir
import (
"encoding/json"
"fmt"
"github.com/StackExchange/dnscontrol/v3/pkg/printer"
"runtime"
"github.com/StackExchange/dnscontrol/v3/providers"
)
// This is the struct that matches either (or both) of the Registrar and/or DNSProvider interfaces:
type activedirProvider struct {
adServer string
fake bool
psOut string
psLog string
}
var features = providers.DocumentationNotes{
providers.CanGetZones: providers.Unimplemented(),
providers.CanUseAlias: providers.Cannot(),
providers.CanUseCAA: providers.Cannot(),
providers.CanUsePTR: providers.Cannot(),
providers.CanUseSRV: providers.Cannot(),
providers.DocCreateDomains: providers.Cannot("AD depends on 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() {
fns := providers.DspFuncs{
Initializer: newDNS,
RecordAuditor: AuditRecords,
}
providers.RegisterDomainServiceProviderType("ACTIVEDIRECTORY_PS", fns, features)
}
func newDNS(config map[string]string, metadata json.RawMessage) (providers.DNSServiceProvider, error) {
printer.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" {
fake = true
} else if fVal != "" && fVal != "false" {
return nil, fmt.Errorf("fakeps value must be 'true' or 'false'")
}
psOut, psLog := config["psout"], config["pslog"]
if psOut == "" {
psOut = "dns_update_commands.ps1"
}
if psLog == "" {
psLog = "powershell.log"
}
p := &activedirProvider{psLog: psLog, psOut: psOut, fake: fake}
if fake {
return p, nil
}
if runtime.GOOS == "windows" {
srv := config["ADServer"]
if srv == "" {
return nil, fmt.Errorf("ADServer required for Active Directory provider")
}
p.adServer = srv
return p, nil
}
printer.Printf("WARNING: PowerShell not available. Active Directory will not be updated.\n")
return providers.None{}, nil
}

View File

@ -1,11 +0,0 @@
package activedir
import (
"github.com/StackExchange/dnscontrol/v3/models"
)
// AuditRecords returns an error if any records are not
// supportable by this provider.
func AuditRecords(records []*models.RecordConfig) error {
return nil
}

View File

@ -1,78 +0,0 @@
### Active Directory
This provider updates a DNS Zone in an Active Directory Integrated Zone.
When run on Windows, AD is updated directly. The code generates
PowerShell commands, executes them, and checks the results.
It leaves behind a log file of the commands that were generated.
When run on non-Windows, AD isn't updated because we can't execute
PowerShell at this time. Instead of reading the existing zone data
from AD, It learns what
records are in the zone by reading
`adzonedump.{ZONENAME}.json`, a file that must be created beforehand.
It does not actually update AD, it generates a file with PowerShell
commands that would do the updates, which you must execute afterwords.
If the `adzonedump.{ZONENAME}.json` does not exist, the zone is quietly skipped.
Not implemented:
* Delete records. This provider will not delete any records. It will only add
and change existing records. See "Note to future devs" below.
* Update TTLs. It ignores TTLs.
## required creds.json config
No "creds.json" configuration is expected.
## example dns config js:
```js
var REG_NONE = NewRegistrar('none', 'NONE')
var DSP_ACTIVEDIRECTORY_DS = NewDSP("activedir", "ACTIVEDIRECTORY_PS");
D('ds.stackexchange.com', REG_NONE,
DSP_ACTIVEDIRECTORY_DS,
)
// records handled by another provider...
);
```
## Special Windows stuff
This provider needs to do 2 things:
* Get a list of zone records:
* powerShellDump: Runs a PS command that dumps the zone to JSON.
* readZoneDump: Opens a adzonedump.$DOMAINNAME.json file and reads JSON out of it. If the file does not exist, this is considered an error and processing stops.
* Update records:
* powerShellExec: Execute PS commands that do the update.
* powerShellRecord: Record the PS command that can be run later to do the updates. This file is -psout=dns_update_commands.ps1
So what happens when? Well, that's complex. We want both Windows and Linux to be able to use -fakewindows
for either debugging or (on Windows) actual use. However only Windows permits -fakewinows=false and actually executes
the PS code. Here's which algorithm is used for each case:
* If -fakewindows is used on any system: readZoneDump and powerShellRecord is used.
* On Windows (without -fakewindows): powerShellDump and powerShellExec is used.
* On Linux (wihtout -fakewindows): the provider loads as "NONE" and nothing happens.
## Note to future devs
### Why doesn't this provider delete records?
Because at this time Stack doesn't fully control AD zones
using dnscontrol. It only needs to add/change records.
What should we do when it does need to delete them?
Currently NO_PURGE is a no-op. I would change it to update
domain metadata to flag that deletes should be enabled/disabled.
Then generate the deletes only if this flag exists. To be paranoid,
the func that does the deleting could check this flag to make sure
that it really should be deleting something.

View File

@ -1,358 +0,0 @@
package activedir
import (
"encoding/json"
"fmt"
"os"
"strings"
"time"
"github.com/StackExchange/dnscontrol/v3/models"
"github.com/StackExchange/dnscontrol/v3/pkg/diff"
"github.com/StackExchange/dnscontrol/v3/pkg/printer"
"github.com/TomOnTime/utfutil"
)
const zoneDumpFilenamePrefix = "adzonedump"
// RecordConfigJSON RecordConfig, reconfigured for JSON input/output.
type RecordConfigJSON struct {
Name string `json:"hostname"`
Type string `json:"recordtype"`
Data string `json:"recorddata"`
TTL uint32 `json:"timetolive"`
}
func (c *activedirProvider) GetNameservers(string) ([]*models.Nameserver, error) {
// TODO: If using AD for publicly hosted zones, probably pull these from config.
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,
}
// GetZoneRecords gets the records of a zone and returns them in RecordConfig format.
func (c *activedirProvider) GetZoneRecords(domain string) (models.Records, error) {
foundRecords, err := c.getExistingRecords(domain)
if err != nil {
return nil, fmt.Errorf("c.getExistingRecords(%q) failed: %v", domain, err)
}
return foundRecords, nil
}
// GetDomainCorrections gets existing records, diffs them against existing, and returns corrections.
func (c *activedirProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
dc.Filter(func(r *models.RecordConfig) bool {
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
})
// Read foundRecords:
foundRecords, err := c.getExistingRecords(dc.Name)
if err != nil {
return nil, fmt.Errorf("c.getExistingRecords(%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
}
// NOTE(tlim): This provider does not delete records. If
// you need to delete a record, either delete it manually
// or see providers/activedir/doc.md for implementation tips.
// Generate changes.
corrections := []*models.Correction{}
for _, del := range dels {
corrections = append(corrections, c.deleteRec(dc.Name, del))
}
for _, cre := range creates {
corrections = append(corrections, c.createRec(dc.Name, cre)...)
}
for _, m := range modifications {
corrections = append(corrections, c.modifyRec(dc.Name, m))
}
return corrections, nil
}
// zoneDumpFilename returns the filename to use to write or read
// an activedirectory zone dump for a particular domain.
func zoneDumpFilename(domainname string) string {
return zoneDumpFilenamePrefix + "." + domainname + ".json"
}
// readZoneDump reads a pre-existing zone dump from adzonedump.*.json.
func (c *activedirProvider) readZoneDump(domainname string) ([]byte, error) {
// File not found is considered an error.
dat, err := utfutil.ReadFile(zoneDumpFilename(domainname), utfutil.WINDOWS)
if err != nil {
printer.Printf("Powershell to generate zone dump:\n")
printer.Printf("%v\n", c.generatePowerShellZoneDump(domainname))
}
return dat, err
}
// powerShellLogCommand logs to flagPsLog that a PowerShell command is going to be run.
func (c *activedirProvider) logCommand(command string) error {
return c.logHelper(fmt.Sprintf("# %s\r\n%s\r\n", time.Now().UTC(), strings.TrimSpace(command)))
}
// powerShellLogOutput logs to flagPsLog that a PowerShell command is going to be run.
func (c *activedirProvider) logOutput(s string) error {
return c.logHelper(fmt.Sprintf("OUTPUT: START\r\n%s\r\nOUTPUT: END\r\n", s))
}
// powerShellLogErr logs that a PowerShell command had an error.
func (c *activedirProvider) logErr(e error) error {
err := c.logHelper(fmt.Sprintf("ERROR: %v\r\r", e)) // Log error to powershell.log
if err != nil {
return err // Bubble up error created in logHelper
}
return e // Bubble up original error
}
func (c *activedirProvider) logHelper(s string) error {
logfile, err := os.OpenFile(c.psLog, os.O_APPEND|os.O_RDWR|os.O_CREATE, 0660)
if err != nil {
return fmt.Errorf("error: Can not create/append to %#v: %v", c.psLog, err)
}
_, err = fmt.Fprintln(logfile, s)
if err != nil {
return fmt.Errorf("append to %#v failed: %v", c.psLog, err)
}
if logfile.Close() != nil {
return fmt.Errorf("closing %#v failed: %v", c.psLog, err)
}
return nil
}
// powerShellRecord records that a PowerShell command should be executed later.
func (c *activedirProvider) powerShellRecord(command string) error {
recordfile, err := os.OpenFile(c.psOut, os.O_APPEND|os.O_RDWR|os.O_CREATE, 0660)
if err != nil {
return fmt.Errorf("can not create/append to %#v: %v", c.psOut, err)
}
_, err = recordfile.WriteString(command)
if err != nil {
return fmt.Errorf("append to %#v failed: %v", c.psOut, err)
}
return recordfile.Close()
}
func (c *activedirProvider) getExistingRecords(domainname string) ([]*models.RecordConfig, error) {
// Get the JSON either from adzonedump or by running a PowerShell script.
data, err := c.getRecords(domainname)
if err != nil {
return nil, fmt.Errorf("getRecords failed on %#v: %v", domainname, err)
}
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, fmt.Errorf("json.Unmarshal failed on %#v: %v", domainname, err)
}
result := make([]*models.RecordConfig, 0, len(recs))
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) (rc *models.RecordConfig, supported bool) {
rc = &models.RecordConfig{
Type: r.Type,
TTL: r.TTL,
}
rc.SetLabel(r.Name, origin)
switch rtype := rc.Type; rtype { // #rtype_variations
case "A", "AAAA":
rc.SetTarget(r.Data)
case "CNAME":
rc.SetTarget(strings.ToLower(r.Data))
case "NS":
// skip root NS
if rc.Name == "@" {
return nil, true
}
rc.SetTarget(strings.ToLower(r.Data))
case "SOA":
return nil, true
default:
return nil, false
}
return rc, true
}
// powerShellDump runs a PowerShell command to get a dump of all records in a DNS zone.
func (c *activedirProvider) 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,$_.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)
cmdTxt = strings.Replace(cmdTxt, "REPLACE_WITH_FILENAMEPREFIX", zoneDumpFilenamePrefix, -1)
return cmdTxt
}
// generatePowerShellCreate generates PowerShell commands to ADD a record.
func (c *activedirProvider) generatePowerShellCreate(domainname string, rec *models.RecordConfig) string {
content := rec.GetTargetField()
text := "\r\n" // Skip a line.
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())
text += fmt.Sprintf(` -TimeToLive $(New-TimeSpan -Seconds %d)`, rec.TTL)
switch rec.Type { // #rtype_variations
case "CNAME":
text += fmt.Sprintf(` -HostNameAlias "%s"`, content)
case "A":
text += fmt.Sprintf(` -IPv4Address "%s"`, content)
case "NS":
text += fmt.Sprintf(` -NS -NameServer "%s"`, content)
default:
panic(fmt.Errorf("generatePowerShellCreate() does not yet handle recType=%s recName=%#v content=%#v)",
rec.Type, rec.GetLabel(), content))
// We panic so that we quickly find any switch statements
// that have not been updated for a new RR type.
}
text += "\r\n"
return text
}
// generatePowerShellModify generates PowerShell commands to MODIFY a record.
func (c *activedirProvider) 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"
case "CNAME":
queryField = "HostNameAlias"
case "NS":
queryField = "NameServer"
default:
panic(fmt.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
// that have not been updated for a new RR type.
}
text := "\r\n" // Skip a line.
text += fmt.Sprintf(`echo "MODIFY %s %s %s old=%s new=%s"`, recName, domainname, recType, oldContent, newContent)
text += "\r\n"
text += "$OldObj = Get-DnsServerResourceRecord"
text += fmt.Sprintf(` -ComputerName "%s"`, c.adServer)
text += fmt.Sprintf(` -ZoneName "%s"`, domainname)
text += fmt.Sprintf(` -Name "%s"`, recName)
text += fmt.Sprintf(` -RRType "%s"`, recType)
text += fmt.Sprintf(" | Where-Object {$_.RecordData.%s -eq %s -and $_.HostName -eq \"%s\"}", queryField, queryContent, recName)
text += "\r\n"
text += `if($OldObj.Length -ne $null){ throw "Error, multiple results for Get-DnsServerResourceRecord" }`
text += "\r\n"
text += "$NewObj = $OldObj.Clone()"
text += "\r\n"
if oldContent != newContent {
text += fmt.Sprintf(`$NewObj.RecordData.%s = "%s"`, queryField, newContent)
text += "\r\n"
}
if oldTTL != newTTL {
text += fmt.Sprintf(`$NewObj.TimeToLive = New-TimeSpan -Seconds %d`, newTTL)
text += "\r\n"
}
text += "Set-DnsServerResourceRecord"
text += fmt.Sprintf(` -ComputerName "%s"`, c.adServer)
text += fmt.Sprintf(` -ZoneName "%s"`, domainname)
text += ` -NewInputObject $NewObj -OldInputObject $OldObj`
text += "\r\n"
return text
}
func (c *activedirProvider) generatePowerShellDelete(domainname, recName, recType, content string) string {
text := fmt.Sprintf(`echo "DELETE %s %s %s"`, recType, recName, content)
text += "\r\n"
text += `Remove-DnsServerResourceRecord -Force -ComputerName "%s" -ZoneName "%s" -Name "%s" -RRType "%s" -RecordData "%s"`
text += "\r\n"
return fmt.Sprintf(text, c.adServer, domainname, recName, recType, content)
}
func (c *activedirProvider) createRec(domainname string, cre diff.Correlation) []*models.Correction {
rec := cre.Desired
arr := []*models.Correction{
{
Msg: cre.String(),
F: func() error {
return c.powerShellDoCommand(c.generatePowerShellCreate(domainname, rec), true)
}},
}
return arr
}
func (c *activedirProvider) modifyRec(domainname string, m diff.Correlation) *models.Correction {
old, rec := m.Existing, m.Desired
return &models.Correction{
Msg: m.String(),
F: func() error {
return c.powerShellDoCommand(c.generatePowerShellModify(domainname, rec.GetLabel(), rec.Type, old.GetTargetField(), rec.GetTargetField(), old.TTL, rec.TTL), true)
},
}
}
func (c *activedirProvider) deleteRec(domainname string, cor diff.Correlation) *models.Correction {
rec := cor.Existing
return &models.Correction{
Msg: cor.String(),
F: func() error {
return c.powerShellDoCommand(c.generatePowerShellDelete(domainname, rec.GetLabel(), rec.Type, rec.GetTargetField()), true)
},
}
}

View File

@ -1,46 +0,0 @@
package activedir
import (
"fmt"
"testing"
"github.com/StackExchange/dnscontrol/v3/models"
)
func makeRC(label, domain, target string, rc models.RecordConfig) *models.RecordConfig {
rc.SetLabel(label, domain)
rc.SetTarget(target)
return &rc
}
func TestGetExistingRecords(t *testing.T) {
cf := &activedirProvider{}
cf.fake = true
actual, err := cf.getExistingRecords("test2")
if err != nil {
t.Fatal(err)
}
expected := []*models.RecordConfig{
makeRC("@", "test2", "10.166.2.11", models.RecordConfig{Type: "A", TTL: 600}),
makeRC("_msdcs", "test2", "other_record", models.RecordConfig{Type: "NS", TTL: 300}),
makeRC("co-devsearch02", "test2", "10.8.2.64", models.RecordConfig{Type: "A", TTL: 3600}),
makeRC("co-devservice01", "test2", "10.8.2.48", models.RecordConfig{Type: "A", TTL: 1200}), // Downcased.
makeRC("yum", "test2", "10.8.0.59", models.RecordConfig{Type: "A", TTL: 3600}),
}
actualS := ""
for i, x := range actual {
actualS += fmt.Sprintf("%d %v\n", i, x)
}
expectedS := ""
for i, x := range expected {
expectedS += fmt.Sprintf("%d %v\n", i, x)
}
if actualS != expectedS {
t.Fatalf("got\n(%s)\nbut expected\n(%s)", actualS, expectedS)
}
}

View File

@ -1,18 +0,0 @@
//go:build !windows
// +build !windows
package activedir
func (c *activedirProvider) getRecords(domainname string) ([]byte, error) {
if !c.fake {
panic("Can not happen: PowerShell on non-windows")
}
return c.readZoneDump(domainname)
}
func (c *activedirProvider) powerShellDoCommand(command string, shouldLog bool) error {
if !c.fake {
panic("Can not happen: PowerShell on non-windows")
}
return c.powerShellRecord(command)
}

View File

@ -1,86 +0,0 @@
package activedir
import (
"fmt"
"os/exec"
"strconv"
"strings"
"sync"
"github.com/StackExchange/dnscontrol/v3/pkg/printer"
)
var checkPS sync.Once
var psAvailible = false
func (c *activedirProvider) getRecords(domainname string) ([]byte, error) {
// If we are using PowerShell, make sure it is enabled
// and then run the PS1 command to generate the adzonedump file.
if !c.fake {
checkPS.Do(func() {
psAvailible = c.isPowerShellReady()
if !psAvailible {
printer.Printf("\n\n\n")
printer.Printf("***********************************************\n")
printer.Printf("PowerShell DnsServer module not installed.\n")
printer.Printf("See http://social.technet.microsoft.com/wiki/contents/articles/2202.remote-server-administration-tools-rsat-for-windows-client-and-windows-server-dsforum2wiki.aspx\n")
printer.Printf("***********************************************\n")
printer.Printf("\n\n\n")
}
})
if !psAvailible {
return nil, fmt.Errorf("powershell module DnsServer not installed")
}
_, err := c.powerShellExec(c.generatePowerShellZoneDump(domainname), true)
if err != nil {
return []byte{}, err
}
}
// Return the contents of zone.*.json file instead.
return c.readZoneDump(domainname)
}
func (c *activedirProvider) isPowerShellReady() bool {
query, _ := c.powerShellExec(`(Get-Module -ListAvailable DnsServer) -ne $null`, true)
q, err := strconv.ParseBool(strings.TrimSpace(string(query)))
if err != nil {
return false
}
return q
}
func (c *activedirProvider) powerShellDoCommand(command string, shouldLog bool) error {
if c.fake {
// If fake, just record the command.
return c.powerShellRecord(command)
}
_, err := c.powerShellExec(command, shouldLog)
return err
}
func (c *activedirProvider) powerShellExec(command string, shouldLog bool) ([]byte, error) {
// log it.
err := c.logCommand(command)
if err != nil {
return nil, err
}
// Run it.
out, err := exec.Command("powershell", "-NoProfile", command).CombinedOutput()
if err != nil {
// If there was an error, log it.
c.logErr(err)
}
if shouldLog {
err = c.logOutput(string(out))
if err != nil {
return []byte{}, err
}
}
// Return the result.
return out, err
}

View File

@ -2,7 +2,9 @@ package akamaiedgedns
import "github.com/StackExchange/dnscontrol/v3/models"
// AuditRecords returns an error if any records are not supportable by this provider.
func AuditRecords(records []*models.RecordConfig) error {
// AuditRecords returns a list of errors corresponding to the records
// that aren't supported by this provider. If all records are
// supported, an empty list is returned.
func AuditRecords(records []*models.RecordConfig) []error {
return nil
}

View File

@ -12,6 +12,7 @@ import (
"github.com/StackExchange/dnscontrol/v3/models"
)
// ZoneListFilter describes a JSON list filter.
type ZoneListFilter struct {
Key string `json:"key"`
Value string `json:"value"`
@ -20,6 +21,7 @@ type ZoneListFilter struct {
Filter []*ZoneListFilter `json:"filters,omitempty"`
}
// ZoneListRequest describes a JSON zone list request.
type ZoneListRequest struct {
Filter []*ZoneListFilter `json:"filters"`
}
@ -27,11 +29,11 @@ type ZoneListRequest struct {
func (api *autoDnsProvider) request(method string, requestPath string, data interface{}) ([]byte, error) {
client := &http.Client{}
requestUrl := api.baseURL
requestUrl.Path = api.baseURL.Path + requestPath
requestURL := api.baseURL
requestURL.Path = api.baseURL.Path + requestPath
request := &http.Request{
URL: &requestUrl,
URL: &requestURL,
Header: api.defaultHeaders,
Method: method,
}
@ -50,7 +52,7 @@ func (api *autoDnsProvider) request(method string, requestPath string, data inte
responseText, _ := ioutil.ReadAll(response.Body)
if response.StatusCode != 200 {
return nil, errors.New("Request to " + requestUrl.Path + " failed: " + string(responseText))
return nil, errors.New("Request to " + requestURL.Path + " failed: " + string(responseText))
}
return responseText, nil

View File

@ -2,8 +2,9 @@ package autodns
import "github.com/StackExchange/dnscontrol/v3/models"
// AuditRecords returns an error if any records are not
// supportable by this provider.
func AuditRecords(records []*models.RecordConfig) error {
// AuditRecords returns a list of errors corresponding to the records
// that aren't supported by this provider. If all records are
// supported, an empty list is returned.
func AuditRecords(records []*models.RecordConfig) []error {
return nil
}

View File

@ -1,11 +1,10 @@
package axfrddns
import (
"github.com/StackExchange/dnscontrol/v3/models"
)
import "github.com/StackExchange/dnscontrol/v3/models"
// AuditRecords returns an error if any records are not
// supportable by this provider.
func AuditRecords(records []*models.RecordConfig) error {
// AuditRecords returns a list of errors corresponding to the records
// that aren't supported by this provider. If all records are
// supported, an empty list is returned.
func AuditRecords(records []*models.RecordConfig) []error {
return nil
}

View File

@ -1,11 +1,10 @@
package azuredns
import (
"github.com/StackExchange/dnscontrol/v3/models"
)
import "github.com/StackExchange/dnscontrol/v3/models"
// AuditRecords returns an error if any records are not
// supportable by this provider.
func AuditRecords(records []*models.RecordConfig) error {
// AuditRecords returns a list of errors corresponding to the records
// that aren't supported by this provider. If all records are
// supported, an empty list is returned.
func AuditRecords(records []*models.RecordConfig) []error {
return nil
}

View File

@ -1,11 +1,10 @@
package bind
import (
"github.com/StackExchange/dnscontrol/v3/models"
)
import "github.com/StackExchange/dnscontrol/v3/models"
// AuditRecords returns an error if any records are not
// supportable by this provider.
func AuditRecords(records []*models.RecordConfig) error {
// AuditRecords returns a list of errors corresponding to the records
// that aren't supported by this provider. If all records are
// supported, an empty list is returned.
func AuditRecords(records []*models.RecordConfig) []error {
return nil
}

View File

@ -2,24 +2,20 @@ package cloudflare
import (
"github.com/StackExchange/dnscontrol/v3/models"
"github.com/StackExchange/dnscontrol/v3/pkg/recordaudit"
"github.com/StackExchange/dnscontrol/v3/pkg/rejectif"
)
// AuditRecords returns an error if any records are not
// supportable by this provider.
func AuditRecords(records []*models.RecordConfig) error {
// AuditRecords returns a list of errors corresponding to the records
// that aren't supported by this provider. If all records are
// supported, an empty list is returned.
func AuditRecords(records []*models.RecordConfig) []error {
a := rejectif.Auditor{}
if err := recordaudit.TxtNoMultipleStrings(records); err != nil {
return err
} // Still needed as of 2022-06-18
a.Add("TXT", rejectif.TxtHasMultipleSegments) // Last verified 2022-06-18
if err := recordaudit.TxtNoTrailingSpace(records); err != nil {
return err
} // Still needed as of 2022-06-18
a.Add("TXT", rejectif.TxtHasTrailingSpace) // Last verified 2022-06-18
if err := recordaudit.TxtNotEmpty(records); err != nil {
return err
} // Still needed as of 2022-06-18
a.Add("TXT", rejectif.TxtIsEmpty) // Last verified 2022-06-18
return nil
return a.Audit(records)
}

View File

@ -16,7 +16,7 @@ func (c *cloudflareProvider) fetchDomainList() error {
c.nameservers = map[string][]string{}
zones, err := c.cfClient.ListZones(context.Background())
if err != nil {
return fmt.Errorf("failed fetching domain list from cloudflare: %s", err)
return fmt.Errorf("failed fetching domain list from cloudflare(%q): %s", c.cfClient.APIEmail, err)
}
for _, zone := range zones {

View File

@ -2,36 +2,24 @@ package cloudns
import (
"github.com/StackExchange/dnscontrol/v3/models"
"github.com/StackExchange/dnscontrol/v3/pkg/recordaudit"
"github.com/StackExchange/dnscontrol/v3/pkg/rejectif"
)
// AuditRecords returns an error if any records are not
// supportable by this provider.
func AuditRecords(records []*models.RecordConfig) error {
// AuditRecords returns a list of errors corresponding to the records
// that aren't supported by this provider. If all records are
// supported, an empty list is returned.
func AuditRecords(records []*models.RecordConfig) []error {
a := rejectif.Auditor{}
if err := recordaudit.TxtNoBackticks(records); err != nil {
return err
}
// Still needed as of 2021-03-01
a.Add("TXT", rejectif.TxtHasBackticks) // Last verified 2021-03-01
if err := recordaudit.TxtNotEmpty(records); err != nil {
return err
}
// Still needed as of 2021-03-01
a.Add("TXT", rejectif.TxtIsEmpty) // Last verified 2021-03-01
if err := recordaudit.TxtNoTrailingSpace(records); err != nil {
return err
}
// Still needed as of 2021-03-01
a.Add("TXT", rejectif.TxtHasTrailingSpace) // Last verified 2021-03-01
if err := recordaudit.TxtNoDoubleQuotes(records); err != nil {
return err
}
// Still needed as of 2021-03-11
a.Add("TXT", rejectif.TxtHasDoubleQuotes) // Last verified 2021-03-01
if err := recordaudit.TxtNoMultipleStrings(records); err != nil {
return err
}
a.Add("TXT", rejectif.TxtHasMultipleSegments) // Last verified 2021-03-01
return nil
return a.Audit(records)
}

View File

@ -2,39 +2,34 @@ package cscglobal
import (
"github.com/StackExchange/dnscontrol/v3/models"
"github.com/StackExchange/dnscontrol/v3/pkg/recordaudit"
"github.com/StackExchange/dnscontrol/v3/pkg/rejectif"
)
// AuditRecords returns an error if any records are not
// supportable by this provider.
func AuditRecords(records []*models.RecordConfig) error {
// AuditRecords returns a list of errors corresponding to the records
// that aren't supported by this provider. If all records are
// supported, an empty list is returned.
func AuditRecords(records []*models.RecordConfig) []error {
a := rejectif.Auditor{}
// Each test should be encapsulated in a function that can be tested
// individually. If the test is of general use, add it to the
// recordaudit module.
a.Add("TXT", rejectif.TxtHasMultipleSegments) // Last verified 2022-06-10
// Each test should document the last time we verified the test was
// still needed. Sometimes companies change their API.
a.Add("TXT", rejectif.TxtHasTrailingSpace) // Last verified 2022-06-10
if err := recordaudit.TxtNoDoubleQuotes(records); err != nil {
return err
} // Needed as of 2022-08-08
a.Add("TXT", rejectif.TxtIsEmpty) // Last verified 2022-06-10
// if err := recordaudit.TxtNoStringsLen256orLonger(records); err != nil {
// return err
// } // Needed as of 2022-06-10
a.Add("TXT", rejectif.TxtHasDoubleQuotes) // Last verified 2022-08-08
if err := recordaudit.TxtNoMultipleStrings(records); err != nil {
return err
} // Needed as of 2022-06-10
if err := recordaudit.TxtNoTrailingSpace(records); err != nil {
return err
} // Needed as of 2022-06-10
if err := recordaudit.TxtNotEmpty(records); err != nil {
return err
} // Needed as of 2022-06-10
return nil
return a.Audit(records)
}
/* How To Write Providers:
Each test should be encapsulated in a function that can be tested
individually. If the test is of general use, add it to the
rejectif module.
The "Last verified" comment logs the last time we verified this
test was needed. Sometimes companies change their API. Once a year,
try removing tests one at a time to verify they are still needed.
*/

View File

@ -1,7 +1,6 @@
package cscglobal
import (
"fmt"
"strings"
"github.com/StackExchange/dnscontrol/v3/models"
@ -113,12 +112,6 @@ func PrepDesiredRecords(dc *models.DomainConfig) {
// GetDomainCorrections gets existing records, diffs them against existing, and returns corrections.
func (client *providerClient) GenerateDomainCorrections(dc *models.DomainConfig, foundRecords models.Records) ([]*models.Correction, error) {
// Read foundRecords:
foundRecords, err := client.GetZoneRecords(dc.Name)
if err != nil {
return nil, fmt.Errorf("c.GetDNSZoneRecords(%v) failed: %v", dc.Name, err)
}
// Normalize
models.PostProcessRecords(foundRecords)
//txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records

View File

@ -4,8 +4,9 @@ import (
"github.com/StackExchange/dnscontrol/v3/models"
)
// AuditRecords returns an error if any records are not
// supportable by this provider.
func AuditRecords(records []*models.RecordConfig) error {
// AuditRecords returns a list of errors corresponding to the records
// that aren't supported by this provider. If all records are
// supported, an empty list is returned.
func AuditRecords(records []*models.RecordConfig) []error {
return nil
}

View File

@ -2,37 +2,32 @@ package digitalocean
import (
"fmt"
"github.com/StackExchange/dnscontrol/v3/models"
"github.com/StackExchange/dnscontrol/v3/pkg/recordaudit"
"github.com/StackExchange/dnscontrol/v3/pkg/rejectif"
)
// AuditRecords returns an error if any records are not
// supportable by this provider.
func AuditRecords(records []*models.RecordConfig) error {
// AuditRecords returns a list of errors corresponding to the records
// that aren't supported by this provider. If all records are
// supported, an empty list is returned.
func AuditRecords(records []*models.RecordConfig) []error {
a := rejectif.Auditor{}
// TODO(tlim): Audit CAA records.
// "Semicolons not supported in issue/issuewild fields.", "https://www.digitalocean.com/docs/networking/dns/how-to/create-caa-records"),
// Users are warned about these limits in docs/_providers/digitalocean.md
a.Add("TXT", MaxLengthDO) // Last verified 2021-03-01
if err := MaxLengthDO(records); err != nil {
return err
}
// Still needed as of 2021-03-01
a.Add("CAA", rejectif.CaaTargetHasSemicolon) // Last verified 2021-03-01
a.Add("TXT", rejectif.TxtHasDoubleQuotes) // Last verified 2021-03-01
// Double-quotes not permitted in TXT strings. I have a hunch that
// this is due to a broken parser on the DO side.
if err := recordaudit.TxtNoDoubleQuotes(records); err != nil {
return err
}
// Still needed as of 2021-03-01
return nil
return a.Audit(records)
}
// MaxLengthDO returns and error if the strings are longer than
// MaxLengthDO returns and error if the string is longer than
// permitted by DigitalOcean. Sadly their length limit is
// undocumented. This is a guess.
func MaxLengthDO(records []*models.RecordConfig) error {
func MaxLengthDO(rc *models.RecordConfig) error {
// The total length of all strings can't be longer than 512; and in
// reality must be shorter due to sloppy validation checks.
// https://github.com/StackExchange/dnscontrol/issues/370
@ -47,14 +42,10 @@ func MaxLengthDO(records []*models.RecordConfig) error {
// In other words, they're doing the checking on the API protocol
// encoded data instead of on on the resulting TXT record. Sigh.
for _, rc := range records {
if rc.HasFormatIdenticalToTXT() { // TXT and similar:
if len(rc.GetTargetField()) > 509 {
return fmt.Errorf("encoded txt too long")
}
}
// FIXME(tlim): Try replacing GetTargetField() with (2 + (3*len(rc.TxtStrings) - 1))
}
return nil
}

View File

@ -2,28 +2,23 @@ package dnsimple
import (
"github.com/StackExchange/dnscontrol/v3/models"
"github.com/StackExchange/dnscontrol/v3/pkg/recordaudit"
"github.com/StackExchange/dnscontrol/v3/pkg/rejectif"
)
// AuditRecords returns an error if any records are not
// supportable by this provider.
func AuditRecords(records []*models.RecordConfig) error {
// AuditRecords returns a list of errors corresponding to the records
// that aren't supported by this provider. If all records are
// supported, an empty list is returned.
func AuditRecords(records []*models.RecordConfig) []error {
a := rejectif.Auditor{}
a.Add("TXT", rejectif.TxtHasMultipleSegments) // Last verified 2022-07
//TODO(onlyhavecans) I think we can support multiple strings.
if err := recordaudit.TxtNoMultipleStrings(records); err != nil {
return err
}
if err := recordaudit.TxtNoTrailingSpace(records); err != nil {
return err
} // as of 2022-07
if err := recordaudit.TxtNotEmpty(records); err != nil {
return err
} // as of 2022-07
if err := recordaudit.TxtNoUnpairedDoubleQuotes(records); err != nil {
return err
} // as of 2022-07
return nil
a.Add("TXT", rejectif.TxtHasTrailingSpace) // Last verified 2022-07
a.Add("TXT", rejectif.TxtIsEmpty) // Last verified 2022-07
a.Add("TXT", rejectif.TxtHasUnpairedDoubleQuotes) // Last verified 2022-07
return a.Audit(records)
}

View File

@ -2,16 +2,16 @@ package dnsmadeeasy
import (
"github.com/StackExchange/dnscontrol/v3/models"
"github.com/StackExchange/dnscontrol/v3/pkg/recordaudit"
"github.com/StackExchange/dnscontrol/v3/pkg/rejectif"
)
// AuditRecords returns an error if any records are not
// supportable by this provider.
func AuditRecords(records []*models.RecordConfig) error {
if err := recordaudit.TxtNoDoubleQuotes(records); err != nil {
return err
}
// Still needed as of 2021-03-11
// AuditRecords returns a list of errors corresponding to the records
// that aren't supported by this provider. If all records are
// supported, an empty list is returned.
func AuditRecords(records []*models.RecordConfig) []error {
a := rejectif.Auditor{}
return nil
a.Add("TXT", rejectif.TxtHasDoubleQuotes) // Last verified 2021-03-11
return a.Audit(records)
}

View File

@ -1,11 +1,10 @@
package doh
import (
"github.com/StackExchange/dnscontrol/v3/models"
)
import "github.com/StackExchange/dnscontrol/v3/models"
// AuditRecords returns an error if any records are not
// supportable by this provider.
func AuditRecords(records []*models.RecordConfig) error {
// AuditRecords returns a list of errors corresponding to the records
// that aren't supported by this provider. If all records are
// supported, an empty list is returned.
func AuditRecords(records []*models.RecordConfig) []error {
return nil
}

View File

@ -0,0 +1,10 @@
package domainnameshop
import "github.com/StackExchange/dnscontrol/v3/models"
// AuditRecords returns a list of errors corresponding to the records
// that aren't supported by this provider. If all records are
// supported, an empty list is returned.
func AuditRecords(records []*models.RecordConfig) []error {
return nil
}

View File

@ -4,7 +4,6 @@ import (
"encoding/json"
"fmt"
"github.com/StackExchange/dnscontrol/v3/models"
"github.com/StackExchange/dnscontrol/v3/providers"
)
@ -46,7 +45,7 @@ var features = providers.DocumentationNotes{
func init() {
fns := providers.DspFuncs{
Initializer: newDomainNameShopProvider,
RecordAuditor: auditRecords,
RecordAuditor: AuditRecords,
}
providers.RegisterDomainServiceProviderType("DOMAINNAMESHOP", fns, features)
@ -69,10 +68,6 @@ func newDomainNameShopProvider(conf map[string]string, metadata json.RawMessage)
return api, nil
}
func auditRecords(records []*models.RecordConfig) error {
return nil
}
type domainResponse struct {
ID int `json:"id"`
Domain string `json:"domain"`

View File

@ -1,11 +1,10 @@
package exoscale
import (
"github.com/StackExchange/dnscontrol/v3/models"
)
import "github.com/StackExchange/dnscontrol/v3/models"
// AuditRecords returns an error if any records are not
// supportable by this provider.
func AuditRecords(records []*models.RecordConfig) error {
// AuditRecords returns a list of errors corresponding to the records
// that aren't supported by this provider. If all records are
// supported, an empty list is returned.
func AuditRecords(records []*models.RecordConfig) []error {
return nil
}

View File

@ -1,11 +1,10 @@
package gandiv5
import (
"github.com/StackExchange/dnscontrol/v3/models"
)
import "github.com/StackExchange/dnscontrol/v3/models"
// AuditRecords returns an error if any records are not
// supportable by this provider.
func AuditRecords(records []*models.RecordConfig) error {
// AuditRecords returns a list of errors corresponding to the records
// that aren't supported by this provider. If all records are
// supported, an empty list is returned.
func AuditRecords(records []*models.RecordConfig) []error {
return nil
}

View File

@ -1,11 +1,10 @@
package gcloud
import (
"github.com/StackExchange/dnscontrol/v3/models"
)
import "github.com/StackExchange/dnscontrol/v3/models"
// AuditRecords returns an error if any records are not
// supportable by this provider.
func AuditRecords(records []*models.RecordConfig) error {
// AuditRecords returns a list of errors corresponding to the records
// that aren't supported by this provider. If all records are
// supported, an empty list is returned.
func AuditRecords(records []*models.RecordConfig) []error {
return nil
}

View File

@ -1,11 +1,10 @@
package hedns
import (
"github.com/StackExchange/dnscontrol/v3/models"
)
import "github.com/StackExchange/dnscontrol/v3/models"
// AuditRecords returns an error if any records are not
// supportable by this provider.
func AuditRecords(records []*models.RecordConfig) error {
// AuditRecords returns a list of errors corresponding to the records
// that aren't supported by this provider. If all records are
// supported, an empty list is returned.
func AuditRecords(records []*models.RecordConfig) []error {
return nil
}

View File

@ -1,11 +1,10 @@
package hetzner
import (
"github.com/StackExchange/dnscontrol/v3/models"
)
import "github.com/StackExchange/dnscontrol/v3/models"
// AuditRecords returns an error if any records are not
// supportable by this provider.
func AuditRecords(records []*models.RecordConfig) error {
// AuditRecords returns a list of errors corresponding to the records
// that aren't supported by this provider. If all records are
// supported, an empty list is returned.
func AuditRecords(records []*models.RecordConfig) []error {
return nil
}

View File

@ -2,17 +2,16 @@ package hexonet
import (
"github.com/StackExchange/dnscontrol/v3/models"
"github.com/StackExchange/dnscontrol/v3/pkg/recordaudit"
"github.com/StackExchange/dnscontrol/v3/pkg/rejectif"
)
// AuditRecords returns an error if any records are not
// supportable by this provider.
func AuditRecords(records []*models.RecordConfig) error {
// AuditRecords returns a list of errors corresponding to the records
// that aren't supported by this provider. If all records are
// supported, an empty list is returned.
func AuditRecords(records []*models.RecordConfig) []error {
a := rejectif.Auditor{}
if err := recordaudit.TxtNotEmpty(records); err != nil {
return err
}
// Still needed as of 2021-10-01
a.Add("TXT", rejectif.TxtIsEmpty) // Last verified 2021-10-01
return nil
return a.Audit(records)
}

View File

@ -1,11 +1,10 @@
package hostingde
import (
"github.com/StackExchange/dnscontrol/v3/models"
)
import "github.com/StackExchange/dnscontrol/v3/models"
// AuditRecords returns an error if any records are not
// supportable by this provider.
func AuditRecords(records []*models.RecordConfig) error {
// AuditRecords returns a list of errors corresponding to the records
// that aren't supported by this provider. If all records are
// supported, an empty list is returned.
func AuditRecords(records []*models.RecordConfig) []error {
return nil
}

View File

@ -1,11 +1,10 @@
package internetbs
import (
"github.com/StackExchange/dnscontrol/v3/models"
)
import "github.com/StackExchange/dnscontrol/v3/models"
// AuditRecords returns an error if any records are not
// supportable by this provider.
func AuditRecords(records []*models.RecordConfig) error {
// AuditRecords returns a list of errors corresponding to the records
// that aren't supported by this provider. If all records are
// supported, an empty list is returned.
func AuditRecords(records []*models.RecordConfig) []error {
return nil
}

View File

@ -2,32 +2,22 @@ package inwx
import (
"github.com/StackExchange/dnscontrol/v3/models"
"github.com/StackExchange/dnscontrol/v3/pkg/recordaudit"
"github.com/StackExchange/dnscontrol/v3/pkg/rejectif"
)
// AuditRecords returns an error if any records are not
// supportable by this provider.
func AuditRecords(records []*models.RecordConfig) error {
// AuditRecords returns a list of errors corresponding to the records
// that aren't supported by this provider. If all records are
// supported, an empty list is returned.
func AuditRecords(records []*models.RecordConfig) []error {
a := rejectif.Auditor{}
if err := recordaudit.TxtNoBackticks(records); err != nil {
return err
}
// Still needed as of 2021-03-01
a.Add("TXT", rejectif.TxtHasBackticks) // Last verified 2021-03-01
if err := recordaudit.TxtNoStringsExactlyLen255(records); err != nil {
return err
}
// Still needed as of 2021-03-01
a.Add("TXT", rejectif.TxtIsExactlyLen255) // Last verified 2021-03-01
if err := recordaudit.TxtNoTrailingSpace(records); err != nil {
return err
}
// Still needed as of 2021-03-01
a.Add("TXT", rejectif.TxtHasTrailingSpace) // Last verified 2021-03-01
if err := recordaudit.TxtNotEmpty(records); err != nil {
return err
}
// Still needed as of 2021-03-01
a.Add("TXT", rejectif.TxtIsEmpty) // Last verified 2021-03-01
return nil
return a.Audit(records)
}

View File

@ -1,11 +1,10 @@
package linode
import (
"github.com/StackExchange/dnscontrol/v3/models"
)
import "github.com/StackExchange/dnscontrol/v3/models"
// AuditRecords returns an error if any records are not
// supportable by this provider.
func AuditRecords(records []*models.RecordConfig) error {
// AuditRecords returns a list of errors corresponding to the records
// that aren't supported by this provider. If all records are
// supported, an empty list is returned.
func AuditRecords(records []*models.RecordConfig) []error {
return nil
}

View File

@ -2,41 +2,26 @@ package msdns
import (
"github.com/StackExchange/dnscontrol/v3/models"
"github.com/StackExchange/dnscontrol/v3/pkg/recordaudit"
"github.com/StackExchange/dnscontrol/v3/pkg/rejectif"
)
// AuditRecords returns an error if any records are not
// supportable by this provider.
func AuditRecords(records []*models.RecordConfig) error {
// AuditRecords returns a list of errors corresponding to the records
// that aren't supported by this provider. If all records are
// supported, an empty list is returned.
func AuditRecords(records []*models.RecordConfig) []error {
a := rejectif.Auditor{}
if err := recordaudit.TxtNoMultipleStrings(records); err != nil {
return err
}
// Still needed as of 2021-03-01
a.Add("TXT", rejectif.TxtHasBackticks) // Last verified 2021-03-01
if err := recordaudit.TxtNoStringsLen256orLonger(records); err != nil {
return err
}
a.Add("TXT", rejectif.TxtHasDoubleQuotes) // Last verified 2021-03-01
if err := recordaudit.TxtNotEmpty(records); err != nil {
return err
}
// Still needed as of 2021-03-01
a.Add("TXT", rejectif.TxtHasMultipleSegments) // Last verified 2021-03-01
if err := recordaudit.TxtNoBackticks(records); err != nil {
return err
}
// Still needed as of 2021-03-01
a.Add("TXT", rejectif.TxtHasSingleQuotes) // Last verified 2021-03-01
if err := recordaudit.TxtNoDoubleQuotes(records); err != nil {
return err
}
// Still needed as of 2021-03-01
a.Add("TXT", rejectif.TxtHasSegmentLen256orLonger)
if err := recordaudit.TxtNoSingleQuotes(records); err != nil {
return err
}
// Still needed as of 2021-03-01
a.Add("TXT", rejectif.TxtIsEmpty) // Last verified 2021-03-01
return nil
return a.Audit(records)
}

View File

@ -1,20 +1,13 @@
package msdns
import (
"fmt"
"github.com/StackExchange/dnscontrol/v3/models"
"github.com/StackExchange/dnscontrol/v3/pkg/diff"
"github.com/StackExchange/dnscontrol/v3/pkg/txtutil"
)
// GetDomainCorrections gets existing records, diffs them against existing, and returns corrections.
func (client *msdnsProvider) GenerateDomainCorrections(dc *models.DomainConfig, existing models.Records) ([]*models.Correction, error) {
// Read foundRecords:
foundRecords, err := client.GetZoneRecords(dc.Name)
if err != nil {
return nil, fmt.Errorf("c.GetDNSZoneRecords(%v) failed: %v", dc.Name, err)
}
func (client *msdnsProvider) GenerateDomainCorrections(dc *models.DomainConfig, foundRecords models.Records) ([]*models.Correction, error) {
// Normalize
models.PostProcessRecords(foundRecords)

View File

@ -1,11 +1,10 @@
package namecheap
import (
"github.com/StackExchange/dnscontrol/v3/models"
)
import "github.com/StackExchange/dnscontrol/v3/models"
// AuditRecords returns an error if any records are not
// supportable by this provider.
func AuditRecords(records []*models.RecordConfig) error {
// AuditRecords returns a list of errors corresponding to the records
// that aren't supported by this provider. If all records are
// supported, an empty list is returned.
func AuditRecords(records []*models.RecordConfig) []error {
return nil
}

View File

@ -5,49 +5,41 @@ import (
"strings"
"github.com/StackExchange/dnscontrol/v3/models"
"github.com/StackExchange/dnscontrol/v3/pkg/recordaudit"
"github.com/StackExchange/dnscontrol/v3/pkg/rejectif"
)
// AuditRecords returns an error if any records are not
// supportable by this provider.
func AuditRecords(records []*models.RecordConfig) error {
// AuditRecords returns a list of errors corresponding to the records
// that aren't supported by this provider. If all records are
// supported, an empty list is returned.
func AuditRecords(records []*models.RecordConfig) []error {
a := rejectif.Auditor{}
if err := MaxLengthNDC(records); err != nil {
return err
}
// Still needed as of 2021-03-01
a.Add("TXT", MaxLengthNDC) // Last verified 2021-03-01
if err := recordaudit.TxtNotEmpty(records); err != nil {
return err
}
// Still needed as of 2021-03-01
a.Add("TXT", rejectif.TxtIsEmpty) // Last verified 2021-03-01
return nil
return a.Audit(records)
}
// MaxLengthNDC returns and error if the sum of the strings
// are longer than permitted by NDC. Sadly their
// length limit is undocumented. This seems to work.
func MaxLengthNDC(records []*models.RecordConfig) error {
for _, rc := range records {
func MaxLengthNDC(rc *models.RecordConfig) error {
if len(rc.TxtStrings) == 0 {
return nil
}
if rc.HasFormatIdenticalToTXT() { // TXT and similar:
// Sum the length of the segments:
sum := 0
sum := 2 // Count the start and end quote.
// Add the length of each segment.
for _, segment := range rc.TxtStrings {
sum += len(segment) // The length of each segment
sum += strings.Count(segment, `"`) // Add 1 for any char to be escaped
}
// Add the overhead of quoting them:
n := len(rc.TxtStrings)
if n > 0 {
sum += 2 + 3*(n-1) // Start and end double-quotes, plus `" "` between each segment.
}
// Add 3 (quote space quote) for each interior join.
sum += 3 * (len(rc.TxtStrings) - 1)
if sum > 512 {
return fmt.Errorf("encoded txt too long")
}
}
}
return nil
}

View File

@ -2,12 +2,16 @@ package netcup
import (
"github.com/StackExchange/dnscontrol/v3/models"
"github.com/StackExchange/dnscontrol/v3/pkg/recordaudit"
"github.com/StackExchange/dnscontrol/v3/pkg/rejectif"
)
// AuditRecords returns an error if any records are not
// supportable by this provider.
func AuditRecords(records []*models.RecordConfig) error {
return recordaudit.TxtNotEmpty(records)
// Still needed as of 2021-03-01
// AuditRecords returns a list of errors corresponding to the records
// that aren't supported by this provider. If all records are
// supported, an empty list is returned.
func AuditRecords(records []*models.RecordConfig) []error {
a := rejectif.Auditor{}
a.Add("TXT", rejectif.TxtIsEmpty) // Last verified 2021-03-01
return a.Audit(records)
}

View File

@ -2,17 +2,16 @@ package ns1
import (
"github.com/StackExchange/dnscontrol/v3/models"
"github.com/StackExchange/dnscontrol/v3/pkg/recordaudit"
"github.com/StackExchange/dnscontrol/v3/pkg/rejectif"
)
// AuditRecords returns an error if any records are not
// supportable by this provider.
func AuditRecords(records []*models.RecordConfig) error {
// AuditRecords returns a list of errors corresponding to the records
// that aren't supported by this provider. If all records are
// supported, an empty list is returned.
func AuditRecords(records []*models.RecordConfig) []error {
a := rejectif.Auditor{}
if err := recordaudit.TxtNoMultipleStrings(records); err != nil {
return err
}
// Still needed as of 2021-03-01
a.Add("TXT", rejectif.TxtHasMultipleSegments)
return nil
return a.Audit(records)
}

View File

@ -1,11 +1,10 @@
package octodns
import (
"github.com/StackExchange/dnscontrol/v3/models"
)
import "github.com/StackExchange/dnscontrol/v3/models"
// AuditRecords returns an error if any records are not
// supportable by this provider.
func AuditRecords(records []*models.RecordConfig) error {
// AuditRecords returns a list of errors corresponding to the records
// that aren't supported by this provider. If all records are
// supported, an empty list is returned.
func AuditRecords(records []*models.RecordConfig) []error {
return nil
}

View File

@ -1,11 +1,10 @@
package opensrs
import (
"github.com/StackExchange/dnscontrol/v3/models"
)
import "github.com/StackExchange/dnscontrol/v3/models"
// AuditRecords returns an error if any records are not
// supportable by this provider.
func AuditRecords(records []*models.RecordConfig) error {
// AuditRecords returns a list of errors corresponding to the records
// that aren't supported by this provider. If all records are
// supported, an empty list is returned.
func AuditRecords(records []*models.RecordConfig) []error {
return nil
}

View File

@ -1,11 +1,10 @@
package oracle
import (
"github.com/StackExchange/dnscontrol/v3/models"
)
import "github.com/StackExchange/dnscontrol/v3/models"
// AuditRecords returns an error if any records are not
// supportable by this provider.
func AuditRecords(records []*models.RecordConfig) error {
// AuditRecords returns a list of errors corresponding to the records
// that aren't supported by this provider. If all records are
// supported, an empty list is returned.
func AuditRecords(records []*models.RecordConfig) []error {
return nil
}

View File

@ -1,11 +1,10 @@
package ovh
import (
"github.com/StackExchange/dnscontrol/v3/models"
)
import "github.com/StackExchange/dnscontrol/v3/models"
// AuditRecords returns an error if any records are not
// supportable by this provider.
func AuditRecords(records []*models.RecordConfig) error {
// AuditRecords returns a list of errors corresponding to the records
// that aren't supported by this provider. If all records are
// supported, an empty list is returned.
func AuditRecords(records []*models.RecordConfig) []error {
return nil
}

View File

@ -1,11 +1,10 @@
package packetframe
import (
"github.com/StackExchange/dnscontrol/v3/models"
)
import "github.com/StackExchange/dnscontrol/v3/models"
// AuditRecords returns an error if any records are not
// supportable by this provider.
func AuditRecords(records []*models.RecordConfig) error {
// AuditRecords returns a list of errors corresponding to the records
// that aren't supported by this provider. If all records are
// supported, an empty list is returned.
func AuditRecords(records []*models.RecordConfig) []error {
return nil
}

View File

@ -1,11 +1,10 @@
package powerdns
import (
"github.com/StackExchange/dnscontrol/v3/models"
)
import "github.com/StackExchange/dnscontrol/v3/models"
// AuditRecords returns an error if any records are not
// supportable by this provider.
func AuditRecords(records []*models.RecordConfig) error {
// AuditRecords returns a list of errors corresponding to the records
// that aren't supported by this provider. If all records are
// supported, an empty list is returned.
func AuditRecords(records []*models.RecordConfig) []error {
return nil
}

View File

@ -41,9 +41,9 @@ var RegistrarTypes = map[string]RegistrarInitializer{}
type DspInitializer func(map[string]string, json.RawMessage) (DNSServiceProvider, error)
// RecordAuditor is a function that verifies that all the records
// are supportable by this provider. It returns an error related to
// the first record that this provider can not support.
type RecordAuditor func([]*models.RecordConfig) error
// are supportable by this provider. It returns a list of errors
// detailing records that this provider can not support.
type RecordAuditor func([]*models.RecordConfig) []error
// DspFuncs lists functions registered with a provider.
type DspFuncs struct {
@ -132,13 +132,13 @@ func beCompatible(n string, config map[string]string) (string, error) {
}
// AuditRecords calls the RecordAudit function for a provider.
func AuditRecords(dType string, rcs models.Records) error {
func AuditRecords(dType string, rcs models.Records) []error {
p, ok := DNSProviderTypes[dType]
if !ok {
return fmt.Errorf("unknown DNS service provider type: %q", dType)
return []error{fmt.Errorf("unknown DNS service provider type: %q", dType)}
}
if p.RecordAuditor == nil {
return fmt.Errorf("DNS service provider type %q has no RecordAuditor", dType)
return []error{fmt.Errorf("DNS service provider type %q has no RecordAuditor", dType)}
}
return p.RecordAuditor(rcs)
}

View File

@ -1,11 +1,10 @@
package route53
import (
"github.com/StackExchange/dnscontrol/v3/models"
)
import "github.com/StackExchange/dnscontrol/v3/models"
// AuditRecords returns an error if any records are not
// supportable by this provider.
func AuditRecords(records []*models.RecordConfig) error {
// AuditRecords returns a list of errors corresponding to the records
// that aren't supported by this provider. If all records are
// supported, an empty list is returned.
func AuditRecords(records []*models.RecordConfig) []error {
return nil
}

View File

@ -2,24 +2,20 @@ package rwth
import (
"github.com/StackExchange/dnscontrol/v3/models"
"github.com/StackExchange/dnscontrol/v3/pkg/recordaudit"
"github.com/StackExchange/dnscontrol/v3/pkg/rejectif"
)
// AuditRecords returns an error if any records are not
// supportable by this provider.
func AuditRecords(records []*models.RecordConfig) error {
// AuditRecords returns a list of errors corresponding to the records
// that aren't supported by this provider. If all records are
// supported, an empty list is returned.
func AuditRecords(records []*models.RecordConfig) []error {
a := rejectif.Auditor{}
if err := recordaudit.TxtNoMultipleStrings(records); err != nil {
return err
}
a.Add("TXT", rejectif.TxtHasMultipleSegments)
if err := recordaudit.TxtNoTrailingSpace(records); err != nil {
return err
}
a.Add("TXT", rejectif.TxtHasTrailingSpace)
if err := recordaudit.TxtNotEmpty(records); err != nil {
return err
}
a.Add("TXT", rejectif.TxtIsEmpty)
return nil
return a.Audit(records)
}

View File

@ -1,11 +1,10 @@
package softlayer
import (
"github.com/StackExchange/dnscontrol/v3/models"
)
import "github.com/StackExchange/dnscontrol/v3/models"
// AuditRecords returns an error if any records are not
// supportable by this provider.
func AuditRecords(records []*models.RecordConfig) error {
// AuditRecords returns a list of errors corresponding to the records
// that aren't supported by this provider. If all records are
// supported, an empty list is returned.
func AuditRecords(records []*models.RecordConfig) []error {
return nil
}

View File

@ -1,11 +1,10 @@
package transip
import (
"github.com/StackExchange/dnscontrol/v3/models"
)
import "github.com/StackExchange/dnscontrol/v3/models"
// AuditRecords returns an error if any records are not
// supportable by this provider.
func AuditRecords(records []*models.RecordConfig) error {
// AuditRecords returns a list of errors corresponding to the records
// that aren't supported by this provider. If all records are
// supported, an empty list is returned.
func AuditRecords(records []*models.RecordConfig) []error {
return nil
}

View File

@ -2,23 +2,20 @@ package vultr
import (
"github.com/StackExchange/dnscontrol/v3/models"
"github.com/StackExchange/dnscontrol/v3/pkg/recordaudit"
"github.com/StackExchange/dnscontrol/v3/pkg/rejectif"
)
// AuditRecords returns an error if any records are not
// supportable by this provider.
func AuditRecords(records []*models.RecordConfig) error {
// AuditRecords returns a list of errors corresponding to the records
// that aren't supported by this provider. If all records are
// supported, an empty list is returned.
func AuditRecords(records []*models.RecordConfig) []error {
a := rejectif.Auditor{}
// TODO(tlim) Needs investigation. Could be a dnscontrol issue or
a.Add("TXT", rejectif.TxtHasDoubleQuotes) // Last verified 2021-03-02
// Needs investigation. Could be a dnscontrol issue or
// the provider doesn't support double quotes.
if err := recordaudit.TxtNoDoubleQuotes(records); err != nil {
return err
}
// Still needed as of 2021-03-02
if err := recordaudit.TxtNoMultipleStrings(records); err != nil {
return err
}
a.Add("TXT", rejectif.TxtHasMultipleSegments)
return nil
return a.Audit(records)
}