├── go.mod ├── assets ├── access.png ├── social.png ├── tunnel.png ├── forward.png └── passport.png ├── internal ├── const.go ├── forward │ ├── broker.go │ ├── tcp.go │ └── udp.go ├── http.go └── tunnel │ ├── client.go │ ├── server.go │ ├── tcp.go │ └── udp.go ├── Dockerfile ├── .github └── workflows │ ├── release.yml │ └── docker.yml ├── .goreleaser.yml ├── cmd └── passport │ ├── main.go │ ├── auth.go │ ├── help.go │ └── core.go ├── pkg ├── conn │ └── conn.go ├── tls │ └── tls.go └── log │ └── log.go ├── LICENSE └── README.md /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/yosebyte/passport 2 | 3 | go 1.23.1 4 | -------------------------------------------------------------------------------- /assets/access.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yosebyte/passport/HEAD/assets/access.png -------------------------------------------------------------------------------- /assets/social.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yosebyte/passport/HEAD/assets/social.png -------------------------------------------------------------------------------- /assets/tunnel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yosebyte/passport/HEAD/assets/tunnel.png -------------------------------------------------------------------------------- /assets/forward.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yosebyte/passport/HEAD/assets/forward.png -------------------------------------------------------------------------------- /assets/passport.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yosebyte/passport/HEAD/assets/passport.png -------------------------------------------------------------------------------- /internal/const.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | const ( 4 | MaxSemaphoreLimit = 1024 5 | MaxSignalBuffer = 1024 6 | MaxDataBuffer = 8192 7 | MaxUDPTimeout = 5 8 | MaxReportInterval = 5 9 | ) 10 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:alpine AS builder 2 | WORKDIR /root 3 | ADD . . 4 | ARG VERSION 5 | WORKDIR /root/cmd/passport 6 | RUN env CGO_ENABLED=0 go build -v -trimpath -ldflags "-s -w -X main.version=${VERSION}" 7 | FROM scratch 8 | COPY --from=builder /root/cmd/passport/passport /passport 9 | ENTRYPOINT ["/passport"] 10 | -------------------------------------------------------------------------------- /internal/forward/broker.go: -------------------------------------------------------------------------------- 1 | package forward 2 | 3 | import ( 4 | "net/url" 5 | "sync" 6 | ) 7 | 8 | func Broker(parsedURL *url.URL, whiteList *sync.Map) error { 9 | errChan := make(chan error, 2) 10 | go func() { 11 | errChan <- HandleTCP(parsedURL, whiteList) 12 | }() 13 | go func() { 14 | errChan <- HandleUDP(parsedURL, whiteList) 15 | }() 16 | return <-errChan 17 | } 18 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: [ 'v*.*.*' ] 6 | 7 | jobs: 8 | goreleaser: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v4 13 | with: 14 | fetch-depth: 0 15 | - name: Set up Go 16 | uses: actions/setup-go@v5 17 | - name: Run GoReleaser 18 | uses: goreleaser/goreleaser-action@v6 19 | with: 20 | distribution: goreleaser 21 | version: 'latest' 22 | args: release --clean 23 | env: 24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 25 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | builds: 4 | - env: 5 | - CGO_ENABLED=0 6 | main: ./cmd/passport 7 | goos: 8 | - darwin 9 | - freebsd 10 | - linux 11 | - windows 12 | goarch: 13 | - 386 14 | - arm 15 | - amd64 16 | - arm64 17 | - mips 18 | - mipsle 19 | - mips64 20 | - mips64le 21 | goarm: 22 | - 6 23 | - 7 24 | gomips: 25 | - hardfloat 26 | - softfloat 27 | flags: 28 | - -trimpath 29 | ldflags: 30 | - -s -w -X main.version={{ .Tag }} 31 | 32 | archives: 33 | - format: tar.gz 34 | format_overrides: 35 | - goos: windows 36 | format: zip 37 | release: 38 | prerelease: true 39 | mode: replace 40 | -------------------------------------------------------------------------------- /cmd/passport/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/url" 5 | "os" 6 | "sync" 7 | 8 | "github.com/yosebyte/passport/pkg/log" 9 | "github.com/yosebyte/passport/pkg/tls" 10 | ) 11 | 12 | var ( 13 | version = "dev" 14 | whiteList sync.Map 15 | ) 16 | 17 | func main() { 18 | if len(os.Args) < 2 { 19 | helpInfo() 20 | os.Exit(1) 21 | } 22 | rawURL := os.Args[1] 23 | parsedURL, err := url.Parse(rawURL) 24 | if err != nil { 25 | log.Fatal("Unable to parse raw URL: %v", err) 26 | } 27 | tlsConfig, err := tls.NewTLSconfig("yosebyte/passport:" + version) 28 | if err != nil { 29 | log.Error("Unable to generate TLS config: %v", err) 30 | } 31 | authSetups(parsedURL, &whiteList, tlsConfig) 32 | coreSelect(parsedURL, rawURL, &whiteList, tlsConfig) 33 | } 34 | -------------------------------------------------------------------------------- /cmd/passport/auth.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "net/url" 6 | "sync" 7 | "time" 8 | 9 | "github.com/yosebyte/passport/internal" 10 | "github.com/yosebyte/passport/pkg/log" 11 | ) 12 | 13 | func authSetups(parsedURL *url.URL, whiteList *sync.Map, tlsConfig *tls.Config) { 14 | if parsedURL.Fragment == "" { 15 | return 16 | } 17 | parsedAuthURL, err := url.Parse(parsedURL.Fragment) 18 | if err != nil { 19 | log.Fatal("Unable to parse auth URL: %v", err) 20 | } 21 | log.Info("Auth mode enabled: %v", parsedAuthURL) 22 | go func() { 23 | for { 24 | if err := internal.HandleHTTP(parsedAuthURL, whiteList, tlsConfig); err != nil { 25 | log.Error("Auth mode error: %v", err) 26 | time.Sleep(1 * time.Second) 27 | log.Info("Auth mode restarted") 28 | continue 29 | } 30 | } 31 | }() 32 | } 33 | -------------------------------------------------------------------------------- /cmd/passport/help.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "runtime" 5 | 6 | "github.com/yosebyte/passport/pkg/log" 7 | ) 8 | 9 | func helpInfo() { 10 | log.Info(`Version: %v %v/%v 11 | 12 | Usage: 13 | passport :///# 14 | 15 | Examples: 16 | # Run as server 17 | passport server://10.0.0.1:10101/:10022#http://:80/secret 18 | 19 | # Run as client 20 | passport client://10.0.0.1:10101/127.0.0.1:22 21 | 22 | # Run as broker 23 | passport broker://:8080/10.0.0.1:8080#https://:443/secret 24 | 25 | Arguments: 26 | Select from "server", "client" or "broker" 27 | Tunneling or forwarding address to connect 28 | Service address to be exposed or forwarded 29 | Optional authorizing options in URL format 30 | `, version, runtime.GOOS, runtime.GOARCH) 31 | } 32 | -------------------------------------------------------------------------------- /pkg/conn/conn.go: -------------------------------------------------------------------------------- 1 | package conn 2 | 3 | import ( 4 | "io" 5 | "net" 6 | "strings" 7 | "sync" 8 | ) 9 | 10 | func DataExchange(conn1, conn2 net.Conn) error { 11 | var ( 12 | once1, once2 sync.Once 13 | wg sync.WaitGroup 14 | ) 15 | closeConn1 := func() { 16 | once1.Do(func() { 17 | if conn1 != nil { 18 | conn1.Close() 19 | } 20 | }) 21 | } 22 | closeConn2 := func() { 23 | once2.Do(func() { 24 | if conn2 != nil { 25 | conn2.Close() 26 | } 27 | }) 28 | } 29 | errChan := make(chan error, 2) 30 | wg.Add(2) 31 | go func() { 32 | defer func() { 33 | closeConn1() 34 | closeConn2() 35 | wg.Done() 36 | }() 37 | if _, err := io.Copy(conn1, conn2); err != nil { 38 | errChan <- err 39 | } 40 | }() 41 | go func() { 42 | defer func() { 43 | closeConn2() 44 | closeConn1() 45 | wg.Done() 46 | }() 47 | if _, err := io.Copy(conn2, conn1); err != nil { 48 | errChan <- err 49 | } 50 | }() 51 | wg.Wait() 52 | err := <-errChan 53 | if strings.Contains(err.Error(), "use of closed network connection") || err == nil { 54 | return io.EOF 55 | } 56 | return err 57 | } 58 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 𝐘𝐨𝐬𝐞𝐛𝐲𝐭𝐞 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /internal/http.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "crypto/tls" 5 | "net" 6 | "net/http" 7 | "net/url" 8 | "sync" 9 | 10 | "github.com/yosebyte/passport/pkg/log" 11 | ) 12 | 13 | func HandleHTTP(parsedURL *url.URL, whiteList *sync.Map, tlsConfig *tls.Config) error { 14 | http.HandleFunc(parsedURL.Path, func(w http.ResponseWriter, r *http.Request) { 15 | clientIP, _, err := net.SplitHostPort(r.RemoteAddr) 16 | if err != nil { 17 | log.Error("Invalid client IP address: [%v]", clientIP) 18 | return 19 | } 20 | if _, err := w.Write([]byte(clientIP + "\n")); err != nil { 21 | log.Error("Unable to write client IP address: [%v]", clientIP) 22 | return 23 | } 24 | whiteList.Store(clientIP, struct{}{}) 25 | log.Info("Authorized IP address added: [%v]", clientIP) 26 | }) 27 | if parsedURL.Scheme == "http" { 28 | if err := http.ListenAndServe(parsedURL.Host, nil); err != nil { 29 | log.Error("Unable to serve HTTP: %v", err) 30 | return err 31 | } 32 | } else { 33 | authServer := &http.Server{ 34 | Addr: parsedURL.Host, 35 | TLSConfig: tlsConfig, 36 | ErrorLog: log.NewLogger(), 37 | } 38 | if err := authServer.ListenAndServeTLS("", ""); err != nil { 39 | log.Error("Unable to serve HTTPS: %v", err) 40 | return err 41 | } 42 | } 43 | return nil 44 | } 45 | -------------------------------------------------------------------------------- /pkg/tls/tls.go: -------------------------------------------------------------------------------- 1 | package tls 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/rsa" 6 | "crypto/tls" 7 | "crypto/x509" 8 | "crypto/x509/pkix" 9 | "encoding/pem" 10 | "math/big" 11 | "time" 12 | ) 13 | 14 | func NewTLSconfig(hostname string) (*tls.Config, error) { 15 | private, err := rsa.GenerateKey(rand.Reader, 2048) 16 | if err != nil { 17 | return nil, err 18 | } 19 | serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) 20 | if err != nil { 21 | return nil, err 22 | } 23 | template := x509.Certificate{ 24 | SerialNumber: serialNumber, 25 | Subject: pkix.Name{ 26 | Organization: []string{hostname}, 27 | }, 28 | NotBefore: time.Now(), 29 | NotAfter: time.Now().Add(365 * 24 * time.Hour), 30 | KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, 31 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, 32 | } 33 | crtDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &private.PublicKey, private) 34 | if err != nil { 35 | return nil, err 36 | } 37 | crtPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: crtDER}) 38 | keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(private)}) 39 | cert, err := tls.X509KeyPair(crtPEM, keyPEM) 40 | if err != nil { 41 | return nil, err 42 | } 43 | tlsConfig := &tls.Config{Certificates: []tls.Certificate{cert}} 44 | return tlsConfig, nil 45 | } 46 | -------------------------------------------------------------------------------- /pkg/log/log.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "log" 7 | "os" 8 | "sync" 9 | "time" 10 | ) 11 | 12 | const ( 13 | LevelInfo = "INFO" 14 | LevelWarn = "WARN" 15 | LevelError = "ERROR" 16 | LevelFatal = "FATAL" 17 | ColorReset = "\033[0m" 18 | ColorInfo = "\033[32m" 19 | ColorWarn = "\033[33m" 20 | ColorError = "\033[31m" 21 | ColorFatal = "\033[35m" 22 | ) 23 | 24 | var ( 25 | colors = map[string]string{ 26 | LevelInfo: ColorInfo, 27 | LevelWarn: ColorWarn, 28 | LevelError: ColorError, 29 | LevelFatal: ColorFatal, 30 | } 31 | mu sync.Mutex 32 | ) 33 | 34 | type Adapter struct{} 35 | 36 | func (a *Adapter) Write(p []byte) (n int, err error) { 37 | Warn("%v", string(bytes.TrimSpace(p))) 38 | return len(p), nil 39 | } 40 | 41 | func NewLogger() *log.Logger { 42 | return log.New(&Adapter{}, "", 0) 43 | } 44 | 45 | func logger(level, format string, v ...interface{}) { 46 | mu.Lock() 47 | defer mu.Unlock() 48 | timestamp := time.Now().Format("2006-01-02 15:04:05.000") 49 | color := colors[level] 50 | message := fmt.Sprintf(format, v...) 51 | fmt.Printf("%v %v%v%v %v\n", timestamp, color, level, ColorReset, message) 52 | if level == LevelFatal { 53 | os.Exit(1) 54 | } 55 | } 56 | 57 | func Info(format string, v ...interface{}) { 58 | logger(LevelInfo, format, v...) 59 | } 60 | 61 | func Warn(format string, v ...interface{}) { 62 | logger(LevelWarn, format, v...) 63 | } 64 | 65 | func Error(format string, v ...interface{}) { 66 | logger(LevelError, format, v...) 67 | } 68 | 69 | func Fatal(format string, v ...interface{}) { 70 | logger(LevelFatal, format, v...) 71 | } 72 | -------------------------------------------------------------------------------- /internal/tunnel/client.go: -------------------------------------------------------------------------------- 1 | package tunnel 2 | 3 | import ( 4 | "crypto/tls" 5 | "net" 6 | "net/url" 7 | "strings" 8 | 9 | "github.com/yosebyte/passport/internal" 10 | "github.com/yosebyte/passport/pkg/log" 11 | ) 12 | 13 | func Client(parsedURL *url.URL) error { 14 | linkAddr, err := net.ResolveTCPAddr("tcp", parsedURL.Host) 15 | if err != nil { 16 | log.Error("Unable to resolve link address: %v", parsedURL.Host) 17 | return err 18 | } 19 | targetTCPAddr, err := net.ResolveTCPAddr("tcp", strings.TrimPrefix(parsedURL.Path, "/")) 20 | if err != nil { 21 | log.Error("Unable to resolve target address: %v", strings.TrimPrefix(parsedURL.Path, "/")) 22 | return err 23 | } 24 | targetUDPAddr, err := net.ResolveUDPAddr("udp", strings.TrimPrefix(parsedURL.Path, "/")) 25 | if err != nil { 26 | log.Error("Unable to resolve target address: %v", strings.TrimPrefix(parsedURL.Path, "/")) 27 | return err 28 | } 29 | linkConn, err := tls.Dial("tcp", linkAddr.String(), &tls.Config{InsecureSkipVerify: true}) 30 | if err != nil { 31 | log.Error("Unable to dial link address: [%v]", linkAddr) 32 | return err 33 | } 34 | defer linkConn.Close() 35 | if err := linkConn.Handshake(); err != nil { 36 | return err 37 | } 38 | log.Info("Tunnel connection established to: [%v]", linkAddr) 39 | buffer := make([]byte, internal.MaxSignalBuffer) 40 | for { 41 | n, err := linkConn.Read(buffer) 42 | if err != nil { 43 | log.Error("Unable to read form link address: [%v] %v", linkAddr, err) 44 | break 45 | } 46 | if string(buffer[:n]) == "[PASSPORT]\n" { 47 | go ClientTCP(linkAddr, targetTCPAddr) 48 | } 49 | if string(buffer[:n]) == "[PASSPORT]\n" { 50 | go ClientUDP(linkAddr, targetUDPAddr) 51 | } 52 | } 53 | return err 54 | } 55 | -------------------------------------------------------------------------------- /cmd/passport/core.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "net/url" 6 | "os" 7 | "strings" 8 | "sync" 9 | "time" 10 | 11 | "github.com/yosebyte/passport/internal/forward" 12 | "github.com/yosebyte/passport/internal/tunnel" 13 | "github.com/yosebyte/passport/pkg/log" 14 | ) 15 | 16 | func coreSelect(parsedURL *url.URL, rawURL string, whiteList *sync.Map, tlsConfig *tls.Config) { 17 | switch parsedURL.Scheme { 18 | case "server": 19 | runServer(parsedURL, rawURL, whiteList, tlsConfig) 20 | case "client": 21 | runClient(parsedURL, rawURL) 22 | case "broker": 23 | runBroker(parsedURL, rawURL, whiteList) 24 | default: 25 | helpInfo() 26 | os.Exit(1) 27 | } 28 | } 29 | 30 | func runServer(parsedURL *url.URL, rawURL string, whiteList *sync.Map, tlsConfig *tls.Config) { 31 | log.Info("Server core selected: %v", strings.Split(rawURL, "#")[0]) 32 | for { 33 | if err := tunnel.Server(parsedURL, whiteList, tlsConfig); err != nil { 34 | log.Error("Server core error: %v", err) 35 | time.Sleep(1 * time.Second) 36 | log.Info("Server core restarted") 37 | } 38 | } 39 | } 40 | 41 | func runClient(parsedURL *url.URL, rawURL string) { 42 | log.Info("Client core selected: %v", strings.Split(rawURL, "#")[0]) 43 | for { 44 | if err := tunnel.Client(parsedURL); err != nil { 45 | log.Error("Client core error: %v", err) 46 | time.Sleep(1 * time.Second) 47 | log.Info("Client core restarted") 48 | } 49 | } 50 | } 51 | 52 | func runBroker(parsedURL *url.URL, rawURL string, whiteList *sync.Map) { 53 | log.Info("Broker core selected: %v", strings.Split(rawURL, "#")[0]) 54 | for { 55 | if err := forward.Broker(parsedURL, whiteList); err != nil { 56 | log.Error("Broker core error: %v", err) 57 | time.Sleep(1 * time.Second) 58 | log.Info("Broker core restarted") 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /internal/forward/tcp.go: -------------------------------------------------------------------------------- 1 | package forward 2 | 3 | import ( 4 | "io" 5 | "net" 6 | "net/url" 7 | "strings" 8 | "sync" 9 | "time" 10 | 11 | "github.com/yosebyte/passport/internal" 12 | "github.com/yosebyte/passport/pkg/conn" 13 | "github.com/yosebyte/passport/pkg/log" 14 | ) 15 | 16 | func HandleTCP(parsedURL *url.URL, whiteList *sync.Map) error { 17 | linkAddr, err := net.ResolveTCPAddr("tcp", parsedURL.Host) 18 | if err != nil { 19 | log.Error("Unable to resolve link address: %v", parsedURL.Host) 20 | return err 21 | } 22 | targetAddr, err := net.ResolveTCPAddr("tcp", strings.TrimPrefix(parsedURL.Path, "/")) 23 | if err != nil { 24 | log.Error("Unable to resolve target address: %v", strings.TrimPrefix(parsedURL.Path, "/")) 25 | return err 26 | } 27 | linkListen, err := net.ListenTCP("tcp", linkAddr) 28 | if err != nil { 29 | log.Error("Unable to listen link address: [%v]", linkAddr) 30 | return err 31 | } 32 | defer linkListen.Close() 33 | sem := make(chan struct{}, internal.MaxSemaphoreLimit) 34 | for { 35 | linkConn, err := linkListen.AcceptTCP() 36 | if err != nil { 37 | log.Error("Unable to accept connections form link address: [%v] %v", linkAddr, err) 38 | time.Sleep(1 * time.Second) 39 | continue 40 | } 41 | sem <- struct{}{} 42 | go func(linkConn *net.TCPConn) { 43 | defer func() { <-sem }() 44 | clientAddr := linkConn.RemoteAddr().String() 45 | log.Info("Client connection established: [%v]", clientAddr) 46 | if parsedURL.Fragment != "" { 47 | clientIP, _, err := net.SplitHostPort(clientAddr) 48 | if err != nil { 49 | log.Error("Unable to extract client IP address: [%v]", clientAddr) 50 | linkConn.Close() 51 | return 52 | } 53 | if _, exists := whiteList.Load(clientIP); !exists { 54 | log.Warn("Unauthorized IP address blocked: [%v]", clientIP) 55 | linkConn.Close() 56 | return 57 | } 58 | } 59 | targetConn, err := net.DialTCP("tcp", nil, targetAddr) 60 | if err != nil { 61 | log.Error("Unable to dial target address: [%v]", targetAddr) 62 | linkConn.Close() 63 | return 64 | } 65 | log.Info("Target connection established: [%v]", targetAddr) 66 | log.Info("Starting data exchange: [%v] <-> [%v]", clientAddr, targetAddr) 67 | if err := conn.DataExchange(linkConn, targetConn); err != nil { 68 | if err == io.EOF { 69 | log.Info("Connection closed successfully: %v", err) 70 | } else { 71 | log.Warn("Connection closed unexpectedly: %v", err) 72 | } 73 | } 74 | }(linkConn) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /internal/forward/udp.go: -------------------------------------------------------------------------------- 1 | package forward 2 | 3 | import ( 4 | "net" 5 | "net/url" 6 | "strings" 7 | "sync" 8 | "time" 9 | 10 | "github.com/yosebyte/passport/internal" 11 | "github.com/yosebyte/passport/pkg/log" 12 | ) 13 | 14 | func HandleUDP(parsedURL *url.URL, whiteList *sync.Map) error { 15 | linkAddr, err := net.ResolveUDPAddr("udp", parsedURL.Host) 16 | if err != nil { 17 | log.Error("Unable to resolve link address: %v", parsedURL.Host) 18 | return err 19 | } 20 | targetAddr, err := net.ResolveUDPAddr("udp", strings.TrimPrefix(parsedURL.Path, "/")) 21 | if err != nil { 22 | log.Error("Unable to resolve target address: %v", strings.TrimPrefix(parsedURL.Path, "/")) 23 | return err 24 | } 25 | linkConn, err := net.ListenUDP("udp", linkAddr) 26 | if err != nil { 27 | log.Error("Unable to listen link address: [%v]", linkAddr) 28 | return err 29 | } 30 | defer linkConn.Close() 31 | sem := make(chan struct{}, internal.MaxSemaphoreLimit) 32 | for { 33 | buffer := make([]byte, internal.MaxDataBuffer) 34 | n, clientAddr, err := linkConn.ReadFromUDP(buffer) 35 | if err != nil { 36 | log.Error("Unable to read from client address: [%v] %v", clientAddr, err) 37 | time.Sleep(1 * time.Second) 38 | continue 39 | } 40 | if parsedURL.Fragment != "" { 41 | clientIP := clientAddr.IP.String() 42 | if _, exists := whiteList.Load(clientIP); !exists { 43 | log.Warn("Unauthorized IP address blocked: [%v]", clientIP) 44 | continue 45 | } 46 | } 47 | sem <- struct{}{} 48 | go func(buffer []byte, n int, clientAddr *net.UDPAddr) { 49 | defer func() { <-sem }() 50 | targetConn, err := net.DialUDP("udp", nil, targetAddr) 51 | if err != nil { 52 | log.Error("Unable to dial target address: [%v] %v", targetAddr, err) 53 | return 54 | } 55 | defer targetConn.Close() 56 | log.Info("Target connection established: [%v]", targetAddr) 57 | err = targetConn.SetDeadline(time.Now().Add(internal.MaxUDPTimeout * time.Second)) 58 | if err != nil { 59 | log.Error("Unable to set deadline: %v", err) 60 | return 61 | } 62 | log.Info("Starting data transfer: [%v] <-> [%v]", clientAddr, targetAddr) 63 | _, err = targetConn.Write(buffer[:n]) 64 | if err != nil { 65 | log.Error("Unable to write to target address: [%v] %v", targetAddr, err) 66 | return 67 | } 68 | n, _, err = targetConn.ReadFromUDP(buffer) 69 | if err != nil { 70 | log.Error("Unable to read from target address: [%v] %v", targetAddr, err) 71 | return 72 | } 73 | _, err = linkConn.WriteToUDP(buffer[:n], clientAddr) 74 | if err != nil { 75 | log.Error("Unable to write to client address: [%v] %v", clientAddr, err) 76 | return 77 | } 78 | log.Info("Transfer completed successfully") 79 | }(buffer, n, clientAddr) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /internal/tunnel/server.go: -------------------------------------------------------------------------------- 1 | package tunnel 2 | 3 | import ( 4 | "crypto/tls" 5 | "net" 6 | "net/url" 7 | "strings" 8 | "sync" 9 | "time" 10 | 11 | "github.com/yosebyte/passport/internal" 12 | "github.com/yosebyte/passport/pkg/log" 13 | ) 14 | 15 | func Server(parsedURL *url.URL, whiteList *sync.Map, tlsConfig *tls.Config) error { 16 | linkAddr, err := net.ResolveTCPAddr("tcp", parsedURL.Host) 17 | if err != nil { 18 | log.Error("Unable to resolve link address: %v", parsedURL.Host) 19 | return err 20 | } 21 | targetTCPAddr, err := net.ResolveTCPAddr("tcp", strings.TrimPrefix(parsedURL.Path, "/")) 22 | if err != nil { 23 | log.Error("Unable to resolve target TCP address: %v", strings.TrimPrefix(parsedURL.Path, "/")) 24 | return err 25 | } 26 | targetUDPAddr, err := net.ResolveUDPAddr("udp", strings.TrimPrefix(parsedURL.Path, "/")) 27 | if err != nil { 28 | log.Error("Unable to resolve target UDP address: %v", strings.TrimPrefix(parsedURL.Path, "/")) 29 | return err 30 | } 31 | linkListen, err := tls.Listen("tcp", linkAddr.String(), tlsConfig) 32 | if err != nil { 33 | log.Error("Unable to listen link address: [%v]", linkAddr) 34 | return err 35 | } 36 | defer linkListen.Close() 37 | linkConn, err := linkListen.Accept() 38 | if err != nil { 39 | log.Error("Unable to accept connections form link address: [%v]", linkAddr) 40 | return err 41 | } 42 | defer linkConn.Close() 43 | linkTLS, ok := linkConn.(*tls.Conn) 44 | if !ok { 45 | log.Error("Non-TLS connection received") 46 | linkConn.Close() 47 | return nil 48 | } 49 | if err := linkTLS.Handshake(); err != nil { 50 | linkConn.Close() 51 | return err 52 | } 53 | log.Info("Tunnel connection established from: [%v]", linkConn.RemoteAddr().String()) 54 | targetTCPListen, err := net.ListenTCP("tcp", targetTCPAddr) 55 | if err != nil { 56 | log.Error("Unable to listen target TCP address: [%v]", targetTCPAddr) 57 | return err 58 | } 59 | defer targetTCPListen.Close() 60 | targetUDPConn, err := net.ListenUDP("udp", targetUDPAddr) 61 | if err != nil { 62 | log.Error("Unable to listen target UDP address: [%v]", targetUDPAddr) 63 | return err 64 | } 65 | defer targetUDPConn.Close() 66 | var sharedMU sync.Mutex 67 | errChan := make(chan error, 2) 68 | done := make(chan struct{}) 69 | go func() { 70 | errChan <- healthCheck(linkListen, targetTCPListen, targetUDPConn, linkTLS, &sharedMU, done) 71 | }() 72 | go func() { 73 | errChan <- ServeTCP(parsedURL, whiteList, targetTCPListen, linkListen, linkTLS, &sharedMU, done) 74 | }() 75 | go func() { 76 | errChan <- ServeUDP(parsedURL, whiteList, targetUDPConn, linkListen, linkTLS, &sharedMU, done) 77 | }() 78 | return <-errChan 79 | } 80 | 81 | func healthCheck(linkListen net.Listener, targetTCPListen *net.TCPListener, targetUDPConn *net.UDPConn, linkTLS *tls.Conn, sharedMU *sync.Mutex, done chan struct{}) error { 82 | for { 83 | time.Sleep(internal.MaxReportInterval * time.Second) 84 | sharedMU.Lock() 85 | _, err := linkTLS.Write([]byte("[]\n")) 86 | sharedMU.Unlock() 87 | if err != nil { 88 | log.Error("Tunnel connection health check failed") 89 | if linkListen != nil { 90 | linkListen.Close() 91 | } 92 | if targetTCPListen != nil { 93 | targetTCPListen.Close() 94 | } 95 | if targetUDPConn != nil { 96 | targetUDPConn.Close() 97 | } 98 | if linkTLS != nil { 99 | linkTLS.Close() 100 | } 101 | close(done) 102 | return err 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /internal/tunnel/tcp.go: -------------------------------------------------------------------------------- 1 | package tunnel 2 | 3 | import ( 4 | "crypto/tls" 5 | "io" 6 | "net" 7 | "net/url" 8 | "sync" 9 | "time" 10 | 11 | "github.com/yosebyte/passport/internal" 12 | "github.com/yosebyte/passport/pkg/conn" 13 | "github.com/yosebyte/passport/pkg/log" 14 | ) 15 | 16 | func ServeTCP(parsedURL *url.URL, whiteList *sync.Map, targetTCPListen *net.TCPListener, linkListen net.Listener, linkTLS *tls.Conn, mu *sync.Mutex, done <-chan struct{}) error { 17 | sem := make(chan struct{}, internal.MaxSemaphoreLimit) 18 | for { 19 | select { 20 | case <-done: 21 | return nil 22 | default: 23 | targetConn, err := targetTCPListen.AcceptTCP() 24 | if err != nil { 25 | log.Error("Unable to accept connections form target address: [%v] %v", targetTCPListen.Addr().String(), err) 26 | time.Sleep(1 * time.Second) 27 | continue 28 | } 29 | clientAddr := targetConn.RemoteAddr().String() 30 | log.Info("Target connection established from: [%v]", clientAddr) 31 | if parsedURL.Fragment != "" { 32 | clientIP, _, err := net.SplitHostPort(clientAddr) 33 | if err != nil { 34 | log.Error("Unable to extract client IP address: [%v] %v", clientAddr, err) 35 | targetConn.Close() 36 | time.Sleep(1 * time.Second) 37 | continue 38 | } 39 | if _, exists := whiteList.Load(clientIP); !exists { 40 | log.Warn("Unauthorized IP address blocked: [%v]", clientIP) 41 | targetConn.Close() 42 | continue 43 | } 44 | } 45 | sem <- struct{}{} 46 | go func(targetConn *net.TCPConn) { 47 | defer func() { <-sem }() 48 | mu.Lock() 49 | _, err = linkTLS.Write([]byte("[PASSPORT]\n")) 50 | mu.Unlock() 51 | if err != nil { 52 | log.Error("Unable to send signal: %v", err) 53 | targetConn.Close() 54 | return 55 | } 56 | remoteConn, err := linkListen.Accept() 57 | if err != nil { 58 | log.Error("Unable to accept connections form link address: [%v] %v", linkListen.Addr().String(), err) 59 | return 60 | } 61 | remoteTLS, ok := remoteConn.(*tls.Conn) 62 | if !ok { 63 | log.Error("Non-TLS connection received") 64 | targetConn.Close() 65 | remoteConn.Close() 66 | return 67 | } 68 | if err := remoteTLS.Handshake(); err != nil { 69 | log.Error("TLS handshake failed: %v", err) 70 | targetConn.Close() 71 | remoteTLS.Close() 72 | return 73 | } 74 | log.Info("Starting data exchange: [%v] <-> [%v]", clientAddr, targetTCPListen.Addr().String()) 75 | if err := conn.DataExchange(remoteTLS, targetConn); err != nil { 76 | if err == io.EOF { 77 | log.Info("Connection closed successfully: %v", err) 78 | } else { 79 | log.Warn("Connection closed unexpectedly: %v", err) 80 | } 81 | } 82 | }(targetConn) 83 | } 84 | } 85 | } 86 | 87 | func ClientTCP(linkAddr, targetTCPAddr *net.TCPAddr) { 88 | targetConn, err := net.DialTCP("tcp", nil, targetTCPAddr) 89 | if err != nil { 90 | log.Error("Unable to dial target address: [%v], %v", targetTCPAddr, err) 91 | return 92 | } 93 | log.Info("Target connection established: [%v]", targetTCPAddr) 94 | remoteTLS, err := tls.Dial("tcp", linkAddr.String(), &tls.Config{InsecureSkipVerify: true}) 95 | if err != nil { 96 | log.Error("Unable to dial target address: [%v], %v", linkAddr, err) 97 | targetConn.Close() 98 | return 99 | } 100 | if err := remoteTLS.Handshake(); err != nil { 101 | log.Error("TLS handshake failed: %v", err) 102 | targetConn.Close() 103 | remoteTLS.Close() 104 | return 105 | } 106 | log.Info("Starting data exchange: [%v] <-> [%v]", linkAddr, targetTCPAddr) 107 | if err := conn.DataExchange(remoteTLS, targetConn); err != nil { 108 | if err == io.EOF { 109 | log.Info("Connection closed successfully: %v", err) 110 | } else { 111 | log.Warn("Connection closed unexpectedly: %v", err) 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | name: Docker 2 | 3 | # This workflow uses actions that are not certified by GitHub. 4 | # They are provided by a third-party and are governed by 5 | # separate terms of service, privacy policy, and support 6 | # documentation. 7 | 8 | on: 9 | push: 10 | tags: [ 'v*.*.*' ] 11 | 12 | env: 13 | # Use docker.io for Docker Hub if empty 14 | REGISTRY: ghcr.io 15 | # github.repository as / 16 | IMAGE_NAME: ${{ github.repository }} 17 | VERSION: ${{ github.ref_name }} 18 | 19 | 20 | jobs: 21 | build: 22 | 23 | runs-on: ubuntu-latest 24 | permissions: 25 | contents: read 26 | packages: write 27 | # This is used to complete the identity challenge 28 | # with sigstore/fulcio when running outside of PRs. 29 | id-token: write 30 | 31 | steps: 32 | - name: Checkout repository 33 | uses: actions/checkout@v4 34 | 35 | # Install the cosign tool except on PR 36 | # https://github.com/sigstore/cosign-installer 37 | - name: Install cosign 38 | if: github.event_name != 'pull_request' 39 | uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 #v3.5.0 40 | with: 41 | cosign-release: 'v2.2.4' 42 | 43 | # Set up BuildKit Docker container builder to be able to build 44 | # multi-platform images and export cache 45 | # https://github.com/docker/setup-buildx-action 46 | - name: Set up Docker Buildx 47 | uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 48 | 49 | # Login against a Docker registry except on PR 50 | # https://github.com/docker/login-action 51 | - name: Log into registry ${{ env.REGISTRY }} 52 | if: github.event_name != 'pull_request' 53 | uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 54 | with: 55 | registry: ${{ env.REGISTRY }} 56 | username: ${{ github.actor }} 57 | password: ${{ secrets.GITHUB_TOKEN }} 58 | 59 | # Extract metadata (tags, labels) for Docker 60 | # https://github.com/docker/metadata-action 61 | - name: Extract Docker metadata 62 | id: meta 63 | uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # v5.0.0 64 | with: 65 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 66 | 67 | # Build and push Docker image with Buildx (don't push on PR) 68 | # https://github.com/docker/build-push-action 69 | - name: Build and push Docker image 70 | id: build-and-push 71 | uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0 72 | with: 73 | context: . 74 | push: ${{ github.event_name != 'pull_request' }} 75 | tags: ${{ steps.meta.outputs.tags }} 76 | labels: ${{ steps.meta.outputs.labels }} 77 | cache-from: type=gha 78 | cache-to: type=gha,mode=max 79 | platforms: linux/amd64,linux/arm64 80 | build-args: VERSION=${{ env.VERSION }} 81 | provenance: false 82 | 83 | # Sign the resulting Docker image digest except on PRs. 84 | # This will only write to the public Rekor transparency log when the Docker 85 | # repository is public to avoid leaking data. If you would like to publish 86 | # transparency data even for private images, pass --force to cosign below. 87 | # https://github.com/sigstore/cosign 88 | - name: Sign the published Docker image 89 | if: ${{ github.event_name != 'pull_request' }} 90 | env: 91 | # https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-an-intermediate-environment-variable 92 | TAGS: ${{ steps.meta.outputs.tags }} 93 | DIGEST: ${{ steps.build-and-push.outputs.digest }} 94 | # This step uses the identity token to provision an ephemeral certificate 95 | # against the sigstore community Fulcio instance. 96 | run: echo ${{ steps.meta.outputs.tags }} | tr ',' '\n' | xargs -I {} cosign sign --yes {}@${DIGEST} 97 | -------------------------------------------------------------------------------- /internal/tunnel/udp.go: -------------------------------------------------------------------------------- 1 | package tunnel 2 | 3 | import ( 4 | "crypto/tls" 5 | "net" 6 | "net/url" 7 | "sync" 8 | "time" 9 | 10 | "github.com/yosebyte/passport/internal" 11 | "github.com/yosebyte/passport/pkg/log" 12 | ) 13 | 14 | func ServeUDP(parsedURL *url.URL, whiteList *sync.Map, targetUDPConn *net.UDPConn, linkListen net.Listener, linkTLS *tls.Conn, mu *sync.Mutex, done <-chan struct{}) error { 15 | sem := make(chan struct{}, internal.MaxSemaphoreLimit) 16 | for { 17 | select { 18 | case <-done: 19 | return nil 20 | default: 21 | buffer := make([]byte, internal.MaxDataBuffer) 22 | n, clientAddr, err := targetUDPConn.ReadFromUDP(buffer) 23 | if err != nil { 24 | log.Error("Unable to read from client address: [%v] %v", clientAddr, err) 25 | time.Sleep(1 * time.Second) 26 | continue 27 | } 28 | if parsedURL.Fragment != "" { 29 | clientIP := clientAddr.IP.String() 30 | if _, exists := whiteList.Load(clientIP); !exists { 31 | log.Warn("Unauthorized IP address blocked: [%v]", clientIP) 32 | continue 33 | } 34 | } 35 | mu.Lock() 36 | _, err = linkTLS.Write([]byte("[PASSPORT]\n")) 37 | mu.Unlock() 38 | if err != nil { 39 | log.Error("Unable to send signal: %v", err) 40 | time.Sleep(1 * time.Second) 41 | continue 42 | } 43 | remoteConn, err := linkListen.Accept() 44 | if err != nil { 45 | log.Error("Unable to accept connections from link address: [%v] %v", linkListen.Addr().String(), err) 46 | time.Sleep(1 * time.Second) 47 | continue 48 | } 49 | remoteTLS, ok := remoteConn.(*tls.Conn) 50 | if !ok { 51 | log.Error("Non-TLS connection received") 52 | remoteConn.Close() 53 | time.Sleep(1 * time.Second) 54 | continue 55 | } 56 | if err := remoteTLS.Handshake(); err != nil { 57 | log.Error("TLS handshake failed: %v", err) 58 | remoteTLS.Close() 59 | time.Sleep(1 * time.Second) 60 | continue 61 | } 62 | sem <- struct{}{} 63 | go func(buffer []byte, n int, remoteTLS *tls.Conn, clientAddr *net.UDPAddr) { 64 | defer func() { 65 | <-sem 66 | remoteTLS.Close() 67 | }() 68 | log.Info("Starting data transfer: [%v] <-> [%v]", clientAddr, targetUDPConn.LocalAddr()) 69 | _, err = remoteTLS.Write(buffer[:n]) 70 | if err != nil { 71 | log.Error("Unable to write to link address: [%v] %v", linkListen.Addr().String(), err) 72 | return 73 | } 74 | n, err = remoteTLS.Read(buffer) 75 | if err != nil { 76 | log.Error("Unable to read from link address: [%v] %v", linkListen.Addr().String(), err) 77 | return 78 | } 79 | _, err = targetUDPConn.WriteToUDP(buffer[:n], clientAddr) 80 | if err != nil { 81 | log.Error("Unable to write to client address: [%v] %v", clientAddr, err) 82 | return 83 | } 84 | log.Info("Transfer completed successfully") 85 | }(buffer, n, remoteTLS, clientAddr) 86 | } 87 | } 88 | } 89 | 90 | func ClientUDP(linkAddr *net.TCPAddr, targetUDPAddr *net.UDPAddr) { 91 | remoteTLS, err := tls.Dial("tcp", linkAddr.String(), &tls.Config{InsecureSkipVerify: true}) 92 | if err != nil { 93 | log.Error("Unable to dial target address: [%v] %v", linkAddr, err) 94 | return 95 | } 96 | defer remoteTLS.Close() 97 | if err := remoteTLS.Handshake(); err != nil { 98 | log.Error("TLS handshake failed: %v", err) 99 | return 100 | } 101 | log.Info("Remote connection established: [%v]", linkAddr) 102 | buffer := make([]byte, internal.MaxDataBuffer) 103 | n, err := remoteTLS.Read(buffer) 104 | if err != nil { 105 | log.Error("Unable to read from remote address: [%v] %v", linkAddr, err) 106 | return 107 | } 108 | targetConn, err := net.DialUDP("udp", nil, targetUDPAddr) 109 | if err != nil { 110 | log.Error("Unable to dial target address: [%v] %v", targetUDPAddr, err) 111 | return 112 | } 113 | defer targetConn.Close() 114 | log.Info("Target connection established: [%v]", targetUDPAddr) 115 | err = targetConn.SetDeadline(time.Now().Add(internal.MaxUDPTimeout * time.Second)) 116 | if err != nil { 117 | log.Error("Unable to set deadline: %v", err) 118 | return 119 | } 120 | log.Info("Starting data transfer: [%v] <-> [%v]", linkAddr, targetUDPAddr) 121 | _, err = targetConn.Write(buffer[:n]) 122 | if err != nil { 123 | log.Error("Unable to write to target address: [%v] %v", targetUDPAddr, err) 124 | return 125 | } 126 | n, _, err = targetConn.ReadFromUDP(buffer) 127 | if err != nil { 128 | log.Error("Unable to read from target address: [%v] %v", targetUDPAddr, err) 129 | return 130 | } 131 | _, err = remoteTLS.Write(buffer[:n]) 132 | if err != nil { 133 | log.Error("Unable to write to remote address: [%v] %v", linkAddr, err) 134 | return 135 | } 136 | log.Info("Transfer completed successfully") 137 | } 138 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Passport](https://img.shields.io/badge/Yosebyte-Passport-blue) 2 | ![GitHub License](https://img.shields.io/github/license/yosebyte/passport) 3 | [![Go Report Card](https://goreportcard.com/badge/github.com/yosebyte/passport)](https://goreportcard.com/report/github.com/yosebyte/passport) 4 | [![Go Reference](https://pkg.go.dev/badge/github.com/yosebyte/passport.svg)](https://pkg.go.dev/github.com/yosebyte/passport) 5 | ![GitHub Release](https://img.shields.io/github/v/release/yosebyte/passport) 6 | ![GitHub last commit](https://img.shields.io/github/last-commit/yosebyte/passport) 7 | ![GitHub commits since latest release](https://img.shields.io/github/commits-since/yosebyte/passport/latest) 8 | 9 | > **Note** 10 | > The tunnel functionality of the `passport` project has been separated into a new project called [NodePass](https://github.com/yosebyte/nodepass), which focuses on secure and efficient TCP tunneling. The remaining port forwarding components in `passport` are currently being refactored. 11 | 12 |
13 | passport 14 |
15 | 16 |

"Access pass required to pass through port."

17 | 18 | ## Overview 19 | 20 | **Passport** is a powerful connection management tool that simplifies network tunneling, port forwarding and more. By seamlessly integrating three distinct running modes within a single binary file, Passport bridges the gap between different network environments, redirecting services and handling connections seamlessly, ensuring reliable network connectivity and ideal network environment. Also with highly integrated authorization handling, Passport empowers you to efficiently manage user permissions and establish uninterrupted data flow, ensuring that sensitive resources remain protected while applications maintain high performance and responsiveness. 21 | 22 | ## Features 23 | 24 | - **Unified Operation**: Passport can function as a server, client, or broker, three roles from a single executable file. 25 | - **Authorization Handling**: By IP address handling, Passport ensures only authorized users gain access to sensitive resources. 26 | - **In-Memory Certificate**: Provides a self-signed HTTPS certificate with a one-year validity, stored entirely in memory. 27 | - **Network Tunneling**: Supports both TCP and/or UDP intranet penetration services with full-process TLS encryption processing. 28 | - **Port Forwarding**: Efficiently manage and redirect your TCP and/or UDP services from one port to entrypoints everywhere. 29 | - **Auto Reconnection**: Providing robust short-term reconnection capabilities, ensuring uninterrupted service. 30 | - **Zero Dependencies**: Fully self-contained, with no external dependencies, ensuring a simple and efficient setup. 31 | - **Zero Configuration File**: Simply execute with a single URL command, making it ideal for containerized environments. 32 | 33 | ## Designs 34 | 35 | ### Network Tunneling 36 | 37 |
38 | tunnel 39 |
40 | Tunneling establishes seamless access to otherwise unreachable resources. A user’s request is sent to the server, which forwards it through a pre-established TLS-encrypted channel to the client. The client then connects to the target service, creating two secure links: one to the server and another to the target. This enables data exchange between the client and the target, and subsequently between the server and the user. For concurrent user requests, multiple TLS-encrypted connections are established, supporting native high-concurrency performance. Notably, UDP tunneling leverages the same TLS-encrypted TCP channels between the server and client, ensuring security and eliminating latency caused by unsuccessful NAT traversal attempts. 41 | 42 | ### Port Forwarding 43 | 44 |
45 | forward 46 |
47 | Forwarding simplifies the process by directly relaying user TCP/UDP requests to the target service via a broker. The broker establishes a connection with the target, exchanges data with the service, and returns responses to the user. While this mode supports high concurrency if the user-side supports multithreading, it does not employ TLS encryption. For secure usage, ensure the target service provides its own transmission security. 48 | 49 | ### Access Control 50 | 51 |
52 | access 53 |
54 | The authentication system employs a secure and dynamic IP whitelisting mechanism designed to manage access control effectively. Verified IP addresses are stored in memory for the duration of the server or broker's runtime, with all entries cleared upon server restart to ensure that no stale or unauthorized IPs remain active. This design prioritizes security by requiring reauthentication after a restart. When a user attempts to access a resource, their IP is checked against the whitelist. If the IP is present, access is granted seamlessly. If the user's IP has changed, or if the IP is not whitelisted, the system blocks access and redirects the user to an authentication URL. Successful authentication not only verifies the user's access but also updates the whitelist by temporarily storing the IP in memory and returning the current IP address to confirm the process. Unauthorized IPs remain blocked until proper authentication is completed. This approach combines real-time validation, adaptability to changing IPs, and enhanced security measures to provide a reliable access control solution. 55 | 56 | ## Basic Usage 57 | 58 | You can easily learn how to use it correctly by running passport directly without parameters. 59 | 60 | ``` 61 | Usage: 62 | passport :///# 63 | 64 | Examples: 65 | # Run as server 66 | passport server://10.0.0.1:10101/:10022#http://:80/secret 67 | 68 | # Run as client 69 | passport client://10.0.0.1:10101/127.0.0.1:22 70 | 71 | # Run as broker 72 | passport broker://:8080/10.0.0.1:8080#https://:443/secret 73 | 74 | Arguments: 75 | Select from "server", "client" or "broker" 76 | Tunneling or forwarding address to connect 77 | Service address to be exposed or forwarded 78 | Optional authorizing options in URL format 79 | ``` 80 | 81 | ### Server Mode 82 | 83 | - `linkAddr`: The address for accepting client connections. For example, `:10101`. 84 | - `targetAddr`: The address for listening to external connections. For example, `:10022`. 85 | 86 | **Run as Server** 87 | 88 | ``` 89 | ./passport server://:10101/:10022 90 | ``` 91 | 92 | - This command will listen for client connections on port `10101` , listen and forward data to port `10022`. 93 | 94 | **Run as Server with authorization** 95 | 96 | ``` 97 | ./passport server://:10101/:10022#https://hostname:8443/server 98 | ``` 99 | 100 | - The server handles authorization at `https://hostname:8443/server`, on your visit and your IP logged. 101 | - The server will listen for client connections on port `10101` , listen and forward data to port `10022`. 102 | 103 | ### Client Mode 104 | 105 | - `linkAddr`: The address of the server to connect to. For example, `server_ip:10101`. 106 | - `targetAddr`: The address of the target service to connect to. For example, `127.0.0.1:22`. 107 | 108 | **Run as Client** 109 | 110 | ``` 111 | ./passport client://server_hostname_or_IP:10101/127.0.0.1:22 112 | ``` 113 | 114 | - This command will establish link with `server_hostname_or_IP:10101` , connect and forward data to `127.0.0.1:22`. 115 | 116 | ### Broker Mode 117 | 118 | - `linkAddr`: The address for accepting client connections. For example, `:10101`. 119 | - `targetAddr`: The address of the target service to connect to. For example, `127.0.0.1:22`. 120 | 121 | **Run as Broker** 122 | 123 | ``` 124 | ./passport broker://:10101/127.0.0.1:22 125 | ``` 126 | 127 | - This command will listen both `tcp` and `udp` on port `10101` , connect and forward data to `127.0.0.1:22`. 128 | 129 | **Run as Broker with authorization** 130 | 131 | ``` 132 | ./passport broker://:10101/127.0.0.1:22#https://hostname:8443/broker 133 | ``` 134 | 135 | - The server handles authorization at `https://hostname:8443/broker`, on your visit and your IP logged. 136 | - This command will listen both `tcp` and `udp` on port `10101` , connect and forward data to `127.0.0.1:22`. 137 | 138 | ## Container Usage 139 | 140 | You can also run **Passport** using docker or podman. The image is available at [ghcr.io/yosebyte/passport](https://ghcr.io/yosebyte/passport). 141 | 142 | To run the container in server mode with or without authorization: 143 | 144 | ``` 145 | docker run -d --rm \ 146 | ghcr.io/yosebyte/passport \ 147 | server://:10101/:10022#https://hostname:8443/server 148 | ``` 149 | 150 | ``` 151 | docker run -d --rm \ 152 | ghcr.io/yosebyte/passport \ 153 | server://:10101/:10022 154 | ``` 155 | 156 | To run the container in client mode: 157 | 158 | ``` 159 | docker run -d --rm \ 160 | ghcr.io/yosebyte/passport \ 161 | client://server_hostname_or_IP:10101/127.0.0.1:22 162 | ``` 163 | 164 | To run the container in server mode with or without authorization: 165 | 166 | ``` 167 | docker run -d --rm \ 168 | ghcr.io/yosebyte/passport \ 169 | broker://:10101/127.0.0.1:22#https://hostname:8443/broker 170 | ``` 171 | 172 | ``` 173 | docker run -d --rm \ 174 | ghcr.io/yosebyte/passport \ 175 | broker://:10101/127.0.0.1:22 176 | ``` 177 | 178 | ## License 179 | 180 | This project is licensed under the [MIT](LICENSE) License. 181 | 182 | ## Stargazers 183 | [![Stargazers over time](https://starchart.cc/yosebyte/passport.svg?variant=adaptive)](https://starchart.cc/yosebyte/passport) 184 | --------------------------------------------------------------------------------