feat: integrate Anyone Relay (Anon) into the development environment

- Added support for installing and configuring the Anyone Relay (Anon) for anonymous networking in the setup process.
- Updated the Makefile to include the Anon client in the development stack, allowing it to run alongside other services.
- Implemented a new HTTP proxy handler for the Anon service, enabling proxied requests through the Anyone network.
- Enhanced the installation script to manage Anon installation, configuration, and firewall settings.
- Introduced tests for the Anon proxy handler to ensure proper request validation and error handling.
- Updated documentation to reflect the new Anon service and its usage in the development environment.
This commit is contained in:
anonpenguin23 2025-10-30 06:21:32 +02:00
parent ad088bd476
commit fe16d503b5
8 changed files with 856 additions and 8 deletions

View File

@ -77,12 +77,34 @@ run-gateway:
@echo "Generate it with: network-cli config init --type gateway"
go run ./cmd/gateway
# One-command dev: Start bootstrap, node2, node3, and gateway in background
# One-command dev: Start bootstrap, node2, node3, gateway, and anon in background
# Requires: configs already exist in ~/.debros
dev: build
@echo "🚀 Starting development network stack..."
@mkdir -p .dev/pids
@mkdir -p $$HOME/.debros/logs
@echo "Starting Anyone client (anon proxy)..."
@if [ "$$(uname)" = "Darwin" ]; then \
echo " Detected macOS - using npx anyone-client"; \
if command -v npx >/dev/null 2>&1; then \
nohup npx anyone-client > $$HOME/.debros/logs/anon.log 2>&1 & echo $$! > .dev/pids/anon.pid; \
echo " Anyone client started (PID: $$(cat .dev/pids/anon.pid))"; \
else \
echo " ⚠️ npx not found - skipping Anyone client"; \
echo " Install with: npm install -g npm"; \
fi; \
elif [ "$$(uname)" = "Linux" ]; then \
echo " Detected Linux - checking systemctl"; \
if systemctl is-active --quiet anon 2>/dev/null; then \
echo " ✓ Anon service already running"; \
elif command -v systemctl >/dev/null 2>&1; then \
echo " Starting anon service..."; \
sudo systemctl start anon 2>/dev/null || echo " ⚠️ Failed to start anon service"; \
else \
echo " ⚠️ systemctl not found - skipping Anon"; \
fi; \
fi
@sleep 2
@echo "Starting bootstrap node..."
@nohup ./bin/node --config bootstrap.yaml > $$HOME/.debros/logs/bootstrap.log 2>&1 & echo $$! > .dev/pids/bootstrap.pid
@sleep 2
@ -100,12 +122,16 @@ dev: build
@echo "============================================================"
@echo ""
@echo "Processes:"
@if [ -f .dev/pids/anon.pid ]; then \
echo " Anon: PID=$$(cat .dev/pids/anon.pid) (SOCKS: 9050)"; \
fi
@echo " Bootstrap: PID=$$(cat .dev/pids/bootstrap.pid)"
@echo " Node2: PID=$$(cat .dev/pids/node2.pid)"
@echo " Node3: PID=$$(cat .dev/pids/node3.pid)"
@echo " Gateway: PID=$$(cat .dev/pids/gateway.pid)"
@echo ""
@echo "Ports:"
@echo " Anon SOCKS: 9050 (proxy endpoint: POST /v1/proxy/anon)"
@echo " Bootstrap P2P: 4001, HTTP: 5001, Raft: 7001"
@echo " Node2 P2P: 4002, HTTP: 5002, Raft: 7002"
@echo " Node3 P2P: 4003, HTTP: 5003, Raft: 7003"
@ -114,8 +140,13 @@ dev: build
@echo "Press Ctrl+C to stop all processes"
@echo "============================================================"
@echo ""
@trap 'echo "Stopping all processes..."; kill $$(cat .dev/pids/*.pid) 2>/dev/null; rm -f .dev/pids/*.pid; exit 0' INT; \
tail -f $$HOME/.debros/logs/bootstrap.log $$HOME/.debros/logs/node2.log $$HOME/.debros/logs/node3.log $$HOME/.debros/logs/gateway.log
@if [ -f .dev/pids/anon.pid ]; then \
trap 'echo "Stopping all processes..."; kill $$(cat .dev/pids/*.pid) 2>/dev/null; rm -f .dev/pids/*.pid; exit 0' INT; \
tail -f $$HOME/.debros/logs/anon.log $$HOME/.debros/logs/bootstrap.log $$HOME/.debros/logs/node2.log $$HOME/.debros/logs/node3.log $$HOME/.debros/logs/gateway.log; \
else \
trap 'echo "Stopping all processes..."; kill $$(cat .dev/pids/*.pid) 2>/dev/null; rm -f .dev/pids/*.pid; exit 0' INT; \
tail -f $$HOME/.debros/logs/bootstrap.log $$HOME/.debros/logs/node2.log $$HOME/.debros/logs/node3.log $$HOME/.debros/logs/gateway.log; \
fi
# Help
help:

