github.com/cilium/cilium@v1.16.2/pkg/hubble/parser/seven/http_test.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Hubble
     3  
     4  package seven
     5  
     6  import (
     7  	"fmt"
     8  	"net/http"
     9  	"net/netip"
    10  	"net/url"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/stretchr/testify/assert"
    15  	"github.com/stretchr/testify/require"
    16  
    17  	flowpb "github.com/cilium/cilium/api/v1/flow"
    18  	"github.com/cilium/cilium/pkg/hubble/defaults"
    19  	"github.com/cilium/cilium/pkg/hubble/parser/getters"
    20  	"github.com/cilium/cilium/pkg/hubble/parser/options"
    21  	"github.com/cilium/cilium/pkg/hubble/testutils"
    22  	"github.com/cilium/cilium/pkg/ipcache"
    23  	"github.com/cilium/cilium/pkg/proxy/accesslog"
    24  	"github.com/cilium/cilium/pkg/u8proto"
    25  )
    26  
    27  func TestDecodeL7HTTPRequest(t *testing.T) {
    28  	requestPath, err := url.Parse("http://myhost/some/path")
    29  	require.NoError(t, err)
    30  	lr := &accesslog.LogRecord{
    31  		Type:                accesslog.TypeRequest,
    32  		Timestamp:           fakeTimestamp,
    33  		NodeAddressInfo:     fakeNodeInfo,
    34  		ObservationPoint:    accesslog.Ingress,
    35  		SourceEndpoint:      fakeSourceEndpoint,
    36  		DestinationEndpoint: fakeDestinationEndpoint,
    37  		IPVersion:           accesslog.VersionIPv4,
    38  		Verdict:             accesslog.VerdictForwarded,
    39  		TransportProtocol:   accesslog.TransportProtocol(u8proto.TCP),
    40  		ServiceInfo:         nil,
    41  		DropReason:          nil,
    42  		HTTP: &accesslog.LogRecordHTTP{
    43  			Code:     0,
    44  			Method:   "POST",
    45  			URL:      requestPath,
    46  			Protocol: "HTTP/1.1",
    47  			Headers: http.Header{
    48  				"Host":        {"myhost"},
    49  				"Traceparent": {"00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"},
    50  			},
    51  		},
    52  	}
    53  	lr.SourceEndpoint.Port = 56789
    54  	lr.DestinationEndpoint.Port = 80
    55  
    56  	dnsGetter := &testutils.FakeFQDNCache{
    57  		OnGetNamesOf: func(epID uint32, ip netip.Addr) (names []string) {
    58  			ipStr := ip.String()
    59  			switch {
    60  			case epID == uint32(fakeSourceEndpoint.ID) && ipStr == fakeDestinationEndpoint.IPv4:
    61  				return []string{"endpoint-1234"}
    62  			case epID == uint32(fakeDestinationEndpoint.ID) && ipStr == fakeSourceEndpoint.IPv4:
    63  				return []string{"endpoint-4321"}
    64  			}
    65  			return nil
    66  		},
    67  	}
    68  	IPGetter := &testutils.FakeIPGetter{
    69  		OnGetK8sMetadata: func(ip netip.Addr) *ipcache.K8sMetadata {
    70  			if ip == netip.MustParseAddr(fakeDestinationEndpoint.IPv4) {
    71  				return &ipcache.K8sMetadata{
    72  					Namespace: "default",
    73  					PodName:   "pod-1234",
    74  				}
    75  			}
    76  			return nil
    77  		},
    78  	}
    79  	serviceGetter := &testutils.FakeServiceGetter{
    80  		OnGetServiceByAddr: func(ip netip.Addr, port uint16) *flowpb.Service {
    81  			if ip == netip.MustParseAddr(fakeDestinationEndpoint.IPv4) && (port == fakeDestinationEndpoint.Port) {
    82  				return &flowpb.Service{
    83  					Name:      "service-1234",
    84  					Namespace: "default",
    85  				}
    86  			}
    87  			return nil
    88  		},
    89  	}
    90  	endpointGetter := &testutils.FakeEndpointGetter{
    91  		OnGetEndpointInfo: func(ip netip.Addr) (endpoint getters.EndpointInfo, ok bool) {
    92  			switch {
    93  			case ip == netip.MustParseAddr(fakeSourceEndpoint.IPv4):
    94  				return &testutils.FakeEndpointInfo{
    95  					ID: fakeSourceEndpoint.ID,
    96  				}, true
    97  			case ip == netip.MustParseAddr(fakeDestinationEndpoint.IPv4):
    98  				return &testutils.FakeEndpointInfo{
    99  					ID: fakeDestinationEndpoint.ID,
   100  				}, true
   101  			}
   102  			return nil, false
   103  		},
   104  	}
   105  
   106  	parser, err := New(log, dnsGetter, IPGetter, serviceGetter, endpointGetter)
   107  	require.NoError(t, err)
   108  
   109  	f := &flowpb.Flow{}
   110  	err = parser.Decode(lr, f)
   111  	require.NoError(t, err)
   112  
   113  	assert.Equal(t, fakeSourceEndpoint.IPv4, f.GetIP().GetSource())
   114  	assert.Equal(t, uint32(56789), f.GetL4().GetTCP().GetSourcePort())
   115  	assert.Equal(t, []string{"endpoint-4321"}, f.GetSourceNames())
   116  	assert.Equal(t, fakeSourceEndpoint.Labels.GetModel(), f.GetSource().GetLabels())
   117  	assert.Equal(t, "", f.GetSource().GetNamespace())
   118  	assert.Equal(t, "", f.GetSource().GetPodName())
   119  	assert.Equal(t, "", f.GetSourceService().GetNamespace())
   120  	assert.Equal(t, "", f.GetSourceService().GetName())
   121  
   122  	assert.Equal(t, fakeDestinationEndpoint.IPv4, f.GetIP().GetDestination())
   123  	assert.Equal(t, uint32(80), f.GetL4().GetTCP().GetDestinationPort())
   124  	assert.Equal(t, []string{"endpoint-1234"}, f.GetDestinationNames())
   125  	assert.Equal(t, fakeDestinationEndpoint.Labels.GetModel(), f.GetDestination().GetLabels())
   126  	assert.Equal(t, "default", f.GetDestination().GetNamespace())
   127  	assert.Equal(t, "pod-1234", f.GetDestination().GetPodName())
   128  	assert.Equal(t, "default", f.GetDestinationService().GetNamespace())
   129  	assert.Equal(t, "service-1234", f.GetDestinationService().GetName())
   130  
   131  	assert.Equal(t, flowpb.Verdict_FORWARDED, f.GetVerdict())
   132  
   133  	assert.Equal(t, &flowpb.HTTP{
   134  		Code:     0,
   135  		Method:   "POST",
   136  		Url:      "http://myhost/some/path",
   137  		Protocol: "HTTP/1.1",
   138  		Headers: []*flowpb.HTTPHeader{
   139  			{Key: "Host", Value: "myhost"},
   140  			{Key: "Traceparent", Value: "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"},
   141  		},
   142  	}, f.GetL7().GetHttp())
   143  	assert.Equal(t, "4bf92f3577b34da6a3ce929d0e0e4736", f.GetTraceContext().GetParent().GetTraceId())
   144  }
   145  
   146  func TestDecodeL7HTTPRecordResponse(t *testing.T) {
   147  	requestPath, err := url.Parse("http://myhost/some/path")
   148  	require.NoError(t, err)
   149  	lr := &accesslog.LogRecord{
   150  		Type:                accesslog.TypeResponse,
   151  		Timestamp:           fakeTimestamp,
   152  		NodeAddressInfo:     fakeNodeInfo,
   153  		ObservationPoint:    accesslog.Ingress,
   154  		SourceEndpoint:      fakeDestinationEndpoint,
   155  		DestinationEndpoint: fakeSourceEndpoint,
   156  		IPVersion:           accesslog.VersionIPv4,
   157  		Verdict:             accesslog.VerdictForwarded,
   158  		TransportProtocol:   accesslog.TransportProtocol(u8proto.TCP),
   159  		ServiceInfo:         nil,
   160  		DropReason:          nil,
   161  		HTTP: &accesslog.LogRecordHTTP{
   162  			Code:     404,
   163  			Method:   "POST",
   164  			URL:      requestPath,
   165  			Protocol: "HTTP/1.1",
   166  		},
   167  	}
   168  	lr.SourceEndpoint.Port = 80
   169  	lr.DestinationEndpoint.Port = 56789
   170  
   171  	dnsGetter := &testutils.FakeFQDNCache{
   172  		OnGetNamesOf: func(epID uint32, ip netip.Addr) (names []string) {
   173  			ipStr := ip.String()
   174  			switch {
   175  			case epID == uint32(fakeSourceEndpoint.ID) && ipStr == fakeDestinationEndpoint.IPv4:
   176  				return []string{"endpoint-1234"}
   177  			case epID == uint32(fakeDestinationEndpoint.ID) && ipStr == fakeSourceEndpoint.IPv4:
   178  				return []string{"endpoint-4321"}
   179  			}
   180  			return nil
   181  		},
   182  	}
   183  	IPGetter := &testutils.FakeIPGetter{
   184  		OnGetK8sMetadata: func(ip netip.Addr) *ipcache.K8sMetadata {
   185  			if ip == netip.MustParseAddr(fakeDestinationEndpoint.IPv4) {
   186  				return &ipcache.K8sMetadata{
   187  					Namespace: "default",
   188  					PodName:   "pod-1234",
   189  				}
   190  			}
   191  			return nil
   192  		},
   193  	}
   194  	serviceGetter := &testutils.FakeServiceGetter{
   195  		OnGetServiceByAddr: func(ip netip.Addr, port uint16) *flowpb.Service {
   196  			if ip == netip.MustParseAddr(fakeDestinationEndpoint.IPv4) && (port == fakeDestinationEndpoint.Port) {
   197  				return &flowpb.Service{
   198  					Name:      "service-1234",
   199  					Namespace: "default",
   200  				}
   201  			}
   202  			return nil
   203  		},
   204  	}
   205  	endpointGetter := &testutils.FakeEndpointGetter{
   206  		OnGetEndpointInfo: func(ip netip.Addr) (endpoint getters.EndpointInfo, ok bool) {
   207  			switch {
   208  			case ip.String() == fakeSourceEndpoint.IPv4:
   209  				return &testutils.FakeEndpointInfo{
   210  					ID: fakeSourceEndpoint.ID,
   211  				}, true
   212  			case ip.String() == fakeDestinationEndpoint.IPv4:
   213  				return &testutils.FakeEndpointInfo{
   214  					ID: fakeDestinationEndpoint.ID,
   215  				}, true
   216  			}
   217  			return nil, false
   218  		},
   219  	}
   220  
   221  	parser, err := New(log, dnsGetter, IPGetter, serviceGetter, endpointGetter)
   222  	require.NoError(t, err)
   223  
   224  	f := &flowpb.Flow{}
   225  	err = parser.Decode(lr, f)
   226  	require.NoError(t, err)
   227  
   228  	assert.Equal(t, fakeSourceEndpoint.IPv4, f.GetIP().GetDestination())
   229  	assert.Equal(t, uint32(56789), f.GetL4().GetTCP().GetDestinationPort())
   230  	assert.Equal(t, []string{"endpoint-4321"}, f.GetDestinationNames())
   231  	assert.Equal(t, fakeSourceEndpoint.Labels.GetModel(), f.GetDestination().GetLabels())
   232  	assert.Equal(t, "", f.GetDestination().GetNamespace())
   233  	assert.Equal(t, "", f.GetDestination().GetPodName())
   234  	assert.Equal(t, "", f.GetDestinationService().GetNamespace())
   235  	assert.Equal(t, "", f.GetDestinationService().GetName())
   236  
   237  	assert.Equal(t, fakeDestinationEndpoint.IPv4, f.GetIP().GetSource())
   238  	assert.Equal(t, uint32(80), f.GetL4().GetTCP().GetSourcePort())
   239  	assert.Equal(t, []string{"endpoint-1234"}, f.GetSourceNames())
   240  	assert.Equal(t, fakeDestinationEndpoint.Labels.GetModel(), f.GetSource().GetLabels())
   241  	assert.Equal(t, "default", f.GetSource().GetNamespace())
   242  	assert.Equal(t, "pod-1234", f.GetSource().GetPodName())
   243  	assert.Equal(t, "default", f.GetSourceService().GetNamespace())
   244  	assert.Equal(t, "service-1234", f.GetSourceService().GetName())
   245  
   246  	assert.Equal(t, flowpb.Verdict_FORWARDED, f.GetVerdict())
   247  
   248  	assert.Equal(t, &flowpb.HTTP{
   249  		Code:     404,
   250  		Method:   "POST",
   251  		Url:      "http://myhost/some/path",
   252  		Protocol: "HTTP/1.1",
   253  	}, f.GetL7().GetHttp())
   254  }
   255  
   256  func TestDecodeL7HTTPResponseTime(t *testing.T) {
   257  	requestID := "req-id"
   258  	headers := http.Header{}
   259  	headers.Add("X-Request-Id", requestID)
   260  	httpRecord := &accesslog.LogRecordHTTP{
   261  		Code:     200,
   262  		Headers:  headers,
   263  		Method:   "GET",
   264  		Protocol: "HTTP/1.1",
   265  		URL: &url.URL{
   266  			Scheme: "http",
   267  			Host:   "example.com",
   268  			Path:   "/",
   269  		},
   270  	}
   271  	requestTimestamp := time.Unix(0, 0).Format(time.RFC3339Nano)
   272  	responseTimestamp := time.Unix(1, 0).Format(time.RFC3339Nano)
   273  
   274  	parser, err := New(log, nil, nil, nil, nil)
   275  	require.NoError(t, err)
   276  
   277  	request := &accesslog.LogRecord{
   278  		Type:      accesslog.TypeRequest,
   279  		Timestamp: requestTimestamp,
   280  		HTTP:      httpRecord,
   281  	}
   282  
   283  	response := &accesslog.LogRecord{
   284  		Type:      accesslog.TypeResponse,
   285  		Timestamp: responseTimestamp,
   286  		HTTP:      httpRecord,
   287  	}
   288  
   289  	f := &flowpb.Flow{}
   290  	err = parser.Decode(request, f)
   291  	require.NoError(t, err)
   292  	_, ok := parser.timestampCache.Get(requestID)
   293  	assert.True(t, ok, "request id should be in the cache")
   294  
   295  	f.Reset()
   296  	err = parser.Decode(response, f)
   297  	require.NoError(t, err)
   298  	assert.Equal(t, 1*time.Second, time.Duration(f.GetL7().GetLatencyNs()))
   299  	_, ok = parser.timestampCache.Get(requestID)
   300  	assert.False(t, ok, "request id should not be in the cache")
   301  
   302  	// it should handle the case where the request id is not in the cache for response type.
   303  	f = &flowpb.Flow{}
   304  	err = parser.Decode(response, f)
   305  	require.NoError(t, err)
   306  	assert.Equal(t, uint64(0), f.GetL7().GetLatencyNs())
   307  	_, ok = parser.timestampCache.Get(requestID)
   308  	assert.False(t, ok, "request id should not be in the cache")
   309  }
   310  
   311  func TestGetL7HTTPResponseTraceID(t *testing.T) {
   312  	requestID := "req-id"
   313  	requestRecord := &accesslog.LogRecordHTTP{
   314  		Method:   "GET",
   315  		Protocol: "HTTP/1.1",
   316  		URL: &url.URL{
   317  			Scheme: "http",
   318  			Host:   "example.com",
   319  			Path:   "/",
   320  		},
   321  		Headers: http.Header{
   322  			"X-Request-Id": {requestID},
   323  			"Host":         {"myhost"},
   324  			"Traceparent":  {"00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"},
   325  		},
   326  	}
   327  	responseRecord := &accesslog.LogRecordHTTP{
   328  		Code:     200,
   329  		Method:   "GET",
   330  		Protocol: "HTTP/1.1",
   331  		URL: &url.URL{
   332  			Scheme: "http",
   333  			Host:   "example.com",
   334  			Path:   "/",
   335  		},
   336  		Headers: http.Header{
   337  			"X-Request-Id": {requestID},
   338  		},
   339  	}
   340  	requestTimestamp := time.Unix(0, 0).Format(time.RFC3339Nano)
   341  	responseTimestamp := time.Unix(1, 0).Format(time.RFC3339Nano)
   342  
   343  	parser, err := New(log, nil, nil, nil, nil)
   344  	require.NoError(t, err)
   345  
   346  	request := &accesslog.LogRecord{
   347  		Type:      accesslog.TypeRequest,
   348  		Timestamp: requestTimestamp,
   349  		HTTP:      requestRecord,
   350  	}
   351  
   352  	response := &accesslog.LogRecord{
   353  		Type:      accesslog.TypeResponse,
   354  		Timestamp: responseTimestamp,
   355  		HTTP:      responseRecord,
   356  	}
   357  
   358  	f := &flowpb.Flow{}
   359  	err = parser.Decode(request, f)
   360  	require.NoError(t, err)
   361  	_, ok := parser.traceContextCache.Get(requestID)
   362  	assert.True(t, ok, "request id should be in the cache")
   363  
   364  	f.Reset()
   365  	err = parser.Decode(response, f)
   366  	require.NoError(t, err)
   367  	assert.Equal(t, "4bf92f3577b34da6a3ce929d0e0e4736", f.GetTraceContext().GetParent().GetTraceId())
   368  	_, ok = parser.traceContextCache.Get(requestID)
   369  	assert.False(t, ok, "request id should not be in the cache")
   370  
   371  	// it should handle the case where the request id is not in the cache for response type.
   372  	f = &flowpb.Flow{}
   373  	err = parser.Decode(response, f)
   374  	require.NoError(t, err)
   375  	// no requestID means no traceID for response
   376  	assert.Empty(t, f.GetTraceContext().GetParent().GetTraceId())
   377  	_, ok = parser.traceContextCache.Get(requestID)
   378  	assert.False(t, ok, "request id should not be in the cache")
   379  }
   380  
   381  // see https://github.com/cilium/cilium/issues/31071
   382  func TestDecodeL7HTTPWithInvalidURL(t *testing.T) {
   383  	requestPath, err := url.Parse("http://myhost/some/path")
   384  	require.NoError(t, err)
   385  
   386  	// mutate requestPath such as url.Parse(requestPath.String()) fails, which
   387  	// triggered the panic described in #31071.
   388  	requestPath.Host += "@" // invalid hostname
   389  	_, err = url.Parse(requestPath.String())
   390  	require.Error(t, err)
   391  
   392  	lr := &accesslog.LogRecord{
   393  		Type:                accesslog.TypeRequest,
   394  		Timestamp:           fakeTimestamp,
   395  		NodeAddressInfo:     fakeNodeInfo,
   396  		ObservationPoint:    accesslog.Ingress,
   397  		SourceEndpoint:      fakeSourceEndpoint,
   398  		DestinationEndpoint: fakeDestinationEndpoint,
   399  		IPVersion:           accesslog.VersionIPv4,
   400  		Verdict:             accesslog.VerdictForwarded,
   401  		TransportProtocol:   accesslog.TransportProtocol(u8proto.TCP),
   402  		ServiceInfo:         nil,
   403  		DropReason:          nil,
   404  		HTTP: &accesslog.LogRecordHTTP{
   405  			Code:     0,
   406  			Method:   "POST",
   407  			URL:      requestPath,
   408  			Protocol: "HTTP/1.1",
   409  			Headers: http.Header{
   410  				"Host":        {"myhost"},
   411  				"Traceparent": {"00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"},
   412  			},
   413  		},
   414  	}
   415  
   416  	parser, err := New(log, nil, nil, nil, nil)
   417  	require.NoError(t, err)
   418  
   419  	f := &flowpb.Flow{}
   420  	err = parser.Decode(lr, f)
   421  	require.NoError(t, err)
   422  
   423  	assert.Equal(t, &flowpb.HTTP{
   424  		Code:     0,
   425  		Method:   "POST",
   426  		Url:      "http://myhost%40/some/path",
   427  		Protocol: "HTTP/1.1",
   428  		Headers: []*flowpb.HTTPHeader{
   429  			{Key: "Host", Value: "myhost"},
   430  			{Key: "Traceparent", Value: "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"},
   431  		},
   432  	}, f.GetL7().GetHttp())
   433  	assert.Equal(t, "4bf92f3577b34da6a3ce929d0e0e4736", f.GetTraceContext().GetParent().GetTraceId())
   434  }
   435  
   436  func TestDecodeL7HTTPWithNilURL(t *testing.T) {
   437  	lr := &accesslog.LogRecord{
   438  		Type:                accesslog.TypeRequest,
   439  		Timestamp:           fakeTimestamp,
   440  		NodeAddressInfo:     fakeNodeInfo,
   441  		ObservationPoint:    accesslog.Ingress,
   442  		SourceEndpoint:      fakeSourceEndpoint,
   443  		DestinationEndpoint: fakeDestinationEndpoint,
   444  		IPVersion:           accesslog.VersionIPv4,
   445  		Verdict:             accesslog.VerdictForwarded,
   446  		TransportProtocol:   accesslog.TransportProtocol(u8proto.TCP),
   447  		ServiceInfo:         nil,
   448  		DropReason:          nil,
   449  		HTTP: &accesslog.LogRecordHTTP{
   450  			Code:     0,
   451  			Method:   "POST",
   452  			URL:      nil,
   453  			Protocol: "HTTP/1.1",
   454  			Headers: http.Header{
   455  				"Host":        {"myhost"},
   456  				"Traceparent": {"00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"},
   457  			},
   458  		},
   459  	}
   460  
   461  	parser, err := New(log, nil, nil, nil, nil)
   462  	require.NoError(t, err)
   463  
   464  	f := &flowpb.Flow{}
   465  	err = parser.Decode(lr, f)
   466  	require.NoError(t, err)
   467  
   468  	assert.Equal(t, &flowpb.HTTP{
   469  		Code:     0,
   470  		Method:   "POST",
   471  		Url:      "",
   472  		Protocol: "HTTP/1.1",
   473  		Headers: []*flowpb.HTTPHeader{
   474  			{Key: "Host", Value: "myhost"},
   475  			{Key: "Traceparent", Value: "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"},
   476  		},
   477  	}, f.GetL7().GetHttp())
   478  	assert.Equal(t, "4bf92f3577b34da6a3ce929d0e0e4736", f.GetTraceContext().GetParent().GetTraceId())
   479  }
   480  
   481  func TestDecodeL7HTTPRequestRemoveUrlQuery(t *testing.T) {
   482  	requestPath, err := url.Parse("http://myhost/some/path?foo=bar")
   483  	require.NoError(t, err)
   484  	lr := &accesslog.LogRecord{
   485  		Type:                accesslog.TypeRequest,
   486  		Timestamp:           fakeTimestamp,
   487  		NodeAddressInfo:     fakeNodeInfo,
   488  		ObservationPoint:    accesslog.Ingress,
   489  		SourceEndpoint:      fakeSourceEndpoint,
   490  		DestinationEndpoint: fakeDestinationEndpoint,
   491  		IPVersion:           accesslog.VersionIPv4,
   492  		Verdict:             accesslog.VerdictForwarded,
   493  		TransportProtocol:   accesslog.TransportProtocol(u8proto.TCP),
   494  		ServiceInfo:         nil,
   495  		DropReason:          nil,
   496  		HTTP: &accesslog.LogRecordHTTP{
   497  			Code:     0,
   498  			Method:   "POST",
   499  			URL:      requestPath,
   500  			Protocol: "HTTP/1.1",
   501  			Headers: http.Header{
   502  				"Host":        {"myhost"},
   503  				"Traceparent": {"00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"},
   504  			},
   505  		},
   506  	}
   507  	lr.SourceEndpoint.Port = 56789
   508  	lr.DestinationEndpoint.Port = 80
   509  
   510  	opts := []options.Option{options.Redact(nil, true, true, false, []string{}, []string{"authorization"})}
   511  	parser, err := New(log, nil, nil, nil, nil, opts...)
   512  	require.NoError(t, err)
   513  
   514  	f := &flowpb.Flow{}
   515  	err = parser.Decode(lr, f)
   516  	require.NoError(t, err)
   517  	assert.Equal(t, &flowpb.HTTP{
   518  		Code:     0,
   519  		Method:   "POST",
   520  		Url:      "http://myhost/some/path",
   521  		Protocol: "HTTP/1.1",
   522  		Headers: []*flowpb.HTTPHeader{
   523  			{Key: "Host", Value: "myhost"},
   524  			{Key: "Traceparent", Value: "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"},
   525  		},
   526  	}, f.GetL7().GetHttp())
   527  }
   528  
   529  func TestDecodeL7HTTPRequestHeadersRedact(t *testing.T) {
   530  	requestPath, err := url.Parse("http://myhost/some/path")
   531  	require.NoError(t, err)
   532  	lr := &accesslog.LogRecord{
   533  		Type:                accesslog.TypeRequest,
   534  		Timestamp:           fakeTimestamp,
   535  		NodeAddressInfo:     fakeNodeInfo,
   536  		ObservationPoint:    accesslog.Ingress,
   537  		SourceEndpoint:      fakeSourceEndpoint,
   538  		DestinationEndpoint: fakeDestinationEndpoint,
   539  		IPVersion:           accesslog.VersionIPv4,
   540  		Verdict:             accesslog.VerdictForwarded,
   541  		TransportProtocol:   accesslog.TransportProtocol(u8proto.TCP),
   542  		ServiceInfo:         nil,
   543  		DropReason:          nil,
   544  		HTTP: &accesslog.LogRecordHTTP{
   545  			Code:     0,
   546  			Method:   "POST",
   547  			URL:      requestPath,
   548  			Protocol: "HTTP/1.1",
   549  			Headers: http.Header{
   550  				"Host":        {"myhost"},
   551  				"traceparent": {"asdf"},
   552  			},
   553  		},
   554  	}
   555  	lr.SourceEndpoint.Port = 56789
   556  	lr.DestinationEndpoint.Port = 80
   557  
   558  	opts := []options.Option{options.Redact(nil, true, true, false, []string{"host"}, []string{})}
   559  	parser, err := New(log, nil, nil, nil, nil, opts...)
   560  	require.NoError(t, err)
   561  
   562  	f := &flowpb.Flow{}
   563  	err = parser.Decode(lr, f)
   564  	require.NoError(t, err)
   565  	assert.Equal(t, &flowpb.HTTP{
   566  		Code:     0,
   567  		Method:   "POST",
   568  		Url:      "http://myhost/some/path",
   569  		Protocol: "HTTP/1.1",
   570  		Headers: []*flowpb.HTTPHeader{
   571  			{Key: "Host", Value: "myhost"},
   572  			{Key: "traceparent", Value: defaults.SensitiveValueRedacted},
   573  		},
   574  	}, f.GetL7().GetHttp())
   575  
   576  	opts = []options.Option{options.Redact(nil, true, true, false, []string{}, []string{"host"})}
   577  	parser, err = New(log, nil, nil, nil, nil, opts...)
   578  	require.NoError(t, err)
   579  
   580  	f = &flowpb.Flow{}
   581  	err = parser.Decode(lr, f)
   582  	require.NoError(t, err)
   583  	assert.Equal(t, &flowpb.HTTP{
   584  		Code:     0,
   585  		Method:   "POST",
   586  		Url:      "http://myhost/some/path",
   587  		Protocol: "HTTP/1.1",
   588  		Headers: []*flowpb.HTTPHeader{
   589  			{Key: "Host", Value: defaults.SensitiveValueRedacted},
   590  			{Key: "traceparent", Value: "asdf"},
   591  		},
   592  	}, f.GetL7().GetHttp())
   593  }
   594  
   595  func TestFilterHeader(t *testing.T) {
   596  	tests := []struct {
   597  		key            string
   598  		val            string
   599  		redactSettings options.HubbleRedactSettings
   600  		expectedVal    string
   601  	}{
   602  		{
   603  			key: "tracecontent",
   604  			val: "foo_not_redacted",
   605  			redactSettings: options.HubbleRedactSettings{
   606  				Enabled: true,
   607  				RedactHttpHeaders: options.HttpHeadersList{
   608  					Allow: map[string]struct{}{"tracecontent": {}},
   609  					Deny:  map[string]struct{}{},
   610  				},
   611  			},
   612  			expectedVal: "foo_not_redacted",
   613  		},
   614  		{
   615  			key: "tracecontent",
   616  			val: "foo",
   617  			redactSettings: options.HubbleRedactSettings{
   618  				Enabled: true,
   619  				RedactHttpHeaders: options.HttpHeadersList{
   620  					Allow: map[string]struct{}{},
   621  					Deny:  map[string]struct{}{"tracecontent": {}},
   622  				},
   623  			},
   624  			expectedVal: defaults.SensitiveValueRedacted,
   625  		},
   626  		{
   627  			key: "tracecontent",
   628  			val: "foo",
   629  			redactSettings: options.HubbleRedactSettings{
   630  				Enabled: true,
   631  				RedactHttpHeaders: options.HttpHeadersList{
   632  					Allow: map[string]struct{}{},
   633  					Deny:  map[string]struct{}{},
   634  				},
   635  			},
   636  			expectedVal: defaults.SensitiveValueRedacted,
   637  		},
   638  		{
   639  			key: "tracecontent",
   640  			val: "foo",
   641  			redactSettings: options.HubbleRedactSettings{
   642  				Enabled: true,
   643  				RedactHttpHeaders: options.HttpHeadersList{
   644  					Allow: map[string]struct{}{"host": {}},
   645  					Deny:  map[string]struct{}{},
   646  				},
   647  			},
   648  			expectedVal: defaults.SensitiveValueRedacted,
   649  		},
   650  		{
   651  			key: "tracecontent",
   652  			val: "foo",
   653  			redactSettings: options.HubbleRedactSettings{
   654  				Enabled: true,
   655  				RedactHttpHeaders: options.HttpHeadersList{
   656  					Allow: map[string]struct{}{},
   657  					Deny:  map[string]struct{}{"authorization": {}},
   658  				},
   659  			},
   660  			expectedVal: "foo",
   661  		},
   662  	}
   663  	for _, tt := range tests {
   664  		t.Run(tt.key, func(t *testing.T) {
   665  			got := filterHeader(tt.key, tt.val, tt.redactSettings)
   666  			assert.Equal(t, tt.expectedVal, got)
   667  		})
   668  	}
   669  }
   670  
   671  func TestDecodeL7HTTPRequestPasswordRedact(t *testing.T) {
   672  	requestPath, err := url.Parse("http://user:pass@myhost")
   673  	require.NoError(t, err)
   674  	lr := &accesslog.LogRecord{
   675  		Type:                accesslog.TypeRequest,
   676  		Timestamp:           fakeTimestamp,
   677  		NodeAddressInfo:     fakeNodeInfo,
   678  		ObservationPoint:    accesslog.Ingress,
   679  		SourceEndpoint:      fakeSourceEndpoint,
   680  		DestinationEndpoint: fakeDestinationEndpoint,
   681  		IPVersion:           accesslog.VersionIPv4,
   682  		Verdict:             accesslog.VerdictForwarded,
   683  		TransportProtocol:   accesslog.TransportProtocol(u8proto.TCP),
   684  		ServiceInfo:         nil,
   685  		DropReason:          nil,
   686  		HTTP: &accesslog.LogRecordHTTP{
   687  			Code:     0,
   688  			Method:   "POST",
   689  			URL:      requestPath,
   690  			Protocol: "HTTP/1.1",
   691  		},
   692  	}
   693  	lr.SourceEndpoint.Port = 56789
   694  	lr.DestinationEndpoint.Port = 80
   695  
   696  	opts := []options.Option{options.Redact(nil, true, true, false, []string{}, []string{})}
   697  	parser, err := New(log, nil, nil, nil, nil, opts...)
   698  	require.NoError(t, err)
   699  
   700  	f := &flowpb.Flow{}
   701  	err = parser.Decode(lr, f)
   702  	require.NoError(t, err)
   703  	assert.Equal(t, &flowpb.HTTP{
   704  		Code:     0,
   705  		Method:   "POST",
   706  		Url:      fmt.Sprintf("http://user:%s@myhost", defaults.SensitiveValueRedacted),
   707  		Protocol: "HTTP/1.1",
   708  	}, f.GetL7().GetHttp())
   709  }