...

Source file src/github.com/emissary-ingress/emissary/v3/cmd/kat-server/services/http.go

Documentation: github.com/emissary-ingress/emissary/v3/cmd/kat-server/services

     1  package services
     2  
     3  import (
     4  	"context"
     5  	"crypto/tls"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io/ioutil"
     9  	"net/http"
    10  	"os"
    11  	"strconv"
    12  	"strings"
    13  	"time"
    14  
    15  	"github.com/datawire/dlib/dgroup"
    16  	"github.com/datawire/dlib/dhttp"
    17  	"github.com/datawire/dlib/dlog"
    18  )
    19  
    20  // HTTP server object (all fields are required).
    21  type HTTP struct {
    22  	Port          int16
    23  	Backend       string
    24  	SecurePort    int16
    25  	SecureBackend string
    26  	Cert          string
    27  	Key           string
    28  	TLSVersion    string
    29  }
    30  
    31  func getTLSVersion(state *tls.ConnectionState) string {
    32  	switch state.Version {
    33  	case tls.VersionTLS10:
    34  		return "v1.0"
    35  	case tls.VersionTLS11:
    36  		return "v1.1"
    37  	case tls.VersionTLS12:
    38  		return "v1.2"
    39  	// TLS v1.3 is experimental.
    40  	case 0x0304:
    41  		return "v1.3"
    42  	default:
    43  		return "unknown"
    44  	}
    45  }
    46  
    47  // Start initializes the HTTP server.
    48  func (h *HTTP) Start(ctx context.Context) <-chan bool {
    49  	dlog.Printf(ctx, "HTTP: %s listening on %d/%d", h.Backend, h.Port, h.SecurePort)
    50  
    51  	mux := http.NewServeMux()
    52  	mux.HandleFunc("/", h.handler)
    53  
    54  	sc := &dhttp.ServerConfig{
    55  		Handler: mux,
    56  	}
    57  
    58  	g := dgroup.NewGroup(ctx, dgroup.GroupConfig{})
    59  	g.Go("cleartext", func(ctx context.Context) error {
    60  		return sc.ListenAndServe(ctx, fmt.Sprintf(":%v", h.Port))
    61  	})
    62  	g.Go("tls", func(ctx context.Context) error {
    63  		return sc.ListenAndServeTLS(ctx, fmt.Sprintf(":%v", h.SecurePort), h.Cert, h.Key)
    64  	})
    65  
    66  	exited := make(chan bool)
    67  	go func() {
    68  		if err := g.Wait(); err != nil {
    69  			dlog.Error(ctx, err)
    70  			panic(err) // TODO: do something better
    71  		}
    72  		close(exited)
    73  	}()
    74  	return exited
    75  }
    76  
    77  // Helpers
    78  func lower(m map[string][]string) (result map[string][]string) {
    79  	result = make(map[string][]string)
    80  	for k, v := range m {
    81  		result[strings.ToLower(k)] = v
    82  	}
    83  	return result
    84  }
    85  
    86  func (h *HTTP) handler(w http.ResponseWriter, r *http.Request) {
    87  	ctx := r.Context()
    88  	// Assume we're the clear side of the world.
    89  	backend := h.Backend
    90  	conntype := "CLR"
    91  
    92  	var request = make(map[string]interface{})
    93  	var url = make(map[string]interface{})
    94  	request["url"] = url
    95  	url["fragment"] = r.URL.Fragment
    96  	url["host"] = r.URL.Host
    97  	url["opaque"] = r.URL.Opaque
    98  	url["path"] = r.URL.Path
    99  	url["query"] = r.URL.Query()
   100  	url["rawQuery"] = r.URL.RawQuery
   101  	url["scheme"] = r.URL.Scheme
   102  	if r.URL.User != nil {
   103  		url["username"] = r.URL.User.Username()
   104  		pw, ok := r.URL.User.Password()
   105  		if ok {
   106  			url["password"] = pw
   107  		}
   108  	}
   109  	request["method"] = r.Method
   110  	request["headers"] = lower(r.Header)
   111  	request["host"] = r.Host
   112  
   113  	var tlsrequest = make(map[string]interface{})
   114  	request["tls"] = tlsrequest
   115  
   116  	tlsrequest["enabled"] = r.TLS != nil
   117  
   118  	if r.TLS != nil {
   119  		// We're the secure side of the world, I guess.
   120  		backend = h.SecureBackend
   121  		conntype = "TLS"
   122  
   123  		tlsrequest["negotiated-protocol"] = r.TLS.NegotiatedProtocol
   124  		tlsrequest["server-name"] = r.TLS.ServerName
   125  		tlsrequest["negotiated-protocol-version"] = getTLSVersion(r.TLS)
   126  	}
   127  
   128  	// respond with the requested status
   129  	status := r.Header.Get("Kat-Req-Http-Requested-Status")
   130  	if status == "" {
   131  		status = "200"
   132  	}
   133  
   134  	statusCode, err := strconv.Atoi(status)
   135  	if err != nil {
   136  		dlog.Print(ctx, err)
   137  		statusCode = 500
   138  	}
   139  
   140  	// copy the requested headers into the response
   141  	headers, ok := r.Header["Kat-Req-Http-Requested-Header"]
   142  	if ok {
   143  		for _, header := range headers {
   144  			canonical := http.CanonicalHeaderKey(header)
   145  			value, ok := r.Header[canonical]
   146  			if ok {
   147  				w.Header()[canonical] = value
   148  			}
   149  		}
   150  	}
   151  
   152  	if b, _ := ioutil.ReadAll(r.Body); b != nil {
   153  		body := string(b)
   154  		if len(body) > 0 {
   155  			dlog.Printf(ctx, "received body: %s", body)
   156  		}
   157  		w.Header()[http.CanonicalHeaderKey("Kat-Resp-Http-Request-Body")] = []string{body}
   158  	}
   159  	defer r.Body.Close()
   160  
   161  	cookies, ok := r.Header["Kat-Req-Http-Requested-Cookie"]
   162  	if ok {
   163  		for _, v := range strings.Split(cookies[0], ",") {
   164  			val := strings.Trim(v, " ")
   165  			http.SetCookie(w, &http.Cookie{
   166  				Name:  val,
   167  				Value: val,
   168  			})
   169  		}
   170  	}
   171  
   172  	// If they asked for a specific location to be returned, handle that too.
   173  	location, ok := r.Header["Kat-Req-Http-Requested-Location"]
   174  
   175  	if ok {
   176  		w.Header()[http.CanonicalHeaderKey("Location")] = location
   177  	}
   178  
   179  	addExtauthEnv := os.Getenv("INCLUDE_EXTAUTH_HEADER")
   180  	// KAT tests that sent really big request headers might 503 if we send the request headers
   181  	// in the response. Enable tests to override the env var
   182  	addExtAuthOverride := r.URL.Query().Get("override_extauth_header")
   183  
   184  	if len(addExtauthEnv) > 0 && len(addExtAuthOverride) == 0 {
   185  		extauth := make(map[string]interface{})
   186  		extauth["request"] = request
   187  		extauth["resp_headers"] = lower(w.Header())
   188  
   189  		eaJSON, err := json.Marshal(extauth)
   190  
   191  		if err != nil {
   192  			eaJSON = []byte(fmt.Sprintf("err: %v", err))
   193  		}
   194  
   195  		eaArray := make([]string, 1, 1)
   196  		eaArray[0] = string(eaJSON)
   197  
   198  		w.Header()[http.CanonicalHeaderKey("extauth")] = eaArray
   199  	}
   200  
   201  	// Check header and delay response.
   202  	if h, ok := r.Header["Kat-Req-Http-Requested-Backend-Delay"]; ok {
   203  		if v, err := strconv.Atoi(h[0]); err == nil {
   204  			dlog.Printf(ctx, "Delaying response by %v ms", v)
   205  			time.Sleep(time.Duration(v) * time.Millisecond)
   206  		}
   207  	}
   208  
   209  	// Set date response header.
   210  	w.Header().Set("Date", time.Now().Format(time.RFC1123))
   211  
   212  	w.WriteHeader(statusCode)
   213  
   214  	// Write out all request/response information
   215  	var response = make(map[string]interface{})
   216  	response["headers"] = lower(w.Header())
   217  
   218  	var body = make(map[string]interface{})
   219  	body["backend"] = backend
   220  	body["request"] = request
   221  	body["response"] = response
   222  
   223  	b, err := json.MarshalIndent(body, "", "  ")
   224  	if err != nil {
   225  		b = []byte(fmt.Sprintf("Error: %v", err))
   226  	}
   227  
   228  	dlog.Printf(ctx, "%s (%s): \"%s %s\" -> HTTP %v", r.Method, r.URL.Path, backend, conntype, statusCode)
   229  	_, _ = w.Write(b)
   230  }
   231  

View as plain text