Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,37 @@ Internally resolved cname, of sorts. Only works internally in a zone.

The target will have the current zone name appended if it's not a FQDN (since v2.2.0).

### CAA

CAA (Certificate Authority Authorization) records allow domain owners to specify which Certificate Authorities are authorized to issue certificates for their domain.

CAA records support the standard DNS text format (similar to bind zone files):

"0 issue ca.example.net"
"0 issuewild ca.example.net"
"128 iodef mailto:security@example.com"

The format is `"flag tag value"` where:
- `flag`: A number (0-255) for processing flags
- `tag`: The property type (issue, issuewild, iodef, etc.)
- `value`: The property value (CA domain, email, URL, etc.)

For weight-based load balancing, use array format:

["0 issue ca.example.net", 10]
["128 iodef mailto:security@example.com", 100]

The older JSON object format is still supported for backward compatibility:

{ "tag": "issue", "value": "ca.example.net" }
{ "flag": 0, "tag": "issuewild", "value": "ca.example.net" }
{ "flag": 128, "tag": "iodef", "value": "mailto:security@example.com", "weight": 100 }

Common tags include:
- `issue`: Authorizes the specified CA to issue certificates for this domain
- `issuewild`: Authorizes the specified CA to issue wildcard certificates for this domain
- `iodef`: Specifies a URL or email address for reporting certificate issue violations

### MX

