mirror of
https://github.com/DeBrosOfficial/network.git
synced 2025-12-13 00:58:50 +00:00
- 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.
188 lines
5.0 KiB
Go
188 lines
5.0 KiB
Go
package gateway
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/multiformats/go-multiaddr"
|
|
)
|
|
|
|
// ValidateConfig performs comprehensive validation of gateway configuration.
|
|
// It returns aggregated errors, allowing the caller to print all issues at once.
|
|
func (c *Config) ValidateConfig() []error {
|
|
var errs []error
|
|
|
|
// Validate listen_addr
|
|
if c.ListenAddr == "" {
|
|
errs = append(errs, fmt.Errorf("gateway.listen_addr: must not be empty"))
|
|
} else {
|
|
if err := validateListenAddr(c.ListenAddr); err != nil {
|
|
errs = append(errs, fmt.Errorf("gateway.listen_addr: %v", err))
|
|
}
|
|
}
|
|
|
|
// Validate client_namespace
|
|
if c.ClientNamespace == "" {
|
|
errs = append(errs, fmt.Errorf("gateway.client_namespace: must not be empty"))
|
|
}
|
|
|
|
// Validate bootstrap_peers if provided
|
|
seenPeers := make(map[string]bool)
|
|
for i, peer := range c.BootstrapPeers {
|
|
path := fmt.Sprintf("gateway.bootstrap_peers[%d]", i)
|
|
|
|
_, err := multiaddr.NewMultiaddr(peer)
|
|
if err != nil {
|
|
errs = append(errs, fmt.Errorf("%s: invalid multiaddr: %v; expected /ip{4,6}/.../tcp/<port>/p2p/<peerID>", path, err))
|
|
continue
|
|
}
|
|
|
|
// Check for /p2p/ component
|
|
if !strings.Contains(peer, "/p2p/") {
|
|
errs = append(errs, fmt.Errorf("%s: missing /p2p/<peerID> component; expected /ip{4,6}/.../tcp/<port>/p2p/<peerID>", path))
|
|
}
|
|
|
|
// Extract TCP port by parsing the multiaddr string directly
|
|
tcpPortStr := extractTCPPort(peer)
|
|
if tcpPortStr == "" {
|
|
errs = append(errs, fmt.Errorf("%s: missing /tcp/<port> component; expected /ip{4,6}/.../tcp/<port>/p2p/<peerID>", path))
|
|
continue
|
|
}
|
|
|
|
tcpPort, err := strconv.Atoi(tcpPortStr)
|
|
if err != nil || tcpPort < 1 || tcpPort > 65535 {
|
|
errs = append(errs, fmt.Errorf("%s: invalid TCP port %s; port must be between 1 and 65535", path, tcpPortStr))
|
|
}
|
|
|
|
if seenPeers[peer] {
|
|
errs = append(errs, fmt.Errorf("%s: duplicate bootstrap peer", path))
|
|
}
|
|
seenPeers[peer] = true
|
|
}
|
|
|
|
// Validate rqlite_dsn if provided
|
|
if c.RQLiteDSN != "" {
|
|
if err := validateRQLiteDSN(c.RQLiteDSN); err != nil {
|
|
errs = append(errs, fmt.Errorf("gateway.rqlite_dsn: %v", err))
|
|
}
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// validateListenAddr checks if a listen address is valid (host:port format)
|
|
func validateListenAddr(addr string) error {
|
|
host, port, err := net.SplitHostPort(addr)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid format; expected host:port")
|
|
}
|
|
|
|
portNum, err := strconv.Atoi(port)
|
|
if err != nil || portNum < 1 || portNum > 65535 {
|
|
return fmt.Errorf("port must be a number between 1 and 65535; got %q", port)
|
|
}
|
|
|
|
// Allow empty host (for wildcard binds like :6001)
|
|
if host != "" && net.ParseIP(host) == nil {
|
|
// Try as hostname (may fail later during bind, but basic validation)
|
|
_, err := net.LookupHost(host)
|
|
if err != nil {
|
|
// Not an IP; assume it's a valid hostname for now
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// validateRQLiteDSN checks if an RQLite DSN is a valid URL
|
|
func validateRQLiteDSN(dsn string) error {
|
|
u, err := url.Parse(dsn)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid URL: %v", err)
|
|
}
|
|
|
|
if u.Scheme != "http" && u.Scheme != "https" {
|
|
return fmt.Errorf("scheme must be http or https; got %q", u.Scheme)
|
|
}
|
|
|
|
if u.Host == "" {
|
|
return fmt.Errorf("host must not be empty")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// extractTCPPort extracts the TCP port from a multiaddr string.
|
|
// It assumes the multiaddr is in the format /ip{4,6}/.../tcp/<port>/p2p/<peerID>.
|
|
func extractTCPPort(multiaddrStr string) string {
|
|
// Find the last /tcp/ component
|
|
lastTCPIndex := strings.LastIndex(multiaddrStr, "/tcp/")
|
|
if lastTCPIndex == -1 {
|
|
return ""
|
|
}
|
|
|
|
// Extract the port part after /tcp/
|
|
portPart := multiaddrStr[lastTCPIndex+len("/tcp/"):]
|
|
|
|
// Find the first / component after the port part
|
|
firstSlashIndex := strings.Index(portPart, "/")
|
|
if firstSlashIndex == -1 {
|
|
return portPart
|
|
}
|
|
|
|
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
|
|
}
|