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 }