Enhance configuration validation for nodes and gateways, implementing strict YAML decoding to reject unknown fields. Added comprehensive validation checks for node types, listen addresses, database settings, and discovery parameters. Updated README with configuration validation details and examples.

This commit is contained in:
anonpenguin23 2025-10-24 10:01:53 +03:00
parent 279c03df82
commit 60affaec5c
No known key found for this signature in database
GPG Key ID: 1CBB1FE35AFBEE30
4 changed files with 182 additions and 10 deletions

View File

@ -8,14 +8,21 @@ The format is based on [Keep a Changelog][keepachangelog] and adheres to [Semant
### Added ### Added
- Added validation for yaml files
- Added authenticaiton command on cli
### Changed ### Changed
- Updated readme
### Deprecated ### Deprecated
### Removed ### Removed
### Fixed ### Fixed
- Regular nodes rqlite not starting
## [0.51.2] - 2025-09-26 ## [0.51.2] - 2025-09-26
### Added ### Added

View File

@ -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 .PHONY: build clean test run-node run-node2 run-node3 run-example deps tidy fmt vet lint clear-ports
VERSION := 0.51.3-beta VERSION := 0.51.4-beta
COMMIT ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown) COMMIT ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown)
DATE ?= $(shell date -u +%Y-%m-%dT%H:%M:%SZ) DATE ?= $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
LDFLAGS := -X 'main.version=$(VERSION)' -X 'main.commit=$(COMMIT)' -X 'main.date=$(DATE)' LDFLAGS := -X 'main.version=$(VERSION)' -X 'main.commit=$(COMMIT)' -X 'main.date=$(DATE)'

View File

