Updating rate limits to also use TOML
This commit is contained in:
@@ -8,22 +8,26 @@ import (
|
||||
"github.com/BurntSushi/toml"
|
||||
)
|
||||
|
||||
type DbConfig struct {
|
||||
Dialect string `toml:"dialect"`
|
||||
Username string `toml:"username"`
|
||||
PasswordSecret string `toml:"password-secret"`
|
||||
Url string `toml:"url"`
|
||||
Port string `toml:"port"`
|
||||
Name string `toml:"name"`
|
||||
}
|
||||
|
||||
type StackConfiguration struct {
|
||||
ConfigLoaded bool
|
||||
|
||||
AllowFreshAdminGeneration bool
|
||||
AdminEmails []string
|
||||
AdminHmacEnv string
|
||||
UserHmacEnv string
|
||||
AuthedRateLimitConfig string
|
||||
UnauthedRateLimitConfig string
|
||||
AllowFreshAdminGeneration bool `toml:"gen-fresh-admin"`
|
||||
AdminEmails []string `toml:"admin-emails"`
|
||||
AdminHmacEnv string `toml:"admin-hmac-env"`
|
||||
UserHmacEnv string `toml:"user-hmac-env"`
|
||||
AuthedRateLimitConfig string `toml:"auth-rate-limit-defs"`
|
||||
UnauthedRateLimitConfig string `toml:"unauth-rate-limit-defs"`
|
||||
|
||||
DbDialect string
|
||||
DbUsername string
|
||||
DbPasswordSecret string
|
||||
DbUrl string
|
||||
DbPort string
|
||||
DbName string
|
||||
Db DbConfig `toml:"db"`
|
||||
}
|
||||
|
||||
var Environment = os.Getenv("STACK_ENVIRONMENT")
|
||||
@@ -60,5 +64,5 @@ func LoadConfig() {
|
||||
|
||||
configInternal.ConfigLoaded = true
|
||||
|
||||
log.Printf("Loaded Config for stack '%s': %+v", Environment, configInternal)
|
||||
log.Printf("Loaded Config for stack '%s':\n%+v\n", Environment, configInternal)
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
AllowFreshAdminGeneration = true
|
||||
AdminEmails = ["admin@admin.invalid"]
|
||||
AdminHmacEnv = "ADMIN_HMAC_ENV"
|
||||
UserHmacEnv = "USER_HMAC_ENV"
|
||||
AuthedRateLimitConfig = "ratelimit.auth.json"
|
||||
UnauthedRateLimitConfig = "ratelimit.unauth.json"
|
||||
gen-fresh-admin = true
|
||||
admin-emails = ["admin@admin.invalid"]
|
||||
admin-hmac-env = "ADMIN_HMAC_ENV"
|
||||
user-hmac-env = "USER_HMAC_ENV"
|
||||
auth-rate-limit-defs = "ratelimit.auth.toml"
|
||||
unauth-rate-limit-defs = "ratelimit.unauth.toml"
|
||||
|
||||
DbDialect = "sqlite"
|
||||
DbUrl = "prepack.db"
|
||||
DbUsername = ""
|
||||
DbPasswordSecret = ""
|
||||
DbPort = ""
|
||||
DbName = ""
|
||||
[db]
|
||||
dialect = "sqlite"
|
||||
url = "prepack.db"
|
||||
username = ""
|
||||
password-secret = ""
|
||||
port = ""
|
||||
name = ""
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"": {"seconds": 60, "max": 30, "_comment": "Global ratelimit."},
|
||||
|
||||
"GET:/v1/sec/doot":
|
||||
{"seconds": 5, "max": 3, "_comment": "One DPS (Doot Per Second) for monitoring?"},
|
||||
|
||||
"GET:/v1/sec/2fa-doot":
|
||||
{"seconds": 10, "max": 1, "_comment": "2FA doot probably doesn't need much usage at all, mainly exists as a proof of concept."},
|
||||
|
||||
"GET:/v1/adm/doot":
|
||||
{"seconds": 5, "max": 3, "_comment": "One DPS (Doot Per Second) for monitoring?"},
|
||||
|
||||
"GET:/v1/adm/2fa-doot":
|
||||
{"seconds": 10, "max": 1, "_comment": "2FA doot probably doesn't need much usage at all, mainly exists as a proof of concept."}
|
||||
|
||||
|
||||
}
|
||||
29
config/dev/ratelimit.auth.toml
Normal file
29
config/dev/ratelimit.auth.toml
Normal file
@@ -0,0 +1,29 @@
|
||||
[[Rules]]
|
||||
# Global Ratelimit
|
||||
match = ""
|
||||
seconds = 60
|
||||
max = 30
|
||||
|
||||
[[Rules]]
|
||||
# One DPS (Doot Per Second) for monitoring
|
||||
match = "GET:/v1/sec/doot"
|
||||
seconds = 5
|
||||
max = 30
|
||||
|
||||
[[Rules]]
|
||||
# 2FA doot probably doesn't need much usage at all, mainly exists as a proof of concept.
|
||||
match = "GET:/v1/sec/2fa-doot"
|
||||
seconds = 10
|
||||
max = 1
|
||||
|
||||
[[Rules]]
|
||||
# One Admin DPS (Doot Per Second) for monitoring
|
||||
match = "GET:/v1/adm/doot"
|
||||
seconds = 5
|
||||
max = 3
|
||||
|
||||
[[Rules]]
|
||||
# 2FA doot probably doesn't need much usage at all, mainly exists as a proof of concept.
|
||||
match = "GET:/v1/adm/2fa-doot"
|
||||
seconds = 10
|
||||
max = 1
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"":
|
||||
{"seconds": 60, "max": 30, "_comment": "Global unauthenticated ratelimit."},
|
||||
|
||||
"GET:/v1/doot":
|
||||
{"seconds": 5, "max": 5, "_comment": "Unauthenticated DOOT for server monitoring."},
|
||||
|
||||
"POST:/v1/login":
|
||||
{"seconds": 60, "max": 3, "_comment": "Prevent bruteforce attacks on Login."},
|
||||
|
||||
"POST:/v1/admin":
|
||||
{"seconds": 60, "max": 1, "_comment": "Prevent bruteforce attacks on Admin Login."},
|
||||
|
||||
"POST:/v1/signup":
|
||||
{"seconds": 1800, "max": 1, "_comment": "Prevent spam account creation."},
|
||||
|
||||
"POST:/v1/forgot":
|
||||
{"seconds": 60, "max": 1, "_comment": "Slow down 'forgot password' enumeration/spam."}
|
||||
}
|
||||
35
config/dev/ratelimit.unauth.toml
Normal file
35
config/dev/ratelimit.unauth.toml
Normal file
@@ -0,0 +1,35 @@
|
||||
[[Rules]]
|
||||
# Global unauthenticated ratelimit.
|
||||
match = ""
|
||||
seconds = 60
|
||||
max = 30
|
||||
|
||||
[[Rules]]
|
||||
# Unauthenticated DOOT for server monitoring.
|
||||
match = "GET:/v1/doot"
|
||||
seconds = 5
|
||||
max = 5
|
||||
|
||||
[[Rules]]
|
||||
# Prevent bruteforce attacks on Login.
|
||||
match = "POST:/v1/login"
|
||||
seconds = 60
|
||||
max = 3
|
||||
|
||||
[[Rules]]
|
||||
# Prevent bruteforce attacks on Admin Login.
|
||||
match = "POST:/v1/admin"
|
||||
seconds = 60
|
||||
max = 1
|
||||
|
||||
[[Rules]]
|
||||
# Prevent spam account creation.
|
||||
match = "GET:/v1/adm/2fa-doot"
|
||||
seconds = 1800
|
||||
max = 1
|
||||
|
||||
[[Rules]]
|
||||
# Slow down 'forgot password' enumeration/spam.
|
||||
match = "POST:/v1/forgot"
|
||||
seconds = 60
|
||||
max = 1
|
||||
@@ -1,21 +1,28 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/yxzzy-wtf/gin-gonic-prepack/config"
|
||||
"github.com/yxzzy-wtf/gin-gonic-prepack/util"
|
||||
)
|
||||
|
||||
type RuleConfig struct {
|
||||
Rules []ruleDescription
|
||||
}
|
||||
|
||||
type ruleDescription struct {
|
||||
seconds int
|
||||
max int
|
||||
Match string `toml:"match"`
|
||||
Seconds int `toml:"seconds"`
|
||||
Max int `toml:"max"`
|
||||
}
|
||||
|
||||
type rule struct {
|
||||
@@ -80,16 +87,27 @@ type megabucket struct {
|
||||
}
|
||||
|
||||
func (m *megabucket) loadFromConfig(filename string) {
|
||||
file, _ := os.Open("conf.json")
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer file.Close()
|
||||
dec := json.NewDecoder(file)
|
||||
ruleMap := map[string]ruleDescription{}
|
||||
if err := dec.Decode(&ruleMap); err != nil {
|
||||
|
||||
rules := RuleConfig{}
|
||||
|
||||
b, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for rkey, r := range ruleMap {
|
||||
m.rules[rkey] = rule{duration: time.Second * time.Duration(r.seconds), limit: r.max}
|
||||
err = toml.Unmarshal(b, &rules)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for _, r := range rules.Rules {
|
||||
fmt.Printf("Loading ratelimit rule: %+v\n", r)
|
||||
m.rules[r.Match] = rule{duration: time.Second * time.Duration(r.Seconds), limit: r.Max}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,8 +137,7 @@ var unauthLoaded = false
|
||||
func UnauthRateLimit() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
if !unauthLoaded {
|
||||
unauthed.loadFromConfig(config.GetConfigPath(config.Config().UnauthedRateLimitConfig))
|
||||
unauthLoaded = true
|
||||
panic("Unauthed rate limits not loaded")
|
||||
}
|
||||
|
||||
ip := c.ClientIP()
|
||||
@@ -145,8 +162,7 @@ var authLoaded = false
|
||||
func AuthedRateLimit() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
if !authLoaded {
|
||||
authed.loadFromConfig(config.GetConfigPath(config.Config().AuthedRateLimitConfig))
|
||||
authLoaded = true
|
||||
panic("Authed rate limits not loaded")
|
||||
}
|
||||
|
||||
pif, exists := c.Get("principal")
|
||||
@@ -162,3 +178,10 @@ func AuthedRateLimit() gin.HandlerFunc {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func LoadRateLimits() {
|
||||
authed.loadFromConfig(config.GetConfigPath(config.Config().AuthedRateLimitConfig))
|
||||
authLoaded = true
|
||||
unauthed.loadFromConfig(config.GetConfigPath(config.Config().UnauthedRateLimitConfig))
|
||||
unauthLoaded = true
|
||||
}
|
||||
|
||||
@@ -18,13 +18,13 @@ var Db *gorm.DB
|
||||
var Dialect gorm.Dialector
|
||||
|
||||
func InitDialect() gorm.Dialector {
|
||||
if config.Config().DbDialect == "sqlite" {
|
||||
return sqlite.Open(config.Config().DbUrl)
|
||||
} else if config.Config().DbDialect == "postgres" {
|
||||
if config.Config().Db.Dialect == "sqlite" {
|
||||
return sqlite.Open(config.Config().Db.Url)
|
||||
} else if config.Config().Db.Dialect == "postgres" {
|
||||
return postgres.New(postgres.Config{
|
||||
DSN: fmt.Sprintf("user=%v password=%v dbname=%v port=%v sslmode=disable TimeZone=UTC",
|
||||
config.Config().DbUsername, config.Config().DbPasswordSecret, config.Config().DbName,
|
||||
config.Config().DbPort),
|
||||
config.Config().Db.Username, config.Config().Db.PasswordSecret, config.Config().Db.Name,
|
||||
config.Config().Db.Port),
|
||||
})
|
||||
} else {
|
||||
panic("No valid DB config set up.")
|
||||
|
||||
Reference in New Issue
Block a user