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  }