|
6 | 6 | "log"
|
7 | 7 | "log/slog"
|
8 | 8 | "net/http"
|
| 9 | + "net/url" |
9 | 10 | "os"
|
10 | 11 | "os/user"
|
11 | 12 | "strconv"
|
@@ -200,3 +201,132 @@ func TestProxyServerBasicHTTPS(t *testing.T) {
|
200 | 201 | err = server.Stop()
|
201 | 202 | require.NoError(t, err)
|
202 | 203 | }
|
| 204 | + |
| 205 | +// TestProxyServerCONNECT tests HTTP CONNECT method for HTTPS tunneling |
| 206 | +func TestProxyServerCONNECT(t *testing.T) { |
| 207 | + // Create test logger |
| 208 | + logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{ |
| 209 | + Level: slog.LevelError, |
| 210 | + })) |
| 211 | + |
| 212 | + // Create test rules (allow all for testing) |
| 213 | + testRules, err := rules.ParseAllowSpecs([]string{"*"}) |
| 214 | + if err != nil { |
| 215 | + t.Fatalf("Failed to parse test rules: %v", err) |
| 216 | + } |
| 217 | + |
| 218 | + // Create rule engine |
| 219 | + ruleEngine := rules.NewRuleEngine(testRules, logger) |
| 220 | + |
| 221 | + // Create mock auditor |
| 222 | + auditor := &mockAuditor{} |
| 223 | + |
| 224 | + // Get current user for TLS setup |
| 225 | + currentUser, err := user.Current() |
| 226 | + if err != nil { |
| 227 | + log.Fatal(err) |
| 228 | + } |
| 229 | + |
| 230 | + uid, _ := strconv.Atoi(currentUser.Uid) |
| 231 | + gid, _ := strconv.Atoi(currentUser.Gid) |
| 232 | + |
| 233 | + // Create TLS certificate manager |
| 234 | + certManager, err := boundary_tls.NewCertificateManager(boundary_tls.Config{ |
| 235 | + Logger: logger, |
| 236 | + ConfigDir: "/tmp/boundary_connect_test", |
| 237 | + Uid: uid, |
| 238 | + Gid: gid, |
| 239 | + }) |
| 240 | + require.NoError(t, err) |
| 241 | + |
| 242 | + // Setup TLS to get cert path for proxy |
| 243 | + tlsConfig, caCertPath, configDir, err := certManager.SetupTLSAndWriteCACert() |
| 244 | + require.NoError(t, err) |
| 245 | + _, _ = caCertPath, configDir |
| 246 | + |
| 247 | + // Create proxy server |
| 248 | + server := NewProxyServer(Config{ |
| 249 | + HTTPPort: 8080, |
| 250 | + |
| 251 | + RuleEngine: ruleEngine, |
| 252 | + Auditor: auditor, |
| 253 | + Logger: logger, |
| 254 | + TLSConfig: tlsConfig, |
| 255 | + }) |
| 256 | + |
| 257 | + // Start server |
| 258 | + err = server.Start() |
| 259 | + require.NoError(t, err) |
| 260 | + |
| 261 | + // Give server time to start |
| 262 | + time.Sleep(100 * time.Millisecond) |
| 263 | + |
| 264 | + // Test HTTPS request through proxy transport (automatic CONNECT) |
| 265 | + t.Run("HTTPSRequestThroughProxyTransport", func(t *testing.T) { |
| 266 | + // Create proxy URL |
| 267 | + proxyURL, err := url.Parse("http://localhost:8080") |
| 268 | + require.NoError(t, err) |
| 269 | + |
| 270 | + // Create HTTP client with proxy transport |
| 271 | + client := &http.Client{ |
| 272 | + Transport: &http.Transport{ |
| 273 | + Proxy: http.ProxyURL(proxyURL), |
| 274 | + TLSClientConfig: &tls.Config{ |
| 275 | + InsecureSkipVerify: true, // Skip cert verification for testing |
| 276 | + }, |
| 277 | + }, |
| 278 | + Timeout: 10 * time.Second, |
| 279 | + } |
| 280 | + |
| 281 | + // Because this is HTTPS, Go will issue CONNECT localhost:8080 → dev.coder.com:443 |
| 282 | + resp, err := client.Get("https://dev.coder.com/api/v2") |
| 283 | + require.NoError(t, err) |
| 284 | + |
| 285 | + // Read response |
| 286 | + body, err := io.ReadAll(resp.Body) |
| 287 | + require.NoError(t, err) |
| 288 | + require.NoError(t, resp.Body.Close()) |
| 289 | + |
| 290 | + // Verify response contains expected content |
| 291 | + expectedResponse := `{"message":"👋"} |
| 292 | +` |
| 293 | + require.Equal(t, expectedResponse, string(body)) |
| 294 | + }) |
| 295 | + |
| 296 | + // Test HTTP request through proxy transport |
| 297 | + t.Run("HTTPRequestThroughProxyTransport", func(t *testing.T) { |
| 298 | + // Create proxy URL |
| 299 | + proxyURL, err := url.Parse("http://localhost:8080") |
| 300 | + require.NoError(t, err) |
| 301 | + |
| 302 | + // Create HTTP client with proxy transport |
| 303 | + client := &http.Client{ |
| 304 | + Transport: &http.Transport{ |
| 305 | + Proxy: http.ProxyURL(proxyURL), |
| 306 | + }, |
| 307 | + Timeout: 10 * time.Second, |
| 308 | + } |
| 309 | + |
| 310 | + // For HTTP requests, Go will send the request directly to the proxy |
| 311 | + // The proxy will forward it to the target server |
| 312 | + resp, err := client.Get("http://jsonplaceholder.typicode.com/todos/1") |
| 313 | + require.NoError(t, err) |
| 314 | + |
| 315 | + // Read response |
| 316 | + body, err := io.ReadAll(resp.Body) |
| 317 | + require.NoError(t, err) |
| 318 | + require.NoError(t, resp.Body.Close()) |
| 319 | + |
| 320 | + // Verify response contains expected content |
| 321 | + expectedResponse := `{ |
| 322 | + "userId": 1, |
| 323 | + "id": 1, |
| 324 | + "title": "delectus aut autem", |
| 325 | + "completed": false |
| 326 | +}` |
| 327 | + require.Equal(t, expectedResponse, string(body)) |
| 328 | + }) |
| 329 | + |
| 330 | + err = server.Stop() |
| 331 | + require.NoError(t, err) |
| 332 | +} |
0 commit comments