CAA: Support issuemail / issuevmc tag in CAA builder (#3774)

Co-authored-by: Paul Sütterlin <psuet@kawo1.rwth-aachen.de>
This commit is contained in:
Paul Sütterlin 2025-09-22 22:12:30 +02:00 committed by GitHub
parent 1abb11de62
commit 589cb77c68
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 73 additions and 9 deletions

View file

@ -384,6 +384,10 @@ declare function AZURE_ALIAS(name: string, type: "A" | "AAAA" | "CNAME", target:
* 1. `"issue"`
* 2. `"issuewild"`
* 3. `"iodef"`
* 4. `"contactemail"`
* 5. `"contactphone"`
* 6. `"issuemail"`
* 7. `"issuevmc"`
*
* Value is a string. The format of the contents is different depending on the tag. DNSControl will handle any escaping or quoting required, similar to TXT records. For example use `CAA("@", "issue", "letsencrypt.org")` rather than `CAA("@", "issue", "\"letsencrypt.org\"")`.
*
@ -406,7 +410,7 @@ declare function AZURE_ALIAS(name: string, type: "A" | "AAAA" | "CNAME", target:
*
* @see https://docs.dnscontrol.org/language-reference/domain-modifiers/caa
*/
declare function CAA(name: string, tag: "issue" | "issuewild" | "iodef", value: string, ...modifiers: RecordModifier[]): DomainModifier;
declare function CAA(name: string, tag: "issue" | "issuewild" | "iodef" | "contactemail" | "contactphone" | "issuemail" | "issuevmc", value: string, ...modifiers: RecordModifier[]): DomainModifier;
/**
* DNSControl contains a `CAA_BUILDER` which can be used to simply create
@ -503,11 +507,15 @@ declare function CAA(name: string, tag: "issue" | "issuewild" | "iodef", value:
* * `issue_critical:` This can be `true` or `false`. If enabled and CA does not support this record, then certificate issue will be refused. (Optional. Default: `false`)
* * `issuewild:` An array of CAs which are allowed to issue wildcard certificates. (Can be simply `"none"` to refuse issuing wildcard certificates for all CAs)
* * `issuewild_critical:` This can be `true` or `false`. If enabled and CA does not support this record, then certificate issue will be refused. (Optional. Default: `false`)
* * `issuevmc:` An array of CAs which are allowed to issue VMC certificates. (Use `"none"` to refuse all CAs)
* * `issuevmc_critical:` This can be `true` or `false`. If enabled and CA does not support this record, then certificate issue will be refused. (Optional. Default: `false`)
* * `issuemail:` An array of CAs which are allowed to issue email certificates. (Use `"none"` to refuse all CAs)
* * `issuemail_critical:` This can be `true` or `false`. If enabled and CA does not support this record, then certificate issue will be refused. (Optional. Default: `false`)
* * `ttl:` Input for `TTL` method (optional)
*
* @see https://docs.dnscontrol.org/language-reference/domain-modifiers/caa_builder
*/
declare function CAA_BUILDER(opts: { label?: string; iodef: string; iodef_critical?: boolean; issue: string[]|string; issue_critical?: boolean; issuewild: string[]|string; issuewild_critical?: boolean; ttl?: Duration }): DomainModifier;
declare function CAA_BUILDER(opts: { label?: string; iodef: string; iodef_critical?: boolean; issue: string[]|string; issue_critical?: boolean; issuewild: string[]|string; issuewild_critical?: boolean; issuevmc: string[]|string; issuevmc_critical?: boolean; issuemail: string[]|string; issuemail_critical?: boolean; ttl?: Duration }): DomainModifier;
/**
* WARNING: Cloudflare is removing this feature and replacing it with a new

View file

@ -7,7 +7,7 @@ parameters:
- modifiers...
parameter_types:
name: string
tag: '"issue" | "issuewild" | "iodef"'
tag: '"issue" | "issuewild" | "iodef" | "contactemail" | "contactphone" | "issuemail" | "issuevmc"'
value: string
"modifiers...": RecordModifier[]
---
@ -18,6 +18,10 @@ Tag can be one of
1. `"issue"`
2. `"issuewild"`
3. `"iodef"`
4. `"contactemail"`
5. `"contactphone"`
6. `"issuemail"`
7. `"issuevmc"`
Value is a string. The format of the contents is different depending on the tag. DNSControl will handle any escaping or quoting required, similar to TXT records. For example use `CAA("@", "issue", "letsencrypt.org")` rather than `CAA("@", "issue", "\"letsencrypt.org\"")`.

View file

@ -8,6 +8,10 @@ parameters:
- issue_critical
- issuewild
- issuewild_critical
- issuevmc
- issuevmc_critical
- issuemail
- issuemail_critical
- ttl
parameters_object: true
parameter_types:
@ -18,6 +22,10 @@ parameter_types:
issue_critical: boolean?
issuewild: string[]|string
issuewild_critical: boolean?
issuevmc: string[]|string
issuevmc_critical: boolean?
issuemail: string[]|string
issuemail_critical: boolean?
ttl: Duration?
---
@ -123,4 +131,8 @@ which in turns yield the following records:
* `issue_critical:` This can be `true` or `false`. If enabled and CA does not support this record, then certificate issue will be refused. (Optional. Default: `false`)
* `issuewild:` An array of CAs which are allowed to issue wildcard certificates. (Can be simply `"none"` to refuse issuing wildcard certificates for all CAs)
* `issuewild_critical:` This can be `true` or `false`. If enabled and CA does not support this record, then certificate issue will be refused. (Optional. Default: `false`)
* `issuevmc:` An array of CAs which are allowed to issue VMC certificates. (Use `"none"` to refuse all CAs)
* `issuevmc_critical:` This can be `true` or `false`. If enabled and CA does not support this record, then certificate issue will be refused. (Optional. Default: `false`)
* `issuemail:` An array of CAs which are allowed to issue email certificates. (Use `"none"` to refuse all CAs)
* `issuemail_critical:` This can be `true` or `false`. If enabled and CA does not support this record, then certificate issue will be refused. (Optional. Default: `false`)
* `ttl:` Input for `TTL` method (optional)

View file

@ -2,6 +2,7 @@ package models
import (
"fmt"
"slices"
"strconv"
)
@ -19,8 +20,10 @@ func (rc *RecordConfig) SetTargetCAA(flag uint8, tag string, target string) erro
panic("assertion failed: SetTargetCAA called when .Type is not CAA")
}
if tag != "issue" && tag != "issuewild" && tag != "iodef" {
return fmt.Errorf("CAA tag (%v) is not one of issue/issuewild/iodef", tag)
// Per: https://www.iana.org/assignments/pkix-parameters/pkix-parameters.xhtml#caa-properties excluding reserved tags
allowedTags := []string{"issue", "issuewild", "iodef", "contactemail", "contactphone", "issuemail", "issuevmc"}
if !slices.Contains(allowedTags, tag) {
return fmt.Errorf("CAA tag (%v) is not one of the valid types.", tag)
}
return nil

View file

@ -1691,6 +1691,12 @@ function SPF_BUILDER(value) {
// iodef_critical: Boolean if sending report is required/critical. If not supported, certificate should be refused. (optional)
// issue: List of CAs which are allowed to issue certificates for the domain (creates one record for each), or the string 'none'.
// issuewild: List of allowed CAs which can issue wildcard certificates for this domain, or the string 'none'. (creates one record for each)
// issuevmc: List of allowed CAs which can issue VMC certificates for this domain, or the string 'none'. (creates one record for each)
// issuemail: List of allowed CAs which can issue email certificates for this domain, or the string 'none'. (creates one record for each)
// issue_critical: Boolean if issue entries are critical. If not supported, certificate should be refused. (optional)
// issuewild_critical: Boolean if issuewild entries are critical. If not supported, certificate should be refused. (optional)
// issuevmc_critical: Boolean if issuevmc entries are critical. If not supported, certificate should be refused. (optional)
// issuemail_critical: Boolean if issuemail entries are critical. If not supported, certificate should be refused. (optional)
// ttl: The time for TTL, integer or string. (default: not defined, using DefaultTTL)
function CAA_BUILDER(value) {
@ -1700,15 +1706,21 @@ function CAA_BUILDER(value) {
if (value.issue && value.issue == 'none') value.issue = [';'];
if (value.issuewild && value.issuewild == 'none') value.issuewild = [';'];
if (value.issuevmc && value.issuevmc == 'none') value.issuevmc = [';'];
if (value.issuemail && value.issuemail == 'none') value.issuemail = [';'];
if (
(!value.issue && !value.issuewild) ||
(!value.issue && !value.issuewild && !value.issuevmc && !value.issuemail) ||
(value.issue &&
value.issue.length == 0 &&
value.issuewild &&
value.issuewild.length == 0)
value.issuewild.length == 0 &&
value.issuevmc &&
value.issuevmc.length == 0 &&
value.issuemail &&
value.issuemail.length == 0)
) {
throw 'CAA_BUILDER requires at least one entry at issue or issuewild';
throw 'CAA_BUILDER requires at least one entry at issue, issuewild, issuevmc or issuemail';
}
var CAA_TTL = function () {};
@ -1747,6 +1759,28 @@ function CAA_BUILDER(value) {
);
}
if (value.issuevmc) {
var flag = function () {};
if (value.issuevmc_critical) {
flag = CAA_CRITICAL;
}
for (var i = 0, len = value.issuevmc.length; i < len; i++)
r.push(
CAA(value.label, 'issuevmc', value.issuevmc[i], flag, CAA_TTL)
);
}
if (value.issuemail) {
var flag = function () {};
if (value.issuemail_critical) {
flag = CAA_CRITICAL;
}
for (var i = 0, len = value.issuemail.length; i < len; i++)
r.push(
CAA(value.label, 'issuemail', value.issuemail[i], flag, CAA_TTL)
);
}
return r;
}

View file

@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"net"
"slices"
"sort"
"strconv"
"strings"
@ -442,7 +443,9 @@ func ValidateAndNormalizeConfig(config *models.DNSConfig) (errs []error) {
}
rec.SetLabel(name, domain.Name)
} else if rec.Type == "CAA" {
if rec.CaaTag != "issue" && rec.CaaTag != "issuewild" && rec.CaaTag != "iodef" && rec.CaaTag != "issuemail" {
// Per: https://www.iana.org/assignments/pkix-parameters/pkix-parameters.xhtml#caa-properties excluding reserved tags
allowedTags := []string{"issue", "issuewild", "iodef", "contactemail", "contactphone", "issuemail", "issuevmc"}
if !slices.Contains(allowedTags, rec.CaaTag) {
errs = append(errs, fmt.Errorf("CAA tag %s is invalid", rec.CaaTag))
}
} else if rec.Type == "TLSA" {