still working?

This commit is contained in:
Adrien PONSIN 2025-04-17 21:16:07 +02:00
parent c55dec6bc4
commit fcaafbf3f4
No known key found for this signature in database
GPG Key ID: 7B4D4A32C05C475E
2 changed files with 116 additions and 35 deletions

View File

@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"gitea.illuad.fr/adrien/middleman" "gitea.illuad.fr/adrien/middleman"
"gitea.illuad.fr/adrien/middleman/flag" "gitea.illuad.fr/adrien/middleman/flag"
"gitea.illuad.fr/adrien/middleman/pkg/fastrp"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v3"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
@ -17,7 +18,6 @@ import (
"regexp" "regexp"
"slices" "slices"
"strings" "strings"
"sync"
"time" "time"
) )
@ -34,10 +34,14 @@ type addrPorts struct {
// methodRegex maps HTTP methods (GET, POST, DELETE...) to a compiled regular expression. // methodRegex maps HTTP methods (GET, POST, DELETE...) to a compiled regular expression.
type methodRegex = map[string]*regexp.Regexp 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. // ProxyHandler takes an incoming request and sends it to another server, proxying the response back to the client.
type ProxyHandler struct { type ProxyHandler struct {
rp *httputil.ReverseProxy frp *fastrp.FastRP
connPool *sync.Pool
} }
// ServeCmd is the command name. // 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) http.Error(w, http.StatusText(code), code)
return return
} }
ph.proxyRequest(w, r) ph.frp.ServeHTTP(w, r)
return return
} }
host, _, _ := net.SplitHostPort(r.RemoteAddr) 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) http.Error(w, http.StatusText(code), code)
return return
} }
ph.proxyRequest(w, r) ph.frp.ServeHTTP(w, r)
return return
} }
} }
@ -157,11 +161,42 @@ func (ph *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
} }
func (ph *ProxyHandler) proxyRequest(w http.ResponseWriter, r *http.Request) { func (ph *ProxyHandlerOld) ServeHTTP(w http.ResponseWriter, r *http.Request) {
transport := ph.connPool.Get().(*http.Transport) log.Debug().Str("remote_addr", r.RemoteAddr).Str("method", r.Method).Str("path", r.URL.Path).Msg("incoming request")
defer ph.connPool.Put(transport) if mr, ok := containerMethodRegex["*"]; ok {
ph.rp.Transport = transport if code := ph.checkMethodAndRegex(mr, r, ""); code != http.StatusOK {
http.Error(w, http.StatusText(code), code)
return
}
ph.rp.ServeHTTP(w, r) 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 { func (ph *ProxyHandler) isContainerAuthorized(containerName, host string) bool {
@ -197,6 +232,20 @@ func logAuthorizedRequest(r *http.Request, containerName, message string) {
l.Msg(message) 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 { func (ph *ProxyHandler) checkMethodAndRegex(mr methodRegex, r *http.Request, containerName string) int {
req, ok := mr[r.Method] req, ok := mr[r.Method]
if !ok { if !ok {
@ -226,33 +275,23 @@ func (s serve) action(ctx context.Context, command *cli.Command) error {
return err return err
} }
dummyURL, _ := url.Parse("http://dummy") dummyURL, _ := url.Parse("http://dummy")
/*
rp := httputil.NewSingleHostReverseProxy(dummyURL) // Old
rp.Transport = &http.Transport{ // rp := httputil.NewSingleHostReverseProxy(dummyURL)
DialContext: func(_ context.Context, _ string, _ string) (net.Conn, error) { // rp.Transport = &http.Transport{
return net.Dial("unix", command.String(dockerSocketPathFlagName)) // DialContext: func(_ context.Context, _ string, _ string) (net.Conn, error) {
}, // return net.Dial("unix", command.String(dockerSocketPathFlagName))
MaxIdleConns: 10, // },
} // }
*/
transport := &http.Transport{ // New
DialContext: func(_ context.Context, _ string, _ string) (net.Conn, error) { rp := fastrp.NewRP("unix", command.String(dockerSocketPathFlagName), dummyURL)
return net.Dial("unix", command.String(dockerSocketPathFlagName))
},
MaxIdleConns: 10,
}
ph := &ProxyHandler{
rp: httputil.NewSingleHostReverseProxy(dummyURL),
connPool: &sync.Pool{
New: func() any {
return transport.Clone()
},
},
}
srv := &http.Server{ // #nosec: G112 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 { s.group.Go(func() error {
if err = srv.Serve(l); err != nil && !errors.Is(err, http.ErrServerClosed) { if err = srv.Serve(l); err != nil && !errors.Is(err, http.ErrServerClosed) {
return err return err

42
pkg/fastrp/fastrp.go Normal file
View File

@ -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)
}