2019-05-13 01:27:26 +02:00
|
|
|
package serv
|
|
|
|
|
2019-06-04 16:54:51 +02:00
|
|
|
import (
|
2019-10-31 06:14:51 +01:00
|
|
|
"fmt"
|
|
|
|
"os"
|
2019-10-24 08:07:42 +02:00
|
|
|
"regexp"
|
2019-06-14 06:32:15 +02:00
|
|
|
"strings"
|
2019-12-09 07:59:30 +01:00
|
|
|
"time"
|
2019-10-24 08:07:42 +02:00
|
|
|
"unicode"
|
2019-06-14 06:32:15 +02:00
|
|
|
|
2019-10-31 06:14:51 +01:00
|
|
|
"github.com/gobuffalo/flect"
|
2019-10-06 22:28:10 +02:00
|
|
|
"github.com/spf13/viper"
|
2019-06-04 16:54:51 +02:00
|
|
|
)
|
|
|
|
|
2019-05-13 01:27:26 +02:00
|
|
|
type config struct {
|
2019-10-28 06:48:04 +01:00
|
|
|
*viper.Viper
|
|
|
|
|
2019-09-08 20:56:32 +02:00
|
|
|
AppName string `mapstructure:"app_name"`
|
|
|
|
Env string
|
|
|
|
HostPort string `mapstructure:"host_port"`
|
|
|
|
Host string
|
|
|
|
Port string
|
2019-12-31 07:30:20 +01:00
|
|
|
HTTPGZip bool `mapstructure:"http_compress"`
|
2019-09-08 20:56:32 +02:00
|
|
|
WebUI bool `mapstructure:"web_ui"`
|
|
|
|
LogLevel string `mapstructure:"log_level"`
|
|
|
|
EnableTracing bool `mapstructure:"enable_tracing"`
|
|
|
|
UseAllowList bool `mapstructure:"use_allow_list"`
|
2019-11-07 08:37:24 +01:00
|
|
|
Production bool
|
2019-09-08 20:56:32 +02:00
|
|
|
WatchAndReload bool `mapstructure:"reload_on_config_change"`
|
2019-11-02 22:13:17 +01:00
|
|
|
AuthFailBlock bool `mapstructure:"auth_fail_block"`
|
2019-09-20 06:19:11 +02:00
|
|
|
SeedFile string `mapstructure:"seed_file"`
|
2019-09-26 06:35:31 +02:00
|
|
|
MigrationsPath string `mapstructure:"migrations_path"`
|
2020-02-10 07:45:37 +01:00
|
|
|
SecretKey string `mapstructure:"secret_key"`
|
2019-09-26 06:35:31 +02:00
|
|
|
|
|
|
|
Inflections map[string]string
|
2019-05-13 01:27:26 +02:00
|
|
|
|
2020-02-03 07:21:07 +01:00
|
|
|
Auth configAuth
|
|
|
|
Auths []configAuth
|
2019-05-13 01:27:26 +02:00
|
|
|
|
|
|
|
DB struct {
|
2019-12-09 07:59:30 +01:00
|
|
|
Type string
|
|
|
|
Host string
|
|
|
|
Port uint16
|
|
|
|
DBName string
|
|
|
|
User string
|
|
|
|
Password string
|
|
|
|
Schema string
|
|
|
|
PoolSize int32 `mapstructure:"pool_size"`
|
|
|
|
MaxRetries int `mapstructure:"max_retries"`
|
|
|
|
SetUserID bool `mapstructure:"set_user_id"`
|
|
|
|
PingTimeout time.Duration `mapstructure:"ping_timeout"`
|
2019-05-13 01:27:26 +02:00
|
|
|
|
2019-11-25 08:22:33 +01:00
|
|
|
Vars map[string]string `mapstructure:"variables"`
|
|
|
|
Blocklist []string
|
2019-05-13 01:27:26 +02:00
|
|
|
|
|
|
|
Tables []configTable
|
|
|
|
} `mapstructure:"database"`
|
2019-10-06 22:28:10 +02:00
|
|
|
|
2020-02-03 07:21:07 +01:00
|
|
|
Actions []configAction
|
|
|
|
|
2019-10-06 22:28:10 +02:00
|
|
|
Tables []configTable
|
2019-10-24 08:07:42 +02:00
|
|
|
|
2019-12-10 06:03:44 +01:00
|
|
|
RolesQuery string `mapstructure:"roles_query"`
|
|
|
|
Roles []configRole
|
|
|
|
roles map[string]*configRole
|
|
|
|
abacEnabled bool
|
2019-05-13 01:27:26 +02:00
|
|
|
}
|
|
|
|
|
2020-02-03 07:21:07 +01:00
|
|
|
type configAuth struct {
|
|
|
|
Name string
|
|
|
|
Type string
|
|
|
|
Cookie string
|
|
|
|
CredsInHeader bool `mapstructure:"creds_in_header"`
|
|
|
|
|
|
|
|
Rails struct {
|
|
|
|
Version string
|
|
|
|
SecretKeyBase string `mapstructure:"secret_key_base"`
|
|
|
|
URL string
|
|
|
|
Password string
|
|
|
|
MaxIdle int `mapstructure:"max_idle"`
|
|
|
|
MaxActive int `mapstructure:"max_active"`
|
|
|
|
Salt string
|
|
|
|
SignSalt string `mapstructure:"sign_salt"`
|
|
|
|
AuthSalt string `mapstructure:"auth_salt"`
|
|
|
|
}
|
|
|
|
|
|
|
|
JWT struct {
|
|
|
|
Provider string
|
|
|
|
Secret string
|
|
|
|
PubKeyFile string `mapstructure:"public_key_file"`
|
|
|
|
PubKeyType string `mapstructure:"public_key_type"`
|
|
|
|
}
|
|
|
|
|
|
|
|
Header struct {
|
|
|
|
Name string
|
|
|
|
Value string
|
|
|
|
Exists bool
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-09 07:48:18 +01:00
|
|
|
type configColumn struct {
|
|
|
|
Name string
|
2020-01-28 06:26:53 +01:00
|
|
|
Type string
|
2019-12-09 07:48:18 +01:00
|
|
|
ForeignKey string `mapstructure:"related_to"`
|
|
|
|
}
|
|
|
|
|
2019-05-13 01:27:26 +02:00
|
|
|
type configTable struct {
|
2019-10-14 08:51:36 +02:00
|
|
|
Name string
|
|
|
|
Table string
|
|
|
|
Blocklist []string
|
|
|
|
Remotes []configRemote
|
2019-12-09 07:48:18 +01:00
|
|
|
Columns []configColumn
|
2019-05-13 01:27:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
type configRemote struct {
|
|
|
|
Name string
|
|
|
|
ID string
|
|
|
|
Path string
|
|
|
|
URL string
|
2019-06-06 14:57:00 +02:00
|
|
|
Debug bool
|
2019-05-13 01:27:26 +02:00
|
|
|
PassHeaders []string `mapstructure:"pass_headers"`
|
|
|
|
SetHeaders []struct {
|
|
|
|
Name string
|
|
|
|
Value string
|
|
|
|
} `mapstructure:"set_headers"`
|
|
|
|
}
|
|
|
|
|
2019-11-02 22:13:17 +01:00
|
|
|
type configQuery struct {
|
|
|
|
Limit int
|
|
|
|
Filters []string
|
|
|
|
Columns []string
|
|
|
|
DisableFunctions bool `mapstructure:"disable_functions"`
|
|
|
|
Block bool
|
|
|
|
}
|
2019-10-14 08:51:36 +02:00
|
|
|
|
2019-11-02 22:13:17 +01:00
|
|
|
type configInsert struct {
|
|
|
|
Filters []string
|
|
|
|
Columns []string
|
|
|
|
Presets map[string]string
|
|
|
|
Block bool
|
|
|
|
}
|
2019-10-14 08:51:36 +02:00
|
|
|
|
2019-11-02 22:13:17 +01:00
|
|
|
type configUpdate struct {
|
|
|
|
Filters []string
|
|
|
|
Columns []string
|
|
|
|
Presets map[string]string
|
|
|
|
Block bool
|
|
|
|
}
|
2019-10-14 08:51:36 +02:00
|
|
|
|
2019-11-02 22:13:17 +01:00
|
|
|
type configDelete struct {
|
|
|
|
Filters []string
|
|
|
|
Columns []string
|
|
|
|
Block bool
|
|
|
|
}
|
2019-10-31 06:14:51 +01:00
|
|
|
|
2019-11-02 22:13:17 +01:00
|
|
|
type configRoleTable struct {
|
|
|
|
Name string
|
|
|
|
|
|
|
|
Query configQuery
|
|
|
|
Insert configInsert
|
|
|
|
Update configUpdate
|
|
|
|
Delete configDelete
|
2019-10-14 08:51:36 +02:00
|
|
|
}
|
|
|
|
|
2019-10-31 06:14:51 +01:00
|
|
|
type configRole struct {
|
2019-11-07 08:37:24 +01:00
|
|
|
Name string
|
|
|
|
Match string
|
|
|
|
Tables []configRoleTable
|
|
|
|
tablesMap map[string]*configRoleTable
|
2019-10-31 06:14:51 +01:00
|
|
|
}
|
|
|
|
|
2020-02-03 07:21:07 +01:00
|
|
|
type configAction struct {
|
|
|
|
Name string
|
|
|
|
SQL string
|
|
|
|
AuthName string `mapstructure:"auth_name"`
|
|
|
|
}
|
|
|
|
|
2019-10-28 06:48:04 +01:00
|
|
|
func newConfig(name string) *viper.Viper {
|
2019-10-06 22:28:10 +02:00
|
|
|
vi := viper.New()
|
|
|
|
|
|
|
|
vi.SetEnvPrefix("SG")
|
|
|
|
vi.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
|
|
|
vi.AutomaticEnv()
|
|
|
|
|
2019-10-28 06:48:04 +01:00
|
|
|
vi.SetConfigName(name)
|
2019-10-06 22:28:10 +02:00
|
|
|
vi.AddConfigPath(confPath)
|
|
|
|
vi.AddConfigPath("./config")
|
|
|
|
|
2019-10-31 06:14:51 +01:00
|
|
|
if dir, _ := os.Getwd(); strings.HasSuffix(dir, "/serv") {
|
|
|
|
vi.AddConfigPath("../config")
|
|
|
|
}
|
|
|
|
|
2019-10-06 22:28:10 +02:00
|
|
|
vi.SetDefault("host_port", "0.0.0.0:8080")
|
|
|
|
vi.SetDefault("web_ui", false)
|
|
|
|
vi.SetDefault("enable_tracing", false)
|
|
|
|
vi.SetDefault("auth_fail_block", "always")
|
|
|
|
vi.SetDefault("seed_file", "seed.js")
|
|
|
|
|
|
|
|
vi.SetDefault("database.type", "postgres")
|
|
|
|
vi.SetDefault("database.host", "localhost")
|
|
|
|
vi.SetDefault("database.port", 5432)
|
|
|
|
vi.SetDefault("database.user", "postgres")
|
|
|
|
vi.SetDefault("database.schema", "public")
|
|
|
|
|
|
|
|
vi.SetDefault("env", "development")
|
2019-11-28 07:25:46 +01:00
|
|
|
|
|
|
|
vi.BindEnv("env", "GO_ENV") //nolint: errcheck
|
|
|
|
vi.BindEnv("HOST", "HOST") //nolint: errcheck
|
|
|
|
vi.BindEnv("PORT", "PORT") //nolint: errcheck
|
2019-10-06 22:28:10 +02:00
|
|
|
|
|
|
|
vi.SetDefault("auth.rails.max_idle", 80)
|
|
|
|
vi.SetDefault("auth.rails.max_active", 12000)
|
|
|
|
|
|
|
|
return vi
|
|
|
|
}
|
|
|
|
|
2019-10-31 06:14:51 +01:00
|
|
|
func (c *config) Init(vi *viper.Viper) error {
|
|
|
|
if err := vi.Unmarshal(c); err != nil {
|
|
|
|
return fmt.Errorf("unable to decode config, %v", err)
|
|
|
|
}
|
|
|
|
c.Viper = vi
|
|
|
|
|
|
|
|
if len(c.Tables) == 0 {
|
|
|
|
c.Tables = c.DB.Tables
|
|
|
|
}
|
|
|
|
|
2019-11-07 08:37:24 +01:00
|
|
|
if c.UseAllowList {
|
|
|
|
c.Production = true
|
|
|
|
}
|
|
|
|
|
2019-10-31 06:14:51 +01:00
|
|
|
for k, v := range c.Inflections {
|
|
|
|
flect.AddPlural(k, v)
|
|
|
|
}
|
|
|
|
|
|
|
|
for i := range c.Tables {
|
|
|
|
t := c.Tables[i]
|
|
|
|
t.Name = flect.Pluralize(strings.ToLower(t.Name))
|
|
|
|
t.Table = flect.Pluralize(strings.ToLower(t.Table))
|
|
|
|
}
|
|
|
|
|
|
|
|
for i := range c.Roles {
|
|
|
|
r := c.Roles[i]
|
|
|
|
r.Name = strings.ToLower(r.Name)
|
|
|
|
}
|
|
|
|
|
|
|
|
for k, v := range c.DB.Vars {
|
|
|
|
c.DB.Vars[k] = sanitize(v)
|
|
|
|
}
|
|
|
|
|
|
|
|
c.RolesQuery = sanitize(c.RolesQuery)
|
2019-11-25 08:22:33 +01:00
|
|
|
c.roles = make(map[string]*configRole)
|
2019-10-31 06:14:51 +01:00
|
|
|
|
|
|
|
for i := range c.Roles {
|
2019-11-07 08:37:24 +01:00
|
|
|
role := &c.Roles[i]
|
2019-10-31 06:14:51 +01:00
|
|
|
|
2019-11-25 08:22:33 +01:00
|
|
|
if _, ok := c.roles[role.Name]; ok {
|
|
|
|
errlog.Fatal().Msgf("duplicate role '%s' found", role.Name)
|
2019-10-31 06:14:51 +01:00
|
|
|
}
|
2019-12-09 07:48:18 +01:00
|
|
|
|
2019-11-25 08:22:33 +01:00
|
|
|
role.Name = strings.ToLower(role.Name)
|
2019-10-31 06:14:51 +01:00
|
|
|
role.Match = sanitize(role.Match)
|
2019-11-07 08:37:24 +01:00
|
|
|
role.tablesMap = make(map[string]*configRoleTable)
|
|
|
|
|
|
|
|
for n, table := range role.Tables {
|
|
|
|
role.tablesMap[table.Name] = &role.Tables[n]
|
|
|
|
}
|
|
|
|
|
2019-11-25 08:22:33 +01:00
|
|
|
c.roles[role.Name] = role
|
2019-10-31 06:14:51 +01:00
|
|
|
}
|
|
|
|
|
2019-11-25 08:22:33 +01:00
|
|
|
if _, ok := c.roles["user"]; !ok {
|
|
|
|
u := configRole{Name: "user"}
|
|
|
|
c.Roles = append(c.Roles, u)
|
|
|
|
c.roles["user"] = &u
|
2019-10-31 06:14:51 +01:00
|
|
|
}
|
|
|
|
|
2019-11-25 08:22:33 +01:00
|
|
|
if _, ok := c.roles["anon"]; !ok {
|
2019-11-02 22:13:17 +01:00
|
|
|
logger.Warn().Msg("unauthenticated requests will be blocked. no role 'anon' defined")
|
|
|
|
c.AuthFailBlock = true
|
2019-10-31 06:14:51 +01:00
|
|
|
}
|
|
|
|
|
2019-12-10 06:03:44 +01:00
|
|
|
if len(c.RolesQuery) == 0 {
|
|
|
|
c.abacEnabled = false
|
|
|
|
} else {
|
|
|
|
switch len(c.Roles) {
|
|
|
|
case 0, 1:
|
|
|
|
c.abacEnabled = false
|
|
|
|
case 2:
|
|
|
|
_, ok1 := c.roles["anon"]
|
|
|
|
_, ok2 := c.roles["user"]
|
|
|
|
c.abacEnabled = !(ok1 && ok2)
|
|
|
|
default:
|
|
|
|
c.abacEnabled = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-31 06:14:51 +01:00
|
|
|
c.validate()
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *config) validate() {
|
2019-10-25 06:01:22 +02:00
|
|
|
rm := make(map[string]struct{})
|
|
|
|
|
2020-02-03 07:21:07 +01:00
|
|
|
for _, v := range c.Roles {
|
|
|
|
name := strings.ToLower(v.Name)
|
2019-10-31 06:14:51 +01:00
|
|
|
|
2019-10-25 06:01:22 +02:00
|
|
|
if _, ok := rm[name]; ok {
|
2020-02-03 07:21:07 +01:00
|
|
|
errlog.Fatal().Msgf("duplicate config for role '%s'", v.Name)
|
2019-10-25 06:01:22 +02:00
|
|
|
}
|
|
|
|
rm[name] = struct{}{}
|
|
|
|
}
|
|
|
|
|
|
|
|
tm := make(map[string]struct{})
|
|
|
|
|
2020-02-03 07:21:07 +01:00
|
|
|
for _, v := range c.Tables {
|
|
|
|
name := strings.ToLower(v.Name)
|
2019-10-31 06:14:51 +01:00
|
|
|
|
2019-10-25 06:01:22 +02:00
|
|
|
if _, ok := tm[name]; ok {
|
2020-02-03 07:21:07 +01:00
|
|
|
errlog.Fatal().Msgf("duplicate config for table '%s'", v.Name)
|
2019-10-25 06:01:22 +02:00
|
|
|
}
|
|
|
|
tm[name] = struct{}{}
|
|
|
|
}
|
|
|
|
|
2020-02-03 07:21:07 +01:00
|
|
|
am := make(map[string]struct{})
|
|
|
|
|
|
|
|
for _, v := range c.Auths {
|
|
|
|
name := strings.ToLower(v.Name)
|
|
|
|
|
|
|
|
if _, ok := am[name]; ok {
|
|
|
|
errlog.Fatal().Msgf("duplicate config for auth '%s'", v.Name)
|
|
|
|
}
|
|
|
|
am[name] = struct{}{}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, v := range c.Actions {
|
|
|
|
if len(v.AuthName) == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
authName := strings.ToLower(v.AuthName)
|
|
|
|
|
|
|
|
if _, ok := am[authName]; !ok {
|
|
|
|
errlog.Fatal().Msgf("invalid auth_name for action '%s'", v.Name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-25 06:01:22 +02:00
|
|
|
if len(c.RolesQuery) == 0 {
|
|
|
|
logger.Warn().Msgf("no 'roles_query' defined.")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-14 06:32:15 +02:00
|
|
|
func (c *config) getAliasMap() map[string][]string {
|
2019-10-06 22:28:10 +02:00
|
|
|
m := make(map[string][]string, len(c.Tables))
|
2019-05-13 01:27:26 +02:00
|
|
|
|
2019-10-06 22:28:10 +02:00
|
|
|
for i := range c.Tables {
|
|
|
|
t := c.Tables[i]
|
2019-05-13 01:27:26 +02:00
|
|
|
|
2020-01-28 06:26:53 +01:00
|
|
|
if len(t.Table) == 0 || len(t.Columns) != 0 {
|
2019-05-13 01:27:26 +02:00
|
|
|
continue
|
|
|
|
}
|
2019-06-14 06:32:15 +02:00
|
|
|
|
2019-10-31 06:14:51 +01:00
|
|
|
m[t.Table] = append(m[t.Table], t.Name)
|
2019-05-13 01:27:26 +02:00
|
|
|
}
|
|
|
|
return m
|
|
|
|
}
|
2019-10-24 08:07:42 +02:00
|
|
|
|
2019-12-10 06:03:44 +01:00
|
|
|
func (c *config) isABACEnabled() bool {
|
|
|
|
return c.abacEnabled
|
2019-12-09 07:48:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *config) isAnonRoleDefined() bool {
|
|
|
|
_, ok := c.roles["anon"]
|
|
|
|
return ok
|
|
|
|
}
|
|
|
|
|
2019-10-24 08:07:42 +02:00
|
|
|
var varRe1 = regexp.MustCompile(`(?mi)\$([a-zA-Z0-9_.]+)`)
|
|
|
|
var varRe2 = regexp.MustCompile(`\{\{([a-zA-Z0-9_.]+)\}\}`)
|
|
|
|
|
|
|
|
func sanitize(s string) string {
|
|
|
|
s0 := varRe1.ReplaceAllString(s, `{{$1}}`)
|
|
|
|
|
|
|
|
s1 := strings.Map(func(r rune) rune {
|
|
|
|
if unicode.IsSpace(r) {
|
|
|
|
return ' '
|
|
|
|
}
|
|
|
|
return r
|
|
|
|
}, s0)
|
|
|
|
|
|
|
|
return varRe2.ReplaceAllStringFunc(s1, func(m string) string {
|
|
|
|
return strings.ToLower(m)
|
|
|
|
})
|
|
|
|
}
|
2020-02-03 07:21:07 +01:00
|
|
|
|
|
|
|
func getConfigName() string {
|
|
|
|
if len(os.Getenv("GO_ENV")) == 0 {
|
|
|
|
return "dev"
|
|
|
|
}
|
|
|
|
|
|
|
|
ge := strings.ToLower(os.Getenv("GO_ENV"))
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case strings.HasPrefix(ge, "pro"):
|
|
|
|
return "prod"
|
|
|
|
|
|
|
|
case strings.HasPrefix(ge, "sta"):
|
|
|
|
return "stage"
|
|
|
|
|
|
|
|
case strings.HasPrefix(ge, "tes"):
|
|
|
|
return "test"
|
|
|
|
|
|
|
|
case strings.HasPrefix(ge, "dev"):
|
|
|
|
return "dev"
|
|
|
|
}
|
|
|
|
|
|
|
|
return ge
|
|
|
|
}
|
|
|
|
|
|
|
|
func isDev() bool {
|
|
|
|
return strings.HasPrefix(os.Getenv("GO_ENV"), "dev")
|
|
|
|
}
|