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

PERFORMANCE: Refactor auditrecords.go to loop only once #1570 (#1658)

* stash

* Use rejectif idea

* rename

* wip!

* Convert old systems to new

* fixup!

* fix typo
This commit is contained in:
Tom Limoncelli
2022-08-11 17:24:47 -04:00
committed by GitHub
parent 443b99aa59
commit 31723ad146
52 changed files with 497 additions and 569 deletions

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