diff --git a/.gitignore b/.gitignore index c85ba3c..69db66e 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,10 @@ build .idea/ coverage.out *.swp + keys/ *.rsa *.rsa.pub + +cover.out +coverage.html diff --git a/fixtures/conf.d/config_base.json b/fixtures/conf.d/config_base.json new file mode 100644 index 0000000..fc7404f --- /dev/null +++ b/fixtures/conf.d/config_base.json @@ -0,0 +1,27 @@ +{ + "sensu": [ + { + "name": "us-east-2", + "host": "10.0.0.2" + }, + { + "name": "us-west-2", + "host": "192.168.0.2", + "port": 4569, + "timeout": 5 + } + ], + "uchiwa": { + "host": "127.0.0.1", + "port": 3000, + "users": [ + { + "username" : "admin", + "password": "secret", + "role": { + "readonly": false + } + } + ] + } +} diff --git a/fixtures/conf.d/config_overwrite.json b/fixtures/conf.d/config_overwrite.json new file mode 100644 index 0000000..aa70f63 --- /dev/null +++ b/fixtures/conf.d/config_overwrite.json @@ -0,0 +1,21 @@ +{ + "sensu": [ + { + "name": "us-east-3", + "host": "10.0.0.3" + } + ], + "uchiwa": { + "host": "192.168.0.1", + "port": 8000, + "users": [ + { + "username" : "readonly", + "password": "secret", + "role": { + "readonly": true + } + } + ] + } +} diff --git a/fixtures/config_test.json b/fixtures/config_test.json index bf02db0..af883a9 100644 --- a/fixtures/config_test.json +++ b/fixtures/config_test.json @@ -1,14 +1,19 @@ { "sensu": [ { - "name": "qux", - "host": "hidden-ravine-4272.herokuapp.com", - "port": 80 + "name": "us-east-1", + "host": "10.0.0.1" + }, + { + "name": "us-west-1", + "host": "192.168.0.1", + "port": 4570, + "timeout": 5 } ], "uchiwa": { "host": "0.0.0.0", - "port": 3000, + "port": 8080, "user": "foo", "pass": "bar" } diff --git a/fixtures/config_test_multiple.json b/fixtures/config_test_multiple.json deleted file mode 100644 index 9c6f6d4..0000000 --- a/fixtures/config_test_multiple.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "sensu": [ - { - "name": "qux", - "host": "hidden-ravine-4272.herokuapp.com", - "port": 80 - } - ], - "uchiwa": { - "host": "0.0.0.0", - "port": 3000, - "users": [ - { - "username" : "admin", - "password":"test", - "role": { "name" : "admin", "readonly": true } - }, - { - "username" : "admin", - "password":"test", - "role": { "name" : "dev", "readonly": false } - } - ] - } -} diff --git a/uchiwa.go b/uchiwa.go index a079156..a6a2663 100644 --- a/uchiwa.go +++ b/uchiwa.go @@ -13,13 +13,11 @@ import ( func main() { configFile := flag.String("c", "./config.json", "Full or relative path to the configuration file") + configDir := flag.String("d", "", "Full or relative path to the configuration directory, or comma delimited directories") publicPath := flag.String("p", "public", "Full or relative path to the public directory") flag.Parse() - config, err := config.Load(*configFile) - if err != nil { - logger.Fatal(err) - } + config := config.Load(*configFile, *configDir) logger.Debug("Debug mode enabled") diff --git a/uchiwa/config/config.go b/uchiwa/config/config.go index 91cb916..9f3d228 100644 --- a/uchiwa/config/config.go +++ b/uchiwa/config/config.go @@ -5,91 +5,123 @@ import ( "fmt" "math/rand" "os" + "path/filepath" "strings" + "github.com/palourde/mergo" "github.com/sensu/uchiwa/uchiwa/auth" "github.com/sensu/uchiwa/uchiwa/logger" - "github.com/sensu/uchiwa/uchiwa/structs" ) -// Config struct contains []SensuConfig and UchiwaConfig structs -type Config struct { - Dashboard *GlobalConfig `json:",omitempty"` - Sensu []SensuConfig - Uchiwa GlobalConfig -} +var ( + defaultGlobalConfig = GlobalConfig{ + Host: "0.0.0.0", + Port: 3000, + Refresh: 10, + Ldap: Ldap{ + Port: 389, + Security: "none", + UserAttribute: "sAMAccountName", + UserObjectClass: "person", + GroupMemberAttribute: "member", + GroupObjectClass: "groupOfNames", + }, + Audit: Audit{ + Level: "default", + Logfile: "/var/log/sensu/sensu-enterprise-dashboard-audit.log", + }, + } + defaultSensuConfig = SensuConfig{ + Port: 4567, + Timeout: 10, + } + defaultConfig = Config{ + Uchiwa: defaultGlobalConfig, + } +) -// SensuConfig struct contains conf about a Sensu API -type SensuConfig struct { - Name string - Host string - Port int - Ssl bool - Insecure bool - URL string - User string - Path string - Pass string - Timeout int -} +// Load retrieves a specified configuration file and return a Config struct +func Load(file, directories string) *Config { + // Load the configuration file + conf, err := loadFile(file) + if err != nil { + logger.Fatal(err) + } -// GlobalConfig struct contains conf about Uchiwa -type GlobalConfig struct { - Host string - Port int - Refresh int - Pass string - User string - Users []auth.User - Audit Audit - Auth structs.Auth - Db Db - Enterprise bool - Github Github - Ldap Ldap -} + // Apply default configs to the configuration file + if err := mergo.Merge(conf, defaultConfig); err != nil { + logger.Fatal(err) + } + for i := range conf.Sensu { + if err := mergo.Merge(&conf.Sensu[i], defaultSensuConfig); err != nil { + logger.Fatal(err) + } + } -// Audit struct contains the config of the Audit logger -type Audit struct { - Level string - Logfile string -} + if directories != "" { + configDir := loadDirectories(directories) + // Overwrite the file config with the configs from the directories + if err := mergo.MergeWithOverwrite(conf, configDir); err != nil { + logger.Fatal(err) + } + } -// Db struct contains the SQL driver configuration -type Db struct { - Driver string - Scheme string -} + conf.Sensu = initSensu(conf.Sensu) + conf.Uchiwa = initUchiwa(conf.Uchiwa) -// Github struct contains the GitHub driver configuration -type Github struct { - ClientID string - ClientSecret string - Roles []auth.Role - Server string + return conf } -// Ldap struct contains the LDAP driver configuration -type Ldap struct { - Server string - Port int - BaseDN string - BindUser string - BindPass string - GroupBaseDN string - GroupObjectClass string - GroupMemberAttribute string - Insecure bool - Roles []auth.Role - Security string - UserAttribute string - UserBaseDN string - UserObjectClass string +// loadDirectories loads a Config struct from one or multiple directories of configuration +func loadDirectories(path string) *Config { + conf := new(Config) + var configFiles []string + directories := strings.Split(strings.ToLower(path), ",") + + for _, directory := range directories { + // Find all JSON files in the specified directories + files, err := filepath.Glob(filepath.Join(directory, "*.json")) + if err != nil { + logger.Warning(err) + continue + } + + // Add the files found to a slice of configuration files to open + for _, file := range files { + configFiles = append(configFiles, file) + } + } + + // Load every configuration files and merge them together bit by bit + for _, file := range configFiles { + // Load the config from the file + c, err := loadFile(file) + if err != nil { + logger.Warning(err) + continue + } + + // Apply this configuration to the existing one + if err := mergo.MergeWithOverwrite(conf, c); err != nil { + logger.Warning(err) + continue + } + } + + // Apply the default config to the Sensu APIs + for i := range conf.Sensu { + if err := mergo.Merge(&conf.Sensu[i], defaultSensuConfig); err != nil { + logger.Fatal(err) + } + } + + return conf } -// Load retrieves a specified configuration file and return a Config struct -func Load(path string) (*Config, error) { - logger.Infof("Loading configuration file %s", path) +// loadFile loads a Config struct from a configuration file +func loadFile(path string) (*Config, error) { + logger.Infof("Loading the configuration file %s", path) + c := new(Config) file, err := os.Open(path) if err != nil { @@ -104,105 +136,64 @@ func Load(path string) (*Config, error) { return nil, fmt.Errorf("Error decoding file %s: %s", path, err) } - c.initUchiwa() - c.initSensu() - return c, nil } -func (c *Config) initSensu() { - for i, api := range c.Sensu { - prot := "http" +func initSensu(apis []SensuConfig) []SensuConfig { + for i, api := range apis { + // Set a datacenter name if missing if api.Name == "" { - logger.Warningf("Sensu API %s has no name property. Generating random one...", api.URL) - c.Sensu[i].Name = fmt.Sprintf("sensu-%v", rand.Intn(100)) + logger.Warningf("Sensu API %s has no name property, make sure to set it in your configuration. Generating a temporary one...", api.URL) + apis[i].Name = fmt.Sprintf("sensu-%v", rand.Intn(100)) } - // escape special characters in DC name + + // Escape special characters in DC name r := strings.NewReplacer(":", "", "/", "", ";", "", "?", "") - c.Sensu[i].Name = r.Replace(api.Name) + apis[i].Name = r.Replace(apis[i].Name) + // Make sure the host is not empty if api.Host == "" { logger.Fatalf("Sensu API %q Host is missing", api.Name) } - if api.Timeout == 0 { - c.Sensu[i].Timeout = 10 - } else if api.Timeout >= 1000 { // backward compatibility with < 0.3.0 version - c.Sensu[i].Timeout = api.Timeout / 1000 - } - if api.Port == 0 { - c.Sensu[i].Port = 4567 - } + + // Determine the protocol to use + prot := "http" if api.Ssl { prot += "s" } - c.Sensu[i].URL = fmt.Sprintf("%s://%s:%d%s", prot, api.Host, c.Sensu[i].Port, api.Path) - } -} -func (c *Config) initUchiwa() { - if c.Dashboard != nil { - c.Uchiwa = *c.Dashboard - } - if c.Uchiwa.Host == "" { - c.Uchiwa.Host = "0.0.0.0" - } - if c.Uchiwa.Port == 0 { - c.Uchiwa.Port = 3000 - } - if c.Uchiwa.Refresh == 0 { - c.Uchiwa.Refresh = 10 - } else if c.Uchiwa.Refresh >= 1000 { // backward compatibility with < 0.3.0 version - c.Uchiwa.Refresh = c.Uchiwa.Refresh / 1000 + // Set the API URL + apis[i].URL = fmt.Sprintf("%s://%s:%d%s", prot, api.Host, api.Port, api.Path) } + return apis +} - // authentication - if c.Uchiwa.Github.Server != "" { - c.Uchiwa.Auth.Driver = "github" - } else if c.Uchiwa.Ldap.Server != "" { - c.Uchiwa.Auth.Driver = "ldap" - if c.Uchiwa.Ldap.Port == 0 { - c.Uchiwa.Ldap.Port = 389 - } - if c.Uchiwa.Ldap.Security == "" { - c.Uchiwa.Ldap.Security = "none" - } - if c.Uchiwa.Ldap.UserAttribute == "" { - c.Uchiwa.Ldap.UserAttribute = "sAMAccountName" - } - if c.Uchiwa.Ldap.UserObjectClass == "" { - c.Uchiwa.Ldap.UserObjectClass = "person" - } - if c.Uchiwa.Ldap.GroupMemberAttribute == "" { - c.Uchiwa.Ldap.GroupMemberAttribute = "member" +func initUchiwa(global GlobalConfig) GlobalConfig { + // Set the proper authentication driver + if global.Github.Server != "" { + global.Auth.Driver = "github" + } else if global.Ldap.Server != "" { + global.Auth.Driver = "ldap" + if global.Ldap.GroupBaseDN == "" { + global.Ldap.GroupBaseDN = global.Ldap.BaseDN } - if c.Uchiwa.Ldap.GroupObjectClass == "" { - c.Uchiwa.Ldap.GroupObjectClass = "groupOfNames" + if global.Ldap.UserBaseDN == "" { + global.Ldap.UserBaseDN = global.Ldap.BaseDN } - if c.Uchiwa.Ldap.GroupBaseDN == "" { - c.Uchiwa.Ldap.GroupBaseDN = c.Uchiwa.Ldap.BaseDN - } - if c.Uchiwa.Ldap.UserBaseDN == "" { - c.Uchiwa.Ldap.UserBaseDN = c.Uchiwa.Ldap.BaseDN - } - - } else if c.Uchiwa.Db.Driver != "" && c.Uchiwa.Db.Scheme != "" { - c.Uchiwa.Auth.Driver = "sql" - } else if len(c.Uchiwa.Users) != 0 { + } else if global.Db.Driver != "" && global.Db.Scheme != "" { + global.Auth.Driver = "sql" + } else if len(global.Users) != 0 { logger.Debug("Loading multiple users from the config") - c.Uchiwa.Auth.Driver = "simple" - } else if c.Uchiwa.User != "" && c.Uchiwa.Pass != "" { + global.Auth.Driver = "simple" + } else if global.User != "" && global.Pass != "" { logger.Debug("Loading single user from the config") - c.Uchiwa.Auth.Driver = "simple" - c.Uchiwa.Users = append(c.Uchiwa.Users, auth.User{Username: c.Uchiwa.User, Password: c.Uchiwa.Pass, FullName: c.Uchiwa.User}) - } + global.Auth.Driver = "simple" - // audit - if c.Uchiwa.Audit.Level != "verbose" && c.Uchiwa.Audit.Level != "disabled" { - c.Uchiwa.Audit.Level = "default" - } - if c.Uchiwa.Audit.Logfile == "" { - c.Uchiwa.Audit.Logfile = "/var/log/sensu/sensu-enterprise-dashboard-audit.log" + // Support multiple users + global.Users = append(global.Users, auth.User{Username: global.User, Password: global.Pass, FullName: global.User}) } + + return global } // GetPublic generates the public configuration diff --git a/uchiwa/config/config_test.go b/uchiwa/config/config_test.go index 9cf3a13..57e33c5 100644 --- a/uchiwa/config/config_test.go +++ b/uchiwa/config/config_test.go @@ -3,72 +3,143 @@ package config import ( "testing" + "github.com/sensu/uchiwa/uchiwa/auth" "github.com/stretchr/testify/assert" ) func TestLoad(t *testing.T) { - _, err := Load("../foo.bar") - assert.NotNil(t, err, "should return an error when file does not exist") + // Only default & config file + conf := Load("../../fixtures/config_test.json", "") + assert.Equal(t, 4567, conf.Sensu[0].Port) + assert.Equal(t, 10, conf.Sensu[0].Timeout) + assert.Equal(t, 10, conf.Uchiwa.Refresh) + assert.Equal(t, 389, conf.Uchiwa.Ldap.Port) + assert.Equal(t, "default", conf.Uchiwa.Audit.Level) - _, err = Load("../uchiwa.go") - assert.NotNil(t, err, "should return an error when it cannot parse a file") - - conf, err := Load("../../fixtures/config_test.json") - assert.Nil(t, err, "got unexpected error: %s", err) - - // private config - assert.NotEqual(t, "*****", conf.Uchiwa.User, "Uchiwa user in private config shouldn't be masked") - assert.NotEqual(t, "*****", conf.Uchiwa.Pass, "Uchiwa pass in private config shouldn't be masked") - for i := range conf.Sensu { - assert.NotEqual(t, "*****", conf.Sensu[i].User, "Sensu APIs user in private config shouldn't be masked") - assert.NotEqual(t, "*****", conf.Sensu[i].Pass, "Sensu APIs pass in private config shouldn't be masked") - } - assert.Equal(t, 1, len(conf.Uchiwa.Users)) - - // public config - public := conf.GetPublic() - assert.Equal(t, "*****", public.Uchiwa.User, "Uchiwa user in public config should be masked") - assert.Equal(t, "*****", public.Uchiwa.Pass, "Uchiwa pass in public config should be masked") - for i := range public.Sensu { - assert.Equal(t, "*****", public.Sensu[i].User, "Sensu APIs user in public config should be masked") - assert.Equal(t, "*****", public.Sensu[i].Pass, "Sensu APIs pass in public config should be masked") - } - assert.Equal(t, 0, len(public.Uchiwa.Users)) + conf = Load("../../fixtures/config_test.json", "../../fixtures/conf.d") + assert.Equal(t, 5, len(conf.Sensu)) + assert.Equal(t, 4567, conf.Sensu[0].Port) + assert.Equal(t, 10, conf.Sensu[0].Timeout) + assert.Equal(t, 4569, conf.Sensu[3].Port) + assert.Equal(t, 5, conf.Sensu[3].Timeout) + assert.Equal(t, 4567, conf.Sensu[4].Port) + assert.Equal(t, 10, conf.Sensu[4].Timeout) + assert.Equal(t, "192.168.0.1", conf.Uchiwa.Host) + assert.Equal(t, 8000, conf.Uchiwa.Port) + assert.Equal(t, 2, len(conf.Uchiwa.Users)) } -func TestLoadArrayOfUsers(t *testing.T) { - conf, err := Load("../../fixtures/config_test_multiple.json") - assert.Nil(t, err, "got unexpected error: %s", err) - assert.NotNil(t, conf, "conf should not be nil") +func TestLoadDirectories(t *testing.T) { + conf := loadDirectories("foobar,../../fixtures/conf.d") + + assert.Equal(t, 3, len(conf.Sensu)) + assert.Equal(t, "us-east-2", conf.Sensu[0].Name) + assert.Equal(t, "us-west-2", conf.Sensu[1].Name) + assert.Equal(t, "us-east-3", conf.Sensu[2].Name) - assert.Equal(t, "simple", conf.Uchiwa.Auth.Driver, "Uchiwa authentication driver should be 'simple'") assert.Equal(t, 2, len(conf.Uchiwa.Users)) + assert.Equal(t, "admin", conf.Uchiwa.Users[0].Username) + assert.Equal(t, "readonly", conf.Uchiwa.Users[1].Username) + assert.Equal(t, "192.168.0.1", conf.Uchiwa.Host) + assert.Equal(t, 8000, conf.Uchiwa.Port) } -func TestLoadArrayOfUsersOnPublicGet(t *testing.T) { - conf, err := Load("../../fixtures/config_test_multiple.json") +func TestLoadFile(t *testing.T) { + _, err := loadFile("foo.bar") + assert.NotNil(t, err, "foo.bar does not exist") + + _, err = loadFile("config.go") + assert.NotNil(t, err, "config.go is not a JSON file") + + conf, err := loadFile("../../fixtures/config_test.json") assert.Nil(t, err, "got unexpected error: %s", err) - assert.NotNil(t, conf, "conf should not be nil") - assert.Equal(t, "simple", conf.Uchiwa.Auth.Driver, "Uchiwa authentication driver should be 'simple'") - public := conf.GetPublic() - assert.Equal(t, 0, len(public.Uchiwa.Users)) + // Sensu APIs + assert.Equal(t, 2, len(conf.Sensu)) + assert.Equal(t, "us-east-1", conf.Sensu[0].Name) + assert.Equal(t, "us-west-1", conf.Sensu[1].Name) + assert.Equal(t, 4570, conf.Sensu[1].Port) + assert.Equal(t, 5, conf.Sensu[1].Timeout) + + // Uchiwa + assert.Equal(t, "0.0.0.0", conf.Uchiwa.Host) + assert.Equal(t, 8080, conf.Uchiwa.Port) + assert.Equal(t, "foo", conf.Uchiwa.User) + assert.Equal(t, "bar", conf.Uchiwa.Pass) } func TestInitSensu(t *testing.T) { - c := Config{ + apis := []SensuConfig{ + SensuConfig{Host: "10.0.0.1", Port: 4567}, + SensuConfig{Name: "test/1", Host: "10.0.10.1", Port: 4567, Ssl: true}, + } + + sensu := initSensu(apis) + assert.NotEqual(t, "", sensu[0].Name) + assert.Equal(t, "http://10.0.0.1:4567", sensu[0].URL) + assert.Equal(t, "test1", sensu[1].Name) + assert.Equal(t, "https://10.0.10.1:4567", sensu[1].URL) +} + +func TestInitUchiwa(t *testing.T) { + conf := GlobalConfig{Github: Github{Server: "127.0.0.1"}} + uchiwa := initUchiwa(conf) + assert.Equal(t, "github", uchiwa.Auth.Driver) + + conf = GlobalConfig{Ldap: Ldap{BaseDN: "cn=foo", Server: "127.0.0.1"}} + uchiwa = initUchiwa(conf) + assert.Equal(t, "ldap", uchiwa.Auth.Driver) + assert.Equal(t, Ldap{BaseDN: "cn=foo", GroupBaseDN: "cn=foo", UserBaseDN: "cn=foo", Server: "127.0.0.1"}, uchiwa.Ldap) + + conf = GlobalConfig{Db: Db{Driver: "mysql", Scheme: "foo"}} + uchiwa = initUchiwa(conf) + assert.Equal(t, "sql", uchiwa.Auth.Driver) + + conf = GlobalConfig{Users: []auth.User{auth.User{ID: 1}}} + uchiwa = initUchiwa(conf) + assert.Equal(t, "simple", uchiwa.Auth.Driver) + + conf = GlobalConfig{User: "foo", Pass: "secret"} + uchiwa = initUchiwa(conf) + assert.Equal(t, "simple", uchiwa.Auth.Driver) + assert.Equal(t, []auth.User{auth.User{ID: 0, FullName: "foo", Password: "secret", Username: "foo"}}, uchiwa.Users) +} + +func TestGetPublic(t *testing.T) { + conf := Config{ Sensu: []SensuConfig{ - {Name: "foo ? bar", Host: "127.0.0.1"}, - {Name: "bar / foo", Host: "127.0.0.1"}, + SensuConfig{ + User: "foo", + Pass: "secret", + }, + }, + Uchiwa: GlobalConfig{ + User: "foo", + Pass: "secret", + Users: []auth.User{auth.User{ID: 1}}, + Db: Db{Scheme: "foo"}, + Github: Github{ClientID: "foo", ClientSecret: "secret"}, + Ldap: Ldap{BindPass: "secret"}, }, } - c.initSensu() + pubConf := conf.GetPublic() - expectedConfig := []SensuConfig{ - {Name: "foo bar", Host: "127.0.0.1", Port: 4567, Ssl: false, Insecure: false, URL: "http://127.0.0.1:4567", User: "", Path: "", Pass: "", Timeout: 10}, - {Name: "bar foo", Host: "127.0.0.1", Port: 4567, Ssl: false, Insecure: false, URL: "http://127.0.0.1:4567", User: "", Path: "", Pass: "", Timeout: 10}, - } - assert.Equal(t, expectedConfig, c.Sensu) + assert.NotEqual(t, conf, pubConf) + + assert.Equal(t, "foo", conf.Sensu[0].User) + assert.Equal(t, "secret", conf.Sensu[0].Pass) + assert.Equal(t, "foo", conf.Uchiwa.User) + assert.Equal(t, "secret", conf.Uchiwa.Pass) + + assert.Equal(t, "*****", pubConf.Sensu[0].User) + assert.Equal(t, "*****", pubConf.Sensu[0].Pass) + assert.Equal(t, "*****", pubConf.Uchiwa.User) + assert.Equal(t, "*****", pubConf.Uchiwa.Pass) + assert.Equal(t, []auth.User{}, pubConf.Uchiwa.Users) + assert.Equal(t, "*****", pubConf.Uchiwa.Db.Scheme) + assert.Equal(t, "*****", pubConf.Uchiwa.Github.ClientID) + assert.Equal(t, "*****", pubConf.Uchiwa.Github.ClientSecret) + assert.Equal(t, "*****", pubConf.Uchiwa.Ldap.BindPass) } diff --git a/uchiwa/config/structs.go b/uchiwa/config/structs.go new file mode 100644 index 0000000..403c33e --- /dev/null +++ b/uchiwa/config/structs.go @@ -0,0 +1,81 @@ +package config + +import ( + "github.com/sensu/uchiwa/uchiwa/auth" + "github.com/sensu/uchiwa/uchiwa/structs" +) + +// Config struct contains []SensuConfig and UchiwaConfig structs +type Config struct { + Dashboard *GlobalConfig `json:",omitempty"` + Sensu []SensuConfig + Uchiwa GlobalConfig +} + +// SensuConfig struct contains conf about a Sensu API +type SensuConfig struct { + Name string + Host string + Port int + Ssl bool + Insecure bool + URL string + User string + Path string + Pass string + Timeout int +} + +// GlobalConfig struct contains conf about Uchiwa +type GlobalConfig struct { + Host string + Port int + Refresh int + Pass string + User string + Users []auth.User + Audit Audit + Auth structs.Auth + Db Db + Enterprise bool + Github Github + Ldap Ldap +} + +// Audit struct contains the config of the Audit logger +type Audit struct { + Level string + Logfile string +} + +// Db struct contains the SQL driver configuration +type Db struct { + Driver string + Scheme string +} + +// Github struct contains the GitHub driver configuration +type Github struct { + ClientID string + ClientSecret string + Roles []auth.Role + Server string +} + +// Ldap struct contains the LDAP driver configuration +type Ldap struct { + Server string + Port int + BaseDN string + BindUser string + BindPass string + GroupBaseDN string + GroupObjectClass string + GroupMemberAttribute string + Insecure bool + Roles []auth.Role + Security string + UserAttribute string + UserBaseDN string + UserObjectClass string +}