whitelist configs moved to it's own yml file

This commit is contained in:
0ceanSlim 2024-10-16 09:57:58 -04:00
parent 9df03646db
commit 5133c3a005
13 changed files with 226 additions and 178 deletions

1
.gitignore vendored
View File

@ -1,5 +1,6 @@
/tmp /tmp
config.yml config.yml
whitelist.yml
relay_metadata.json relay_metadata.json
grain.exe grain.exe
/build /build

View File

@ -63,19 +63,6 @@ rate_limit:
limit: 25 limit: 25
burst: 50 burst: 50
pubkey_whitelist:
enabled: false
pubkeys: [] # List of allowed public keys
npubs: [] # List of allowed npubs (Nostr public keys in bech32 format)
kind_whitelist:
enabled: false
kinds: [] # List of allowed event kinds
domain_whitelist:
enabled: false
domains: [] # List of allowed domains
blacklist: #Removing a pubkey from the Blacklist requires a hard restart; Blacklist overides the Whitelist blacklist: #Removing a pubkey from the Blacklist requires a hard restart; Blacklist overides the Whitelist
enabled: true enabled: true
permanent_ban_words: [] # Words that trigger a permanent ban permanent_ban_words: [] # Words that trigger a permanent ban

View File

@ -0,0 +1,20 @@
pubkey_whitelist:
enabled: false
pubkeys:
- pubkey1
- pubkey2
npubs:
- npub18ls2km9aklhzw9yzqgjfu0anhz2z83hkeknw7sl22ptu8kfs3rjq54am44
- npub2
kind_whitelist:
enabled: false
kinds:
- "1"
- "2"
domain_whitelist:
enabled: false
domains:
- "example.com"
- "anotherdomain.com"

View File

@ -6,52 +6,50 @@ import (
"strconv" "strconv"
) )
// Helper function to check if a pubkey or npub is whitelisted // Check if a pubkey or npub is whitelisted
func IsPubKeyWhitelisted(pubKey string) bool { func IsPubKeyWhitelisted(pubKey string) bool {
cfg := GetConfig() cfg := GetWhitelistConfig()
if !cfg.PubkeyWhitelist.Enabled { if !cfg.PubkeyWhitelist.Enabled {
return true return true
} }
// Check pubkeys for _, whitelistedKey := range cfg.PubkeyWhitelist.Pubkeys {
for _, whitelistedKey := range cfg.PubkeyWhitelist.Pubkeys { if pubKey == whitelistedKey {
if pubKey == whitelistedKey { return true
return true }
} }
}
// Check npubs for _, npub := range cfg.PubkeyWhitelist.Npubs {
for _, npub := range cfg.PubkeyWhitelist.Npubs { decodedPubKey, err := utils.DecodeNpub(npub)
decodedPubKey, err := utils.DecodeNpub(npub) if err != nil {
if err != nil { fmt.Println("Error decoding npub:", err)
fmt.Println("Error decoding npub:", err) continue
continue }
} if pubKey == decodedPubKey {
if pubKey == decodedPubKey { return true
return true }
} }
}
return false return false
} }
// Check if a kind is whitelisted
func IsKindWhitelisted(kind int) bool { func IsKindWhitelisted(kind int) bool {
cfg := GetConfig() cfg := GetWhitelistConfig()
if !cfg.KindWhitelist.Enabled { if !cfg.KindWhitelist.Enabled {
return true return true
} }
// Check event kinds for _, whitelistedKindStr := range cfg.KindWhitelist.Kinds {
for _, whitelistedKindStr := range cfg.KindWhitelist.Kinds { whitelistedKind, err := strconv.Atoi(whitelistedKindStr)
whitelistedKind, err := strconv.Atoi(whitelistedKindStr) if err != nil {
if err != nil { fmt.Println("Error converting whitelisted kind to int:", err)
fmt.Println("Error converting whitelisted kind to int:", err) continue
continue }
} if kind == whitelistedKind {
if kind == whitelistedKind { return true
return true }
} }
}
return false return false
} }

