Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions proxy/proxy_audit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ func TestAuditURLIsFullyFormed_HTTPS(t *testing.T) {
WithCertManager(t.TempDir()),
WithAllowedDomain("dev.coder.com"),
WithAuditor(auditor),
WithFakeUpstream(fakePublicHosts()),
).Start()
defer pt.Stop()

Expand Down
26 changes: 6 additions & 20 deletions proxy/proxy_connect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,29 +13,22 @@ func TestProxyServerImplicitCONNECT(t *testing.T) {
WithCertManager("/tmp/boundary_connect_test"),
WithAllowedDomain("dev.coder.com"),
WithAllowedDomain("jsonplaceholder.typicode.com"),
WithFakeUpstream(fakePublicHosts()),
).
Start()
defer pt.Stop()

// Test HTTPS request through proxy transport (automatic CONNECT)
t.Run("HTTPSRequestThroughProxyTransport", func(t *testing.T) {
expectedResponse := `{"message":"👋"}
`
// Because this is HTTPS, Go will issue CONNECT localhost:8080 → dev.coder.com:443
pt.ExpectAllowedViaProxy("https://dev.coder.com/api/v2", expectedResponse)
pt.ExpectAllowedViaProxy("https://dev.coder.com/api/v2", devCoderAPIResponse)
})

// Test HTTP request through proxy transport
t.Run("HTTPRequestThroughProxyTransport", func(t *testing.T) {
expectedResponse := `{
"userId": 1,
"id": 1,
"title": "delectus aut autem",
"completed": false
}`
// For HTTP requests, Go will send the request directly to the proxy
// The proxy will forward it to the target server
pt.ExpectAllowedViaProxy("http://jsonplaceholder.typicode.com/todos/1", expectedResponse)
pt.ExpectAllowedViaProxy("http://jsonplaceholder.typicode.com/todos/1", todoResponse)
})
}

Expand All @@ -45,6 +38,7 @@ func TestMultipleRequestsOverExplicitCONNECT(t *testing.T) {
WithCertManager("/tmp/boundary_explicit_connect_test"),
WithAllowedDomain("dev.coder.com"),
WithAllowedDomain("jsonplaceholder.typicode.com"),
WithFakeUpstream(fakePublicHosts()),
).
Start()
defer pt.Stop()
Expand All @@ -60,22 +54,14 @@ func TestMultipleRequestsOverExplicitCONNECT(t *testing.T) {
t.Run("AllowedRequestToDevCoder", func(t *testing.T) {
body1, err := tunnel.sendRequest("dev.coder.com", "/api/v2")
require.NoError(t, err, "Failed to send first request")
expectedResponse1 := `{"message":"👋"}
`
require.Equal(t, expectedResponse1, string(body1), "First response does not match")
require.Equal(t, devCoderAPIResponse, string(body1), "First response does not match")
})

// Positive test: Send second request to a different domain (jsonplaceholder.typicode.com) over the same tunnel
t.Run("AllowedRequestToJsonPlaceholder", func(t *testing.T) {
body2, err := tunnel.sendRequest("jsonplaceholder.typicode.com", "/todos/1")
require.NoError(t, err, "Failed to send second request")
expectedResponse2 := `{
"userId": 1,
"id": 1,
"title": "delectus aut autem",
"completed": false
}`
require.Equal(t, expectedResponse2, string(body2), "Second response does not match")
require.Equal(t, todoResponse, string(body2), "Second response does not match")
})

// Negative test: Try to send request to a blocked domain over the same tunnel
Expand Down
56 changes: 56 additions & 0 deletions proxy/proxy_framework_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package proxy

import (
"bufio"
"context"
"crypto/tls"
"fmt"
"io"
Expand Down Expand Up @@ -137,6 +138,61 @@ func WithForwardTransport(transport http.RoundTripper) ProxyTestOption {
}
}

// Canned upstream responses.
const (
devCoderAPIResponse = "{\"message\":\"👋\"}\n"
todoResponse = `{
"userId": 1,
"id": 1,
"title": "delectus aut autem",
"completed": false
}`
)

// fakePublicHosts serves canned responses for the public hosts that proxy
// tests allow, so the tests do not depend on those hosts being reachable or
// unchanged. Use it with WithFakeUpstream.
func fakePublicHosts() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch {
case r.Host == "dev.coder.com" && r.URL.Path == "/api/v2":
_, _ = io.WriteString(w, devCoderAPIResponse)
case r.Host == "jsonplaceholder.typicode.com" && r.URL.Path == "/todos/1":
_, _ = io.WriteString(w, todoResponse)
default:
http.NotFound(w, r)
}
})
}

// WithFakeUpstream makes the proxy forward allowed requests to local test
// servers running handler instead of the internet. Requests for port 443
// reach a TLS server, and all others reach a plain HTTP server. The handler
// sees the original Host header.
func WithFakeUpstream(handler http.Handler) ProxyTestOption {
return func(pt *ProxyTest) {
plain := httptest.NewServer(handler)
secure := httptest.NewTLSServer(handler)
pt.t.Cleanup(plain.Close)
pt.t.Cleanup(secure.Close)

transport := secure.Client().Transport.(*http.Transport).Clone()
// The httptest certificate is valid for example.com rather than for
// the hosts that tests request.
transport.TLSClientConfig.ServerName = "example.com"
transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
target := plain.Listener.Addr().String()
if _, port, err := net.SplitHostPort(addr); err == nil && port == "443" {
target = secure.Listener.Addr().String()
}
var dialer net.Dialer
return dialer.DialContext(ctx, network, target)
}
pt.t.Cleanup(transport.CloseIdleConnections)
pt.forwardTransport = transport
}
}

// Start starts the proxy server
func (pt *ProxyTest) Start() *ProxyTest {
pt.t.Helper()
Expand Down
14 changes: 4 additions & 10 deletions proxy/proxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,13 @@ import (
func TestProxyServerBasicHTTP(t *testing.T) {
pt := NewProxyTest(t,
WithAllowedDomain("jsonplaceholder.typicode.com"),
WithFakeUpstream(fakePublicHosts()),
).
Start()
defer pt.Stop()

t.Run("BasicHTTPRequest", func(t *testing.T) {
expectedResponse := `{
"userId": 1,
"id": 1,
"title": "delectus aut autem",
"completed": false
}`
pt.ExpectAllowed("http://localhost:8080/todos/1", "jsonplaceholder.typicode.com", expectedResponse)
pt.ExpectAllowed("http://localhost:8080/todos/1", "jsonplaceholder.typicode.com", todoResponse)
})

t.Run("BlockedHTTPRequest", func(t *testing.T) {
Expand Down Expand Up @@ -49,14 +44,13 @@ func TestProxyServerBasicHTTPS(t *testing.T) {
pt := NewProxyTest(t,
WithCertManager("/tmp/boundary"),
WithAllowedDomain("dev.coder.com"),
WithFakeUpstream(fakePublicHosts()),
).
Start()
defer pt.Stop()

t.Run("BasicHTTPSRequest", func(t *testing.T) {
expectedResponse := `{"message":"👋"}
`
pt.ExpectAllowed("https://localhost:8080/api/v2", "dev.coder.com", expectedResponse)
pt.ExpectAllowed("https://localhost:8080/api/v2", "dev.coder.com", devCoderAPIResponse)
})

t.Run("BlockedHTTPSRequest", func(t *testing.T) {
Expand Down