github.com/cilium/cilium@v1.16.2/pkg/hubble/filters/http.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Hubble
     3  
     4  package filters
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"fmt"
    10  	"net/url"
    11  	"regexp"
    12  	"strings"
    13  
    14  	flowpb "github.com/cilium/cilium/api/v1/flow"
    15  	v1 "github.com/cilium/cilium/pkg/hubble/api/v1"
    16  	"github.com/cilium/cilium/pkg/monitor/api"
    17  )
    18  
    19  func httpMatchCompatibleEventFilter(types []*flowpb.EventTypeFilter) bool {
    20  	if len(types) == 0 {
    21  		return true
    22  	}
    23  
    24  	for _, t := range types {
    25  		if t.GetType() == api.MessageTypeAccessLog {
    26  			return true
    27  		}
    28  	}
    29  
    30  	return false
    31  }
    32  
    33  var (
    34  	httpStatusCodeFull   = regexp.MustCompile(`^[1-5][0-9]{2}$`)
    35  	httpStatusCodePrefix = regexp.MustCompile(`^[1-5][0-9]?\+$`)
    36  )
    37  
    38  func filterByHTTPStatusCode(statusCodePrefixes []string) (FilterFunc, error) {
    39  	var full, prefix []string
    40  	for _, s := range statusCodePrefixes {
    41  		switch {
    42  		case httpStatusCodeFull.MatchString(s):
    43  			full = append(full, s)
    44  		case httpStatusCodePrefix.MatchString(s):
    45  			prefix = append(prefix, strings.TrimSuffix(s, "+"))
    46  		default:
    47  			return nil, fmt.Errorf("invalid status code prefix: %q", s)
    48  		}
    49  	}
    50  
    51  	return func(ev *v1.Event) bool {
    52  		http := ev.GetFlow().GetL7().GetHttp()
    53  		// Not an HTTP response record
    54  		if http == nil || http.Code == 0 {
    55  			return false
    56  		}
    57  
    58  		// Check for both full matches or prefix matches
    59  		httpStatusCode := fmt.Sprintf("%03d", http.Code)
    60  		for _, f := range full {
    61  			if httpStatusCode == f {
    62  				return true
    63  			}
    64  		}
    65  		for _, p := range prefix {
    66  			if strings.HasPrefix(httpStatusCode, p) {
    67  				return true
    68  			}
    69  		}
    70  
    71  		return false
    72  	}, nil
    73  }
    74  
    75  func filterByHTTPMethods(methods []string) (FilterFunc, error) {
    76  	return func(ev *v1.Event) bool {
    77  		http := ev.GetFlow().GetL7().GetHttp()
    78  
    79  		if http == nil || http.Method == "" {
    80  			// Not an HTTP or method is missing
    81  			return false
    82  		}
    83  
    84  		for _, method := range methods {
    85  			if strings.EqualFold(http.Method, method) {
    86  				return true
    87  			}
    88  		}
    89  
    90  		return false
    91  	}, nil
    92  }
    93  
    94  func filterByHTTPUrls(urlRegexpStrs []string) (FilterFunc, error) {
    95  	urlRegexps := make([]*regexp.Regexp, 0, len(urlRegexpStrs))
    96  	for _, urlRegexpStr := range urlRegexpStrs {
    97  		urlRegexp, err := regexp.Compile(urlRegexpStr)
    98  		if err != nil {
    99  			return nil, fmt.Errorf("%s: %w", urlRegexpStr, err)
   100  		}
   101  		urlRegexps = append(urlRegexps, urlRegexp)
   102  	}
   103  
   104  	return func(ev *v1.Event) bool {
   105  		http := ev.GetFlow().GetL7().GetHttp()
   106  
   107  		if http == nil || http.Url == "" {
   108  			return false
   109  		}
   110  
   111  		for _, urlRegexp := range urlRegexps {
   112  			if urlRegexp.MatchString(http.Url) {
   113  				return true
   114  			}
   115  		}
   116  
   117  		return false
   118  	}, nil
   119  }
   120  
   121  func filterByHTTPHeaders(headers []*flowpb.HTTPHeader) (FilterFunc, error) {
   122  	return func(ev *v1.Event) bool {
   123  		http := ev.GetFlow().GetL7().GetHttp()
   124  
   125  		if http == nil || http.GetHeaders() == nil {
   126  			// Not an HTTP or headers are missing
   127  			return false
   128  		}
   129  
   130  		for _, httpHeader := range http.GetHeaders() {
   131  			for _, header := range headers {
   132  				if header.Key == httpHeader.Key && header.Value == httpHeader.Value {
   133  					return true
   134  				}
   135  			}
   136  		}
   137  
   138  		return false
   139  	}, nil
   140  }
   141  
   142  func filterByHTTPPaths(pathRegexpStrs []string) (FilterFunc, error) {
   143  	pathRegexps := make([]*regexp.Regexp, 0, len(pathRegexpStrs))
   144  	for _, pathRegexpStr := range pathRegexpStrs {
   145  		pathRegexp, err := regexp.Compile(pathRegexpStr)
   146  		if err != nil {
   147  			return nil, fmt.Errorf("%s: %w", pathRegexpStr, err)
   148  		}
   149  		pathRegexps = append(pathRegexps, pathRegexp)
   150  	}
   151  
   152  	return func(ev *v1.Event) bool {
   153  		http := ev.GetFlow().GetL7().GetHttp()
   154  
   155  		if http == nil || http.Url == "" {
   156  			return false
   157  		}
   158  
   159  		uri, err := url.ParseRequestURI(http.Url)
   160  		if err != nil {
   161  			// Silently drop all invalid URIs as there is nothing else we can
   162  			// do.
   163  			return false
   164  		}
   165  		for _, pathRegexp := range pathRegexps {
   166  			if pathRegexp.MatchString(uri.Path) {
   167  				return true
   168  			}
   169  		}
   170  
   171  		return false
   172  	}, nil
   173  }
   174  
   175  // HTTPFilter implements filtering based on HTTP metadata
   176  type HTTPFilter struct{}
   177  
   178  // OnBuildFilter builds a HTTP filter
   179  func (h *HTTPFilter) OnBuildFilter(ctx context.Context, ff *flowpb.FlowFilter) ([]FilterFunc, error) {
   180  	var fs []FilterFunc
   181  
   182  	if ff.GetHttpStatusCode() != nil {
   183  		if !httpMatchCompatibleEventFilter(ff.GetEventType()) {
   184  			return nil, errors.New("filtering by http status code requires " +
   185  				"the event type filter to only match 'l7' events")
   186  		}
   187  
   188  		hsf, err := filterByHTTPStatusCode(ff.GetHttpStatusCode())
   189  		if err != nil {
   190  			return nil, fmt.Errorf("invalid http status code filter: %w", err)
   191  		}
   192  		fs = append(fs, hsf)
   193  	}
   194  
   195  	if ff.GetHttpMethod() != nil {
   196  		if !httpMatchCompatibleEventFilter(ff.GetEventType()) {
   197  			return nil, errors.New("filtering by http method requires " +
   198  				"the event type filter to only match 'l7' events")
   199  		}
   200  
   201  		methodf, err := filterByHTTPMethods(ff.GetHttpMethod())
   202  		if err != nil {
   203  			return nil, fmt.Errorf("invalid http method filter: %w", err)
   204  		}
   205  		fs = append(fs, methodf)
   206  	}
   207  
   208  	if ff.GetHttpPath() != nil {
   209  		if !httpMatchCompatibleEventFilter(ff.GetEventType()) {
   210  			return nil, errors.New("filtering by http path requires " +
   211  				"the event type filter to only match 'l7' events")
   212  		}
   213  
   214  		pathf, err := filterByHTTPPaths(ff.GetHttpPath())
   215  		if err != nil {
   216  			return nil, fmt.Errorf("invalid http path filter: %w", err)
   217  		}
   218  		fs = append(fs, pathf)
   219  	}
   220  
   221  	if ff.GetHttpUrl() != nil {
   222  		if !httpMatchCompatibleEventFilter(ff.GetEventType()) {
   223  			return nil, errors.New("filtering by http url requires " +
   224  				"the event type filter to only match 'l7' events")
   225  		}
   226  
   227  		pathf, err := filterByHTTPUrls(ff.GetHttpUrl())
   228  		if err != nil {
   229  			return nil, fmt.Errorf("invalid http url filter: %w", err)
   230  		}
   231  		fs = append(fs, pathf)
   232  	}
   233  
   234  	if ff.GetHttpHeader() != nil {
   235  		if !httpMatchCompatibleEventFilter(ff.GetEventType()) {
   236  			return nil, errors.New("filtering by http headers requires " +
   237  				"the event type filter to only match 'l7' events")
   238  		}
   239  
   240  		headerf, err := filterByHTTPHeaders(ff.GetHttpHeader())
   241  		if err != nil {
   242  			return nil, fmt.Errorf("invalid http header filter: %w", err)
   243  		}
   244  		fs = append(fs, headerf)
   245  	}
   246  
   247  	return fs, nil
   248  }