permit works

This commit is contained in:
Thomas Limoncelli 2025-11-24 19:30:50 -05:00
parent c2a64002fe
commit cc8884824d
No known key found for this signature in database
7 changed files with 137 additions and 59 deletions

View file

@ -22,9 +22,10 @@ func Test_whichZonesToProcess(t *testing.T) {
dcTaggedEmpty,
}
// for _, dc := range allDC {
// dc.UpdateSplitHorizonNames()
// }
for _, dc := range allDC {
//dc.UpdateSplitHorizonNames()
dc.PostProcess()
}
type args struct {
dc []*models.DomainConfig
@ -158,6 +159,7 @@ func Test_whichZonesToProcess(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Logf("whichZonesToProcess() %s filter=%v", tt.name, tt.args.filter)
got := whichZonesToProcess(tt.args.dc, tt.args.filter)
if len(got) != len(tt.want) {
t.Errorf("whichZonesToProcess() %s: %s", tt.name, tt.why)

View file

@ -69,7 +69,8 @@ func (dc *DomainConfig) PostProcess() {
}
// Turn the user-supplied name into the fixed forms.
dc.Tag, dc.NameRaw, dc.Name, dc.NameUnicode, dc.UniqueName = domaintags.MakeDomainFixForms(dc.Name)
ff := domaintags.MakeDomainFixForms(dc.Name)
dc.Tag, dc.NameRaw, dc.Name, dc.NameUnicode, dc.UniqueName = ff.Tag, ff.NameRaw, ff.NameIDN, ff.NameUnicode, ff.UniqueName
// Store the split horizon info in metadata for backward compatibility.
dc.Metadata[DomainTag] = dc.Tag
@ -90,7 +91,6 @@ func (dc *DomainConfig) GetUniqueName() (uniquename string) {
// // (uniquename and tag) based on name.
// func (dc *DomainConfig) UpdateSplitHorizonNames() {
// // This should probably be done elsewhere (maybe where we first ingest a domain).
// // Convert all domain names to punycode.
// for _, domain := range config.Domains {

View file

@ -6,20 +6,36 @@ import (
"golang.org/x/net/idna"
)
// DomainFixedForms stores the various fixed forms of a domain name and tag.
type DomainFixedForms struct {
NameRaw string // "originalinput.com" (name as input by the user, lowercased (no tag))
NameIDN string // "punycode.com"
NameUnicode string // "unicode.com"
UniqueName string // "punycode.com!tag"
Tag string // The tag portion of `example.com!tag`
HasBang bool // Was there a "!" in the input when creating this struct?
}
// MakeDomainFixedForms turns the user-supplied name into the fixed forms.
// * .Tag: the domain tag (of "example.com!tag")
// * .NameRaw: lowercase version of how the user input the name in dnsconfig.js.
// * .Name: punycode version, downcased.
// * .NameUnicode: unicode version of the name, downcased.
// * .UniqueName: "example.com!tag" unique across the entire config.
func MakeDomainFixForms(n string) (tag, nameRaw, nameIDN, nameUnicode, UniqueName string) {
func MakeDomainFixForms(n string) DomainFixedForms {
var err error
var tag, nameRaw, nameIDN, nameUnicode, uniqueName string
var hasBang bool
// Split tag from name.
p := strings.SplitN(n, "!", 2)
if len(p) != 2 {
tag = ""
} else {
if len(p) == 2 {
tag = p[1]
hasBang = true
} else {
tag = ""
hasBang = false
}
nameRaw = strings.ToLower(p[0])
@ -48,7 +64,18 @@ func MakeDomainFixForms(n string) (tag, nameRaw, nameIDN, nameUnicode, UniqueNam
}
}
UniqueName = nameIDN + "!" + tag
if hasBang {
uniqueName = nameIDN + "!" + tag
} else {
uniqueName = nameIDN
}
return
return DomainFixedForms{
Tag: tag,
NameRaw: nameRaw,
NameIDN: nameIDN,
NameUnicode: nameUnicode,
UniqueName: uniqueName,
HasBang: hasBang,
}
}

View file

@ -13,6 +13,7 @@ func Test_MakeDomainFixForms(t *testing.T) {
wantNameIDN string
wantNameUnicode string
wantUniqueName string
wantHasBang bool
}{
{
name: "simple domain",
@ -21,7 +22,8 @@ func Test_MakeDomainFixForms(t *testing.T) {
wantNameRaw: "example.com",
wantNameIDN: "example.com",
wantNameUnicode: "example.com",
wantUniqueName: "example.com!",
wantUniqueName: "example.com",
wantHasBang: false,
},
{
name: "domain with tag",
@ -31,6 +33,7 @@ func Test_MakeDomainFixForms(t *testing.T) {
wantNameIDN: "example.com",
wantNameUnicode: "example.com",
wantUniqueName: "example.com!mytag",
wantHasBang: true,
},
{
name: "domain with empty tag",
@ -40,6 +43,7 @@ func Test_MakeDomainFixForms(t *testing.T) {
wantNameIDN: "example.com",
wantNameUnicode: "example.com",
wantUniqueName: "example.com!",
wantHasBang: true,
},
{
name: "unicode domain",
@ -48,7 +52,8 @@ func Test_MakeDomainFixForms(t *testing.T) {
wantNameRaw: "उदाहरण.com",
wantNameIDN: "xn--p1b6ci4b4b3a.com",
wantNameUnicode: "उदाहरण.com",
wantUniqueName: "xn--p1b6ci4b4b3a.com!",
wantUniqueName: "xn--p1b6ci4b4b3a.com",
wantHasBang: false,
},
{
name: "unicode domain with tag",
@ -58,6 +63,7 @@ func Test_MakeDomainFixForms(t *testing.T) {
wantNameIDN: "xn--p1b6ci4b4b3a.com",
wantNameUnicode: "उदाहरण.com",
wantUniqueName: "xn--p1b6ci4b4b3a.com!mytag",
wantHasBang: true,
},
{
name: "punycode domain",
@ -66,7 +72,8 @@ func Test_MakeDomainFixForms(t *testing.T) {
wantNameRaw: "xn--p1b6ci4b4b3a.com",
wantNameIDN: "xn--p1b6ci4b4b3a.com",
wantNameUnicode: "उदाहरण.com",
wantUniqueName: "xn--p1b6ci4b4b3a.com!",
wantUniqueName: "xn--p1b6ci4b4b3a.com",
wantHasBang: false,
},
{
name: "punycode domain with tag",
@ -76,6 +83,7 @@ func Test_MakeDomainFixForms(t *testing.T) {
wantNameIDN: "xn--p1b6ci4b4b3a.com",
wantNameUnicode: "उदाहरण.com",
wantUniqueName: "xn--p1b6ci4b4b3a.com!mytag",
wantHasBang: true,
},
{
name: "mixed case domain",
@ -84,7 +92,8 @@ func Test_MakeDomainFixForms(t *testing.T) {
wantNameRaw: "example.com",
wantNameIDN: "example.com",
wantNameUnicode: "example.com",
wantUniqueName: "example.com!",
wantUniqueName: "example.com",
wantHasBang: false,
},
{
name: "mixed case domain with tag",
@ -94,26 +103,30 @@ func Test_MakeDomainFixForms(t *testing.T) {
wantNameIDN: "example.com",
wantNameUnicode: "example.com",
wantUniqueName: "example.com!MyTag",
wantHasBang: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotTag, gotNameRaw, gotNameIDN, gotNameUnicode, gotUniqueName := MakeDomainFixForms(tt.input)
if gotTag != tt.wantTag {
t.Errorf("MakeDomainFixForms() gotTag = %v, want %v", gotTag, tt.wantTag)
got := MakeDomainFixForms(tt.input)
if got.Tag != tt.wantTag {
t.Errorf("MakeDomainFixForms() gotTag = %v, want %v", got.Tag, tt.wantTag)
}
if gotNameRaw != tt.wantNameRaw {
t.Errorf("MakeDomainFixForms() gotNameRaw = %v, want %v", gotNameRaw, tt.wantNameRaw)
if got.NameRaw != tt.wantNameRaw {
t.Errorf("MakeDomainFixForms() gotNameRaw = %v, want %v", got.NameRaw, tt.wantNameRaw)
}
if gotNameIDN != tt.wantNameIDN {
t.Errorf("MakeDomainFixForms() gotNameIDN = %v, want %v", gotNameIDN, tt.wantNameIDN)
if got.NameIDN != tt.wantNameIDN {
t.Errorf("MakeDomainFixForms() gotNameIDN = %v, want %v", got.NameIDN, tt.wantNameIDN)
}
if gotNameUnicode != tt.wantNameUnicode {
t.Errorf("MakeDomainFixForms() gotNameUnicode = %v, want %v", gotNameUnicode, tt.wantNameUnicode)
if got.NameUnicode != tt.wantNameUnicode {
t.Errorf("MakeDomainFixForms() gotNameUnicode = %v, want %v", got.NameUnicode, tt.wantNameUnicode)
}
if gotUniqueName != tt.wantUniqueName {
t.Errorf("MakeDomainFixForms() gotUniqueName = %v, want %v", gotUniqueName, tt.wantUniqueName)
if got.UniqueName != tt.wantUniqueName {
t.Errorf("MakeDomainFixForms() gotUniqueName = %v, want %v", got.UniqueName, tt.wantUniqueName)
}
if got.HasBang != tt.wantHasBang {
t.Errorf("MakeDomainFixForms() gotHasTag = %v, want %v", got.HasBang, tt.wantHasBang)
}
})
}

View file

@ -1,20 +1,22 @@
package domaintags
import "strings"
import (
"strings"
)
type PermitList struct {
// If the permit list is "all" or "".
all bool
items []permitListItem
}
type permitListItem struct {
tag, nameRaw, nameIDN, nameUnicode, uniqueName string
items []DomainFixedForms
}
// CompilePermitList compiles a list of domain strings into a PermitList structure. The
func CompilePermitList(s string) PermitList {
//fmt.Printf("DEBUG: CompilePermitList(%q)\n", s)
s = strings.TrimSpace(s)
if s == "" || strings.ToLower(s) == "all" {
if s == "" || s == "*" || strings.ToLower(s) == "all" {
//fmt.Printf("DEBUG: CompilePermitList: ALL\n")
return PermitList{all: true}
}
@ -24,58 +26,72 @@ func CompilePermitList(s string) PermitList {
if l == "" { // Skip empty entries. They match nothing.
continue
}
tag, nameRaw, nameIDN, nameUnicode, uniqueName := MakeDomainFixForms(l)
if tag == "" { // Treat empty tag as wildcard.
tag = "*"
ff := MakeDomainFixForms(l)
if ff.HasBang && ff.NameIDN == "" { // Treat empty name as wildcard.
ff.NameIDN = "*"
}
if nameIDN == "" { // Treat empty name as wildcard.
nameIDN = "*"
}
sl.items = append(sl.items, permitListItem{
tag: tag,
nameRaw: nameRaw,
nameIDN: nameIDN,
nameUnicode: nameUnicode,
uniqueName: uniqueName,
})
sl.items = append(sl.items, ff)
}
//fmt.Printf("DEBUG: CompilePermitList: RETURN %+v\n", sl)
return sl
}
func (pl *PermitList) Permitted(u string) bool {
func (pl *PermitList) Permitted(domToCheck string) bool {
//fmt.Printf("DEBUG: Permitted(%q)\n", domToCheck)
// If the permit list is "all", everything is permitted.
if pl.all {
//fmt.Printf("DEBUG: Permitted RETURN true\n")
return true
}
tag, _, nameIDN, nameUnicode, _ := MakeDomainFixForms(u)
domToCheckFF := MakeDomainFixForms(domToCheck)
// fmt.Printf("DEBUG: input: %+v\n", domToCheckFF)
for _, filterItem := range pl.items {
// fmt.Printf("DEBUG: Checking item %+v\n", filterItem)
// Special case: filter=example.com!* does not match example.com (no tag)
if filterItem.Tag == "*" && !domToCheckFF.HasBang {
// fmt.Printf("DEBUG: Skipping due to no tag present\n")
continue
}
// Special case: filter=example.com!* does not match example.com! (empty tag)
if filterItem.Tag == "*" && domToCheckFF.HasBang && domToCheckFF.Tag == "" {
// fmt.Printf("DEBUG: Skipping due to empty tag present\n")
continue
}
// Special case: filter=example.com! does not match example.com!tag
if filterItem.HasBang && filterItem.Tag == "" && domToCheckFF.HasBang && domToCheckFF.Tag != "" {
// fmt.Printf("DEBUG: Skipping due to non-empty tag present\n")
continue
}
for _, item := range pl.items {
// Skip if the tag doesn't match
if item.tag != "*" && tag != item.tag {
if (filterItem.Tag != "*") && (domToCheckFF.Tag != filterItem.Tag) {
continue
}
// Now that we know the tag matches, we can focus on the name.
if item.nameIDN == "*" {
if filterItem.NameIDN == "*" {
// `*!tag` or `*` matches everything.
return true
}
// If the name starts with "*." then match the suffix.
if strings.HasPrefix(item.nameIDN, "*.") {
if strings.HasPrefix(filterItem.NameIDN, "*.") {
// example.com matches *.example.com
if nameIDN == item.nameIDN[2:] || nameUnicode == item.nameUnicode[2:] {
if domToCheckFF.NameIDN == filterItem.NameIDN[2:] || domToCheckFF.NameUnicode == filterItem.NameUnicode[2:] {
return true
}
// foo.example.com matches *.example.com
if strings.HasSuffix(nameIDN, item.nameIDN[1:]) || strings.HasSuffix(nameUnicode, item.nameUnicode[1:]) {
if strings.HasSuffix(domToCheckFF.NameIDN, filterItem.NameIDN[1:]) || strings.HasSuffix(domToCheckFF.NameUnicode, filterItem.NameUnicode[1:]) {
return true
}
}
// No wildcards? Exact match.
if item.nameIDN == nameIDN || item.nameUnicode == nameUnicode {
if filterItem.NameIDN == domToCheckFF.NameIDN || filterItem.NameUnicode == domToCheckFF.NameUnicode {
return true
}
}

View file

@ -39,7 +39,7 @@ func TestPermitList_Permitted(t *testing.T) {
// Wildcard tag
{"wildcard tag matches", "example.com!*", "example.com!tag1", true},
{"wildcard tag matches no tag", "example.com!*", "example.com", true},
{"wildcard tag matches no tag", "example.com!*", "example.com", false},
{"wildcard tag mismatch domain", "example.com!*", "google.com!tag1", false},
// Suffix matching
@ -66,12 +66,12 @@ func TestPermitList_Permitted(t *testing.T) {
// IDN/Unicode cases (assuming MakeDomainFixForms works)
{"IDN exact match punycode", "xn--e1a4c.com", "xn--e1a4c.com", true}, // д.com
{"IDN exact match unicode", "д.com", "д.com", true},
{"IDN mixed match", "xn--e1a4c.com", "д.com", true},
{"IDN mixed match reversed", "д.com", "xn--e1a4c.com", true},
{"IDN mixed match", "xn--d1a.com", "д.com", true},
{"IDN mixed match reversed", "д.com", "xn--d1a.com", true},
{"IDN suffix match punycode", "*.xn--e1a4c.com", "sub.xn--e1a4c.com", true},
{"IDN suffix match unicode", "*.д.com", "sub.д.com", true},
{"IDN suffix match mixed", "*.xn--e1a4c.com", "sub.д.com", true},
{"IDN suffix match mixed reversed", "*.д.com", "sub.xn--e1a4c.com", true},
{"IDN suffix match mixed", "*.xn--d1a.com", "sub.д.com", true},
{"IDN suffix match mixed reversed", "*.д.com", "sub.xn--d1a.com", true},
{"IDN suffix match base", "*.д.com", "д.com", true},
// Edge cases
@ -80,6 +80,22 @@ func TestPermitList_Permitted(t *testing.T) {
{"list with empty items 2", "one.com,,two.com", "two.com", true},
{"list with empty items no match", "one.com,,two.com", "three.com", false},
{"no match on empty list", "nonexistent", "example.com", false},
// Weird backwards compatibility with no tag being different than empty tag
{"empty tag vs no tag mismatch", "example.com", "example.com!foo", false},
// testMultiFilterTaggedWildcard
{"testMultiFilterTaggedWildcard_0", "example.com!*", "example.com!", false},
{"testMultiFilterTaggedWildcard_1", "example.com!*", "example.com", false},
{"testMultiFilterTaggedWildcard_2", "example.com!*", "example.net", false},
{"testMultiFilterTaggedWildcard_3", "example.com!*", "example.com!george", true},
{"testMultiFilterTaggedWildcard_4", "example.com!*", "example.com!john", true},
// testFilterEmptyTagAndNoTag
{"testFilterEmptyTagAndNoTag_0", "example.com!,example.com", "example.com!", true},
{"testFilterEmptyTagAndNoTag_1", "example.com!,example.com", "example.com", true},
{"testFilterEmptyTagAndNoTag_2", "example.com!,example.com", "example.net", false},
{"testFilterEmptyTagAndNoTag_3", "example.com!,example.com", "example.com!tag", false},
}
for _, tc := range testCases {

View file

@ -32,6 +32,10 @@ func TestImportTransform(t *testing.T) {
cfg := &models.DNSConfig{
Domains: []*models.DomainConfig{src, dst},
}
err := cfg.PostProcess()
if err != nil {
t.Fatal(err)
}
if errs := ValidateAndNormalizeConfig(cfg); len(errs) != 0 {
for _, err := range errs {
t.Error(err)