mirror of
https://github.com/DeBrosOfficial/network.git
synced 2025-12-11 07:58:50 +00:00
- Added automatic setup for IPFS and IPFS Cluster during the network setup process. - Implemented initialization of IPFS repositories and Cluster configurations for each node. - Enhanced Makefile to support starting IPFS and Cluster daemons with improved logging. - Introduced a new documentation guide for IPFS Cluster setup, detailing configuration and verification steps. - Updated changelog to reflect the new features and improvements.
265 lines
7.6 KiB
Go
265 lines
7.6 KiB
Go
package gateway
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/DeBrosOfficial/network/pkg/anyoneproxy"
|
|
"github.com/DeBrosOfficial/network/pkg/logging"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// anonProxyRequest represents the JSON payload for proxy requests
|
|
type anonProxyRequest struct {
|
|
URL string `json:"url"`
|
|
Method string `json:"method"`
|
|
Headers map[string]string `json:"headers,omitempty"`
|
|
Body string `json:"body,omitempty"`
|
|
}
|
|
|
|
// anonProxyResponse represents the JSON response from proxy requests
|
|
type anonProxyResponse struct {
|
|
StatusCode int `json:"status_code"`
|
|
Headers map[string]string `json:"headers"`
|
|
Body string `json:"body"`
|
|
Error string `json:"error,omitempty"`
|
|
}
|
|
|
|
const (
|
|
maxProxyRequestSize = 10 * 1024 * 1024 // 10MB
|
|
maxProxyTimeout = 60 * time.Second
|
|
)
|
|
|
|
// anonProxyHandler handles proxied HTTP requests through the Anyone network
|
|
func (g *Gateway) anonProxyHandler(w http.ResponseWriter, r *http.Request) {
|
|
// Only accept POST requests
|
|
if r.Method != http.MethodPost {
|
|
writeError(w, http.StatusMethodNotAllowed, "only POST method is allowed")
|
|
return
|
|
}
|
|
|
|
// Limit request body size
|
|
r.Body = http.MaxBytesReader(w, r.Body, maxProxyRequestSize)
|
|
|
|
// Parse request payload
|
|
var req anonProxyRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeError(w, http.StatusBadRequest, fmt.Sprintf("invalid JSON payload: %v", err))
|
|
return
|
|
}
|
|
|
|
// Validate URL
|
|
targetURL, err := url.Parse(req.URL)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, fmt.Sprintf("invalid URL: %v", err))
|
|
return
|
|
}
|
|
|
|
// Only allow HTTPS for external requests
|
|
if targetURL.Scheme != "https" && targetURL.Scheme != "http" {
|
|
writeError(w, http.StatusBadRequest, "only http/https schemes are allowed")
|
|
return
|
|
}
|
|
|
|
// Block requests to private/local addresses
|
|
if isPrivateOrLocalHost(targetURL.Host) {
|
|
writeError(w, http.StatusForbidden, "requests to private/local addresses are not allowed")
|
|
return
|
|
}
|
|
|
|
// Validate HTTP method
|
|
method := strings.ToUpper(req.Method)
|
|
if method == "" {
|
|
method = "GET"
|
|
}
|
|
allowedMethods := map[string]bool{
|
|
"GET": true,
|
|
"POST": true,
|
|
"PUT": true,
|
|
"DELETE": true,
|
|
"PATCH": true,
|
|
"HEAD": true,
|
|
}
|
|
if !allowedMethods[method] {
|
|
writeError(w, http.StatusBadRequest, fmt.Sprintf("method %s not allowed", method))
|
|
return
|
|
}
|
|
|
|
// Check if Anyone proxy is running (after all validation)
|
|
if !anyoneproxy.Running() {
|
|
g.logger.ComponentWarn(logging.ComponentGeneral, "Anyone proxy not available",
|
|
zap.String("socks_addr", anyoneproxy.Address()))
|
|
writeJSON(w, http.StatusServiceUnavailable, anonProxyResponse{
|
|
Error: fmt.Sprintf("Anyone proxy not available at %s", anyoneproxy.Address()),
|
|
})
|
|
return
|
|
}
|
|
|
|
// Create HTTP client with Anyone proxy
|
|
client := anyoneproxy.NewHTTPClient()
|
|
client.Timeout = maxProxyTimeout
|
|
|
|
// Create the proxied request
|
|
var bodyReader io.Reader
|
|
if req.Body != "" {
|
|
bodyReader = strings.NewReader(req.Body)
|
|
}
|
|
|
|
proxyReq, err := http.NewRequestWithContext(r.Context(), method, req.URL, bodyReader)
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, fmt.Sprintf("failed to create request: %v", err))
|
|
return
|
|
}
|
|
|
|
// Copy headers, excluding hop-by-hop headers
|
|
for key, value := range req.Headers {
|
|
if !isHopByHopHeader(key) {
|
|
proxyReq.Header.Set(key, value)
|
|
}
|
|
}
|
|
|
|
// Set default User-Agent if not provided
|
|
if proxyReq.Header.Get("User-Agent") == "" {
|
|
proxyReq.Header.Set("User-Agent", "DeBros-Gateway/1.0")
|
|
}
|
|
|
|
// Log the proxy request
|
|
g.logger.ComponentInfo(logging.ComponentGeneral, "proxying request through Anyone",
|
|
zap.String("method", method),
|
|
zap.String("url", req.URL),
|
|
zap.String("socks_addr", anyoneproxy.Address()))
|
|
|
|
// Execute the request
|
|
start := time.Now()
|
|
resp, err := client.Do(proxyReq)
|
|
duration := time.Since(start)
|
|
|
|
if err != nil {
|
|
g.logger.ComponentError(logging.ComponentGeneral, "proxy request failed",
|
|
zap.Error(err),
|
|
zap.String("url", req.URL),
|
|
zap.Duration("duration", duration))
|
|
writeJSON(w, http.StatusBadGateway, anonProxyResponse{
|
|
Error: fmt.Sprintf("proxy request failed: %v", err),
|
|
})
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
// Read response body
|
|
respBody, err := io.ReadAll(io.LimitReader(resp.Body, maxProxyRequestSize))
|
|
if err != nil {
|
|
g.logger.ComponentError(logging.ComponentGeneral, "failed to read proxy response",
|
|
zap.Error(err))
|
|
writeJSON(w, http.StatusBadGateway, anonProxyResponse{
|
|
Error: fmt.Sprintf("failed to read response: %v", err),
|
|
})
|
|
return
|
|
}
|
|
|
|
// Extract response headers (excluding hop-by-hop)
|
|
respHeaders := make(map[string]string)
|
|
for key, values := range resp.Header {
|
|
if !isHopByHopHeader(key) && len(values) > 0 {
|
|
respHeaders[key] = values[0]
|
|
}
|
|
}
|
|
|
|
g.logger.ComponentInfo(logging.ComponentGeneral, "proxy request completed",
|
|
zap.String("url", req.URL),
|
|
zap.Int("status", resp.StatusCode),
|
|
zap.Int("bytes", len(respBody)),
|
|
zap.Duration("duration", duration))
|
|
|
|
// Base64-encode the body to safely handle binary data in JSON
|
|
// This prevents corruption when binary data is converted to a string
|
|
bodyBase64 := base64.StdEncoding.EncodeToString(respBody)
|
|
|
|
// Return the proxied response
|
|
writeJSON(w, http.StatusOK, anonProxyResponse{
|
|
StatusCode: resp.StatusCode,
|
|
Headers: respHeaders,
|
|
Body: bodyBase64,
|
|
})
|
|
}
|
|
|
|
// isHopByHopHeader returns true for HTTP hop-by-hop headers that should not be forwarded
|
|
func isHopByHopHeader(header string) bool {
|
|
hopByHop := map[string]bool{
|
|
"Connection": true,
|
|
"Keep-Alive": true,
|
|
"Proxy-Authenticate": true,
|
|
"Proxy-Authorization": true,
|
|
"Te": true,
|
|
"Trailers": true,
|
|
"Transfer-Encoding": true,
|
|
"Upgrade": true,
|
|
}
|
|
return hopByHop[http.CanonicalHeaderKey(header)]
|
|
}
|
|
|
|
// isPrivateOrLocalHost checks if a host is private, local, or loopback
|
|
func isPrivateOrLocalHost(host string) bool {
|
|
// Strip port if present, handling IPv6 addresses properly
|
|
// IPv6 addresses in URLs are bracketed: [::1]:8080
|
|
if strings.HasPrefix(host, "[") {
|
|
// IPv6 address with brackets
|
|
if idx := strings.LastIndex(host, "]"); idx != -1 {
|
|
if idx+1 < len(host) && host[idx+1] == ':' {
|
|
// Port present, strip it
|
|
host = host[1:idx] // Remove brackets and port
|
|
} else {
|
|
// No port, just remove brackets
|
|
host = host[1:idx]
|
|
}
|
|
}
|
|
} else {
|
|
// IPv4 or hostname, check for port
|
|
if idx := strings.LastIndex(host, ":"); idx != -1 {
|
|
// Check if it's an IPv6 address without brackets (contains multiple colons)
|
|
colonCount := strings.Count(host, ":")
|
|
if colonCount == 1 {
|
|
// Single colon, likely IPv4 with port
|
|
host = host[:idx]
|
|
}
|
|
// If multiple colons, it's IPv6 without brackets and no port
|
|
// Leave host as-is
|
|
}
|
|
}
|
|
|
|
// Check for localhost variants
|
|
if host == "localhost" || host == "localhost" || host == "::1" {
|
|
return true
|
|
}
|
|
|
|
// Check common private ranges (basic check)
|
|
if strings.HasPrefix(host, "10.") ||
|
|
strings.HasPrefix(host, "192.168.") ||
|
|
strings.HasPrefix(host, "172.16.") ||
|
|
strings.HasPrefix(host, "172.17.") ||
|
|
strings.HasPrefix(host, "172.18.") ||
|
|
strings.HasPrefix(host, "172.19.") ||
|
|
strings.HasPrefix(host, "172.20.") ||
|
|
strings.HasPrefix(host, "172.21.") ||
|
|
strings.HasPrefix(host, "172.22.") ||
|
|
strings.HasPrefix(host, "172.23.") ||
|
|
strings.HasPrefix(host, "172.24.") ||
|
|
strings.HasPrefix(host, "172.25.") ||
|
|
strings.HasPrefix(host, "172.26.") ||
|
|
strings.HasPrefix(host, "172.27.") ||
|
|
strings.HasPrefix(host, "172.28.") ||
|
|
strings.HasPrefix(host, "172.29.") ||
|
|
strings.HasPrefix(host, "172.30.") ||
|
|
strings.HasPrefix(host, "172.31.") {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|