Skip to content

Commit 9be2b61

Browse files
authored
Merge pull request #55 from jungaretti/no-duplicate-cname
Do not allow records to share host with a CNAME record
2 parents 5b810ea + 4a5d9d3 commit 9be2b61

File tree

5 files changed

+150
-78
lines changed

5 files changed

+150
-78
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ records:
8686
- `host` - Required.
8787
- `content` - Required.
8888
- `ttl` - Required. Minimum value: `600`.
89+
- `priority` - Optional. Allowed for `MX` and `SRV` records.
8990

9091
Bacon does not support `priority`. In order to specify a certain `priority`, you must create the record with Bacon and update the `priority` manually.
9192

pkg/config/config.go

+1-17
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package config
22

33
import (
4-
"fmt"
54
"io"
65
"os"
76

@@ -13,21 +12,6 @@ type Config struct {
1312
Records []Record `yaml:"records"`
1413
}
1514

16-
func (c Config) Validate() error {
17-
if c.Domain == "" {
18-
return fmt.Errorf("domain is required")
19-
}
20-
21-
for _, record := range c.Records {
22-
err := record.Validate()
23-
if err != nil {
24-
return fmt.Errorf("record is invalid %v: %v", record, err)
25-
}
26-
}
27-
28-
return nil
29-
}
30-
3115
func ReadFile(configFile string) (*Config, error) {
3216
file, err := os.Open(configFile)
3317
if err != nil {
@@ -50,7 +34,7 @@ func ReadFile(configFile string) (*Config, error) {
5034
return nil, err
5135
}
5236

53-
err = config.Validate()
37+
err = ValidateConfiguration(config)
5438
if err != nil {
5539
return nil, err
5640
}

pkg/config/config_test.go

+24
Original file line numberDiff line numberDiff line change
@@ -165,3 +165,27 @@ records:
165165
t.Fatal("priority is not allowed on ALIAS records", err)
166166
}
167167
}
168+
169+
func TestInvalidConfigCnameSameHost(t *testing.T) {
170+
configFile, err := SeedConfigToTempFile(`
171+
domain: bacontest42.com
172+
records:
173+
- host: '*.bacontest42.com'
174+
type: CNAME
175+
ttl: 600
176+
content: pixie.porkbun.com
177+
- type: MX
178+
host: "*.bacontest42.com"
179+
content: in1-smtp.messagingengine.com
180+
ttl: 600
181+
priority: 10
182+
`)
183+
if err != nil {
184+
t.Fatal("could not seed config to temp file", err)
185+
}
186+
187+
_, err = ReadFile(configFile)
188+
if err == nil {
189+
t.Fatal("cannot have a CNAME and another record for the same host", err)
190+
}
191+
}

pkg/config/record.go

-61
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,6 @@ package config
33
import (
44
"bacon/pkg/dns"
55
"fmt"
6-
"strings"
7-
)
8-
9-
const (
10-
// Record types that are allowed. Found in Porkbun's API documentation.
11-
TYPE_ALLOWLIST = "A, MX, CNAME, ALIAS, TXT, NS, AAAA, SRV, TLSA, CAA, HTTPS, SVCB"
12-
// Record types that are allowed to have a priority. Found in Porkbun's web app.
13-
PRIORITY_ALLOWLIST = "MX, SRV"
146
)
157

168
type Record struct {
@@ -41,57 +33,4 @@ func (r Record) GetPriority() string {
4133
return fmt.Sprint(r.Priority)
4234
}
4335

44-
func (r Record) Validate() error {
45-
if r.Name == "" {
46-
return fmt.Errorf("host is required")
47-
}
48-
49-
if r.Type == "" {
50-
return fmt.Errorf("type is required")
51-
}
52-
if !isTypeAllowed(r) {
53-
return fmt.Errorf("type must be one of %v", TYPE_ALLOWLIST)
54-
}
55-
56-
if r.Ttl < 600 {
57-
return fmt.Errorf("ttl must be at least 600")
58-
}
59-
60-
if r.Data == "" {
61-
return fmt.Errorf("content is required")
62-
}
63-
64-
if r.Priority != 0 && !isPriorityAllowed(r) {
65-
return fmt.Errorf("priority must be one of %v", PRIORITY_ALLOWLIST)
66-
}
67-
68-
return nil
69-
}
70-
71-
func isTypeAllowed(r Record) bool {
72-
allowedTypes := make(map[string]bool)
73-
for _, t := range strings.Split(TYPE_ALLOWLIST, ", ") {
74-
allowedTypes[t] = true
75-
}
76-
77-
if _, ok := allowedTypes[r.Type]; !ok {
78-
return false
79-
}
80-
81-
return true
82-
}
83-
84-
func isPriorityAllowed(r Record) bool {
85-
allowedPriorities := make(map[string]bool)
86-
for _, t := range strings.Split(PRIORITY_ALLOWLIST, ", ") {
87-
allowedPriorities[t] = true
88-
}
89-
90-
if _, ok := allowedPriorities[r.Type]; !ok {
91-
return false
92-
}
93-
94-
return true
95-
}
96-
9736
var _ dns.Record = Record{}

pkg/config/validation.go

+124
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package config
2+
3+
import (
4+
"fmt"
5+
)
6+
7+
var (
8+
// Record types that are allowed. Found in Porkbun's API documentation.
9+
TYPE_ALLOWLIST = []string{"A", "MX", "CNAME", "ALIAS", "TXT", "NS", "AAAA", "SRV", "TLSA", "CAA", "HTTPS", "SVCB"}
10+
// Record types that are allowed to have a priority. Found in Porkbun's web app.
11+
PRIORITY_ALLOWLIST = []string{"MX", "SRV"}
12+
)
13+
14+
func ValidateConfiguration(config Config) error {
15+
if config.Domain == "" {
16+
return fmt.Errorf("domain is required")
17+
}
18+
19+
for _, record := range config.Records {
20+
if err := ValidateRecord(record); err != nil {
21+
return fmt.Errorf("%v is invalid: %v", record, err)
22+
}
23+
}
24+
25+
if err := configHasUniqueCnameHosts(config.Records); err != nil {
26+
return err
27+
}
28+
29+
return nil
30+
}
31+
32+
func ValidateRecord(record Record) error {
33+
if err := recordHasRequiredFields(record); err != nil {
34+
return err
35+
}
36+
37+
if err := recordHasValidType(record); err != nil {
38+
return err
39+
}
40+
41+
if err := recordHasValidTtl(record); err != nil {
42+
return err
43+
}
44+
45+
if err := recordHasValidPriority(record); err != nil {
46+
return err
47+
}
48+
49+
return nil
50+
}
51+
52+
func recordHasRequiredFields(record Record) error {
53+
if record.Name == "" {
54+
return fmt.Errorf("host is required")
55+
}
56+
57+
if record.Type == "" {
58+
return fmt.Errorf("type is required")
59+
}
60+
61+
if record.Data == "" {
62+
return fmt.Errorf("content is required")
63+
}
64+
65+
return nil
66+
}
67+
68+
func recordHasValidType(record Record) error {
69+
for _, allowedType := range TYPE_ALLOWLIST {
70+
if record.Type == allowedType {
71+
return nil
72+
}
73+
}
74+
75+
return fmt.Errorf("type must be one of %v", TYPE_ALLOWLIST)
76+
}
77+
78+
func recordHasValidTtl(record Record) error {
79+
if record.Ttl < 600 {
80+
return fmt.Errorf("ttl must be at least 600")
81+
}
82+
83+
return nil
84+
}
85+
86+
func recordHasValidPriority(record Record) error {
87+
if record.Priority == 0 {
88+
return nil
89+
}
90+
91+
allowedPriorityTypes := make(map[string]bool)
92+
for _, t := range PRIORITY_ALLOWLIST {
93+
allowedPriorityTypes[t] = true
94+
}
95+
96+
if _, ok := allowedPriorityTypes[record.Type]; !ok {
97+
return fmt.Errorf("type must be one of %v to have priority", PRIORITY_ALLOWLIST)
98+
}
99+
100+
return nil
101+
}
102+
103+
func configHasUniqueCnameHosts(records []Record) error {
104+
cnameHosts := make(map[string]bool)
105+
for _, record := range records {
106+
if record.Type == "CNAME" {
107+
if _, ok := cnameHosts[record.Name]; ok {
108+
return fmt.Errorf("multiple CNAME records exist for host %s", record.Name)
109+
}
110+
cnameHosts[record.Name] = true
111+
}
112+
}
113+
114+
for _, record := range records {
115+
if record.Type == "CNAME" {
116+
continue
117+
}
118+
if cnameHosts[record.Name] {
119+
return fmt.Errorf("non-CNAME record %v shares host with a CNAME record", record)
120+
}
121+
}
122+
123+
return nil
124+
}

0 commit comments

Comments
 (0)