gitlab.com/gitlab-org/labkit@v1.21.0/correlation/inbound_http_test.go (about)

     1  package correlation
     2  
     3  import (
     4  	"net/http"
     5  	"net/http/httptest"
     6  	"testing"
     7  
     8  	"github.com/stretchr/testify/require"
     9  )
    10  
    11  // nolint:gocognit,cyclop
    12  func TestInjectCorrelationID(t *testing.T) {
    13  	tests := []struct {
    14  		name                      string
    15  		opts                      []InboundHandlerOption
    16  		header                    http.Header
    17  		shouldMatch               string
    18  		shouldNotMatch            string
    19  		shouldSetResponseHeader   bool
    20  		expectedResponseHeader    string
    21  		downstreamResponseHeaders http.Header
    22  		remoteAddr                string
    23  		xffHeader                 string
    24  	}{
    25  		{
    26  			name: "without_propagation",
    27  		},
    28  		{
    29  			name: "without_propagation_ignore_incoming_header",
    30  			header: map[string][]string{
    31  				propagationHeader: {"123"},
    32  			},
    33  			shouldNotMatch: "123",
    34  		},
    35  		{
    36  			name: "with_propagation_no_incoming_header",
    37  			opts: []InboundHandlerOption{WithPropagation()},
    38  		},
    39  		{
    40  			name: "with_propagation_incoming_header",
    41  			opts: []InboundHandlerOption{WithPropagation()},
    42  			header: map[string][]string{
    43  				propagationHeader: {"123"},
    44  			},
    45  			shouldMatch: "123",
    46  		},
    47  		{
    48  			name: "with_propagation_double_incoming_header",
    49  			opts: []InboundHandlerOption{WithPropagation()},
    50  			header: map[string][]string{
    51  				propagationHeader: {"123", "456"},
    52  			},
    53  			shouldMatch: "123",
    54  		},
    55  		{
    56  			name:                    "with_set_response_header",
    57  			opts:                    []InboundHandlerOption{WithSetResponseHeader()},
    58  			shouldSetResponseHeader: true,
    59  		},
    60  		{
    61  			name:                    "with_set_response_header_and_with_propagation_incoming_header",
    62  			opts:                    []InboundHandlerOption{WithSetResponseHeader(), WithPropagation()},
    63  			shouldSetResponseHeader: true,
    64  		},
    65  		{
    66  			name:                    "with_set_response_header_and_with_propagation_incoming_header_set",
    67  			opts:                    []InboundHandlerOption{WithSetResponseHeader(), WithPropagation()},
    68  			shouldSetResponseHeader: true,
    69  			header: map[string][]string{
    70  				propagationHeader: {"123"},
    71  			},
    72  			expectedResponseHeader: "123",
    73  		},
    74  		{
    75  			name: "mismatching_correlation_ids_without_propagation",
    76  			opts: []InboundHandlerOption{},
    77  			downstreamResponseHeaders: map[string][]string{
    78  				propagationHeader: {"CLIENT_CORRELATION_ID"},
    79  			},
    80  			shouldSetResponseHeader: true,
    81  			expectedResponseHeader:  "CLIENT_CORRELATION_ID",
    82  		},
    83  
    84  		{
    85  			name: "mismatching_correlation_ids_with_propagation",
    86  			opts: []InboundHandlerOption{WithSetResponseHeader()},
    87  			downstreamResponseHeaders: map[string][]string{
    88  				propagationHeader: {"CLIENT_CORRELATION_ID"},
    89  			},
    90  			shouldSetResponseHeader: true,
    91  			expectedResponseHeader:  "CLIENT_CORRELATION_ID",
    92  		},
    93  		{
    94  			name:                    "with_set_response_header_and_with_propagation_incoming_header_set",
    95  			opts:                    []InboundHandlerOption{WithSetResponseHeader(), WithPropagation()},
    96  			shouldSetResponseHeader: true,
    97  			header: map[string][]string{
    98  				propagationHeader: {"123"},
    99  			},
   100  			downstreamResponseHeaders: map[string][]string{
   101  				propagationHeader: {"CLIENT_CORRELATION_ID"},
   102  			},
   103  			expectedResponseHeader: "CLIENT_CORRELATION_ID",
   104  		},
   105  		{
   106  			name: "trusted_cidrs_and_propagation",
   107  			opts: []InboundHandlerOption{
   108  				WithCIDRsTrustedForXForwardedFor([]string{"192.168.0.1/32"}),
   109  				WithCIDRsTrustedForPropagation([]string{"1.2.3.4/32"}),
   110  				WithPropagation(),
   111  			},
   112  			xffHeader:  "1.2.3.4, 127.0.0.1",
   113  			remoteAddr: "192.168.0.1:1024",
   114  			header: map[string][]string{
   115  				propagationHeader: {"123"},
   116  			},
   117  			shouldMatch: "123",
   118  		},
   119  		{
   120  			name: "trusted_xff_cidrs_for_propagation",
   121  			opts: []InboundHandlerOption{
   122  				WithCIDRsTrustedForXForwardedFor([]string{"192.168.0.1/32"}),
   123  				WithCIDRsTrustedForPropagation([]string{"192.168.0.1/32"}),
   124  				WithPropagation(),
   125  			},
   126  			xffHeader:  "1.2.3.4, 127.0.0.1",
   127  			remoteAddr: "192.168.0.1:1024",
   128  			header: map[string][]string{
   129  				propagationHeader: {"123"},
   130  			},
   131  			shouldMatch: "123",
   132  		},
   133  		{
   134  			name: "untrusted_xff_cidr_propagation",
   135  			opts: []InboundHandlerOption{
   136  				WithCIDRsTrustedForXForwardedFor([]string{"1.2.3.4/32"}),
   137  				WithCIDRsTrustedForPropagation([]string{"1.2.3.5/32"}),
   138  				WithPropagation(),
   139  			},
   140  			xffHeader:  "1.2.3.5, 127.0.0.1",
   141  			remoteAddr: "192.168.0.1:1024",
   142  			header: map[string][]string{
   143  				propagationHeader: {"123"},
   144  			},
   145  			shouldNotMatch: "123",
   146  		},
   147  		{
   148  			name: "trusted_xff_cidr_untrusted_propagation_cidr",
   149  			opts: []InboundHandlerOption{
   150  				WithCIDRsTrustedForXForwardedFor([]string{"192.168.0.1/32"}),
   151  				WithCIDRsTrustedForPropagation([]string{"127.0.0.1/32"}),
   152  				WithPropagation(),
   153  			},
   154  			xffHeader:  "1.2.3.5, 127.0.0.1",
   155  			remoteAddr: "192.168.0.1:1024",
   156  			header: map[string][]string{
   157  				propagationHeader: {"123"},
   158  			},
   159  			shouldNotMatch: "123",
   160  		},
   161  		{
   162  			name: "trusted_cidrs_no_propagation",
   163  			opts: []InboundHandlerOption{
   164  				WithCIDRsTrustedForXForwardedFor([]string{"192.168.0.1/32"}),
   165  				WithCIDRsTrustedForPropagation([]string{"1.2.3.4/32"}),
   166  			},
   167  			xffHeader:  "1.2.3.4, 127.0.0.1",
   168  			remoteAddr: "192.168.0.1:1024",
   169  			header: map[string][]string{
   170  				propagationHeader: {"123"},
   171  			},
   172  			shouldNotMatch: "123",
   173  		},
   174  		{
   175  			name: "trusted_propagation_cidr_remote_addr_propagation",
   176  			opts: []InboundHandlerOption{
   177  				WithCIDRsTrustedForPropagation([]string{"1.2.3.4/32"}),
   178  				WithPropagation(),
   179  			},
   180  			remoteAddr: "1.2.3.4:1024",
   181  			header: map[string][]string{
   182  				propagationHeader: {"123"},
   183  			},
   184  			shouldMatch: "123",
   185  		},
   186  		{
   187  			name: "no_xff_trusted_cidrs",
   188  			opts: []InboundHandlerOption{
   189  				WithCIDRsTrustedForPropagation([]string{"192.168.0.1/8"}),
   190  				WithPropagation(),
   191  			},
   192  			xffHeader:  "1.2.3.4, 127.0.0.1",
   193  			remoteAddr: "192.168.0.1:1024",
   194  			header: map[string][]string{
   195  				propagationHeader: {"123"},
   196  			},
   197  			shouldMatch: "123",
   198  		},
   199  		{
   200  			name: "xff_trusted_cidrs_with_propagation_unix_socket",
   201  			opts: []InboundHandlerOption{
   202  				WithCIDRsTrustedForXForwardedFor([]string{"192.168.0.1/8", "127.0.0.1/32"}),
   203  				WithCIDRsTrustedForPropagation([]string{"1.2.3.4/32"}),
   204  				WithPropagation(),
   205  			},
   206  			xffHeader:  "1.2.3.4, 127.0.0.1",
   207  			remoteAddr: "@",
   208  			header: map[string][]string{
   209  				propagationHeader: {"123"},
   210  			},
   211  			shouldMatch: "123",
   212  		},
   213  		{
   214  			name: "no_xff_with_propagation_unix_socket",
   215  			opts: []InboundHandlerOption{
   216  				WithCIDRsTrustedForPropagation([]string{"1.2.3.4/8"}),
   217  				WithPropagation(),
   218  			},
   219  			xffHeader:  "1.2.3.4, 127.0.0.1",
   220  			remoteAddr: "@",
   221  			header: map[string][]string{
   222  				propagationHeader: {"123"},
   223  			},
   224  			shouldNotMatch: "123",
   225  		},
   226  		{
   227  			name: "bad_cidrs_ignore_propagation",
   228  			opts: []InboundHandlerOption{
   229  				WithCIDRsTrustedForXForwardedFor([]string{"a.b.c"}),
   230  				WithCIDRsTrustedForPropagation([]string{"x.y.z"}),
   231  				WithPropagation(),
   232  			},
   233  			xffHeader:  "1.2.3.4, 127.0.0.1",
   234  			remoteAddr: "192.168.1.2:9876",
   235  			header: map[string][]string{
   236  				propagationHeader: {"123"},
   237  			},
   238  			shouldNotMatch: "123",
   239  		},
   240  	}
   241  
   242  	for _, test := range tests {
   243  		t.Run(test.name, func(t *testing.T) {
   244  			invoked := false
   245  
   246  			h := InjectCorrelationID(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   247  				invoked = true
   248  
   249  				ctx := r.Context()
   250  				correlationID := ExtractFromContext(ctx)
   251  				require.NotNil(t, correlationID, "CorrelationID is missing")
   252  				require.NotEmpty(t, correlationID, "CorrelationID is missing")
   253  				if test.shouldMatch != "" {
   254  					require.Equal(t, test.shouldMatch, correlationID, "CorrelationID should match")
   255  				}
   256  
   257  				if test.shouldNotMatch != "" {
   258  					require.NotEqual(t, test.shouldNotMatch, correlationID, "CorrelationID should not match")
   259  				}
   260  
   261  				for k, vs := range test.downstreamResponseHeaders {
   262  					for _, v := range vs {
   263  						w.Header().Set(k, v)
   264  					}
   265  				}
   266  			}), test.opts...)
   267  
   268  			r := httptest.NewRequest("GET", "http://example.com", nil)
   269  			for k, v := range test.header {
   270  				r.Header[http.CanonicalHeaderKey(k)] = v
   271  			}
   272  
   273  			if test.remoteAddr != "" {
   274  				r.RemoteAddr = test.remoteAddr
   275  			}
   276  			if test.xffHeader != "" {
   277  				r.Header.Add("X-Forwarded-For", test.xffHeader)
   278  			}
   279  
   280  			w := httptest.NewRecorder()
   281  			h.ServeHTTP(w, r)
   282  
   283  			require.True(t, invoked, "handler not executed")
   284  
   285  			v, ok := w.Result().Header[http.CanonicalHeaderKey(propagationHeader)]
   286  			require.Equal(t, test.shouldSetResponseHeader, ok, "response header existence mismatch")
   287  			if test.shouldSetResponseHeader {
   288  				require.Len(t, v, 1, "expected exactly one correlation header")
   289  				require.NotEmpty(t, v[0], "expected non-empty correlation header")
   290  			}
   291  
   292  			if test.expectedResponseHeader != "" {
   293  				responseHeader := w.Header().Get(propagationHeader)
   294  				require.Equal(t, test.expectedResponseHeader, responseHeader, "response header should match")
   295  			}
   296  		})
   297  	}
   298  }