mirror of
https://github.com/umputun/reproxy.git
synced 2025-09-16 08:46:17 +02:00
fix: use alternative port for ACME HTTP challenge in CI environments
- Configure CertMagic to use RedirHTTPPort as AltHTTPPort for HTTP challenges - Improve HTTP-to-HTTPS redirect handler to omit standard HTTPS port in URL - Update tests to use random high-numbered ports for HTTP challenges - Fix various linting issues in the code
This commit is contained in:
@@ -60,7 +60,7 @@ var opts struct {
|
||||
Route53 struct {
|
||||
Region string `long:"region" env:"REGION" description:"AWS region"`
|
||||
Profile string `long:"profile" env:"PROFILE" description:"AWS profile"`
|
||||
AccessKeyId string `long:"access-key-id" env:"ACCESS_KEY_ID" description:"AWS access key id"`
|
||||
AccessKeyID string `long:"access-key-id" env:"ACCESS_KEY_ID" description:"AWS access key id"`
|
||||
SecretAccessKey string `long:"secret-access-key" env:"SECRET_ACCESS_KEY" description:"AWS secret access key"`
|
||||
SessionToken string `long:"session-token" env:"SESSION_TOKEN" description:"AWS session token"`
|
||||
HostedZoneID string `long:"hosted-zone-id" env:"HOSTED_ZONE_ID" description:"AWS hosted zone id"`
|
||||
@@ -442,7 +442,7 @@ func makeSSLConfig() (config proxy.SSLConfig, err error) {
|
||||
config.DNSProvider = &route53.Provider{
|
||||
Region: opts.SSL.DNS.Route53.Region,
|
||||
Profile: opts.SSL.DNS.Route53.Profile,
|
||||
AccessKeyId: opts.SSL.DNS.Route53.AccessKeyId,
|
||||
AccessKeyId: opts.SSL.DNS.Route53.AccessKeyID,
|
||||
SecretAccessKey: opts.SSL.DNS.Route53.SecretAccessKey,
|
||||
SessionToken: opts.SSL.DNS.Route53.SessionToken,
|
||||
HostedZoneID: opts.SSL.DNS.Route53.HostedZoneID,
|
||||
|
@@ -108,13 +108,15 @@ func Test_MainWithSSL(t *testing.T) {
|
||||
setupLogger()
|
||||
|
||||
port := 40000 + int(rand.Int31n(10000))
|
||||
httpPort := 50000 + int(rand.Int31n(10000)) // use a high port for HTTP redirect
|
||||
os.Args = []string{"test", "--static.enabled",
|
||||
"--static.rule=*,/svc1, https://httpbin.org/get,https://feedmaster.umputun.com/ping",
|
||||
"--static.rule=*,/svc2/(.*), https://echo.umputun.com/$1,https://feedmaster.umputun.com/ping",
|
||||
"--file.enabled", "--file.name=discovery/provider/testdata/config.yml",
|
||||
"--dbg", "--logger.enabled", "--logger.stdout", "--logger.file=/tmp/reproxy.log",
|
||||
"--listen=127.0.0.1:" + strconv.Itoa(port), "--signature", "--ssl.type=static",
|
||||
"--ssl.cert=proxy/testdata/localhost.crt", "--ssl.key=proxy/testdata/localhost.key"}
|
||||
"--ssl.cert=proxy/testdata/localhost.crt", "--ssl.key=proxy/testdata/localhost.key",
|
||||
"--ssl.http-port=" + strconv.Itoa(httpPort)}
|
||||
defer os.Remove("/tmp/reproxy.log")
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
|
@@ -403,8 +403,12 @@ func (s *ACMEServer) certCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/pem-certificate-chain")
|
||||
pem.Encode(w, &pem.Block{Type: "CERTIFICATE", Bytes: cert})
|
||||
pem.Encode(w, &pem.Block{Type: "CERTIFICATE", Bytes: s.rootCert})
|
||||
if err := pem.Encode(w, &pem.Block{Type: "CERTIFICATE", Bytes: cert}); err != nil {
|
||||
s.t.Logf("failed to encode certificate: %v", err)
|
||||
}
|
||||
if err := pem.Encode(w, &pem.Block{Type: "CERTIFICATE", Bytes: s.rootCert}); err != nil {
|
||||
s.t.Logf("failed to encode root certificate: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// POST /challenge - verify a challenge
|
||||
@@ -449,11 +453,11 @@ func (s *ACMEServer) challengeCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case challengeType == "http-01":
|
||||
switch challengeType {
|
||||
case "http-01":
|
||||
s.verifyHTTP01Challenge(w, token, domain)
|
||||
o.HTTP01Accepted = true
|
||||
case challengeType == "dns-01":
|
||||
case "dns-01":
|
||||
s.verifyDNS01Challenge(w, domain)
|
||||
o.DNS01Accepted = true
|
||||
default:
|
||||
|
@@ -64,7 +64,14 @@ func (h *Http) httpChallengeRouter(m AutocertManager) http.Handler {
|
||||
func (h *Http) redirectHandler() http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
server := strings.Split(r.Host, ":")[0]
|
||||
newURL := fmt.Sprintf("https://%s:443%s", server, r.URL.Path)
|
||||
// use standard HTTPS port by default
|
||||
httpsPort := 443
|
||||
// no need to specify the port if it's the standard HTTPS port
|
||||
portSuffix := fmt.Sprintf(":%d", httpsPort)
|
||||
if httpsPort == 443 {
|
||||
portSuffix = ""
|
||||
}
|
||||
newURL := fmt.Sprintf("https://%s%s%s", server, portSuffix, r.URL.Path)
|
||||
if r.URL.RawQuery != "" {
|
||||
newURL += "?" + r.URL.RawQuery
|
||||
}
|
||||
@@ -111,7 +118,7 @@ func (h *Http) makeAutocertManager() AutocertManager {
|
||||
KeySource: certmagic.DefaultKeyGenerator,
|
||||
Storage: &certmagic.FileStorage{Path: h.SSLConfig.ACMELocation},
|
||||
OnDemand: &certmagic.OnDemandConfig{
|
||||
DecisionFunc: func(ctx context.Context, name string) error {
|
||||
DecisionFunc: func(_ context.Context, name string) error {
|
||||
if _, ok := fqdns[name]; ok {
|
||||
return nil
|
||||
}
|
||||
@@ -122,18 +129,29 @@ func (h *Http) makeAutocertManager() AutocertManager {
|
||||
}
|
||||
var cache *certmagic.Cache
|
||||
cache = certmagic.NewCache(certmagic.CacheOptions{
|
||||
GetConfigForCert: func(cert certmagic.Certificate) (*certmagic.Config, error) {
|
||||
GetConfigForCert: func(_ certmagic.Certificate) (*certmagic.Config, error) {
|
||||
return certmagic.New(cache, magicTmpl), nil
|
||||
},
|
||||
Logger: logger,
|
||||
})
|
||||
magic := certmagic.New(cache, magicTmpl)
|
||||
acme := certmagic.NewACMEIssuer(magic, certmagic.ACMEIssuer{
|
||||
|
||||
// configure the ACMEIssuer
|
||||
acmeIssuer := certmagic.ACMEIssuer{
|
||||
CA: h.SSLConfig.ACMEDirectory,
|
||||
Email: h.SSLConfig.ACMEEmail,
|
||||
Agreed: true,
|
||||
Logger: logger,
|
||||
})
|
||||
}
|
||||
|
||||
// use RedirHTTPPort if specified for the HTTP challenge
|
||||
// this allows ACME HTTP challenges to work in environments
|
||||
// where binding to port 80 isn't possible
|
||||
if h.SSLConfig.RedirHTTPPort != 0 {
|
||||
acmeIssuer.AltHTTPPort = h.SSLConfig.RedirHTTPPort
|
||||
}
|
||||
|
||||
acme := certmagic.NewACMEIssuer(magic, acmeIssuer)
|
||||
if h.SSLConfig.DNSProvider != nil {
|
||||
acme.DNS01Solver = &certmagic.DNS01Solver{
|
||||
DNSManager: certmagic.DNSManager{
|
||||
|
@@ -7,6 +7,7 @@ import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
@@ -43,7 +44,7 @@ func TestSSL_Redirect(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
assert.Equal(t, 307, resp.StatusCode)
|
||||
assert.Equal(t, "https://localhost:443/blah?param=1", resp.Header.Get("Location"))
|
||||
assert.Equal(t, "https://localhost/blah?param=1", resp.Header.Get("Location"))
|
||||
}
|
||||
|
||||
func TestSSL_ACME_HTTPChallengeRouter(t *testing.T) {
|
||||
@@ -61,11 +62,21 @@ func TestSSL_ACME_HTTPChallengeRouter(t *testing.T) {
|
||||
}),
|
||||
)
|
||||
|
||||
// prepare a port for HTTP challenges
|
||||
httpListener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
require.NoError(t, err)
|
||||
_, httpPortStr, err := net.SplitHostPort(httpListener.Addr().String())
|
||||
require.NoError(t, err)
|
||||
httpPort, err := strconv.Atoi(httpPortStr)
|
||||
require.NoError(t, err)
|
||||
httpListener.Close() // close it since we only needed it to get a port
|
||||
|
||||
p := Http{
|
||||
SSLConfig: SSLConfig{
|
||||
ACMELocation: dir,
|
||||
FQDNs: []string{"example.com", "localhost"},
|
||||
ACMEDirectory: cas.URL(),
|
||||
RedirHTTPPort: httpPort, // use high-numbered port for ACME HTTP challenge
|
||||
},
|
||||
}
|
||||
|
||||
@@ -86,7 +97,7 @@ func TestSSL_ACME_HTTPChallengeRouter(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
assert.Equal(t, 307, resp.StatusCode)
|
||||
assert.Equal(t, "https://localhost:443/blah?param=1", resp.Header.Get("Location"))
|
||||
assert.Equal(t, "https://localhost/blah?param=1", resp.Header.Get("Location"))
|
||||
|
||||
// acquire new cert from CA and check it with timeout
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
@@ -197,8 +208,8 @@ func TestSSL_ACME_DNSChallenge(t *testing.T) {
|
||||
// signal that the DNS server is ready to accept connections
|
||||
close(dnsReady)
|
||||
// set a timeout for the DNS server
|
||||
err := dnsMock.ActivateAndServe()
|
||||
if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
|
||||
dnsErr := dnsMock.ActivateAndServe()
|
||||
if dnsErr != nil && !strings.Contains(dnsErr.Error(), "use of closed network connection") {
|
||||
t.Logf("DNS server error: %v", err)
|
||||
}
|
||||
}()
|
||||
@@ -211,11 +222,21 @@ func TestSSL_ACME_DNSChallenge(t *testing.T) {
|
||||
|
||||
t.Log("dns server started at", dnsMock.Addr)
|
||||
|
||||
// prepare a port for HTTP challenges
|
||||
httpListener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
require.NoError(t, err)
|
||||
_, httpPortStr, err := net.SplitHostPort(httpListener.Addr().String())
|
||||
require.NoError(t, err)
|
||||
httpPort, err := strconv.Atoi(httpPortStr)
|
||||
require.NoError(t, err)
|
||||
httpListener.Close() // close it since we only needed it to get a port
|
||||
|
||||
p := Http{
|
||||
SSLConfig: SSLConfig{
|
||||
ACMELocation: dir,
|
||||
FQDNs: []string{"example.com", "localhost"},
|
||||
ACMEDirectory: cas.URL(),
|
||||
RedirHTTPPort: httpPort, // use high-numbered port for ACME HTTP challenge
|
||||
DNSProvider: &dnsProviderMock{
|
||||
AppendRecordsFunc: func(ctx context.Context, zone string, recs []libdns.Record) ([]libdns.Record, error) {
|
||||
assert.Equal(t, "example.com.", zone)
|
||||
|
Reference in New Issue
Block a user