Skip to content

Commit

Permalink
Merge pull request #101 from gardener/delegated-domains-for-DNS01
Browse files Browse the repository at this point in the history
Support delegated domains for DNS01 challenges
  • Loading branch information
mandelsoft authored Mar 2, 2022
2 parents efad951 + fd0c371 commit 8fc429e
Show file tree
Hide file tree
Showing 36 changed files with 243 additions and 74 deletions.
5 changes: 5 additions & 0 deletions charts/cert-management/templates/crds-v1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,11 @@ spec:
this date.
format: date-time
type: string
followCNAME:
description: FollowCNAME if true delegated domain for DNS01 challenge
is used if CNAME record for DNS01 challange domain `_acme-challenge.<domain>`
is set.
type: boolean
issuerRef:
description: IssuerRef is the reference of the issuer to use.
properties:
Expand Down
5 changes: 5 additions & 0 deletions charts/cert-management/templates/crds-v1beta1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,11 @@ spec:
this date.
format: date-time
type: string
followCNAME:
description: FollowCNAME if true delegated domain for DNS01 challenge
is used if CNAME record for DNS01 challange domain `_acme-challenge.<domain>`
is set.
type: boolean
issuerRef:
description: IssuerRef is the reference of the issuer to use.
properties:
Expand Down
6 changes: 6 additions & 0 deletions examples/30-cert-simple.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,9 @@ spec:
secretRef:
name: cert-simple-secret
namespace: default

