github.com/avenga/couper@v1.12.2/server/tls_dev_proxy.go (about)

     1  package server
     2  
     3  import (
     4  	"context"
     5  	"crypto/tls"
     6  	"fmt"
     7  	"net"
     8  	"net/http"
     9  	"net/http/httputil"
    10  	"net/url"
    11  	"strconv"
    12  	"strings"
    13  	"sync"
    14  	"time"
    15  
    16  	"github.com/rs/xid"
    17  	"github.com/sirupsen/logrus"
    18  
    19  	"github.com/avenga/couper/config"
    20  	"github.com/avenga/couper/config/request"
    21  	"github.com/avenga/couper/config/runtime"
    22  	"github.com/avenga/couper/errors"
    23  	"github.com/avenga/couper/handler/middleware"
    24  	"github.com/avenga/couper/logging"
    25  	"github.com/avenga/couper/server/writer"
    26  )
    27  
    28  type ListenPort string
    29  type Ports []string
    30  type TLSDevPorts map[ListenPort]Ports
    31  
    32  const TLSProxyOption = "https_dev_proxy"
    33  
    34  var httpsDevProxyIDField = "x-" + xid.New().String()
    35  
    36  func (tdp TLSDevPorts) Add(pair string) error {
    37  	ports := strings.Split(pair, ":")
    38  	if len(ports) != 2 {
    39  		return errors.Configuration.Messagef("%s: invalid port mapping: %s", TLSProxyOption, pair)
    40  	}
    41  	for _, p := range ports {
    42  		if _, err := strconv.Atoi(p); err != nil {
    43  			return errors.Configuration.Messagef("%s: invalid format: %s", TLSProxyOption, pair).With(err)
    44  		}
    45  	}
    46  
    47  	if dp, exist := tdp[ListenPort(ports[1])]; exist && dp.Contains(ports[0]) {
    48  		return errors.Configuration.Messagef("https_dev_proxy: tls port already defined: %s", pair)
    49  	}
    50  
    51  	tdp[ListenPort(ports[1])] = append(tdp[ListenPort(ports[1])], ports[0])
    52  	return nil
    53  }
    54  
    55  func (tdp TLSDevPorts) Get(p string) []string {
    56  	if result, exist := tdp[ListenPort(p)]; exist {
    57  		return result
    58  	}
    59  	return nil
    60  }
    61  
    62  func (tp Ports) Contains(needle string) bool {
    63  	for _, p := range tp {
    64  		if p == needle {
    65  			return true
    66  		}
    67  	}
    68  	return false
    69  }
    70  
    71  func (lp ListenPort) Port() runtime.Port {
    72  	i, _ := strconv.Atoi(string(lp))
    73  	return runtime.Port(i)
    74  }
    75  
    76  func NewTLSProxy(addr, port string, logger logrus.FieldLogger, settings *config.Settings) (*http.Server, error) {
    77  	origin, err := url.Parse(fmt.Sprintf("http://%s/", addr))
    78  	if err != nil {
    79  		return nil, err
    80  	}
    81  
    82  	logEntry := logger.WithField("type", "couper_access_tls")
    83  
    84  	httpProxy := httputil.NewSingleHostReverseProxy(origin)
    85  	httpProxy.Transport = &http.Transport{ // http.DefaultTransport /wo Proxy
    86  		DialContext: (&net.Dialer{
    87  			Timeout:   30 * time.Second,
    88  			KeepAlive: 30 * time.Second,
    89  		}).DialContext,
    90  		ForceAttemptHTTP2:     true,
    91  		MaxIdleConns:          100,
    92  		IdleConnTimeout:       90 * time.Second,
    93  		TLSHandshakeTimeout:   10 * time.Second,
    94  		ExpectContinueTimeout: 1 * time.Second,
    95  	}
    96  
    97  	headers := []string{"Connection", "Upgrade"}
    98  	accessLog := logging.NewAccessLog(&logging.Config{
    99  		RequestHeaders:  append(logging.DefaultConfig.RequestHeaders, headers...),
   100  		ResponseHeaders: append(logging.DefaultConfig.ResponseHeaders, headers...),
   101  	}, logEntry)
   102  
   103  	initialConfig, err := getTLSConfig(&tls.ClientHelloInfo{})
   104  	if err != nil {
   105  		return nil, err
   106  	}
   107  
   108  	listener, err := net.Listen("tcp4", ":"+port)
   109  	if err != nil {
   110  		return nil, err
   111  	}
   112  
   113  	uidFn := middleware.NewUIDFunc(settings.RequestIDFormat)
   114  
   115  	tlsServer := &http.Server{
   116  		Addr:     ":" + port,
   117  		ErrorLog: newErrorLogWrapper(logEntry),
   118  		Handler: http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
   119  			uid := uidFn()
   120  			req.Header.Set(httpsDevProxyIDField, uid)
   121  
   122  			ctx := context.WithValue(req.Context(), request.ServerName, "couper_tls")
   123  			ctx = context.WithValue(ctx, request.UID, uid)
   124  			ctx = context.WithValue(ctx, request.StartTime, time.Now())
   125  
   126  			req.Header.Set("Forwarded", fmt.Sprintf("for=%s;proto=https;host=%s;by=%s", req.RemoteAddr, req.Host, listener.Addr().String()))
   127  			req.Header.Set("Via", "couper-https-dev-proxy")
   128  			req.Header.Set("X-Forwarded-For", req.RemoteAddr+", "+listener.Addr().String())
   129  			req.Header.Set("X-Forwarded-Host", req.Host)
   130  			req.Header.Set("X-Forwarded-Proto", "https")
   131  
   132  			req.URL.Host = req.Host
   133  
   134  			respW := writer.NewResponseWriter(rw, "")
   135  			outreq := req.WithContext(ctx)
   136  			httpProxy.ServeHTTP(respW, outreq)
   137  			accessLog.Do(respW, outreq)
   138  		}),
   139  		TLSConfig: initialConfig,
   140  	}
   141  
   142  	go tlsServer.ServeTLS(listener, "", "")
   143  	return tlsServer, err
   144  }
   145  
   146  var tlsConfigurations = map[string]*tls.Config{}
   147  var tlsLock = sync.RWMutex{}
   148  
   149  // getTLSConfig returns a clone from created or memorized tls configuration due to
   150  // transport protocol upgrades / clones later on which would result in data races.
   151  func getTLSConfig(info *tls.ClientHelloInfo) (*tls.Config, error) {
   152  	var hosts []string
   153  	key := "localhost"
   154  	if info.ServerName != "" {
   155  		hosts = append(hosts, info.ServerName)
   156  		key = info.ServerName
   157  	}
   158  
   159  	// Global lock to prevent recreate loop for new connections.
   160  	tlsLock.Lock()
   161  	defer tlsLock.Unlock()
   162  
   163  	tlsConfig, ok := tlsConfigurations[key]
   164  	if !ok || tlsConfig.Certificates[0].Leaf.NotAfter.Before(time.Now()) {
   165  		selfSigned, err := NewCertificate(time.Hour*24, hosts, nil)
   166  		if err != nil {
   167  			return nil, err
   168  		}
   169  		tlsConf := &tls.Config{
   170  			Certificates:       []tls.Certificate{*selfSigned.Server},
   171  			GetConfigForClient: getTLSConfig,
   172  		}
   173  
   174  		tlsConfigurations[key] = tlsConf
   175  		return tlsConf.Clone(), nil
   176  	}
   177  
   178  	return tlsConfig.Clone(), nil
   179  }