mirror of
https://github.com/lightninglabs/aperture.git
synced 2025-12-18 17:44:20 +01:00
proxy: implement blocklist
This commit is contained in:
@@ -862,7 +862,9 @@ func createProxy(cfg *Config, challenger challenger.Challenger,
|
|||||||
},
|
},
|
||||||
))
|
))
|
||||||
|
|
||||||
prxy, err := proxy.New(authenticator, cfg.Services, localServices...)
|
prxy, err := proxy.New(
|
||||||
|
authenticator, cfg.Services, cfg.Blocklist, localServices...,
|
||||||
|
)
|
||||||
return prxy, proxyCleanup, err
|
return prxy, proxyCleanup, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -226,6 +226,9 @@ type Config struct {
|
|||||||
|
|
||||||
// Logging controls various aspects of aperture logging.
|
// Logging controls various aspects of aperture logging.
|
||||||
Logging *build.LogConfig `group:"logging" namespace:"logging"`
|
Logging *build.LogConfig `group:"logging" namespace:"logging"`
|
||||||
|
|
||||||
|
// Blocklist is a list of IPs to deny access to.
|
||||||
|
Blocklist []string `long:"blocklist" description:"List of IP addresses to block from accessing the proxy."`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) validate() error {
|
func (c *Config) validate() error {
|
||||||
@@ -270,5 +273,6 @@ func NewConfig() *Config {
|
|||||||
WriteTimeout: defaultWriteTimeout,
|
WriteTimeout: defaultWriteTimeout,
|
||||||
InvoiceBatchSize: defaultInvoiceBatchSize,
|
InvoiceBatchSize: defaultInvoiceBatchSize,
|
||||||
Logging: build.DefaultLogConfig(),
|
Logging: build.DefaultLogConfig(),
|
||||||
|
Blocklist: []string{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
"os"
|
"os"
|
||||||
@@ -73,18 +74,30 @@ type Proxy struct {
|
|||||||
localServices []LocalService
|
localServices []LocalService
|
||||||
authenticator auth.Authenticator
|
authenticator auth.Authenticator
|
||||||
services []*Service
|
services []*Service
|
||||||
|
blocklist map[string]struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a new Proxy instance that proxies between the services specified,
|
// New returns a new Proxy instance that proxies between the services specified,
|
||||||
// using the auth to validate each request's headers and get new challenge
|
// using the auth to validate each request's headers and get new challenge
|
||||||
// headers if necessary.
|
// headers if necessary.
|
||||||
func New(auth auth.Authenticator, services []*Service,
|
func New(auth auth.Authenticator, services []*Service,
|
||||||
localServices ...LocalService) (*Proxy, error) {
|
blocklist []string, localServices ...LocalService) (*Proxy, error) {
|
||||||
|
|
||||||
|
blMap := make(map[string]struct{})
|
||||||
|
for _, ip := range blocklist {
|
||||||
|
parsed := net.ParseIP(ip)
|
||||||
|
if parsed == nil {
|
||||||
|
log.Warnf("Could not parse IP %q in blocklist; skipping", ip)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
blMap[parsed.String()] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
proxy := &Proxy{
|
proxy := &Proxy{
|
||||||
localServices: localServices,
|
localServices: localServices,
|
||||||
authenticator: auth,
|
authenticator: auth,
|
||||||
services: services,
|
services: services,
|
||||||
|
blocklist: blMap,
|
||||||
}
|
}
|
||||||
err := proxy.UpdateServices(services)
|
err := proxy.UpdateServices(services)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -106,6 +119,14 @@ func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
defer logRequest()
|
defer logRequest()
|
||||||
|
|
||||||
|
// Blocklist check
|
||||||
|
if _, blocked := p.blocklist[remoteIP.String()]; blocked {
|
||||||
|
log.Debugf("Blocked request from IP: %s", remoteIP)
|
||||||
|
addCorsHeaders(w.Header())
|
||||||
|
sendDirectResponse(w, r, http.StatusForbidden, "access denied")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// For OPTIONS requests we only need to set the CORS headers, not serve
|
// For OPTIONS requests we only need to set the CORS headers, not serve
|
||||||
// any content;
|
// any content;
|
||||||
if r.Method == "OPTIONS" {
|
if r.Method == "OPTIONS" {
|
||||||
|
|||||||
@@ -111,6 +111,72 @@ func TestProxyHTTP(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestProxyHTTPBlocklist tests that the proxy can block HTTP requests from
|
||||||
|
// a blocked IP.
|
||||||
|
func TestProxyHTTPBlocklist(t *testing.T) {
|
||||||
|
services := []*proxy.Service{{
|
||||||
|
Address: testTargetServiceAddress,
|
||||||
|
HostRegexp: testHostRegexp,
|
||||||
|
PathRegexp: testPathRegexpHTTP,
|
||||||
|
Protocol: "http",
|
||||||
|
Auth: "off",
|
||||||
|
}}
|
||||||
|
|
||||||
|
mockAuth := auth.NewMockAuthenticator()
|
||||||
|
|
||||||
|
// Block the IP that will be used in the request.
|
||||||
|
blockedIP := "127.0.0.1"
|
||||||
|
p, err := proxy.New(mockAuth, services, []string{blockedIP})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Start the proxy server.
|
||||||
|
server := &http.Server{
|
||||||
|
Addr: testProxyAddr,
|
||||||
|
Handler: http.HandlerFunc(p.ServeHTTP),
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
if err := server.ListenAndServe(); err != http.ErrServerClosed {
|
||||||
|
t.Errorf("proxy serve error: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
defer closeOrFail(t, server)
|
||||||
|
|
||||||
|
// Start the backend server.
|
||||||
|
backendService := &http.Server{Addr: testTargetServiceAddress}
|
||||||
|
go func() { _ = startBackendHTTP(backendService) }()
|
||||||
|
defer closeOrFail(t, backendService)
|
||||||
|
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
|
||||||
|
// Make a request with a spoofed RemoteAddr that matches the blocklist.
|
||||||
|
req, err := http.NewRequest(
|
||||||
|
"GET",
|
||||||
|
fmt.Sprintf("http://%s/http/test", testProxyAddr),
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Create a custom transport to override the local IP — simulate blocked IP.
|
||||||
|
customTransport := &http.Transport{
|
||||||
|
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
|
||||||
|
lAddr, _ := net.ResolveTCPAddr("tcp", "127.0.0.1:0")
|
||||||
|
d := net.Dialer{LocalAddr: lAddr}
|
||||||
|
return d.DialContext(context.Background(), "tcp", testProxyAddr)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
client := &http.Client{Transport: customTransport}
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusForbidden, resp.StatusCode)
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "access denied\n", string(body))
|
||||||
|
}
|
||||||
|
|
||||||
// runHTTPTest tests that the proxy can forward HTTP requests to a backend
|
// runHTTPTest tests that the proxy can forward HTTP requests to a backend
|
||||||
// service and handle L402 authentication correctly.
|
// service and handle L402 authentication correctly.
|
||||||
func runHTTPTest(t *testing.T, tc *testCase, method string) {
|
func runHTTPTest(t *testing.T, tc *testCase, method string) {
|
||||||
@@ -125,7 +191,7 @@ func runHTTPTest(t *testing.T, tc *testCase, method string) {
|
|||||||
}}
|
}}
|
||||||
|
|
||||||
mockAuth := auth.NewMockAuthenticator()
|
mockAuth := auth.NewMockAuthenticator()
|
||||||
p, err := proxy.New(mockAuth, services)
|
p, err := proxy.New(mockAuth, services, []string{})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Start server that gives requests to the proxy.
|
// Start server that gives requests to the proxy.
|
||||||
@@ -288,7 +354,7 @@ func runGRPCTest(t *testing.T, tc *testCase) {
|
|||||||
|
|
||||||
// Create the proxy server and start serving on TLS.
|
// Create the proxy server and start serving on TLS.
|
||||||
mockAuth := auth.NewMockAuthenticator()
|
mockAuth := auth.NewMockAuthenticator()
|
||||||
p, err := proxy.New(mockAuth, services)
|
p, err := proxy.New(mockAuth, services, []string{})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
server := &http.Server{
|
server := &http.Server{
|
||||||
Addr: testProxyAddr,
|
Addr: testProxyAddr,
|
||||||
|
|||||||
@@ -61,6 +61,10 @@ authenticator:
|
|||||||
# Set to true to skip verification of the mailbox server's tls cert.
|
# Set to true to skip verification of the mailbox server's tls cert.
|
||||||
devserver: false
|
devserver: false
|
||||||
|
|
||||||
|
# List of IPs to block from accessing the proxy.
|
||||||
|
blocklist:
|
||||||
|
- "1.1.1.1"
|
||||||
|
- "1.0.0.1"
|
||||||
|
|
||||||
# The selected database backend. The current default backend is "sqlite".
|
# The selected database backend. The current default backend is "sqlite".
|
||||||
# Aperture also has support for postgres and etcd.
|
# Aperture also has support for postgres and etcd.
|
||||||
|
|||||||
Reference in New Issue
Block a user