@ -76,6 +76,8 @@ func main() {
handleConnect(args[0]) handleConnect(args[0])
case "peer-id": case "peer-id":
handlePeerID() handlePeerID()
case "auth":
handleAuth(args)
case "help", "--help", "-h": case "help", "--help", "-h":
showHelp() showHelp()
@ -289,6 +291,145 @@ func handlePubSub(args []string) {
} }
} }
func handleAuth(args []string) {
if len(args) == 0 {
showAuthHelp()
return
}
subcommand := args[0]
switch subcommand {
case "login":
handleAuthLogin()
case "logout":
handleAuthLogout()
case "whoami":
handleAuthWhoami()
case "status":
handleAuthStatus()
default:
fmt.Fprintf(os.Stderr, "Unknown auth command: %s\n", subcommand)
showAuthHelp()
os.Exit(1)
}
}
func handleAuthLogin() {
gatewayURL := auth.GetDefaultGatewayURL()
fmt.Printf("🔐 Authenticating with gateway at: %s\n", gatewayURL)
// Use the wallet authentication flow
creds, err := auth.PerformWalletAuthentication(gatewayURL)
if err != nil {
fmt.Fprintf(os.Stderr, "❌ Authentication failed: %v\n", err)
os.Exit(1)
}
// Save credentials to file
if err := auth.SaveCredentialsForDefaultGateway(creds); err != nil {
fmt.Fprintf(os.Stderr, "❌ Failed to save credentials: %v\n", err)
os.Exit(1)
}
credsPath, _ := auth.GetCredentialsPath()
fmt.Printf("✅ Authentication successful!\n")
fmt.Printf("📁 Credentials saved to: %s\n", credsPath)
fmt.Printf("🎯 Wallet: %s\n", creds.Wallet)
fmt.Printf("🏢 Namespace: %s\n", creds.Namespace)
}
func handleAuthLogout() {
if err := auth.ClearAllCredentials(); err != nil {
fmt.Fprintf(os.Stderr, "❌ Failed to clear credentials: %v\n", err)
os.Exit(1)
}
fmt.Println("✅ Logged out successfully - all credentials have been cleared")
}
func handleAuthWhoami() {
store, err := auth.LoadCredentials()
if err != nil {
fmt.Fprintf(os.Stderr, "❌ Failed to load credentials: %v\n", err)
os.Exit(1)
}
gatewayURL := auth.GetDefaultGatewayURL()
creds, exists := store.GetCredentialsForGateway(gatewayURL)
if !exists || !creds.IsValid() {
fmt.Println("❌ Not authenticated - run 'network-cli auth login' to authenticate")
os.Exit(1)
}
fmt.Println("✅ Authenticated")
fmt.Printf(" Wallet: %s\n", creds.Wallet)
fmt.Printf(" Namespace: %s\n", creds.Namespace)
fmt.Printf(" Issued At: %s\n", creds.IssuedAt.Format("2006-01-02 15:04:05"))
if !creds.ExpiresAt.IsZero() {
fmt.Printf(" Expires At: %s\n", creds.ExpiresAt.Format("2006-01-02 15:04:05"))
}
if !creds.LastUsedAt.IsZero() {
fmt.Printf(" Last Used: %s\n", creds.LastUsedAt.Format("2006-01-02 15:04:05"))
}
if creds.Plan != "" {
fmt.Printf(" Plan: %s\n", creds.Plan)
}
}
func handleAuthStatus() {
store, err := auth.LoadCredentials()
if err != nil {
fmt.Fprintf(os.Stderr, "❌ Failed to load credentials: %v\n", err)
os.Exit(1)
}
gatewayURL := auth.GetDefaultGatewayURL()
creds, exists := store.GetCredentialsForGateway(gatewayURL)
fmt.Println("🔐 Authentication Status")
fmt.Printf(" Gateway URL: %s\n", gatewayURL)
if !exists || creds == nil {
fmt.Println(" Status: ❌ Not authenticated")
return
}
if !creds.IsValid() {
fmt.Println(" Status: ⚠️ Credentials expired")
if !creds.ExpiresAt.IsZero() {
fmt.Printf(" Expired At: %s\n", creds.ExpiresAt.Format("2006-01-02 15:04:05"))
}
return
}
fmt.Println(" Status: ✅ Authenticated")
fmt.Printf(" Wallet: %s\n", creds.Wallet)
fmt.Printf(" Namespace: %s\n", creds.Namespace)
if !creds.ExpiresAt.IsZero() {
fmt.Printf(" Expires: %s\n", creds.ExpiresAt.Format("2006-01-02 15:04:05"))
}
if !creds.LastUsedAt.IsZero() {
fmt.Printf(" Last Used: %s\n", creds.LastUsedAt.Format("2006-01-02 15:04:05"))
}
}
func showAuthHelp() {
fmt.Printf("🔐 Authentication Commands\n\n")
fmt.Printf("Usage: network-cli auth <subcommand>\n\n")
fmt.Printf("Subcommands:\n")
fmt.Printf(" login - Authenticate with wallet\n")
fmt.Printf(" logout - Clear stored credentials\n")
fmt.Printf(" whoami - Show current authentication status\n")
fmt.Printf(" status - Show detailed authentication info\n\n")
fmt.Printf("Examples:\n")
fmt.Printf(" network-cli auth login\n")
fmt.Printf(" network-cli auth whoami\n")
fmt.Printf(" network-cli auth status\n")
fmt.Printf(" network-cli auth logout\n\n")
fmt.Printf("Environment Variables:\n")
fmt.Printf(" DEBROS_GATEWAY_URL - Gateway URL (default: http://localhost:6001)\n")
}
func ensureAuthenticated() *auth.Credentials { func ensureAuthenticated() *auth.Credentials {
gatewayURL := auth.GetDefaultGatewayURL() gatewayURL := auth.GetDefaultGatewayURL()
@ -440,6 +581,7 @@ func showHelp() {
fmt.Printf("Usage: network-cli <command> [args...]\n\n") fmt.Printf("Usage: network-cli <command> [args...]\n\n")
fmt.Printf("🔐 Authentication: Commands requiring authentication will automatically prompt for wallet connection.\n\n") fmt.Printf("🔐 Authentication: Commands requiring authentication will automatically prompt for wallet connection.\n\n")
fmt.Printf("Commands:\n") fmt.Printf("Commands:\n")
fmt.Printf(" auth <subcommand> 🔐 Authentication management (login, logout, whoami, status)\n")
fmt.Printf(" health - Check network health\n") fmt.Printf(" health - Check network health\n")
fmt.Printf(" peers - List connected peers\n") fmt.Printf(" peers - List connected peers\n")
fmt.Printf(" status - Show network status\n") fmt.Printf(" status - Show network status\n")
@ -457,10 +599,13 @@ func showHelp() {
fmt.Printf(" -t, --timeout <duration> - Operation timeout (default: 30s)\n") fmt.Printf(" -t, --timeout <duration> - Operation timeout (default: 30s)\n")
fmt.Printf(" --production - Connect to production bootstrap peers\n\n") fmt.Printf(" --production - Connect to production bootstrap peers\n\n")
fmt.Printf("Authentication:\n") fmt.Printf("Authentication:\n")
fmt.Printf(" Use 'network-cli auth login' to authenticate with your wallet\n")
fmt.Printf(" Commands marked with 🔐 will automatically prompt for wallet authentication\n") fmt.Printf(" Commands marked with 🔐 will automatically prompt for wallet authentication\n")
fmt.Printf(" if no valid credentials are found. You can manage multiple wallets and\n") fmt.Printf(" if no valid credentials are found. You can manage multiple wallets and\n")
fmt.Printf(" choose between them during the authentication flow.\n\n") fmt.Printf(" choose between them during the authentication flow.\n\n")
fmt.Printf("Examples:\n") fmt.Printf("Examples:\n")
fmt.Printf(" network-cli auth login\n")
fmt.Printf(" network-cli auth whoami\n")
fmt.Printf(" network-cli health\n") fmt.Printf(" network-cli health\n")
fmt.Printf(" network-cli peer-id\n") fmt.Printf(" network-cli peer-id\n")
fmt.Printf(" network-cli peer-id --format json\n") fmt.Printf(" network-cli peer-id --format json\n")

View File

@ -8,7 +8,6 @@ import (
"strings" "strings"
"github.com/multiformats/go-multiaddr" "github.com/multiformats/go-multiaddr"
manet "github.com/multiformats/go-multiaddr/net"
) )
// ValidateConfig performs comprehensive validation of gateway configuration. // ValidateConfig performs comprehensive validation of gateway configuration.
@ -35,7 +34,7 @@ func (c *Config) ValidateConfig() []error {
for i, peer := range c.BootstrapPeers { for i, peer := range c.BootstrapPeers {
path := fmt.Sprintf("gateway.bootstrap_peers[%d]", i) path := fmt.Sprintf("gateway.bootstrap_peers[%d]", i)
ma, err := multiaddr.NewMultiaddr(peer) _, err := multiaddr.NewMultiaddr(peer)
if err != nil { if err != nil {
errs = append(errs, fmt.Errorf("%s: invalid multiaddr: %v; expected /ip{4,6}/.../tcp/<port>/p2p/<peerID>", path, err)) errs = append(errs, fmt.Errorf("%s: invalid multiaddr: %v; expected /ip{4,6}/.../tcp/<port>/p2p/<peerID>", path, err))
continue continue
@ -46,16 +45,16 @@ func (c *Config) ValidateConfig() []error {
errs = append(errs, fmt.Errorf("%s: missing /p2p/<peerID> component; expected /ip{4,6}/.../tcp/<port>/p2p/<peerID>", path)) errs = append(errs, fmt.Errorf("%s: missing /p2p/<peerID> component; expected /ip{4,6}/.../tcp/<port>/p2p/<peerID>", path))
} }
// Try to extract TCP addr to validate port // Extract TCP port by parsing the multiaddr string directly
tcpAddr, err := manet.ToNetAddr(ma) tcpPortStr := extractTCPPort(peer)
if err != nil { if tcpPortStr == "" {
errs = append(errs, fmt.Errorf("%s: cannot convert to network address: %v", path, err)) errs = append(errs, fmt.Errorf("%s: missing /tcp/<port> component; expected /ip{4,6}/.../tcp/<port>/p2p/<peerID>", path))
continue continue
} }
tcpPort := tcpAddr.(*net.TCPAddr).Port tcpPort, err := strconv.Atoi(tcpPortStr)
if tcpPort < 1 || tcpPort > 65535 { if err != nil || tcpPort < 1 || tcpPort > 65535 {
errs = append(errs, fmt.Errorf("%s: invalid TCP port %d; port must be between 1 and 65535", path, tcpPort)) errs = append(errs, fmt.Errorf("%s: invalid TCP port %s; port must be between 1 and 65535", path, tcpPortStr))
} }
if seenPeers[peer] { if seenPeers[peer] {
@ -115,3 +114,24 @@ func validateRQLiteDSN(dsn string) error {
return nil 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]
}