mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-16 15:26:40 +00:00
141 lines
3.5 KiB
Go
141 lines
3.5 KiB
Go
package reverseproxy
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
"golang.org/x/crypto/acme/autocert"
|
|
)
|
|
|
|
// Start starts the reverse proxy server (non-blocking)
|
|
func (p *Proxy) Start() error {
|
|
p.mu.Lock()
|
|
if p.isRunning {
|
|
p.mu.Unlock()
|
|
return fmt.Errorf("reverse proxy already running")
|
|
}
|
|
p.isRunning = true
|
|
p.mu.Unlock()
|
|
|
|
// Build the main HTTP handler
|
|
handler := p.buildHandler()
|
|
|
|
if p.config.EnableHTTPS {
|
|
return p.startHTTPS(handler)
|
|
}
|
|
|
|
return p.startHTTP(handler)
|
|
}
|
|
|
|
// startHTTPS starts the proxy with HTTPS and Let's Encrypt (non-blocking)
|
|
func (p *Proxy) startHTTPS(handler http.Handler) error {
|
|
// Setup autocert manager with dynamic host policy
|
|
p.autocertManager = &autocert.Manager{
|
|
Prompt: autocert.AcceptTOS,
|
|
HostPolicy: p.dynamicHostPolicy,
|
|
Cache: autocert.DirCache(p.config.CertCacheDir),
|
|
Email: p.config.TLSEmail,
|
|
RenewBefore: 0, // Use default
|
|
}
|
|
|
|
// Start HTTP server for ACME challenges
|
|
p.httpServer = &http.Server{
|
|
Addr: p.config.HTTPListenAddress,
|
|
Handler: p.autocertManager.HTTPHandler(nil),
|
|
}
|
|
|
|
go func() {
|
|
log.Infof("Starting HTTP server for ACME challenges on %s", p.config.HTTPListenAddress)
|
|
if err := p.httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
log.Errorf("HTTP server error: %v", err)
|
|
}
|
|
}()
|
|
|
|
// Start HTTPS server in background
|
|
p.server = &http.Server{
|
|
Addr: p.config.ListenAddress,
|
|
Handler: handler,
|
|
TLSConfig: p.autocertManager.TLSConfig(),
|
|
}
|
|
|
|
go func() {
|
|
log.Infof("Starting HTTPS reverse proxy server on %s", p.config.ListenAddress)
|
|
if err := p.server.ListenAndServeTLS("", ""); err != nil && err != http.ErrServerClosed {
|
|
log.Errorf("HTTPS server failed: %v", err)
|
|
}
|
|
}()
|
|
|
|
return nil
|
|
}
|
|
|
|
// startHTTP starts the proxy with HTTP only (non-blocking)
|
|
func (p *Proxy) startHTTP(handler http.Handler) error {
|
|
p.server = &http.Server{
|
|
Addr: p.config.HTTPListenAddress,
|
|
Handler: handler,
|
|
}
|
|
|
|
go func() {
|
|
log.Infof("Starting HTTP reverse proxy server on %s (HTTPS disabled)", p.config.HTTPListenAddress)
|
|
if err := p.server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
log.Errorf("HTTP server failed: %w", err)
|
|
}
|
|
}()
|
|
|
|
return nil
|
|
}
|
|
|
|
// dynamicHostPolicy validates that a requested host has a configured route
|
|
func (p *Proxy) dynamicHostPolicy(ctx context.Context, host string) error {
|
|
p.mu.RLock()
|
|
defer p.mu.RUnlock()
|
|
|
|
// Check if we have a route for this host
|
|
if _, exists := p.routes[host]; exists {
|
|
log.Infof("ACME challenge accepted for configured host: %s", host)
|
|
return nil
|
|
}
|
|
|
|
log.Warnf("ACME challenge rejected for unconfigured host: %s", host)
|
|
return fmt.Errorf("host %s not configured", host)
|
|
}
|
|
|
|
// Stop gracefully stops the reverse proxy server
|
|
func (p *Proxy) Stop(ctx context.Context) error {
|
|
p.mu.Lock()
|
|
if !p.isRunning {
|
|
p.mu.Unlock()
|
|
return fmt.Errorf("reverse proxy not running")
|
|
}
|
|
p.isRunning = false
|
|
p.mu.Unlock()
|
|
|
|
log.Info("Stopping reverse proxy server...")
|
|
|
|
// Stop HTTP server (for ACME challenges)
|
|
if p.httpServer != nil {
|
|
if err := p.httpServer.Shutdown(ctx); err != nil {
|
|
log.Errorf("Error shutting down HTTP server: %v", err)
|
|
}
|
|
}
|
|
|
|
// Stop main server
|
|
if p.server != nil {
|
|
if err := p.server.Shutdown(ctx); err != nil {
|
|
return fmt.Errorf("error shutting down server: %w", err)
|
|
}
|
|
}
|
|
|
|
log.Info("Reverse proxy server stopped")
|
|
return nil
|
|
}
|
|
|
|
// IsRunning returns whether the proxy is running
|
|
func (p *Proxy) IsRunning() bool {
|
|
p.mu.RLock()
|
|
defer p.mu.RUnlock()
|
|
return p.isRunning
|
|
}
|