first draft CAA checking

Signed-off-by: Daniel Morsing <dmo@jetstack.io>
This commit is contained in:
Daniel Morsing 2019-02-05 14:25:10 +00:00
parent 4db43bf04c
commit bb853e5e79
3 changed files with 75 additions and 0 deletions

View File

@ -17,6 +17,7 @@ go_library(
"//pkg/controller:go_default_library",
"//pkg/controller/acmechallenges/scheduler:go_default_library",
"//pkg/issuer/acme/dns:go_default_library",
"//pkg/issuer/acme/dns/util:go_default_library",
"//pkg/issuer/acme/http:go_default_library",
"//pkg/util:go_default_library",
"//third_party/crypto/acme:go_default_library",

View File

@ -31,6 +31,8 @@ import (
cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
controllerpkg "github.com/jetstack/cert-manager/pkg/controller"
acmeapi "github.com/jetstack/cert-manager/third_party/crypto/acme"
dnsutil "github.com/jetstack/cert-manager/pkg/issuer/acme/dns/util"
)
const (
@ -131,6 +133,26 @@ func (c *Controller) Sync(ctx context.Context, ch *cmapi.Challenge) (err error)
return nil
}
// check for CAA records.
// CAA records are static, so we don't have to present anything
// before we check for them.
// Find out which identity the ACME server says it will use.
dir, err := cl.Discover(ctx)
if err != nil {
return err
}
// TODO(dmo): figure out if missing CAA identity in directory
// means no CAA check is performed by ACME server or if any valid
// CAA would stop issuance (strongly suspect the former)
if len(dir.CAA) != 0 {
err := dnsutil.ValidateCAA(ch.Spec.DNSName, dir.CAA, ch.Spec.Wildcard)
if err != nil {
ch.Status.Reason = fmt.Sprintf("CAA self-check failed: %s", err)
return err
}
}
solver, err := c.solverFor(ch.Spec.Type)
if err != nil {
return err

View File

@ -168,6 +168,58 @@ func dnsQuery(fqdn string, rtype uint16, nameservers []string, recursive bool) (
return
}
func ValidateCAA(domain string, issuerID []string, iswildcard bool) error {
// see https://tools.ietf.org/html/rfc6844#section-4
// for more information about how CAA lookup is performed
fqdn := ToFqdn(domain)
issuerSet := make(map[string]bool)
for _, s := range issuerID {
issuerSet[s] = true
}
//TODO(dmo): figure out if we need these servers to be configurable as well
msg, err := dnsQuery(fqdn, dns.TypeCAA, RecursiveNameservers, true)
if err != nil {
return fmt.Errorf("Could not validate CAA record: %s", err)
}
//TODO(dmo): follow CNAMES
//TODO(dmo): look at labels above this one
caas := make([]*dns.CAA, 0, len(msg.Answer))
for _, rr := range msg.Answer {
caa, ok := rr.(*dns.CAA)
if !ok {
continue
}
caas = append(caas, caa)
}
if len(caas) == 0 {
// TODO(dmo): work up in the label
return nil
}
if !matchCAA(caas, issuerSet, iswildcard) {
// TODO(dmo): better error message
return fmt.Errorf("CAA record does not match issuer")
}
return nil
}
func matchCAA(caas []*dns.CAA, issuerIDs map[string]bool, iswildcard bool) bool {
expectedTag := "issue"
if iswildcard {
expectedTag = "issuewild"
}
for _, caa := range caas {
if caa.Tag != expectedTag {
continue
}
if issuerIDs[caa.Value] {
return true
}
}
return false
}
// lookupNameservers returns the authoritative nameservers for the given fqdn.
func lookupNameservers(fqdn string, nameservers []string) ([]string, error) {
var authoritativeNss []string