mirror of
https://github.com/0ceanSlim/grain.git
synced 2024-10-29 17:16:31 +00:00
whitelist configs moved to it's own yml file
This commit is contained in:
parent
9df03646db
commit
5133c3a005
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,5 +1,6 @@
|
||||
/tmp
|
||||
config.yml
|
||||
whitelist.yml
|
||||
relay_metadata.json
|
||||
grain.exe
|
||||
/build
|
||||
|
@ -63,19 +63,6 @@ rate_limit:
|
||||
limit: 25
|
||||
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
|
||||
enabled: true
|
||||
permanent_ban_words: [] # Words that trigger a permanent ban
|
||||
|
20
app/static/examples/whitelist.example.yml
Normal file
20
app/static/examples/whitelist.example.yml
Normal 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"
|
@ -6,52 +6,50 @@ import (
|
||||
"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 {
|
||||
cfg := GetConfig()
|
||||
if !cfg.PubkeyWhitelist.Enabled {
|
||||
return true
|
||||
}
|
||||
cfg := GetWhitelistConfig()
|
||||
if !cfg.PubkeyWhitelist.Enabled {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check pubkeys
|
||||
for _, whitelistedKey := range cfg.PubkeyWhitelist.Pubkeys {
|
||||
if pubKey == whitelistedKey {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for _, whitelistedKey := range cfg.PubkeyWhitelist.Pubkeys {
|
||||
if pubKey == whitelistedKey {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Check npubs
|
||||
for _, npub := range cfg.PubkeyWhitelist.Npubs {
|
||||
decodedPubKey, err := utils.DecodeNpub(npub)
|
||||
if err != nil {
|
||||
fmt.Println("Error decoding npub:", err)
|
||||
continue
|
||||
}
|
||||
if pubKey == decodedPubKey {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for _, npub := range cfg.PubkeyWhitelist.Npubs {
|
||||
decodedPubKey, err := utils.DecodeNpub(npub)
|
||||
if err != nil {
|
||||
fmt.Println("Error decoding npub:", err)
|
||||
continue
|
||||
}
|
||||
if pubKey == decodedPubKey {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if a kind is whitelisted
|
||||
func IsKindWhitelisted(kind int) bool {
|
||||
cfg := GetConfig()
|
||||
if !cfg.KindWhitelist.Enabled {
|
||||
return true
|
||||
}
|
||||
cfg := GetWhitelistConfig()
|
||||
if !cfg.KindWhitelist.Enabled {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check event kinds
|
||||
for _, whitelistedKindStr := range cfg.KindWhitelist.Kinds {
|
||||
whitelistedKind, err := strconv.Atoi(whitelistedKindStr)
|
||||
if err != nil {
|
||||
fmt.Println("Error converting whitelisted kind to int:", err)
|
||||
continue
|
||||
}
|
||||
if kind == whitelistedKind {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for _, whitelistedKindStr := range cfg.KindWhitelist.Kinds {
|
||||
whitelistedKind, err := strconv.Atoi(whitelistedKindStr)
|
||||
if err != nil {
|
||||
fmt.Println("Error converting whitelisted kind to int:", err)
|
||||
continue
|
||||
}
|
||||
if kind == whitelistedKind {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
@ -10,29 +10,56 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
cfg *configTypes.ServerConfig
|
||||
once sync.Once
|
||||
cfg *configTypes.ServerConfig
|
||||
whitelistCfg *configTypes.WhitelistConfig
|
||||
once sync.Once
|
||||
whitelistOnce sync.Once
|
||||
)
|
||||
|
||||
// LoadConfig loads the server configuration from config.yml
|
||||
func LoadConfig(filename string) (*configTypes.ServerConfig, error) {
|
||||
data, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var config configTypes.ServerConfig
|
||||
err = yaml.Unmarshal(data, &config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var config configTypes.ServerConfig
|
||||
err = yaml.Unmarshal(data, &config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
once.Do(func() {
|
||||
cfg = &config
|
||||
})
|
||||
once.Do(func() {
|
||||
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 {
|
||||
return cfg
|
||||
return cfg
|
||||
}
|
||||
|
||||
func GetWhitelistConfig() *configTypes.WhitelistConfig {
|
||||
return whitelistCfg
|
||||
}
|
@ -13,12 +13,9 @@ type ServerConfig struct {
|
||||
MaxConnections int `yaml:"max_connections"` // Maximum number of concurrent connections
|
||||
MaxSubscriptionsPerClient int `yaml:"max_subscriptions_per_client"` // Maximum number of subscriptions per client
|
||||
} `yaml:"server"`
|
||||
RateLimit RateLimitConfig `yaml:"rate_limit"`
|
||||
PubkeyWhitelist PubkeyWhitelistConfig `yaml:"pubkey_whitelist"`
|
||||
KindWhitelist KindWhitelistConfig `yaml:"kind_whitelist"`
|
||||
DomainWhitelist DomainWhitelistConfig `yaml:"domain_whitelist"`
|
||||
Blacklist BlacklistConfig `yaml:"blacklist"`
|
||||
ResourceLimits ResourceLimits `yaml:"resource_limits"`
|
||||
Auth AuthConfig `yaml:"auth"`
|
||||
EventPurge EventPurgeConfig `yaml:"event_purge"`
|
||||
RateLimit RateLimitConfig `yaml:"rate_limit"`
|
||||
Blacklist BlacklistConfig `yaml:"blacklist"`
|
||||
ResourceLimits ResourceLimits `yaml:"resource_limits"`
|
||||
Auth AuthConfig `yaml:"auth"`
|
||||
EventPurge EventPurgeConfig `yaml:"event_purge"`
|
||||
}
|
||||
|
19
config/types/whitelistConfig.go
Normal file
19
config/types/whitelistConfig.go
Normal 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"`
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
package config
|
||||
|
||||
type DomainWhitelistConfig struct {
|
||||
Enabled bool `yaml:"enabled"`
|
||||
Domains []string `yaml:"domains"`
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
package config
|
||||
|
||||
type KindWhitelistConfig struct {
|
||||
Enabled bool `yaml:"enabled"`
|
||||
Kinds []string `yaml:"kinds"`
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
package config
|
||||
|
||||
type PubkeyWhitelistConfig struct {
|
||||
Enabled bool `yaml:"enabled"`
|
||||
Pubkeys []string `yaml:"pubkeys"`
|
||||
Npubs []string `yaml:"npubs"`
|
||||
}
|
94
main.go
94
main.go
@ -21,64 +21,64 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
utils.EnsureFileExists("config.yml", "app/static/examples/config.example.yml")
|
||||
utils.EnsureFileExists("relay_metadata.json", "app/static/examples/relay_metadata.example.json")
|
||||
utils.EnsureFileExists("config.yml", "app/static/examples/config.example.yml")
|
||||
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{})
|
||||
go config.WatchConfigFile("config.yml", restartChan) // Critical goroutine
|
||||
restartChan := make(chan struct{})
|
||||
go config.WatchConfigFile("config.yml", restartChan)
|
||||
|
||||
signalChan := make(chan os.Signal, 1)
|
||||
signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
|
||||
signalChan := make(chan os.Signal, 1)
|
||||
signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for {
|
||||
wg.Add(1) // Add to WaitGroup for the server goroutine
|
||||
var wg sync.WaitGroup
|
||||
for {
|
||||
wg.Add(1)
|
||||
|
||||
cfg, err := config.LoadConfig("config.yml")
|
||||
if err != nil {
|
||||
log.Fatal("Error loading config: ", err)
|
||||
}
|
||||
cfg, err := config.LoadConfig("config.yml")
|
||||
if err != nil {
|
||||
log.Fatal("Error loading config: ", err)
|
||||
}
|
||||
|
||||
// Start event purging in the background
|
||||
go mongo.ScheduleEventPurging(cfg)
|
||||
_, err = config.LoadWhitelistConfig("whitelist.yml")
|
||||
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)
|
||||
if err != nil {
|
||||
log.Fatal("Error initializing database: ", err)
|
||||
}
|
||||
config.SetResourceLimit(&cfg.ResourceLimits)
|
||||
client, err := mongo.InitDB(cfg)
|
||||
if err != nil {
|
||||
log.Fatal("Error initializing database: ", err)
|
||||
}
|
||||
|
||||
config.SetRateLimit(cfg)
|
||||
config.SetSizeLimit(cfg)
|
||||
config.SetRateLimit(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()
|
||||
if err != nil {
|
||||
log.Fatal("Failed to load relay metadata: ", err)
|
||||
}
|
||||
mux := setupRoutes()
|
||||
server := startServer(cfg, mux, &wg)
|
||||
|
||||
mux := setupRoutes()
|
||||
|
||||
// Start the server
|
||||
server := startServer(cfg, mux, &wg)
|
||||
|
||||
select {
|
||||
case <-restartChan:
|
||||
log.Println("Restarting server...")
|
||||
server.Close() // Stop the current server instance
|
||||
wg.Wait() // Wait for the server goroutine to finish
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
select {
|
||||
case <-restartChan:
|
||||
log.Println("Restarting server...")
|
||||
server.Close()
|
||||
wg.Wait()
|
||||
time.Sleep(3 * time.Second)
|
||||
case <-signalChan:
|
||||
log.Println("Shutting down server...")
|
||||
server.Close()
|
||||
mongo.DisconnectDB(client)
|
||||
wg.Wait()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setupRoutes() *http.ServeMux {
|
||||
|
@ -2,6 +2,7 @@ package mongo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"grain/config"
|
||||
types "grain/config/types"
|
||||
"grain/server/utils"
|
||||
"log"
|
||||
@ -10,6 +11,7 @@ import (
|
||||
"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) {
|
||||
if !cfg.Enabled {
|
||||
return
|
||||
@ -21,10 +23,12 @@ func PurgeOldEvents(cfg *types.EventPurgeConfig, whitelist []string) {
|
||||
// Calculate the cutoff time
|
||||
cutoff := time.Now().AddDate(0, 0, -cfg.KeepDurationDays).Unix()
|
||||
|
||||
// Create the filter for purging old events
|
||||
filter := bson.M{
|
||||
"created_at": bson.M{"$lt": cutoff}, // Filter older events
|
||||
}
|
||||
|
||||
// Exclude whitelisted pubkeys if specified in the config
|
||||
if cfg.ExcludeWhitelisted && len(whitelist) > 0 {
|
||||
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.
|
||||
func ScheduleEventPurging(cfg *types.ServerConfig) {
|
||||
// Use the purge interval from the configuration
|
||||
@ -61,26 +64,37 @@ func ScheduleEventPurging(cfg *types.ServerConfig) {
|
||||
defer ticker.Stop()
|
||||
|
||||
for range ticker.C {
|
||||
whitelist := getWhitelistedPubKeys(cfg)
|
||||
// Fetch the whitelisted pubkeys without passing cfg directly
|
||||
whitelist := getWhitelistedPubKeys()
|
||||
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.
|
||||
func getWhitelistedPubKeys(cfg *types.ServerConfig) []string {
|
||||
whitelistedPubkeys := cfg.PubkeyWhitelist.Pubkeys
|
||||
// Fetch whitelisted pubkeys from both the whitelist config and any additional domains.
|
||||
func getWhitelistedPubKeys() []string {
|
||||
// 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
|
||||
if cfg.DomainWhitelist.Enabled {
|
||||
domains := cfg.DomainWhitelist.Domains
|
||||
if whitelistCfg.DomainWhitelist.Enabled {
|
||||
domains := whitelistCfg.DomainWhitelist.Domains
|
||||
pubkeys, err := utils.FetchPubkeysFromDomains(domains)
|
||||
if err != nil {
|
||||
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
|
||||
whitelistedPubkeys = append(whitelistedPubkeys, pubkeys...)
|
||||
}
|
||||
|
||||
return whitelistedPubkeys
|
||||
}
|
||||
}
|
@ -68,37 +68,41 @@ func HandleEvent(ws *websocket.Conn, message []interface{}) {
|
||||
}
|
||||
|
||||
func handleBlacklistAndWhitelist(ws *websocket.Conn, evt nostr.Event) bool {
|
||||
if config.GetConfig().DomainWhitelist.Enabled {
|
||||
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)
|
||||
}
|
||||
}
|
||||
whitelistCfg := config.GetWhitelistConfig()
|
||||
|
||||
if blacklisted, msg := config.CheckBlacklist(evt.PubKey, evt.Content); blacklisted {
|
||||
response.SendOK(ws, evt.ID, false, msg)
|
||||
return false
|
||||
}
|
||||
// If domain whitelisting is enabled, dynamically fetch pubkeys from domains
|
||||
if whitelistCfg.DomainWhitelist.Enabled {
|
||||
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) {
|
||||
response.SendOK(ws, evt.ID, false, "not allowed: event kind is not whitelisted")
|
||||
return false
|
||||
}
|
||||
// Check if the event's pubkey or content is blacklisted
|
||||
if blacklisted, msg := config.CheckBlacklist(evt.PubKey, evt.Content); blacklisted {
|
||||
response.SendOK(ws, evt.ID, false, msg)
|
||||
return false
|
||||
}
|
||||
|
||||
if config.GetConfig().PubkeyWhitelist.Enabled && !config.IsPubKeyWhitelisted(evt.PubKey) {
|
||||
response.SendOK(ws, evt.ID, false, "not allowed: pubkey or npub is not whitelisted")
|
||||
return false
|
||||
}
|
||||
// Check if the event's kind is whitelisted
|
||||
if whitelistCfg.KindWhitelist.Enabled && !config.IsKindWhitelisted(evt.Kind) {
|
||||
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 {
|
||||
rateLimiter := config.GetRateLimiter()
|
||||
sizeLimiter := config.GetSizeLimiter()
|
||||
|
Loading…
Reference in New Issue
Block a user