github.com/PurpleSec/switchproxy@v1.6.2/proxy.go (about) 1 // Copyright 2021 - 2022 PurpleSec Team 2 // 3 // This program is free software: you can redistribute it and/or modify 4 // it under the terms of the GNU Affero General Public License as published 5 // by the Free Software Foundation, either version 3 of the License, or 6 // (at your option) any later version. 7 // 8 // This program is distributed in the hope that it will be useful, 9 // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 // GNU Affero General Public License for more details. 12 // 13 // You should have received a copy of the GNU Affero General Public License 14 // along with this program. If not, see <https://www.gnu.org/licenses/>. 15 // 16 17 package switchproxy 18 19 import ( 20 "bytes" 21 "context" 22 "crypto/tls" 23 "io" 24 "net" 25 "net/http" 26 "sync" 27 "time" 28 ) 29 30 // DefaultTimeout is the default timeout value used when a Timeout is not 31 // specified in NewProxy. 32 const DefaultTimeout = time.Second * time.Duration(15) 33 34 // Proxy is a struct that represents a stacked proxy that allows a forwarding proxy 35 // with secondary read only Switch connections that allow logging and storing 36 // the connection data. 37 type Proxy struct { 38 ctx context.Context 39 key string 40 cert string 41 pool *sync.Pool 42 server *http.Server 43 cancel context.CancelFunc 44 primary *Switch 45 secondary []*Switch 46 } 47 type transfer struct { 48 in *bytes.Reader 49 out *bytes.Buffer 50 read *bytes.Buffer 51 data []byte 52 } 53 54 // Close attempts to gracefully close and stop the proxy and all remaining 55 // connections. 56 func (p *Proxy) Close() error { 57 p.cancel() 58 return p.server.Close() 59 } 60 61 // Start starts the Server listening loop and returns an error if the server 62 // could not be started. 63 // 64 // Only returns an error if any IO issues occur during operation. 65 func (p *Proxy) Start() error { 66 var err error 67 if len(p.cert) > 0 && len(p.key) > 0 { 68 p.server.TLSConfig = &tls.Config{ 69 NextProtos: []string{"h2", "http/1.1"}, 70 MinVersion: tls.VersionTLS12, 71 CipherSuites: []uint16{ 72 tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, 73 tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, 74 tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, 75 tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, 76 tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 77 tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 78 }, 79 CurvePreferences: []tls.CurveID{tls.CurveP256, tls.X25519}, 80 } 81 err = p.server.ListenAndServeTLS(p.cert, p.key) 82 } else { 83 err = p.server.ListenAndServe() 84 } 85 p.Close() 86 return err 87 } 88 89 // Primary sets the primary Proxy Switch context. 90 func (p *Proxy) Primary(s *Switch) { 91 p.primary = s 92 } 93 func (p *Proxy) clear(t *transfer) { 94 t.in, t.data = nil, nil 95 t.out.Reset() 96 t.read.Reset() 97 p.pool.Put(t) 98 } 99 100 // AddSecondary adds a one-way Switch context. 101 func (p *Proxy) AddSecondary(s ...*Switch) { 102 p.secondary = append(p.secondary, s...) 103 } 104 func (p *Proxy) context(_ net.Listener) context.Context { 105 return p.ctx 106 } 107 108 // ServeHTTP satisfies the http.Handler interface. 109 func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { 110 t := p.pool.Get().(*transfer) 111 if _, err := io.Copy(t.read, r.Body); err != nil { 112 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 113 p.clear(t) 114 r.Body.Close() 115 return 116 } 117 t.data = t.read.Bytes() 118 if t.in = bytes.NewReader(t.data); p.primary != nil { 119 if s, h, err := p.primary.process(p.ctx, r, t); err != nil { 120 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 121 } else { 122 for k, v := range h { 123 w.Header()[k] = v 124 } 125 w.WriteHeader(s) 126 if _, err := io.Copy(w, t.out); err != nil { 127 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 128 } 129 } 130 } else { 131 http.Error(w, http.StatusText(http.StatusServiceUnavailable), http.StatusServiceUnavailable) 132 } 133 if len(p.secondary) > 0 { 134 for i := range p.secondary { 135 t.out.Reset() 136 t.in.Seek(0, 0) 137 p.secondary[i].process(p.ctx, r, t) 138 } 139 } 140 p.clear(t) 141 r.Body.Close() 142 }