Upgrades to Config
* Added config file and config tests * Configs per stack can be set up depending on their config/STACK folder and tested appropriately to add config redundancy
This commit is contained in:
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"AllowFreshAdminGeneration": true,
|
|
||||||
"AdminEmails": ["admin@admin.invalid"]
|
|
||||||
}
|
|
||||||
@@ -2,21 +2,37 @@ package config
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
type StackConfiguration struct {
|
type StackConfiguration struct {
|
||||||
ConfigLoaded bool
|
ConfigLoaded bool
|
||||||
|
|
||||||
AllowFreshAdminGeneration bool
|
AllowFreshAdminGeneration bool
|
||||||
AdminEmails []string
|
AdminEmails []string
|
||||||
AdminHmacEnv string
|
AdminHmacEnv string
|
||||||
UserHmacEnv string
|
UserHmacEnv string
|
||||||
|
AuthedRateLimitConfig string
|
||||||
|
UnauthedRateLimitConfig string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var Environment = os.Getenv("STACK_ENVIRONMENT")
|
||||||
|
|
||||||
var Config = StackConfiguration{}
|
var Config = StackConfiguration{}
|
||||||
|
|
||||||
|
func GetConfigPath(filename string) string {
|
||||||
|
if Environment == "" {
|
||||||
|
Environment = "dev"
|
||||||
|
}
|
||||||
|
return Environment + "/" + filename
|
||||||
|
}
|
||||||
|
|
||||||
func LoadConfig() {
|
func LoadConfig() {
|
||||||
file, _ := os.Open("conf.json")
|
file, err := os.Open(GetConfigPath("conf.json"))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
dec := json.NewDecoder(file)
|
dec := json.NewDecoder(file)
|
||||||
if err := dec.Decode(&Config); err != nil {
|
if err := dec.Decode(&Config); err != nil {
|
||||||
@@ -24,4 +40,6 @@ func LoadConfig() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Config.ConfigLoaded = true
|
Config.ConfigLoaded = true
|
||||||
|
|
||||||
|
log.Printf("Loaded Config for stack " + Environment)
|
||||||
}
|
}
|
||||||
|
|||||||
68
config/config_test.go
Normal file
68
config/config_test.go
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAllConfigs(t *testing.T) {
|
||||||
|
SingleStackTest(t, "dev", StackConfiguration{
|
||||||
|
AllowFreshAdminGeneration: true,
|
||||||
|
AdminEmails: []string{"admin@admin.invalid"},
|
||||||
|
AdminHmacEnv: "ADMIN_HMAC_ENV",
|
||||||
|
UserHmacEnv: "USER_HMAC_ENV",
|
||||||
|
AuthedRateLimitConfig: "ratelimit.auth.json",
|
||||||
|
UnauthedRateLimitConfig: "ratelimit.unauth.json",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func SingleStackTest(t *testing.T, stack string, expected StackConfiguration) {
|
||||||
|
Config = StackConfiguration{}
|
||||||
|
|
||||||
|
if Config.ConfigLoaded {
|
||||||
|
t.Errorf("Config.ConfigLoaded should be false before any processing")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(Config.AdminEmails) > 0 ||
|
||||||
|
Config.AdminHmacEnv != "" ||
|
||||||
|
Config.UserHmacEnv != "" ||
|
||||||
|
Config.AllowFreshAdminGeneration ||
|
||||||
|
Config.AuthedRateLimitConfig != "" ||
|
||||||
|
Config.UnauthedRateLimitConfig != "" { // Extend this IF for any other config values
|
||||||
|
t.Errorf("Config already has values before loading")
|
||||||
|
}
|
||||||
|
|
||||||
|
Environment = stack
|
||||||
|
LoadConfig()
|
||||||
|
|
||||||
|
if !Config.ConfigLoaded {
|
||||||
|
t.Errorf("Config was not set to loaded")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally test values
|
||||||
|
if Config.AllowFreshAdminGeneration != expected.AllowFreshAdminGeneration {
|
||||||
|
t.Errorf("AllowFreshAdminGeneration value not set properly")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, email := range Config.AdminEmails {
|
||||||
|
if expected.AdminEmails[i] != email {
|
||||||
|
t.Errorf("AdminEmails value not set properly, expected %v at %v, was %v", expected.AdminEmails[i], i, email)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if Config.AdminHmacEnv != expected.AdminHmacEnv {
|
||||||
|
t.Errorf("AdminHmacEnv value not set properly")
|
||||||
|
}
|
||||||
|
|
||||||
|
if Config.UserHmacEnv != expected.UserHmacEnv {
|
||||||
|
t.Errorf("UserHmacEnv value not set properly")
|
||||||
|
}
|
||||||
|
|
||||||
|
if Config.AuthedRateLimitConfig != expected.AuthedRateLimitConfig {
|
||||||
|
t.Errorf("AuthedRateLimitConfig value not set properly")
|
||||||
|
}
|
||||||
|
|
||||||
|
if Config.UnauthedRateLimitConfig != expected.UnauthedRateLimitConfig {
|
||||||
|
t.Errorf("UnauthedRateLimitConfig value not set properly")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
8
config/dev/conf.json
Normal file
8
config/dev/conf.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"AllowFreshAdminGeneration": true,
|
||||||
|
"AdminEmails": ["admin@admin.invalid"],
|
||||||
|
"AdminHmacEnv": "ADMIN_HMAC_ENV",
|
||||||
|
"UserHmacEnv": "USER_HMAC_ENV",
|
||||||
|
"AuthedRateLimitConfig": "ratelimit.auth.json",
|
||||||
|
"UnauthedRateLimitConfig": "ratelimit.unauth.json"
|
||||||
|
}
|
||||||
17
config/dev/ratelimit.auth.json
Normal file
17
config/dev/ratelimit.auth.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"": {"seconds": 60, "max": 30, "_comment": "Global ratelimit."},
|
||||||
|
|
||||||
|
"/v1/sec/doot":
|
||||||
|
{"seconds": 5, "max": 3, "_comment": "One DPS (Doot Per Second) for monitoring?"},
|
||||||
|
|
||||||
|
"/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."},
|
||||||
|
|
||||||
|
"/v1/adm/doot":
|
||||||
|
{"seconds": 5, "max": 3, "_comment": "One DPS (Doot Per Second) for monitoring?"},
|
||||||
|
|
||||||
|
"/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."}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
19
config/dev/ratelimit.unauth.json
Normal file
19
config/dev/ratelimit.unauth.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"":
|
||||||
|
{"seconds": 60, "max": 30, "_comment": "Global unauthenticated ratelimit."},
|
||||||
|
|
||||||
|
"/v1/doot":
|
||||||
|
{"seconds": 5, "max": 5, "_comment": "Unauthenticated DOOT for server monitoring."},
|
||||||
|
|
||||||
|
"/v1/login":
|
||||||
|
{"seconds": 60, "max": 3, "_comment": "Prevent bruteforce attacks on Login."},
|
||||||
|
|
||||||
|
"/v1/admin":
|
||||||
|
{"seconds": 60, "max": 1, "_comment": "Prevent bruteforce attacks on Admin Login."},
|
||||||
|
|
||||||
|
"/v1/signup":
|
||||||
|
{"seconds": 1800, "max": 1, "_comment": "Prevent spam account creation."},
|
||||||
|
|
||||||
|
"/v1/forgot":
|
||||||
|
{"seconds": 60, "max": 1, "_comment": "Slow down 'forgot password' enumeration/spam."}
|
||||||
|
}
|
||||||
@@ -301,8 +301,7 @@ func UserResetForgottenPassword() gin.HandlerFunc {
|
|||||||
|
|
||||||
resetting.SetPassword(resetVals.NewPassword)
|
resetting.SetPassword(resetVals.NewPassword)
|
||||||
if err := resetting.Save(); err != nil {
|
if err := resetting.Save(); err != nil {
|
||||||
log.
|
log.Println("could not save user", err)
|
||||||
log.Error("could not save user", err)
|
|
||||||
c.AbortWithStatus(http.StatusUnauthorized)
|
c.AbortWithStatus(http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,23 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/yxzzy-wtf/gin-gonic-prepack/config"
|
||||||
"github.com/yxzzy-wtf/gin-gonic-prepack/util"
|
"github.com/yxzzy-wtf/gin-gonic-prepack/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type ruleDescription struct {
|
||||||
|
seconds int
|
||||||
|
max int
|
||||||
|
}
|
||||||
|
|
||||||
type rule struct {
|
type rule struct {
|
||||||
duration time.Duration
|
duration time.Duration
|
||||||
limit int
|
limit int
|
||||||
@@ -71,6 +79,20 @@ type megabucket struct {
|
|||||||
rules map[string]rule
|
rules map[string]rule
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *megabucket) loadFromConfig(filename string) {
|
||||||
|
file, _ := os.Open("conf.json")
|
||||||
|
defer file.Close()
|
||||||
|
dec := json.NewDecoder(file)
|
||||||
|
ruleMap := map[string]ruleDescription{}
|
||||||
|
if err := dec.Decode(&ruleMap); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for rkey, r := range ruleMap {
|
||||||
|
m.rules[rkey] = rule{duration: time.Second * time.Duration(r.seconds), limit: r.max}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (m *megabucket) take(signature string, resource string) bool {
|
func (m *megabucket) take(signature string, resource string) bool {
|
||||||
b, ex := m.buckets[signature]
|
b, ex := m.buckets[signature]
|
||||||
if !ex {
|
if !ex {
|
||||||
@@ -86,10 +108,9 @@ func (m *megabucket) take(signature string, resource string) bool {
|
|||||||
|
|
||||||
var unauthed = megabucket{
|
var unauthed = megabucket{
|
||||||
buckets: map[string]bucket{},
|
buckets: map[string]bucket{},
|
||||||
rules: map[string]rule{
|
rules: map[string]rule{},
|
||||||
"*": {duration: time.Second * 10, limit: 20},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
var unauthLoaded = false
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies rate limiting to unauthorized actors based on their IP address.
|
* Applies rate limiting to unauthorized actors based on their IP address.
|
||||||
@@ -97,6 +118,11 @@ var unauthed = megabucket{
|
|||||||
*/
|
*/
|
||||||
func UnauthRateLimit() gin.HandlerFunc {
|
func UnauthRateLimit() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
|
if !unauthLoaded {
|
||||||
|
unauthed.loadFromConfig(config.GetConfigPath(config.Config.UnauthedRateLimitConfig))
|
||||||
|
unauthLoaded = true
|
||||||
|
}
|
||||||
|
|
||||||
ip := c.ClientIP()
|
ip := c.ClientIP()
|
||||||
|
|
||||||
if !unauthed.take(ip, "") {
|
if !unauthed.take(ip, "") {
|
||||||
@@ -108,10 +134,9 @@ func UnauthRateLimit() gin.HandlerFunc {
|
|||||||
|
|
||||||
var authed = megabucket{
|
var authed = megabucket{
|
||||||
buckets: map[string]bucket{},
|
buckets: map[string]bucket{},
|
||||||
rules: map[string]rule{
|
rules: map[string]rule{},
|
||||||
"*": {duration: time.Second * 10, limit: 5},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
var authLoaded = false
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Authorized rate limit. Using the UID of the authorized user as the
|
* Authorized rate limit. Using the UID of the authorized user as the
|
||||||
@@ -119,6 +144,11 @@ var authed = megabucket{
|
|||||||
*/
|
*/
|
||||||
func AuthedRateLimit() gin.HandlerFunc {
|
func AuthedRateLimit() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
|
if !authLoaded {
|
||||||
|
authed.loadFromConfig(config.GetConfigPath(config.Config.AuthedRateLimitConfig))
|
||||||
|
authLoaded = true
|
||||||
|
}
|
||||||
|
|
||||||
pif, exists := c.Get("principal")
|
pif, exists := c.Get("principal")
|
||||||
p := pif.(util.PrincipalInfo)
|
p := pif.(util.PrincipalInfo)
|
||||||
if !exists {
|
if !exists {
|
||||||
|
|||||||
1
main.go
1
main.go
@@ -75,6 +75,7 @@ func main() {
|
|||||||
v1Admin := v1.Group("/adm", core.AdminAuth())
|
v1Admin := v1.Group("/adm", core.AdminAuth())
|
||||||
|
|
||||||
v1Admin.GET("/doot", core.Doot())
|
v1Admin.GET("/doot", core.Doot())
|
||||||
|
v1Admin.GET("/2fa-doot", core.LiveTwoFactor(), core.Doot())
|
||||||
|
|
||||||
// Start server
|
// Start server
|
||||||
if err := http.ListenAndServe(":9091", r); err != nil {
|
if err := http.ListenAndServe(":9091", r); err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user