MX records support a `weight` similar to A records to indicate how often the particular
Expand Down
36 changes: 0 additions & 36 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,8 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hamba/avro/v2 v2.14.1 h1:mRkiRKjRTTs+yx0nVuM6z/q5zg3VBZfOe/01ngAnU6A=
github.com/hamba/avro/v2 v2.14.1/go.mod h1:Q9YK+qxAhtVrNqOhwlZTATLgLA8qxG2vtvkhK8fJ7Jo=
github.com/hamba/avro/v2 v2.16.0 h1:0XhyP65Hs8iMLtdSR0v7ZrwRjsbIZdvr7KzYgmx1Mbo=
github.com/hamba/avro/v2 v2.16.0/go.mod h1:Q9YK+qxAhtVrNqOhwlZTATLgLA8qxG2vtvkhK8fJ7Jo=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
Expand All @@ -42,8 +38,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo=
github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
github.com/miekg/dns v1.1.56 h1:5imZaSeoRNvpM9SzWNhEcP9QliKiz20/dA2QabIGVnE=
github.com/miekg/dns v1.1.56/go.mod h1:cRm6Oo2C8TY9ZS/TqsSrseAcncm74lfK5G+ikN2SWWY=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
Expand All @@ -61,20 +55,12 @@ github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw=
github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q=
github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY=
github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 h1:v7DLqVdK4VrYkVD5diGdl4sxJurKJEMnODWRJlxV9oM=
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY=
github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI=
github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY=
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
Expand All @@ -88,42 +74,20 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
go.ntppool.org/common v0.2.0 h1:ufVBJoflAwq1HzT1/kezUBPTP2lkYEBPRmg1wkuqDbo=
go.ntppool.org/common v0.2.0/go.mod h1:2vW9Wsc+N45GkBoo+i8gn4a2dPeGP9gLQntzw6aKH6E=
go.ntppool.org/common v0.2.1 h1:UZFFn/39Rn6esx+gzVceY4v5oznyNORJ7JugixdmKzM=
go.ntppool.org/common v0.2.1/go.mod h1:rTTb+LHJRogQ8rdmu3lZsa7zwWA9vg33fNaM6u/EKtI=
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ=
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 h1:Vve/L0v7CXXuxUmaMGIEK/dEeq7uiqb5qBgQrZzIE7E=
golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
Expand Down
138 changes: 134 additions & 4 deletions zones/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ func setupZoneData(data map[string]interface{}, zone *Zone) {
"a": dns.TypeA,
"aaaa": dns.TypeAAAA,
"alias": dns.TypeMF,
"caa": dns.TypeCAA,
"cname": dns.TypeCNAME,
"mx": dns.TypeMX,
"ns": dns.TypeNS,
Expand Down Expand Up @@ -227,7 +228,7 @@ func setupZoneData(data map[string]interface{}, zone *Zone) {

//log.Printf("RECORDS %s TYPE-REC %T\n", Records, Records)

label.Records[dnsType] = make(Records, len(records[rType]))
validRecords := make([]*Record, 0, len(records[rType]))

for i := 0; i < len(records[rType]); i++ {
//log.Printf("RT %T %#v\n", records[rType][i], records[rType][i])
Expand Down Expand Up @@ -472,6 +473,95 @@ func setupZoneData(data map[string]interface{}, zone *Zone) {
continue
}

case dns.TypeCAA:
rec := records[rType][i]

var flag uint8 = 0
var tag, value string

switch rec.(type) {
case string:
// Text format: "flag tag value"
var err error
flag, tag, value, err = parseCAAText(rec.(string))
if err != nil {
log.Printf("Error parsing CAA record '%s' for '%s' in '%s': %v\n", rec.(string), label.Label, zone.Origin, err)
continue
}
case []interface{}:
// Array format: ["flag tag value", weight]
arr := rec.([]interface{})
if len(arr) == 0 {
log.Printf("Empty CAA record array for '%s' in '%s'\n", label.Label, zone.Origin)
continue
}

caaText, ok := arr[0].(string)
if !ok {
log.Printf("First element of CAA record array must be string for '%s' in '%s'\n", label.Label, zone.Origin)
continue
}

var err error
flag, tag, value, err = parseCAAText(caaText)
if err != nil {
log.Printf("Error parsing CAA record '%s' for '%s' in '%s': %v\n", caaText, label.Label, zone.Origin, err)
continue
}

if len(arr) > 1 {
switch weight := arr[1].(type) {
case int:
record.Weight = weight
case float64:
record.Weight = int(weight)
case string:
var err error
record.Weight, err = strconv.Atoi(weight)
if err != nil {
log.Printf("Error converting CAA weight '%s' to integer for '%s' in '%s': %v\n", weight, label.Label, zone.Origin, err)
}
default:
record.Weight = typeutil.ToInt(arr[1])
}
}
case map[string]interface{}:
// JSON format for backward compatibility
recmap := rec.(map[string]interface{})

if recmap["flag"] != nil {
flag = uint8(typeutil.ToInt(recmap["flag"]))
}

if recmap["tag"] != nil {
tag = recmap["tag"].(string)
} else {
log.Printf("CAA record missing required 'tag' field for '%s' in '%s'\n", label.Label, zone.Origin)
continue
}

if recmap["value"] != nil {
value = recmap["value"].(string)
} else {
log.Printf("CAA record missing required 'value' field for '%s' in '%s'\n", label.Label, zone.Origin)
continue
}

if recmap["weight"] != nil {
record.Weight = typeutil.ToInt(recmap["weight"])
}
default:
log.Printf("CAA record must be string, array, or map format for '%s' in '%s'\n", label.Label, zone.Origin)
continue
}

record.RR = &dns.CAA{
Hdr: h,
Flag: flag,
Tag: tag,
Value: value,
}

default:
log.Println("type:", rType)
panic("Don't know how to handle this type")
Expand All @@ -482,10 +572,17 @@ func setupZoneData(data map[string]interface{}, zone *Zone) {
}

label.Weight[dnsType] += record.Weight
label.Records[dnsType][i] = record
validRecords = append(validRecords, record)
}
if label.Weight[dnsType] > 0 {
sort.Sort(RecordsByWeight{label.Records[dnsType]})

// Only create the Records array if we have valid records
if len(validRecords) > 0 {
label.Records[dnsType] = make(Records, len(validRecords))
copy(label.Records[dnsType], validRecords)

if label.Weight[dnsType] > 0 {
sort.Sort(RecordsByWeight{label.Records[dnsType]})
}
}
}
}
Expand Down Expand Up @@ -559,3 +656,36 @@ func getStringWeight(rec []interface{}) (string, int) {

return str, weight
}

// parseCAAText parses CAA record text format: "flag tag value"
// Example: "0 issue ca.example.net" or "128 iodef mailto:security@example.com"
func parseCAAText(text string) (flag uint8, tag, value string, err error) {
parts := strings.Fields(text)
if len(parts) < 3 {
return 0, "", "", fmt.Errorf("CAA record must have at least 3 parts: flag tag value")
}

// Parse flag
flagInt, err := strconv.Atoi(parts[0])
if err != nil {
return 0, "", "", fmt.Errorf("invalid CAA flag '%s': %v", parts[0], err)
}
if flagInt < 0 || flagInt > 255 {
return 0, "", "", fmt.Errorf("CAA flag must be between 0 and 255, got %d", flagInt)
}
flag = uint8(flagInt)

// Tag is the second part
tag = parts[1]

// Value is everything from the third part onwards, joined with spaces
// This handles values that contain spaces (like quoted strings)
value = strings.Join(parts[2:], " ")

// Remove quotes if present (common in bind format)
if len(value) >= 2 && value[0] == '"' && value[len(value)-1] == '"' {
value = value[1 : len(value)-1]
}

return flag, tag, value, nil
}
Loading