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  }