Add per-IP rate limiting and Unix socket support for gRPC admin API
Rate limiting: per-source-IP connection rate limiter in the firewall layer with configurable limit and sliding window. Blocklisted IPs are rejected before rate limit evaluation to avoid wasting quota. Unix socket: the gRPC admin API can now listen on a Unix domain socket (no TLS required), secured by file permissions (0600), as a simpler alternative for local-only access. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -68,7 +68,7 @@ func serverCmd() *cobra.Command {
|
||||
}
|
||||
|
||||
// Load firewall rules from DB.
|
||||
fw, err := loadFirewallFromDB(store, cfg.Firewall.GeoIPDB)
|
||||
fw, err := loadFirewallFromDB(store, cfg.Firewall)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -90,7 +90,12 @@ func serverCmd() *cobra.Command {
|
||||
logger.Error("gRPC server error", "error", err)
|
||||
}
|
||||
}()
|
||||
defer grpcSrv.GracefulStop()
|
||||
defer func() {
|
||||
grpcSrv.GracefulStop()
|
||||
if cfg.GRPC.IsUnixSocket() {
|
||||
os.Remove(cfg.GRPC.SocketPath())
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// SIGHUP reloads the GeoIP database.
|
||||
@@ -140,7 +145,7 @@ func loadListenersFromDB(store *db.Store) ([]server.ListenerData, error) {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func loadFirewallFromDB(store *db.Store, geoIPPath string) (*firewall.Firewall, error) {
|
||||
func loadFirewallFromDB(store *db.Store, fwCfg config.Firewall) (*firewall.Firewall, error) {
|
||||
rules, err := store.ListFirewallRules()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("loading firewall rules: %w", err)
|
||||
@@ -158,7 +163,7 @@ func loadFirewallFromDB(store *db.Store, geoIPPath string) (*firewall.Firewall,
|
||||
}
|
||||
}
|
||||
|
||||
fw, err := firewall.New(geoIPPath, ips, cidrs, countries)
|
||||
fw, err := firewall.New(fwCfg.GeoIPDB, ips, cidrs, countries, fwCfg.RateLimit, fwCfg.RateWindow.Duration)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("initializing firewall: %w", err)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
@@ -32,10 +34,29 @@ func snapshotCmd() *cobra.Command {
|
||||
}
|
||||
defer store.Close()
|
||||
|
||||
dataDir := filepath.Dir(cfg.Database.Path)
|
||||
|
||||
if outputPath == "" {
|
||||
dir := filepath.Dir(cfg.Database.Path)
|
||||
ts := time.Now().UTC().Format("20060102T150405Z")
|
||||
outputPath = filepath.Join(dir, "backups", fmt.Sprintf("mc-proxy-%s.db", ts))
|
||||
outputPath = filepath.Join(dataDir, "backups", fmt.Sprintf("mc-proxy-%s.db", ts))
|
||||
}
|
||||
|
||||
// Validate the output path is within the data directory.
|
||||
absOutput, err := filepath.Abs(filepath.Clean(outputPath))
|
||||
if err != nil {
|
||||
return fmt.Errorf("resolving output path: %w", err)
|
||||
}
|
||||
absDataDir, err := filepath.Abs(dataDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("resolving data directory: %w", err)
|
||||
}
|
||||
if !strings.HasPrefix(absOutput, absDataDir+string(os.PathSeparator)) {
|
||||
return fmt.Errorf("output path must be within the data directory (%s)", absDataDir)
|
||||
}
|
||||
|
||||
// Ensure the parent directory exists.
|
||||
if err := os.MkdirAll(filepath.Dir(outputPath), 0700); err != nil {
|
||||
return fmt.Errorf("creating backup directory: %w", err)
|
||||
}
|
||||
|
||||
if err := store.Snapshot(outputPath); err != nil {
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
|
||||
pb "git.wntrmute.dev/kyle/mc-proxy/gen/mc_proxy/v1"
|
||||
"git.wntrmute.dev/kyle/mc-proxy/internal/config"
|
||||
@@ -70,6 +71,11 @@ func statusCmd() *cobra.Command {
|
||||
}
|
||||
|
||||
func dialGRPC(cfg config.GRPC) (*grpc.ClientConn, error) {
|
||||
if cfg.IsUnixSocket() {
|
||||
return grpc.NewClient("unix://"+cfg.SocketPath(),
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
}
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
MinVersion: tls.VersionTLS13,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user