mirror of
https://github.com/DeBrosOfficial/network.git
synced 2025-12-11 09:18:50 +00:00
feat: add HTTPS configuration options and server setup
- Introduced new configuration fields for enabling HTTPS, specifying a domain name, and setting a TLS cache directory in the gateway configuration. - Enhanced the main server logic to support HTTPS with ACME integration, including automatic HTTP to HTTPS redirection and error handling for server startup. - Added validation for HTTPS settings to ensure proper domain and cache directory configuration. - Implemented interactive prompts in the CLI for domain and HTTPS setup, including DNS verification and port availability checks.
This commit is contained in:
parent
f2d6254b7b
commit
8a7ae4ad6f
26
CHANGELOG.md
26
CHANGELOG.md
@ -14,6 +14,32 @@ The format is based on [Keep a Changelog][keepachangelog] and adheres to [Semant
|
||||
|
||||
### Fixed
|
||||
|
||||
## [0.53.8] - 2025-10-31
|
||||
|
||||
### Added
|
||||
|
||||
- **HTTPS/ACME Support**: Gateway now supports automatic HTTPS with Let's Encrypt certificates via ACME
|
||||
- Interactive domain configuration during `network-cli setup` command
|
||||
- Automatic port availability checking for ports 80 and 443 before enabling HTTPS
|
||||
- DNS resolution verification to ensure domain points to the server IP
|
||||
- TLS certificate cache directory management (`~/.debros/tls-cache`)
|
||||
- Gateway automatically serves HTTP (port 80) for ACME challenges and HTTPS (port 443) for traffic
|
||||
- New gateway config fields: `enable_https`, `domain_name`, `tls_cache_dir`
|
||||
- **Domain Validation**: Added domain name validation and DNS verification helpers in setup CLI
|
||||
- **Port Checking**: Added port availability checking utilities to detect conflicts before HTTPS setup
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated `generateGatewayConfigDirect` to include HTTPS configuration fields
|
||||
- Enhanced gateway config parsing to support HTTPS settings with validation
|
||||
- Modified gateway startup to handle both HTTP-only and HTTPS+ACME modes
|
||||
- Gateway now automatically manages ACME certificate acquisition and renewal
|
||||
|
||||
### Fixed
|
||||
|
||||
- Improved error handling during HTTPS setup with clear messaging when ports are unavailable
|
||||
- Enhanced DNS verification flow with better user feedback during setup
|
||||
|
||||
## [0.53.0] - 2025-10-31
|
||||
|
||||
### Added
|
||||
|
||||
2
Makefile
2
Makefile
@ -21,7 +21,7 @@ test-e2e:
|
||||
|
||||
.PHONY: build clean test run-node run-node2 run-node3 run-example deps tidy fmt vet lint clear-ports
|
||||
|
||||
VERSION := 0.53.6
|
||||
VERSION := 0.53.8
|
||||
COMMIT ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown)
|
||||
DATE ?= $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
|
||||
LDFLAGS := -X 'main.version=$(VERSION)' -X 'main.commit=$(COMMIT)' -X 'main.date=$(DATE)'
|
||||
|
||||
@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/DeBrosOfficial/network/pkg/config"
|
||||
@ -53,6 +54,9 @@ func parseGatewayConfig(logger *logging.ColoredLogger) *gateway.Config {
|
||||
ClientNamespace string `yaml:"client_namespace"`
|
||||
RQLiteDSN string `yaml:"rqlite_dsn"`
|
||||
BootstrapPeers []string `yaml:"bootstrap_peers"`
|
||||
EnableHTTPS bool `yaml:"enable_https"`
|
||||
DomainName string `yaml:"domain_name"`
|
||||
TLSCacheDir string `yaml:"tls_cache_dir"`
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(configPath)
|
||||
@ -79,6 +83,9 @@ func parseGatewayConfig(logger *logging.ColoredLogger) *gateway.Config {
|
||||
ClientNamespace: "default",
|
||||
BootstrapPeers: nil,
|
||||
RQLiteDSN: "",
|
||||
EnableHTTPS: false,
|
||||
DomainName: "",
|
||||
TLSCacheDir: "",
|
||||
}
|
||||
|
||||
if v := strings.TrimSpace(y.ListenAddr); v != "" {
|
||||
@ -103,6 +110,21 @@ func parseGatewayConfig(logger *logging.ColoredLogger) *gateway.Config {
|
||||
}
|
||||
}
|
||||
|
||||
// HTTPS configuration
|
||||
cfg.EnableHTTPS = y.EnableHTTPS
|
||||
if v := strings.TrimSpace(y.DomainName); v != "" {
|
||||
cfg.DomainName = v
|
||||
}
|
||||
if v := strings.TrimSpace(y.TLSCacheDir); v != "" {
|
||||
cfg.TLSCacheDir = v
|
||||
} else if cfg.EnableHTTPS {
|
||||
// Default TLS cache directory if HTTPS is enabled but not specified
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err == nil {
|
||||
cfg.TLSCacheDir = filepath.Join(homeDir, ".debros", "tls-cache")
|
||||
}
|
||||
}
|
||||
|
||||
// Validate configuration
|
||||
if errs := cfg.ValidateConfig(); len(errs) > 0 {
|
||||
fmt.Fprintf(os.Stderr, "\nGateway configuration errors (%d):\n", len(errs))
|
||||
|
||||
@ -12,6 +12,7 @@ import (
|
||||
"github.com/DeBrosOfficial/network/pkg/gateway"
|
||||
"github.com/DeBrosOfficial/network/pkg/logging"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/crypto/acme/autocert"
|
||||
)
|
||||
|
||||
func setupLogger() *logging.ColoredLogger {
|
||||
@ -42,6 +43,123 @@ func main() {
|
||||
|
||||
logger.ComponentInfo(logging.ComponentGeneral, "Creating HTTP server and routes...")
|
||||
|
||||
// Check if HTTPS is enabled
|
||||
if cfg.EnableHTTPS && cfg.DomainName != "" {
|
||||
logger.ComponentInfo(logging.ComponentGeneral, "HTTPS enabled with ACME",
|
||||
zap.String("domain", cfg.DomainName),
|
||||
zap.String("tls_cache_dir", cfg.TLSCacheDir),
|
||||
)
|
||||
|
||||
// Set up ACME manager
|
||||
manager := &autocert.Manager{
|
||||
Prompt: autocert.AcceptTOS,
|
||||
HostPolicy: autocert.HostWhitelist(cfg.DomainName),
|
||||
}
|
||||
|
||||
// Set cache directory if specified
|
||||
if cfg.TLSCacheDir != "" {
|
||||
manager.Cache = autocert.DirCache(cfg.TLSCacheDir)
|
||||
logger.ComponentInfo(logging.ComponentGeneral, "Using TLS certificate cache",
|
||||
zap.String("cache_dir", cfg.TLSCacheDir),
|
||||
)
|
||||
}
|
||||
|
||||
// Create HTTP server for ACME challenge (port 80)
|
||||
httpServer := &http.Server{
|
||||
Addr: ":80",
|
||||
Handler: manager.HTTPHandler(nil), // Redirects all HTTP traffic to HTTPS except ACME challenge
|
||||
}
|
||||
|
||||
// Create HTTPS server (port 443)
|
||||
httpsServer := &http.Server{
|
||||
Addr: ":443",
|
||||
Handler: gw.Routes(),
|
||||
TLSConfig: manager.TLSConfig(),
|
||||
}
|
||||
|
||||
// Start HTTP server for ACME challenge
|
||||
logger.ComponentInfo(logging.ComponentGeneral, "Starting HTTP server for ACME challenge on port 80...")
|
||||
httpLn, err := net.Listen("tcp", ":80")
|
||||
if err != nil {
|
||||
logger.ComponentError(logging.ComponentGeneral, "failed to bind HTTP listen address (port 80)", zap.Error(err))
|
||||
os.Exit(1)
|
||||
}
|
||||
logger.ComponentInfo(logging.ComponentGeneral, "HTTP listener bound", zap.String("listen_addr", httpLn.Addr().String()))
|
||||
|
||||
// Start HTTPS server
|
||||
logger.ComponentInfo(logging.ComponentGeneral, "Starting HTTPS server on port 443...")
|
||||
httpsLn, err := net.Listen("tcp", ":443")
|
||||
if err != nil {
|
||||
logger.ComponentError(logging.ComponentGeneral, "failed to bind HTTPS listen address (port 443)", zap.Error(err))
|
||||
os.Exit(1)
|
||||
}
|
||||
logger.ComponentInfo(logging.ComponentGeneral, "HTTPS listener bound", zap.String("listen_addr", httpsLn.Addr().String()))
|
||||
|
||||
// Serve HTTP in a goroutine
|
||||
httpServeErrCh := make(chan error, 1)
|
||||
go func() {
|
||||
if err := httpServer.Serve(httpLn); err != nil && err != http.ErrServerClosed {
|
||||
httpServeErrCh <- err
|
||||
return
|
||||
}
|
||||
httpServeErrCh <- nil
|
||||
}()
|
||||
|
||||
// Serve HTTPS in a goroutine
|
||||
httpsServeErrCh := make(chan error, 1)
|
||||
go func() {
|
||||
if err := httpsServer.ServeTLS(httpsLn, "", ""); err != nil && err != http.ErrServerClosed {
|
||||
httpsServeErrCh <- err
|
||||
return
|
||||
}
|
||||
httpsServeErrCh <- nil
|
||||
}()
|
||||
|
||||
// Wait for termination signal or server error
|
||||
quit := make(chan os.Signal, 1)
|
||||
signal.Notify(quit, os.Interrupt, syscall.SIGTERM)
|
||||
|
||||
select {
|
||||
case sig := <-quit:
|
||||
logger.ComponentInfo(logging.ComponentGeneral, "shutdown signal received", zap.String("signal", sig.String()))
|
||||
case err := <-httpServeErrCh:
|
||||
if err != nil {
|
||||
logger.ComponentError(logging.ComponentGeneral, "HTTP server error", zap.Error(err))
|
||||
} else {
|
||||
logger.ComponentInfo(logging.ComponentGeneral, "HTTP server exited normally")
|
||||
}
|
||||
case err := <-httpsServeErrCh:
|
||||
if err != nil {
|
||||
logger.ComponentError(logging.ComponentGeneral, "HTTPS server error", zap.Error(err))
|
||||
} else {
|
||||
logger.ComponentInfo(logging.ComponentGeneral, "HTTPS server exited normally")
|
||||
}
|
||||
}
|
||||
|
||||
logger.ComponentInfo(logging.ComponentGeneral, "Shutting down gateway servers...")
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Shutdown HTTPS server
|
||||
if err := httpsServer.Shutdown(ctx); err != nil {
|
||||
logger.ComponentError(logging.ComponentGeneral, "HTTPS server shutdown error", zap.Error(err))
|
||||
} else {
|
||||
logger.ComponentInfo(logging.ComponentGeneral, "HTTPS server shutdown complete")
|
||||
}
|
||||
|
||||
// Shutdown HTTP server
|
||||
if err := httpServer.Shutdown(ctx); err != nil {
|
||||
logger.ComponentError(logging.ComponentGeneral, "HTTP server shutdown error", zap.Error(err))
|
||||
} else {
|
||||
logger.ComponentInfo(logging.ComponentGeneral, "HTTP server shutdown complete")
|
||||
}
|
||||
|
||||
logger.ComponentInfo(logging.ComponentGeneral, "Gateway shutdown complete")
|
||||
return
|
||||
}
|
||||
|
||||
// Standard HTTP server (no HTTPS)
|
||||
server := &http.Server{
|
||||
Addr: cfg.ListenAddr,
|
||||
Handler: gw.Routes(),
|
||||
|
||||
241
pkg/cli/setup.go
241
pkg/cli/setup.go
@ -125,13 +125,57 @@ func HandleSetupCommand(args []string) {
|
||||
fmt.Printf(" network-cli service restart gateway\n\n")
|
||||
fmt.Printf("Access DeBros User:\n")
|
||||
fmt.Printf(" sudo -u debros bash\n\n")
|
||||
|
||||
// Check if HTTPS is enabled
|
||||
gatewayConfigPath := "/home/debros/.debros/gateway.yaml"
|
||||
httpsEnabled := false
|
||||
var domainName string
|
||||
if data, err := os.ReadFile(gatewayConfigPath); err == nil {
|
||||
var cfg config.Config
|
||||
if err := config.DecodeStrict(strings.NewReader(string(data)), &cfg); err == nil {
|
||||
// Try to parse as gateway config
|
||||
if strings.Contains(string(data), "enable_https: true") {
|
||||
httpsEnabled = true
|
||||
// Extract domain name from config
|
||||
lines := strings.Split(string(data), "\n")
|
||||
for _, line := range lines {
|
||||
if strings.HasPrefix(strings.TrimSpace(line), "domain_name:") {
|
||||
parts := strings.Split(line, ":")
|
||||
if len(parts) > 1 {
|
||||
domainName = strings.Trim(strings.TrimSpace(parts[1]), "\"")
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("Verify Installation:\n")
|
||||
fmt.Printf(" curl http://localhost:6001/health\n")
|
||||
if httpsEnabled && domainName != "" {
|
||||
fmt.Printf(" curl https://%s/health\n", domainName)
|
||||
fmt.Printf(" curl http://localhost:6001/health (HTTP fallback)\n")
|
||||
} else {
|
||||
fmt.Printf(" curl http://localhost:6001/health\n")
|
||||
}
|
||||
fmt.Printf(" curl http://localhost:5001/status\n\n")
|
||||
|
||||
if httpsEnabled && domainName != "" {
|
||||
fmt.Printf("HTTPS Configuration:\n")
|
||||
fmt.Printf(" Domain: %s\n", domainName)
|
||||
fmt.Printf(" HTTPS endpoint: https://%s\n", domainName)
|
||||
fmt.Printf(" Certificate cache: /home/debros/.debros/tls-cache\n")
|
||||
fmt.Printf(" Certificates are automatically managed via Let's Encrypt (ACME)\n\n")
|
||||
}
|
||||
|
||||
fmt.Printf("Anyone Relay (Anon):\n")
|
||||
fmt.Printf(" sudo systemctl status anon\n")
|
||||
fmt.Printf(" sudo tail -f /home/debros/.debros/logs/anon/notices.log\n")
|
||||
fmt.Printf(" Proxy endpoint: POST http://localhost:6001/v1/proxy/anon\n\n")
|
||||
if httpsEnabled && domainName != "" {
|
||||
fmt.Printf(" Proxy endpoint: POST https://%s/v1/proxy/anon\n\n", domainName)
|
||||
} else {
|
||||
fmt.Printf(" Proxy endpoint: POST http://localhost:6001/v1/proxy/anon\n\n")
|
||||
}
|
||||
}
|
||||
|
||||
// extractIPFromMultiaddr extracts the IP address from a multiaddr string
|
||||
@ -334,6 +378,86 @@ func isValidHostPort(s string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// isPortAvailable checks if a port is available for binding
|
||||
func isPortAvailable(port int) bool {
|
||||
ln, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
ln.Close()
|
||||
return true
|
||||
}
|
||||
|
||||
// checkPorts80And443 checks if ports 80 and 443 are available
|
||||
func checkPorts80And443() (bool, string) {
|
||||
port80Available := isPortAvailable(80)
|
||||
port443Available := isPortAvailable(443)
|
||||
|
||||
if !port80Available || !port443Available {
|
||||
var issues []string
|
||||
if !port80Available {
|
||||
issues = append(issues, "port 80")
|
||||
}
|
||||
if !port443Available {
|
||||
issues = append(issues, "port 443")
|
||||
}
|
||||
return false, strings.Join(issues, " and ")
|
||||
}
|
||||
|
||||
return true, ""
|
||||
}
|
||||
|
||||
// isValidDomain validates a domain name format
|
||||
func isValidDomain(domain string) bool {
|
||||
domain = strings.TrimSpace(domain)
|
||||
if domain == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
// Basic validation: domain should contain at least one dot
|
||||
// and not start/end with dot or hyphen
|
||||
if !strings.Contains(domain, ".") {
|
||||
return false
|
||||
}
|
||||
|
||||
if strings.HasPrefix(domain, ".") || strings.HasSuffix(domain, ".") {
|
||||
return false
|
||||
}
|
||||
|
||||
if strings.HasPrefix(domain, "-") || strings.HasSuffix(domain, "-") {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check for valid characters (letters, numbers, dots, hyphens)
|
||||
for _, char := range domain {
|
||||
if !((char >= 'a' && char <= 'z') ||
|
||||
(char >= 'A' && char <= 'Z') ||
|
||||
(char >= '0' && char <= '9') ||
|
||||
char == '.' ||
|
||||
char == '-') {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// verifyDNSResolution verifies that a domain resolves to the VPS IP
|
||||
func verifyDNSResolution(domain, expectedIP string) bool {
|
||||
ips, err := net.LookupIP(domain)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, ip := range ips {
|
||||
if ip.To4() != nil && ip.String() == expectedIP {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func setupDebrosUser() {
|
||||
fmt.Printf("👤 Setting up 'debros' user...\n")
|
||||
|
||||
@ -973,9 +1097,100 @@ func generateConfigsInteractive(force bool) {
|
||||
}
|
||||
|
||||
if !gatewayExists || force {
|
||||
// Prompt for domain and HTTPS configuration
|
||||
var domain string
|
||||
var enableHTTPS bool
|
||||
var tlsCacheDir string
|
||||
|
||||
fmt.Printf("\n🌐 Domain and HTTPS Configuration\n")
|
||||
fmt.Printf("Would you like to configure HTTPS with a domain name? (yes/no) [default: no]: ")
|
||||
response, _ := reader.ReadString('\n')
|
||||
response = strings.ToLower(strings.TrimSpace(response))
|
||||
|
||||
if response == "yes" || response == "y" {
|
||||
// Check if ports 80 and 443 are available
|
||||
portsAvailable, portIssues := checkPorts80And443()
|
||||
if !portsAvailable {
|
||||
fmt.Fprintf(os.Stderr, "\n⚠️ Cannot enable HTTPS: %s is already in use\n", portIssues)
|
||||
fmt.Fprintf(os.Stderr, " You will need to configure HTTPS manually if you want to use a domain.\n")
|
||||
fmt.Fprintf(os.Stderr, " Continuing without HTTPS configuration...\n\n")
|
||||
enableHTTPS = false
|
||||
} else {
|
||||
enableHTTPS = true
|
||||
|
||||
// Prompt for domain name
|
||||
for {
|
||||
fmt.Printf("\nEnter your domain name (e.g., example.com): ")
|
||||
domainInput, _ := reader.ReadString('\n')
|
||||
domain = strings.TrimSpace(domainInput)
|
||||
|
||||
if domain == "" {
|
||||
fmt.Printf(" Domain name cannot be empty. Skipping HTTPS configuration.\n")
|
||||
enableHTTPS = false
|
||||
break
|
||||
}
|
||||
|
||||
if !isValidDomain(domain) {
|
||||
fmt.Printf(" ❌ Invalid domain format. Please enter a valid domain name.\n")
|
||||
continue
|
||||
}
|
||||
|
||||
// Verify DNS is configured
|
||||
fmt.Printf("\n Verifying DNS configuration...\n")
|
||||
fmt.Printf(" Please ensure your domain %s points to this server's IP (%s)\n", domain, vpsIP)
|
||||
fmt.Printf(" Have you configured the DNS record? (yes/no): ")
|
||||
dnsResponse, _ := reader.ReadString('\n')
|
||||
dnsResponse = strings.ToLower(strings.TrimSpace(dnsResponse))
|
||||
|
||||
if dnsResponse == "yes" || dnsResponse == "y" {
|
||||
// Try to verify DNS resolution
|
||||
fmt.Printf(" Checking DNS resolution...\n")
|
||||
if verifyDNSResolution(domain, vpsIP) {
|
||||
fmt.Printf(" ✓ DNS is correctly configured\n")
|
||||
break
|
||||
} else {
|
||||
fmt.Printf(" ⚠️ DNS does not resolve to this server's IP (%s)\n", vpsIP)
|
||||
fmt.Printf(" DNS may still be propagating. Continue anyway? (yes/no): ")
|
||||
continueResponse, _ := reader.ReadString('\n')
|
||||
continueResponse = strings.ToLower(strings.TrimSpace(continueResponse))
|
||||
if continueResponse == "yes" || continueResponse == "y" {
|
||||
fmt.Printf(" Continuing with domain configuration (DNS may need time to propagate)\n")
|
||||
break
|
||||
}
|
||||
// User chose not to continue, ask for domain again
|
||||
fmt.Printf(" Please configure DNS and try again, or press Enter to skip HTTPS\n")
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
fmt.Printf(" Please configure DNS first. Type 'skip' to skip HTTPS configuration: ")
|
||||
skipResponse, _ := reader.ReadString('\n')
|
||||
skipResponse = strings.ToLower(strings.TrimSpace(skipResponse))
|
||||
if skipResponse == "skip" {
|
||||
enableHTTPS = false
|
||||
domain = ""
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Set TLS cache directory if HTTPS is enabled
|
||||
if enableHTTPS && domain != "" {
|
||||
tlsCacheDir = "/home/debros/.debros/tls-cache"
|
||||
// Create TLS cache directory
|
||||
if err := os.MkdirAll(tlsCacheDir, 0755); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "⚠️ Failed to create TLS cache directory: %v\n", err)
|
||||
} else {
|
||||
exec.Command("chown", "-R", "debros:debros", tlsCacheDir).Run()
|
||||
fmt.Printf(" ✓ TLS cache directory created: %s\n", tlsCacheDir)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Gateway config should include bootstrap peers if this is a regular node
|
||||
// (bootstrap nodes don't need bootstrap peers since they are the bootstrap)
|
||||
gatewayConfig := generateGatewayConfigDirect(bootstrapPeers)
|
||||
gatewayConfig := generateGatewayConfigDirect(bootstrapPeers, enableHTTPS, domain, tlsCacheDir)
|
||||
if err := os.WriteFile(gatewayPath, []byte(gatewayConfig), 0644); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "❌ Failed to write gateway config: %v\n", err)
|
||||
os.Exit(1)
|
||||
@ -1107,7 +1322,7 @@ logging:
|
||||
}
|
||||
|
||||
// generateGatewayConfigDirect generates gateway config directly
|
||||
func generateGatewayConfigDirect(bootstrapPeers string) string {
|
||||
func generateGatewayConfigDirect(bootstrapPeers string, enableHTTPS bool, domain, tlsCacheDir string) string {
|
||||
var peers []string
|
||||
if bootstrapPeers != "" {
|
||||
for _, p := range strings.Split(bootstrapPeers, ",") {
|
||||
@ -1127,11 +1342,23 @@ func generateGatewayConfigDirect(bootstrapPeers string) string {
|
||||
}
|
||||
}
|
||||
|
||||
var httpsYAML strings.Builder
|
||||
if enableHTTPS && domain != "" {
|
||||
fmt.Fprintf(&httpsYAML, "enable_https: true\n")
|
||||
fmt.Fprintf(&httpsYAML, "domain_name: \"%s\"\n", domain)
|
||||
if tlsCacheDir != "" {
|
||||
fmt.Fprintf(&httpsYAML, "tls_cache_dir: \"%s\"\n", tlsCacheDir)
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintf(&httpsYAML, "enable_https: false\n")
|
||||
}
|
||||
|
||||
return fmt.Sprintf(`listen_addr: ":6001"
|
||||
client_namespace: "default"
|
||||
rqlite_dsn: ""
|
||||
%s
|
||||
`, peersYAML.String())
|
||||
%s
|
||||
`, peersYAML.String(), httpsYAML.String())
|
||||
}
|
||||
|
||||
func createSystemdServices() {
|
||||
@ -1191,6 +1418,10 @@ StandardOutput=journal
|
||||
StandardError=journal
|
||||
SyslogIdentifier=debros-gateway
|
||||
|
||||
# Allow binding to privileged ports (80, 443) for HTTPS/ACME
|
||||
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
|
||||
|
||||
NoNewPrivileges=yes
|
||||
PrivateTmp=yes
|
||||
ProtectSystem=strict
|
||||
|
||||
@ -70,6 +70,21 @@ func (c *Config) ValidateConfig() []error {
|
||||
}
|
||||
}
|
||||
|
||||
// Validate HTTPS configuration
|
||||
if c.EnableHTTPS {
|
||||
if c.DomainName == "" {
|
||||
errs = append(errs, fmt.Errorf("gateway.domain_name: must be set when enable_https is true"))
|
||||
} else {
|
||||
// Basic domain validation
|
||||
if !isValidDomainName(c.DomainName) {
|
||||
errs = append(errs, fmt.Errorf("gateway.domain_name: invalid domain format"))
|
||||
}
|
||||
}
|
||||
if c.TLSCacheDir == "" {
|
||||
errs = append(errs, fmt.Errorf("gateway.tls_cache_dir: must be set when enable_https is true"))
|
||||
}
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
@ -135,3 +150,38 @@ func extractTCPPort(multiaddrStr string) string {
|
||||
|
||||
return portPart[:firstSlashIndex]
|
||||
}
|
||||
|
||||
// isValidDomainName validates a domain name format
|
||||
func isValidDomainName(domain string) bool {
|
||||
domain = strings.TrimSpace(domain)
|
||||
if domain == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
// Basic validation: domain should contain at least one dot
|
||||
// and not start/end with dot or hyphen
|
||||
if !strings.Contains(domain, ".") {
|
||||
return false
|
||||
}
|
||||
|
||||
if strings.HasPrefix(domain, ".") || strings.HasSuffix(domain, ".") {
|
||||
return false
|
||||
}
|
||||
|
||||
if strings.HasPrefix(domain, "-") || strings.HasSuffix(domain, "-") {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check for valid characters (letters, numbers, dots, hyphens)
|
||||
for _, char := range domain {
|
||||
if !((char >= 'a' && char <= 'z') ||
|
||||
(char >= 'A' && char <= 'Z') ||
|
||||
(char >= '0' && char <= '9') ||
|
||||
char == '.' ||
|
||||
char == '-') {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@ -26,6 +26,11 @@ type Config struct {
|
||||
// Optional DSN for rqlite database/sql driver, e.g. "http://localhost:4001"
|
||||
// If empty, defaults to "http://localhost:4001".
|
||||
RQLiteDSN string
|
||||
|
||||
// HTTPS configuration
|
||||
EnableHTTPS bool // Enable HTTPS with ACME (Let's Encrypt)
|
||||
DomainName string // Domain name for HTTPS certificate
|
||||
TLSCacheDir string // Directory to cache TLS certificates (default: ~/.debros/tls-cache)
|
||||
}
|
||||
|
||||
type Gateway struct {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user