View File

@ -56,11 +56,12 @@ func HandleSetupCommand(args []string) {
fmt.Printf(" 2. Install system dependencies (curl, git, make, build tools)\n")
fmt.Printf(" 3. Install Go 1.21+ (if needed)\n")
fmt.Printf(" 4. Install RQLite database\n")
fmt.Printf(" 5. Create directories (/home/debros/bin, /home/debros/src)\n")
fmt.Printf(" 6. Clone and build DeBros Network\n")
fmt.Printf(" 7. Generate configuration files\n")
fmt.Printf(" 8. Create systemd services (debros-node, debros-gateway)\n")
fmt.Printf(" 9. Start and enable services\n")
fmt.Printf(" 5. Install Anyone Relay (Anon) for anonymous networking\n")
fmt.Printf(" 6. Create directories (/home/debros/bin, /home/debros/src)\n")
fmt.Printf(" 7. Clone and build DeBros Network\n")
fmt.Printf(" 8. Generate configuration files\n")
fmt.Printf(" 9. Create systemd services (debros-node, debros-gateway)\n")
fmt.Printf(" 10. Start and enable services\n")
fmt.Printf(strings.Repeat("=", 70) + "\n\n")
fmt.Printf("Ready to begin setup? (yes/no): ")
@ -83,6 +84,9 @@ func HandleSetupCommand(args []string) {
// Step 4: Install RQLite
installRQLite()
// Step 4.5: Install Anon (Anyone relay)
installAnon()
// Step 5: Setup directories
setupDirectories()
@ -112,6 +116,10 @@ func HandleSetupCommand(args []string) {
fmt.Printf("Verify Installation:\n")
fmt.Printf(" curl http://localhost:6001/health\n")
fmt.Printf(" curl http://localhost:5001/status\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")
}
func detectLinuxDistro() string {
@ -377,6 +385,212 @@ func installRQLite() {
fmt.Printf(" ✓ RQLite installed\n")
}
func installAnon() {
fmt.Printf("🔐 Installing Anyone Relay (Anon)...\n")
// Check if already installed
if _, err := exec.LookPath("anon"); err == nil {
fmt.Printf(" ✓ Anon already installed\n")
configureAnonLogs()
configureFirewallForAnon()
return
}
// Install via APT (official method from docs.anyone.io)
fmt.Printf(" Adding Anyone APT repository...\n")
// Add GPG key
cmd := exec.Command("sh", "-c", "curl -fsSL https://deb.anyone.io/gpg.key | gpg --dearmor -o /usr/share/keyrings/anyone-archive-keyring.gpg")
if err := cmd.Run(); err != nil {
fmt.Fprintf(os.Stderr, "⚠️ Failed to add Anyone GPG key: %v\n", err)
fmt.Fprintf(os.Stderr, " You can manually install with:\n")
fmt.Fprintf(os.Stderr, " curl -fsSL https://deb.anyone.io/gpg.key | sudo gpg --dearmor -o /usr/share/keyrings/anyone-archive-keyring.gpg\n")
fmt.Fprintf(os.Stderr, " echo 'deb [signed-by=/usr/share/keyrings/anyone-archive-keyring.gpg] https://deb.anyone.io/ anyone main' | sudo tee /etc/apt/sources.list.d/anyone.list\n")
fmt.Fprintf(os.Stderr, " sudo apt update && sudo apt install -y anon\n")
return
}
// Add repository
repoLine := "deb [signed-by=/usr/share/keyrings/anyone-archive-keyring.gpg] https://deb.anyone.io/ anyone main"
if err := os.WriteFile("/etc/apt/sources.list.d/anyone.list", []byte(repoLine+"\n"), 0644); err != nil {
fmt.Fprintf(os.Stderr, "⚠️ Failed to add Anyone repository: %v\n", err)
return
}
// Update package list
fmt.Printf(" Updating package list...\n")
exec.Command("apt", "update", "-qq").Run()
// Install anon
fmt.Printf(" Installing Anon package...\n")
cmd = exec.Command("apt", "install", "-y", "anon")
if err := cmd.Run(); err != nil {
fmt.Fprintf(os.Stderr, "⚠️ Anon installation failed: %v\n", err)
return
}
// Verify installation
if _, err := exec.LookPath("anon"); err != nil {
fmt.Fprintf(os.Stderr, "⚠️ Anon installation may have failed\n")
return
}
fmt.Printf(" ✓ Anon installed\n")
// Configure with sensible defaults
configureAnonDefaults()
// Configure logs
configureAnonLogs()
// Configure firewall
configureFirewallForAnon()
// Enable and start service
fmt.Printf(" Enabling Anon service...\n")
exec.Command("systemctl", "enable", "anon").Run()
exec.Command("systemctl", "start", "anon").Run()
if exec.Command("systemctl", "is-active", "--quiet", "anon").Run() == nil {
fmt.Printf(" ✓ Anon service is running\n")
} else {
fmt.Fprintf(os.Stderr, "⚠️ Anon service may not be running. Check: systemctl status anon\n")
}
}
func configureAnonDefaults() {
fmt.Printf(" Configuring Anon with default settings...\n")
hostname := "debros-node"
if h, err := os.Hostname(); err == nil && h != "" {
hostname = strings.Split(h, ".")[0]
}
anonrcPath := "/etc/anon/anonrc"
if _, err := os.Stat(anonrcPath); err == nil {
// Backup existing config
exec.Command("cp", anonrcPath, anonrcPath+".bak").Run()
// Read existing config
data, err := os.ReadFile(anonrcPath)
if err != nil {
return
}
config := string(data)
// Add settings if not present
if !strings.Contains(config, "Nickname") {
config += fmt.Sprintf("\nNickname %s\n", hostname)
}
if !strings.Contains(config, "ControlPort") {
config += "ControlPort 9051\n"
}
if !strings.Contains(config, "SocksPort") {
config += "SocksPort 9050\n"
}
// Write back
os.WriteFile(anonrcPath, []byte(config), 0644)
fmt.Printf(" Nickname: %s\n", hostname)
fmt.Printf(" ORPort: 9001 (default)\n")
fmt.Printf(" ControlPort: 9051\n")
fmt.Printf(" SOCKSPort: 9050\n")
}
}
func configureAnonLogs() {
fmt.Printf(" Configuring Anon logs...\n")
// Create log directory
logDir := "/home/debros/.debros/logs/anon"
if err := os.MkdirAll(logDir, 0755); err != nil {
fmt.Fprintf(os.Stderr, "⚠️ Failed to create log directory: %v\n", err)
return
}
// Change ownership to debian-anon (the user anon runs as)
exec.Command("chown", "-R", "debian-anon:debian-anon", logDir).Run()
// Update anonrc if it exists
anonrcPath := "/etc/anon/anonrc"
if _, err := os.Stat(anonrcPath); err == nil {
// Read current config
data, err := os.ReadFile(anonrcPath)
if err == nil {
config := string(data)
// Replace log file path
newConfig := strings.ReplaceAll(config,
"Log notice file /var/log/anon/notices.log",
"Log notice file /home/debros/.debros/logs/anon/notices.log")
// Write back
if err := os.WriteFile(anonrcPath, []byte(newConfig), 0644); err != nil {
fmt.Fprintf(os.Stderr, "⚠️ Failed to update anonrc: %v\n", err)
} else {
fmt.Printf(" ✓ Anon logs configured to %s\n", logDir)
// Restart anon service if running
if exec.Command("systemctl", "is-active", "--quiet", "anon").Run() == nil {
exec.Command("systemctl", "restart", "anon").Run()
}
}
}
}
}
func configureFirewallForAnon() {
fmt.Printf(" Checking firewall configuration...\n")
// Check for UFW
if _, err := exec.LookPath("ufw"); err == nil {
output, _ := exec.Command("ufw", "status").CombinedOutput()
if strings.Contains(string(output), "Status: active") {
fmt.Printf(" Adding UFW rules for Anon...\n")
exec.Command("ufw", "allow", "9001/tcp", "comment", "Anon ORPort").Run()
exec.Command("ufw", "allow", "9051/tcp", "comment", "Anon ControlPort").Run()
fmt.Printf(" ✓ UFW rules added\n")
return
}
}
// Check for firewalld
if _, err := exec.LookPath("firewall-cmd"); err == nil {
output, _ := exec.Command("firewall-cmd", "--state").CombinedOutput()
if strings.Contains(string(output), "running") {
fmt.Printf(" Adding firewalld rules for Anon...\n")
exec.Command("firewall-cmd", "--permanent", "--add-port=9001/tcp").Run()
exec.Command("firewall-cmd", "--permanent", "--add-port=9051/tcp").Run()
exec.Command("firewall-cmd", "--reload").Run()
fmt.Printf(" ✓ firewalld rules added\n")
return
}
}
// Check for iptables
if _, err := exec.LookPath("iptables"); err == nil {
output, _ := exec.Command("iptables", "-L", "-n").CombinedOutput()
if strings.Contains(string(output), "Chain INPUT") {
fmt.Printf(" Adding iptables rules for Anon...\n")
exec.Command("iptables", "-A", "INPUT", "-p", "tcp", "--dport", "9001", "-j", "ACCEPT", "-m", "comment", "--comment", "Anon ORPort").Run()
exec.Command("iptables", "-A", "INPUT", "-p", "tcp", "--dport", "9051", "-j", "ACCEPT", "-m", "comment", "--comment", "Anon ControlPort").Run()
// Try to save rules
if _, err := exec.LookPath("netfilter-persistent"); err == nil {
exec.Command("netfilter-persistent", "save").Run()
} else if _, err := exec.LookPath("iptables-save"); err == nil {
cmd := exec.Command("sh", "-c", "iptables-save > /etc/iptables/rules.v4")
cmd.Run()
}
fmt.Printf(" ✓ iptables rules added\n")
return
}
}
fmt.Printf(" No active firewall detected\n")
}
func setupDirectories() {
fmt.Printf("📁 Creating directories...\n")

View File

@ -0,0 +1,237 @@
package gateway
import (
"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))
// Return the proxied response
writeJSON(w, http.StatusOK, anonProxyResponse{
StatusCode: resp.StatusCode,
Headers: respHeaders,
Body: string(respBody),
})
}
// 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
if idx := strings.LastIndex(host, ":"); idx != -1 {
host = host[:idx]
}
// Check for localhost variants
if host == "localhost" || host == "127.0.0.1" || 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
}

View File

@ -0,0 +1,189 @@
package gateway
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/DeBrosOfficial/network/pkg/logging"
)
func newTestGateway(t *testing.T) *Gateway {
logger, err := logging.NewColoredLogger(logging.ComponentGeneral, true)
if err != nil {
t.Fatalf("Failed to create logger: %v", err)
}
return &Gateway{logger: logger}
}
func TestAnonProxyHandler_MethodValidation(t *testing.T) {
gw := newTestGateway(t)
// Test GET request (should fail - only POST allowed)
req := httptest.NewRequest(http.MethodGet, "/v1/proxy/anon", nil)
w := httptest.NewRecorder()
gw.anonProxyHandler(w, req)
if w.Code != http.StatusMethodNotAllowed {
t.Errorf("Expected status %d, got %d", http.StatusMethodNotAllowed, w.Code)
}
}
func TestAnonProxyHandler_InvalidJSON(t *testing.T) {
gw := newTestGateway(t)
// Test invalid JSON
req := httptest.NewRequest(http.MethodPost, "/v1/proxy/anon", bytes.NewBufferString("invalid json"))
w := httptest.NewRecorder()
gw.anonProxyHandler(w, req)
if w.Code != http.StatusBadRequest {
t.Errorf("Expected status %d, got %d", http.StatusBadRequest, w.Code)
}
}
func TestAnonProxyHandler_InvalidURL(t *testing.T) {
gw := newTestGateway(t)
tests := []struct {
name string
payload anonProxyRequest
}{
{
name: "invalid URL scheme",
payload: anonProxyRequest{
URL: "ftp://example.com",
Method: "GET",
},
},
{
name: "malformed URL",
payload: anonProxyRequest{
URL: "://invalid",
Method: "GET",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
body, _ := json.Marshal(tt.payload)
req := httptest.NewRequest(http.MethodPost, "/v1/proxy/anon", bytes.NewBuffer(body))
w := httptest.NewRecorder()
gw.anonProxyHandler(w, req)
if w.Code != http.StatusBadRequest {
t.Errorf("Expected status %d, got %d", http.StatusBadRequest, w.Code)
}
})
}
}
func TestAnonProxyHandler_PrivateAddressBlocking(t *testing.T) {
gw := newTestGateway(t)
tests := []struct {
name string
url string
}{
{"localhost", "http://localhost/test"},
{"127.0.0.1", "http://127.0.0.1/test"},
{"private 10.x", "http://10.0.0.1/test"},
{"private 192.168.x", "http://192.168.1.1/test"},
{"private 172.16.x", "http://172.16.0.1/test"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
payload := anonProxyRequest{
URL: tt.url,
Method: "GET",
}
body, _ := json.Marshal(payload)
req := httptest.NewRequest(http.MethodPost, "/v1/proxy/anon", bytes.NewBuffer(body))
w := httptest.NewRecorder()
gw.anonProxyHandler(w, req)
if w.Code != http.StatusForbidden {
t.Errorf("Expected status %d for %s, got %d", http.StatusForbidden, tt.url, w.Code)
}
})
}
}
func TestAnonProxyHandler_InvalidMethod(t *testing.T) {
gw := newTestGateway(t)
payload := anonProxyRequest{
URL: "https://example.com",
Method: "INVALID",
}
body, _ := json.Marshal(payload)
req := httptest.NewRequest(http.MethodPost, "/v1/proxy/anon", bytes.NewBuffer(body))
w := httptest.NewRecorder()
gw.anonProxyHandler(w, req)
if w.Code != http.StatusBadRequest {
t.Errorf("Expected status %d, got %d", http.StatusBadRequest, w.Code)
}
}
func TestIsHopByHopHeader(t *testing.T) {
tests := []struct {
header string
expected bool
}{
{"Connection", true},
{"Keep-Alive", true},
{"Proxy-Authorization", true},
{"Transfer-Encoding", true},
{"Upgrade", true},
{"Content-Type", false},
{"Authorization", false},
{"User-Agent", false},
}
for _, tt := range tests {
t.Run(tt.header, func(t *testing.T) {
result := isHopByHopHeader(tt.header)
if result != tt.expected {
t.Errorf("isHopByHopHeader(%s) = %v, want %v", tt.header, result, tt.expected)
}
})
}
}
func TestIsPrivateOrLocalHost(t *testing.T) {
tests := []struct {
host string
expected bool
}{
{"localhost", true},
{"127.0.0.1", true},
{"::1", true},
{"10.0.0.1", true},
{"192.168.1.1", true},
{"172.16.0.1", true},
{"172.31.255.255", true},
{"example.com", false},
{"8.8.8.8", false},
{"1.1.1.1", false},
{"172.32.0.1", false}, // Not in private range
}
for _, tt := range tests {
t.Run(tt.host, func(t *testing.T) {
result := isPrivateOrLocalHost(tt.host)
if result != tt.expected {
t.Errorf("isPrivateOrLocalHost(%s) = %v, want %v", tt.host, result, tt.expected)
}
})
}
}

View File

@ -303,6 +303,9 @@ func requiresNamespaceOwnership(p string) bool {
if strings.HasPrefix(p, "/v1/rqlite/") {
return true
}
if strings.HasPrefix(p, "/v1/proxy/") {
return true
}
return false
}

View File

@ -44,5 +44,8 @@ func (g *Gateway) Routes() http.Handler {
mux.HandleFunc("/v1/pubsub/publish", g.pubsubPublishHandler)
mux.HandleFunc("/v1/pubsub/topics", g.pubsubTopicsHandler)
// anon proxy (authenticated users only)
mux.HandleFunc("/v1/proxy/anon", g.anonProxyHandler)
return g.withMiddleware(mux)
}

View File

@ -190,6 +190,168 @@ verify_installation() {
fi
}
install_anon() {
echo -e ""
echo -e "${BLUE}========================================${NOCOLOR}"
echo -e "${GREEN}Step 1.5: Install Anyone Relay (Anon)${NOCOLOR}"
echo -e "${BLUE}========================================${NOCOLOR}"
echo -e ""
log "Installing Anyone relay for anonymous networking..."
# Check if anon is already installed
if command -v anon &>/dev/null; then
success "Anon already installed"
configure_anon_logs
configure_firewall_for_anon
return 0
fi
# Install via APT (official method from docs.anyone.io)
log "Adding Anyone APT repository..."
# Add GPG key
if ! curl -fsSL https://deb.anyone.io/gpg.key | sudo gpg --dearmor -o /usr/share/keyrings/anyone-archive-keyring.gpg 2>/dev/null; then
warning "Failed to add Anyone GPG key"
log "You can manually install later with:"
log " curl -fsSL https://deb.anyone.io/gpg.key | sudo gpg --dearmor -o /usr/share/keyrings/anyone-archive-keyring.gpg"
log " echo 'deb [signed-by=/usr/share/keyrings/anyone-archive-keyring.gpg] https://deb.anyone.io/ anyone main' | sudo tee /etc/apt/sources.list.d/anyone.list"
log " sudo apt update && sudo apt install -y anon"
return 1
fi
# Add repository
echo "deb [signed-by=/usr/share/keyrings/anyone-archive-keyring.gpg] https://deb.anyone.io/ anyone main" | sudo tee /etc/apt/sources.list.d/anyone.list >/dev/null
# Update and install
log "Installing Anon package..."
sudo apt update -qq
if ! sudo apt install -y anon; then
warning "Anon installation failed"
return 1
fi
# Verify installation
if ! command -v anon &>/dev/null; then
warning "Anon installation may have failed"
return 1
fi
success "Anon installed successfully"
# Configure with sensible defaults
configure_anon_defaults
# Configure log directory
configure_anon_logs
# Configure firewall if present
configure_firewall_for_anon
# Enable and start service
log "Enabling Anon service..."
sudo systemctl enable anon 2>/dev/null || true
sudo systemctl start anon 2>/dev/null || true
if systemctl is-active --quiet anon; then
success "Anon service is running"
else
warning "Anon service may not be running. Check: sudo systemctl status anon"
fi
return 0
}
configure_anon_defaults() {
log "Configuring Anon with default settings..."
HOSTNAME=$(hostname -s 2>/dev/null || echo "debros-node")
# Create or update anonrc with our defaults
if [ -f /etc/anon/anonrc ]; then
# Backup existing config
sudo cp /etc/anon/anonrc /etc/anon/anonrc.bak 2>/dev/null || true
# Update key settings if not already set
if ! grep -q "^Nickname" /etc/anon/anonrc; then
echo "Nickname ${HOSTNAME}" | sudo tee -a /etc/anon/anonrc >/dev/null
fi
if ! grep -q "^ControlPort" /etc/anon/anonrc; then
echo "ControlPort 9051" | sudo tee -a /etc/anon/anonrc >/dev/null
fi
if ! grep -q "^SocksPort" /etc/anon/anonrc; then
echo "SocksPort 9050" | sudo tee -a /etc/anon/anonrc >/dev/null
fi
log " Nickname: ${HOSTNAME}"
log " ORPort: 9001 (default)"
log " ControlPort: 9051"
log " SOCKSPort: 9050"
fi
}
configure_anon_logs() {
log "Configuring Anon logs..."
# Create log directory
sudo mkdir -p /home/debros/.debros/logs/anon
# Change ownership to debian-anon (the user anon runs as)
sudo chown -R debian-anon:debian-anon /home/debros/.debros/logs/anon 2>/dev/null || true
# Update anonrc to point logs to our directory
if [ -f /etc/anon/anonrc ]; then
sudo sed -i.bak 's|Log notice file.*|Log notice file /home/debros/.debros/logs/anon/notices.log|g' /etc/anon/anonrc
success "Anon logs configured to /home/debros/.debros/logs/anon"
fi
}
configure_firewall_for_anon() {
log "Checking firewall configuration..."
# Check for UFW
if command -v ufw &>/dev/null && sudo ufw status | grep -q "Status: active"; then
log "UFW detected and active, adding Anon ports..."
sudo ufw allow 9001/tcp comment 'Anon ORPort' 2>/dev/null || true
sudo ufw allow 9051/tcp comment 'Anon ControlPort' 2>/dev/null || true
success "UFW rules added for Anon"
return 0
fi
# Check for firewalld
if command -v firewall-cmd &>/dev/null && sudo firewall-cmd --state 2>/dev/null | grep -q "running"; then
log "firewalld detected and active, adding Anon ports..."
sudo firewall-cmd --permanent --add-port=9001/tcp 2>/dev/null || true
sudo firewall-cmd --permanent --add-port=9051/tcp 2>/dev/null || true
sudo firewall-cmd --reload 2>/dev/null || true
success "firewalld rules added for Anon"
return 0
fi
# Check for iptables
if command -v iptables &>/dev/null; then
# Check if iptables has any rules (indicating it's in use)
if sudo iptables -L -n | grep -q "Chain INPUT"; then
log "iptables detected, adding Anon ports..."
sudo iptables -A INPUT -p tcp --dport 9001 -j ACCEPT -m comment --comment "Anon ORPort" 2>/dev/null || true
sudo iptables -A INPUT -p tcp --dport 9051 -j ACCEPT -m comment --comment "Anon ControlPort" 2>/dev/null || true
# Try to save rules if iptables-persistent is available
if command -v netfilter-persistent &>/dev/null; then
sudo netfilter-persistent save 2>/dev/null || true
elif command -v iptables-save &>/dev/null; then
sudo iptables-save | sudo tee /etc/iptables/rules.v4 >/dev/null 2>&1 || true
fi
success "iptables rules added for Anon"
return 0
fi
fi
log "No active firewall detected, skipping firewall configuration"
}
run_setup() {
echo -e ""
echo -e "${BLUE}========================================${NOCOLOR}"
@ -241,6 +403,11 @@ show_completion() {
echo -e " • Switch to testnet: ${CYAN}network-cli testnet enable${NOCOLOR}"
echo -e " • Show environment: ${CYAN}network-cli env current${NOCOLOR}"
echo -e ""
echo -e "${CYAN}Anyone Relay (Anon):${NOCOLOR}"
echo -e " • Check Anon status: ${CYAN}sudo systemctl status anon${NOCOLOR}"
echo -e " • View Anon logs: ${CYAN}sudo tail -f /home/debros/.debros/logs/anon/notices.log${NOCOLOR}"
echo -e " • Proxy endpoint: ${CYAN}POST http://localhost:6001/v1/proxy/anon${NOCOLOR}"
echo -e ""
echo -e "${CYAN}Documentation: https://docs.debros.io${NOCOLOR}"
echo -e ""
}
@ -270,6 +437,9 @@ main() {
exit 1
fi
# Install Anon (optional but recommended)
install_anon || warning "Anon installation skipped or failed"
# Run setup
run_setup

1
terms-agreement Normal file
View File

@ -0,0 +1 @@
agreed