# If delegated domain for DNS01 challenge should be used. This has only an effect if a CNAME record is set for
# either '_acme-challenge.cert1.mydomain.com' or '_acme-challenge.cert1.my-other-domain.com'.
# For example: If a CNAME record exists '_acme-challenge.cert1.mydomain.com' => '_acme-challenge.writable.domain.com',
# the DNS challenge will be written to '_acme-challenge.writable.domain.com'.
#followCNAME: true
1 change: 1 addition & 0 deletions examples/40-ingress-echoheaders.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ metadata:
#dns.gardener.cloud/class: garden # needed on Gardener shoot clusters for managed DNS record creation
#cert.gardener.cloud/commonname: "*.demo.mydomain.com" # optional, if not specified the first name from spec.tls[].hosts is used as common name
#cert.gardener.cloud/dnsnames: "" # optional, if not specified the names from spec.tls[].hosts are used
#cert.gardener.cloud/follow-cname: "true" # optional, same as spec.followCNAME in certificates
spec:
tls:
- hosts:
Expand Down
1 change: 1 addition & 0 deletions examples/40-service-loadbalancer.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ metadata:
#dns.gardener.cloud/class: garden # needed on Gardener shoot clusters for managed DNS record creation
#cert.gardener.cloud/commonname: "*.demo.mydomain.com" # optional, if not specified the first name from dns.gardener.cloud/dnsnames is used as common name
#cert.gardener.cloud/dnsnames: "" # optional, if specified overrides dns.gardener.cloud/dnsnames annotation for certificate names
#cert.gardener.cloud/follow-cname: "true" # optional, same as spec.followCNAME in certificates
name: test-service
namespace: default
spec:
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ require (
github.com/ahmetb/gen-crd-api-reference-docs v0.2.0
github.com/gardener/controller-manager-library v0.2.1-0.20220127132236-5167be276ff5
github.com/gardener/external-dns-management v0.11.4
github.com/go-acme/lego/v4 v4.5.3
github.com/go-acme/lego/v4 v4.6.0
github.com/miekg/dns v1.1.44
github.com/onsi/ginkgo v1.16.4
github.com/onsi/gomega v1.15.0
Expand Down
10 changes: 6 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -257,8 +257,8 @@ github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/go-acme/lego/v4 v4.5.3 h1:v5RSN8l+RAeNHKTSL80eqLiec6q6UNaFpl2Df5x/5tM=
github.com/go-acme/lego/v4 v4.5.3/go.mod h1:mL1DY809LzjvRuaxINNxsI26f5oStVhBGTpJMiinkZM=
github.com/go-acme/lego/v4 v4.6.0 h1:w1rQtE/YHY5SupCTRpRJQbaZ6bkySJJ0z+kl8p6pVJU=
github.com/go-acme/lego/v4 v4.6.0/go.mod h1:v19/zU0bumGNzvsbx07zQ6c9IxAvy55XIKhXCZio3NQ=
github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs=
github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
Expand Down Expand Up @@ -846,6 +846,8 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.287/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.287/go.mod h1:CuOaLxOQr477GhMWAQPYQFUJrsZbW+ZqkAgP2uHDZXg=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
Expand All @@ -864,7 +866,7 @@ github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyC
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
github.com/vinyldns/go-vinyldns v0.0.0-20200917153823-148a5f6b8f14/go.mod h1:RWc47jtnVuQv6+lY3c768WtXCas/Xi+U5UFc5xULmYg=
github.com/vinyldns/go-vinyldns v0.9.16/go.mod h1:5qIJOdmzAnatKjurI+Tl4uTus7GJKJxb+zitufjHs3Q=
github.com/vultr/govultr/v2 v2.7.1/go.mod h1:BvOhVe6/ZpjwcoL6/unkdQshmbS9VGbowI4QT+3DGVU=
github.com/wacul/ptr v0.0.0-20170209030335-91632201dfc8/go.mod h1:BD0gjsZrCwtoR+yWDB9v2hQ8STlq9tT84qKfa+3txOc=
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
Expand Down Expand Up @@ -1052,7 +1054,6 @@ golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985 h1:4CSI6oo7cOjJKajidEljs9h+uP0rRZBPPPhcCbj5mw8=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
Expand Down Expand Up @@ -1578,3 +1579,4 @@ sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZa
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
software.sslmate.com/src/go-pkcs12 v0.0.0-20210415151418-c5206de65a78/go.mod h1:B7Wf0Ya4DHF9Yw+qfZuJijQYkWicqDa+79Ytmmq3Kjg=
5 changes: 5 additions & 0 deletions pkg/apis/cert/crds/cert.gardener.cloud_certificates.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ spec:
this date.
format: date-time
type: string
followCNAME:
description: FollowCNAME if true delegated domain for DNS01 challenge
is used if CNAME record for DNS01 challange domain `_acme-challenge.<domain>`
is set.
type: boolean
issuerRef:
description: IssuerRef is the reference of the issuer to use.
properties:
Expand Down
5 changes: 5 additions & 0 deletions pkg/apis/cert/crds/zz_generated_crds.go
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,11 @@ spec:
this date.
format: date-time
type: string
followCNAME:
description: FollowCNAME if true delegated domain for DNS01 challenge
is used if CNAME record for DNS01 challange domain `+"`"+`_acme-challenge.<domain>`+"`"+`
is set.
type: boolean
issuerRef:
description: IssuerRef is the reference of the issuer to use.
properties:
Expand Down
3 changes: 3 additions & 0 deletions pkg/apis/cert/v1alpha1/certificate.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ type CertificateSpec struct {
// EnsureRenewedAfter specifies a time stamp in the past. Renewing is only triggered if certificate notBefore date is before this date.
// +optional
EnsureRenewedAfter *metav1.Time `json:"ensureRenewedAfter,omitempty"`
// FollowCNAME if true delegated domain for DNS01 challenge is used if CNAME record for DNS01 challange domain `_acme-challenge.<domain>` is set.
// +optional
FollowCNAME *bool `json:"followCNAME,omitempty"`
}

// IssuerRef is the reference of the issuer by name.
Expand Down
5 changes: 5 additions & 0 deletions pkg/apis/cert/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions pkg/cert/legobridge/certificate.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ type DNSControllerSettings struct {
AdditionalWait time.Duration
// PropagationTimeout is the propagation timeout for the DNS challenge.
PropagationTimeout time.Duration
// FollowCNAME if true checks and follows CNAME records for DNS01 challenge domains.
FollowCNAME bool
}

// ObtainOutput is the result of the certificate obtain request.
Expand Down
9 changes: 9 additions & 0 deletions pkg/cert/legobridge/dnscontrollerprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,15 @@ func (p *dnsControllerProvider) Present(domain, token, keyAuth string) error {
atomic.AddInt32(&p.count, 1)
fqdn, value := dns01.GetRecord(domain, keyAuth)

if p.settings.FollowCNAME {
var err error
orgfqdn := fqdn
fqdn, err = utils.FollowCNAMEs(fqdn, p.settings.PrecheckNameservers)
if err != nil {
return fmt.Errorf("following CNAME for DNS01 challenge for %s failed: %w", orgfqdn, err)
}
}

values := p.addPresentingDomainValue(domain, value)

setSpec := func(e *dnsapi.DNSEntry) {
Expand Down
2 changes: 2 additions & 0 deletions pkg/cert/source/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ const (
AnnotCommonName = "cert.gardener.cloud/commonname"
// AnnotCertDNSNames is the annotation for explicitly specifying the DNS names (if not specified, values from "dns.gardener.cloud/dnsnames" is used)
AnnotCertDNSNames = "cert.gardener.cloud/dnsnames"
// AnnotFollowCNAME is the annotation for allowing delegated domains for DNS01 challenge
AnnotFollowCNAME = "cert.gardener.cloud/follow-cname"

// OptClass is the cert-class command line option
OptClass = "cert-class"
Expand Down
8 changes: 7 additions & 1 deletion pkg/cert/source/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package source

import (
"fmt"
"strconv"
"strings"
"sync"

Expand Down Expand Up @@ -182,7 +183,12 @@ func (s *DefaultCertSource) GetCertsInfo(logger logger.LogContext, obj resources
issuer = &annotatedIssuer
}

info.Certs[secretName] = CertInfo{SecretName: secretName, Domains: annotatedDomains, IssuerName: issuer}
followCNAME := false
if value, ok := resources.GetAnnotation(obj.Data(), AnnotFollowCNAME); ok {
followCNAME, _ = strconv.ParseBool(value)
}

info.Certs[secretName] = CertInfo{SecretName: secretName, Domains: annotatedDomains, IssuerName: issuer, FollowCNAME: followCNAME}
return info, nil
}

Expand Down
7 changes: 4 additions & 3 deletions pkg/cert/source/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ import (

// CertInfo contains basic certificate data.
type CertInfo struct {
SecretName string
Domains []string
IssuerName *string
SecretName string
Domains []string
IssuerName *string
FollowCNAME bool
}

// CertsInfo contains a map of CertInfo.
Expand Down
3 changes: 3 additions & 0 deletions pkg/cert/source/reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,9 @@ func (r *sourceReconciler) createEntryFor(logger logger.LogContext, obj resource
} else {
cert.Namespace = r.namespace
}
if info.FollowCNAME {
cert.Spec.FollowCNAME = &info.FollowCNAME
}

e, _ := r.SlaveResoures()[0].Wrap(cert)

Expand Down
33 changes: 32 additions & 1 deletion pkg/cert/utils/dns_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ package utils

import (
"fmt"
"time"

"github.com/go-acme/lego/v4/challenge/dns01"
"github.com/miekg/dns"
"time"
)

const defaultPath = "/etc/resolv.conf"
Expand Down Expand Up @@ -91,6 +92,36 @@ func checkTXTValue(answer []dns.RR, values []string) bool {
return true
}

// FollowCNAMEs follows the CNAME records and returns the last non-CNAME fully qualified domain name
// that it finds. Returns an error when a loop is found in the CNAME chain. The
// argument fqdnChain is used by the function itself to keep track of which fqdns it
// already encountered and detect loops.
// Method copied from https://github.com/cert-manager/cert-manager/pkg/issuer/acme/dns/util/wait.go
func FollowCNAMEs(fqdn string, nameservers []string, fqdnChain ...string) (string, error) {
r, err := dnsQuery(fqdn, dns.TypeCNAME, nameservers, true)
if err != nil {
return "", err
}
if r.Rcode != dns.RcodeSuccess {
return fqdn, err
}
for _, rr := range r.Answer {
cn, ok := rr.(*dns.CNAME)
if !ok || cn.Hdr.Name != fqdn {
continue
}
// Check if we were here before to prevent loops in the chain of CNAME records.
for _, fqdnInChain := range fqdnChain {
if cn.Target != fqdnInChain {
continue
}
return "", fmt.Errorf("Found recursive CNAME record to %q when looking up %q", cn.Target, fqdn)
}
return FollowCNAMEs(cn.Target, nameservers, append(fqdnChain, fqdn)...)
}
return fqdn, nil
}

// The following methods are copied from github.com/go-acme/lego/v3/challenge/dns01/nameserver.go

// dnsTimeout is used to override the default DNS timeout of 10 seconds.
Expand Down
5 changes: 5 additions & 0 deletions pkg/controller/issuer/certificate/reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -445,13 +445,18 @@ func (r *certReconciler) obtainCertificateAndPendingACME(logctx logger.LogContex
}
var dnsSettings *legobridge.DNSControllerSettings
if issuer.Spec.ACME.SkipDNSChallengeValidation == nil || !*issuer.Spec.ACME.SkipDNSChallengeValidation {
followCNAME := false
if cert.Spec.FollowCNAME != nil && *cert.Spec.FollowCNAME {
followCNAME = true
}
dnsSettings = &legobridge.DNSControllerSettings{
Cluster: r.dnsCluster,
Namespace: cert.Namespace,
OwnerID: r.dnsOwnerID,
PrecheckNameservers: r.precheckNameservers,
AdditionalWait: r.additionalWait,
PropagationTimeout: r.propagationTimeout,
FollowCNAME: followCNAME,
}
if r.dnsNamespace != nil {
dnsSettings.Namespace = *r.dnsNamespace
Expand Down
8 changes: 7 additions & 1 deletion pkg/controller/source/ingress/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package ingress

import (
"fmt"
"strconv"
"strings"

networkingv1 "k8s.io/api/networking/v1"
Expand Down Expand Up @@ -56,6 +57,11 @@ func (s *CIngressSource) GetCertsInfo(logger logger.LogContext, obj resources.Ob
return info, nil
}

followCNAME := false
if value, ok := resources.GetAnnotation(obj.Data(), source.AnnotFollowCNAME); ok {
followCNAME, _ = strconv.ParseBool(value)
}

cn, _ := resources.GetAnnotation(obj.Data(), source.AnnotCommonName)
cn = strings.TrimSpace(cn)
var issuer *string
Expand Down Expand Up @@ -83,7 +89,7 @@ func (s *CIngressSource) GetCertsInfo(logger logger.LogContext, obj resources.Ob
} else {
domains = mergeCommonName(cn, tls.Hosts)
}
info.Certs[tls.SecretName] = source.CertInfo{SecretName: tls.SecretName, Domains: domains, IssuerName: issuer}
info.Certs[tls.SecretName] = source.CertInfo{SecretName: tls.SecretName, Domains: domains, IssuerName: issuer, FollowCNAME: followCNAME}
}
return info, err
}
Expand Down
Empty file.
2 changes: 1 addition & 1 deletion vendor/github.com/go-acme/lego/v4/acme/api/api.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions vendor/github.com/go-acme/lego/v4/acme/api/certificate.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 8fc429e

Please sign in to comment.