Skip to content

Commit 1735730

Browse files
mohammed90francislavoiemholt
authored
core: add modular network_proxy support (#6399)
* core: add modular `network_proxy` support Co-authored-by: @ImpostorKeanu Signed-off-by: Mohammed Al Sahaf <[email protected]> * move modules around Signed-off-by: Mohammed Al Sahaf <[email protected]> * add caddyfile implementation Signed-off-by: Mohammed Al Sahaf <[email protected]> * address feedbcak * Apply suggestions from code review Co-authored-by: Francis Lavoie <[email protected]> * adapt ForwardProxyURL to use the NetworkProxyRaw Signed-off-by: Mohammed Al Sahaf <[email protected]> * remove redundant `url` in log Co-authored-by: Matt Holt <[email protected]> * code review Signed-off-by: Mohammed Al Sahaf <[email protected]> * remove `.source` from the module ID Signed-off-by: Mohammed Al Sahaf <[email protected]> --------- Signed-off-by: Mohammed Al Sahaf <[email protected]> Co-authored-by: Francis Lavoie <[email protected]> Co-authored-by: Matt Holt <[email protected]>
1 parent 7b1f00c commit 1735730

File tree

8 files changed

+347
-12
lines changed

8 files changed

+347
-12
lines changed
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
:8884
2+
reverse_proxy 127.0.0.1:65535 {
3+
transport http {
4+
forward_proxy_url http://localhost:8080
5+
}
6+
}
7+
----------
8+
{
9+
"apps": {
10+
"http": {
11+
"servers": {
12+
"srv0": {
13+
"listen": [
14+
":8884"
15+
],
16+
"routes": [
17+
{
18+
"handle": [
19+
{
20+
"handler": "reverse_proxy",
21+
"transport": {
22+
"network_proxy": {
23+
"from": "url",
24+
"url": "http://localhost:8080"
25+
},
26+
"protocol": "http"
27+
},
28+
"upstreams": [
29+
{
30+
"dial": "127.0.0.1:65535"
31+
}
32+
]
33+
}
34+
]
35+
}
36+
]
37+
}
38+
}
39+
}
40+
}
41+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
:8884
2+
reverse_proxy 127.0.0.1:65535 {
3+
transport http {
4+
network_proxy none
5+
}
6+
}
7+
----------
8+
{
9+
"apps": {
10+
"http": {
11+
"servers": {
12+
"srv0": {
13+
"listen": [
14+
":8884"
15+
],
16+
"routes": [
17+
{
18+
"handle": [
19+
{
20+
"handler": "reverse_proxy",
21+
"transport": {
22+
"network_proxy": {
23+
"from": "none"
24+
},
25+
"protocol": "http"
26+
},
27+
"upstreams": [
28+
{
29+
"dial": "127.0.0.1:65535"
30+
}
31+
]
32+
}
33+
]
34+
}
35+
]
36+
}
37+
}
38+
}
39+
}
40+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
:8884
2+
reverse_proxy 127.0.0.1:65535 {
3+
transport http {
4+
network_proxy url http://localhost:8080
5+
}
6+
}
7+
----------
8+
{
9+
"apps": {
10+
"http": {
11+
"servers": {
12+
"srv0": {
13+
"listen": [
14+
":8884"
15+
],
16+
"routes": [
17+
{
18+
"handle": [
19+
{
20+
"handler": "reverse_proxy",
21+
"transport": {
22+
"network_proxy": {
23+
"from": "url",
24+
"url": "http://localhost:8080"
25+
},
26+
"protocol": "http"
27+
},
28+
"upstreams": [
29+
{
30+
"dial": "127.0.0.1:65535"
31+
}
32+
]
33+
}
34+
]
35+
}
36+
]
37+
}
38+
}
39+
}
40+
}
41+
}

modules.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import (
1818
"bytes"
1919
"encoding/json"
2020
"fmt"
21+
"net/http"
22+
"net/url"
2123
"reflect"
2224
"sort"
2325
"strings"
@@ -360,6 +362,14 @@ func isModuleMapType(typ reflect.Type) bool {
360362
isJSONRawMessage(typ.Elem())
361363
}
362364

365+
// ProxyFuncProducer is implemented by modules which produce a
366+
// function that returns a URL to use as network proxy. Modules
367+
// in the namespace `caddy.network_proxy` must implement this
368+
// interface.
369+
type ProxyFuncProducer interface {
370+
ProxyFunc() func(*http.Request) (*url.URL, error)
371+
}
372+
363373
var (
364374
modules = make(map[string]ModuleInfo)
365375
modulesMu sync.RWMutex

modules/caddyhttp/reverseproxy/caddyfile.go

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import (
3333
"github.com/caddyserver/caddy/v2/modules/caddyhttp/headers"
3434
"github.com/caddyserver/caddy/v2/modules/caddyhttp/rewrite"
3535
"github.com/caddyserver/caddy/v2/modules/caddytls"
36+
"github.com/caddyserver/caddy/v2/modules/internal/network"
3637
)
3738

3839
func init() {
@@ -979,7 +980,9 @@ func (h *Handler) FinalizeUnmarshalCaddyfile(helper httpcaddyfile.Helper) error
979980
// read_buffer <size>
980981
// write_buffer <size>
981982
// max_response_header <size>
982-
// forward_proxy_url <url>
983+
// network_proxy <module> {
984+
// ...
985+
// }
983986
// dial_timeout <duration>
984987
// dial_fallback_delay <duration>
985988
// response_header_timeout <duration>
@@ -990,6 +993,9 @@ func (h *Handler) FinalizeUnmarshalCaddyfile(helper httpcaddyfile.Helper) error
990993
// tls_insecure_skip_verify
991994
// tls_timeout <duration>
992995
// tls_trusted_ca_certs <cert_files...>
996+
// tls_trust_pool <module> {
997+
// ...
998+
// }
993999
// tls_server_name <sni>
9941000
// tls_renegotiation <level>
9951001
// tls_except_ports <ports...>
@@ -1068,10 +1074,24 @@ func (h *HTTPTransport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
10681074
}
10691075

10701076
case "forward_proxy_url":
1077+
caddy.Log().Warn("The 'forward_proxy_url' field is deprecated. Use 'network_proxy <url>' instead.")
10711078
if !d.NextArg() {
10721079
return d.ArgErr()
10731080
}
1074-
h.ForwardProxyURL = d.Val()
1081+
u := network.ProxyFromURL{URL: d.Val()}
1082+
h.NetworkProxyRaw = caddyconfig.JSONModuleObject(u, "from", "url", nil)
1083+
1084+
case "network_proxy":
1085+
if !d.NextArg() {
1086+
return d.ArgErr()
1087+
}
1088+
modStem := d.Val()
1089+
modID := "caddy.network_proxy." + modStem
1090+
unm, err := caddyfile.UnmarshalModule(d, modID)
1091+
if err != nil {
1092+
return err
1093+
}
1094+
h.NetworkProxyRaw = caddyconfig.JSONModuleObject(unm, "from", modStem, nil)
10751095

10761096
case "dial_timeout":
10771097
if !d.NextArg() {

modules/caddyhttp/reverseproxy/httptransport.go

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import (
2424
weakrand "math/rand"
2525
"net"
2626
"net/http"
27-
"net/url"
2827
"os"
2928
"reflect"
3029
"slices"
@@ -38,8 +37,10 @@ import (
3837
"golang.org/x/net/http2"
3938

4039
"github.com/caddyserver/caddy/v2"
40+
"github.com/caddyserver/caddy/v2/caddyconfig"
4141
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
4242
"github.com/caddyserver/caddy/v2/modules/caddytls"
43+
"github.com/caddyserver/caddy/v2/modules/internal/network"
4344
)
4445

4546
func init() {
@@ -90,6 +91,7 @@ type HTTPTransport struct {
9091
// forward_proxy_url -> upstream
9192
//
9293
// Default: http.ProxyFromEnvironment
94+
// DEPRECATED: Use NetworkProxyRaw|`network_proxy` instead. Subject to removal.
9395
ForwardProxyURL string `json:"forward_proxy_url,omitempty"`
9496

9597
// How long to wait before timing out trying to connect to
@@ -141,6 +143,22 @@ type HTTPTransport struct {
141143
// The pre-configured underlying HTTP transport.
142144
Transport *http.Transport `json:"-"`
143145

146+
// The module that provides the network (forward) proxy
147+
// URL that the HTTP transport will use to proxy
148+
// requests to the upstream. See [http.Transport.Proxy](https://pkg.go.dev/net/http#Transport.Proxy)
149+
// for information regarding supported protocols.
150+
//
151+
// Providing a value to this parameter results in requests
152+
// flowing through the reverse_proxy in the following way:
153+
//
154+
// User Agent ->
155+
// reverse_proxy ->
156+
// [proxy provided by the module] -> upstream
157+
//
158+
// If nil, defaults to reading the `HTTP_PROXY`,
159+
// `HTTPS_PROXY`, and `NO_PROXY` environment variables.
160+
NetworkProxyRaw json.RawMessage `json:"network_proxy,omitempty" caddy:"namespace=caddy.network_proxy inline_key=from"`
161+
144162
h2cTransport *http2.Transport
145163
h3Transport *http3.Transport // TODO: EXPERIMENTAL (May 2024)
146164
}
@@ -328,16 +346,22 @@ func (h *HTTPTransport) NewTransport(caddyCtx caddy.Context) (*http.Transport, e
328346
}
329347

330348
// negotiate any HTTP/SOCKS proxy for the HTTP transport
331-
var proxy func(*http.Request) (*url.URL, error)
349+
proxy := http.ProxyFromEnvironment
332350
if h.ForwardProxyURL != "" {
333-
pUrl, err := url.Parse(h.ForwardProxyURL)
351+
caddyCtx.Logger().Warn("forward_proxy_url is deprecated; use network_proxy instead")
352+
u := network.ProxyFromURL{URL: h.ForwardProxyURL}
353+
h.NetworkProxyRaw = caddyconfig.JSONModuleObject(u, "from", "url", nil)
354+
}
355+
if len(h.NetworkProxyRaw) != 0 {
356+
proxyMod, err := caddyCtx.LoadModule(h, "ForwardProxyRaw")
334357
if err != nil {
335-
return nil, fmt.Errorf("failed to parse transport proxy url: %v", err)
358+
return nil, fmt.Errorf("failed to load network_proxy module: %v", err)
359+
}
360+
if m, ok := proxyMod.(caddy.ProxyFuncProducer); ok {
361+
proxy = m.ProxyFunc()
362+
} else {
363+
return nil, fmt.Errorf("network_proxy module is not `(func(*http.Request) (*url.URL, error))``")
336364
}
337-
caddyCtx.Logger().Info("setting transport proxy url", zap.String("url", h.ForwardProxyURL))
338-
proxy = http.ProxyURL(pUrl)
339-
} else {
340-
proxy = http.ProxyFromEnvironment
341365
}
342366

343367
rt := &http.Transport{

modules/caddytls/acmeissuer.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,9 @@ type ACMEIssuer struct {
106106
// be used. EXPERIMENTAL: Subject to change.
107107
CertificateLifetime caddy.Duration `json:"certificate_lifetime,omitempty"`
108108

109+
// Forward proxy module
110+
NetworkProxyRaw json.RawMessage `json:"network_proxy,omitempty" caddy:"namespace=caddy.network_proxy inline_key=from"`
111+
109112
rootPool *x509.CertPool
110113
logger *zap.Logger
111114

@@ -194,15 +197,15 @@ func (iss *ACMEIssuer) Provision(ctx caddy.Context) error {
194197
}
195198

196199
var err error
197-
iss.template, err = iss.makeIssuerTemplate()
200+
iss.template, err = iss.makeIssuerTemplate(ctx)
198201
if err != nil {
199202
return err
200203
}
201204

202205
return nil
203206
}
204207

205-
func (iss *ACMEIssuer) makeIssuerTemplate() (certmagic.ACMEIssuer, error) {
208+
func (iss *ACMEIssuer) makeIssuerTemplate(ctx caddy.Context) (certmagic.ACMEIssuer, error) {
206209
template := certmagic.ACMEIssuer{
207210
CA: iss.CA,
208211
TestCA: iss.TestCA,
@@ -216,6 +219,18 @@ func (iss *ACMEIssuer) makeIssuerTemplate() (certmagic.ACMEIssuer, error) {
216219
Logger: iss.logger,
217220
}
218221

222+
if len(iss.NetworkProxyRaw) != 0 {
223+
proxyMod, err := ctx.LoadModule(iss, "ForwardProxyRaw")
224+
if err != nil {
225+
return template, fmt.Errorf("failed to load network_proxy module: %v", err)
226+
}
227+
if m, ok := proxyMod.(caddy.ProxyFuncProducer); ok {
228+
template.HTTPProxy = m.ProxyFunc()
229+
} else {
230+
return template, fmt.Errorf("network_proxy module is not `(func(*http.Request) (*url.URL, error))``")
231+
}
232+
}
233+
219234
if iss.Challenges != nil {
220235
if iss.Challenges.HTTP != nil {
221236
template.DisableHTTPChallenge = iss.Challenges.HTTP.Disabled

0 commit comments

Comments
 (0)