CAA: Support issuemail / issuevmc tag in CAA builder

This commit is contained in:
Paul Sütterlin 2025-09-20 13:01:51 +02:00
parent 1abb11de62
commit 6624e237ed
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"` * 1. `"issue"`
* 2. `"issuewild"` * 2. `"issuewild"`
* 3. `"iodef"` * 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\"")`. * 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 * @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 * 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`) * * `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:` 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`) * * `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) * * `ttl:` Input for `TTL` method (optional)
* *
* @see https://docs.dnscontrol.org/language-reference/domain-modifiers/caa_builder * @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 * WARNING: Cloudflare is removing this feature and replacing it with a new

View file

@ -7,7 +7,7 @@ parameters:
- modifiers... - modifiers...
parameter_types: parameter_types:
name: string name: string
tag: '"issue" | "issuewild" | "iodef"' tag: '"issue" | "issuewild" | "iodef" | "contactemail" | "contactphone" | "issuemail" | "issuevmc"'
value: string value: string
"modifiers...": RecordModifier[] "modifiers...": RecordModifier[]
--- ---
@ -18,6 +18,10 @@ Tag can be one of
1. `"issue"` 1. `"issue"`
2. `"issuewild"` 2. `"issuewild"`
3. `"iodef"` 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\"")`. 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 - issue_critical
- issuewild - issuewild
- issuewild_critical - issuewild_critical
- issuevmc
- issuevmc_critical
- issuemail
- issuemail_critical
- ttl - ttl
parameters_object: true parameters_object: true
parameter_types: parameter_types:
@ -18,6 +22,10 @@ parameter_types:
issue_critical: boolean? issue_critical: boolean?
issuewild: string[]|string issuewild: string[]|string
issuewild_critical: boolean? issuewild_critical: boolean?
issuevmc: string[]|string
issuevmc_critical: boolean?
issuemail: string[]|string
issuemail_critical: boolean?
ttl: Duration? 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`) * `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:` 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`) * `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) * `ttl:` Input for `TTL` method (optional)

View file

@ -2,6 +2,7 @@ package models
import ( import (
"fmt" "fmt"
"slices"
"strconv" "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") panic("assertion failed: SetTargetCAA called when .Type is not CAA")
} }
if tag != "issue" && tag != "issuewild" && tag != "iodef" { // Per: https://www.iana.org/assignments/pkix-parameters/pkix-parameters.xhtml#caa-properties excluding reserved tags
return fmt.Errorf("CAA tag (%v) is not one of issue/issuewild/iodef", tag) 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 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) // 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'. // 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) // 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) // ttl: The time for TTL, integer or string. (default: not defined, using DefaultTTL)
function CAA_BUILDER(value) { function CAA_BUILDER(value) {
@ -1700,15 +1706,21 @@ function CAA_BUILDER(value) {
if (value.issue && value.issue == 'none') value.issue = [';']; if (value.issue && value.issue == 'none') value.issue = [';'];
if (value.issuewild && value.issuewild == 'none') value.issuewild = [';']; 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 ( if (
(!value.issue && !value.issuewild) || (!value.issue && !value.issuewild && !value.issuevmc && !value.issuemail) ||
(value.issue && (value.issue &&
value.issue.length == 0 && value.issue.length == 0 &&
value.issuewild && 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 () {}; 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; return r;
} }

View file

@ -4,6 +4,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"net" "net"
"slices"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
@ -442,7 +443,9 @@ func ValidateAndNormalizeConfig(config *models.DNSConfig) (errs []error) {
} }
rec.SetLabel(name, domain.Name) rec.SetLabel(name, domain.Name)
} else if rec.Type == "CAA" { } 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)) errs = append(errs, fmt.Errorf("CAA tag %s is invalid", rec.CaaTag))
} }
} else if rec.Type == "TLSA" { } else if rec.Type == "TLSA" {