@marcel91200 / caddy Mirror · read-only / commits / fead9c3f
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 11, 03:30 UTC

caddytls: keep ACME issuer network proxy after RebuildCertMagic (#8288)

* caddytls: keep ACME issuer network proxy across re-provisioning LoadModule clears NetworkProxyRaw, so when the HTTP app provisions an ACME issuer a second time (fillInACMEIssuer), the rebuilt template has no HTTPProxy. Since #7510 that template is used via SetConfig, and ACME requests bypass the configured proxy. Keep the loaded proxy func on the issuer, like the DNS solver. Fixes #8283 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 2366825f2380383dacd8adeb1508e34c3e25634d) * caddytls: test that ACME requests use the proxy after RebuildCertMagic Replace the provision-twice check with a regression for the path the HTTP app takes: Provision, re-provision in fillInACMEIssuer, then RebuildCertMagic. The test issues against a local proxy and asserts the ACME request goes through it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: k4lleanka <257361691+k4lleanka@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

K4lleAnka committed Oct 28, 2026 at 10:37 UTC fead9c3f24ee1783b4cd3822e923f7cf4930d18e
2 files changed +96 -4
modules/caddytls/acmeissuer.go
+7 -3
@@ -109,8 +109,9 @@ type ACMEIssuer struct {
109 // Forward proxy module
110 NetworkProxyRaw json.RawMessage `json:"network_proxy,omitempty" caddy:"namespace=caddy.network_proxy inline_key=from"`
111
112 - rootPool *x509.CertPool
113 - logger *zap.Logger
112 + rootPool *x509.CertPool
113 + logger *zap.Logger
114 + httpProxy func(*http.Request) (*url.URL, error)
115
116 template certmagic.ACMEIssuer // set at Provision
117 magic *certmagic.Config // set at PreCheck
@@ -265,17 +266,20 @@ func (iss *ACMEIssuer) makeIssuerTemplate(ctx caddy.Context) (certmagic.ACMEIssu
266 Logger: iss.logger,
267 }
268
269 + // LoadModule clears NetworkProxyRaw, so keep the proxy for when the
270 + // issuer is provisioned again (e.g. by the HTTP app's auto-HTTPS)
271 if len(iss.NetworkProxyRaw) != 0 {
272 proxyMod, err := ctx.LoadModule(iss, "NetworkProxyRaw")
273 if err != nil {
274 return template, fmt.Errorf("failed to load network_proxy module: %v", err)
275 }
276 if m, ok := proxyMod.(caddy.ProxyFuncProducer); ok {
274 - template.HTTPProxy = m.ProxyFunc()
277 + iss.httpProxy = m.ProxyFunc()
278 } else {
279 return template, fmt.Errorf("network_proxy module is not `(func(*http.Request) (*url.URL, error))``")
280 }
281 }
282 + template.HTTPProxy = iss.httpProxy
283
284 if iss.Challenges != nil {
285 if iss.Challenges.HTTP != nil {
modules/caddytls/acmeissuer_test.go
+89 -1
@@ -1,9 +1,22 @@
1 package caddytls
2
3 import (
4 + "context"
5 + "crypto/ecdsa"
6 + "crypto/elliptic"
7 + "crypto/rand"
8 + "crypto/x509"
9 + "net/http"
10 + "net/http/httptest"
11 + "sync/atomic"
12 + "testing"
13 + "time"
14 +
15 "github.com/caddyserver/caddy/v2"
16 + _ "github.com/caddyserver/caddy/v2/modules/internal/network"
17 + "github.com/caddyserver/certmagic"
18 "github.com/mholt/acmez/v3/acme"
6 - "testing"
19 + "go.uber.org/zap"
20 )
21
22 func TestACMEIssuerExpandPlaceholders(t *testing.T) {
@@ -41,3 +54,78 @@ func TestACMEIssuerExpandPlaceholders(t *testing.T) {
54 t.Errorf("ExternalAccount.MACKey: got %q, want %q", iss.ExternalAccount.MACKey, want)
55 }
56 }
57 +
58 +// The HTTP app provisions ACME issuers again to fill in its ports and then
59 +// rebuilds the CertMagic config; requests must still use the configured proxy (#8283).
60 +func TestACMEIssuerNetworkProxyAfterRebuildCertMagic(t *testing.T) {
61 + var proxied atomic.Int32
62 + proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
63 + if r.Method == http.MethodConnect && r.Host == "acme.invalid:443" {
64 + proxied.Add(1)
65 + }
66 + http.Error(w, "test proxy", http.StatusBadGateway)
67 + }))
68 + defer proxy.Close()
69 +
70 + certCacheMu.Lock()
71 + prevCache := certCache
72 + certCache = certmagic.NewCache(certmagic.CacheOptions{
73 + GetConfigForCert: func(certmagic.Certificate) (*certmagic.Config, error) { return nil, nil },
74 + })
75 + certCacheMu.Unlock()
76 + defer func() {
77 + certCacheMu.Lock()
78 + certCache.Stop()
79 + certCache = prevCache
80 + certCacheMu.Unlock()
81 + }()
82 +
83 + caddyCtx, cancel := caddy.NewContext(caddy.Context{Context: t.Context()})
84 + defer cancel()
85 + tlsApp := &TLS{ctx: caddyCtx, logger: zap.NewNop()}
86 +
87 + iss := &ACMEIssuer{
88 + CA: "https://acme.invalid/directory",
89 + NetworkProxyRaw: []byte(`{"from":"url","url":"` + proxy.URL + `"}`),
90 + }
91 + if err := iss.Provision(caddyCtx); err != nil {
92 + t.Fatalf("provisioning issuer: %v", err)
93 + }
94 + ap := &AutomationPolicy{
95 + Issuers: []certmagic.Issuer{iss},
96 + storage: &certmagic.FileStorage{Path: t.TempDir()},
97 + }
98 + if err := ap.Provision(tlsApp); err != nil {
99 + t.Fatalf("provisioning automation policy: %v", err)
100 + }
101 +
102 + // what the HTTP app's fillInACMEIssuer and createAutomationPolicies do
103 + if err := iss.Provision(caddyCtx); err != nil {
104 + t.Fatalf("re-provisioning issuer: %v", err)
105 + }
106 + if err := ap.RebuildCertMagic(tlsApp); err != nil {
107 + t.Fatalf("rebuilding certmagic config: %v", err)
108 + }
109 +
110 + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
111 + if err != nil {
112 + t.Fatal(err)
113 + }
114 + csrDER, err := x509.CreateCertificateRequest(rand.Reader, &x509.CertificateRequest{DNSNames: []string{"example.com"}}, key)
115 + if err != nil {
116 + t.Fatal(err)
117 + }
118 + csr, err := x509.ParseCertificateRequest(csrDER)
119 + if err != nil {
120 + t.Fatal(err)
121 + }
122 +
123 + ctx, cancelIssue := context.WithTimeout(t.Context(), 10*time.Second)
124 + defer cancelIssue()
125 + if _, err := iss.Issue(ctx, csr); err == nil {
126 + t.Fatal("expected issuance to fail against the test proxy")
127 + }
128 + if proxied.Load() == 0 {
129 + t.Error("ACME request did not go through the configured network proxy")
130 + }
131 +}