diff --git a/examples/route-proxy/main.go b/examples/route-proxy/main.go index 6ae5440..3235d18 100644 --- a/examples/route-proxy/main.go +++ b/examples/route-proxy/main.go @@ -203,13 +203,21 @@ func run() error { if err != nil { return fmt.Errorf("ACME_CA_BUNDLE names %s and it cannot be read: %w", bundle, err) } - pool := x509.NewCertPool() - if !pool.AppendCertsFromPEM(pem) { - return fmt.Errorf("%s holds no certificate this can trust", bundle) - } - client.HTTPClient = &http.Client{ - Timeout: 30 * time.Second, - Transport: &http.Transport{TLSClientConfig: &tls.Config{RootCAs: pool}}, + // An empty bundle means the issuer's root is already in the system trust store — a public + // authority whose root ships with the OS, pointed at by a provider that serves an empty + // root (novox/hq ADR 0056). The mesh always writes the bundle file, so it exists and holds + // nothing; that is the signal to fall back to the system roots, the same as if nothing had + // named a bundle at all. A file that holds bytes but no certificate is still a + // misconfiguration and is refused, because there the operator meant to trust something. + if strings.TrimSpace(string(pem)) != "" { + pool := x509.NewCertPool() + if !pool.AppendCertsFromPEM(pem) { + return fmt.Errorf("%s holds no certificate this can trust", bundle) + } + client.HTTPClient = &http.Client{ + Timeout: 30 * time.Second, + Transport: &http.Transport{TLSClientConfig: &tls.Config{RootCAs: pool}}, + } } } manager := &autocert.Manager{