From 47cd934e31392146ab7f9fe3c3d2d689c8e2278d Mon Sep 17 00:00:00 2001 From: Achmad Irianto Eka Putra Date: Thu, 12 Dec 2024 06:21:06 +0700 Subject: [PATCH 1/7] feat(proxy): add HTTP proxy server and enhance auth handling Introduced an HTTP proxy server alongside the existing SOCKS5 implementation. Refactored and renamed packages (e.g., credentials, resolver) for better modularity and reuse across proxies. Added HTTP basic authentication support and improved logging for both modes. --- Dockerfile | 1 + Dockerfile-tor | 1 + nanoproxy.go | 63 ++++- pkg/config/config.go | 1 + pkg/{socks5 => credential}/credentials.go | 4 +- .../credentials_test.go | 5 +- pkg/httpproxy/httpproxy.go | 251 ++++++++++++++++++ pkg/{socks5 => resolver}/resolver.go | 2 +- pkg/{socks5 => resolver}/resolver_test.go | 2 +- pkg/socks5/auth.go | 17 +- pkg/socks5/request.go | 2 +- pkg/socks5/request_test.go | 3 +- pkg/socks5/socks5.go | 18 +- pkg/socks5/socks5_test.go | 32 +-- 14 files changed, 348 insertions(+), 54 deletions(-) rename pkg/{socks5 => credential}/credentials.go (87%) rename pkg/{socks5 => credential}/credentials_test.go (82%) create mode 100644 pkg/httpproxy/httpproxy.go rename pkg/{socks5 => resolver}/resolver.go (94%) rename pkg/{socks5 => resolver}/resolver_test.go (95%) diff --git a/Dockerfile b/Dockerfile index f68266a..11a8f51 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,5 +2,6 @@ FROM busybox:1.36.1-glibc COPY nanoproxy /usr/bin/nanoproxy EXPOSE 1080 +EXPOSE 8080 ENTRYPOINT ["nanoproxy"] \ No newline at end of file diff --git a/Dockerfile-tor b/Dockerfile-tor index 109f878..72e9512 100644 --- a/Dockerfile-tor +++ b/Dockerfile-tor @@ -14,5 +14,6 @@ RUN mkdir -p /etc/tor && \ RUN mkdir -p /var/lib/tor EXPOSE 1080 +EXPOSE 8080 ENTRYPOINT ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"] \ No newline at end of file diff --git a/nanoproxy.go b/nanoproxy.go index 34a53d7..7dea083 100644 --- a/nanoproxy.go +++ b/nanoproxy.go @@ -6,8 +6,13 @@ import ( "github.com/caarlos0/env/v10" "github.com/rs/zerolog" "github.com/ryanbekhen/nanoproxy/pkg/config" + "github.com/ryanbekhen/nanoproxy/pkg/credential" + "github.com/ryanbekhen/nanoproxy/pkg/httpproxy" + "github.com/ryanbekhen/nanoproxy/pkg/resolver" "github.com/ryanbekhen/nanoproxy/pkg/socks5" "github.com/ryanbekhen/nanoproxy/pkg/tor" + "net" + "net/http" "os" "strings" "time" @@ -28,28 +33,40 @@ func main() { time.Local = loc } - socks5Config := socks5.Config{ - Logger: &logger, - DestConnTimeout: cfg.DestTimeout, - ClientConnTimeout: cfg.ClientTimeout, - Resolver: &socks5.DNSResolver{}, - } - - credentials := socks5.StaticCredentialStore{} + credentials := credential.StaticCredentialStore{} for _, cred := range cfg.Credentials { credArr := strings.Split(cred, ":") if len(credArr) != 2 { logger.Fatal().Msgf("Invalid credential: %s", cred) } + credentials[credArr[0]] = credArr[1] } - if len(credentials) > 0 { - socks5Config.Credentials = credentials + + dnsResolver := &resolver.DNSResolver{} + + httpConfig := httpproxy.Config{ + Credentials: credentials, + Logger: &logger, + DestConnTimeout: cfg.DestTimeout, + ClientConnTimeout: cfg.ClientTimeout, + Dial: net.Dial, + Resolver: dnsResolver, + } + + httpServer := httpproxy.New(&httpConfig) + + socks5Config := socks5.Config{ + Logger: &logger, + DestConnTimeout: cfg.DestTimeout, + ClientConnTimeout: cfg.ClientTimeout, + Resolver: dnsResolver, } if cfg.TorEnabled { torDialer := &tor.DefaultDialer{} socks5Config.Dial = torDialer.Dial + httpConfig.Dial = torDialer.Dial logger.Info().Msg("Tor mode enabled") torController := tor.NewTorController(torDialer) @@ -62,10 +79,28 @@ func main() { }() } + if len(credentials) > 0 { + authenticator := &socks5.UserPassAuthenticator{ + Credentials: credentials, + } + socks5Config.Authentication = append(socks5Config.Authentication, authenticator) + } + sock5Server := socks5.New(&socks5Config) - logger.Info().Msgf("Starting socks5 server on %s://%s", cfg.Network, cfg.ADDR) - if err := sock5Server.ListenAndServe(cfg.Network, cfg.ADDR); err != nil { - logger.Fatal().Msg(err.Error()) - } + go func() { + logger.Info().Msgf("Starting HTTP proxy server on %s://%s", cfg.Network, cfg.ADDRHttp) + if err := http.ListenAndServe(cfg.ADDRHttp, httpServer); err != nil { + logger.Fatal().Msg(err.Error()) + } + }() + + go func() { + logger.Info().Msgf("Starting socks5 server on %s://%s", cfg.Network, cfg.ADDR) + if err := sock5Server.ListenAndServe(cfg.Network, cfg.ADDR); err != nil { + logger.Fatal().Msg(err.Error()) + } + }() + + select {} } diff --git a/pkg/config/config.go b/pkg/config/config.go index f0a6dc3..d7239a4 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -6,6 +6,7 @@ type Config struct { Timezone string `env:"TZ" envDefault:"Local"` Network string `env:"NETWORK" envDefault:"tcp"` ADDR string `env:"ADDR" envDefault:":1080"` + ADDRHttp string `env:"ADDR_HTTP" envDefault:":8080"` Credentials []string `env:"CREDENTIALS" envSeparator:","` ClientTimeout time.Duration `env:"CLIENT_TIMEOUT" envDefault:"15s"` DestTimeout time.Duration `env:"DEST_TIMEOUT" envDefault:"15s"` diff --git a/pkg/socks5/credentials.go b/pkg/credential/credentials.go similarity index 87% rename from pkg/socks5/credentials.go rename to pkg/credential/credentials.go index e222e3b..e6bccea 100644 --- a/pkg/socks5/credentials.go +++ b/pkg/credential/credentials.go @@ -1,10 +1,10 @@ -package socks5 +package credential import ( "golang.org/x/crypto/bcrypt" ) -type CredentialStore interface { +type Store interface { Valid(user, password string) bool } diff --git a/pkg/socks5/credentials_test.go b/pkg/credential/credentials_test.go similarity index 82% rename from pkg/socks5/credentials_test.go rename to pkg/credential/credentials_test.go index a8490a2..b65deea 100644 --- a/pkg/socks5/credentials_test.go +++ b/pkg/credential/credentials_test.go @@ -1,4 +1,4 @@ -package socks5 +package credential import ( "github.com/stretchr/testify/assert" @@ -6,8 +6,7 @@ import ( ) func Test_CredentialStore_Valid(t *testing.T) { - var s CredentialStore - s = StaticCredentialStore{ + s := StaticCredentialStore{ "foo": "$2y$05$Xr4Vj6wbsCuf70.Fif2guuX8Ez97GB0VysyCTRL2EMkIikCpY/ugi", } assert.True(t, s.Valid("foo", "bar")) diff --git a/pkg/httpproxy/httpproxy.go b/pkg/httpproxy/httpproxy.go new file mode 100644 index 0000000..e59a330 --- /dev/null +++ b/pkg/httpproxy/httpproxy.go @@ -0,0 +1,251 @@ +package httpproxy + +import ( + "encoding/base64" + "fmt" + "github.com/rs/zerolog" + "github.com/ryanbekhen/nanoproxy/pkg/credential" + "github.com/ryanbekhen/nanoproxy/pkg/resolver" + "io" + "net" + "net/http" + "os" + "strings" + "time" +) + +var hopHeaders = []string{ + "Connection", + "Proxy-Connection", + "Keep-Alive", + "Proxy-Authenticate", + "Proxy-Authorization", + "Te", + "Trailers", + "Transfer-Encoding", + "Upgrade", +} + +type Config struct { + Credentials credential.Store + Logger *zerolog.Logger + DestConnTimeout time.Duration + ClientConnTimeout time.Duration + Dial func(network, addr string) (net.Conn, error) + Resolver resolver.Resolver +} + +type Server struct { + config *Config +} + +func New(conf *Config) *Server { + if conf.Logger == nil { + logger := zerolog.New(zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: time.RFC3339}).With().Timestamp().Logger() + conf.Logger = &logger + } + + if conf.Resolver == nil { + conf.Resolver = &resolver.DNSResolver{} + } + + if conf.DestConnTimeout == 0 { + conf.DestConnTimeout = 5 * time.Second + } + + if conf.ClientConnTimeout == 0 { + conf.ClientConnTimeout = 5 * time.Second + } + + server := &Server{ + config: conf, + } + + return server +} + +func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodConnect { + s.handleConnect(w, r) + } else { + s.handleHTTP(w, r) + } +} + +func (s *Server) authenticateRequest(r *http.Request) bool { + if s.config.Credentials == nil { + return true + } + + authHeader := r.Header.Get("Proxy-Authorization") + if authHeader == "" { + return false + } + + if strings.HasPrefix(authHeader, "Basic ") { + encodedCreds := strings.TrimPrefix(authHeader, "Basic ") + decoded, err := base64.StdEncoding.DecodeString(encodedCreds) + if err != nil { + return false + } + + parts := strings.SplitN(string(decoded), ":", 2) + if len(parts) != 2 { + return false + } + + username, password := parts[0], parts[1] + return s.config.Credentials.Valid(username, password) + } + + return false +} + +func (s *Server) handleConnect(w http.ResponseWriter, r *http.Request) { + if !s.authenticateRequest(r) { + s.config.Logger.Error(). + Str("client_addr", r.RemoteAddr). + Msg("Unauthorized CONNECT request") + w.Header().Set("Proxy-Authenticate", "Basic realm=\"Restricted area\"") + http.Error(w, "Proxy authentication required or unauthorized", http.StatusProxyAuthRequired) + return + } + startTime := time.Now() + serverConn, err := s.config.Dial("tcp", r.Host) + latency := time.Since(startTime).Milliseconds() + if err != nil { + s.config.Logger.Error(). + Str("client_addr", r.RemoteAddr). + Str("dest_addr", r.Host). + Str("latency", fmt.Sprintf("%dms", latency)). + Msg("CONNECT failed") + http.Error(w, err.Error(), http.StatusServiceUnavailable) + return + } + defer serverConn.Close() + + clientConn, _, err := w.(http.Hijacker).Hijack() + if err != nil { + s.config.Logger.Error(). + Str("client_addr", r.RemoteAddr). + Msg("Failed to hijack client connection") + http.Error(w, err.Error(), http.StatusServiceUnavailable) + return + } + defer clientConn.Close() + + _, _ = clientConn.Write([]byte("HTTP/1.1 200 Connection Established\r\n\r\n")) + + s.config.Logger.Info(). + Str("client_addr", r.RemoteAddr). + Str("dest_addr", r.Host). + Str("latency", fmt.Sprintf("%dms", latency)). + Msg("CONNECT request completed") + + go func() { + _, _ = io.Copy(serverConn, clientConn) + }() + _, _ = io.Copy(clientConn, serverConn) +} + +func (s *Server) handleHTTP(w http.ResponseWriter, r *http.Request) { + if !s.authenticateRequest(r) { + s.config.Logger.Error(). + Str("client_addr", r.RemoteAddr). + Msg("Unauthorized HTTP request") + w.Header().Set("Proxy-Authenticate", "Basic realm=\"Restricted area\"") + http.Error(w, "Proxy authentication required or unauthorized", http.StatusProxyAuthRequired) + return + } + startTime := time.Now() + clientIP := r.RemoteAddr + + if !strings.HasPrefix(r.URL.Scheme, "http") { + s.config.Logger.Error(). + Str("client_addr", clientIP). + Str("dest_addr", r.URL.String()). + Msg("Invalid URL scheme") + http.Error(w, "Invalid URL scheme", http.StatusBadRequest) + return + } + + destAddr := r.URL.Host + if s.config.Resolver != nil { + ip, err := s.config.Resolver.Resolve(destAddr) + if err != nil { + latency := time.Since(startTime).Milliseconds() + s.config.Logger.Error(). + Str("client_addr", clientIP). + Str("dest_addr", r.URL.String()). + Str("latency", fmt.Sprintf("%dms", latency)). + Msg("Failed to resolve destination") + http.Error(w, err.Error(), http.StatusBadGateway) + return + } + destAddr = net.JoinHostPort(ip.String(), r.URL.Port()) + } + + proxyReq, err := http.NewRequest(r.Method, r.URL.String(), r.Body) + if err != nil { + latency := time.Since(startTime).Milliseconds() + s.config.Logger.Error(). + Str("client_addr", clientIP). + Str("dest_addr", r.URL.String()). + Str("latency", fmt.Sprintf("%dms", latency)). + Msg("Failed to create request") + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + for key, values := range r.Header { + if !isHopHeader(key) { + for _, value := range values { + proxyReq.Header.Add(key, value) + } + } + } + + client := &http.Client{ + Timeout: s.config.ClientConnTimeout, + } + resp, err := client.Do(proxyReq) + latency := time.Since(startTime).Milliseconds() + if err != nil { + s.config.Logger.Error(). + Str("client_addr", clientIP). + Str("dest_addr", r.URL.String()). + Str("latency", fmt.Sprintf("%dms", latency)). + Msg("Failed to send request") + http.Error(w, err.Error(), http.StatusServiceUnavailable) + return + } + defer resp.Body.Close() + + s.config.Logger.Info(). + Str("client_addr", clientIP). + Str("dest_addr", r.URL.String()). + Str("latency", fmt.Sprintf("%dms", latency)). + Msg("HTTP request completed") + + for key, values := range resp.Header { + if !isHopHeader(key) { + for _, value := range values { + w.Header().Add(key, value) + } + } + } + + w.WriteHeader(resp.StatusCode) + _, _ = io.Copy(w, resp.Body) +} + +func isHopHeader(header string) bool { + header = strings.ToLower(header) + for _, hopHeader := range hopHeaders { + if header == strings.ToLower(hopHeader) { + return true + } + } + + return false +} diff --git a/pkg/socks5/resolver.go b/pkg/resolver/resolver.go similarity index 94% rename from pkg/socks5/resolver.go rename to pkg/resolver/resolver.go index d9acd9d..17071fe 100644 --- a/pkg/socks5/resolver.go +++ b/pkg/resolver/resolver.go @@ -1,4 +1,4 @@ -package socks5 +package resolver import ( "net" diff --git a/pkg/socks5/resolver_test.go b/pkg/resolver/resolver_test.go similarity index 95% rename from pkg/socks5/resolver_test.go rename to pkg/resolver/resolver_test.go index 3156825..939ab3a 100644 --- a/pkg/socks5/resolver_test.go +++ b/pkg/resolver/resolver_test.go @@ -1,4 +1,4 @@ -package socks5 +package resolver import ( "github.com/stretchr/testify/assert" diff --git a/pkg/socks5/auth.go b/pkg/socks5/auth.go index 70eca58..826ba27 100644 --- a/pkg/socks5/auth.go +++ b/pkg/socks5/auth.go @@ -2,6 +2,7 @@ package socks5 import ( "fmt" + "github.com/ryanbekhen/nanoproxy/pkg/credential" "io" ) @@ -34,8 +35,8 @@ var ( ErrAuthFailure = fmt.Errorf("authentication failure") ) -// AuthContext encapsulates authentication state provided during negotiation -type AuthContext struct { +// Context encapsulates authentication state provided during negotiation +type Context struct { // Method is the provided auth method Method AuthType // Payload provided during negotiation. @@ -46,7 +47,7 @@ type AuthContext struct { // Authenticator is the interface implemented by types that can handle authentication type Authenticator interface { - Authenticate(reader io.Reader, writer io.Writer) (*AuthContext, error) + Authenticate(reader io.Reader, writer io.Writer) (*Context, error) GetCode() AuthType } @@ -59,14 +60,14 @@ func (a *NoAuthAuthenticator) GetCode() AuthType { } // Authenticate handles the authentication process -func (a *NoAuthAuthenticator) Authenticate(_ io.Reader, writer io.Writer) (*AuthContext, error) { +func (a *NoAuthAuthenticator) Authenticate(_ io.Reader, writer io.Writer) (*Context, error) { _, err := writer.Write([]byte{Version, uint8(NoAuth)}) - return &AuthContext{NoAuth, nil}, err + return &Context{NoAuth, nil}, err } // UserPassAuthenticator is used to handle username/password-based authentication type UserPassAuthenticator struct { - Credentials CredentialStore + Credentials credential.Store } // GetCode returns the code of the authenticator @@ -75,7 +76,7 @@ func (a *UserPassAuthenticator) GetCode() AuthType { } // Authenticate handles the authentication process -func (a *UserPassAuthenticator) Authenticate(reader io.Reader, writer io.Writer) (*AuthContext, error) { +func (a *UserPassAuthenticator) Authenticate(reader io.Reader, writer io.Writer) (*Context, error) { if _, err := writer.Write([]byte{Version, uint8(UserPassAuth)}); err != nil { return nil, err } @@ -122,7 +123,7 @@ func (a *UserPassAuthenticator) Authenticate(reader io.Reader, writer io.Writer) return nil, fmt.Errorf("invalid credentials") } - return &AuthContext{UserPassAuth, map[string]string{"Username": string(user)}}, nil + return &Context{UserPassAuth, map[string]string{"Username": string(user)}}, nil } func readMethods(bufConn io.Reader) ([]byte, error) { diff --git a/pkg/socks5/request.go b/pkg/socks5/request.go index 5439f14..60ab1c5 100644 --- a/pkg/socks5/request.go +++ b/pkg/socks5/request.go @@ -45,7 +45,7 @@ func (a *AddrSpec) Address() string { type Request struct { Version uint8 Command Command - AuthContext *AuthContext + AuthContext *Context RemoteAddr *AddrSpec DestAddr *AddrSpec realAddr *AddrSpec diff --git a/pkg/socks5/request_test.go b/pkg/socks5/request_test.go index f53549a..24e74a6 100644 --- a/pkg/socks5/request_test.go +++ b/pkg/socks5/request_test.go @@ -3,6 +3,7 @@ package socks5 import ( "bytes" "encoding/binary" + "github.com/ryanbekhen/nanoproxy/pkg/resolver" "github.com/stretchr/testify/assert" "io" "net" @@ -89,7 +90,7 @@ func Test_NewRequest(t *testing.T) { lAddr := l.Addr().(*net.TCPAddr) s := &Server{ config: &Config{ - Resolver: &DNSResolver{}, + Resolver: &resolver.DNSResolver{}, }, } diff --git a/pkg/socks5/socks5.go b/pkg/socks5/socks5.go index 064f850..630c44e 100644 --- a/pkg/socks5/socks5.go +++ b/pkg/socks5/socks5.go @@ -5,6 +5,8 @@ import ( "errors" "fmt" "github.com/rs/zerolog" + "github.com/ryanbekhen/nanoproxy/pkg/credential" + "github.com/ryanbekhen/nanoproxy/pkg/resolver" "net" "os" "strings" @@ -13,13 +15,13 @@ import ( type Config struct { Authentication []Authenticator - Credentials CredentialStore + Credentials credential.Store Logger *zerolog.Logger DestConnTimeout time.Duration ClientConnTimeout time.Duration Dial func(network, addr string) (net.Conn, error) AfterRequest func(req *Request, conn net.Conn) - Resolver Resolver + Resolver resolver.Resolver Rewriter AddressRewriter } @@ -44,7 +46,7 @@ func New(conf *Config) *Server { } if conf.Resolver == nil { - conf.Resolver = &DNSResolver{} + conf.Resolver = &resolver.DNSResolver{} } if conf.DestConnTimeout == 0 { @@ -129,7 +131,7 @@ func (s *Server) handleConnection(conn net.Conn) { // Authenticate authContext, err := s.authenticate(conn, connectionBuffer) if err != nil { - s.config.Logger.Err(err).Msg("failed to authenticate") + s.config.Logger.Err(err).Msg("SOCKS5 authentication failed") return } @@ -159,7 +161,7 @@ func (s *Server) handleConnection(conn net.Conn) { Str("client_addr", conn.RemoteAddr().String()). Str("dest_addr", request.DestAddr.String()). Str("latency", request.Latency.String()). - Msg("request completed") + Msg("SOCKS5 request completed") } if s.config.AfterRequest != nil { @@ -167,7 +169,7 @@ func (s *Server) handleConnection(conn net.Conn) { } } -func (s *Server) authenticate(conn net.Conn, bufConn *bufio.Reader) (*AuthContext, error) { +func (s *Server) authenticate(conn net.Conn, bufConn *bufio.Reader) (*Context, error) { // Get the methods methods, err := readMethods(bufConn) if err != nil { @@ -176,8 +178,8 @@ func (s *Server) authenticate(conn net.Conn, bufConn *bufio.Reader) (*AuthContex // Select a usable method for _, method := range methods { - if auth, ok := s.authentication[AuthType(method)]; ok { - return auth.Authenticate(bufConn, conn) + if a, ok := s.authentication[AuthType(method)]; ok { + return a.Authenticate(bufConn, conn) } } diff --git a/pkg/socks5/socks5_test.go b/pkg/socks5/socks5_test.go index 6c12d7b..c887483 100644 --- a/pkg/socks5/socks5_test.go +++ b/pkg/socks5/socks5_test.go @@ -5,6 +5,8 @@ import ( "encoding/binary" "errors" "fmt" + "github.com/ryanbekhen/nanoproxy/pkg/credential" + "github.com/ryanbekhen/nanoproxy/pkg/resolver" "github.com/stretchr/testify/assert" "io" "net" @@ -16,7 +18,7 @@ func TestNew(t *testing.T) { conf := &Config{ Authentication: []Authenticator{&NoAuthAuthenticator{}}, Logger: nil, - Resolver: &DNSResolver{}, + Resolver: &resolver.DNSResolver{}, } server := New(conf) @@ -46,12 +48,12 @@ func TestListenAndServe(t *testing.T) { }() lAddr := l.Addr().(*net.TCPAddr) - credentials := StaticCredentialStore{ + credentials := credential.StaticCredentialStore{ "foo": "$2y$05$Xr4Vj6wbsCuf70.Fif2guuX8Ez97GB0VysyCTRL2EMkIikCpY/ugi", // foo:bar } auth := &UserPassAuthenticator{Credentials: credentials} conf := &Config{ - Authentication: []Authenticator{auth}, + Authentication: []auth.Authenticator{auth}, } server := New(conf) assert.NotNil(t, server) @@ -67,7 +69,7 @@ func TestListenAndServe(t *testing.T) { req := bytes.NewBuffer(nil) req.Write([]byte{5}) - req.Write([]byte{2, NoAuth.Uint8(), UserPassAuth.Uint8()}) + req.Write([]byte{2, auth.NoAuth.Uint8(), auth.UserPassAuth.Uint8()}) req.Write([]byte{1, 3, 'f', 'o', 'o', 3, 'b', 'a', 'r'}) req.Write([]byte{5, 1, 0, 1, 127, 0, 0, 1}) @@ -80,8 +82,8 @@ func TestListenAndServe(t *testing.T) { conn.Write(req.Bytes()) expected := []byte{ - Version, UserPassAuth.Uint8(), - 1, AuthSuccess.Uint8(), + Version, auth.UserPassAuth.Uint8(), + 1, auth.AuthSuccess.Uint8(), 5, 0, 0, @@ -110,12 +112,12 @@ func TestListenAndServe_InvalidCredentials(t *testing.T) { lAddr := l.Addr().(*net.TCPAddr) - credentials := StaticCredentialStore{ + credentials := credential.StaticCredentialStore{ "foo": "bar", } auth := &UserPassAuthenticator{Credentials: credentials} conf := &Config{ - Authentication: []Authenticator{auth}, + Authentication: []auth.Authenticator{auth}, } server := New(conf) assert.NotNil(t, server) @@ -131,7 +133,7 @@ func TestListenAndServe_InvalidCredentials(t *testing.T) { req := bytes.NewBuffer(nil) req.Write([]byte{5}) - req.Write([]byte{2, NoAuth.Uint8(), UserPassAuth.Uint8()}) + req.Write([]byte{2, auth.NoAuth.Uint8(), auth.UserPassAuth.Uint8()}) req.Write([]byte{1, 3, 'b', 'a', 'd', 3, 'p', 'a', 's', 's'}) // invalid username and password req.Write([]byte{5, 1, 0, 1, 127, 0, 0, 1}) @@ -144,8 +146,8 @@ func TestListenAndServe_InvalidCredentials(t *testing.T) { conn.Write(req.Bytes()) expected := []byte{ - Version, UserPassAuth.Uint8(), - 1, AuthFailure.Uint8(), // expect authentication failure + Version, auth.UserPassAuth.Uint8(), + 1, auth.AuthFailure.Uint8(), // expect authentication failure } out := make([]byte, len(expected)) @@ -162,13 +164,13 @@ func TestListenAndServe_InvalidAuthType(t *testing.T) { assert.NoError(t, err) lAddr := l.Addr().(*net.TCPAddr) - credentials := StaticCredentialStore{ + credentials := credential.StaticCredentialStore{ "foo": "bar", } auth := &UserPassAuthenticator{Credentials: credentials} conf := &Config{ - Authentication: []Authenticator{auth}, + Authentication: []auth.Authenticator{auth}, } server := New(conf) @@ -188,7 +190,7 @@ func TestListenAndServe_InvalidAuthType(t *testing.T) { req.Write([]byte{5}) // invalid auth type - req.Write([]byte{2, NoAuth.Uint8(), 0}) + req.Write([]byte{2, auth.NoAuth.Uint8(), 0}) req.Write([]byte{1, 3, 'f', 'o', 'o', 3, 'b', 'a', 'r'}) req.Write([]byte{5, 1, 0, 1, 127, 0, 0, 1}) @@ -201,7 +203,7 @@ func TestListenAndServe_InvalidAuthType(t *testing.T) { conn.Write(req.Bytes()) expected := []byte{ - Version, NoAcceptable.Uint8(), + Version, auth.NoAcceptable.Uint8(), } out := make([]byte, len(expected)) From 6c9fbd3fb84d70434212bda101476dd610d52b22 Mon Sep 17 00:00:00 2001 From: Achmad Irianto Eka Putra Date: Thu, 12 Dec 2024 06:21:17 +0700 Subject: [PATCH 2/7] feat(README): add HTTP proxy support documentation Updated the README to include details about HTTP proxy support in NanoProxy. Added sections on data flow, configuration changes, authentication, and usage examples for HTTP proxying. Enhanced clarity in differentiating between SOCKS5 and HTTP functionalities. --- README.md | 89 +++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 74 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index f9cc2c7..c2f819c 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,9 @@ Note: This code includes modifications from the original go-socks5 project (http Modifications have been made as part of maintenance for NanoProxy. This version is licensed under the MIT license. -NanoProxy is a lightweight SOCKS5 proxy server written in Go. It is designed to be simple, minimalistic, and easy to -use. +NanoProxy is a lightweight proxy server written in Go. It supports both **SOCKS5** and **HTTP Proxy** protocols, making +it flexible for proxying various types of network traffic. NanoProxy is designed to be simple, minimalistic, and easy to +use. It can be run as a standalone service or as a Docker container. > ⚠️ **Notice:** NanoProxy is currently in pre-production stage. While it provides essential proxying capabilities, > please be aware that it is still under active development. Full backward compatibility is not guaranteed until @@ -17,10 +18,10 @@ use. ## Data Flow Through Proxy -NanoProxy acts as a proxy server that forwards network traffic between the user and the destination server. -When a user makes a request, the request is sent to the proxy server. The proxy server then forwards the request to -the destination server. The destination server processes the request and responds back to the proxy server, which then -sends the response back to the user. This allows the proxy server to intercept and manage network traffic effectively. +NanoProxy acts as a proxy Server that forwards network traffic between the user and the destination Server. +When a user makes a request, the request is sent to the proxy Server. The proxy Server then forwards the request to +the destination Server. The destination Server processes the request and responds back to the proxy Server, which then +sends the response back to the user. This allows the proxy Server to intercept and manage network traffic effectively. Here's how the data flows through the proxy: @@ -60,6 +61,27 @@ sequenceDiagram NanoProxy ->> User: Respond ``` +## Data Flow Through Proxy with HTTP Support + +NanoProxy supports HTTP proxying by handling HTTP requests and forwarding them to the destination server. Depending on +the request method (e.g., GET, POST, CONNECT), NanoProxy processes and forwards the request accordingly. + +Here's how the data flows through the proxy when using HTTP support: + +```mermaid +sequenceDiagram + participant User + participant NanoProxy + participant DestinationServer + User ->> NanoProxy: HTTP Request + NanoProxy ->> DestinationServer: Forward HTTP Request + DestinationServer ->> NanoProxy: Process & Respond + NanoProxy ->> User: Deliver Response +``` + +This process allows NanoProxy to act as an intermediary between the client and the destination server for HTTP traffic, +ensuring flexibility and traffic management. + ### Features of NanoProxy with Tor: - **Enhanced Anonymity**: Traffic is routed through multiple Tor nodes, making it difficult to trace the origin of the @@ -68,7 +90,7 @@ sequenceDiagram - **Secure Data Transmission**: Encryption between Tor nodes protects data from snooping. This distinct data flow employing the Tor network ensures that users enjoy increased privacy without compromising on the -flexible functionality of the proxy server. +flexible functionality of the proxy Server. ### Impact of Using NanoProxy with Tor: @@ -87,8 +109,9 @@ especially if anonymity is prioritized over connection speed or stability. NanoProxy provides the following features: -- [x] **SOCKS5 proxy server.** NanoProxy is a SOCKS5 proxy server that can be used to proxy network traffic for various +- [x] **SOCKS5 proxy Server.** NanoProxy is a SOCKS5 proxy Server that can be used to proxy network traffic for various applications. +- [x] **HTTP proxy Server.** NanoProxy can now act as an HTTP proxy Server for forwarding HTTP requests. - [x] **TOR support.** NanoProxy can be run with Tor support to provide anonymized network traffic (Docker only). - [x] **IP Rotation with Tor.** NanoProxy allows for IP rotation using the Tor network, providing enhanced anonymity and privacy by periodically changing exit nodes. @@ -201,6 +224,7 @@ desired values: ```text ADDR=:1080 +ADDR_HTTP=:8080 NETWORK=tcp TZ=Asia/Jakarta CLIENT_TIMEOUT=10s @@ -229,17 +253,24 @@ The following table lists the available configuration options: | Name | Description | Default Value | |-----------------------|-----------------------------------------------------------------|---------------| | ADDR | The address to listen on. | `:1080` | +| ADDR_HTTP | The address to listen on for HTTP requests. | `:8080` | | NETWORK | The network to listen on. (tcp, tcp4, tcp6) | `tcp` | | TZ | The timezone to use. | `Local` | -| CLIENT_TIMEOUT | The timeout for connecting to the destination server. | `10s` | +| CLIENT_TIMEOUT | The timeout for connecting to the destination Server. | `10s` | | DNS_TIMEOUT | The timeout for DNS resolution. | `10s` | | CREDENTIALS | The credentials to use for authentication. | `""` | | TOR_ENABLED | Enable Tor support. (works only on Docker) | `false` | -| TOR_IDENTITY_INTERVAL | The interval to change the Tor identity. (works only on Docker) | `10m` |ß +| TOR_IDENTITY_INTERVAL | The interval to change the Tor identity. (works only on Docker) | `10m` | + +- **ADDR_HTTP**: By default, NanoProxy listens for HTTP proxy traffic on `:8080`. You can set this address to any host: + port combination for custom setups. +- **CREDENTIALS**: When enabled, both SOCKS5 and HTTP Proxy requests are authenticated using the credentials provided in + this field. This supports `username:password` pairs. ## Logging -NanoProxy logs all requests and responses to the standard output. You can use the `journalctl` command to view the logs: +NanoProxy logs all requests and responses to standard output, including SOCKS5 and HTTP Proxy traffic. To view logs for +HTTP Proxy requests, use the `journalctl` command: ```shell journalctl -u nanoproxy @@ -250,15 +281,43 @@ journalctl -u nanoproxy To test the proxy using cURL, you can use the `-x` flag followed by the proxy URL. For example, to fetch the Google homepage using the proxy running on `localhost:8080`, use the following command: +### SOCKS5 Proxy + ```shell curl -x socks5://localhost:1080 https://google.com ``` -Replace localhost:8080 with the actual address and port where your NanoProxy instance is running. This command instructs -cURL to use the specified proxy for the request, allowing you to see the request and response through the proxy server. +### HTTP Proxy + +```shell +curl -x localhost:8080 https://google.com +``` + +If credentials are enabled for HTTP Proxy, use the `-U` flag to supply the username and password: + +```shell +curl -x http://localhost:8080 -U username:password https://example.com +``` + +In both cases, replace `localhost:8080` with the actual address and port where your NanoProxy instance is running. + +## Authentication for HTTP Proxy + +If authentication is enabled (via the `CREDENTIALS` configuration), the HTTP Proxy requires clients to include the +`Proxy-Authorization` header in their requests. The header must use the following format: + +```http +Proxy-Authorization: Basic +``` + +For example, to use the HTTP Proxy with curl and provide authentication, run: + +```shell +curl -x http://localhost:8080 -U username:password https://example.com +``` -Remember that you can adjust the proxy address and port as needed based on your setup. This is a convenient way to -verify that NanoProxy is correctly intercepting and forwarding the traffic. +If authentication fails or is not provided, the proxy will return `407 Proxy Authentication Required` along with the +appropriate `Proxy-Authenticate` header. ## Contributions From 9a3263df2d46b15bd5b1040e86c8662dfc62c963 Mon Sep 17 00:00:00 2001 From: Achmad Irianto Eka Putra Date: Thu, 12 Dec 2024 07:26:36 +0700 Subject: [PATCH 3/7] fix:(test): unit testing --- .testcoverage.yml | 3 +- go.mod | 1 + go.sum | 2 + pkg/httpproxy/httpproxy_test.go | 187 ++++++++++++++++++++++++++++++++ pkg/socks5/socks5_test.go | 22 ++-- 5 files changed, 203 insertions(+), 12 deletions(-) create mode 100644 pkg/httpproxy/httpproxy_test.go diff --git a/.testcoverage.yml b/.testcoverage.yml index 8ca2812..a4f9ad6 100644 --- a/.testcoverage.yml +++ b/.testcoverage.yml @@ -7,4 +7,5 @@ threshold: exclude: paths: - \.pb\.go$ # excludes all protobuf generated files - - nanoproxy\.go$ # excludes the main package \ No newline at end of file + - nanoproxy\.go$ # excludes the main package + - pkg/config/.*\.go$ # excludes all files in the pkg/config package \ No newline at end of file diff --git a/go.mod b/go.mod index ee2e9bd..26e31aa 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/objx v0.5.2 // indirect golang.org/x/sys v0.28.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 27f687c..2430ac4 100644 --- a/go.sum +++ b/go.sum @@ -16,6 +16,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY= diff --git a/pkg/httpproxy/httpproxy_test.go b/pkg/httpproxy/httpproxy_test.go new file mode 100644 index 0000000..f40cae0 --- /dev/null +++ b/pkg/httpproxy/httpproxy_test.go @@ -0,0 +1,187 @@ +package httpproxy + +import ( + "encoding/base64" + "fmt" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "net" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" +) + +// Mock implementation of credential.Store +type mockCredentialStore struct { + mock.Mock +} + +func (m *mockCredentialStore) Valid(username, password string) bool { + args := m.Called(username, password) + return args.Bool(0) +} + +// Mock resolver implementation for tests +type mockResolver struct{} + +func (m *mockResolver) Resolve(hostname string) (net.IP, error) { + // Handle localhost for testing purposes + if strings.Contains(hostname, "127.0.0.1") { + return net.ParseIP("127.0.0.1"), nil + } + return nil, fmt.Errorf("failed to resolve %s", hostname) +} + +func TestHTTPProxy_ValidCredentials(t *testing.T) { + // Create a mock credential store + mockCredStore := &mockCredentialStore{} + mockCredStore.On("Valid", "testuser", "testpassword").Return(true) + + // Fixed resolver + mockResolver := &mockResolver{} + + // Configure the proxy server + proxy := New(&Config{ + Credentials: mockCredStore, + Resolver: mockResolver, + DestConnTimeout: 5 * time.Second, + ClientConnTimeout: 5 * time.Second, + }) + + // Create target server + targetServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte("Target Server OK")) + })) + defer targetServer.Close() + + // Create a request + req, err := http.NewRequest(http.MethodGet, targetServer.URL, nil) + assert.NoError(t, err) + + // Add valid Proxy-Authorization header + authHeader := base64.StdEncoding.EncodeToString([]byte("testuser:testpassword")) + req.Header.Set("Proxy-Authorization", "Basic "+authHeader) + + rr := httptest.NewRecorder() + + // Call the proxy + proxy.ServeHTTP(rr, req) + + // Assert proper behavior + assert.Equal(t, http.StatusOK, rr.Code) + assert.Equal(t, "Target Server OK", rr.Body.String()) + + mockCredStore.AssertExpectations(t) +} + +func TestHTTPProxy_InvalidCredentials(t *testing.T) { + // Create a mock credential store + mockCredStore := &mockCredentialStore{} + mockCredStore.On("Valid", "invaliduser", "invalidpassword").Return(false) + + // Fixed resolver + mockResolver := &mockResolver{} + + // Configure the proxy server + proxy := New(&Config{ + Credentials: mockCredStore, + Resolver: mockResolver, + DestConnTimeout: 5 * time.Second, + ClientConnTimeout: 5 * time.Second, + }) + + // Create target server + targetServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte("Target Server OK")) + })) + defer targetServer.Close() + + // Create a request + req, err := http.NewRequest(http.MethodGet, targetServer.URL, nil) + assert.NoError(t, err) + + // Add invalid Proxy-Authorization header + authHeader := base64.StdEncoding.EncodeToString([]byte("invaliduser:invalidpassword")) + req.Header.Set("Proxy-Authorization", "Basic "+authHeader) + + rr := httptest.NewRecorder() + + // Call the proxy + proxy.ServeHTTP(rr, req) + + // Assert proxy responds with 407 + assert.Equal(t, http.StatusProxyAuthRequired, rr.Code) + + mockCredStore.AssertExpectations(t) +} + +func TestHTTPProxy_NoCredentialsProvided(t *testing.T) { + // Fixed resolver + mockResolver := &mockResolver{} + + // Configure the proxy server without credentials + proxy := New(&Config{ + Credentials: nil, // No auth required + Resolver: mockResolver, + DestConnTimeout: 5 * time.Second, + ClientConnTimeout: 5 * time.Second, + }) + + // Create target server + targetServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte("Target Server OK")) + })) + defer targetServer.Close() + + // Create a request + req, err := http.NewRequest(http.MethodGet, targetServer.URL, nil) + assert.NoError(t, err) + + // No Proxy-Authorization header added + rr := httptest.NewRecorder() + + // Call the proxy + proxy.ServeHTTP(rr, req) + + // Assert proxy responds with success + assert.Equal(t, http.StatusOK, rr.Code) + assert.Equal(t, "Target Server OK", rr.Body.String()) +} + +func TestHTTPProxy_WithoutAuth_WhenAuthDisabled(t *testing.T) { + // Fixed resolver + mockResolver := &mockResolver{} + + // Configure the proxy server without credentials + proxy := New(&Config{ + Credentials: nil, // No auth required + Resolver: mockResolver, + DestConnTimeout: 5 * time.Second, + ClientConnTimeout: 5 * time.Second, + }) + + // Create target server + targetServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte("No Auth Required")) + })) + defer targetServer.Close() + + // Create a request + req, err := http.NewRequest(http.MethodGet, targetServer.URL, nil) + assert.NoError(t, err) + + rr := httptest.NewRecorder() + + // Call the proxy + proxy.ServeHTTP(rr, req) + + // Assert proxy responds with success + assert.Equal(t, http.StatusOK, rr.Code) + assert.Equal(t, "No Auth Required", rr.Body.String()) +} diff --git a/pkg/socks5/socks5_test.go b/pkg/socks5/socks5_test.go index c887483..fc167bc 100644 --- a/pkg/socks5/socks5_test.go +++ b/pkg/socks5/socks5_test.go @@ -53,7 +53,7 @@ func TestListenAndServe(t *testing.T) { } auth := &UserPassAuthenticator{Credentials: credentials} conf := &Config{ - Authentication: []auth.Authenticator{auth}, + Authentication: []Authenticator{auth}, } server := New(conf) assert.NotNil(t, server) @@ -69,7 +69,7 @@ func TestListenAndServe(t *testing.T) { req := bytes.NewBuffer(nil) req.Write([]byte{5}) - req.Write([]byte{2, auth.NoAuth.Uint8(), auth.UserPassAuth.Uint8()}) + req.Write([]byte{2, NoAuth.Uint8(), UserPassAuth.Uint8()}) req.Write([]byte{1, 3, 'f', 'o', 'o', 3, 'b', 'a', 'r'}) req.Write([]byte{5, 1, 0, 1, 127, 0, 0, 1}) @@ -82,8 +82,8 @@ func TestListenAndServe(t *testing.T) { conn.Write(req.Bytes()) expected := []byte{ - Version, auth.UserPassAuth.Uint8(), - 1, auth.AuthSuccess.Uint8(), + Version, UserPassAuth.Uint8(), + 1, AuthSuccess.Uint8(), 5, 0, 0, @@ -117,7 +117,7 @@ func TestListenAndServe_InvalidCredentials(t *testing.T) { } auth := &UserPassAuthenticator{Credentials: credentials} conf := &Config{ - Authentication: []auth.Authenticator{auth}, + Authentication: []Authenticator{auth}, } server := New(conf) assert.NotNil(t, server) @@ -133,7 +133,7 @@ func TestListenAndServe_InvalidCredentials(t *testing.T) { req := bytes.NewBuffer(nil) req.Write([]byte{5}) - req.Write([]byte{2, auth.NoAuth.Uint8(), auth.UserPassAuth.Uint8()}) + req.Write([]byte{2, NoAuth.Uint8(), UserPassAuth.Uint8()}) req.Write([]byte{1, 3, 'b', 'a', 'd', 3, 'p', 'a', 's', 's'}) // invalid username and password req.Write([]byte{5, 1, 0, 1, 127, 0, 0, 1}) @@ -146,8 +146,8 @@ func TestListenAndServe_InvalidCredentials(t *testing.T) { conn.Write(req.Bytes()) expected := []byte{ - Version, auth.UserPassAuth.Uint8(), - 1, auth.AuthFailure.Uint8(), // expect authentication failure + Version, UserPassAuth.Uint8(), + 1, AuthFailure.Uint8(), // expect authentication failure } out := make([]byte, len(expected)) @@ -170,7 +170,7 @@ func TestListenAndServe_InvalidAuthType(t *testing.T) { auth := &UserPassAuthenticator{Credentials: credentials} conf := &Config{ - Authentication: []auth.Authenticator{auth}, + Authentication: []Authenticator{auth}, } server := New(conf) @@ -190,7 +190,7 @@ func TestListenAndServe_InvalidAuthType(t *testing.T) { req.Write([]byte{5}) // invalid auth type - req.Write([]byte{2, auth.NoAuth.Uint8(), 0}) + req.Write([]byte{2, NoAuth.Uint8(), 0}) req.Write([]byte{1, 3, 'f', 'o', 'o', 3, 'b', 'a', 'r'}) req.Write([]byte{5, 1, 0, 1, 127, 0, 0, 1}) @@ -203,7 +203,7 @@ func TestListenAndServe_InvalidAuthType(t *testing.T) { conn.Write(req.Bytes()) expected := []byte{ - Version, auth.NoAcceptable.Uint8(), + Version, NoAcceptable.Uint8(), } out := make([]byte, len(expected)) From 9fa7a5bdadd6de0fd1a4c224309f9620dba519f3 Mon Sep 17 00:00:00 2001 From: Achmad Irianto Eka Putra Date: Thu, 12 Dec 2024 11:41:27 +0700 Subject: [PATCH 4/7] feat(test)!: add extensive mock and test coverage for proxy/tors Enhanced test coverage with detailed mocks for `httpproxy` and `tor`, ensuring edge cases like invalid URLs, authentication handling, and DNS resolution are fully tested. Adjusted error messages in the proxy for better clarity during failures. Reduced package level test coverage threshold in `.testcoverage.yml`. --- .testcoverage.yml | 2 +- pkg/httpproxy/httpproxy.go | 42 ++-- pkg/httpproxy/httpproxy_test.go | 351 +++++++++++++++++++++----------- pkg/tor/identity_test.go | 97 ++++++--- 4 files changed, 324 insertions(+), 168 deletions(-) diff --git a/.testcoverage.yml b/.testcoverage.yml index a4f9ad6..79fdf86 100644 --- a/.testcoverage.yml +++ b/.testcoverage.yml @@ -2,7 +2,7 @@ profile: cover.out local-prefix: "github.com/ryanbekhen/nanoproxy" threshold: file: 60 - package: 80 + package: 60 total: 80 exclude: paths: diff --git a/pkg/httpproxy/httpproxy.go b/pkg/httpproxy/httpproxy.go index e59a330..b4b09ef 100644 --- a/pkg/httpproxy/httpproxy.go +++ b/pkg/httpproxy/httpproxy.go @@ -119,7 +119,7 @@ func (s *Server) handleConnect(w http.ResponseWriter, r *http.Request) { Str("dest_addr", r.Host). Str("latency", fmt.Sprintf("%dms", latency)). Msg("CONNECT failed") - http.Error(w, err.Error(), http.StatusServiceUnavailable) + http.Error(w, "Service unavailable", http.StatusServiceUnavailable) return } defer serverConn.Close() @@ -129,7 +129,7 @@ func (s *Server) handleConnect(w http.ResponseWriter, r *http.Request) { s.config.Logger.Error(). Str("client_addr", r.RemoteAddr). Msg("Failed to hijack client connection") - http.Error(w, err.Error(), http.StatusServiceUnavailable) + http.Error(w, "Service unavailable", http.StatusServiceUnavailable) return } defer clientConn.Close() @@ -157,6 +157,7 @@ func (s *Server) handleHTTP(w http.ResponseWriter, r *http.Request) { http.Error(w, "Proxy authentication required or unauthorized", http.StatusProxyAuthRequired) return } + startTime := time.Now() clientIP := r.RemoteAddr @@ -178,8 +179,8 @@ func (s *Server) handleHTTP(w http.ResponseWriter, r *http.Request) { Str("client_addr", clientIP). Str("dest_addr", r.URL.String()). Str("latency", fmt.Sprintf("%dms", latency)). - Msg("Failed to resolve destination") - http.Error(w, err.Error(), http.StatusBadGateway) + Msg("Failed to resolve destination - Bad Gateway") + http.Error(w, "Bad gateway: failed to resolve destination", http.StatusBadGateway) return } destAddr = net.JoinHostPort(ip.String(), r.URL.Port()) @@ -192,16 +193,18 @@ func (s *Server) handleHTTP(w http.ResponseWriter, r *http.Request) { Str("client_addr", clientIP). Str("dest_addr", r.URL.String()). Str("latency", fmt.Sprintf("%dms", latency)). - Msg("Failed to create request") - http.Error(w, err.Error(), http.StatusInternalServerError) + Msg("Failed to create request - Internal Server Error") + http.Error(w, "Internal server error while creating request", http.StatusInternalServerError) return } for key, values := range r.Header { - if !isHopHeader(key) { - for _, value := range values { - proxyReq.Header.Add(key, value) - } + if isHopHeader(key) { + continue + } + + for _, value := range values { + proxyReq.Header.Add(key, value) } } @@ -215,8 +218,8 @@ func (s *Server) handleHTTP(w http.ResponseWriter, r *http.Request) { Str("client_addr", clientIP). Str("dest_addr", r.URL.String()). Str("latency", fmt.Sprintf("%dms", latency)). - Msg("Failed to send request") - http.Error(w, err.Error(), http.StatusServiceUnavailable) + Msg("Failed to send request - Bad Gateway") + http.Error(w, "Bad gateway: failed to send request", http.StatusBadGateway) return } defer resp.Body.Close() @@ -225,13 +228,15 @@ func (s *Server) handleHTTP(w http.ResponseWriter, r *http.Request) { Str("client_addr", clientIP). Str("dest_addr", r.URL.String()). Str("latency", fmt.Sprintf("%dms", latency)). - Msg("HTTP request completed") + Msg("HTTP request successfully proxied") + + for _, key := range hopHeaders { + resp.Header.Del(key) + } for key, values := range resp.Header { if !isHopHeader(key) { - for _, value := range values { - w.Header().Add(key, value) - } + w.Header()[key] = values } } @@ -241,11 +246,10 @@ func (s *Server) handleHTTP(w http.ResponseWriter, r *http.Request) { func isHopHeader(header string) bool { header = strings.ToLower(header) - for _, hopHeader := range hopHeaders { - if header == strings.ToLower(hopHeader) { + for _, h := range hopHeaders { + if strings.EqualFold(header, h) { return true } } - return false } diff --git a/pkg/httpproxy/httpproxy_test.go b/pkg/httpproxy/httpproxy_test.go index f40cae0..8574b3e 100644 --- a/pkg/httpproxy/httpproxy_test.go +++ b/pkg/httpproxy/httpproxy_test.go @@ -1,187 +1,296 @@ package httpproxy import ( + "bufio" "encoding/base64" - "fmt" + "encoding/json" + "errors" + "github.com/rs/zerolog" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" + "io" "net" "net/http" "net/http/httptest" - "strings" + "net/url" "testing" "time" ) -// Mock implementation of credential.Store -type mockCredentialStore struct { - mock.Mock -} +type MockCredentialStore struct{} -func (m *mockCredentialStore) Valid(username, password string) bool { - args := m.Called(username, password) - return args.Bool(0) +func (m *MockCredentialStore) Valid(username, password string) bool { + return username == "user" && password == "password" } -// Mock resolver implementation for tests -type mockResolver struct{} +type MockResolver struct{} -func (m *mockResolver) Resolve(hostname string) (net.IP, error) { - // Handle localhost for testing purposes - if strings.Contains(hostname, "127.0.0.1") { +func (m *MockResolver) Resolve(host string) (net.IP, error) { + if host == "validhost.com" { return net.ParseIP("127.0.0.1"), nil } - return nil, fmt.Errorf("failed to resolve %s", hostname) + return nil, errors.New("host not found") } -func TestHTTPProxy_ValidCredentials(t *testing.T) { - // Create a mock credential store - mockCredStore := &mockCredentialStore{} - mockCredStore.On("Valid", "testuser", "testpassword").Return(true) +type MockNetConn struct{} - // Fixed resolver - mockResolver := &mockResolver{} +func (m *MockNetConn) Read(b []byte) (n int, err error) { + return 0, io.EOF +} - // Configure the proxy server - proxy := New(&Config{ - Credentials: mockCredStore, - Resolver: mockResolver, - DestConnTimeout: 5 * time.Second, - ClientConnTimeout: 5 * time.Second, +func (m *MockNetConn) Write(b []byte) (n int, err error) { + return len(b), nil +} + +func (m *MockNetConn) Close() error { + return nil +} + +func (m *MockNetConn) LocalAddr() net.Addr { return nil } +func (m *MockNetConn) RemoteAddr() net.Addr { return nil } +func (m *MockNetConn) SetDeadline(t time.Time) error { return nil } +func (m *MockNetConn) SetReadDeadline(t time.Time) error { return nil } +func (m *MockNetConn) SetWriteDeadline(t time.Time) error { return nil } + +type MockHijacker struct { + *httptest.ResponseRecorder +} + +func (m *MockHijacker) Hijack() (net.Conn, *bufio.ReadWriter, error) { + mockConn := &MockNetConn{} + buf := bufio.NewReadWriter(bufio.NewReader(mockConn), bufio.NewWriter(mockConn)) + return mockConn, buf, nil +} + +func TestServer_ServeHTTP(t *testing.T) { + logger := zerolog.New(io.Discard) + mockCredentials := &MockCredentialStore{} + mockResolver := &MockResolver{} + + server := New(&Config{ + Credentials: mockCredentials, + Logger: &logger, + DestConnTimeout: 2 * time.Second, + ClientConnTimeout: 2 * time.Second, + Dial: func(network, addr string) (net.Conn, error) { + return &MockNetConn{}, nil + }, + Resolver: mockResolver, }) - // Create target server - targetServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - w.Write([]byte("Target Server OK")) - })) - defer targetServer.Close() + t.Run("Handle HTTP - unauthorized request", func(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, "http://example.com", nil) + rr := httptest.NewRecorder() - // Create a request - req, err := http.NewRequest(http.MethodGet, targetServer.URL, nil) - assert.NoError(t, err) + server.ServeHTTP(rr, req) - // Add valid Proxy-Authorization header - authHeader := base64.StdEncoding.EncodeToString([]byte("testuser:testpassword")) - req.Header.Set("Proxy-Authorization", "Basic "+authHeader) + assert.Equal(t, http.StatusProxyAuthRequired, rr.Code) + assert.Contains(t, rr.Body.String(), "Proxy authentication required") + }) - rr := httptest.NewRecorder() + t.Run("Handle HTTP - successful authorization but Dial fails", func(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, "http://example.com", nil) + req.Header.Set("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("user:password"))) + rr := httptest.NewRecorder() - // Call the proxy - proxy.ServeHTTP(rr, req) + server.ServeHTTP(rr, req) - // Assert proper behavior - assert.Equal(t, http.StatusOK, rr.Code) - assert.Equal(t, "Target Server OK", rr.Body.String()) + assert.Equal(t, http.StatusBadGateway, rr.Code) + }) + + t.Run("Handle HTTP - failed to resolve host", func(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, "http://invalidhost.com", nil) + req.Header.Set("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("user:password"))) + rr := httptest.NewRecorder() + + server.ServeHTTP(rr, req) - mockCredStore.AssertExpectations(t) + assert.Equal(t, http.StatusBadGateway, rr.Code) + assert.Contains(t, rr.Body.String(), "Bad gateway: failed to resolve destination") + }) } -func TestHTTPProxy_InvalidCredentials(t *testing.T) { - // Create a mock credential store - mockCredStore := &mockCredentialStore{} - mockCredStore.On("Valid", "invaliduser", "invalidpassword").Return(false) +func TestServer_HandleCONNECT(t *testing.T) { + logger := zerolog.New(io.Discard) + mockCredentials := &MockCredentialStore{} + + server := New(&Config{ + Credentials: mockCredentials, + Logger: &logger, + DestConnTimeout: 2 * time.Second, + ClientConnTimeout: 2 * time.Second, + Dial: func(network, addr string) (net.Conn, error) { + return &MockNetConn{}, nil + }, + }) - // Fixed resolver - mockResolver := &mockResolver{} + t.Run("Handle CONNECT - unauthorized request", func(t *testing.T) { + req := httptest.NewRequest(http.MethodConnect, "http://example.com", nil) + rr := httptest.NewRecorder() - // Configure the proxy server - proxy := New(&Config{ - Credentials: mockCredStore, - Resolver: mockResolver, - DestConnTimeout: 5 * time.Second, - ClientConnTimeout: 5 * time.Second, + server.ServeHTTP(rr, req) + + assert.Equal(t, http.StatusProxyAuthRequired, rr.Code) + assert.Contains(t, rr.Body.String(), "Proxy authentication required") + }) + + t.Run("Handle CONNECT - successful connection", func(t *testing.T) { + req := httptest.NewRequest(http.MethodConnect, "example.com:443", nil) + req.Header.Set("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("user:password"))) + rr := httptest.NewRecorder() + + hj := &MockHijacker{ResponseRecorder: rr} + done := make(chan bool, 1) + + go func() { + defer close(done) + server.ServeHTTP(hj, req) + done <- true + }() + + select { + case <-done: + assert.Equal(t, http.StatusOK, rr.Code) + case <-time.After(5 * time.Second): + t.Fatal("Test timeout after 5 seconds") + } }) +} - // Create target server +func TestProxy_ForwardRequests(t *testing.T) { targetServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Empty(t, r.Header.Get("Connection")) + assert.Empty(t, r.Header.Get("Keep-Alive")) + w.Header().Set("X-Test-Header", "TestValue") w.WriteHeader(http.StatusOK) - w.Write([]byte("Target Server OK")) + w.Write([]byte("Target server response")) })) defer targetServer.Close() - // Create a request - req, err := http.NewRequest(http.MethodGet, targetServer.URL, nil) - assert.NoError(t, err) + logger := zerolog.New(io.Discard) - // Add invalid Proxy-Authorization header - authHeader := base64.StdEncoding.EncodeToString([]byte("invaliduser:invalidpassword")) - req.Header.Set("Proxy-Authorization", "Basic "+authHeader) + server := New(&Config{ + Logger: &logger, + ClientConnTimeout: 2 * time.Second, + }) - rr := httptest.NewRecorder() + proxy := httptest.NewServer(server) + defer proxy.Close() - // Call the proxy - proxy.ServeHTTP(rr, req) + t.Run("Successful forward request", func(t *testing.T) { + client := &http.Client{Timeout: 2 * time.Second} + req, _ := http.NewRequest(http.MethodGet, targetServer.URL, nil) - // Assert proxy responds with 407 - assert.Equal(t, http.StatusProxyAuthRequired, rr.Code) + resp, err := client.Do(req) + assert.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + body, _ := io.ReadAll(resp.Body) + defer resp.Body.Close() - mockCredStore.AssertExpectations(t) + assert.Equal(t, "Target server response", string(body)) + }) } -func TestHTTPProxy_NoCredentialsProvided(t *testing.T) { - // Fixed resolver - mockResolver := &mockResolver{} +func TestServer_HandleHTTP_WithProxyRequest(t *testing.T) { + targetURL := "http://httpbin.org" + logger := zerolog.New(io.Discard) - // Configure the proxy server without credentials proxy := New(&Config{ - Credentials: nil, // No auth required - Resolver: mockResolver, - DestConnTimeout: 5 * time.Second, - ClientConnTimeout: 5 * time.Second, + Credentials: &MockCredentialStore{}, + Logger: &logger, + DestConnTimeout: 2 * time.Second, + ClientConnTimeout: 2 * time.Second, + Dial: func(network, addr string) (net.Conn, error) { + return net.Dial(network, addr) + }, + }) + proxyServer := httptest.NewServer(proxy) + defer proxyServer.Close() + + t.Run("Forward HTTP request successfully", func(t *testing.T) { + clientReq, err := http.NewRequest(http.MethodGet, targetURL+"/anything", nil) + if err != nil { + t.Fatalf("Gagal membuat request: %v", err) + } + + clientReq.Header.Set("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("user:password"))) + clientReq.Header.Set("X-Custom-Header", "TestValue") + clientReq.Header.Set("X-Backend-Response", "Success") + + proxyClient := &http.Client{ + Transport: &http.Transport{ + Proxy: func(req *http.Request) (*url.URL, error) { + return url.Parse(proxyServer.URL) + }, + }, + Timeout: 2 * time.Second, + } + + resp, err := proxyClient.Do(clientReq) + assert.NoError(t, err) + if err != nil { + t.Fatalf("[ERROR] Proxy client mengalami error: %v", err) + } + + assert.Equal(t, http.StatusOK, resp.StatusCode) + body, _ := io.ReadAll(resp.Body) + defer resp.Body.Close() + + var responseJSON map[string]interface{} + err = json.Unmarshal(body, &responseJSON) + assert.NoError(t, err) + + headers := responseJSON["headers"].(map[string]interface{}) + + expectedBackendResponse := "Success" + actualBackendResponse := headers["X-Backend-Response"] + assert.Equal(t, expectedBackendResponse, actualBackendResponse) + + expectedCustomHeader := "TestValue" + actualCustomHeader := headers["X-Custom-Header"] + assert.Equal(t, expectedCustomHeader, actualCustomHeader) }) +} - // Create target server - targetServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - w.Write([]byte("Target Server OK")) - })) - defer targetServer.Close() +func TestServer_HandleHTTP_InvalidURLScheme(t *testing.T) { + logger := zerolog.New(io.Discard) - // Create a request - req, err := http.NewRequest(http.MethodGet, targetServer.URL, nil) - assert.NoError(t, err) + server := New(&Config{ + Logger: &logger, + ClientConnTimeout: 2 * time.Second, + }) - // No Proxy-Authorization header added - rr := httptest.NewRecorder() + t.Run("Invalid URL Scheme", func(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, "ftp://example.com", nil) // Skema tidak valid (ftp) + rr := httptest.NewRecorder() - // Call the proxy - proxy.ServeHTTP(rr, req) + server.ServeHTTP(rr, req) - // Assert proxy responds with success - assert.Equal(t, http.StatusOK, rr.Code) - assert.Equal(t, "Target Server OK", rr.Body.String()) + assert.Equal(t, http.StatusBadRequest, rr.Code) // Memastikan statusnya Bad Request + assert.Contains(t, rr.Body.String(), "Invalid URL scheme") // Memastikan pesan error sesuai + }) } -func TestHTTPProxy_WithoutAuth_WhenAuthDisabled(t *testing.T) { - // Fixed resolver - mockResolver := &mockResolver{} +func TestServer_HandleHTTP_ClientDoError(t *testing.T) { + logger := zerolog.New(io.Discard) - // Configure the proxy server without credentials - proxy := New(&Config{ - Credentials: nil, // No auth required - Resolver: mockResolver, - DestConnTimeout: 5 * time.Second, - ClientConnTimeout: 5 * time.Second, + server := New(&Config{ + Logger: &logger, + ClientConnTimeout: 2 * time.Second, }) - // Create target server - targetServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - w.Write([]byte("No Auth Required")) - })) - defer targetServer.Close() + t.Run("Failed to resolve DNS", func(t *testing.T) { + // Membuat permintaan HTTP Proxy + proxyReq := httptest.NewRequest(http.MethodGet, "http://unreachablehost", nil) + proxyReq.Header.Set("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("user:password"))) - // Create a request - req, err := http.NewRequest(http.MethodGet, targetServer.URL, nil) - assert.NoError(t, err) + rr := httptest.NewRecorder() - rr := httptest.NewRecorder() + server.ServeHTTP(rr, proxyReq) - // Call the proxy - proxy.ServeHTTP(rr, req) + // Kode status harus 502: Bad Gateway karena resolve gagal + assert.Equal(t, http.StatusBadGateway, rr.Code) - // Assert proxy responds with success - assert.Equal(t, http.StatusOK, rr.Code) - assert.Equal(t, "No Auth Required", rr.Body.String()) + // Validasi pesan error + assert.Contains(t, rr.Body.String(), "Bad gateway: failed to resolve destination") + }) } diff --git a/pkg/tor/identity_test.go b/pkg/tor/identity_test.go index 278b3f4..f693f3d 100644 --- a/pkg/tor/identity_test.go +++ b/pkg/tor/identity_test.go @@ -1,45 +1,88 @@ -package tor_test +package tor import ( - "bytes" - "fmt" - "github.com/ryanbekhen/nanoproxy/pkg/tor" + "errors" + "github.com/rs/zerolog" "testing" "time" - - "github.com/rs/zerolog" - "github.com/stretchr/testify/assert" ) +// Mock Requester untuk menggantikan implementasi sebenarnya type MockRequester struct { - shouldFail bool - callCount int + RequestNewTorIdentityFunc func(logger *zerolog.Logger) error } func (m *MockRequester) RequestNewTorIdentity(logger *zerolog.Logger) error { - m.callCount++ - if m.shouldFail { - return fmt.Errorf("simulated failure") - } - return nil + return m.RequestNewTorIdentityFunc(logger) +} + +func TestWaitForTorBootstrap(t *testing.T) { + logger := zerolog.Nop() + timeout := 2 * time.Second + + t.Run("Successful bootstrap", func(t *testing.T) { + mockRequester := &MockRequester{ + RequestNewTorIdentityFunc: func(logger *zerolog.Logger) error { + return nil // selalu sukses + }, + } + + err := WaitForTorBootstrap(&logger, mockRequester, timeout) + if err != nil { + t.Errorf("expected no error, got %v", err) + } + }) + + t.Run("Timeout occurs", func(t *testing.T) { + mockRequester := &MockRequester{ + RequestNewTorIdentityFunc: func(logger *zerolog.Logger) error { + time.Sleep(3 * time.Second) // memicu timeout + return nil + }, + } + + err := WaitForTorBootstrap(&logger, mockRequester, timeout) + if err == nil { + t.Errorf("expected timeout error, got nil") + } + }) + + t.Run("Error in RequestNewTorIdentity", func(t *testing.T) { + mockRequester := &MockRequester{ + RequestNewTorIdentityFunc: func(logger *zerolog.Logger) error { + return errors.New("requester error") + }, + } + + err := WaitForTorBootstrap(&logger, mockRequester, timeout) + if err == nil || err.Error() != "timeout: Tor bootstrap not complete after 2s" { + t.Errorf("expected timeout error due to RequestNewTorIdentity failure, got %v", err) + } + }) } func TestSwitcherIdentity(t *testing.T) { - logger := zerolog.New(zerolog.ConsoleWriter{Out: &bytes.Buffer{}}).With().Logger() - requester := &MockRequester{shouldFail: false} - done := make(chan bool) + logger := zerolog.Nop() + switchInterval := 1 * time.Second + done := make(chan bool, 1) - // Set up a Goroutine to stop the SwitcherIdentity after a short delay - go func() { - time.Sleep(10 * time.Millisecond) - done <- true - }() + t.Run("Switcher stops when done signal is received", func(t *testing.T) { + mockRequester := &MockRequester{ + RequestNewTorIdentityFunc: func(logger *zerolog.Logger) error { + return nil + }, + } - // Call the SwitcherIdentity function with a very short interval - go tor.SwitcherIdentity(&logger, requester, 1*time.Millisecond, done) + go func() { + time.Sleep(2 * time.Second) + done <- true + }() - // Wait for a moment to ensure goroutine have run - time.Sleep(15 * time.Millisecond) + go func() { + SwitcherIdentity(&logger, mockRequester, switchInterval, done) + }() - assert.True(t, requester.callCount > 0, "expected SwitcherIdentity to call RequestNewTorIdentity multiple times") + time.Sleep(3 * time.Second) + // Tidak ada log error karena mockRequester selalu berhasil + }) } From 656d7f68f44dd0097b0c737bebabaab9d6c1473f Mon Sep 17 00:00:00 2001 From: Achmad Irianto Eka Putra Date: Thu, 12 Dec 2024 11:45:39 +0700 Subject: [PATCH 5/7] fix(server)!: improve HTTP server configuration Refactored HTTP server initialization to include proper timeouts for improved stability and resource management. Updated logging message for SOCKS5 server to use consistent capitalization. Removed redundant build directive for cleaner code. --- nanoproxy.go | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/nanoproxy.go b/nanoproxy.go index 7dea083..5eca6ee 100644 --- a/nanoproxy.go +++ b/nanoproxy.go @@ -1,5 +1,3 @@ -//go:build !test - package main import ( @@ -90,13 +88,22 @@ func main() { go func() { logger.Info().Msgf("Starting HTTP proxy server on %s://%s", cfg.Network, cfg.ADDRHttp) - if err := http.ListenAndServe(cfg.ADDRHttp, httpServer); err != nil { + + server := &http.Server{ + Addr: cfg.ADDRHttp, + Handler: httpServer, + ReadTimeout: 15 * time.Second, + WriteTimeout: 15 * time.Second, + IdleTimeout: 60 * time.Second, + } + + if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { logger.Fatal().Msg(err.Error()) } }() go func() { - logger.Info().Msgf("Starting socks5 server on %s://%s", cfg.Network, cfg.ADDR) + logger.Info().Msgf("Starting SOCKS5 server on %s://%s", cfg.Network, cfg.ADDR) if err := sock5Server.ListenAndServe(cfg.Network, cfg.ADDR); err != nil { logger.Fatal().Msg(err.Error()) } From 8c4115f9bcd2b7465062747f56853b7c7544bcf9 Mon Sep 17 00:00:00 2001 From: Achmad Irianto Eka Putra Date: Thu, 12 Dec 2024 11:48:28 +0700 Subject: [PATCH 6/7] feat(docs): update Docker commands to include port 8080 Updated the Docker run commands in the README to expose port 8080 alongside port 1080. This ensures better configurability and support for additional features or services requiring port 8080. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c2f819c..e1b0e7b 100644 --- a/README.md +++ b/README.md @@ -208,13 +208,13 @@ nanoproxy You can also run NanoProxy using Docker. To do so, you can use the following command: ```shell -docker run -p 1080:1080 ghcr.io/ryanbekhen/nanoproxy:latest +docker run -p 1080:1080 -p 8080:8080 ghcr.io/ryanbekhen/nanoproxy:latest ``` You can also run NanoProxy behind Tor using the following command: ```shell -docker run --rm -e TOR_ENABLED=true -d --privileged --cap-add=NET_ADMIN --sysctl net.ipv6.conf.all.disable_ipv6=0 --sysctl net.ipv4.conf.all.src_valid_mark=1 -p 1080:1080 ghcr.io/ryanbekhen/nanoproxy-tor:latest +docker run --rm -e TOR_ENABLED=true -d --privileged --cap-add=NET_ADMIN --sysctl net.ipv6.conf.all.disable_ipv6=0 --sysctl net.ipv4.conf.all.src_valid_mark=1 -p 1080:1080 -p 8080:8080 ghcr.io/ryanbekhen/nanoproxy-tor:latest ``` ## Configuration From 14db46f95f4edaa91b9a0337193393dcb8e46389 Mon Sep 17 00:00:00 2001 From: Achmad Irianto Eka Putra Date: Thu, 12 Dec 2024 11:49:55 +0700 Subject: [PATCH 7/7] fix(deps): update golang.org/x/crypto to v0.31.0 Updated the dependency golang.org/x/crypto from v0.30.0 to v0.31.0 for compatibility and security improvements. Removed unused indirect dependency github.com/stretchr/objx to clean up the go.mod and go.sum files. --- go.mod | 3 +-- go.sum | 6 ++---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 26e31aa..9849020 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/caarlos0/env/v10 v10.0.0 github.com/rs/zerolog v1.33.0 github.com/stretchr/testify v1.10.0 - golang.org/x/crypto v0.30.0 + golang.org/x/crypto v0.31.0 golang.org/x/net v0.32.0 ) @@ -15,7 +15,6 @@ require ( github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/stretchr/objx v0.5.2 // indirect golang.org/x/sys v0.28.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 2430ac4..206380c 100644 --- a/go.sum +++ b/go.sum @@ -16,12 +16,10 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY= -golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=