Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

core: add modular network_proxy support #6399

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions modules/caddyhttp/reverseproxy/httptransport.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ type HTTPTransport struct {
// The pre-configured underlying HTTP transport.
Transport *http.Transport `json:"-"`

// Forward proxy module
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs expanded godoc

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

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)
}
Expand Down Expand Up @@ -297,16 +300,26 @@ 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 {
return nil, fmt.Errorf("failed to parse transport proxy url: %v", err)
}
caddyCtx.Logger().Info("setting transport proxy url", zap.String("url", h.ForwardProxyURL))
proxy = http.ProxyURL(pUrl)
} else {
proxy = http.ProxyFromEnvironment
}

rt := &http.Transport{
Expand Down
19 changes: 17 additions & 2 deletions modules/caddytls/acmeissuer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -170,15 +173,15 @@ 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
}

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,
Expand All @@ -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
Expand Down
128 changes: 128 additions & 0 deletions network.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package caddy

import (
"errors"
"net/http"
"net/url"
"strings"

"go.uber.org/zap"
)

func init() {
RegisterModule(ProxyFromEnvironment{})
mohammed90 marked this conversation as resolved.
Show resolved Hide resolved
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