...

Source file src/github.com/launchdarkly/go-server-sdk/v6/ldntlm/ntlm_proxy_test.go

Documentation: github.com/launchdarkly/go-server-sdk/v6/ldntlm

     1  package ldntlm
     2  
     3  import (
     4  	"crypto/x509"
     5  	"fmt"
     6  	"io"
     7  	"net/http"
     8  	"net/http/httptest"
     9  	"regexp"
    10  	"testing"
    11  
    12  	"github.com/launchdarkly/go-test-helpers/v3/httphelpers"
    13  
    14  	"github.com/stretchr/testify/assert"
    15  	"github.com/stretchr/testify/require"
    16  
    17  	"github.com/launchdarkly/go-server-sdk/v6/ldhttp"
    18  )
    19  
    20  const (
    21  	username      = "username"
    22  	password      = "password"
    23  	domain        = "domain"
    24  	targetURL     = "http://example.com/test"
    25  	targetServer  = "example.com:80"
    26  	targetURLPath = "/test"
    27  	responseBody  = "hello"
    28  	// The following base64 NTLM message strings/patterns should not be considered authoritative; the exact content will
    29  	// vary depending on the time, the server implementation, etc. We're just verifying that the proxy logic is sending
    30  	// well-formed messages in the order that we expect, and is able to decode a well-formed server response.
    31  	proxyAuthStep1Expected      = "NTLM TlRMTVNTUAABAAAAAZKIoAYABgAoAAAAAAAAAC4AAAAGAbEdAAAAD0RPTUFJTg=="
    32  	proxyAuthStep2Challenge     = "NTLM TlRMTVNTUAACAAAADAAMADAAAAA1gomgZ38cVXpe6WwAAAAAAAAAAEYARgA8AAAAVABFAFMAVABOAFQAAgAMAFQARQBTAFQATgBUAAEADABNAEUATQBCAEUAUgADAB4AbQBlAG0AYgBlAHIALgB0AGUAcwB0AC4AYwBvAG0AAAAAAA=="
    33  	proxyAuthStep3ExpectedRegex = "NTLM TlRMTVNTUAADAAAAAAAAAEAAAAB2AHYAQAAAAAwADAC2AAAAEAAQAMIAAAAUABQA0gAAAAAAAAAAAAAANYK.*AAAAAAgAMAFQARQBTAFQATgBUAAEADABNAEUATQBCAEUAUgADAB4AbQBlAG0AYgBlAHIALgB0AGUAcwB0AC4AYwBvAG0AAAAAAAAAAABUAEUAUwBUAE4AVAB1AHMAZQByAG4AYQBtAGUAZwBvAC0AbgB0AGwAbQBzAHMAcAA="
    34  )
    35  
    36  func TestCanConnectToNTLMProxyServer(t *testing.T) {
    37  	httphelpers.WithServer(makeFakeNTLMProxyHandler(), func(server *httptest.Server) {
    38  		factory, err := NewNTLMProxyHTTPClientFactory(server.URL, username, password, domain)
    39  		require.NoError(t, err)
    40  		client := factory()
    41  
    42  		resp, err := client.Get(targetURL)
    43  		require.NoError(t, err)
    44  		assert.Equal(t, 200, resp.StatusCode)
    45  		body, err := io.ReadAll(resp.Body)
    46  		require.NoError(t, err)
    47  		assert.Equal(t, responseBody, string(body))
    48  	})
    49  }
    50  
    51  func TestCanConnectSecurelyToNTLMProxyServerWithSelfSignedCert(t *testing.T) {
    52  	handler := makeFakeNTLMProxyHandler()
    53  	httphelpers.WithSelfSignedServer(handler, func(server *httptest.Server, certData []byte, certs *x509.CertPool) {
    54  		factory, err := NewNTLMProxyHTTPClientFactory(server.URL, username, password, domain,
    55  			ldhttp.CACertOption(certData))
    56  		require.NoError(t, err)
    57  		client := factory()
    58  
    59  		resp, err := client.Get(targetURL)
    60  		require.NoError(t, err)
    61  		assert.Equal(t, 200, resp.StatusCode)
    62  		body, err := io.ReadAll(resp.Body)
    63  		require.NoError(t, err)
    64  		assert.Equal(t, responseBody, string(body))
    65  	})
    66  }
    67  
    68  func TestInvalidParameters(t *testing.T) {
    69  	_, err := NewNTLMProxyHTTPClientFactory("", "user", "pass", domain)
    70  	assert.Error(t, err)
    71  
    72  	_, err = NewNTLMProxyHTTPClientFactory("http://proxy", "", "pass", domain)
    73  	assert.Error(t, err)
    74  
    75  	_, err = NewNTLMProxyHTTPClientFactory("http://proxy", "user", "", domain)
    76  	assert.Error(t, err)
    77  
    78  	_, err = NewNTLMProxyHTTPClientFactory("://bad", "user", "pass", domain)
    79  	assert.Error(t, err)
    80  
    81  	_, err = NewNTLMProxyHTTPClientFactory("http://proxy", "user", "pass", "domain",
    82  		ldhttp.CACertOption([]byte("not a valid cert")))
    83  	assert.Error(t, err)
    84  }
    85  
    86  func makeFakeNTLMProxyHandler() http.Handler {
    87  	step := 0
    88  	// This is an extremely minimal simulation of an NTLM proxy exchange:
    89  	// 1. Client sends CONNECT request, with Proxy-Authorization header containing "negotiate" message.
    90  	//    Server sends 407 response, with Proxy-Authenticate header containing "challenge" message.
    91  	// 2. Client sends CONNECT request, with Proxy-Authorization header containing "authorization" message.
    92  	//    Server sends 200 response.
    93  	// 3. Client sends GET request for target URL.
    94  	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
    95  		step = step + 1
    96  		expectedMethod := "CONNECT"
    97  		expectedURL := targetServer
    98  		if step == 3 {
    99  			expectedMethod = "GET"
   100  			expectedURL = targetURLPath
   101  		}
   102  		if step < 3 {
   103  			if req.Method != expectedMethod {
   104  				fmt.Printf("Expected %s, got %s for step %d\n", expectedMethod, req.Method, step)
   105  				w.WriteHeader(405)
   106  				return
   107  			}
   108  			if req.RequestURI != expectedURL {
   109  				fmt.Printf("Expected %s, got %s for step %d\n", expectedURL, req.RequestURI, step)
   110  				w.WriteHeader(404)
   111  				return
   112  			}
   113  		}
   114  		proxyAuth := req.Header.Get("Proxy-Authorization")
   115  		badAuth := func() {
   116  			fmt.Printf("Unexpected Proxy-Authorization value: %s\n", proxyAuth)
   117  			w.WriteHeader(401)
   118  		}
   119  		switch step {
   120  		case 1:
   121  			if proxyAuth == proxyAuthStep1Expected {
   122  				w.Header().Set("Proxy-Authenticate", proxyAuthStep2Challenge)
   123  				w.WriteHeader(407)
   124  			} else {
   125  				badAuth()
   126  			}
   127  		case 2:
   128  			if matched, _ := regexp.MatchString(proxyAuthStep3ExpectedRegex, proxyAuth); matched {
   129  				w.WriteHeader(200)
   130  			} else {
   131  				badAuth()
   132  			}
   133  		case 3:
   134  			w.WriteHeader(200)
   135  			w.Write([]byte(responseBody))
   136  		}
   137  	})
   138  }
   139  

View as plain text