Documentation
¶
Overview ¶
Package swg provides an HTTPS man-in-the-middle (MITM) proxy for content filtering. It intercepts HTTPS connections by dynamically generating TLS certificates signed by a trusted CA, allowing inspection and filtering of encrypted traffic.
Architecture ¶
The proxy handles both HTTP and HTTPS (CONNECT) requests. For HTTPS, it performs a TLS handshake with the client using a dynamically generated certificate for the requested host, then forwards the decrypted request to the origin server. Filters can inspect and block requests at any point.
Basic Proxy ¶
Create a proxy with certificate management and start serving:
cm, err := swg.NewCertManager("ca.crt", "ca.key")
if err != nil {
log.Fatal(err)
}
proxy := swg.NewProxy(":8080", cm)
log.Fatal(proxy.ListenAndServe())
Domain Filtering ¶
Block requests by domain name with optional wildcard support:
filter := swg.NewDomainFilter()
filter.AddDomain("blocked.com")
filter.AddDomain("*.ads.example.com")
proxy.Filter = filter
Advanced Filtering with RuleSet ¶
RuleSet supports domain, URL prefix, and regex pattern matching with metadata such as reason and category:
rs := swg.NewRuleSet()
rs.AddDomain("ads.example.com")
rs.AddURL("https://evil.com/malware")
rs.AddRegex(`.*\.tracking\..*`)
rs.AddRule(swg.Rule{
Type: "domain",
Pattern: "malware.com",
Reason: "known malware host",
Category: "security",
})
proxy.Filter = rs
Reloadable Filters ¶
Load rules from external sources (CSV files, HTTP endpoints, databases) with automatic periodic reloading:
loader := swg.NewCSVLoader("blocklist.csv")
loader.HasHeader = true
filter := swg.NewReloadableFilter(loader)
filter.OnReload = func(count int) {
log.Printf("Loaded %d rules", count)
}
ctx := context.Background()
filter.Load(ctx)
cancel := filter.StartAutoReload(ctx, 5*time.Minute)
defer cancel()
proxy.Filter = filter
Multiple sources can be combined:
multi := swg.NewMultiLoader(
swg.NewCSVLoader("local.csv"),
swg.NewURLLoader("https://blocklist.example.com/rules.csv"),
swg.NewStaticLoader(swg.Rule{Type: "domain", Pattern: "always-blocked.com"}),
)
filter := swg.NewReloadableFilter(multi)
Custom Filters ¶
Implement the Filter interface or use FilterFunc for simple cases:
proxy.Filter = swg.FilterFunc(func(req *http.Request) (bool, string) {
if req.Host == "blocked.com" {
return true, "domain blocked"
}
return false, ""
})
Block Pages ¶
Display a customizable HTML page when requests are blocked:
proxy.BlockPage = swg.NewBlockPage()
// Or from a custom template file
bp, err := swg.NewBlockPageFromFile("block.html")
proxy.BlockPage = bp
Template variables available in block pages: {{.URL}}, {{.Host}}, {{.Path}}, {{.Reason}}, and {{.Timestamp}}.
PAC File Generation ¶
Generate Proxy Auto-Configuration files for client setup:
pac := swg.NewPACGenerator("proxy.example.com:8080")
pac.AddBypassDomain("internal.company.com")
pac.AddBypassNetwork("10.0.0.0/8")
// Serve as HTTP handler
http.Handle("/proxy.pac", pac)
// Or write to file
pac.WriteFile("proxy.pac")
Prometheus Metrics ¶
Instrument the proxy with Prometheus metrics for monitoring:
metrics := swg.NewMetrics()
http.Handle("/metrics", metrics.Handler())
The Metrics type provides methods for recording requests, blocked connections, certificate cache statistics, filter reloads, and more.
Health Check Endpoints ¶
Expose /healthz and /readyz endpoints for Kubernetes and load balancers:
health := swg.NewHealthChecker() proxy.HealthChecker = health health.SetAlive(true) health.SetReady(true)
Custom readiness checks verify downstream dependencies:
health.ReadinessChecks = append(health.ReadinessChecks, func() error {
if !dbPing() {
return errors.New("database unreachable")
}
return nil
})
Structured Access Log ¶
Write JSON access log entries for every proxied request:
f, _ := os.OpenFile("access.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
alLogger := slog.New(slog.NewJSONHandler(f, nil))
proxy.AccessLog = swg.NewAccessLogger(alLogger)
Each entry includes method, host, path, scheme, status code, duration, bytes written, client address, blocked/reason, and user agent.
SIGHUP Reload ¶
Reload filter rules on SIGHUP without restarting the proxy:
reloader := swg.WatchSIGHUP(proxy, func(ctx context.Context) (swg.Filter, error) {
cfg, _ := swg.LoadConfig("swg.yaml")
loader, _ := cfg.BuildRuleLoader()
filter := swg.NewReloadableFilter(loader)
filter.Load(ctx)
return filter, nil
}, logger)
defer reloader.Cancel()
Configuration ¶
Load configuration from YAML, JSON, or TOML files with environment variable overrides (SWG_ prefix):
cfg, err := swg.LoadConfig("swg.yaml")
if err != nil {
log.Fatal(err)
}
rs, err := cfg.BuildRuleSet()
proxy.Filter = rs
ACME / Let's Encrypt Certificates ¶
Use ACMECertManager to obtain and automatically renew TLS certificates from Let's Encrypt or any RFC 8555-compliant CA, eliminating the need to manage a self-signed CA for the proxy's own listener certificate:
acm, err := swg.NewACMECertManager(swg.ACMEConfig{
Email: "admin@example.com",
Domains: []string{"proxy.example.com"},
AcceptTOS: true,
CA: swg.LetsEncryptStaging,
})
if err != nil {
log.Fatal(err)
}
defer acm.Close()
ctx := context.Background()
if err := acm.Initialize(ctx); err != nil {
log.Fatal(err)
}
if err := acm.ObtainCertificates(ctx); err != nil {
log.Fatal(err)
}
acm.StartAutoRenewal(12 * time.Hour)
The manager persists account data and certificates to disk so restarts do not re-issue certificates. Combine with a self-signed CertManager for MITM per-host certificates while using ACME for the proxy's own listener TLS:
srv := &http.Server{
TLSConfig: &tls.Config{GetCertificate: acm.GetCertificate},
}
See ACMEConfig for the full set of configuration fields including challenge ports, key types, External Account Binding, and renewal timing.
CA Certificate Generation ¶
Generate a new CA certificate and key pair programmatically:
certPEM, keyPEM, err := swg.GenerateCA("My Organization", 10)
cm, err := swg.NewCertManagerFromPEM(certPEM, keyPEM)
Graceful Shutdown ¶
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := proxy.Shutdown(ctx); err != nil {
log.Printf("shutdown error: %v", err)
}
Index ¶
- Constants
- Variables
- func CompressBytes(data []byte, encoding string) ([]byte, error)
- func GenerateCA(org string, validYears int) (certPEM, keyPEM []byte, err error)
- func GenerateClientCert(caCert *x509.Certificate, caKeyPEM []byte, cn string, orgs []string, ...) (certPEM, keyPEM []byte, err error)
- func LimitRequestBody(maxSize int64, next http.Handler) http.Handler
- func WithRequestContext(ctx context.Context, rc *RequestContext) context.Context
- func WriteExampleConfig(path string) error
- type ACMECertManager
- func (acm *ACMECertManager) CacheSize() int
- func (acm *ACMECertManager) Close() error
- func (acm *ACMECertManager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error)
- func (acm *ACMECertManager) GetCertificateForHost(host string) (*tls.Certificate, error)
- func (acm *ACMECertManager) Initialize(ctx context.Context) error
- func (acm *ACMECertManager) ObtainCertificates(ctx context.Context) error
- func (acm *ACMECertManager) SetLogger(logger *slog.Logger)
- func (acm *ACMECertManager) StartAutoRenewal(checkInterval time.Duration)
- type ACMEConfig
- type AccessLogEntry
- type AccessLogger
- type AdminAPI
- type AllowListFilter
- type BlockPage
- type BlockPageConfig
- type BlockPageData
- type BodyLimitConfig
- type BodyLimiter
- func (bl *BodyLimiter) Check(req *http.Request) error
- func (bl *BodyLimiter) GetPathLimit(path string) int64
- func (bl *BodyLimiter) HandleRequest(ctx context.Context, req *http.Request, rc *RequestContext) *http.Response
- func (bl *BodyLimiter) Middleware(next http.Handler) http.Handler
- func (bl *BodyLimiter) SetPathLimit(pathPrefix string, limit int64)
- type Bypass
- type CSVLoader
- type CertManager
- type CertRotator
- func (cr *CertRotator) CACert() *x509.Certificate
- func (cr *CertRotator) CAKey() *rsa.PrivateKey
- func (cr *CertRotator) CacheSize() int
- func (cr *CertRotator) CertManager() *CertManager
- func (cr *CertRotator) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error)
- func (cr *CertRotator) GetCertificateForHost(host string) (*tls.Certificate, error)
- func (cr *CertRotator) Rotate() (*CertManager, error)
- func (cr *CertRotator) RotateFromPEM(certPEM, keyPEM []byte) (*CertManager, error)
- func (cr *CertRotator) WatchCAFiles(interval func() <-chan time.Time) func()
- type ChainFilter
- type ClientAuth
- func (ca *ClientAuth) AddCACert(cert *x509.Certificate)
- func (ca *ClientAuth) AddCAPEM(pemData []byte) error
- func (ca *ClientAuth) IdentityFromConn(conn *tls.Conn) (identity string, groups []string)
- func (ca *ClientAuth) Policy() tls.ClientAuthType
- func (ca *ClientAuth) SetPolicy(policy tls.ClientAuthType)
- func (ca *ClientAuth) TLSConfig() *tls.Config
- func (ca *ClientAuth) VerifyPeerCertificate(rawCerts [][]byte, _ [][]*x509.Certificate) error
- func (ca *ClientAuth) WrapListener(inner net.Listener, serverCert tls.Certificate) net.Listener
- type CompressHandler
- type CompressionConfig
- type Config
- type ContentTypeFilter
- type DomainFilter
- type ErrorResponse
- type ExporterType
- type Filter
- type FilterConfig
- type FilterFunc
- type GroupPolicyFilter
- type HealthChecker
- func (h *HealthChecker) HandleHealthz(w http.ResponseWriter, _ *http.Request)
- func (h *HealthChecker) HandleReadyz(w http.ResponseWriter, _ *http.Request)
- func (h *HealthChecker) IsAlive() bool
- func (h *HealthChecker) IsReady() bool
- func (h *HealthChecker) SetAlive(alive bool)
- func (h *HealthChecker) SetReady(ready bool)
- type HealthResponse
- type IPIdentityResolver
- type IdentityResolver
- type IdentityResolverFunc
- type LoggingConfig
- type MessageResponse
- type Metrics
- func (m *Metrics) DecActiveConns()
- func (m *Metrics) Handler() http.Handler
- func (m *Metrics) IncActiveConns()
- func (m *Metrics) RecordBlocked(reason string)
- func (m *Metrics) RecordCertCacheHit()
- func (m *Metrics) RecordCertCacheMiss()
- func (m *Metrics) RecordFilterReload()
- func (m *Metrics) RecordFilterReloadError()
- func (m *Metrics) RecordRequest(method, scheme string)
- func (m *Metrics) RecordRequestDuration(method string, statusCode int, duration time.Duration)
- func (m *Metrics) RecordTLSHandshakeError()
- func (m *Metrics) RecordUpstreamError(host string)
- func (m *Metrics) SetCertCacheSize(size int)
- func (m *Metrics) SetFilterRuleCount(count int)
- type MultiLoader
- type PACGenerator
- func (g *PACGenerator) AddBypassDomain(domain string)
- func (g *PACGenerator) AddBypassNetwork(cidr string)
- func (g *PACGenerator) Generate(w io.Writer) error
- func (g *PACGenerator) GenerateString() (string, error)
- func (g *PACGenerator) ServeHTTP(w http.ResponseWriter, r *http.Request)
- func (g *PACGenerator) WriteFile(path string) error
- type PolicyEngine
- type Proxy
- type ProxyTracer
- func (pt *ProxyTracer) AddEvent(ctx context.Context, name string, attrs ...attribute.KeyValue)
- func (pt *ProxyTracer) Enabled() bool
- func (pt *ProxyTracer) RecordError(ctx context.Context, err error)
- func (pt *ProxyTracer) SetAllowed(ctx context.Context)
- func (pt *ProxyTracer) SetBlocked(ctx context.Context, reason string)
- func (pt *ProxyTracer) SetIdentity(ctx context.Context, identity string, groups []string)
- func (pt *ProxyTracer) SetUpstreamResponse(ctx context.Context, statusCode int, contentLength int64)
- func (pt *ProxyTracer) StartBodyScan(ctx context.Context, contentType string, size int64) (context.Context, trace.Span)
- func (pt *ProxyTracer) StartCertGeneration(ctx context.Context, host string) (context.Context, trace.Span)
- func (pt *ProxyTracer) StartConnect(ctx context.Context, host string, clientAddr string) (context.Context, trace.Span)
- func (pt *ProxyTracer) StartFilter(ctx context.Context, host string) (context.Context, trace.Span)
- func (pt *ProxyTracer) StartRequest(ctx context.Context, r *http.Request) (context.Context, trace.Span)
- func (pt *ProxyTracer) StartTLSHandshake(ctx context.Context, host string) (context.Context, trace.Span)
- func (pt *ProxyTracer) StartUpstream(ctx context.Context, r *http.Request) (context.Context, trace.Span)
- type RateLimiter
- type ReadinessCheck
- type ReloadFunc
- type ReloadableFilter
- func (rf *ReloadableFilter) Count() int
- func (rf *ReloadableFilter) Load(ctx context.Context) error
- func (rf *ReloadableFilter) RuleSet() *RuleSet
- func (rf *ReloadableFilter) ShouldBlock(req *http.Request) (bool, string)
- func (rf *ReloadableFilter) StartAutoReload(ctx context.Context, interval time.Duration) context.CancelFunc
- type RequestContext
- type RequestHook
- type RequestHookFunc
- type ResponseBodyScanner
- type ResponseBodyScannerFunc
- type ResponseHook
- type ResponseHookFunc
- type Rule
- type RuleConfig
- type RuleLoader
- type RuleLoaderFunc
- type RuleRequest
- type RuleSet
- func (rs *RuleSet) AddDomain(domain string)
- func (rs *RuleSet) AddRegex(pattern string) error
- func (rs *RuleSet) AddRule(r Rule) error
- func (rs *RuleSet) AddURL(urlPrefix string)
- func (rs *RuleSet) Clear()
- func (rs *RuleSet) Count() int
- func (rs *RuleSet) Match(req *http.Request) (*Rule, bool)
- func (rs *RuleSet) RemoveRule(ruleType, pattern string) bool
- func (rs *RuleSet) Rules() []Rule
- func (rs *RuleSet) ShouldBlock(req *http.Request) (bool, string)
- type RulesResponse
- type SIGHUPReloader
- type ScanResult
- type ScanVerdict
- type ServerConfig
- type SourceConfig
- type StaticLoader
- type StatusResponse
- type TLSConfig
- type TimeRule
- type Tracer
- func (t *Tracer) Enabled() bool
- func (t *Tracer) Extract(ctx context.Context, headers http.Header) context.Context
- func (t *Tracer) Inject(ctx context.Context, headers http.Header)
- func (t *Tracer) Shutdown(ctx context.Context) error
- func (t *Tracer) SpanFromContext(ctx context.Context) trace.Span
- func (t *Tracer) StartSpan(ctx context.Context, name string, opts ...trace.SpanStartOption) (context.Context, trace.Span)
- func (t *Tracer) TracerProvider() *sdktrace.TracerProvider
- type TracingConfig
- type TracingMiddleware
- type TracingMiddlewareOptions
- type TransportPool
- type TransportPoolStats
- type URLLoader
- type UpstreamAuth
- type UpstreamProxy
Constants ¶
const ( LetsEncryptProduction = lego.LEDirectoryProduction LetsEncryptStaging = lego.LEDirectoryStaging )
ACME CA directory URLs for use with ACMEConfig.CA.
LetsEncryptProduction is the default when CA is empty. Use LetsEncryptStaging during development and testing to avoid Let's Encrypt production rate limits (5 duplicate certificates per week, 50 certificates per registered domain per week).
const ( KB = 1024 MB = 1024 * KB GB = 1024 * MB )
Common body size constants for convenience.
const ( EncodingGzip = "gzip" EncodingZstd = "zstd" EncodingBrotli = "br" EncodingDeflate = "deflate" )
Compression encoding constants.
const DefaultBlockPageHTML = `` /* 4263-byte string literal not displayed */
DefaultBlockPageHTML is the default block page template.
const DefaultBypassHeader = "X-SWG-Bypass"
DefaultBypassHeader is the default HTTP header used to carry a bypass token.
Variables ¶
var ErrBodyTooLarge = errors.New("request body too large")
ErrBodyTooLarge is returned when the request body exceeds the configured limit.
Functions ¶
func CompressBytes ¶
CompressBytes compresses data with the specified encoding.
func GenerateCA ¶
GenerateCA generates a new CA certificate and private key. Returns PEM-encoded certificate and key.
func GenerateClientCert ¶
func GenerateClientCert(caCert *x509.Certificate, caKeyPEM []byte, cn string, orgs []string, validYears int) (certPEM, keyPEM []byte, err error)
GenerateClientCert generates a client certificate signed by the given CA. The certificate includes the x509.ExtKeyUsageClientAuth extended key usage and is valid for the specified number of years.
This is a convenience function for testing and development. Production deployments should use a proper PKI or certificate authority.
func LimitRequestBody ¶
LimitRequestBody is a convenience function that wraps an http.Handler with body size limiting middleware.
func WithRequestContext ¶
func WithRequestContext(ctx context.Context, rc *RequestContext) context.Context
WithRequestContext attaches a RequestContext to the given context.
func WriteExampleConfig ¶
WriteExampleConfig writes an example configuration file.
Types ¶
type ACMECertManager ¶
type ACMECertManager struct {
// OnCertObtained is called after a new certificate is obtained for a
// domain. It is called from the goroutine that performed the obtain.
OnCertObtained func(domain string)
// OnCertRenewed is called after an existing certificate is renewed.
OnCertRenewed func(domain string)
// OnError is called when obtaining or renewing a certificate fails.
OnError func(domain string, err error)
// contains filtered or unexported fields
}
ACMECertManager obtains and renews TLS certificates from an ACME CA such as Let's Encrypt. It implements the same GetCertificate / GetCertificateForHost surface as CertManager, so it can be used with tls.Config.GetCertificate or anywhere a per-host certificate provider is needed.
Lifecycle ¶
The typical usage follows four steps:
- Create — NewACMECertManager validates the config and creates the on-disk storage directory.
- Initialize — ACMECertManager.Initialize registers (or loads) the ACME account, configures challenge solvers, and loads any previously obtained certificates from disk.
- Obtain — ACMECertManager.ObtainCertificates contacts the CA and obtains certificates for every domain in the config.
- Renew — ACMECertManager.StartAutoRenewal spawns a background goroutine that periodically checks certificate expiration and renews before the RenewBefore window.
Call ACMECertManager.Close to stop the renewal goroutine and release resources.
Callbacks ¶
Three optional callbacks are available for observability:
- OnCertObtained — fired after a certificate is successfully obtained.
- OnCertRenewed — fired after a certificate is successfully renewed.
- OnError — fired when obtaining or renewing a certificate fails.
Thread Safety ¶
All public methods are safe for concurrent use. The certificate cache is protected by an internal sync.RWMutex.
Example ¶
acm, err := swg.NewACMECertManager(swg.ACMEConfig{
Email: "admin@example.com",
Domains: []string{"proxy.example.com"},
AcceptTOS: true,
CA: swg.LetsEncryptStaging, // use staging for testing
})
if err != nil {
log.Fatal(err)
}
defer acm.Close()
if err := acm.Initialize(ctx); err != nil {
log.Fatal(err)
}
if err := acm.ObtainCertificates(ctx); err != nil {
log.Fatal(err)
}
acm.StartAutoRenewal(12 * time.Hour)
srv := &http.Server{
TLSConfig: &tls.Config{GetCertificate: acm.GetCertificate},
}
func NewACMECertManager ¶
func NewACMECertManager(cfg ACMEConfig) (*ACMECertManager, error)
NewACMECertManager validates cfg and returns a new ACMECertManager. It creates the storage directory specified by [ACMEConfig.StoragePath] but does not contact the CA — call ACMECertManager.Initialize next.
Returns an error if Email is empty, Domains is empty, or AcceptTOS is false.
func (*ACMECertManager) CacheSize ¶
func (acm *ACMECertManager) CacheSize() int
CacheSize returns the number of certificates currently held in the in-memory cache.
func (*ACMECertManager) Close ¶
func (acm *ACMECertManager) Close() error
Close stops the auto-renewal goroutine (if running) and waits for it to exit. It is safe to call Close multiple times.
func (*ACMECertManager) GetCertificate ¶
func (acm *ACMECertManager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error)
GetCertificate returns a TLS certificate for the SNI host name in hello. It is intended for use as tls.Config.GetCertificate:
srv := &http.Server{
TLSConfig: &tls.Config{
GetCertificate: acm.GetCertificate,
},
}
Returns an error if the ClientHelloInfo contains no SNI server name.
func (*ACMECertManager) GetCertificateForHost ¶
func (acm *ACMECertManager) GetCertificateForHost(host string) (*tls.Certificate, error)
GetCertificateForHost returns the cached TLS certificate for host. If the certificate is not in the cache but the host is one of the configured [ACMEConfig.Domains], an on-demand obtain is attempted.
Returns an error if the host is not in the configured domain list.
func (*ACMECertManager) Initialize ¶
func (acm *ACMECertManager) Initialize(ctx context.Context) error
Initialize creates the lego ACME client, configures the HTTP-01 and TLS-ALPN-01 challenge providers, and either loads an existing account from disk or registers a new one with the CA.
On first run the account private key is generated and persisted at <StoragePath>/account.json. Subsequent calls load the existing key.
If [ACMEConfig.EABKeyID] and [ACMEConfig.EABMACKey] are set, External Account Binding is used during registration.
Any certificates previously stored on disk are loaded into the in-memory cache so they are available immediately without contacting the CA.
func (*ACMECertManager) ObtainCertificates ¶
func (acm *ACMECertManager) ObtainCertificates(ctx context.Context) error
ObtainCertificates iterates over every domain in [ACMEConfig.Domains] and obtains a certificate from the CA. If a valid certificate already exists in the cache and is not within the RenewBefore window, the domain is skipped.
Certificates are persisted to disk under <StoragePath>/certificates/<domain>/. The [ACMECertManager.OnCertObtained] callback is invoked for each newly obtained certificate.
Returns the first error encountered; remaining domains are not attempted.
func (*ACMECertManager) SetLogger ¶
func (acm *ACMECertManager) SetLogger(logger *slog.Logger)
SetLogger replaces the default slog.Default logger used by the ACMECertManager. Call this before ACMECertManager.Initialize to capture all log output.
func (*ACMECertManager) StartAutoRenewal ¶
func (acm *ACMECertManager) StartAutoRenewal(checkInterval time.Duration)
StartAutoRenewal spawns a background goroutine that checks all cached certificates at the given interval and renews any that will expire within the [ACMEConfig.RenewBefore] window.
If checkInterval is zero it defaults to 12 hours. A typical production value is 12*time.Hour, which balances CA load against timely renewal.
The goroutine is stopped when ACMECertManager.Close is called.
type ACMEConfig ¶
type ACMEConfig struct {
// Email is the address registered with the ACME account. The CA sends
// certificate expiration warnings here. Required.
Email string `mapstructure:"email"`
// CA is the ACME directory URL. Defaults to [LetsEncryptProduction].
// Use [LetsEncryptStaging] during development to avoid rate limits.
// Any RFC 8555-compliant CA directory URL is accepted (e.g. ZeroSSL,
// Buypass, Google Trust Services).
CA string `mapstructure:"ca"`
// KeyType selects the private key algorithm for issued certificates.
// Supported values:
//
// - "ec256" — ECDSA P-256 (default, recommended)
// - "ec384" — ECDSA P-384
// - "rsa2048" — RSA 2048-bit
// - "rsa4096" — RSA 4096-bit
// - "rsa8192" — RSA 8192-bit
//
// ECDSA keys produce smaller certificates and faster TLS handshakes.
KeyType string `mapstructure:"key_type"`
// StoragePath is the directory where account data and certificates are
// persisted. The directory is created automatically with mode 0700.
// Defaults to "./acme".
StoragePath string `mapstructure:"storage_path"`
// HTTPPort is the listen port for HTTP-01 ACME challenges.
// Defaults to 80. Set to 0 to disable the HTTP-01 challenge solver.
HTTPPort int `mapstructure:"http_port"`
// TLSPort is the listen port for TLS-ALPN-01 ACME challenges.
// Defaults to 443. Set to 0 to disable the TLS-ALPN-01 challenge solver.
TLSPort int `mapstructure:"tls_port"`
// RenewBefore is how far in advance of expiration the certificate is
// renewed during auto-renewal. Defaults to 30 days. Let's Encrypt
// certificates are valid for 90 days, so 30 days gives two retry
// windows.
RenewBefore time.Duration `mapstructure:"renew_before"`
// Domains lists the fully-qualified domain names for which
// certificates will be obtained. At least one is required. Each
// domain receives its own certificate (no SANs across entries).
Domains []string `mapstructure:"domains"`
// AcceptTOS must be set to true to indicate acceptance of the CA's
// Terms of Service. [NewACMECertManager] returns an error if false.
AcceptTOS bool `mapstructure:"accept_tos"`
// EABKeyID is the External Account Binding key identifier.
// Required only for CAs that mandate EAB (e.g. ZeroSSL).
EABKeyID string `mapstructure:"eab_key_id"`
// EABMACKey is the base64url-encoded HMAC key for EAB.
// Required only for CAs that mandate EAB (e.g. ZeroSSL).
EABMACKey string `mapstructure:"eab_mac_key"`
}
ACMEConfig holds the configuration for obtaining and renewing TLS certificates via the ACME protocol (RFC 8555). It is the sole input to NewACMECertManager.
At minimum you must set [ACMEConfig.Email], [ACMEConfig.Domains], and [ACMEConfig.AcceptTOS]. All other fields have sensible defaults provided by DefaultACMEConfig.
Challenge Types ¶
The ACME protocol verifies domain ownership through challenge types. ACMEConfig supports two:
- HTTP-01 — The CA makes an HTTP request to port 80 on the domain. Controlled by [ACMEConfig.HTTPPort]. Set to 0 to disable.
- TLS-ALPN-01 — The CA performs a TLS handshake on port 443 using a special ALPN protocol. Controlled by [ACMEConfig.TLSPort]. Set to 0 to disable.
At least one challenge type must remain enabled. Both ports must be reachable from the public internet for the challenge to succeed.
External Account Binding (EAB) ¶
Some CAs (ZeroSSL, Google Trust Services, Buypass Go) require External Account Binding. Set [ACMEConfig.EABKeyID] and [ACMEConfig.EABMACKey] to the values provided by the CA's dashboard.
Storage Layout ¶
Certificates, private keys, and account data are persisted under [ACMEConfig.StoragePath] (default "./acme"):
<StoragePath>/
├── account.json # ACME account + private key
└── certificates/
└── <domain>/
├── certificate.pem # Leaf + intermediates
├── private_key.pem # Certificate private key
├── issuer.pem # Issuer certificate
└── metadata.json # Domain, URL, timestamp
All files are created with mode 0600/0700 so only the process owner can read them.
func DefaultACMEConfig ¶
func DefaultACMEConfig() ACMEConfig
DefaultACMEConfig returns an ACMEConfig populated with production-ready defaults. The caller must still set Email, Domains, and AcceptTOS before passing the config to NewACMECertManager.
cfg := swg.DefaultACMEConfig()
cfg.Email = "admin@example.com"
cfg.Domains = []string{"proxy.example.com"}
cfg.AcceptTOS = true
type AccessLogEntry ¶
type AccessLogEntry struct {
// Timestamp when the request was received.
Timestamp time.Time
// Method is the HTTP method (GET, POST, CONNECT, etc.).
Method string
// Host is the target hostname.
Host string
// Path is the request URL path.
Path string
// Scheme is "http" or "https".
Scheme string
// StatusCode is the upstream response status code. Zero if blocked or errored.
StatusCode int
// Duration is the time to process the request.
Duration time.Duration
// BytesWritten is the response body size.
BytesWritten int64
// ClientAddr is the client's remote address.
ClientAddr string
// Blocked is true if the request was blocked by a filter.
Blocked bool
// BlockReason is the reason the request was blocked (if Blocked is true).
BlockReason string
// Error is a description of any error that occurred.
Error string
// UserAgent is the client's User-Agent header.
UserAgent string
}
AccessLogEntry contains all fields for a single access log record.
type AccessLogger ¶
type AccessLogger struct {
// contains filtered or unexported fields
}
AccessLogger writes structured access log entries for each proxied request. It uses slog.LogAttrs for low-allocation logging on the hot path.
func NewAccessLogger ¶
func NewAccessLogger(logger *slog.Logger) *AccessLogger
NewAccessLogger creates a new AccessLogger that writes to the given slog.Logger. For best performance, pass a logger configured with slog.NewJSONHandler.
func (*AccessLogger) Log ¶
func (al *AccessLogger) Log(e AccessLogEntry)
Log writes an access log entry using slog.LogAttrs to minimize allocations.
type AdminAPI ¶
type AdminAPI struct {
// Proxy is the proxy instance to manage.
Proxy *Proxy
// Logger for admin API events.
Logger *slog.Logger
// PathPrefix is the URL path prefix for admin routes (default "/api").
PathPrefix string
// ReloadFunc is called when POST /api/reload is invoked. It should
// rebuild the filter from its source (e.g. config file, database).
// If nil, the reload endpoint returns 501 Not Implemented.
ReloadFunc func(ctx context.Context) error
// contains filtered or unexported fields
}
AdminAPI provides REST endpoints for managing the proxy at runtime. It exposes routes for listing, adding, and removing filter rules, viewing proxy status, and triggering filter reloads.
The API is mounted at a configurable path prefix (default "/api") and uses chi for routing.
All endpoints return JSON responses with appropriate status codes. Mutations require the filter to be a *RuleSet or a *ReloadableFilter that exposes a RuleSet.
func NewAdminAPI ¶
NewAdminAPI creates an AdminAPI wired to the given proxy.
type AllowListFilter ¶
type AllowListFilter struct {
Reason string // block reason for denied requests
// contains filtered or unexported fields
}
AllowListFilter implements Filter with a deny-by-default policy. Only requests matching allowed domains pass through; everything else is blocked.
func NewAllowListFilter ¶
func NewAllowListFilter() *AllowListFilter
NewAllowListFilter creates a deny-by-default filter.
func (*AllowListFilter) AddDomain ¶
func (f *AllowListFilter) AddDomain(domain string)
AddDomain adds a domain to the allow list. Supports wildcards: "*.example.com" allows all subdomains.
func (*AllowListFilter) AddDomains ¶
func (f *AllowListFilter) AddDomains(domains []string)
AddDomains adds multiple domains to the allow list.
func (*AllowListFilter) ShouldBlock ¶
func (f *AllowListFilter) ShouldBlock(req *http.Request) (bool, string)
ShouldBlock implements Filter. Returns true for domains NOT in the allow list.
type BlockPage ¶
type BlockPage struct {
// contains filtered or unexported fields
}
BlockPage represents a customizable block page.
func NewBlockPage ¶
func NewBlockPage() *BlockPage
NewBlockPage creates a new BlockPage with the default template.
func NewBlockPageFromFile ¶
NewBlockPageFromFile creates a BlockPage from a template file.
func NewBlockPageFromTemplate ¶
NewBlockPageFromTemplate creates a BlockPage from a custom template string.
func (*BlockPage) Render ¶
func (bp *BlockPage) Render(w io.Writer, data BlockPageData) error
Render writes the block page to the given writer.
func (*BlockPage) RenderString ¶
func (bp *BlockPage) RenderString(data BlockPageData) (string, error)
RenderString returns the block page as a string.
type BlockPageConfig ¶
type BlockPageConfig struct {
// Enabled determines if custom block page is used
Enabled bool `mapstructure:"enabled"`
// RedirectURL to redirect blocked requests (optional)
RedirectURL string `mapstructure:"redirect_url"`
// TemplatePath to custom block page template
TemplatePath string `mapstructure:"template_path"`
// TemplateInline is inline template content
TemplateInline string `mapstructure:"template_inline"`
}
BlockPageConfig contains block page settings.
type BlockPageData ¶
BlockPageData contains the data passed to the block page template.
type BodyLimitConfig ¶
type BodyLimitConfig struct {
// MaxSize is the maximum allowed request body size in bytes.
// Zero means no limit.
MaxSize int64
// StreamCheck enables early rejection by checking Content-Length header
// before reading the body. If Content-Length exceeds MaxSize, the request
// is rejected immediately without buffering.
StreamCheck bool
// RejectResponse is an optional custom response to send when the limit is
// exceeded. If nil, a default 413 Payload Too Large response is sent.
RejectResponse *http.Response
// SkipPaths is a list of URL path prefixes to skip body limit checks.
// Useful for upload endpoints that need larger limits.
SkipPaths []string
// SkipMethods is a list of HTTP methods to skip body limit checks.
// By default, GET, HEAD, OPTIONS, and TRACE are skipped as they
// typically don't have request bodies.
SkipMethods []string
}
BodyLimitConfig configures request body size limits.
func DefaultBodyLimitConfig ¶
func DefaultBodyLimitConfig() BodyLimitConfig
DefaultBodyLimitConfig returns a configuration with sensible defaults.
type BodyLimiter ¶
type BodyLimiter struct {
Config BodyLimitConfig
// contains filtered or unexported fields
}
BodyLimiter enforces request body size limits.
func NewBodyLimiter ¶
func NewBodyLimiter(maxSize int64) *BodyLimiter
NewBodyLimiter creates a new BodyLimiter with the given maximum size.
func NewBodyLimiterWithConfig ¶
func NewBodyLimiterWithConfig(cfg BodyLimitConfig) *BodyLimiter
NewBodyLimiterWithConfig creates a BodyLimiter with custom configuration.
func (*BodyLimiter) Check ¶
func (bl *BodyLimiter) Check(req *http.Request) error
Check validates the request body size against configured limits. Returns ErrBodyTooLarge if the body exceeds the limit. If StreamCheck is enabled and Content-Length is set, validation happens without reading the body. Otherwise, the body is wrapped with a limiting reader.
func (*BodyLimiter) GetPathLimit ¶
func (bl *BodyLimiter) GetPathLimit(path string) int64
GetPathLimit returns the effective limit for a given path. Returns the path-specific limit if set, otherwise the global MaxSize.
func (*BodyLimiter) HandleRequest ¶
func (bl *BodyLimiter) HandleRequest(ctx context.Context, req *http.Request, rc *RequestContext) *http.Response
HandleRequest implements RequestHook for integration with PolicyEngine. Returns a 413 response if the body size limit is exceeded.
func (*BodyLimiter) Middleware ¶
func (bl *BodyLimiter) Middleware(next http.Handler) http.Handler
Middleware returns an http.Handler middleware that enforces body size limits.
func (*BodyLimiter) SetPathLimit ¶
func (bl *BodyLimiter) SetPathLimit(pathPrefix string, limit int64)
SetPathLimit sets a custom body size limit for a specific path prefix. This overrides the global MaxSize for requests matching the prefix. Set limit to 0 to disable limits for this path, or -1 to use the global limit.
type Bypass ¶
type Bypass struct {
// Header is the HTTP header name that carries the bypass token.
// Defaults to [DefaultBypassHeader] ("X-SWG-Bypass").
Header string
// Identities is a set of [RequestContext] identity values (e.g.
// usernames from mTLS certificates) that are granted bypass.
// Identity matching is case-sensitive and checked after token
// matching.
Identities map[string]bool
// Logger for bypass events. If nil, bypass is silent.
Logger *slog.Logger
// contains filtered or unexported fields
}
Bypass allows authorized clients to skip content filtering. Clients present a secret token via an HTTP header or are identified by their RequestContext identity. When a request is granted bypass, filtering and policy hooks are skipped and the request is forwarded directly.
Tokens are compared using constant-time comparison to prevent timing side-channels.
Usage:
b := swg.NewBypass()
b.AddToken("debug-token-abc123")
proxy.Bypass = b
Clients then set the header:
curl -H "X-SWG-Bypass: debug-token-abc123" -x http://proxy:8080 http://example.com
func NewBypass ¶
func NewBypass() *Bypass
NewBypass creates a Bypass with the default header name and no tokens. Use Bypass.AddToken or Bypass.GenerateToken to register tokens.
func (*Bypass) AddToken ¶
AddToken registers a bypass token. Duplicate tokens are ignored. AddToken is safe for concurrent use.
func (*Bypass) GenerateToken ¶
GenerateToken creates a cryptographically random 32-byte hex token, registers it, and returns the token string. The returned token is suitable for use in HTTP headers.
func (*Bypass) RemoveToken ¶
RemoveToken revokes a previously registered bypass token. RemoveToken is safe for concurrent use.
func (*Bypass) RevokeAll ¶
func (b *Bypass) RevokeAll()
RevokeAll removes all registered bypass tokens. RevokeAll is safe for concurrent use.
func (*Bypass) ShouldBypass ¶
ShouldBypass reports whether the request should skip content filtering. It checks the bypass header for a valid token using constant-time comparison, then falls back to identity matching via RequestContext. Returns true if bypass is granted.
func (*Bypass) TokenCount ¶
TokenCount returns the number of registered bypass tokens. TokenCount is safe for concurrent use.
type CSVLoader ¶
type CSVLoader struct {
// Path to the CSV file
Path string
// HasHeader indicates if the first row is a header (skipped)
HasHeader bool
// DefaultReason is used when the reason column is empty
DefaultReason string
// DefaultCategory is used when the category column is empty
DefaultCategory string
}
CSVLoader loads rules from a CSV file. Expected CSV format: type,pattern,reason,category Where type is one of: domain, url, regex
func NewCSVLoader ¶
NewCSVLoader creates a new CSV loader for the given file path.
type CertManager ¶
type CertManager struct {
// contains filtered or unexported fields
}
CertManager manages CA and per-host certificate generation for MITM proxying.
func NewCertManager ¶
func NewCertManager(caCertPath, caKeyPath string) (*CertManager, error)
NewCertManager creates a CertManager from existing CA certificate and key files.
func NewCertManagerFromPEM ¶
func NewCertManagerFromPEM(caCertPEM, caKeyPEM []byte) (*CertManager, error)
NewCertManagerFromPEM creates a CertManager from PEM-encoded CA cert and key.
func (*CertManager) GetCertificate ¶
func (cm *CertManager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error)
GetCertificate returns a TLS certificate for the given host, generating one if needed. This is suitable for use as tls.Config.GetCertificate.
func (*CertManager) GetCertificateForHost ¶
func (cm *CertManager) GetCertificateForHost(host string) (*tls.Certificate, error)
GetCertificateForHost returns a TLS certificate for the given hostname.
type CertRotator ¶
type CertRotator struct {
// OnRotate is called after a successful rotation with the new CA subject.
OnRotate func(subject string)
// OnError is called when a rotation attempt fails.
OnError func(err error)
// contains filtered or unexported fields
}
CertRotator wraps a CertManager and adds the ability to atomically swap the underlying CA certificate and key at runtime, e.g. from a SIGHUP handler or periodic refresh. All in-flight TLS handshakes continue using the old CA; new connections pick up the rotated CA immediately.
Host-certificate caches are flushed on every rotation because the old certs were signed by the previous CA.
func NewCertRotator ¶
func NewCertRotator(cm *CertManager, certPath, keyPath string) *CertRotator
NewCertRotator creates a CertRotator that can reload the CA from disk.
func (*CertRotator) CACert ¶
func (cr *CertRotator) CACert() *x509.Certificate
CACert returns the current CA certificate.
func (*CertRotator) CAKey ¶
func (cr *CertRotator) CAKey() *rsa.PrivateKey
CAKey returns the current CA private key.
func (*CertRotator) CacheSize ¶
func (cr *CertRotator) CacheSize() int
CacheSize returns the number of cached host certificates.
func (*CertRotator) CertManager ¶
func (cr *CertRotator) CertManager() *CertManager
CertManager returns the current CertManager. The caller must not hold a reference across a rotation boundary — call this each time you need it.
func (*CertRotator) GetCertificate ¶
func (cr *CertRotator) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error)
GetCertificate implements the tls.Config.GetCertificate callback, delegating to the current CertManager. This should be used instead of cm.GetCertificate when certificate rotation is enabled.
func (*CertRotator) GetCertificateForHost ¶
func (cr *CertRotator) GetCertificateForHost(host string) (*tls.Certificate, error)
GetCertificateForHost generates (or retrieves from cache) a host certificate signed by the current CA.
func (*CertRotator) Rotate ¶
func (cr *CertRotator) Rotate() (*CertManager, error)
Rotate reloads the CA certificate and key from the paths configured at creation time. On success the internal CertManager is swapped atomically and the host-cert cache is cleared. Returns the new CertManager.
func (*CertRotator) RotateFromPEM ¶
func (cr *CertRotator) RotateFromPEM(certPEM, keyPEM []byte) (*CertManager, error)
RotateFromPEM reloads the CA from in-memory PEM bytes.
func (*CertRotator) WatchCAFiles ¶
func (cr *CertRotator) WatchCAFiles(interval func() <-chan time.Time) func()
WatchCAFiles watches the CA cert and key files for changes and automatically rotates when they are modified. Returns a cancel function. This uses a simple polling approach; for production use consider fsnotify or similar.
type ChainFilter ¶
type ChainFilter struct {
Filters []Filter
}
ChainFilter composes multiple Filters into one. Filters are checked in order; the first one that blocks wins.
func (*ChainFilter) ShouldBlock ¶
func (cf *ChainFilter) ShouldBlock(req *http.Request) (bool, string)
ShouldBlock implements Filter.
type ClientAuth ¶
type ClientAuth struct {
// IdentityFromCert controls whether the client certificate's subject
// is used for identity resolution. When true, the certificate's
// CommonName populates [RequestContext.Identity] and the Organization
// fields populate [RequestContext.Groups]. This overrides the
// PolicyEngine's IdentityResolver for mTLS-authenticated clients.
IdentityFromCert bool
// Logger for client auth events.
Logger *slog.Logger
// contains filtered or unexported fields
}
ClientAuth configures mutual TLS (mTLS) client certificate authentication for the proxy listener. When enabled, clients must present a valid TLS certificate signed by one of the trusted client CAs to use the proxy.
The proxy listener is wrapped with TLS so that the initial connection requires a client certificate. The mTLS handshake happens before any HTTP traffic, meaning unauthenticated clients cannot even send a CONNECT request.
ClientAuth integrates with the PolicyEngine identity system: when [ClientAuth.IdentityFromCert] is true, the certificate's Common Name is injected as the client identity and the certificate's Organization fields are used as group memberships. This populates [RequestContext.Identity] and [RequestContext.Groups] for downstream policy decisions.
func NewClientAuth ¶
func NewClientAuth(pool *x509.CertPool) *ClientAuth
NewClientAuth creates a ClientAuth that requires and verifies client certificates against the provided CA certificate pool.
func NewClientAuthFromFile ¶
func NewClientAuthFromFile(path string) (*ClientAuth, error)
NewClientAuthFromFile creates a ClientAuth by loading a PEM-encoded CA certificate bundle from the given file path.
func NewClientAuthFromPEM ¶
func NewClientAuthFromPEM(pemData []byte) (*ClientAuth, error)
NewClientAuthFromPEM creates a ClientAuth from PEM-encoded CA certificates. Multiple certificates may be concatenated in the PEM data.
func (*ClientAuth) AddCACert ¶
func (ca *ClientAuth) AddCACert(cert *x509.Certificate)
AddCACert adds a CA certificate to the trusted pool. This is safe for concurrent use.
func (*ClientAuth) AddCAPEM ¶
func (ca *ClientAuth) AddCAPEM(pemData []byte) error
AddCAPEM appends PEM-encoded CA certificates to the trusted pool. Returns an error if no valid certificates are found.
func (*ClientAuth) IdentityFromConn ¶
func (ca *ClientAuth) IdentityFromConn(conn *tls.Conn) (identity string, groups []string)
IdentityFromConn extracts client identity from a TLS connection's peer certificate. Returns the CommonName as identity and the Organization fields as groups. If the connection has no verified peer certificates, returns empty strings.
func (*ClientAuth) Policy ¶
func (ca *ClientAuth) Policy() tls.ClientAuthType
Policy returns the current TLS client auth policy.
func (*ClientAuth) SetPolicy ¶
func (ca *ClientAuth) SetPolicy(policy tls.ClientAuthType)
SetPolicy sets the TLS client auth policy. The default is tls.RequireAndVerifyClientCert. Use tls.VerifyClientCertIfGiven for optional mTLS where unauthenticated clients are still allowed.
func (*ClientAuth) TLSConfig ¶
func (ca *ClientAuth) TLSConfig() *tls.Config
TLSConfig returns a tls.Config suitable for wrapping the proxy listener. The returned config requires client certificates verified against the trusted CA pool.
func (*ClientAuth) VerifyPeerCertificate ¶
func (ca *ClientAuth) VerifyPeerCertificate(rawCerts [][]byte, _ [][]*x509.Certificate) error
VerifyPeerCertificate returns a function suitable for tls.Config.VerifyPeerCertificate that checks the raw client certificate against the trusted CA pool. This is useful when integrating with custom TLS configurations.
func (*ClientAuth) WrapListener ¶
func (ca *ClientAuth) WrapListener(inner net.Listener, serverCert tls.Certificate) net.Listener
WrapListener wraps a net.Listener with TLS using the ClientAuth configuration. The returned listener performs TLS handshakes with client certificate verification on every accepted connection.
The serverCert is the proxy's own TLS certificate presented to clients. For self-signed proxy deployments, generate one with GenerateCA or use the proxy's existing CertManager CA.
type CompressHandler ¶
type CompressHandler struct {
Handler http.Handler
Config CompressionConfig
}
CompressHandler wraps an http.Handler with response compression.
func NewCompressHandler ¶
func NewCompressHandler(h http.Handler) *CompressHandler
NewCompressHandler creates a compression middleware with default config.
func (*CompressHandler) ServeHTTP ¶
func (c *CompressHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
ServeHTTP implements http.Handler with transparent response compression.
type CompressionConfig ¶
type CompressionConfig struct {
// MinSize is the minimum response size to compress (default: 256 bytes).
// Responses smaller than this are sent uncompressed.
MinSize int
// Level is the compression level (1-9 for gzip, 1-22 for brotli, 1-4 for zstd).
// 0 uses the default level for each algorithm.
Level int
// ContentTypes is a list of content-type prefixes to compress.
// Empty means compress common text types (text/*, application/json, etc.).
ContentTypes []string
// PreferOrder is the preferred encoding order when client accepts multiple.
// Default: ["br", "zstd", "gzip"]
PreferOrder []string
}
CompressionConfig controls response compression behavior.
func DefaultCompressionConfig ¶
func DefaultCompressionConfig() CompressionConfig
DefaultCompressionConfig returns a CompressionConfig with sensible defaults.
type Config ¶
type Config struct {
// Server configuration
Server ServerConfig `mapstructure:"server"`
// TLS/CA configuration
TLS TLSConfig `mapstructure:"tls"`
// Filtering configuration
Filter FilterConfig `mapstructure:"filter"`
// Block page configuration
BlockPage BlockPageConfig `mapstructure:"block_page"`
// Logging configuration
Logging LoggingConfig `mapstructure:"logging"`
}
Config represents the complete proxy configuration.
func DefaultConfig ¶
func DefaultConfig() Config
DefaultConfig returns a Config with sensible defaults.
func LoadConfig ¶
LoadConfig loads configuration from file, environment, and defaults. It searches for config files in the following order: 1. Explicit path (if provided) 2. ./swg.yaml, ./swg.yml, ./swg.json, ./swg.toml 3. $HOME/.swg/config.yaml 4. /etc/swg/config.yaml
func LoadConfigFromReader ¶
LoadConfigFromReader loads configuration from a reader. Useful for testing or embedded configs.
func (*Config) BuildRuleLoader ¶
func (c *Config) BuildRuleLoader() (RuleLoader, error)
BuildRuleLoader creates a RuleLoader from the filter sources configuration.
func (*Config) BuildRuleSet ¶
BuildRuleSet creates a RuleSet from the filter configuration.
type ContentTypeFilter ¶
type ContentTypeFilter struct {
// contains filtered or unexported fields
}
ContentTypeFilter blocks responses based on Content-Type. It is used as a ResponseHook to inspect the upstream response headers.
func NewContentTypeFilter ¶
func NewContentTypeFilter() *ContentTypeFilter
NewContentTypeFilter creates a content-type response filter.
func (*ContentTypeFilter) Block ¶
func (f *ContentTypeFilter) Block(contentTypePrefix, reason string)
Block adds a content-type prefix to the block list. For example, "application/x-executable" blocks that exact type, while "application/" blocks all application/* types.
func (*ContentTypeFilter) HandleResponse ¶
func (f *ContentTypeFilter) HandleResponse(_ context.Context, _ *http.Request, resp *http.Response, _ *RequestContext) *http.Response
HandleResponse implements ResponseHook.
type DomainFilter ¶
type DomainFilter struct {
// contains filtered or unexported fields
}
DomainFilter is a simple filter that blocks requests to specific domains.
func NewDomainFilter ¶
func NewDomainFilter() *DomainFilter
NewDomainFilter creates a new domain-based filter.
func (*DomainFilter) AddDomain ¶
func (f *DomainFilter) AddDomain(domain string)
AddDomain adds a domain to the blocklist. Supports wildcards: "*.example.com" blocks all subdomains.
func (*DomainFilter) AddDomains ¶
func (f *DomainFilter) AddDomains(domains []string)
AddDomains adds multiple domains to the blocklist.
func (*DomainFilter) ShouldBlock ¶
func (f *DomainFilter) ShouldBlock(req *http.Request) (bool, string)
ShouldBlock implements Filter.
type ErrorResponse ¶
type ErrorResponse struct {
Error string `json:"error"`
}
ErrorResponse is returned for error conditions.
type ExporterType ¶
type ExporterType string
ExporterType defines the type of trace exporter to use.
const ( // ExporterOTLPHTTP exports traces via OTLP over HTTP. ExporterOTLPHTTP ExporterType = "otlp-http" // ExporterOTLPGRPC exports traces via OTLP over gRPC. ExporterOTLPGRPC ExporterType = "otlp-grpc" )
type Filter ¶
type Filter interface {
// ShouldBlock returns true if the request should be blocked, along with a reason.
ShouldBlock(req *http.Request) (blocked bool, reason string)
}
Filter determines whether a request should be blocked.
type FilterConfig ¶
type FilterConfig struct {
// Enabled determines if filtering is active
Enabled bool `mapstructure:"enabled"`
// Domains is a list of domains to block
Domains []string `mapstructure:"domains"`
// URLs is a list of URL prefixes to block
URLs []string `mapstructure:"urls"`
// Regex is a list of regex patterns to block
Regex []string `mapstructure:"regex"`
// Rules is a list of full rule definitions
Rules []RuleConfig `mapstructure:"rules"`
// Sources defines external rule sources
Sources []SourceConfig `mapstructure:"sources"`
// ReloadInterval for external sources (0 = no auto-reload)
ReloadInterval time.Duration `mapstructure:"reload_interval"`
}
FilterConfig contains filtering settings.
type FilterFunc ¶
FilterFunc is a function adapter for Filter.
func (FilterFunc) ShouldBlock ¶
func (f FilterFunc) ShouldBlock(req *http.Request) (bool, string)
ShouldBlock calls the underlying function to determine if a request should be blocked.
type GroupPolicyFilter ¶
type GroupPolicyFilter struct {
Default Filter // applied when no group matches
// contains filtered or unexported fields
}
GroupPolicyFilter applies different filters based on the client's resolved group membership. It reads groups from the RequestContext.
func NewGroupPolicyFilter ¶
func NewGroupPolicyFilter() *GroupPolicyFilter
NewGroupPolicyFilter creates a group-based policy filter.
func (*GroupPolicyFilter) SetPolicy ¶
func (gf *GroupPolicyFilter) SetPolicy(group string, filter Filter)
SetPolicy assigns a filter to a group name.
func (*GroupPolicyFilter) ShouldBlock ¶
func (gf *GroupPolicyFilter) ShouldBlock(req *http.Request) (bool, string)
ShouldBlock implements Filter. Checks the RequestContext for group membership and applies the first matching group policy.
type HealthChecker ¶
type HealthChecker struct {
// ReadinessChecks are optional functions that must all return nil
// for the readiness probe to pass. If empty, readiness follows liveness.
ReadinessChecks []ReadinessCheck
// contains filtered or unexported fields
}
HealthChecker provides liveness and readiness probes for the proxy. It tracks whether the proxy has started successfully and can optionally run custom readiness checks (e.g., verifying filter rules are loaded).
func NewHealthChecker ¶
func NewHealthChecker() *HealthChecker
NewHealthChecker creates a new HealthChecker.
func (*HealthChecker) HandleHealthz ¶
func (h *HealthChecker) HandleHealthz(w http.ResponseWriter, _ *http.Request)
HandleHealthz handles the /healthz liveness probe endpoint.
func (*HealthChecker) HandleReadyz ¶
func (h *HealthChecker) HandleReadyz(w http.ResponseWriter, _ *http.Request)
HandleReadyz handles the /readyz readiness probe endpoint.
func (*HealthChecker) IsAlive ¶
func (h *HealthChecker) IsAlive() bool
IsAlive returns true if the proxy is alive.
func (*HealthChecker) IsReady ¶
func (h *HealthChecker) IsReady() bool
IsReady returns true if the proxy is ready to serve traffic. If ReadinessChecks are configured, all must pass. Otherwise, readiness follows the explicitly set ready state.
func (*HealthChecker) SetAlive ¶
func (h *HealthChecker) SetAlive(alive bool)
SetAlive marks the proxy as alive (liveness probe passes).
func (*HealthChecker) SetReady ¶
func (h *HealthChecker) SetReady(ready bool)
SetReady marks the proxy as ready (readiness probe passes).
type HealthResponse ¶
type HealthResponse struct {
Status string `json:"status"`
Uptime string `json:"uptime,omitempty"`
Reason string `json:"reason,omitempty"`
Details []string `json:"details,omitempty"`
}
HealthResponse is the JSON body returned by health endpoints.
type IPIdentityResolver ¶
type IPIdentityResolver struct {
// contains filtered or unexported fields
}
IPIdentityResolver maps client IPs to identity/groups.
func NewIPIdentityResolver ¶
func NewIPIdentityResolver() *IPIdentityResolver
NewIPIdentityResolver creates an empty IP-based identity resolver.
func (*IPIdentityResolver) AddCIDR ¶
func (r *IPIdentityResolver) AddCIDR(cidr, identity string, groups []string) error
AddCIDR maps a CIDR range to an identity and groups.
func (*IPIdentityResolver) AddIP ¶
func (r *IPIdentityResolver) AddIP(ip, identity string, groups []string)
AddIP maps a single IP to an identity and groups.
type IdentityResolver ¶
type IdentityResolver interface {
Resolve(req *http.Request) (identity string, groups []string, err error)
}
IdentityResolver determines who a client is from the request. This drives per-user/group policy decisions. Implementations might use client certificates, Proxy-Authorization headers, IP-to-user mappings, or external auth services.
type IdentityResolverFunc ¶
IdentityResolverFunc is a function adapter for IdentityResolver.
type LoggingConfig ¶
type LoggingConfig struct {
// Level is the log level: debug, info, warn, error
Level string `mapstructure:"level"`
// Format is the log format: text, json
Format string `mapstructure:"format"`
// Output is where to write logs: stdout, stderr, or file path
Output string `mapstructure:"output"`
}
LoggingConfig contains logging settings.
type MessageResponse ¶
type MessageResponse struct {
Message string `json:"message"`
}
MessageResponse is returned for successful mutations.
type Metrics ¶
type Metrics struct {
// contains filtered or unexported fields
}
Metrics holds all Prometheus metrics for the proxy.
func NewMetrics ¶
func NewMetrics() *Metrics
NewMetrics creates a new Metrics instance with all collectors registered.
func (*Metrics) DecActiveConns ¶
func (m *Metrics) DecActiveConns()
DecActiveConns decrements the active connection gauge.
func (*Metrics) IncActiveConns ¶
func (m *Metrics) IncActiveConns()
IncActiveConns increments the active connection gauge.
func (*Metrics) RecordBlocked ¶
RecordBlocked records a blocked request.
func (*Metrics) RecordCertCacheHit ¶
func (m *Metrics) RecordCertCacheHit()
RecordCertCacheHit records a certificate cache hit.
func (*Metrics) RecordCertCacheMiss ¶
func (m *Metrics) RecordCertCacheMiss()
RecordCertCacheMiss records a certificate cache miss.
func (*Metrics) RecordFilterReload ¶
func (m *Metrics) RecordFilterReload()
RecordFilterReload records a successful filter reload.
func (*Metrics) RecordFilterReloadError ¶
func (m *Metrics) RecordFilterReloadError()
RecordFilterReloadError records a failed filter reload.
func (*Metrics) RecordRequest ¶
RecordRequest records a processed request.
func (*Metrics) RecordRequestDuration ¶
RecordRequestDuration records the duration of a request.
func (*Metrics) RecordTLSHandshakeError ¶
func (m *Metrics) RecordTLSHandshakeError()
RecordTLSHandshakeError records a TLS handshake failure.
func (*Metrics) RecordUpstreamError ¶
RecordUpstreamError records an upstream connection error.
func (*Metrics) SetCertCacheSize ¶
SetCertCacheSize sets the certificate cache size gauge.
func (*Metrics) SetFilterRuleCount ¶
SetFilterRuleCount sets the current filter rule count.
type MultiLoader ¶
type MultiLoader struct {
Loaders []RuleLoader
}
MultiLoader combines multiple loaders into one.
func NewMultiLoader ¶
func NewMultiLoader(loaders ...RuleLoader) *MultiLoader
NewMultiLoader creates a loader that combines rules from multiple sources.
type PACGenerator ¶
type PACGenerator struct {
// ProxyAddr is the proxy address in host:port format (e.g., "proxy.local:8080").
ProxyAddr string
// BypassDomains are domains that should bypass the proxy (direct connection).
BypassDomains []string
// BypassNetworks are CIDR networks that should bypass the proxy.
BypassNetworks []string
// FallbackDirect determines whether to fall back to direct connection
// if the proxy is unreachable.
FallbackDirect bool
}
PACGenerator generates Proxy Auto-Configuration (PAC) files.
func NewPACGenerator ¶
func NewPACGenerator(proxyAddr string) *PACGenerator
NewPACGenerator creates a PACGenerator for the given proxy address.
func (*PACGenerator) AddBypassDomain ¶
func (g *PACGenerator) AddBypassDomain(domain string)
AddBypassDomain adds a domain to the bypass list. Supports wildcards: ".example.com" bypasses all subdomains.
func (*PACGenerator) AddBypassNetwork ¶
func (g *PACGenerator) AddBypassNetwork(cidr string)
AddBypassNetwork adds a CIDR network to the bypass list.
func (*PACGenerator) Generate ¶
func (g *PACGenerator) Generate(w io.Writer) error
Generate writes the PAC file content to the given writer.
func (*PACGenerator) GenerateString ¶
func (g *PACGenerator) GenerateString() (string, error)
GenerateString returns the PAC file content as a string.
func (*PACGenerator) ServeHTTP ¶
func (g *PACGenerator) ServeHTTP(w http.ResponseWriter, r *http.Request)
ServeHTTP implements http.Handler, serving the PAC file with the correct content type.
func (*PACGenerator) WriteFile ¶
func (g *PACGenerator) WriteFile(path string) error
WriteFile writes the PAC file to the given path.
type PolicyEngine ¶
type PolicyEngine struct {
// RequestHooks are called in order when a request arrives.
// Any hook may short-circuit by returning a response.
RequestHooks []RequestHook
// ResponseHooks are called in order after the upstream response
// is received. Any hook may replace the response.
ResponseHooks []ResponseHook
// IdentityResolver resolves client identity before hooks run.
IdentityResolver IdentityResolver
// BodyScanners inspect response bodies. They run after
// ResponseHooks, only for responses whose Content-Type matches
// ScanContentTypes (or all responses if ScanContentTypes is empty).
BodyScanners []ResponseBodyScanner
// ScanContentTypes limits body scanning to responses with matching
// Content-Type prefixes (e.g. "application/octet-stream",
// "application/zip"). Empty means scan all responses.
ScanContentTypes []string
// MaxScanSize is the maximum number of bytes to buffer for body
// scanning. Responses larger than this are passed through without
// scanning. Default is 10 MiB.
MaxScanSize int64
}
PolicyEngine manages the request/response pipeline with hooks, identity resolution, and body scanning. It is set on Proxy.Policy.
func NewPolicyEngine ¶
func NewPolicyEngine() *PolicyEngine
NewPolicyEngine creates a PolicyEngine with sensible defaults.
func (*PolicyEngine) ProcessRequest ¶
func (pe *PolicyEngine) ProcessRequest(ctx context.Context, req *http.Request) (*RequestContext, *http.Response)
ProcessRequest runs the request-side pipeline: identity resolution then request hooks. Returns a non-nil response to short-circuit.
func (*PolicyEngine) ProcessResponse ¶
func (pe *PolicyEngine) ProcessResponse(ctx context.Context, req *http.Request, resp *http.Response, rc *RequestContext) (*http.Response, error)
ProcessResponse runs the response-side pipeline: response hooks then body scanners. Returns the (possibly replaced) response.
type Proxy ¶
type Proxy struct {
// Addr is the address to listen on (e.g., ":8080")
Addr string
// CertManager handles dynamic certificate generation
CertManager *CertManager
// Filter determines whether requests should be blocked
Filter Filter
// BlockPageURL is the URL to redirect blocked requests to (optional)
BlockPageURL string
// BlockPage is a custom block page template (optional, uses default if nil)
BlockPage *BlockPage
// Logger for proxy events
Logger *slog.Logger
// Transport for outbound requests (optional, uses default if nil)
Transport http.RoundTripper
// Metrics collects Prometheus metrics (optional)
Metrics *Metrics
// PACHandler serves PAC files at /proxy.pac (optional)
PACHandler *PACGenerator
// HealthChecker provides /healthz and /readyz endpoints (optional)
HealthChecker *HealthChecker
// AccessLog writes structured access log entries for each request (optional)
AccessLog *AccessLogger
// UpstreamProxy forwards requests through a parent proxy (optional).
// When set, CONNECT tunnels are established via the upstream proxy
// and plain HTTP requests are forwarded through it.
UpstreamProxy *UpstreamProxy
// RateLimiter provides per-client request throttling (optional).
// When set, requests exceeding the rate limit receive 429 responses.
RateLimiter *RateLimiter
// TransportPool provides a connection-pooled transport with HTTP/2
// support (optional). When set, its Transport() is used as the base
// transport instead of the Transport field.
TransportPool *TransportPool
// Policy provides lifecycle hooks, identity resolution, and response
// body scanning (optional). When set, request hooks run before
// filtering and response hooks run after the upstream response is
// received. This enables pluggable AV scanning, DLP, content-type
// blocking, per-group policies, and more.
Policy *PolicyEngine
// Admin provides REST endpoints for runtime rule management,
// status inspection, and filter reloads (optional). When set,
// requests matching the AdminAPI.PathPrefix are routed to the
// admin handler instead of being proxied.
Admin *AdminAPI
// ClientAuth enables mutual TLS (mTLS) on the proxy listener.
// When set, clients must present a valid certificate signed by
// a trusted CA to connect. See [ClientAuth] for configuration.
ClientAuth *ClientAuth
// Bypass allows authorized clients to skip content filtering.
// When set, requests carrying a valid bypass token in an HTTP
// header or originating from a whitelisted identity skip the
// filter and policy hooks. See [Bypass] for configuration.
Bypass *Bypass
// contains filtered or unexported fields
}
Proxy is an HTTPS MITM proxy that intercepts TLS traffic for content filtering.
func NewProxy ¶
func NewProxy(addr string, cm *CertManager) *Proxy
NewProxy creates a new HTTPS MITM proxy.
func (*Proxy) ListenAndServe ¶
ListenAndServe starts the proxy server. When [Proxy.ClientAuth] is set, the listener is wrapped with TLS to enforce mutual TLS authentication before any HTTP traffic.
type ProxyTracer ¶
type ProxyTracer struct {
// contains filtered or unexported fields
}
ProxyTracer provides tracing specifically for proxy operations.
func NewProxyTracer ¶
func NewProxyTracer(t *Tracer) *ProxyTracer
NewProxyTracer creates a ProxyTracer wrapping a Tracer.
func (*ProxyTracer) Enabled ¶
func (pt *ProxyTracer) Enabled() bool
Enabled returns whether tracing is enabled.
func (*ProxyTracer) RecordError ¶
func (pt *ProxyTracer) RecordError(ctx context.Context, err error)
RecordError records an error on the current span.
func (*ProxyTracer) SetAllowed ¶
func (pt *ProxyTracer) SetAllowed(ctx context.Context)
SetAllowed marks the request as allowed through the filter.
func (*ProxyTracer) SetBlocked ¶
func (pt *ProxyTracer) SetBlocked(ctx context.Context, reason string)
SetBlocked marks the request as blocked by the filter.
func (*ProxyTracer) SetIdentity ¶
func (pt *ProxyTracer) SetIdentity(ctx context.Context, identity string, groups []string)
SetIdentity sets identity information on the span.
func (*ProxyTracer) SetUpstreamResponse ¶
func (pt *ProxyTracer) SetUpstreamResponse(ctx context.Context, statusCode int, contentLength int64)
SetUpstreamResponse records upstream response details.
func (*ProxyTracer) StartBodyScan ¶
func (pt *ProxyTracer) StartBodyScan(ctx context.Context, contentType string, size int64) (context.Context, trace.Span)
StartBodyScan starts a span for response body scanning.
func (*ProxyTracer) StartCertGeneration ¶
func (pt *ProxyTracer) StartCertGeneration(ctx context.Context, host string) (context.Context, trace.Span)
StartCertGeneration starts a span for certificate generation.
func (*ProxyTracer) StartConnect ¶
func (pt *ProxyTracer) StartConnect(ctx context.Context, host string, clientAddr string) (context.Context, trace.Span)
StartConnect starts a span for a CONNECT tunnel establishment.
func (*ProxyTracer) StartFilter ¶
StartFilter starts a span for filter evaluation.
func (*ProxyTracer) StartRequest ¶
func (pt *ProxyTracer) StartRequest(ctx context.Context, r *http.Request) (context.Context, trace.Span)
StartRequest starts a span for an incoming proxy request.
func (*ProxyTracer) StartTLSHandshake ¶
func (pt *ProxyTracer) StartTLSHandshake(ctx context.Context, host string) (context.Context, trace.Span)
StartTLSHandshake starts a span for TLS handshake with client.
type RateLimiter ¶
type RateLimiter struct {
// Rate is the number of requests permitted per second per client.
Rate float64
// Burst is the maximum number of requests a client can make in a
// single burst before being throttled.
Burst int
// CleanupInterval controls how often stale buckets are removed.
// Defaults to 1 minute.
CleanupInterval time.Duration
// contains filtered or unexported fields
}
RateLimiter provides per-client request throttling using a token-bucket algorithm. Each client IP gets an independent bucket that refills at a steady rate up to a configurable burst size.
func NewRateLimiter ¶
func NewRateLimiter(rate float64, burst int) *RateLimiter
NewRateLimiter creates a new per-client rate limiter. rate is requests/second, burst is the max tokens a client can accumulate.
func (*RateLimiter) Allow ¶
func (rl *RateLimiter) Allow(addr string) bool
Allow returns true if the request from the given client address is permitted under the rate limit.
func (*RateLimiter) AllowHTTP ¶
func (rl *RateLimiter) AllowHTTP(w http.ResponseWriter, r *http.Request) bool
AllowHTTP checks the rate limit for the given HTTP request and writes a 429 Too Many Requests response if the client is throttled. Returns true if the request is allowed.
func (*RateLimiter) ClientCount ¶
func (rl *RateLimiter) ClientCount() int
ClientCount returns the number of tracked clients.
func (*RateLimiter) Close ¶
func (rl *RateLimiter) Close()
Close stops the background cleanup goroutine.
type ReadinessCheck ¶
type ReadinessCheck func() error
ReadinessCheck is a function that returns nil if the component is ready, or an error describing why it is not.
type ReloadFunc ¶
ReloadFunc is called on each SIGHUP. It should reload configuration and return the new Filter (or nil to keep the current one) and any error.
type ReloadableFilter ¶
type ReloadableFilter struct {
// OnReload is called after successful reload with the rule count
OnReload func(count int)
// OnError is called when reload fails
OnError func(err error)
// contains filtered or unexported fields
}
ReloadableFilter wraps a RuleSet with automatic reloading from a RuleLoader.
func NewReloadableFilter ¶
func NewReloadableFilter(loader RuleLoader) *ReloadableFilter
NewReloadableFilter creates a new filter that can reload rules from a loader.
func (*ReloadableFilter) Count ¶
func (rf *ReloadableFilter) Count() int
Count returns the current number of rules.
func (*ReloadableFilter) Load ¶
func (rf *ReloadableFilter) Load(ctx context.Context) error
Load loads rules from the configured loader, replacing existing rules.
func (*ReloadableFilter) RuleSet ¶
func (rf *ReloadableFilter) RuleSet() *RuleSet
RuleSet returns the underlying RuleSet for direct rule manipulation. The returned RuleSet is thread-safe for concurrent reads and writes.
func (*ReloadableFilter) ShouldBlock ¶
func (rf *ReloadableFilter) ShouldBlock(req *http.Request) (bool, string)
ShouldBlock implements the Filter interface.
func (*ReloadableFilter) StartAutoReload ¶
func (rf *ReloadableFilter) StartAutoReload(ctx context.Context, interval time.Duration) context.CancelFunc
StartAutoReload starts a goroutine that reloads rules at the specified interval. Returns a cancel function to stop the reload goroutine.
type RequestContext ¶
type RequestContext struct {
// ClientIP is the connecting client's IP (without port).
ClientIP string
// Identity is resolved by an IdentityResolver (optional).
// May represent a username, group, device ID, or any string.
Identity string
// Groups the client belongs to (resolved by IdentityResolver).
Groups []string
// Tags are arbitrary key-value metadata set by hooks.
Tags map[string]string
// Blocked is set to true when any stage decides to block the request.
Blocked bool
// BlockReason is the human-readable reason for blocking.
BlockReason string
// StartTime is when the request was first received.
StartTime time.Time
}
RequestContext carries metadata through the proxy request lifecycle. Hooks and middleware attach information here for downstream stages.
func GetRequestContext ¶
func GetRequestContext(ctx context.Context) *RequestContext
GetRequestContext retrieves the RequestContext from the context, or nil.
type RequestHook ¶
type RequestHook interface {
HandleRequest(ctx context.Context, req *http.Request, rc *RequestContext) *http.Response
}
RequestHook is called when a request is first received, before any filtering. Hooks may inspect, modify, or block the request. They may also resolve identity, attach tags, or perform early access control.
Returning a non-nil *http.Response short-circuits the pipeline: that response is sent to the client and no further hooks or forwarding occur.
type RequestHookFunc ¶
type RequestHookFunc func(ctx context.Context, req *http.Request, rc *RequestContext) *http.Response
RequestHookFunc is a function adapter for RequestHook.
func (RequestHookFunc) HandleRequest ¶
func (f RequestHookFunc) HandleRequest(ctx context.Context, req *http.Request, rc *RequestContext) *http.Response
type ResponseBodyScanner ¶
type ResponseBodyScanner interface {
Scan(ctx context.Context, body []byte, req *http.Request, resp *http.Response) (ScanResult, error)
}
ResponseBodyScanner inspects response bodies for threats or policy violations. Implementations can wrap AV engines, DLP scanners, keyword detectors, or any content analysis tool.
Scan receives the full body as a byte slice (up to the configured MaxScanSize) plus the request and response for context. Returning an error causes the proxy to serve a 502 error to the client.
type ResponseBodyScannerFunc ¶
type ResponseBodyScannerFunc func(ctx context.Context, body []byte, req *http.Request, resp *http.Response) (ScanResult, error)
ResponseBodyScannerFunc is a function adapter for ResponseBodyScanner.
type ResponseHook ¶
type ResponseHook interface {
HandleResponse(ctx context.Context, req *http.Request, resp *http.Response, rc *RequestContext) *http.Response
}
ResponseHook is called after the upstream response is received but before it is sent back to the client. Hooks may inspect headers, content-type, or the response body. They may replace the response entirely (e.g. with a block page) by returning a non-nil *http.Response.
The original response body is readable (and should be closed by the hook if it replaces the response). For body inspection, use ResponseBodyScanner which handles buffering and streaming.
type ResponseHookFunc ¶
type ResponseHookFunc func(ctx context.Context, req *http.Request, resp *http.Response, rc *RequestContext) *http.Response
ResponseHookFunc is a function adapter for ResponseHook.
func (ResponseHookFunc) HandleResponse ¶
func (f ResponseHookFunc) HandleResponse(ctx context.Context, req *http.Request, resp *http.Response, rc *RequestContext) *http.Response
type Rule ¶
type Rule struct {
// Type of rule: "domain", "url", "regex"
Type string
// Pattern is the matching pattern (domain, URL prefix, or regex)
Pattern string
// Reason for blocking (shown to user)
Reason string
// Category for grouping/reporting (optional)
Category string
// contains filtered or unexported fields
}
Rule represents a blocking rule that can match domains, URLs, or patterns.
type RuleConfig ¶
type RuleConfig struct {
Type string `mapstructure:"type"`
Pattern string `mapstructure:"pattern"`
Reason string `mapstructure:"reason"`
Category string `mapstructure:"category"`
}
RuleConfig represents a single blocking rule in config.
type RuleLoader ¶
type RuleLoader interface {
// Load reads rules from the source and returns them.
Load(ctx context.Context) ([]Rule, error)
}
RuleLoader defines the interface for loading rules from various sources.
type RuleLoaderFunc ¶
RuleLoaderFunc is a function adapter for RuleLoader.
type RuleRequest ¶
type RuleRequest struct {
Type string `json:"type"`
Pattern string `json:"pattern"`
Reason string `json:"reason,omitempty"`
Category string `json:"category,omitempty"`
}
RuleRequest is the body for POST /api/rules and DELETE /api/rules.
type RuleSet ¶
type RuleSet struct {
// contains filtered or unexported fields
}
RuleSet is a collection of blocking rules with efficient lookup.
func (*RuleSet) Match ¶
Match checks if a request matches any rule in the set. Returns the matching rule and true if blocked, nil and false otherwise.
func (*RuleSet) RemoveRule ¶
RemoveRule removes the first rule matching the given type and pattern. Returns true if a rule was removed.
type RulesResponse ¶
RulesResponse is returned by GET /api/rules.
type SIGHUPReloader ¶
type SIGHUPReloader struct {
// contains filtered or unexported fields
}
SIGHUPReloader watches for SIGHUP signals and reloads the proxy filter. Call Cancel to stop watching.
func WatchSIGHUP ¶
func WatchSIGHUP(proxy *Proxy, reload ReloadFunc, logger *slog.Logger) *SIGHUPReloader
WatchSIGHUP starts a goroutine that listens for SIGHUP signals and calls the reload function. If reload returns a non-nil Filter, it is assigned to the proxy. The returned SIGHUPReloader can be used to stop watching.
type ScanResult ¶
type ScanResult struct {
Verdict ScanVerdict
// Reason is set when Verdict is VerdictBlock.
Reason string
// ReplacementBody is set when Verdict is VerdictReplace. The caller
// is responsible for closing it.
ReplacementBody io.ReadCloser
// ReplacementContentType overrides Content-Type when replacing.
ReplacementContentType string
}
ScanResult holds the outcome of a body scan.
type ScanVerdict ¶
type ScanVerdict int
ScanVerdict is the result of a ResponseBodyScanner inspection.
const ( // VerdictAllow means the content is clean. VerdictAllow ScanVerdict = iota // VerdictBlock means the content should be blocked. VerdictBlock // VerdictReplace means the scanner is providing a replacement body. VerdictReplace )
type ServerConfig ¶
type ServerConfig struct {
// Address to listen on (e.g., ":8080", "0.0.0.0:8080")
Addr string `mapstructure:"addr"`
// ReadTimeout for incoming connections
ReadTimeout time.Duration `mapstructure:"read_timeout"`
// WriteTimeout for outgoing responses
WriteTimeout time.Duration `mapstructure:"write_timeout"`
// IdleTimeout for keep-alive connections
IdleTimeout time.Duration `mapstructure:"idle_timeout"`
}
ServerConfig contains server-related settings.
type SourceConfig ¶
type SourceConfig struct {
// Type of source: "csv", "url"
Type string `mapstructure:"type"`
// Path for file-based sources
Path string `mapstructure:"path"`
// URL for remote sources
URL string `mapstructure:"url"`
// HasHeader indicates if CSV has a header row
HasHeader bool `mapstructure:"has_header"`
}
SourceConfig defines an external rule source.
type StaticLoader ¶
type StaticLoader struct {
Rules []Rule
}
StaticLoader returns a fixed set of rules. Useful for testing or combining with other loaders.
func NewStaticLoader ¶
func NewStaticLoader(rules ...Rule) *StaticLoader
NewStaticLoader creates a loader with a fixed set of rules.
type StatusResponse ¶
type StatusResponse struct {
Status string `json:"status"`
RuleCount int `json:"rule_count"`
Uptime string `json:"uptime,omitempty"`
Filter string `json:"filter_type"`
}
StatusResponse is returned by GET /api/status.
type TLSConfig ¶
type TLSConfig struct {
// CACert is the path to the CA certificate file (for self-signed mode)
CACert string `mapstructure:"ca_cert"`
// CAKey is the path to the CA private key file (for self-signed mode)
CAKey string `mapstructure:"ca_key"`
// Organization name for generated certificates
Organization string `mapstructure:"organization"`
// CertValidityDays for generated host certificates
CertValidityDays int `mapstructure:"cert_validity_days"`
// ACME configuration for Let's Encrypt certificates (optional)
// When enabled, certificates are obtained from Let's Encrypt instead of self-signed.
ACME *ACMEConfig `mapstructure:"acme"`
}
TLSConfig contains TLS/certificate settings.
type TimeRule ¶
type TimeRule struct {
// Inner is the filter to apply during the active window.
Inner Filter
// StartHour is the hour (0-23) when the rule becomes active.
StartHour int
// EndHour is the hour (0-23) when the rule becomes inactive.
// If EndHour < StartHour, the window wraps past midnight.
EndHour int
// Weekdays limits the rule to specific days. Empty means every day.
Weekdays []time.Weekday
// Location for time evaluation. Defaults to UTC.
Location *time.Location
// NowFunc returns the current time. Defaults to time.Now.
// Exposed for testing.
NowFunc func() time.Time
}
TimeRule wraps a Filter and only activates it during specific time windows. Outside the window, the inner filter is bypassed.
type Tracer ¶
type Tracer struct {
// contains filtered or unexported fields
}
Tracer provides OpenTelemetry tracing for the proxy.
func NewTracer ¶
func NewTracer(cfg TracingConfig) (*Tracer, error)
NewTracer creates a new Tracer with the given configuration.
func (*Tracer) SpanFromContext ¶
SpanFromContext returns the current span from the context.
func (*Tracer) StartSpan ¶
func (t *Tracer) StartSpan(ctx context.Context, name string, opts ...trace.SpanStartOption) (context.Context, trace.Span)
StartSpan starts a new span with the given name.
func (*Tracer) TracerProvider ¶
func (t *Tracer) TracerProvider() *sdktrace.TracerProvider
TracerProvider returns the underlying TracerProvider.
type TracingConfig ¶
type TracingConfig struct {
// Enabled enables or disables tracing.
Enabled bool
// ServiceName is the name of the service reported in traces.
// Defaults to "swg-proxy".
ServiceName string
// ServiceVersion is the version of the service reported in traces.
ServiceVersion string
// Exporter specifies which exporter to use.
// Defaults to ExporterOTLPHTTP.
Exporter ExporterType
// Endpoint is the OTLP collector endpoint.
// For OTLP HTTP: defaults to "localhost:4318"
// For OTLP gRPC: defaults to "localhost:4317"
Endpoint string
// Insecure disables TLS for the exporter connection.
Insecure bool
// Headers are additional headers to send with OTLP requests.
Headers map[string]string
// SampleRate is the sampling rate (0.0 to 1.0).
// 1.0 means sample everything, 0.0 means sample nothing.
// Defaults to 1.0.
SampleRate float64
// BatchTimeout is the maximum time to wait before exporting a batch.
// Defaults to 5 seconds.
BatchTimeout time.Duration
// MaxExportBatchSize is the maximum number of spans per batch.
// Defaults to 512.
MaxExportBatchSize int
// MaxQueueSize is the maximum number of spans to queue.
// Defaults to 2048.
MaxQueueSize int
// ResourceAttributes are additional attributes to add to the resource.
ResourceAttributes map[string]string
}
TracingConfig configures OpenTelemetry tracing for the proxy.
func DefaultTracingConfig ¶
func DefaultTracingConfig() TracingConfig
DefaultTracingConfig returns a TracingConfig with sensible defaults.
type TracingMiddleware ¶
type TracingMiddleware struct {
// contains filtered or unexported fields
}
TracingMiddleware wraps an http.Handler with tracing.
func NewTracingMiddleware ¶
func NewTracingMiddleware(tracer *Tracer, next http.Handler, opts TracingMiddlewareOptions) *TracingMiddleware
NewTracingMiddleware creates a new tracing middleware.
func (*TracingMiddleware) ServeHTTP ¶
func (m *TracingMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request)
ServeHTTP implements http.Handler.
type TracingMiddlewareOptions ¶
type TracingMiddlewareOptions struct {
// IncludeHeaders includes request/response headers in span attributes.
IncludeHeaders bool
// HeadersToCapture specifies which headers to capture (if IncludeHeaders is true).
// If empty, captures all headers.
HeadersToCapture []string
// IncludeQuery includes the query string in span attributes.
IncludeQuery bool
// SkipPaths are URL paths to skip tracing for.
SkipPaths []string
}
TracingMiddlewareOptions configures the tracing middleware.
type TransportPool ¶
type TransportPool struct {
// MaxIdleConns is the total maximum number of idle connections
// across all hosts. Zero means the default (100).
MaxIdleConns int
// MaxIdleConnsPerHost is the maximum number of idle connections
// per host. Zero means the default (2 per host).
MaxIdleConnsPerHost int
// MaxConnsPerHost limits the total number of connections per host,
// including connections in the dialing, active, and idle states.
// Zero means no limit.
MaxConnsPerHost int
// IdleConnTimeout is how long an idle connection remains in the
// pool before being closed. Zero means the default (90 seconds).
IdleConnTimeout time.Duration
// DialTimeout is the maximum time to wait for a TCP dial to complete.
// Zero means the default (30 seconds).
DialTimeout time.Duration
// TLSHandshakeTimeout is the maximum time to wait for a TLS
// handshake. Zero means the default (10 seconds).
TLSHandshakeTimeout time.Duration
// ResponseHeaderTimeout is the maximum time to wait for a server's
// response headers after the request has been fully written.
// Zero means no timeout.
ResponseHeaderTimeout time.Duration
// EnableHTTP2 enables HTTP/2 negotiation with upstream servers.
// When true, the transport will attempt h2 via ALPN during TLS.
EnableHTTP2 bool
// TLSConfig provides custom TLS settings for upstream connections.
// If nil, a default configuration is used.
TLSConfig *tls.Config
// DisableKeepAlives disables HTTP keep-alives; each request will
// use a fresh connection. This overrides connection pool settings.
DisableKeepAlives bool
// WriteBufferSize specifies the size of the write buffer used
// when writing to the transport. Zero uses the default.
WriteBufferSize int
// ReadBufferSize specifies the size of the read buffer used
// when reading from the transport. Zero uses the default.
ReadBufferSize int
// contains filtered or unexported fields
}
TransportPool provides a configurable HTTP transport with connection pooling and optional HTTP/2 support. It wraps http.Transport with sensible defaults for a forward proxy workload and exposes connection pool statistics for monitoring.
func NewTransportPool ¶
func NewTransportPool() *TransportPool
NewTransportPool creates a TransportPool with sensible proxy defaults.
func (*TransportPool) Build ¶
func (tp *TransportPool) Build() *http.Transport
Build creates the underlying http.Transport. Call this after setting all configuration fields. It is safe to call multiple times; each call creates a fresh transport and closes idle connections on the previous one.
func (*TransportPool) CloseIdleConnections ¶
func (tp *TransportPool) CloseIdleConnections()
CloseIdleConnections closes all idle connections in the pool.
func (*TransportPool) Stats ¶
func (tp *TransportPool) Stats() TransportPoolStats
Stats returns a snapshot of transport statistics.
func (*TransportPool) Transport ¶
func (tp *TransportPool) Transport() http.RoundTripper
Transport returns an http.RoundTripper that wraps the pooled transport with request counting. If [Build] has not been called, it is called automatically.
type TransportPoolStats ¶
TransportPoolStats holds a snapshot of connection pool statistics.
type URLLoader ¶
type URLLoader struct {
// URL to fetch rules from
URL string
// Client for HTTP requests (uses http.DefaultClient if nil)
Client *http.Client
// HasHeader indicates if the first row is a header
HasHeader bool
// DefaultReason is used when the reason column is empty
DefaultReason string
}
URLLoader loads rules from an HTTP endpoint. Expects the same CSV format as CSVLoader.
func NewURLLoader ¶
NewURLLoader creates a loader that fetches rules from a URL.
type UpstreamAuth ¶
UpstreamAuth holds basic-auth credentials for an upstream proxy.
type UpstreamProxy ¶
type UpstreamProxy struct {
// URL is the upstream proxy address (e.g., "http://proxy.corp:3128").
URL *url.URL
// Auth is optional basic-auth credentials for the upstream proxy.
Auth *UpstreamAuth
// TLSConfig for connecting to TLS-enabled upstream proxies (optional).
TLSConfig *tls.Config
// DialTimeout is the timeout for establishing a connection to the upstream proxy.
// Defaults to 10 seconds.
DialTimeout time.Duration
// ProxyProtocol enables sending a PROXY protocol header (v1 or v2) when
// connecting to the upstream proxy. This preserves the original client address.
// 0 = disabled, 1 = v1 (text), 2 = v2 (binary).
ProxyProtocol int
}
UpstreamProxy configures forwarding through a parent proxy. Both HTTP CONNECT proxies and SOCKS-style chaining are supported via the standard CONNECT tunnel method.
func NewUpstreamProxy ¶
func NewUpstreamProxy(rawURL string) (*UpstreamProxy, error)
NewUpstreamProxy creates an UpstreamProxy from a URL string.
func (*UpstreamProxy) DialConnect ¶
func (up *UpstreamProxy) DialConnect(ctx context.Context, network, addr string, clientAddr net.Addr) (net.Conn, error)
DialConnect establishes a CONNECT tunnel through the upstream proxy to the given target address. This is used for HTTPS proxy chaining where the downstream proxy needs a raw TCP tunnel to the target.
func (*UpstreamProxy) Transport ¶
func (up *UpstreamProxy) Transport(base http.RoundTripper) http.RoundTripper
Transport returns an http.RoundTripper that forwards requests through the upstream proxy. For HTTPS requests, it establishes a CONNECT tunnel.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
_examples
|
|
|
acme
command
Example: SWG proxy with ACME / Let's Encrypt certificates
|
Example: SWG proxy with ACME / Let's Encrypt certificates |
|
admin
command
Example: Admin API for runtime rule management
|
Example: Admin API for runtime rule management |
|
allowlist
command
Example: Allow-list mode with time-based rules
|
Example: Allow-list mode with time-based rules |
|
bypass
command
Example: Bypass token for debugging
|
Example: Bypass token for debugging |
|
config
command
Example: Using SWG with a configuration file
|
Example: Using SWG with a configuration file |
|
csv
command
Example: Loading blocklist rules from a CSV file
|
Example: Loading blocklist rules from a CSV file |
|
mtls
command
Example: mTLS client certificate authentication
|
Example: mTLS client certificate authentication |
|
policy
command
Example: Policy engine with lifecycle hooks, identity, and body scanning
|
Example: Policy engine with lifecycle hooks, identity, and body scanning |
|
postgres
command
Example: Loading blocklist rules from PostgreSQL using sqlx
|
Example: Loading blocklist rules from PostgreSQL using sqlx |
|
scanner
command
Example: Response body scanning with a pluggable AV scanner
|
Example: Response body scanning with a pluggable AV scanner |