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 }