mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-26 13:43:47 +03:00
core: add modular network_proxy
support
Co-authored-by: @ImpostorKeanu Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
This commit is contained in:
parent
aca4002fd8
commit
62c711c66e
3 changed files with 161 additions and 5 deletions
|
@ -135,6 +135,9 @@ type HTTPTransport struct {
|
|||
// The pre-configured underlying HTTP transport.
|
||||
Transport *http.Transport `json:"-"`
|
||||
|
||||
// Forward proxy module
|
||||
NetworkProxyRaw json.RawMessage `json:"network_proxy,omitempty" caddy:"namespace=caddy.network_proxy.source inline_key=from"`
|
||||
|
||||
h2cTransport *http2.Transport
|
||||
h3Transport *http3.RoundTripper // TODO: EXPERIMENTAL (May 2024)
|
||||
}
|
||||
|
@ -297,7 +300,19 @@ func (h *HTTPTransport) NewTransport(caddyCtx caddy.Context) (*http.Transport, e
|
|||
}
|
||||
|
||||
// negotiate any HTTP/SOCKS proxy for the HTTP transport
|
||||
var proxy func(*http.Request) (*url.URL, error)
|
||||
proxy := http.ProxyFromEnvironment
|
||||
if len(h.NetworkProxyRaw) != 0 {
|
||||
proxyMod, err := caddyCtx.LoadModule(h, "ForwardProxyRaw")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load network_proxy module: %v", err)
|
||||
}
|
||||
if m, ok := proxyMod.(caddy.ProxyFuncProducer); ok {
|
||||
proxy = m.ProxyFunc()
|
||||
} else {
|
||||
return nil, fmt.Errorf("network_proxy module is not `(func(*http.Request) (*url.URL, error))``")
|
||||
}
|
||||
}
|
||||
|
||||
if h.ForwardProxyURL != "" {
|
||||
pUrl, err := url.Parse(h.ForwardProxyURL)
|
||||
if err != nil {
|
||||
|
@ -305,8 +320,6 @@ func (h *HTTPTransport) NewTransport(caddyCtx caddy.Context) (*http.Transport, e
|
|||
}
|
||||
caddyCtx.Logger().Info("setting transport proxy url", zap.String("url", h.ForwardProxyURL))
|
||||
proxy = http.ProxyURL(pUrl)
|
||||
} else {
|
||||
proxy = http.ProxyFromEnvironment
|
||||
}
|
||||
|
||||
rt := &http.Transport{
|
||||
|
|
|
@ -97,6 +97,9 @@ type ACMEIssuer struct {
|
|||
// be used. EXPERIMENTAL: Subject to change.
|
||||
CertificateLifetime caddy.Duration `json:"certificate_lifetime,omitempty"`
|
||||
|
||||
// Forward proxy module
|
||||
NetworkProxyRaw json.RawMessage `json:"network_proxy,omitempty" caddy:"namespace=caddy.network_proxy.source inline_key=from"`
|
||||
|
||||
rootPool *x509.CertPool
|
||||
logger *zap.Logger
|
||||
|
||||
|
@ -170,7 +173,7 @@ func (iss *ACMEIssuer) Provision(ctx caddy.Context) error {
|
|||
}
|
||||
|
||||
var err error
|
||||
iss.template, err = iss.makeIssuerTemplate()
|
||||
iss.template, err = iss.makeIssuerTemplate(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -178,7 +181,7 @@ func (iss *ACMEIssuer) Provision(ctx caddy.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (iss *ACMEIssuer) makeIssuerTemplate() (certmagic.ACMEIssuer, error) {
|
||||
func (iss *ACMEIssuer) makeIssuerTemplate(ctx caddy.Context) (certmagic.ACMEIssuer, error) {
|
||||
template := certmagic.ACMEIssuer{
|
||||
CA: iss.CA,
|
||||
TestCA: iss.TestCA,
|
||||
|
@ -191,6 +194,18 @@ func (iss *ACMEIssuer) makeIssuerTemplate() (certmagic.ACMEIssuer, error) {
|
|||
Logger: iss.logger,
|
||||
}
|
||||
|
||||
if len(iss.NetworkProxyRaw) != 0 {
|
||||
proxyMod, err := ctx.LoadModule(iss, "ForwardProxyRaw")
|
||||
if err != nil {
|
||||
return template, fmt.Errorf("failed to load network_proxy module: %v", err)
|
||||
}
|
||||
if m, ok := proxyMod.(caddy.ProxyFuncProducer); ok {
|
||||
template.HTTPProxy = m.ProxyFunc()
|
||||
} else {
|
||||
return template, fmt.Errorf("network_proxy module is not `(func(*http.Request) (*url.URL, error))``")
|
||||
}
|
||||
}
|
||||
|
||||
if iss.Challenges != nil {
|
||||
if iss.Challenges.HTTP != nil {
|
||||
template.DisableHTTPChallenge = iss.Challenges.HTTP.Disabled
|
||||
|
|
128
network.go
Normal file
128
network.go
Normal file
|
@ -0,0 +1,128 @@
|
|||
package caddy
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegisterModule(ProxyFromEnvironment{})
|
||||
RegisterModule(ProxyFromURL{})
|
||||
}
|
||||
|
||||
type ProxyFuncProducer interface {
|
||||
ProxyFunc() func(*http.Request) (*url.URL, error)
|
||||
}
|
||||
|
||||
type ProxyFromEnvironment struct{}
|
||||
|
||||
// ProxyFunc implements ProxyFuncProducer.
|
||||
func (p ProxyFromEnvironment) ProxyFunc() func(*http.Request) (*url.URL, error) {
|
||||
return http.ProxyFromEnvironment
|
||||
}
|
||||
|
||||
// CaddyModule implements Module.
|
||||
func (p ProxyFromEnvironment) CaddyModule() ModuleInfo {
|
||||
return ModuleInfo{
|
||||
ID: "caddy.network_proxy.source.environment",
|
||||
New: func() Module {
|
||||
return ProxyFromEnvironment{}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type ProxyFromURL struct {
|
||||
URL string `json:"url"`
|
||||
|
||||
ctx Context
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// CaddyModule implements Module.
|
||||
func (p ProxyFromURL) CaddyModule() ModuleInfo {
|
||||
return ModuleInfo{
|
||||
ID: "caddy.network_proxy.source.url",
|
||||
New: func() Module {
|
||||
return &ProxyFromURL{}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ProxyFromURL) Provision(ctx Context) error {
|
||||
p.ctx = ctx
|
||||
p.logger = ctx.Logger()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate implements Validator.
|
||||
func (p ProxyFromURL) Validate() error {
|
||||
if _, err := url.Parse(p.URL); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ProxyFunc implements ProxyFuncProducer.
|
||||
func (p ProxyFromURL) ProxyFunc() func(*http.Request) (*url.URL, error) {
|
||||
if strings.Contains(p.URL, "{") && strings.Contains(p.URL, "}") {
|
||||
// courtesy of @ImpostorKeanu: https://github.com/caddyserver/caddy/pull/6397
|
||||
return func(r *http.Request) (*url.URL, error) {
|
||||
// retrieve the replacer from context.
|
||||
repl, ok := r.Context().Value(ReplacerCtxKey).(*Replacer)
|
||||
if !ok {
|
||||
err := errors.New("failed to obtain replacer from request")
|
||||
p.logger.Error(err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// apply placeholders to the value
|
||||
// note: h.ForwardProxyURL should never be empty at this point
|
||||
s := repl.ReplaceAll(p.URL, "")
|
||||
if s == "" {
|
||||
p.logger.Error("forward_proxy_url was empty after applying placeholders",
|
||||
zap.String("initial_value", p.URL),
|
||||
zap.String("final_value", s),
|
||||
zap.String("hint", "check for invalid placeholders"))
|
||||
return nil, errors.New("empty value for forward_proxy_url")
|
||||
}
|
||||
|
||||
// parse the url
|
||||
pUrl, err := url.Parse(s)
|
||||
if err != nil {
|
||||
p.logger.Warn("failed to derive transport proxy from forward_proxy_url")
|
||||
pUrl = nil
|
||||
} else if pUrl.Host == "" || strings.Split("", pUrl.Host)[0] == ":" {
|
||||
// url.Parse does not return an error on these values:
|
||||
//
|
||||
// - http://:80
|
||||
// - pUrl.Host == ":80"
|
||||
// - /some/path
|
||||
// - pUrl.Host == ""
|
||||
//
|
||||
// Super edge cases, but humans are human.
|
||||
err = errors.New("supplied forward_proxy_url is missing a host value")
|
||||
pUrl = nil
|
||||
} else {
|
||||
p.logger.Debug("setting transport proxy url", zap.String("url", s))
|
||||
}
|
||||
|
||||
return pUrl, err
|
||||
}
|
||||
}
|
||||
return func(*http.Request) (*url.URL, error) {
|
||||
return url.Parse(p.URL)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
_ Module = ProxyFromEnvironment{}
|
||||
_ ProxyFuncProducer = ProxyFromEnvironment{}
|
||||
_ Module = ProxyFromURL{}
|
||||
_ Provisioner = &ProxyFromURL{}
|
||||
_ Validator = ProxyFromURL{}
|
||||
_ ProxyFuncProducer = ProxyFromURL{}
|
||||
)
|
Loading…
Reference in a new issue