View File

@ -10,29 +10,56 @@ import (
) )
var ( var (
cfg *configTypes.ServerConfig cfg *configTypes.ServerConfig
once sync.Once whitelistCfg *configTypes.WhitelistConfig
once sync.Once
whitelistOnce sync.Once
) )
// LoadConfig loads the server configuration from config.yml
func LoadConfig(filename string) (*configTypes.ServerConfig, error) { func LoadConfig(filename string) (*configTypes.ServerConfig, error) {
data, err := os.ReadFile(filename) data, err := os.ReadFile(filename)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var config configTypes.ServerConfig var config configTypes.ServerConfig
err = yaml.Unmarshal(data, &config) err = yaml.Unmarshal(data, &config)
if err != nil { if err != nil {
return nil, err return nil, err
} }
once.Do(func() { once.Do(func() {
cfg = &config cfg = &config
}) })
return cfg, nil return cfg, nil
}
// LoadWhitelistConfig loads the whitelist configuration from whitelist.yml
func LoadWhitelistConfig(filename string) (*configTypes.WhitelistConfig, error) {
data, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
var config configTypes.WhitelistConfig
err = yaml.Unmarshal(data, &config)
if err != nil {
return nil, err
}
whitelistOnce.Do(func() {
whitelistCfg = &config
})
return whitelistCfg, nil
} }
func GetConfig() *configTypes.ServerConfig { func GetConfig() *configTypes.ServerConfig {
return cfg return cfg
} }
func GetWhitelistConfig() *configTypes.WhitelistConfig {
return whitelistCfg
}

View File

@ -13,12 +13,9 @@ type ServerConfig struct {
MaxConnections int `yaml:"max_connections"` // Maximum number of concurrent connections MaxConnections int `yaml:"max_connections"` // Maximum number of concurrent connections
MaxSubscriptionsPerClient int `yaml:"max_subscriptions_per_client"` // Maximum number of subscriptions per client MaxSubscriptionsPerClient int `yaml:"max_subscriptions_per_client"` // Maximum number of subscriptions per client
} `yaml:"server"` } `yaml:"server"`
RateLimit RateLimitConfig `yaml:"rate_limit"` RateLimit RateLimitConfig `yaml:"rate_limit"`
PubkeyWhitelist PubkeyWhitelistConfig `yaml:"pubkey_whitelist"` Blacklist BlacklistConfig `yaml:"blacklist"`
KindWhitelist KindWhitelistConfig `yaml:"kind_whitelist"` ResourceLimits ResourceLimits `yaml:"resource_limits"`
DomainWhitelist DomainWhitelistConfig `yaml:"domain_whitelist"` Auth AuthConfig `yaml:"auth"`
Blacklist BlacklistConfig `yaml:"blacklist"` EventPurge EventPurgeConfig `yaml:"event_purge"`
ResourceLimits ResourceLimits `yaml:"resource_limits"`
Auth AuthConfig `yaml:"auth"`
EventPurge EventPurgeConfig `yaml:"event_purge"`
} }

View File

@ -0,0 +1,19 @@
package config
type WhitelistConfig struct {
PubkeyWhitelist struct {
Enabled bool `yaml:"enabled"`
Pubkeys []string `yaml:"pubkeys"`
Npubs []string `yaml:"npubs"`
} `yaml:"pubkey_whitelist"`
KindWhitelist struct {
Enabled bool `yaml:"enabled"`
Kinds []string `yaml:"kinds"`
} `yaml:"kind_whitelist"`
DomainWhitelist struct {
Enabled bool `yaml:"enabled"`
Domains []string `yaml:"domains"`
} `yaml:"domain_whitelist"`
}

View File

@ -1,6 +0,0 @@
package config
type DomainWhitelistConfig struct {
Enabled bool `yaml:"enabled"`
Domains []string `yaml:"domains"`
}

