From fcaafbf3f4d7bdc205cd3285f55c3d96da613796 Mon Sep 17 00:00:00 2001 From: Adrien PONSIN Date: Thu, 17 Apr 2025 21:16:07 +0200 Subject: [PATCH] still working? --- command/serve.go | 109 +++++++++++++++++++++++++++++-------------- pkg/fastrp/fastrp.go | 42 +++++++++++++++++ 2 files changed, 116 insertions(+), 35 deletions(-) create mode 100644 pkg/fastrp/fastrp.go diff --git a/command/serve.go b/command/serve.go index 06adb8f..694b73f 100644 --- a/command/serve.go +++ b/command/serve.go @@ -6,6 +6,7 @@ import ( "fmt" "gitea.illuad.fr/adrien/middleman" "gitea.illuad.fr/adrien/middleman/flag" + "gitea.illuad.fr/adrien/middleman/pkg/fastrp" "github.com/rs/zerolog/log" "github.com/urfave/cli/v3" "golang.org/x/sync/errgroup" @@ -17,7 +18,6 @@ import ( "regexp" "slices" "strings" - "sync" "time" ) @@ -34,10 +34,14 @@ type addrPorts struct { // methodRegex maps HTTP methods (GET, POST, DELETE...) to a compiled regular expression. type methodRegex = map[string]*regexp.Regexp +// ProxyHandlerOld takes an incoming request and sends it to another server, proxying the response back to the client. +type ProxyHandlerOld struct { + rp *httputil.ReverseProxy +} + // ProxyHandler takes an incoming request and sends it to another server, proxying the response back to the client. type ProxyHandler struct { - rp *httputil.ReverseProxy - connPool *sync.Pool + frp *fastrp.FastRP } // ServeCmd is the command name. @@ -139,7 +143,7 @@ func (ph *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { http.Error(w, http.StatusText(code), code) return } - ph.proxyRequest(w, r) + ph.frp.ServeHTTP(w, r) return } host, _, _ := net.SplitHostPort(r.RemoteAddr) @@ -149,7 +153,7 @@ func (ph *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { http.Error(w, http.StatusText(code), code) return } - ph.proxyRequest(w, r) + ph.frp.ServeHTTP(w, r) return } } @@ -157,11 +161,42 @@ func (ph *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) } -func (ph *ProxyHandler) proxyRequest(w http.ResponseWriter, r *http.Request) { - transport := ph.connPool.Get().(*http.Transport) - defer ph.connPool.Put(transport) - ph.rp.Transport = transport - ph.rp.ServeHTTP(w, r) +func (ph *ProxyHandlerOld) ServeHTTP(w http.ResponseWriter, r *http.Request) { + log.Debug().Str("remote_addr", r.RemoteAddr).Str("method", r.Method).Str("path", r.URL.Path).Msg("incoming request") + if mr, ok := containerMethodRegex["*"]; ok { + if code := ph.checkMethodAndRegex(mr, r, ""); code != http.StatusOK { + http.Error(w, http.StatusText(code), code) + return + } + ph.rp.ServeHTTP(w, r) + return + } + host, _, _ := net.SplitHostPort(r.RemoteAddr) + for containerName, mr := range containerMethodRegex { + if ph.isContainerAuthorized(containerName, host) { + if code := ph.checkMethodAndRegex(mr, r, containerName); code != http.StatusOK { + http.Error(w, http.StatusText(code), code) + return + } + ph.rp.ServeHTTP(w, r) + return + } + } + logDeniedRequest(r, http.StatusUnauthorized, "this container is not on the list of authorized ones") + http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) +} + +func (ph *ProxyHandlerOld) isContainerAuthorized(containerName, host string) bool { + resolvedIPs, err := net.LookupIP(containerName) + if err != nil { + return false + } + for resolvedIP := range slices.Values(resolvedIPs) { + if resolvedIP.Equal(net.ParseIP(host)) { + return true + } + } + return false } func (ph *ProxyHandler) isContainerAuthorized(containerName, host string) bool { @@ -197,6 +232,20 @@ func logAuthorizedRequest(r *http.Request, containerName, message string) { l.Msg(message) } +func (ph *ProxyHandlerOld) checkMethodAndRegex(mr methodRegex, r *http.Request, containerName string) int { + req, ok := mr[r.Method] + if !ok { + logDeniedRequest(r, http.StatusMethodNotAllowed, "this HTTP method is not in the list of those authorized for this container") + return http.StatusMethodNotAllowed + } + if !req.MatchString(r.URL.Path) { + logDeniedRequest(r, http.StatusForbidden, "this path does not match any regular expression for this HTTP method") + return http.StatusForbidden + } + logAuthorizedRequest(r, containerName, "incoming request matches a registered regular expression") + return http.StatusOK +} + func (ph *ProxyHandler) checkMethodAndRegex(mr methodRegex, r *http.Request, containerName string) int { req, ok := mr[r.Method] if !ok { @@ -226,33 +275,23 @@ func (s serve) action(ctx context.Context, command *cli.Command) error { return err } dummyURL, _ := url.Parse("http://dummy") - /* - rp := httputil.NewSingleHostReverseProxy(dummyURL) - rp.Transport = &http.Transport{ - DialContext: func(_ context.Context, _ string, _ string) (net.Conn, error) { - return net.Dial("unix", command.String(dockerSocketPathFlagName)) - }, - MaxIdleConns: 10, - } - */ - transport := &http.Transport{ - DialContext: func(_ context.Context, _ string, _ string) (net.Conn, error) { - return net.Dial("unix", command.String(dockerSocketPathFlagName)) - }, - MaxIdleConns: 10, - } - ph := &ProxyHandler{ - rp: httputil.NewSingleHostReverseProxy(dummyURL), - connPool: &sync.Pool{ - New: func() any { - return transport.Clone() - }, - }, - } + + // Old + // rp := httputil.NewSingleHostReverseProxy(dummyURL) + // rp.Transport = &http.Transport{ + // DialContext: func(_ context.Context, _ string, _ string) (net.Conn, error) { + // return net.Dial("unix", command.String(dockerSocketPathFlagName)) + // }, + // } + + // New + rp := fastrp.NewRP("unix", command.String(dockerSocketPathFlagName), dummyURL) + srv := &http.Server{ // #nosec: G112 - Handler: ph, + // Handler: &ProxyHandlerOld{rp: rp}, + Handler: &ProxyHandler{frp: rp}, } - ph.rp.Transport = ph.connPool.Get().(*http.Transport) + s.group.Go(func() error { if err = srv.Serve(l); err != nil && !errors.Is(err, http.ErrServerClosed) { return err diff --git a/pkg/fastrp/fastrp.go b/pkg/fastrp/fastrp.go new file mode 100644 index 0000000..9285187 --- /dev/null +++ b/pkg/fastrp/fastrp.go @@ -0,0 +1,42 @@ +package fastrp + +import ( + "context" + "net" + "net/http" + "net/http/httputil" + "net/url" + "sync" +) + +// FastRP takes an incoming request and sends it fast to another server, proxying the response back to the client fast. +type FastRP struct { + rp *httputil.ReverseProxy + connPool *sync.Pool +} + +func NewRP(network, address string, url *url.URL) *FastRP { + roundTripper := &http.Transport{ + DialContext: func(_ context.Context, _ string, _ string) (net.Conn, error) { + return net.Dial(network, address) + }, + MaxIdleConns: 10, + } + frp := &FastRP{ + rp: httputil.NewSingleHostReverseProxy(url), + connPool: &sync.Pool{ + New: func() any { + return roundTripper.Clone() + }, + }, + } + frp.rp.Transport = frp.connPool.Get().(http.RoundTripper) + return frp +} + +func (frp *FastRP) ServeHTTP(w http.ResponseWriter, r *http.Request) { + roundTripper := frp.connPool.Get().(http.RoundTripper) + defer frp.connPool.Put(roundTripper) + frp.rp.Transport = roundTripper + frp.rp.ServeHTTP(w, r) +}