Skip to content

Commit

Permalink
feat: change engine to Fiber, and fix authentication can work on netw…
Browse files Browse the repository at this point in the history
…ork proxy
  • Loading branch information
ryanbekhen committed Sep 24, 2023
1 parent f341795 commit 4a684d6
Show file tree
Hide file tree
Showing 13 changed files with 662 additions and 185 deletions.
2 changes: 1 addition & 1 deletion .testcoverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ local-prefix: "github.com/ryanbekhen/nanoproxy"
threshold:
file: 80
package: 80
total: 95
total: 80
exclude:
paths:
- \.pb\.go$ # excludes all protobuf generated files
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
[![Go Report Card](https://goreportcard.com/badge/github.com/ryanbekhen/nanoproxy?cache=v1)](https://goreportcard.com/report/github.com/vladopajic/go-test-coverage)

NanoProxy is a lightweight HTTP proxy server designed to provide basic proxying functionality.
It supports handling HTTP requests and tunneling for HTTPS. NanoProxy is written in Go and built on top of FastHTTP.
It supports handling HTTP requests and tunneling for HTTPS. NanoProxy is written in Go and built on top of Fiber.

> ⚠️ **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
Expand Down
97 changes: 77 additions & 20 deletions config/config.go
Original file line number Diff line number Diff line change
@@ -1,57 +1,114 @@
package config

import (
"errors"
"flag"
"os"
"strings"
"time"
)

type Config struct {
PemPath string
KeyPath string
Proto string
Addr string
TunnelTimeout time.Duration
BasicAuth string
Debug bool
pemPath string
keyPath string
proto string
addr string
tunnelTimeout time.Duration
basicAuth string
debug bool
}

var (
ErrInvalidProto = errors.New("invalid protocol")
ErrInvalidBasicAuth = errors.New("invalid basic auth")
)

func New() *Config {
c := &Config{}
flag.StringVar(&c.PemPath, "pem", "server.pem", "path to pem file")
flag.StringVar(&c.KeyPath, "key", "server.key", "path to key file")
flag.StringVar(&c.Proto, "proto", "http", "proxy protocol (http or https)")
flag.StringVar(&c.Addr, "addr", ":8080", "proxy listen address (default :8080)")
flag.DurationVar(&c.TunnelTimeout, "timeout", time.Second*15, "tunnel timeout (default 15s)")
flag.StringVar(&c.BasicAuth, "auth", "", "basic auth (username:password)")
flag.BoolVar(&c.Debug, "debug", false, "debug mode")
flag.StringVar(&c.pemPath, "pem", "server.pem", "path to pem file")
flag.StringVar(&c.keyPath, "key", "server.key", "path to key file")
flag.StringVar(&c.proto, "proto", "http", "proxy protocol (http or https)")
flag.StringVar(&c.addr, "addr", ":8080", "proxy listen address (default :8080)")
flag.DurationVar(&c.tunnelTimeout, "timeout", time.Second*15, "tunnel timeout (default 15s)")
flag.StringVar(&c.basicAuth, "auth", "", "basic auth (username:password)")
flag.BoolVar(&c.debug, "debug", false, "debug mode")
flag.Parse()

if os.Getenv("PEM") != "" {
c.PemPath = os.Getenv("PEM")
c.pemPath = os.Getenv("PEM")
}

if os.Getenv("KEY") != "" {
c.KeyPath = os.Getenv("KEY")
c.keyPath = os.Getenv("KEY")
}

if os.Getenv("PROTO") != "" {
c.Proto = os.Getenv("PROTO")
c.proto = os.Getenv("PROTO")
}

if os.Getenv("ADDR") != "" {
c.Addr = os.Getenv("ADDR")
c.addr = os.Getenv("ADDR")
}

if os.Getenv("TIMEOUT") != "" {
d, err := time.ParseDuration(os.Getenv("TIMEOUT"))
if err == nil {
c.TunnelTimeout = d
c.tunnelTimeout = d
}
}

if os.Getenv("AUTH") != "" {
c.BasicAuth = os.Getenv("AUTH")
c.basicAuth = os.Getenv("AUTH")
}
return c
}

func (c *Config) PemPath() string {
return c.pemPath
}

func (c *Config) KeyPath() string {
return c.keyPath
}

func (c *Config) Addr() string {
return c.addr
}

func (c *Config) TunnelTimeout() time.Duration {
return c.tunnelTimeout
}

func (c *Config) BasicAuth() map[string]string {
cred := strings.Split(c.basicAuth, ",")
users := map[string]string{}
for _, cred := range cred {
userPass := strings.Split(cred, ":")
if len(userPass) == 2 {
users[userPass[0]] = userPass[1]
}
}
return users
}

func (c *Config) Debug() bool {
return c.debug
}

func (c *Config) IsHTTPS() bool {
return c.proto == "https"
}

func (c *Config) Validate() error {
if c.proto != "http" && c.proto != "https" {
return ErrInvalidProto
}

if c.basicAuth != "" {
if len(c.BasicAuth()) == 0 {
return ErrInvalidBasicAuth
}
}

return nil
}
7 changes: 7 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,22 @@ module github.com/ryanbekhen/nanoproxy
go 1.21

require (
github.com/gofiber/contrib/fiberzerolog v0.2.2
github.com/gofiber/fiber/v2 v2.49.2
github.com/rs/zerolog v1.30.0
github.com/valyala/fasthttp v1.50.0
)

require (
github.com/andybalholm/brotli v1.0.5 // indirect
github.com/google/uuid v1.3.1 // indirect
github.com/jochasinga/relay v0.0.0-20161125200856-6a088273228f // indirect
github.com/klauspost/compress v1.17.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect
golang.org/x/sys v0.12.0 // indirect
)
14 changes: 14 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@ github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofiber/contrib/fiberzerolog v0.2.2 h1:tvHBW5k+udW02LU1eNneh65znGwhsKcv8XWf22U7dlc=
github.com/gofiber/contrib/fiberzerolog v0.2.2/go.mod h1:CSpu4UUPGWAA/jIIuHXIhJt3W1cRxprxupXndAYuvpU=
github.com/gofiber/fiber/v2 v2.49.2 h1:ONEN3/Vc+dUCxxDgZZwpqvhISgHqb+bu+isBiEyKEQs=
github.com/gofiber/fiber/v2 v2.49.2/go.mod h1:gNsKnyrmfEWFpJxQAV0qvW6l70K1dZGno12oLtukcts=
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/jochasinga/relay v0.0.0-20161125200856-6a088273228f h1:QWP/EhlAPeJGlvSfA/wwUgmUMDWPp4HOMMCEiXt5WC0=
github.com/jochasinga/relay v0.0.0-20161125200856-6a088273228f/go.mod h1:qlpuzDguMQeZebM9+/rTCxDTXmN3oN1ctP+zcX3AejM=
github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM=
github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
Expand All @@ -11,14 +19,20 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.30.0 h1:SymVODrcRsaRaSInD9yQtKbtWqwsfoPcRff/oRXLj4c=
github.com/rs/zerolog v1.30.0/go.mod h1:/tk+P47gFdPXq4QYjvCmT5/Gsug2nagsFWBWhAiSi1w=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.50.0 h1:H7fweIlBm0rXLs2q0XbalvJ6r0CUPFWK3/bB4N13e9M=
github.com/valyala/fasthttp v1.50.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA=
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
Expand Down
55 changes: 55 additions & 0 deletions middleware/basicauth/basicauth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package basicauth

import (
"encoding/base64"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/utils"
"strings"
)

func New(config Config) fiber.Handler {
cfg := configDefault(config)
return func(c *fiber.Ctx) error {
// Skip if basic auth is empty
if len(cfg.Users) == 0 {
return c.Next()
}

// Get authorization header
auth := c.Get(fiber.HeaderProxyAuthorization)

// Check if header is valid
if len(auth) < 6 || !utils.EqualFold(auth[:6], "basic ") {
return cfg.Unauthorized(c)
}

// Decode header
raw, err := base64.StdEncoding.DecodeString(auth[6:])
if err != nil {
return cfg.Unauthorized(c)
}

// Get credentials
creds := utils.UnsafeString(raw)

// Split username and password
index := strings.Index(creds, ":")
if index == -1 {
return cfg.Unauthorized(c)
}

// Get username and password
user := creds[:index]
pass := creds[index+1:]

// Check credentials
if cfg.Authorized(user, pass) {
c.Locals("username", user)
c.Locals("password", pass)
return c.Next()
}

// Credentials doesn't match
return cfg.Unauthorized(c)
}
}
98 changes: 98 additions & 0 deletions middleware/basicauth/basicauth_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package basicauth

import (
"encoding/base64"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/utils"
"io"
"net/http/httptest"
"testing"
)

func Test_Middleware_BasicAuth(t *testing.T) {
t.Parallel()

app := fiber.New()

app.Use(New(Config{
Users: map[string]string{
"john": "doe",
"jane": "doe",
},
}))

app.Get("/testauth", func(c *fiber.Ctx) error {
username := c.Locals("username").(string)
password := c.Locals("password").(string)

return c.SendString(username + password)
})

tests := []struct {
url string
statusCode int
username string
password string
}{
{
url: "/testauth",
statusCode: fiber.StatusOK,
username: "john",
password: "doe",
},
{
url: "/testauth",
statusCode: fiber.StatusOK,
username: "jane",
password: "doe",
},
{
url: "/testauth",
statusCode: fiber.StatusUnauthorized,
username: "john",
password: "wrong",
},
}

for _, tt := range tests {
// Encode credentials to base64
cred := base64.StdEncoding.EncodeToString([]byte(tt.username + ":" + tt.password))

req := httptest.NewRequest(fiber.MethodGet, "/testauth", nil)
req.Header.Set(fiber.HeaderProxyAuthorization, "Basic "+cred)
resp, err := app.Test(req)
utils.AssertEqual(t, nil, err)

body, err := io.ReadAll(resp.Body)
utils.AssertEqual(t, nil, err)

utils.AssertEqual(t, tt.statusCode, resp.StatusCode)
if tt.statusCode == fiber.StatusOK {
utils.AssertEqual(t, tt.username+tt.password, string(body))
}
}
}

func Test_Middleware_BasicAuth_No_Users(t *testing.T) {
t.Parallel()

app := fiber.New()

app.Use(New(Config{
Users: map[string]string{},
}))

app.Get("/testauth", func(c *fiber.Ctx) error {
return c.SendString("testauth")
})

req := httptest.NewRequest(fiber.MethodGet, "/testauth", nil)
resp, err := app.Test(req)
utils.AssertEqual(t, nil, err)

body, err := io.ReadAll(resp.Body)
utils.AssertEqual(t, nil, err)

utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode)
utils.AssertEqual(t, "testauth", string(body))
}
Loading

0 comments on commit 4a684d6

Please sign in to comment.