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

"Target" RecordConfig should not be exported (#1061)

* Unexport RecordConfig.Target
* Fix tests
* HEDNS: Fix usage of target field to resolve TXT handling (#1067)

Co-authored-by: Robert Blenkinsopp <robert@blenkinsopp.net>
This commit is contained in:
Tom Limoncelli
2021-03-04 18:58:23 -05:00
committed by GitHub
parent 3e5e976766
commit 21e85e6528
22 changed files with 454 additions and 219 deletions

View File

@@ -9,7 +9,7 @@ func TestRR(t *testing.T) {
Type: "A",
Name: "foo",
NameFQDN: "foo.example.com",
Target: "1.2.3.4",
target: "1.2.3.4",
TTL: 0,
MxPreference: 0,
}
@@ -23,7 +23,7 @@ func TestRR(t *testing.T) {
Type: "CAA",
Name: "@",
NameFQDN: "example.com",
Target: "mailto:test@example.com",
target: "mailto:test@example.com",
TTL: 300,
CaaTag: "iodef",
CaaFlag: 1,
@@ -38,7 +38,7 @@ func TestRR(t *testing.T) {
Type: "TLSA",
Name: "@",
NameFQDN: "_443._tcp.example.com",
Target: "abcdef0123456789",
target: "abcdef0123456789",
TTL: 300,
TlsaUsage: 0,
TlsaSelector: 0,
@@ -53,8 +53,8 @@ func TestRR(t *testing.T) {
func TestDowncase(t *testing.T) {
dc := DomainConfig{Records: Records{
&RecordConfig{Type: "MX", Name: "lower", Target: "targetmx"},
&RecordConfig{Type: "MX", Name: "UPPER", Target: "TARGETMX"},
&RecordConfig{Type: "MX", Name: "lower", target: "targetmx"},
&RecordConfig{Type: "MX", Name: "UPPER", target: "TARGETMX"},
}}
downcase(dc.Records)
if !dc.Records.HasRecordTypeName("MX", "lower") {

View File

@@ -3,6 +3,7 @@ package models
import (
"fmt"
"github.com/qdm12/reprint"
"golang.org/x/net/idna"
)
@@ -34,21 +35,19 @@ type DomainConfig struct {
// Copy returns a deep copy of the DomainConfig.
func (dc *DomainConfig) Copy() (*DomainConfig, error) {
newDc := &DomainConfig{}
// provider instances are interfaces that gob hates if you don't register them.
// and the specific types are not gob encodable since nothing is exported.
// should find a better solution for this now.
//
// current strategy: remove everything, gob copy it. Then set both to stored copy.
reg := dc.RegistrarInstance
dnsps := dc.DNSProviderInstances
dc.RegistrarInstance = nil
dc.DNSProviderInstances = nil
err := copyObj(dc, newDc)
dc.RegistrarInstance = reg
newDc.RegistrarInstance = reg
dc.DNSProviderInstances = dnsps
newDc.DNSProviderInstances = dnsps
err := reprint.FromTo(dc, newDc) // Deep copy
return newDc, err
// NB(tlim): The old version of this copied the structure by gob-encoding
// and decoding it. gob doesn't like the dc.RegisterInstance or
// dc.DNSProviderInstances fields, so we saved a temporary copy of those,
// nil'ed out the original, did the gob copy, and then manually copied those
// fields using the temp variables we saved. It looked like:
//reg, dnsps := dc.RegistrarInstance, dc.DNSProviderInstances
//dc.RegistrarInstance, dc.DNSProviderInstances = nil, nil
// (perform the copy)
//dc.RegistrarInstance, dc.DNSProviderInstances = reg, dnsps
//newDc.RegistrarInstance, newDc.DNSProviderInstances = reg, dnsps
}
// Filter removes all records that don't match the filter f.

View File

@@ -1,13 +1,16 @@
package models
import (
"encoding/json"
"fmt"
"log"
"sort"
"strings"
"github.com/jinzhu/copier"
"github.com/miekg/dns"
"github.com/miekg/dns/dnsutil"
"github.com/qdm12/reprint"
)
// RecordConfig stores a DNS record.
@@ -54,7 +57,7 @@ import (
// NOTE: Eventually we will unexport Name/NameFQDN. Please start using
// the setters (SetLabel/SetLabelFromFQDN) and getters (GetLabel/GetLabelFQDN).
// as they will always work.
// Target:
// target:
// This is the host or IP address of the record, with
// the other related parameters (weight, priority, etc.) stored in individual
// fields.
@@ -69,13 +72,16 @@ import (
// rec.Label() == "@" // Is this record at the apex?
//
type RecordConfig struct {
Type string `json:"type"` // All caps rtype name.
Name string `json:"name"` // The short name. See above.
SubDomain string `json:"subdomain,omitempty"`
NameFQDN string `json:"-"` // Must end with ".$origin". See above.
Target string `json:"target"` // If a name, must end with "."
TTL uint32 `json:"ttl,omitempty"`
Metadata map[string]string `json:"meta,omitempty"`
Type string `json:"type"` // All caps rtype name.
Name string `json:"name"` // The short name. See above.
SubDomain string `json:"subdomain,omitempty"`
NameFQDN string `json:"-"` // Must end with ".$origin". See above.
target string // If a name, must end with "."
TTL uint32 `json:"ttl,omitempty"`
Metadata map[string]string `json:"meta,omitempty"`
Original interface{} `json:"-"` // Store pointer to provider-specific record object. Used in diffing.
// If you add a field to this struct, also add it to the list on MarshalJSON.
MxPreference uint16 `json:"mxpreference,omitempty"`
SrvPriority uint16 `json:"srvpriority,omitempty"`
SrvWeight uint16 `json:"srvweight,omitempty"`
@@ -105,14 +111,100 @@ type RecordConfig struct {
TxtStrings []string `json:"txtstrings,omitempty"` // TxtStrings stores all strings (including the first). Target stores only the first one.
R53Alias map[string]string `json:"r53_alias,omitempty"`
AzureAlias map[string]string `json:"azure_alias,omitempty"`
}
Original interface{} `json:"-"` // Store pointer to provider-specific record object. Used in diffing.
// MarshalJSON marshals RecordConfig.
func (rc *RecordConfig) MarshalJSON() ([]byte, error) {
recj := &struct {
RecordConfig
Target string `json:"target"`
}{
RecordConfig: *rc,
Target: rc.GetTargetField(),
}
j, err := json.Marshal(*recj)
if err != nil {
return nil, err
}
return j, nil
}
// UnmarshalJSON unmarshals RecordConfig.
func (rc *RecordConfig) UnmarshalJSON(b []byte) error {
recj := &struct {
Target string `json:"target"`
Type string `json:"type"` // All caps rtype name.
Name string `json:"name"` // The short name. See above.
SubDomain string `json:"subdomain,omitempty"`
NameFQDN string `json:"-"` // Must end with ".$origin". See above.
target string // If a name, must end with "."
TTL uint32 `json:"ttl,omitempty"`
Metadata map[string]string `json:"meta,omitempty"`
Original interface{} `json:"-"` // Store pointer to provider-specific record object. Used in diffing.
MxPreference uint16 `json:"mxpreference,omitempty"`
SrvPriority uint16 `json:"srvpriority,omitempty"`
SrvWeight uint16 `json:"srvweight,omitempty"`
SrvPort uint16 `json:"srvport,omitempty"`
CaaTag string `json:"caatag,omitempty"`
CaaFlag uint8 `json:"caaflag,omitempty"`
DsKeyTag uint16 `json:"dskeytag,omitempty"`
DsAlgorithm uint8 `json:"dsalgorithm,omitempty"`
DsDigestType uint8 `json:"dsdigesttype,omitempty"`
DsDigest string `json:"dsdigest,omitempty"`
NaptrOrder uint16 `json:"naptrorder,omitempty"`
NaptrPreference uint16 `json:"naptrpreference,omitempty"`
NaptrFlags string `json:"naptrflags,omitempty"`
NaptrService string `json:"naptrservice,omitempty"`
NaptrRegexp string `json:"naptrregexp,omitempty"`
SshfpAlgorithm uint8 `json:"sshfpalgorithm,omitempty"`
SshfpFingerprint uint8 `json:"sshfpfingerprint,omitempty"`
SoaMbox string `json:"soambox,omitempty"`
SoaSerial uint32 `json:"soaserial,omitempty"`
SoaRefresh uint32 `json:"soarefresh,omitempty"`
SoaRetry uint32 `json:"soaretry,omitempty"`
SoaExpire uint32 `json:"soaexpire,omitempty"`
SoaMinttl uint32 `json:"soaminttl,omitempty"`
TlsaUsage uint8 `json:"tlsausage,omitempty"`
TlsaSelector uint8 `json:"tlsaselector,omitempty"`
TlsaMatchingType uint8 `json:"tlsamatchingtype,omitempty"`
TxtStrings []string `json:"txtstrings,omitempty"` // TxtStrings stores all strings (including the first). Target stores only the first one.
R53Alias map[string]string `json:"r53_alias,omitempty"`
AzureAlias map[string]string `json:"azure_alias,omitempty"`
// NB(tlim): If anyone can figure out how to do this without listing all
// the fields, please let us know!
}{}
if err := json.Unmarshal(b, &recj); err != nil {
return err
}
// Copy the exported fields.
copier.CopyWithOption(&rc, &recj, copier.Option{IgnoreEmpty: true, DeepCopy: true})
// Set each unexported field.
rc.SetTarget(recj.Target)
// Some sanity checks:
if recj.Type != rc.Type {
panic("DEBUG: TYPE NOT COPIED\n")
}
if recj.Type == "" {
panic("DEBUG: TYPE BLANK\n")
}
if recj.Name != rc.Name {
panic("DEBUG: NAME NOT COPIED\n")
}
return nil
}
// Copy returns a deep copy of a RecordConfig.
func (rc *RecordConfig) Copy() (*RecordConfig, error) {
newR := &RecordConfig{}
err := copyObj(rc, newR)
// Copy the exported fields.
err := reprint.FromTo(rc, newR) // Deep copy
// Set each unexported field.
newR.target = rc.target
return newR, err
}
@@ -129,7 +221,9 @@ func (rc *RecordConfig) SetLabel(short, origin string) {
panic(fmt.Errorf("origin (%s) is not supposed to end with a dot", origin))
}
if strings.HasSuffix(short, ".") {
panic(fmt.Errorf("short (%s) is not supposed to end with a dot", origin))
if short != "**current-domain**" {
panic(fmt.Errorf("short (%s) is not supposed to end with a dot", origin))
}
}
// TODO(tlim): We should add more validation here or in a separate validation
@@ -198,7 +292,7 @@ func (rc *RecordConfig) GetLabelFQDN() string {
func (rc *RecordConfig) ToDiffable(extraMaps ...map[string]string) string {
content := fmt.Sprintf("%v ttl=%d", rc.GetTargetCombined(), rc.TTL)
if rc.Type == "SOA" {
content = fmt.Sprintf("%s %v %d %d %d %d ttl=%d", rc.Target, rc.SoaMbox, rc.SoaRefresh, rc.SoaRetry, rc.SoaExpire, rc.SoaMinttl, rc.TTL)
content = fmt.Sprintf("%s %v %d %d %d %d ttl=%d", rc.target, rc.SoaMbox, rc.SoaRefresh, rc.SoaRetry, rc.SoaExpire, rc.SoaMinttl, rc.TTL)
// SoaSerial is not used in comparison
}
for _, valueMap := range extraMaps {
@@ -406,13 +500,13 @@ func downcase(recs []*RecordConfig) {
switch r.Type { // #rtype_variations
case "ANAME", "CNAME", "DS", "MX", "NS", "PTR", "NAPTR", "SRV":
// These record types have a target that is case insensitive, so we downcase it.
r.Target = strings.ToLower(r.Target)
r.target = strings.ToLower(r.target)
case "A", "AAAA", "ALIAS", "CAA", "IMPORT_TRANSFORM", "TLSA", "TXT", "SSHFP", "CF_REDIRECT", "CF_TEMP_REDIRECT":
// These record types have a target that is case sensitive, or is an IP address. We leave them alone.
// Do nothing.
case "SOA":
if r.Target != "DEFAULT_NOT_SET." {
r.Target = strings.ToLower(r.Target) // .Target stores the Ns
if r.target != "DEFAULT_NOT_SET." {
r.target = strings.ToLower(r.target) // .target stores the Ns
}
if r.SoaMbox != "DEFAULT_NOT_SET." {
r.SoaMbox = strings.ToLower(r.SoaMbox)

View File

@@ -1,6 +1,9 @@
package models
import "testing"
import (
"reflect"
"testing"
)
func TestHasRecordTypeName(t *testing.T) {
x := &RecordConfig{
@@ -49,3 +52,184 @@ func TestKey(t *testing.T) {
}
}
}
func TestRecordConfig_Copy(t *testing.T) {
type fields struct {
Type string
Name string
SubDomain string
NameFQDN string
target string
TTL uint32
Metadata map[string]string
MxPreference uint16
SrvPriority uint16
SrvWeight uint16
SrvPort uint16
CaaTag string
CaaFlag uint8
DsKeyTag uint16
DsAlgorithm uint8
DsDigestType uint8
DsDigest string
NaptrOrder uint16
NaptrPreference uint16
NaptrFlags string
NaptrService string
NaptrRegexp string
SshfpAlgorithm uint8
SshfpFingerprint uint8
SoaMbox string
SoaSerial uint32
SoaRefresh uint32
SoaRetry uint32
SoaExpire uint32
SoaMinttl uint32
TlsaUsage uint8
TlsaSelector uint8
TlsaMatchingType uint8
TxtStrings []string
R53Alias map[string]string
AzureAlias map[string]string
Original interface{}
}
tests := []struct {
name string
fields fields
want *RecordConfig
wantErr bool
}{
{
name: "only",
fields: fields{
Type: "type",
Name: "name",
SubDomain: "sub",
NameFQDN: "namef",
target: "targette",
TTL: 12345,
Metadata: map[string]string{"me": "ah", "da": "ta"},
MxPreference: 123,
SrvPriority: 223,
SrvWeight: 345,
SrvPort: 456,
CaaTag: "caata",
CaaFlag: 100,
DsKeyTag: 12341,
DsAlgorithm: 99,
DsDigestType: 98,
DsDigest: "dsdig",
NaptrOrder: 10000,
NaptrPreference: 12220,
NaptrFlags: "naptrfl",
NaptrService: "naptrser",
NaptrRegexp: "naptrreg",
SshfpAlgorithm: 4,
SshfpFingerprint: 5,
SoaMbox: "soambox",
SoaSerial: 456789,
SoaRefresh: 192000,
SoaRetry: 293293,
SoaExpire: 3434343,
SoaMinttl: 34234324,
TlsaUsage: 1,
TlsaSelector: 2,
TlsaMatchingType: 3,
TxtStrings: []string{"one", "two", "three"},
R53Alias: map[string]string{"a": "eh", "b": "bee"},
AzureAlias: map[string]string{"az": "az", "ure": "your"},
//Original interface{},
},
want: &RecordConfig{
Type: "type",
Name: "name",
SubDomain: "sub",
NameFQDN: "namef",
target: "targette",
TTL: 12345,
Metadata: map[string]string{"me": "ah", "da": "ta"},
MxPreference: 123,
SrvPriority: 223,
SrvWeight: 345,
SrvPort: 456,
CaaTag: "caata",
CaaFlag: 100,
DsKeyTag: 12341,
DsAlgorithm: 99,
DsDigestType: 98,
DsDigest: "dsdig",
NaptrOrder: 10000,
NaptrPreference: 12220,
NaptrFlags: "naptrfl",
NaptrService: "naptrser",
NaptrRegexp: "naptrreg",
SshfpAlgorithm: 4,
SshfpFingerprint: 5,
SoaMbox: "soambox",
SoaSerial: 456789,
SoaRefresh: 192000,
SoaRetry: 293293,
SoaExpire: 3434343,
SoaMinttl: 34234324,
TlsaUsage: 1,
TlsaSelector: 2,
TlsaMatchingType: 3,
TxtStrings: []string{"one", "two", "three"},
R53Alias: map[string]string{"a": "eh", "b": "bee"},
AzureAlias: map[string]string{"az": "az", "ure": "your"},
//Original interface{},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
rc := &RecordConfig{
Type: tt.fields.Type,
Name: tt.fields.Name,
SubDomain: tt.fields.SubDomain,
NameFQDN: tt.fields.NameFQDN,
target: tt.fields.target,
TTL: tt.fields.TTL,
Metadata: tt.fields.Metadata,
MxPreference: tt.fields.MxPreference,
SrvPriority: tt.fields.SrvPriority,
SrvWeight: tt.fields.SrvWeight,
SrvPort: tt.fields.SrvPort,
CaaTag: tt.fields.CaaTag,
CaaFlag: tt.fields.CaaFlag,
DsKeyTag: tt.fields.DsKeyTag,
DsAlgorithm: tt.fields.DsAlgorithm,
DsDigestType: tt.fields.DsDigestType,
DsDigest: tt.fields.DsDigest,
NaptrOrder: tt.fields.NaptrOrder,
NaptrPreference: tt.fields.NaptrPreference,
NaptrFlags: tt.fields.NaptrFlags,
NaptrService: tt.fields.NaptrService,
NaptrRegexp: tt.fields.NaptrRegexp,
SshfpAlgorithm: tt.fields.SshfpAlgorithm,
SshfpFingerprint: tt.fields.SshfpFingerprint,
SoaMbox: tt.fields.SoaMbox,
SoaSerial: tt.fields.SoaSerial,
SoaRefresh: tt.fields.SoaRefresh,
SoaRetry: tt.fields.SoaRetry,
SoaExpire: tt.fields.SoaExpire,
SoaMinttl: tt.fields.SoaMinttl,
TlsaUsage: tt.fields.TlsaUsage,
TlsaSelector: tt.fields.TlsaSelector,
TlsaMatchingType: tt.fields.TlsaMatchingType,
TxtStrings: tt.fields.TxtStrings,
R53Alias: tt.fields.R53Alias,
AzureAlias: tt.fields.AzureAlias,
Original: tt.fields.Original,
}
got, err := rc.Copy()
if (err != nil) != tt.wantErr {
t.Errorf("RecordConfig.Copy() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("RecordConfig.Copy() = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -8,7 +8,7 @@ import (
"github.com/miekg/dns"
)
/* .Target is kind of a mess.
/* .target is kind of a mess.
For simple rtypes it is the record's value. (i.e. for an A record
it is the IP address).
For complex rtypes (like an MX record has a preference and a value)
@@ -26,7 +26,7 @@ func (rc *RecordConfig) GetTargetField() string {
if rc.HasFormatIdenticalToTXT() {
return strings.Join(rc.TxtStrings, "")
}
return rc.Target
return rc.target
}
// // GetTargetSingle returns the target for types that have a single value target
@@ -35,15 +35,15 @@ func (rc *RecordConfig) GetTargetField() string {
// if rc.Type == "MX" || rc.Type == "SRV" || rc.Type == "CAA" || rc.Type == "TLSA" || rc.Type == "TXT" {
// panic("TargetSingle called on a type with a multi-parameter rtype.")
// }
// return rc.Target
// return rc.target
// }
// GetTargetIP returns the net.IP stored in Target.
// GetTargetIP returns the net.IP stored in .target.
func (rc *RecordConfig) GetTargetIP() net.IP {
if rc.Type != "A" && rc.Type != "AAAA" {
panic(fmt.Errorf("GetTargetIP called on an inappropriate rtype (%s)", rc.Type))
}
return net.ParseIP(rc.Target)
return net.ParseIP(rc.target)
}
// GetTargetCombined returns a string with the various fields combined.
@@ -54,15 +54,15 @@ func (rc *RecordConfig) GetTargetCombined() string {
switch rc.Type { // #rtype_variations
case "R53_ALIAS":
// Differentiate between multiple R53_ALIASs on the same label.
return fmt.Sprintf("%s atype=%s zone_id=%s", rc.Target, rc.R53Alias["type"], rc.R53Alias["zone_id"])
return fmt.Sprintf("%s atype=%s zone_id=%s", rc.target, rc.R53Alias["type"], rc.R53Alias["zone_id"])
case "AZURE_ALIAS":
// Differentiate between multiple AZURE_ALIASs on the same label.
return fmt.Sprintf("%s atype=%s", rc.Target, rc.AzureAlias["type"])
return fmt.Sprintf("%s atype=%s", rc.target, rc.AzureAlias["type"])
case "SOA":
return fmt.Sprintf("%s %v %d %d %d %d %d", rc.Target, rc.SoaMbox, rc.SoaSerial, rc.SoaRefresh, rc.SoaRetry, rc.SoaExpire, rc.SoaMinttl)
return fmt.Sprintf("%s %v %d %d %d %d %d", rc.target, rc.SoaMbox, rc.SoaSerial, rc.SoaRefresh, rc.SoaRetry, rc.SoaExpire, rc.SoaMinttl)
default:
// Just return the target.
return rc.Target
return rc.target
}
}
@@ -87,7 +87,7 @@ func (rc *RecordConfig) GetTargetSortable() string {
// GetTargetDebug returns a string with the various fields spelled out.
func (rc *RecordConfig) GetTargetDebug() string {
content := fmt.Sprintf("%s %s %s %d", rc.Type, rc.NameFQDN, rc.Target, rc.TTL)
content := fmt.Sprintf("%s %s %s %d", rc.Type, rc.NameFQDN, rc.target, rc.TTL)
switch rc.Type { // #rtype_variations
case "A", "AAAA", "CNAME", "NS", "PTR", "TXT":
// Nothing special.
@@ -98,7 +98,7 @@ func (rc *RecordConfig) GetTargetDebug() string {
case "MX":
content += fmt.Sprintf(" pref=%d", rc.MxPreference)
case "SOA":
content = fmt.Sprintf("%s ns=%v mbox=%v serial=%v refresh=%v retry=%v expire=%v minttl=%v", rc.Type, rc.Target, rc.SoaMbox, rc.SoaSerial, rc.SoaRefresh, rc.SoaRetry, rc.SoaExpire, rc.SoaMinttl)
content = fmt.Sprintf("%s ns=%v mbox=%v serial=%v refresh=%v retry=%v expire=%v minttl=%v", rc.Type, rc.target, rc.SoaMbox, rc.SoaSerial, rc.SoaRefresh, rc.SoaRetry, rc.SoaExpire, rc.SoaMinttl)
case "SRV":
content += fmt.Sprintf(" srvpriority=%d srvweight=%d srvport=%d", rc.SrvPriority, rc.SrvWeight, rc.SrvPort)
case "SSHFP":
@@ -124,7 +124,7 @@ func (rc *RecordConfig) GetTargetDebug() string {
// SetTarget sets the target, assuming that the rtype is appropriate.
func (rc *RecordConfig) SetTarget(target string) error {
rc.Target = target
rc.target = target
return nil
}

View File

@@ -1,16 +0,0 @@
package models
import (
"bytes"
"encoding/gob"
)
func copyObj(input interface{}, output interface{}) error {
buf := &bytes.Buffer{}
enc := gob.NewEncoder(buf)
dec := gob.NewDecoder(buf)
if err := enc.Encode(input); err != nil {
return err
}
return dec.Decode(output)
}