Compare commits
4 Commits
v1.0.0
...
8a456d2cde
| Author | SHA1 | Date | |
|---|---|---|---|
| 8a456d2cde | |||
| acd02aadd3 | |||
| 8841230653 | |||
| 4d0a7deaf2 |
35
internal/config/config.go
Normal file
35
internal/config/config.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/joho/godotenv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if _, err := os.Stat(".env"); err == nil {
|
||||||
|
if err := godotenv.Load(".env"); err != nil {
|
||||||
|
log.Printf("Warning: Failed to load .env file: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Getenv(key, defaultValue string) string {
|
||||||
|
val := os.Getenv(key)
|
||||||
|
if val == "" {
|
||||||
|
val = defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetBufferSize() int {
|
||||||
|
sizeStr := Getenv("BUFFER_SIZE", "32768")
|
||||||
|
size, err := strconv.Atoi(sizeStr)
|
||||||
|
if err != nil || size < 4096 || size > 1048576 {
|
||||||
|
return 32768
|
||||||
|
}
|
||||||
|
return size
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package utils
|
package key
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
@@ -6,54 +6,12 @@ import (
|
|||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"log"
|
"log"
|
||||||
mathrand "math/rand"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/joho/godotenv"
|
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
|
||||||
if _, err := os.Stat(".env"); err == nil {
|
|
||||||
if err := godotenv.Load(".env"); err != nil {
|
|
||||||
log.Printf("Warning: Failed to load .env file: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func GenerateRandomString(length int) string {
|
|
||||||
const charset = "abcdefghijklmnopqrstuvwxyz"
|
|
||||||
seededRand := mathrand.New(mathrand.NewSource(time.Now().UnixNano() + int64(mathrand.Intn(9999))))
|
|
||||||
var result strings.Builder
|
|
||||||
for i := 0; i < length; i++ {
|
|
||||||
randomIndex := seededRand.Intn(len(charset))
|
|
||||||
result.WriteString(string(charset[randomIndex]))
|
|
||||||
}
|
|
||||||
return result.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func Getenv(key, defaultValue string) string {
|
|
||||||
val := os.Getenv(key)
|
|
||||||
if val == "" {
|
|
||||||
val = defaultValue
|
|
||||||
}
|
|
||||||
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetBufferSize() int {
|
|
||||||
sizeStr := Getenv("BUFFER_SIZE", "32768")
|
|
||||||
size, err := strconv.Atoi(sizeStr)
|
|
||||||
if err != nil || size < 4096 || size > 1048576 {
|
|
||||||
return 32768
|
|
||||||
}
|
|
||||||
return size
|
|
||||||
}
|
|
||||||
|
|
||||||
func GenerateSSHKeyIfNotExist(keyPath string) error {
|
func GenerateSSHKeyIfNotExist(keyPath string) error {
|
||||||
if _, err := os.Stat(keyPath); err == nil {
|
if _, err := os.Stat(keyPath); err == nil {
|
||||||
log.Printf("SSH key already exists at %s", keyPath)
|
log.Printf("SSH key already exists at %s", keyPath)
|
||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"tunnel_pls/utils"
|
"tunnel_pls/internal/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Manager interface {
|
type Manager interface {
|
||||||
@@ -28,7 +28,7 @@ var Default Manager = &manager{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rawRange := utils.Getenv("ALLOWED_PORTS", "")
|
rawRange := config.Getenv("ALLOWED_PORTS", "")
|
||||||
if rawRange == "" {
|
if rawRange == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
18
internal/random/random.go
Normal file
18
internal/random/random.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package random
|
||||||
|
|
||||||
|
import (
|
||||||
|
mathrand "math/rand"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GenerateRandomString(length int) string {
|
||||||
|
const charset = "abcdefghijklmnopqrstuvwxyz"
|
||||||
|
seededRand := mathrand.New(mathrand.NewSource(time.Now().UnixNano() + int64(mathrand.Intn(9999))))
|
||||||
|
var result strings.Builder
|
||||||
|
for i := 0; i < length; i++ {
|
||||||
|
randomIndex := seededRand.Intn(len(charset))
|
||||||
|
result.WriteString(string(charset[randomIndex]))
|
||||||
|
}
|
||||||
|
return result.String()
|
||||||
|
}
|
||||||
9
main.go
9
main.go
@@ -6,8 +6,9 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
_ "net/http/pprof"
|
_ "net/http/pprof"
|
||||||
"os"
|
"os"
|
||||||
|
"tunnel_pls/internal/config"
|
||||||
|
"tunnel_pls/internal/key"
|
||||||
"tunnel_pls/server"
|
"tunnel_pls/server"
|
||||||
"tunnel_pls/utils"
|
|
||||||
"tunnel_pls/version"
|
"tunnel_pls/version"
|
||||||
|
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
@@ -24,9 +25,9 @@ func main() {
|
|||||||
|
|
||||||
log.Printf("Starting %s", version.GetVersion())
|
log.Printf("Starting %s", version.GetVersion())
|
||||||
|
|
||||||
pprofEnabled := utils.Getenv("PPROF_ENABLED", "false")
|
pprofEnabled := config.Getenv("PPROF_ENABLED", "false")
|
||||||
if pprofEnabled == "true" {
|
if pprofEnabled == "true" {
|
||||||
pprofPort := utils.Getenv("PPROF_PORT", "6060")
|
pprofPort := config.Getenv("PPROF_PORT", "6060")
|
||||||
go func() {
|
go func() {
|
||||||
pprofAddr := fmt.Sprintf("localhost:%s", pprofPort)
|
pprofAddr := fmt.Sprintf("localhost:%s", pprofPort)
|
||||||
log.Printf("Starting pprof server on http://%s/debug/pprof/", pprofAddr)
|
log.Printf("Starting pprof server on http://%s/debug/pprof/", pprofAddr)
|
||||||
@@ -42,7 +43,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sshKeyPath := "certs/ssh/id_rsa"
|
sshKeyPath := "certs/ssh/id_rsa"
|
||||||
if err := utils.GenerateSSHKeyIfNotExist(sshKeyPath); err != nil {
|
if err := key.GenerateSSHKeyIfNotExist(sshKeyPath); err != nil {
|
||||||
log.Fatalf("Failed to generate SSH key: %s", err)
|
log.Fatalf("Failed to generate SSH key: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
"tunnel_pls/internal/config"
|
||||||
"tunnel_pls/session"
|
"tunnel_pls/session"
|
||||||
"tunnel_pls/utils"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
@@ -231,12 +231,12 @@ func (cw *customWriter) AddInteraction(interaction Interaction) {
|
|||||||
var redirectTLS = false
|
var redirectTLS = false
|
||||||
|
|
||||||
func NewHTTPServer() error {
|
func NewHTTPServer() error {
|
||||||
httpPort := utils.Getenv("HTTP_PORT", "8080")
|
httpPort := config.Getenv("HTTP_PORT", "8080")
|
||||||
listener, err := net.Listen("tcp", ":"+httpPort)
|
listener, err := net.Listen("tcp", ":"+httpPort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("Error listening: " + err.Error())
|
return errors.New("Error listening: " + err.Error())
|
||||||
}
|
}
|
||||||
if utils.Getenv("TLS_ENABLED", "false") == "true" && utils.Getenv("TLS_REDIRECT", "false") == "true" {
|
if config.Getenv("TLS_ENABLED", "false") == "true" && config.Getenv("TLS_REDIRECT", "false") == "true" {
|
||||||
redirectTLS = true
|
redirectTLS = true
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
@@ -288,7 +288,7 @@ func Handler(conn net.Conn) {
|
|||||||
|
|
||||||
if redirectTLS {
|
if redirectTLS {
|
||||||
_, err = conn.Write([]byte("HTTP/1.1 301 Moved Permanently\r\n" +
|
_, err = conn.Write([]byte("HTTP/1.1 301 Moved Permanently\r\n" +
|
||||||
fmt.Sprintf("Location: https://%s.%s/\r\n", slug, utils.Getenv("DOMAIN", "localhost")) +
|
fmt.Sprintf("Location: https://%s.%s/\r\n", slug, config.Getenv("DOMAIN", "localhost")) +
|
||||||
"Content-Length: 0\r\n" +
|
"Content-Length: 0\r\n" +
|
||||||
"Connection: close\r\n" +
|
"Connection: close\r\n" +
|
||||||
"\r\n"))
|
"\r\n"))
|
||||||
|
|||||||
@@ -8,13 +8,13 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
|
"tunnel_pls/internal/config"
|
||||||
"tunnel_pls/session"
|
"tunnel_pls/session"
|
||||||
"tunnel_pls/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewHTTPSServer() error {
|
func NewHTTPSServer() error {
|
||||||
domain := utils.Getenv("DOMAIN", "localhost")
|
domain := config.Getenv("DOMAIN", "localhost")
|
||||||
httpsPort := utils.Getenv("HTTPS_PORT", "8443")
|
httpsPort := config.Getenv("HTTPS_PORT", "8443")
|
||||||
|
|
||||||
tlsConfig, err := NewTLSConfig(domain)
|
tlsConfig, err := NewTLSConfig(domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"tunnel_pls/utils"
|
"tunnel_pls/internal/config"
|
||||||
|
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
@@ -28,13 +28,13 @@ func (s *Server) GetHttpServer() *http.Server {
|
|||||||
return s.httpServer
|
return s.httpServer
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServer(config *ssh.ServerConfig) *Server {
|
func NewServer(sshConfig *ssh.ServerConfig) *Server {
|
||||||
listener, err := net.Listen("tcp", fmt.Sprintf(":%s", utils.Getenv("PORT", "2200")))
|
listener, err := net.Listen("tcp", fmt.Sprintf(":%s", config.Getenv("PORT", "2200")))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed to listen on port 2200: %v", err)
|
log.Fatalf("failed to listen on port 2200: %v", err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if utils.Getenv("TLS_ENABLED", "false") == "true" {
|
if config.Getenv("TLS_ENABLED", "false") == "true" {
|
||||||
err = NewHTTPSServer()
|
err = NewHTTPSServer()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed to start https server: %v", err)
|
log.Fatalf("failed to start https server: %v", err)
|
||||||
@@ -46,7 +46,7 @@ func NewServer(config *ssh.ServerConfig) *Server {
|
|||||||
}
|
}
|
||||||
return &Server{
|
return &Server{
|
||||||
conn: &listener,
|
conn: &listener,
|
||||||
config: config,
|
config: sshConfig,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
"tunnel_pls/utils"
|
"tunnel_pls/internal/config"
|
||||||
|
|
||||||
"github.com/caddyserver/certmagic"
|
"github.com/caddyserver/certmagic"
|
||||||
"github.com/libdns/cloudflare"
|
"github.com/libdns/cloudflare"
|
||||||
@@ -92,7 +92,7 @@ func NewTLSConfig(domain string) (*tls.Config, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func isACMEConfigComplete() bool {
|
func isACMEConfigComplete() bool {
|
||||||
cfAPIToken := utils.Getenv("CF_API_TOKEN", "")
|
cfAPIToken := config.Getenv("CF_API_TOKEN", "")
|
||||||
return cfAPIToken != ""
|
return cfAPIToken != ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,9 +241,9 @@ func (tm *tlsManager) initCertMagic() error {
|
|||||||
return fmt.Errorf("failed to create cert storage directory: %w", err)
|
return fmt.Errorf("failed to create cert storage directory: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
acmeEmail := utils.Getenv("ACME_EMAIL", "admin@"+tm.domain)
|
acmeEmail := config.Getenv("ACME_EMAIL", "admin@"+tm.domain)
|
||||||
cfAPIToken := utils.Getenv("CF_API_TOKEN", "")
|
cfAPIToken := config.Getenv("CF_API_TOKEN", "")
|
||||||
acmeStaging := utils.Getenv("ACME_STAGING", "false") == "true"
|
acmeStaging := config.Getenv("ACME_STAGING", "false") == "true"
|
||||||
|
|
||||||
if cfAPIToken == "" {
|
if cfAPIToken == "" {
|
||||||
return fmt.Errorf("CF_API_TOKEN environment variable is required for automatic certificate generation")
|
return fmt.Errorf("CF_API_TOKEN environment variable is required for automatic certificate generation")
|
||||||
|
|||||||
@@ -10,16 +10,16 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
"tunnel_pls/internal/config"
|
||||||
"tunnel_pls/session/slug"
|
"tunnel_pls/session/slug"
|
||||||
"tunnel_pls/types"
|
"tunnel_pls/types"
|
||||||
"tunnel_pls/utils"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
var bufferPool = sync.Pool{
|
var bufferPool = sync.Pool{
|
||||||
New: func() interface{} {
|
New: func() interface{} {
|
||||||
bufSize := utils.GetBufferSize()
|
bufSize := config.GetBufferSize()
|
||||||
return make([]byte, bufSize)
|
return make([]byte, bufSize)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,10 +7,9 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
portUtil "tunnel_pls/internal/port"
|
portUtil "tunnel_pls/internal/port"
|
||||||
|
"tunnel_pls/internal/random"
|
||||||
"tunnel_pls/types"
|
"tunnel_pls/types"
|
||||||
|
|
||||||
"tunnel_pls/utils"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -276,7 +275,7 @@ func generateUniqueSlug() string {
|
|||||||
maxAttempts := 5
|
maxAttempts := 5
|
||||||
|
|
||||||
for i := 0; i < maxAttempts; i++ {
|
for i := 0; i < maxAttempts; i++ {
|
||||||
slug := utils.GenerateRandomString(20)
|
slug := random.GenerateRandomString(20)
|
||||||
|
|
||||||
clientsMutex.RLock()
|
clientsMutex.RLock()
|
||||||
_, exists := Clients[slug]
|
_, exists := Clients[slug]
|
||||||
|
|||||||
@@ -6,9 +6,10 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
"tunnel_pls/internal/config"
|
||||||
|
"tunnel_pls/internal/random"
|
||||||
"tunnel_pls/session/slug"
|
"tunnel_pls/session/slug"
|
||||||
"tunnel_pls/types"
|
"tunnel_pls/types"
|
||||||
"tunnel_pls/utils"
|
|
||||||
|
|
||||||
"github.com/charmbracelet/bubbles/help"
|
"github.com/charmbracelet/bubbles/help"
|
||||||
"github.com/charmbracelet/bubbles/key"
|
"github.com/charmbracelet/bubbles/key"
|
||||||
@@ -722,9 +723,9 @@ func (m model) View() string {
|
|||||||
func (i *Interaction) Start() {
|
func (i *Interaction) Start() {
|
||||||
lipgloss.SetColorProfile(termenv.TrueColor)
|
lipgloss.SetColorProfile(termenv.TrueColor)
|
||||||
|
|
||||||
domain := utils.Getenv("DOMAIN", "localhost")
|
domain := config.Getenv("DOMAIN", "localhost")
|
||||||
protocol := "http"
|
protocol := "http"
|
||||||
if utils.Getenv("TLS_ENABLED", "false") == "true" {
|
if config.Getenv("TLS_ENABLED", "false") == "true" {
|
||||||
protocol = "https"
|
protocol = "https"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -811,7 +812,7 @@ func buildURL(protocol, subdomain, domain string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func generateRandomSubdomain() string {
|
func generateRandomSubdomain() string {
|
||||||
return utils.GenerateRandomString(20)
|
return random.GenerateRandomString(20)
|
||||||
}
|
}
|
||||||
|
|
||||||
func isValidSlug(slug string) bool {
|
func isValidSlug(slug string) bool {
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
"tunnel_pls/internal/config"
|
||||||
"tunnel_pls/session/forwarder"
|
"tunnel_pls/session/forwarder"
|
||||||
"tunnel_pls/session/interaction"
|
"tunnel_pls/session/interaction"
|
||||||
"tunnel_pls/session/lifecycle"
|
"tunnel_pls/session/lifecycle"
|
||||||
"tunnel_pls/session/slug"
|
"tunnel_pls/session/slug"
|
||||||
"tunnel_pls/utils"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
@@ -79,7 +79,7 @@ func New(conn *ssh.ServerConn, forwardingReq <-chan *ssh.Request, sshChan <-chan
|
|||||||
|
|
||||||
tcpipReq := session.waitForTCPIPForward(forwardingReq)
|
tcpipReq := session.waitForTCPIPForward(forwardingReq)
|
||||||
if tcpipReq == nil {
|
if tcpipReq == nil {
|
||||||
log.Printf("Port forwarding request not received. Ensure you ran the correct command with -R flag. Example: ssh %s -p %s -R 80:localhost:3000", utils.Getenv("DOMAIN", "localhost"), utils.Getenv("PORT", "2200"))
|
log.Printf("Port forwarding request not received. Ensure you ran the correct command with -R flag. Example: ssh %s -p %s -R 80:localhost:3000", config.Getenv("DOMAIN", "localhost"), config.Getenv("PORT", "2200"))
|
||||||
if err := session.lifecycle.Close(); err != nil {
|
if err := session.lifecycle.Close(); err != nil {
|
||||||
log.Printf("failed to close session: %v", err)
|
log.Printf("failed to close session: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user