Skip to content

Commit

Permalink
fix(https): enable external https verifications
Browse files Browse the repository at this point in the history
The local proxy currently assumes that a verification will take place
against either a) a local endpoint or b) an http endpoint. It did not
support external hosts on https.o

It also did not rewrite the Host header correctly (see golang/go#28168)

This change:

1. Rewrites the header during proxying
2. Hard codes the local proxy to run only on http - there is no reason why it should run on https
   even if the endpoint under test _is_.
3. Opens the door for client configured transports during verification,
   to enable self-signed certificates to be easily used
  • Loading branch information
mefellows committed Aug 16, 2019
1 parent 3640617 commit 6eacf8e
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 10 deletions.
20 changes: 12 additions & 8 deletions dsl/pact.go
Original file line number Diff line number Diff line change
Expand Up @@ -328,9 +328,11 @@ func (p *Pact) VerifyProviderRaw(request types.VerifyRequest) (types.ProviderVer

// Configure HTTP Verification Proxy
opts := proxy.Options{
TargetAddress: fmt.Sprintf("%s:%s", u.Hostname(), u.Port()),
TargetScheme: u.Scheme,
Middleware: m,
TargetAddress: fmt.Sprintf("%s:%s", u.Hostname(), u.Port()),
TargetScheme: u.Scheme,
TargetPath: u.Path,
Middleware: m,
InternalRequestPathPrefix: providerStatesSetupPath,
}

// Starts the message wrapper API with hooks back to the state handlers
Expand All @@ -342,13 +344,13 @@ func (p *Pact) VerifyProviderRaw(request types.VerifyRequest) (types.ProviderVer
// Backwards compatibility, setup old provider states URL if given
// Otherwise point to proxy
setupURL := request.ProviderStatesSetupURL
if request.ProviderStatesSetupURL == "" {
setupURL = fmt.Sprintf("%s://localhost:%d/__setup", u.Scheme, port)
if request.ProviderStatesSetupURL == "" && len(request.StateHandlers) > 0 {
setupURL = fmt.Sprintf("http://localhost:%d%s", port, providerStatesSetupPath)
}

// Construct verifier request
verificationRequest := types.VerifyRequest{
ProviderBaseURL: fmt.Sprintf("%s://localhost:%d", u.Scheme, port), //
ProviderBaseURL: fmt.Sprintf("http://localhost:%d", port),
PactURLs: request.PactURLs,
BrokerURL: request.BrokerURL,
Tags: request.Tags,
Expand Down Expand Up @@ -413,7 +415,7 @@ var checkCliCompatibility = func() {
func BeforeEachMiddleware(BeforeEach types.Hook) proxy.Middleware {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/__setup" {
if r.URL.Path == providerStatesSetupPath {

log.Println("[DEBUG] executing before hook")
err := BeforeEach()
Expand Down Expand Up @@ -458,7 +460,7 @@ func AfterEachMiddleware(AfterEach types.Hook) proxy.Middleware {
func stateHandlerMiddleware(stateHandlers types.StateHandlers) proxy.Middleware {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/__setup" {
if r.URL.Path == providerStatesSetupPath {
var s *types.ProviderState
decoder := json.NewDecoder(r.Body)
decoder.Decode(&s)
Expand Down Expand Up @@ -706,3 +708,5 @@ func (p *Pact) VerifyMessageConsumer(t *testing.T, message *Message, handler Mes

return err
}

const providerStatesSetupPath = "/__setup"
59 changes: 57 additions & 2 deletions proxy/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"net/http"
"net/http/httputil"
"net/url"
"strings"

"github.com/pact-foundation/pact-go/utils"
)
Expand All @@ -25,12 +26,18 @@ type Options struct {
// TargetAddress is the host:port component to proxy
TargetAddress string

// TargetPath is the path on the target to proxy
TargetPath string

// ProxyPort is the port to make available for proxying
// Defaults to a random port
ProxyPort int

// Middleware to apply to the Proxy
Middleware []Middleware

// Internal request prefix for proxy to not rewrite
InternalRequestPathPrefix string
}

// loggingMiddleware logs requests to the proxy
Expand Down Expand Up @@ -59,13 +66,16 @@ func chainHandlers(mw ...Middleware) Middleware {
// HTTPReverseProxy provides a default setup for proxying
// internal components within the framework
func HTTPReverseProxy(options Options) (int, error) {
log.Println("[DEBUG] starting new proxy with opts", options)
port := options.ProxyPort
var err error

proxy := httputil.NewSingleHostReverseProxy(&url.URL{
url := &url.URL{
Scheme: options.TargetScheme,
Host: options.TargetAddress,
})
Path: options.TargetPath,
}
proxy := createProxy(url, options.InternalRequestPathPrefix)

if port == 0 {
port, err = utils.GetFreePort()
Expand All @@ -82,3 +92,48 @@ func HTTPReverseProxy(options Options) (int, error) {

return port, nil
}

// Adapted from https://github.com/golang/go/blob/master/src/net/http/httputil/reverseproxy.go
func createProxy(target *url.URL, ignorePrefix string) *httputil.ReverseProxy {
targetQuery := target.RawQuery
director := func(req *http.Request) {
if !strings.HasPrefix(req.URL.Path, ignorePrefix) {
log.Println("[DEBUG] setting proxy to target")
log.Println("[DEBUG] incoming request", req.URL)
req.URL.Scheme = target.Scheme
req.URL.Host = target.Host
req.Host = target.Host

req.URL.Path = singleJoiningSlash(target.Path, req.URL.Path)
log.Println("[DEBUG] outgoing request", req.URL)
if targetQuery == "" || req.URL.RawQuery == "" {
req.URL.RawQuery = targetQuery + req.URL.RawQuery
} else {
req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
}
if _, ok := req.Header["User-Agent"]; !ok {
req.Header.Set("User-Agent", "Pact Go")
}
} else {
log.Println("[DEBUG] setting proxy to internal server")
req.URL.Scheme = "http"
req.URL.Host = "localhost"
req.Host = "localhost"
}
}
return &httputil.ReverseProxy{Director: director}
}

// From httputil package
// https://github.com/golang/go/blob/master/src/net/http/httputil/reverseproxy.go
func singleJoiningSlash(a, b string) string {
aslash := strings.HasSuffix(a, "/")
bslash := strings.HasPrefix(b, "/")
switch {
case aslash && bslash:
return a + b[1:]
case !aslash && !bslash:
return a + "/" + b
}
return a + b
}

0 comments on commit 6eacf8e

Please sign in to comment.