diff --git a/pkg/controller/tls/certs.go b/pkg/controller/tls/certs.go index 5e465dcc0..f10f82b22 100644 --- a/pkg/controller/tls/certs.go +++ b/pkg/controller/tls/certs.go @@ -5,6 +5,7 @@ import ( "crypto/x509" "encoding/pem" "fmt" + "net" "strings" "time" @@ -49,6 +50,24 @@ func RequireSecretTypeTLS(h router.Handler) router.Handler { }) } +func WaitForDomain(domain string, retryInterval time.Duration, maxRetries int) error { + done := make(chan error) + + go func() { + for retries := 0; retries < maxRetries; retries++ { + ips, err := net.LookupIP(domain) + if err == nil && len(ips) > 0 { + done <- nil // Domain is resolvable + return + } + time.Sleep(retryInterval) + } + done <- fmt.Errorf("domain %s is not resolvable after %d retries", domain, maxRetries) // Domain is not resolvable + }() + + return <-done +} + // RenewCert handles the renewal of existing TLS certificates func RenewCert(req router.Request, resp router.Response) error { sec := req.Object.(*corev1.Secret) @@ -82,6 +101,11 @@ func RenewCert(req router.Request, resp router.Response) error { logrus.Infof("Renewing TLS cert for %s", domain) + if err := WaitForDomain(domain, 5*time.Second, 12); err != nil { + logrus.Warnf("Domain %s is not resolvable, skipping certificate renewal: %v", domain, err) + return + } + // Get new certificate cert, err := leUser.getCert(req.Ctx, domain) if err != nil { diff --git a/pkg/publish/cert.go b/pkg/publish/cert.go index a5dc236af..05befad75 100644 --- a/pkg/publish/cert.go +++ b/pkg/publish/cert.go @@ -5,11 +5,14 @@ import ( "encoding/pem" "fmt" "sort" + "sync" + "time" "github.com/acorn-io/baaah/pkg/name" "github.com/acorn-io/baaah/pkg/router" "github.com/acorn-io/baaah/pkg/typed" v1 "github.com/acorn-io/runtime/pkg/apis/internal.acorn.io/v1" + "github.com/acorn-io/runtime/pkg/controller/tls" "github.com/acorn-io/runtime/pkg/labels" "github.com/acorn-io/runtime/pkg/system" "github.com/sirupsen/logrus" @@ -155,6 +158,25 @@ func setupCertsForRules(req router.Request, svc *v1.ServiceInstance, rules []net ingressTLS = setupCertManager(svc.Name, rules) } + // Best effort to wait for all domains to be ready, so we don't spam Let's Encrypt + // with requests for domains where the DNS entry was not yet propagated + hostsSeen := map[string]struct{}{} + wg := sync.WaitGroup{} + for _, rule := range rules { + if _, ok := hostsSeen[rule.Host]; ok { + continue + } + hostsSeen[rule.Host] = struct{}{} + wg.Add(1) + go func(host string) { + err := tls.WaitForDomain(host, 5*time.Second, 6) + if err != nil { + logrus.Debugln(err) + } + wg.Done() + }(rule.Host) + } + wg.Wait() return secrets, ingressTLS, annotations, nil }