namecheap: add experimental proxy support (#2715)

This commit is contained in:
Ludovic Fernandez
2025-11-14 14:12:57 +01:00
committed by GitHub
parent b338263c96
commit a8226a6713
5 changed files with 120 additions and 6 deletions

View File

@@ -216,11 +216,7 @@ linters:
text: load is a global variable
linters:
- gochecknoglobals
- path: providers/dns/([\d\w]+/)*[\d\w]+_test.go
text: envTest is a global variable
linters:
- gochecknoglobals
- path: providers/http/([\d\w]+/)*[\d\w]+_test.go
- path: providers/(dns|http)/([\d\w]+/)*[\d\w]+_test.go
text: envTest is a global variable
linters:
- gochecknoglobals
@@ -228,6 +224,10 @@ linters:
text: testCases is a global variable
linters:
- gochecknoglobals
- path: providers/dns/namecheap/transport.go
text: (envProxyOnce|envProxyFuncValue) is a global variable
linters:
- gochecknoglobals
- path: providers/dns/acmedns/mock_test.go
text: egTestAccount is a global variable
linters:

View File

@@ -91,6 +91,9 @@ func (d *DumpTransport) RoundTrip(h *http.Request) (*http.Response, error) {
_, _ = fmt.Fprintln(d.writer, d.redact(data))
resp, err := d.rt.RoundTrip(h)
if err != nil {
return nil, err
}
data, _ = httputil.DumpResponse(resp, true)

View File

@@ -76,7 +76,8 @@ func NewDefaultConfig() *Config {
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, time.Hour),
PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 15*time.Second),
HTTPClient: &http.Client{
Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, time.Minute),
Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, time.Minute),
Transport: defaultTransport(envNamespace),
},
}
}

View File

@@ -0,0 +1,71 @@
package namecheap
import (
"net/http"
"net/url"
"strings"
"sync"
"github.com/go-acme/lego/v4/platform/config/env"
"golang.org/x/net/http/httpproxy"
)
const (
envHTTPProxy = "HTTP_PROXY"
envHTTPProxyLower = "http_proxy"
envHTTPSProxy = "HTTPS_PROXY"
envHTTPSProxyLower = "https_proxy"
envNoProxy = "NO_PROXY"
envNoProxyLower = "no_proxy"
envRequestMethod = "REQUEST_METHOD"
)
// Allows lazy loading of the proxy.
var (
envProxyOnce sync.Once
envProxyFuncValue func(*url.URL) (*url.URL, error)
)
func defaultTransport(namespace string) http.RoundTripper {
tr, ok := http.DefaultTransport.(*http.Transport)
if !ok {
return nil
}
clone := tr.Clone()
clone.Proxy = proxyFromEnvironment(namespace)
return clone
}
// Inspired by:
// - https://pkg.go.dev/net/http#ProxyFromEnvironment
// - https://pkg.go.dev/golang.org/x/net/http/httpproxy#FromEnvironment
func envProxyFunc(namespace string) func(*url.URL) (*url.URL, error) {
envProxyOnce.Do(func() {
cfg := &httpproxy.Config{
HTTPProxy: getEnv(namespace, envHTTPProxy, envHTTPProxyLower),
HTTPSProxy: getEnv(namespace, envHTTPSProxy, envHTTPSProxyLower),
NoProxy: getEnv(namespace, envNoProxy, envNoProxyLower),
CGI: env.GetOneWithFallback(namespace+envRequestMethod, "", env.ParseString, envRequestMethod) != "",
}
envProxyFuncValue = cfg.ProxyFunc()
})
return envProxyFuncValue
}
// Inspired by:
// - https://pkg.go.dev/net/http#ProxyFromEnvironment
// - https://pkg.go.dev/golang.org/x/net/http/httpproxy#FromEnvironment
func proxyFromEnvironment(namespace string) func(req *http.Request) (*url.URL, error) {
return func(req *http.Request) (*url.URL, error) {
return envProxyFunc(namespace)(req.URL)
}
}
func getEnv(namespace, baseEnvName, baseEnvNameLower string) string {
return env.GetOneWithFallback(namespace+baseEnvName, "", env.ParseString,
strings.ToLower(namespace)+baseEnvNameLower, baseEnvName, baseEnvNameLower)
}

View File

@@ -0,0 +1,39 @@
package namecheap
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/go-acme/lego/v4/platform/tester/servermock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_defaultTransport(t *testing.T) {
client := servermock.NewBuilder(
func(server *httptest.Server) (*http.Client, error) {
cl := server.Client()
t.Setenv("NAMECHEAP_HTTP_PROXY", server.URL)
cl.Transport = defaultTransport(envNamespace)
return cl, nil
}).
Route("/",
servermock.Noop().WithStatusCode(http.StatusTeapot)).
Build(t)
req, err := http.NewRequest(http.MethodGet, "http://example.com", nil)
require.NoError(t, err)
resp, err := client.Do(req)
require.NoError(t, err)
t.Cleanup(func() {
_ = resp.Body.Close()
})
assert.Equal(t, http.StatusTeapot, resp.StatusCode)
}