github.com/avenga/couper@v1.12.2/fuzz/server/http.go (about) 1 package server 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "net" 8 "net/http" 9 "os" 10 "runtime" 11 "strings" 12 "time" 13 14 "github.com/sirupsen/logrus" 15 16 "github.com/avenga/couper/cache" 17 "github.com/avenga/couper/command" 18 "github.com/avenga/couper/config/configload" 19 couperruntime "github.com/avenga/couper/config/runtime" 20 "github.com/avenga/couper/internal/test" 21 "github.com/avenga/couper/server" 22 ) 23 24 var client *http.Client 25 var logs io.Reader 26 27 func init() { 28 upstream := test.NewBackend() 29 30 configFileContent := fmt.Sprintf(` 31 server "fuzz" { 32 endpoint "/**" { 33 add_request_headers = { 34 x-fuzz = request.headers.x-data 35 } 36 37 add_query_params = { 38 x-quzz = request.headers.x-data 39 } 40 41 request "sidekick" { 42 url = "http://%s/anything/" 43 body = request.headers.x-data 44 } 45 46 # default 47 proxy { 48 path = "/anything" 49 url = "http://%s" 50 } 51 52 add_response_headers = { 53 y-fuzz = request.headers.x-data 54 x-sidekick = backend_responses.sidekick.json_body 55 } 56 } 57 } 58 59 settings { 60 default_port = 0 61 no_proxy_from_env = true 62 } 63 `, upstream.Addr(), upstream.Addr()) 64 65 configFile, err := configload.LoadBytes([]byte(configFileContent), "fuzz_http.hcl") 66 if err != nil { 67 panic(err) 68 } 69 70 r, w, err := os.Pipe() 71 if err != nil { 72 panic(err) 73 } 74 logs = r 75 logger := logrus.New() 76 logger.Out = w 77 log := logger.WithField("fuzz", "server/http") 78 79 cmdCtx := command.ContextWithSignal(context.Background()) 80 config, err := couperruntime.NewServerConfiguration(configFile, log, cache.New(log, cmdCtx.Done())) 81 if err != nil { 82 panic("init error: " + err.Error()) 83 } 84 85 servers, _, err := server.NewServers(cmdCtx, configFile.Context, log, configFile.Settings, &couperruntime.DefaultTimings, config) 86 if err != nil { 87 panic("init error: " + err.Error()) 88 } 89 90 var addr string 91 for _, s := range servers { 92 if err = s.Listen(); err != nil { 93 panic("init error: " + err.Error()) 94 } else { 95 addr = s.Addr() 96 break // support just one server 97 } 98 } 99 100 d := &net.Dialer{Timeout: time.Second} 101 client = &http.Client{ 102 Timeout: time.Second * 30, 103 Transport: &http.Transport{ 104 MaxIdleConns: 10, 105 IdleConnTimeout: time.Second, 106 MaxConnsPerHost: 0, 107 DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) { 108 return d.DialContext(ctx, "tcp4", addr) 109 }, 110 }, 111 } 112 } 113 114 func Fuzz(data []byte) int { 115 req, _ := http.NewRequest(http.MethodGet, "http://localhost:8080/", nil) 116 req.URL.Path += string(data) 117 118 req.Header.Set("X-Data", string(data)) 119 120 values := req.URL.Query() 121 values.Add("X-Data", string(data)) 122 req.URL.RawQuery = values.Encode() 123 124 res, err := client.Do(req) 125 if err != nil { 126 if strings.Contains(err.Error(), "net/http: invalid") { 127 return 0 // useless input, invalid http 128 } 129 panic(err) 130 return 1 // useful input 131 } 132 133 logData, err := io.ReadAll(logs) 134 if err != nil { 135 panic("reading log-data:" + err.Error()) 136 } 137 138 if res.Header.Get("y-fuzz") != string(data) { 139 panic("request / response data are not equal:\n" + string(logData)) 140 return 1 141 } 142 143 if res.StatusCode > 499 { // useful fuzz input 144 panic("server error: " + res.Status + "\n" + string(logData)) 145 return 1 146 } 147 148 if strings.Contains(string(logData), "panic:") { 149 panic("couper panic: " + string(logData)) 150 return 1 151 } 152 153 if runtime.NumGoroutine() > 100 { 154 panic("goroutine leak") 155 return 1 156 } 157 158 return 0 159 }