View File

@ -1,6 +0,0 @@
package config
type KindWhitelistConfig struct {
Enabled bool `yaml:"enabled"`
Kinds []string `yaml:"kinds"`
}

View File

@ -1,7 +0,0 @@
package config
type PubkeyWhitelistConfig struct {
Enabled bool `yaml:"enabled"`
Pubkeys []string `yaml:"pubkeys"`
Npubs []string `yaml:"npubs"`
}

94
main.go
View File

@ -21,64 +21,64 @@ import (
) )
func main() { func main() {
utils.EnsureFileExists("config.yml", "app/static/examples/config.example.yml") utils.EnsureFileExists("config.yml", "app/static/examples/config.example.yml")
utils.EnsureFileExists("relay_metadata.json", "app/static/examples/relay_metadata.example.json") utils.EnsureFileExists("whitelist.yml", "app/static/examples/whitelist.example.yml")
utils.EnsureFileExists("relay_metadata.json", "app/static/examples/relay_metadata.example.json")
restartChan := make(chan struct{}) restartChan := make(chan struct{})
go config.WatchConfigFile("config.yml", restartChan) // Critical goroutine go config.WatchConfigFile("config.yml", restartChan)
signalChan := make(chan os.Signal, 1) signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM) signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
var wg sync.WaitGroup var wg sync.WaitGroup
for { for {
wg.Add(1) // Add to WaitGroup for the server goroutine wg.Add(1)
cfg, err := config.LoadConfig("config.yml") cfg, err := config.LoadConfig("config.yml")
if err != nil { if err != nil {
log.Fatal("Error loading config: ", err) log.Fatal("Error loading config: ", err)
} }
// Start event purging in the background _, err = config.LoadWhitelistConfig("whitelist.yml")
go mongo.ScheduleEventPurging(cfg) if err != nil {
log.Fatal("Error loading whitelist config: ", err)
}
config.SetResourceLimit(&cfg.ResourceLimits) // Apply limits once before starting the server go mongo.ScheduleEventPurging(cfg)
client, err := mongo.InitDB(cfg) config.SetResourceLimit(&cfg.ResourceLimits)
if err != nil { client, err := mongo.InitDB(cfg)
log.Fatal("Error initializing database: ", err) if err != nil {
} log.Fatal("Error initializing database: ", err)
}
config.SetRateLimit(cfg) config.SetRateLimit(cfg)
config.SetSizeLimit(cfg) config.SetSizeLimit(cfg)
config.ClearTemporaryBans()
config.ClearTemporaryBans() err = utils.LoadRelayMetadataJSON()
if err != nil {
log.Fatal("Failed to load relay metadata: ", err)
}
err = utils.LoadRelayMetadataJSON() mux := setupRoutes()
if err != nil { server := startServer(cfg, mux, &wg)
log.Fatal("Failed to load relay metadata: ", err)
}
mux := setupRoutes() select {
case <-restartChan:
// Start the server log.Println("Restarting server...")
server := startServer(cfg, mux, &wg) server.Close()
wg.Wait()
select { time.Sleep(3 * time.Second)
case <-restartChan: case <-signalChan:
log.Println("Restarting server...") log.Println("Shutting down server...")
server.Close() // Stop the current server instance server.Close()
wg.Wait() // Wait for the server goroutine to finish mongo.DisconnectDB(client)
time.Sleep(3 * time.Second) wg.Wait()
return
case <-signalChan: }
log.Println("Shutting down server...") }
server.Close() // Stop the server
mongo.DisconnectDB(client) // Disconnect from MongoDB
wg.Wait() // Wait for all goroutines to finish
return
}
}
} }
func setupRoutes() *http.ServeMux { func setupRoutes() *http.ServeMux {

View File

@ -2,6 +2,7 @@ package mongo
import ( import (
"context" "context"
"grain/config"
types "grain/config/types" types "grain/config/types"
"grain/server/utils" "grain/server/utils"
"log" "log"
@ -10,6 +11,7 @@ import (
"go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson"
) )
// PurgeOldEvents removes old events based on the configuration and a list of whitelisted pubkeys.
func PurgeOldEvents(cfg *types.EventPurgeConfig, whitelist []string) { func PurgeOldEvents(cfg *types.EventPurgeConfig, whitelist []string) {
if !cfg.Enabled { if !cfg.Enabled {
return return
@ -21,10 +23,12 @@ func PurgeOldEvents(cfg *types.EventPurgeConfig, whitelist []string) {
// Calculate the cutoff time // Calculate the cutoff time
cutoff := time.Now().AddDate(0, 0, -cfg.KeepDurationDays).Unix() cutoff := time.Now().AddDate(0, 0, -cfg.KeepDurationDays).Unix()
// Create the filter for purging old events
filter := bson.M{ filter := bson.M{
"created_at": bson.M{"$lt": cutoff}, // Filter older events "created_at": bson.M{"$lt": cutoff}, // Filter older events
} }
// Exclude whitelisted pubkeys if specified in the config
if cfg.ExcludeWhitelisted && len(whitelist) > 0 { if cfg.ExcludeWhitelisted && len(whitelist) > 0 {
filter["pubkey"] = bson.M{"$nin": whitelist} // Exclude whitelisted pubkeys filter["pubkey"] = bson.M{"$nin": whitelist} // Exclude whitelisted pubkeys
} }
@ -52,7 +56,6 @@ func PurgeOldEvents(cfg *types.EventPurgeConfig, whitelist []string) {
} }
} }
// Example of a periodic purging task
// ScheduleEventPurging runs the event purging at a configurable interval. // ScheduleEventPurging runs the event purging at a configurable interval.
func ScheduleEventPurging(cfg *types.ServerConfig) { func ScheduleEventPurging(cfg *types.ServerConfig) {
// Use the purge interval from the configuration // Use the purge interval from the configuration
@ -61,26 +64,37 @@ func ScheduleEventPurging(cfg *types.ServerConfig) {
defer ticker.Stop() defer ticker.Stop()
for range ticker.C { for range ticker.C {
whitelist := getWhitelistedPubKeys(cfg) // Fetch the whitelisted pubkeys without passing cfg directly
whitelist := getWhitelistedPubKeys()
PurgeOldEvents(&cfg.EventPurge, whitelist) PurgeOldEvents(&cfg.EventPurge, whitelist)
log.Printf("Purged old events, keeping whitelisted pubkeys: %v", whitelist)
} }
} }
// Fetch whitelisted pubkeys from both the config and any additional domains. // Fetch whitelisted pubkeys from both the whitelist config and any additional domains.
func getWhitelistedPubKeys(cfg *types.ServerConfig) []string { func getWhitelistedPubKeys() []string {
whitelistedPubkeys := cfg.PubkeyWhitelist.Pubkeys // Get the whitelist configuration
whitelistCfg := config.GetWhitelistConfig()
if whitelistCfg == nil {
log.Println("whitelistCfg is nil, returning an empty list of whitelisted pubkeys.")
return []string{}
}
// Start with the statically defined pubkeys
whitelistedPubkeys := whitelistCfg.PubkeyWhitelist.Pubkeys
// Fetch pubkeys from domains if domain whitelist is enabled // Fetch pubkeys from domains if domain whitelist is enabled
if cfg.DomainWhitelist.Enabled { if whitelistCfg.DomainWhitelist.Enabled {
domains := cfg.DomainWhitelist.Domains domains := whitelistCfg.DomainWhitelist.Domains
pubkeys, err := utils.FetchPubkeysFromDomains(domains) pubkeys, err := utils.FetchPubkeysFromDomains(domains)
if err != nil { if err != nil {
log.Printf("Error fetching pubkeys from domains: %v", err) log.Printf("Error fetching pubkeys from domains: %v", err)
return whitelistedPubkeys // Return existing whitelisted pubkeys in case of error // Return the existing statically whitelisted pubkeys in case of an error
return whitelistedPubkeys
} }
// Append fetched pubkeys from domains to the whitelisted pubkeys // Append fetched pubkeys from domains to the whitelisted pubkeys
whitelistedPubkeys = append(whitelistedPubkeys, pubkeys...) whitelistedPubkeys = append(whitelistedPubkeys, pubkeys...)
} }
return whitelistedPubkeys return whitelistedPubkeys
} }

View File

@ -68,37 +68,41 @@ func HandleEvent(ws *websocket.Conn, message []interface{}) {
} }
func handleBlacklistAndWhitelist(ws *websocket.Conn, evt nostr.Event) bool { func handleBlacklistAndWhitelist(ws *websocket.Conn, evt nostr.Event) bool {
if config.GetConfig().DomainWhitelist.Enabled { whitelistCfg := config.GetWhitelistConfig()
domains := config.GetConfig().DomainWhitelist.Domains
pubkeys, err := utils.FetchPubkeysFromDomains(domains)
if err != nil {
fmt.Println("Error fetching pubkeys from domains:", err)
response.SendNotice(ws, "", "Error fetching pubkeys from domains")
return false
}
for _, pubkey := range pubkeys {
config.GetConfig().PubkeyWhitelist.Pubkeys = append(config.GetConfig().PubkeyWhitelist.Pubkeys, pubkey)
}
}
if blacklisted, msg := config.CheckBlacklist(evt.PubKey, evt.Content); blacklisted { // If domain whitelisting is enabled, dynamically fetch pubkeys from domains
response.SendOK(ws, evt.ID, false, msg) if whitelistCfg.DomainWhitelist.Enabled {
return false domains := whitelistCfg.DomainWhitelist.Domains
} pubkeys, err := utils.FetchPubkeysFromDomains(domains)
if err != nil {
fmt.Println("Error fetching pubkeys from domains:", err)
response.SendNotice(ws, "", "Error fetching pubkeys from domains")
return false
}
// Update the whitelisted pubkeys dynamically
whitelistCfg.PubkeyWhitelist.Pubkeys = append(whitelistCfg.PubkeyWhitelist.Pubkeys, pubkeys...)
}
if config.GetConfig().KindWhitelist.Enabled && !config.IsKindWhitelisted(evt.Kind) { // Check if the event's pubkey or content is blacklisted
response.SendOK(ws, evt.ID, false, "not allowed: event kind is not whitelisted") if blacklisted, msg := config.CheckBlacklist(evt.PubKey, evt.Content); blacklisted {
return false response.SendOK(ws, evt.ID, false, msg)
} return false
}
if config.GetConfig().PubkeyWhitelist.Enabled && !config.IsPubKeyWhitelisted(evt.PubKey) { // Check if the event's kind is whitelisted
response.SendOK(ws, evt.ID, false, "not allowed: pubkey or npub is not whitelisted") if whitelistCfg.KindWhitelist.Enabled && !config.IsKindWhitelisted(evt.Kind) {
return false response.SendOK(ws, evt.ID, false, "not allowed: event kind is not whitelisted")
} return false
}
return true // Check if the event's pubkey is whitelisted
if whitelistCfg.PubkeyWhitelist.Enabled && !config.IsPubKeyWhitelisted(evt.PubKey) {
response.SendOK(ws, evt.ID, false, "not allowed: pubkey or npub is not whitelisted")
return false
}
return true
} }
func handleRateAndSizeLimits(ws *websocket.Conn, evt nostr.Event, eventSize int) bool { func handleRateAndSizeLimits(ws *websocket.Conn, evt nostr.Event, eventSize int) bool {
rateLimiter := config.GetRateLimiter() rateLimiter := config.GetRateLimiter()
sizeLimiter := config.GetSizeLimiter() sizeLimiter := config.GetSizeLimiter()