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 }