mirror of
https://github.com/Gouryella/drip.git
synced 2026-02-23 21:00:44 +00:00
Added predefined tunnel functionality, allowing users to define multiple tunnels in the configuration file and start them by name, including the following improvements: - Added --all flag to start all configured tunnels - Added parameterless start command to list available tunnels - Support configuration of multiple tunnel types (http, https, tcp) - Support advanced configurations such as subdomains, transport protocols, and IP allowlists refactor(deployments): Refactor Docker deployment configuration Removed old Dockerfile and Compose configurations, added new deployment files: - Removed .env.example and old Docker build files - Added Caddy reverse proxy configuration file - Added two deployment modes: standard and Caddy reverse proxy - Added detailed server configuration example files docs: Update documentation to include tunnel configuration and deployment guide Updated Chinese and English README documents: - Added usage instructions and configuration examples for predefined tunnels - Expanded server deployment section to include direct TLS and reverse proxy modes - Added server configuration reference table with detailed configuration item descriptions - Added specific configuration methods for Caddy and Nginx reverse proxies
236 lines
6.6 KiB
Go
236 lines
6.6 KiB
Go
package config
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
// ServerConfig holds the server configuration
|
|
type ServerConfig struct {
|
|
Port int `yaml:"port"`
|
|
PublicPort int `yaml:"public_port"` // Port to display in URLs (for reverse proxy scenarios)
|
|
Domain string `yaml:"domain"` // Domain for client connections (e.g., connect.example.com)
|
|
TunnelDomain string `yaml:"tunnel_domain"` // Domain for tunnel URLs (e.g., example.com for *.example.com)
|
|
|
|
// TCP tunnel dynamic port allocation
|
|
TCPPortMin int `yaml:"tcp_port_min"`
|
|
TCPPortMax int `yaml:"tcp_port_max"`
|
|
|
|
// TLS settings
|
|
TLSEnabled bool `yaml:"tls_enabled"`
|
|
TLSCertFile string `yaml:"tls_cert"`
|
|
TLSKeyFile string `yaml:"tls_key"`
|
|
|
|
// Security
|
|
AuthToken string `yaml:"token"`
|
|
MetricsToken string `yaml:"metrics_token"`
|
|
|
|
// Logging
|
|
Debug bool `yaml:"debug"`
|
|
|
|
// Performance
|
|
PprofPort int `yaml:"pprof_port"`
|
|
|
|
// Allowed transports: "tcp", "wss", or "tcp,wss" (default: "tcp,wss")
|
|
AllowedTransports []string `yaml:"transports"`
|
|
|
|
// Allowed tunnel types: "http", "https", "tcp" (default: all)
|
|
AllowedTunnelTypes []string `yaml:"tunnel_types"`
|
|
}
|
|
|
|
// Validate checks if the server configuration is valid
|
|
func (c *ServerConfig) Validate() error {
|
|
// Validate port
|
|
if c.Port < 1 || c.Port > 65535 {
|
|
return fmt.Errorf("invalid port %d: must be between 1 and 65535", c.Port)
|
|
}
|
|
|
|
// Validate public port if set
|
|
if c.PublicPort != 0 && (c.PublicPort < 1 || c.PublicPort > 65535) {
|
|
return fmt.Errorf("invalid public port %d: must be between 1 and 65535", c.PublicPort)
|
|
}
|
|
|
|
// Validate domain
|
|
if c.Domain == "" {
|
|
return fmt.Errorf("domain is required")
|
|
}
|
|
if strings.Contains(c.Domain, ":") {
|
|
return fmt.Errorf("domain should not contain port, got: %s", c.Domain)
|
|
}
|
|
|
|
// Validate tunnel domain if set
|
|
if c.TunnelDomain != "" && strings.Contains(c.TunnelDomain, ":") {
|
|
return fmt.Errorf("tunnel domain should not contain port, got: %s", c.TunnelDomain)
|
|
}
|
|
|
|
// Validate TCP port range
|
|
if c.TCPPortMin < 1 || c.TCPPortMin > 65535 {
|
|
return fmt.Errorf("invalid TCPPortMin %d: must be between 1 and 65535", c.TCPPortMin)
|
|
}
|
|
if c.TCPPortMax < 1 || c.TCPPortMax > 65535 {
|
|
return fmt.Errorf("invalid TCPPortMax %d: must be between 1 and 65535", c.TCPPortMax)
|
|
}
|
|
if c.TCPPortMin >= c.TCPPortMax {
|
|
return fmt.Errorf("TCPPortMin (%d) must be less than TCPPortMax (%d)", c.TCPPortMin, c.TCPPortMax)
|
|
}
|
|
|
|
// Validate TLS settings
|
|
if c.TLSEnabled {
|
|
if c.TLSCertFile == "" {
|
|
return fmt.Errorf("TLS certificate file is required when TLS is enabled")
|
|
}
|
|
if c.TLSKeyFile == "" {
|
|
return fmt.Errorf("TLS key file is required when TLS is enabled")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// LoadTLSConfig loads TLS configuration
|
|
func (c *ServerConfig) LoadTLSConfig() (*tls.Config, error) {
|
|
if !c.TLSEnabled {
|
|
return nil, nil
|
|
}
|
|
|
|
if c.TLSCertFile == "" || c.TLSKeyFile == "" {
|
|
return nil, fmt.Errorf("TLS enabled but certificate files not specified")
|
|
}
|
|
|
|
if _, err := os.Stat(c.TLSCertFile); os.IsNotExist(err) {
|
|
return nil, fmt.Errorf("certificate file not found: %s", c.TLSCertFile)
|
|
}
|
|
|
|
if _, err := os.Stat(c.TLSKeyFile); os.IsNotExist(err) {
|
|
return nil, fmt.Errorf("key file not found: %s", c.TLSKeyFile)
|
|
}
|
|
|
|
cert, err := tls.LoadX509KeyPair(c.TLSCertFile, c.TLSKeyFile)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to load certificate: %w", err)
|
|
}
|
|
|
|
// Force TLS 1.3 only
|
|
tlsConfig := &tls.Config{
|
|
Certificates: []tls.Certificate{cert},
|
|
MinVersion: tls.VersionTLS13, // Only TLS 1.3
|
|
MaxVersion: tls.VersionTLS13, // Only TLS 1.3
|
|
PreferServerCipherSuites: true, // Prefer server cipher suites (ignored in TLS 1.3 but set for consistency)
|
|
CipherSuites: []uint16{
|
|
tls.TLS_AES_128_GCM_SHA256,
|
|
tls.TLS_AES_256_GCM_SHA384,
|
|
tls.TLS_CHACHA20_POLY1305_SHA256,
|
|
},
|
|
}
|
|
|
|
return tlsConfig, nil
|
|
}
|
|
|
|
// GetClientTLSConfig returns TLS config for client connections
|
|
func GetClientTLSConfig(serverName string) *tls.Config {
|
|
return &tls.Config{
|
|
ServerName: serverName,
|
|
MinVersion: tls.VersionTLS13,
|
|
MaxVersion: tls.VersionTLS13,
|
|
ClientSessionCache: tls.NewLRUClientSessionCache(0),
|
|
PreferServerCipherSuites: true,
|
|
CipherSuites: []uint16{
|
|
tls.TLS_AES_128_GCM_SHA256,
|
|
tls.TLS_AES_256_GCM_SHA384,
|
|
tls.TLS_CHACHA20_POLY1305_SHA256,
|
|
},
|
|
}
|
|
}
|
|
|
|
// GetClientTLSConfigInsecure returns TLS config for client with InsecureSkipVerify
|
|
// WARNING: Only use for testing!
|
|
func GetClientTLSConfigInsecure() *tls.Config {
|
|
return &tls.Config{
|
|
InsecureSkipVerify: true,
|
|
MinVersion: tls.VersionTLS13,
|
|
MaxVersion: tls.VersionTLS13,
|
|
ClientSessionCache: tls.NewLRUClientSessionCache(0),
|
|
PreferServerCipherSuites: true,
|
|
CipherSuites: []uint16{
|
|
tls.TLS_AES_128_GCM_SHA256,
|
|
tls.TLS_AES_256_GCM_SHA384,
|
|
tls.TLS_CHACHA20_POLY1305_SHA256,
|
|
},
|
|
}
|
|
}
|
|
|
|
// DefaultServerConfigPath returns the default server configuration path
|
|
func DefaultServerConfigPath() string {
|
|
// Check /etc/drip/config.yaml first (system-wide)
|
|
systemPath := "/etc/drip/config.yaml"
|
|
if _, err := os.Stat(systemPath); err == nil {
|
|
return systemPath
|
|
}
|
|
|
|
// Fall back to user home directory
|
|
home, err := os.UserHomeDir()
|
|
if err != nil {
|
|
return ".drip/server.yaml"
|
|
}
|
|
return filepath.Join(home, ".drip", "server.yaml")
|
|
}
|
|
|
|
// LoadServerConfig loads server configuration from file
|
|
func LoadServerConfig(path string) (*ServerConfig, error) {
|
|
if path == "" {
|
|
path = DefaultServerConfigPath()
|
|
}
|
|
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil, fmt.Errorf("config file not found at %s", path)
|
|
}
|
|
return nil, fmt.Errorf("failed to read config file: %w", err)
|
|
}
|
|
|
|
var config ServerConfig
|
|
if err := yaml.Unmarshal(data, &config); err != nil {
|
|
return nil, fmt.Errorf("failed to parse config file: %w", err)
|
|
}
|
|
|
|
return &config, nil
|
|
}
|
|
|
|
// SaveServerConfig saves server configuration to file
|
|
func SaveServerConfig(config *ServerConfig, path string) error {
|
|
if path == "" {
|
|
path = DefaultServerConfigPath()
|
|
}
|
|
|
|
dir := filepath.Dir(path)
|
|
if err := os.MkdirAll(dir, 0700); err != nil {
|
|
return fmt.Errorf("failed to create config directory: %w", err)
|
|
}
|
|
|
|
data, err := yaml.Marshal(config)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal config: %w", err)
|
|
}
|
|
|
|
if err := os.WriteFile(path, data, 0600); err != nil {
|
|
return fmt.Errorf("failed to write config file: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ServerConfigExists checks if server config file exists
|
|
func ServerConfigExists(path string) bool {
|
|
if path == "" {
|
|
path = DefaultServerConfigPath()
|
|
}
|
|
_, err := os.Stat(path)
|
|
return err == nil
|
|
}
|