From 6cb05a58e659f5e6a8adcd7c5875e5a1225aacb3 Mon Sep 17 00:00:00 2001 From: Zachary Wasserman Date: Mon, 12 Sep 2016 10:26:56 -0700 Subject: [PATCH] Pattern for defining and loading application configuration (#149) This provides a new pattern for defining and loading configuration for the Kolide server. With this PR, configuration is stored in the `config.KolideConfig` struct, and loaded through `config.Manager`. Refer to the patterns in `config/config.go` and `cli/prepare.go` to see how configuration is defined and used. A follow-up PR will work on removing further references to `viper` throughout the codebase, instead preferring to access configuration through the strongly typed `KolideConfig`. --- cli/config_dump.go | 38 +++++ cli/prepare.go | 140 +++++++++-------- cli/root.go | 66 +++----- cli/serve.go | 132 ++++++++-------- config/config.go | 380 ++++++++++++++++++++++++++++++++++++--------- glide.yaml | 1 + 6 files changed, 502 insertions(+), 255 deletions(-) create mode 100644 cli/config_dump.go diff --git a/cli/config_dump.go b/cli/config_dump.go new file mode 100644 index 0000000000..8c52c8d217 --- /dev/null +++ b/cli/config_dump.go @@ -0,0 +1,38 @@ +package cli + +import ( + "fmt" + + "github.com/Sirupsen/logrus" + "github.com/kolide/kolide-ose/config" + + "github.com/spf13/cobra" + "gopkg.in/yaml.v2" +) + +func createConfigDumpCmd(configManager config.Manager) *cobra.Command { + var configDumpCmd = &cobra.Command{ + Use: "config_dump", + Short: "Dump the parsed configuration in yaml format", + Long: ` +Dump the parsed configuration in yaml format. + +Kolide retrieves configuration options from many locations, and it can be +useful to see the result of merging those configs. + +The following precedence is used when reading configs: +1. CLI flags +2. Environment Variables +3. Config File +4. Default Values +`, + Run: func(cmd *cobra.Command, args []string) { + buf, err := yaml.Marshal(configManager.LoadConfig()) + if err != nil { + logrus.Fatal("Error marshalling config to yaml") + } + fmt.Println(string(buf)) + }} + + return configDumpCmd +} diff --git a/cli/prepare.go b/cli/prepare.go index 74e3920780..faea4ae8d1 100644 --- a/cli/prepare.go +++ b/cli/prepare.go @@ -4,81 +4,87 @@ import ( "fmt" "github.com/Sirupsen/logrus" + "github.com/kolide/kolide-ose/config" "github.com/kolide/kolide-ose/datastore" "github.com/kolide/kolide-ose/kolide" "github.com/spf13/cobra" - "github.com/spf13/viper" ) -func init() { - prepareCmd.AddCommand(dbCmd) - rootCmd.AddCommand(prepareCmd) - rootCmd.AddCommand(testDataCmd) -} +func createPrepareCmd(configManager config.Manager) *cobra.Command { -var prepareCmd = &cobra.Command{ - Use: "prepare", - Short: "Subcommands for initializing kolide infrastructure", - Long: ` + var prepareCmd = &cobra.Command{ + Use: "prepare", + Short: "Subcommands for initializing kolide infrastructure", + Long: ` Subcommands for initializing kolide infrastructure To setup kolide infrastructure, use one of the available commands. `, - Run: func(cmd *cobra.Command, args []string) { - cmd.Help() - }, -} - -var dbCmd = &cobra.Command{ - Use: "db", - Short: "Given correct database configurations, prepare the databases for use", - Long: ``, - Run: func(cmd *cobra.Command, args []string) { - connString := fmt.Sprintf( - "%s:%s@(%s)/%s?charset=utf8&parseTime=True&loc=Local", - viper.GetString("mysql.username"), - viper.GetString("mysql.password"), - viper.GetString("mysql.address"), - viper.GetString("mysql.database"), - ) - ds, err := datastore.New("gorm-mysql", connString) - if err != nil { - logrus.WithError(err).Fatal("error creating db connection") - } - if err := ds.Drop(); err != nil { - logrus.WithError(err).Fatal("error dropping db tables") - } - - if err := ds.Migrate(); err != nil { - logrus.WithError(err).Fatal("error setting up db schema") - } - }, -} - -var testDataCmd = &cobra.Command{ - Use: "test-data", - Short: "Generate test data", - Long: ``, - Run: func(cmd *cobra.Command, arg []string) { - connString := fmt.Sprintf( - "%s:%s@(%s)/%s?charset=utf8&parseTime=True&loc=Local", - viper.GetString("mysql.username"), - viper.GetString("mysql.password"), - viper.GetString("mysql.address"), - viper.GetString("mysql.database"), - ) - ds, err := datastore.New("gorm-mysql", connString) - if err != nil { - logrus.WithError(err).Fatal("error creating db connection") - } - - admin, err := kolide.NewUser("admin", "admin", "admin@kolide.co", true, false) - if err != nil { - logrus.WithError(err).Fatal("Could not create new user object") - } - _, err = ds.NewUser(admin) - if err != nil { - logrus.WithError(err).Fatal("Could not create new user in the database") - } - }, + Run: func(cmd *cobra.Command, args []string) { + cmd.Help() + }, + } + + var dbCmd = &cobra.Command{ + Use: "db", + Short: "Given correct database configurations, prepare the databases for use", + Long: ``, + Run: func(cmd *cobra.Command, args []string) { + config := configManager.LoadConfig() + connString := fmt.Sprintf( + "%s:%s@(%s)/%s?charset=utf8&parseTime=True&loc=Local", + config.Mysql.Username, + config.Mysql.Password, + config.Mysql.Address, + config.Mysql.Database, + ) + ds, err := datastore.New("gorm-mysql", connString) + if err != nil { + logrus.WithError(err).Fatal("error creating db connection") + } + if err := ds.Drop(); err != nil { + logrus.WithError(err).Fatal("error dropping db tables") + } + + if err := ds.Migrate(); err != nil { + logrus.WithError(err).Fatal("error setting up db schema") + } + }, + } + + prepareCmd.AddCommand(dbCmd) + + var testDataCmd = &cobra.Command{ + Use: "test-data", + Short: "Generate test data", + Long: ``, + Run: func(cmd *cobra.Command, arg []string) { + config := configManager.LoadConfig() + connString := fmt.Sprintf( + "%s:%s@(%s)/%s?charset=utf8&parseTime=True&loc=Local", + config.Mysql.Username, + config.Mysql.Password, + config.Mysql.Address, + config.Mysql.Database, + ) + ds, err := datastore.New("gorm-mysql", connString) + if err != nil { + logrus.WithError(err).Fatal("error creating db connection") + } + + admin, err := kolide.NewUser("admin", "admin", "admin@kolide.co", true, false) + if err != nil { + logrus.WithError(err).Fatal("Could not create new user object") + } + _, err = ds.NewUser(admin) + if err != nil { + logrus.WithError(err).Fatal("Could not create new user in the database") + } + }, + } + + prepareCmd.AddCommand(testDataCmd) + + return prepareCmd + } diff --git a/cli/root.go b/cli/root.go index 8e5bf81b32..8004b7ffe0 100644 --- a/cli/root.go +++ b/cli/root.go @@ -8,22 +8,28 @@ import ( "github.com/spf13/cobra" ) -func init() { - rootCmd.PersistentFlags().StringVar(&config.File, "config", "", "Path to a configuration file") -} - +// Launch is the entrypoint that sets up and runs the Kolide commands. func Launch() { + rootCmd := createRootCmd() + + configManager := config.NewManager(rootCmd) + + rootCmd.AddCommand(createPrepareCmd(configManager)) + rootCmd.AddCommand(createServeCmd(configManager)) + rootCmd.AddCommand(createConfigDumpCmd(configManager)) + if err := rootCmd.Execute(); err != nil { fmt.Println(err) os.Exit(-1) } } -// RootCmd represents the base command when called without any subcommands -var rootCmd = &cobra.Command{ - Use: "kolide", - Short: "osquery management and orchestration", - Long: ` +func createRootCmd() *cobra.Command { + // RootCmd represents the base command when called without any subcommands + var rootCmd = &cobra.Command{ + Use: "kolide", + Short: "osquery management and orchestration", + Long: ` osquery management and orchestration Configurable Options: @@ -31,42 +37,10 @@ Configurable Options: Options may be supplied in a yaml configuration file or via environment variables. You only need to define the configuration values for which you wish to override the default value. - -Available Configurations: - - mysql: - address (string) (KOLIDE_MYSQL_ADDRESS) - username (string) (KOLIDE_MYSQL_USERNAME) - password (string) (KOLIDE_MYSQL_PASSWORD) - database (string) (KOLIDE_MYSQL_DATABASE) - server: - address (string) (KOLIDE_SERVER_ADDRESS) - cert (string) (KOLIDE_SERVER_CERT) - key (string) (KOLIDE_SERVER_KEY) - auth: - jwt_key (string) (KOLIDE_AUTH_JWT_KEY) - salt_key_size (int) (KOLIDE_AUTH_SALT_KEY_SIZE) - bcrypt_cost (int) (KOLIDE_AUTH_BCRYPT_COST) - app: - web_address (string) (KOLIDE_APP_WEB_ADDRESS) - smtp: - server (string) (KOLIDE_SMTP_SERVER) - username (string) (KOLIDE_SMTP_USERNAME) - password (string) (KOLIDE_SMTP_PASSWORD) - pool_connections (int) (KOLIDE_SMTP_POOL_CONNECTIONS) - token_key_size (int) (KOLIDE_SMTP_TOKEN_KEY_SIZE) - session: - key_size (int) (KOLIDE_SESSION_KEY_SIZE) - expiration_seconds (float64) (KOLIDE_SESSION_EXPIRATION_SECONDS) - cookie_name (string) (KOLIDE_SESSION_COOKIE_NAME) - osquery: - enroll_secret (string) (KOLIDE_OSQUERY_ENROLL_SECRET) - node_key_size (int) (KOLIDE_OSQUERY_NODE_KEY_SIZE) - status_log_file (string) (KOLIDE_OSQUERY_STATUS_LOG_FILE) - result_log_file (string) (KOLIDE_OSQUERY_RESULT_LOG_FILE) - label_up_interval (int) (KOLIDE_OSQUERY_LABEL_UP_INTERVAL) - logging: - debug (bool) (KOLIDE_LOGGING_DEBUG) - disable_banner (bool) (KOLIDE_LOGGING_DISABLE_BANNER) `, + } + + rootCmd.PersistentFlags().String("config", "", "Path to a configuration file") + + return rootCmd } diff --git a/cli/serve.go b/cli/serve.go index 9809f1d5d9..3a1e7cc241 100644 --- a/cli/serve.go +++ b/cli/serve.go @@ -9,6 +9,7 @@ import ( "syscall" kitlog "github.com/go-kit/kit/log" + "github.com/kolide/kolide-ose/config" "github.com/kolide/kolide-ose/datastore" "github.com/kolide/kolide-ose/kolide" "github.com/kolide/kolide-ose/server" @@ -16,14 +17,11 @@ import ( "golang.org/x/net/context" ) -func init() { - rootCmd.AddCommand(serveCmd) -} - -var serveCmd = &cobra.Command{ - Use: "serve", - Short: "Launch the kolide server", - Long: ` +func createServeCmd(configManager config.Manager) *cobra.Command { + return &cobra.Command{ + Use: "serve", + Short: "Launch the kolide server", + Long: ` Launch the kolide server Use kolide serve to run the main HTTPS server. The Kolide server bundles @@ -31,72 +29,74 @@ together all static assets and dependent libraries into a statically linked go binary (which you're executing right now). Use the options below to customize the way that the kolide server works. `, - Run: func(cmd *cobra.Command, args []string) { - var ( - httpAddr = flag.String("http.addr", ":8080", "HTTP listen address") - ctx = context.Background() - logger kitlog.Logger - ) - flag.Parse() - logger = kitlog.NewLogfmtLogger(os.Stderr) - logger = kitlog.NewContext(logger).With("ts", kitlog.DefaultTimestampUTC) - - ds, _ := datastore.New("inmem", "") - svcConfig := server.ServiceConfig{ - Datastore: ds, - SessionCookieName: "KolideSession", - BcryptCost: 12, - SaltKeySize: 24, - JWTKey: "foobar", - } - svcLogger := kitlog.NewContext(logger).With("component", "service") - var svc kolide.Service - { // temp create an admin user - svc, _ = server.NewService(svcConfig) + Run: func(cmd *cobra.Command, args []string) { var ( - name = "admin" - username = "admin" - password = "secret" - email = "admin@kolide.co" - enabled = true - isAdmin = true + httpAddr = flag.String("http.addr", ":8080", "HTTP listen address") + ctx = context.Background() + logger kitlog.Logger ) - admin := kolide.UserPayload{ - Name: &name, - Username: &username, - Password: &password, - Email: &email, - Enabled: &enabled, - Admin: &isAdmin, + flag.Parse() + logger = kitlog.NewLogfmtLogger(os.Stderr) + logger = kitlog.NewContext(logger).With("ts", kitlog.DefaultTimestampUTC) + + ds, _ := datastore.New("inmem", "") + svcConfig := server.ServiceConfig{ + Datastore: ds, + SessionCookieName: "KolideSession", + BcryptCost: 12, + SaltKeySize: 24, + JWTKey: "foobar", } - _, err := svc.NewUser(ctx, admin) - if err != nil { - logger.Log("err", err) - os.Exit(1) + svcLogger := kitlog.NewContext(logger).With("component", "service") + var svc kolide.Service + { // temp create an admin user + svc, _ = server.NewService(svcConfig) + svc, _ = server.NewService(svcConfig) + var ( + name = "admin" + username = "admin" + password = "secret" + email = "admin@kolide.co" + enabled = true + isAdmin = true + ) + admin := kolide.UserPayload{ + Name: &name, + Username: &username, + Password: &password, + Email: &email, + Enabled: &enabled, + Admin: &isAdmin, + } + _, err := svc.NewUser(ctx, admin) + if err != nil { + logger.Log("err", err) + os.Exit(1) + } + svc = server.NewLoggingService(svc, svcLogger) } - svc = server.NewLoggingService(svc, svcLogger) - } - httpLogger := kitlog.NewContext(logger).With("component", "http") + httpLogger := kitlog.NewContext(logger).With("component", "http") - apiHandler := server.MakeHandler(ctx, svc, svcConfig.JWTKey, ds, httpLogger) - http.Handle("/api/", accessControl(apiHandler)) - http.Handle("/assets/", server.ServeStaticAssets("/assets/")) - http.Handle("/", server.ServeFrontend()) + apiHandler := server.MakeHandler(ctx, svc, svcConfig.JWTKey, ds, httpLogger) + http.Handle("/api/", accessControl(apiHandler)) + http.Handle("/assets/", server.ServeStaticAssets("/assets/")) + http.Handle("/", server.ServeFrontend()) - errs := make(chan error, 2) - go func() { - logger.Log("transport", "http", "address", *httpAddr, "msg", "listening") - errs <- http.ListenAndServe(*httpAddr, nil) - }() - go func() { - c := make(chan os.Signal) - signal.Notify(c, syscall.SIGINT) - errs <- fmt.Errorf("%s", <-c) - }() + errs := make(chan error, 2) + go func() { + logger.Log("transport", "http", "address", *httpAddr, "msg", "listening") + errs <- http.ListenAndServe(*httpAddr, nil) + }() + go func() { + c := make(chan os.Signal) + signal.Notify(c, syscall.SIGINT) + errs <- fmt.Errorf("%s", <-c) + }() - logger.Log("terminated", <-errs) - }, + logger.Log("terminated", <-errs) + }, + } } // cors headers diff --git a/config/config.go b/config/config.go index 644b272722..4437896cfc 100644 --- a/config/config.go +++ b/config/config.go @@ -1,100 +1,328 @@ package config import ( + "fmt" "strings" - "time" - "github.com/Sirupsen/logrus" + "github.com/spf13/cast" "github.com/spf13/cobra" "github.com/spf13/viper" ) -var ( - // File may or may not contain the path to the config file - File string +const ( + envPrefix = "KOLIDE" ) -func init() { - cobra.OnInitialize(initConfig) +// MysqlConfig defines configs related to MySQL +type MysqlConfig struct { + Address string + Username string + Password string + Database string } -// Due to a deficiency in viper (https://github.com/spf13/viper/issues/71), one -// can not set the default values of nested config elements. For example, if the -// "mysql" section of the config allows a user to define "username", "password", -// and "database", but the only wants to override the default for "username". -// they should be able to create a config which looks like: -// -// mysql: -// username: foobar -// -// In viper, that would nullify the default values of all other config keys in -// the mysql section ("mysql.*"). To get around this, instead of using the -// provided API for setting default values, after we've read the config and env, -// we manually check to see if the value has been set and, if it hasn't, we set -// it manually. -func setDefaultConfigValue(key string, value interface{}) { - if viper.Get(key) == nil { - viper.Set(key, value) +// ServerConfig defines configs related to the Kolide server +type ServerConfig struct { + Address string + Cert string + Key string +} + +// AuthConfig defines configs related to user authorization +type AuthConfig struct { + JwtKey string + BcryptCost int + SaltKeySize int +} + +// AppConfig defines configs related to HTTP +type AppConfig struct { + WebAddress string +} + +// SMTPConfig defines configs related to SMTP email +type SMTPConfig struct { + Server string + Username string + Password string + PoolConnections int + TokenKeySize int +} + +// SessionConfig defines configs related to user sessions +type SessionConfig struct { + KeySize int + ExpirationSeconds int + CookieName string +} + +// OsqueryConfig defines configs related to osquery +type OsqueryConfig struct { + EnrollSecret string + NodeKeySize int + StatusLogFile string + ResultLogFile string +} + +// LoggingConfig defines configs related to logging +type LoggingConfig struct { + Debug bool + DisableBanner bool +} + +// KolideConfig stores the application configuration. Each subcategory is +// broken up into it's own struct, defined above. When editing any of these +// structs, Manager.addConfigs and Manager.LoadConfig should be +// updated to set and retrieve the configurations as appropriate. +type KolideConfig struct { + Mysql MysqlConfig + Server ServerConfig + Auth AuthConfig + App AppConfig + SMTP SMTPConfig + Session SessionConfig + Osquery OsqueryConfig + Logging LoggingConfig +} + +// addConfigs adds the configuration keys and default values that will be +// filled into the KolideConfig struct +func (man Manager) addConfigs() { + // MySQL + man.addConfigString("mysql.address", "localhost:3306") + man.addConfigString("mysql.username", "kolide") + man.addConfigString("mysql.password", "kolide") + man.addConfigString("mysql.database", "kolide") + + // Server + man.addConfigString("server.address", "0.0.0.0:8080") + man.addConfigString("server.cert", "./tools/osquery/kolide.crt") + man.addConfigString("server.key", "./tools/osquery/kolide.key") + + // Auth + man.addConfigString("auth.jwt_key", "CHANGEME") + man.addConfigInt("auth.bcrypt_cost", 12) + man.addConfigInt("auth.salt_key_size", 24) + + // App + man.addConfigString("app.web_address", "0.0.0.0:8080") + + // SMTP + man.addConfigString("smtp.server", "") + man.addConfigString("smtp.username", "") + man.addConfigString("smtp.password", "") + man.addConfigInt("smtp.pool_connections", 4) + man.addConfigInt("smtp.token_key_size", 24) + + // Session + man.addConfigInt("session.key_size", 64) + man.addConfigInt("session.expiration_seconds", 60*60*24*90) + man.addConfigString("session.cookie_name", "KolideSession") + + // Osquery + man.addConfigString("osquery.enroll_secret", "") + man.addConfigInt("osquery.node_key_size", 24) + man.addConfigString("osquery.status_log_file", "/tmp/osquery_status") + man.addConfigString("osquery.result_log_file", "/tmp/osquery_result") + + // Logging + man.addConfigBool("logging.debug", false) + man.addConfigBool("logging.disable_banner", false) +} + +// LoadConfig will load the config variables into a fully initialized +// KolideConfig struct +func (man Manager) LoadConfig() KolideConfig { + man.loadConfigFile() + + return KolideConfig{ + Mysql: MysqlConfig{ + Address: man.getConfigString("mysql.address"), + Username: man.getConfigString("mysql.username"), + Password: man.getConfigString("mysql.password"), + Database: man.getConfigString("mysql.database"), + }, + Server: ServerConfig{ + Address: man.getConfigString("server.address"), + Cert: man.getConfigString("server.cert"), + Key: man.getConfigString("server.key"), + }, + Auth: AuthConfig{ + JwtKey: man.getConfigString("auth.jwt_key"), + BcryptCost: man.getConfigInt("auth.bcrypt_cost"), + SaltKeySize: man.getConfigInt("auth.salt_key_size"), + }, + App: AppConfig{ + WebAddress: man.getConfigString("app.web_address"), + }, + SMTP: SMTPConfig{ + Server: man.getConfigString("smtp.server"), + Username: man.getConfigString("smtp.username"), + Password: man.getConfigString("smtp.password"), + PoolConnections: man.getConfigInt("smtp.pool_connections"), + TokenKeySize: man.getConfigInt("smtp.token_key_size"), + }, + Session: SessionConfig{ + KeySize: man.getConfigInt("session.key_size"), + ExpirationSeconds: man.getConfigInt("session.expiration_seconds"), + CookieName: man.getConfigString("session.cookie_name"), + }, + Osquery: OsqueryConfig{ + EnrollSecret: man.getConfigString("osquery.enroll_secret"), + NodeKeySize: man.getConfigInt("osquery.node_key_size"), + StatusLogFile: man.getConfigString("osquery.status_log_file"), + ResultLogFile: man.getConfigString("osquery.result_log_file"), + }, + Logging: LoggingConfig{ + Debug: man.getConfigBool("logging.debug"), + DisableBanner: man.getConfigBool("logging.disable_banner"), + }, } } -func initConfig() { - if File != "" { - viper.SetConfigFile(File) +// envNameFromConfigKey converts a config key into the corresponding +// environment variable name +func envNameFromConfigKey(key string) string { + return envPrefix + "_" + strings.ToUpper(strings.Replace(key, ".", "_", -1)) +} + +// flagNameFromConfigKey converts a config key into the corresponding flag name +func flagNameFromConfigKey(key string) string { + return strings.Replace(key, ".", "_", -1) +} + +// Manager manages the addition and retrieval of config values for Kolide +// configs. It's only public API method is LoadConfig, which will return the +// populated KolideConfig struct. +type Manager struct { + viper *viper.Viper + command *cobra.Command + defaults map[string]interface{} +} + +// NewManager initializes a Manager wrapping the provided cobra +// command. All config flags will be attached to that command (and inherited by +// the subcommands). Typically this should be called just once, with the root +// command. +func NewManager(command *cobra.Command) Manager { + man := Manager{ + viper: viper.New(), + command: command, + defaults: map[string]interface{}{}, } - viper.SetConfigName("kolide") - viper.AddConfigPath(".") - viper.AddConfigPath("$HOME") - viper.AddConfigPath("./tools/app") - viper.AddConfigPath("/etc/kolide") + man.addConfigs() + return man +} - viper.SetConfigType("yaml") +// addDefault will check for duplication, then add a default value to the +// defaults map +func (man Manager) addDefault(key string, defVal interface{}) { + if _, exists := man.defaults[key]; exists { + panic("Trying to add duplicate config for key " + key) + } - viper.SetEnvPrefix("KOLIDE") - viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) - viper.AutomaticEnv() + man.defaults[key] = defVal +} - err := viper.ReadInConfig() +// getInterfaceVal is a helper function used by the getConfig* functions to +// retrieve the config value as interface{}, which will then be cast to the +// appropriate type by the getConfig* function. +func (man Manager) getInterfaceVal(key string) interface{} { + interfaceVal := man.viper.Get(key) + if interfaceVal == nil { + var ok bool + interfaceVal, ok = man.defaults[key] + if !ok { + panic("Tried to look up default value for nonexistent config option: " + key) + } + } + return interfaceVal +} + +// addConfigString adds a string config to the config options +func (man Manager) addConfigString(key string, defVal string) { + man.command.PersistentFlags().String(flagNameFromConfigKey(key), defVal, "Env: "+envNameFromConfigKey(key)) + man.viper.BindPFlag(key, man.command.PersistentFlags().Lookup(flagNameFromConfigKey(key))) + man.viper.BindEnv(key, envNameFromConfigKey(key)) + + // Add default + man.addDefault(key, defVal) +} + +// getConfigString retrieves a string from the loaded config +func (man Manager) getConfigString(key string) string { + interfaceVal := man.getInterfaceVal(key) + stringVal, err := cast.ToStringE(interfaceVal) if err != nil { - logrus.Infoln("Not reading config file. Relying on environment variables and default values.") + panic("Unable to cast to string for key " + key + ": " + err.Error()) + } + + return stringVal +} + +// addConfigInt adds a int config to the config options +func (man Manager) addConfigInt(key string, defVal int) { + man.command.PersistentFlags().Int(flagNameFromConfigKey(key), defVal, "Env: "+envNameFromConfigKey(key)) + man.viper.BindPFlag(key, man.command.PersistentFlags().Lookup(flagNameFromConfigKey(key))) + man.viper.BindEnv(key, envNameFromConfigKey(key)) + + // Add default + man.addDefault(key, defVal) +} + +// getConfigInt retrieves a int from the loaded config +func (man Manager) getConfigInt(key string) int { + interfaceVal := man.getInterfaceVal(key) + intVal, err := cast.ToIntE(interfaceVal) + if err != nil { + panic("Unable to cast to int for key " + key + ": " + err.Error()) + } + + return intVal +} + +// addConfigBool adds a bool config to the config options +func (man Manager) addConfigBool(key string, defVal bool) { + man.command.PersistentFlags().Bool(flagNameFromConfigKey(key), defVal, "Env: "+envNameFromConfigKey(key)) + man.viper.BindPFlag(key, man.command.PersistentFlags().Lookup(flagNameFromConfigKey(key))) + man.viper.BindEnv(key, envNameFromConfigKey(key)) + + // Add default + man.addDefault(key, defVal) +} + +// getConfigBool retrieves a bool from the loaded config +func (man Manager) getConfigBool(key string) bool { + interfaceVal := man.getInterfaceVal(key) + boolVal, err := cast.ToBoolE(interfaceVal) + if err != nil { + panic("Unable to cast to bool for key " + key + ": " + err.Error()) + } + + return boolVal +} + +// loadConfigFile handles the loading of the config file. +func (man Manager) loadConfigFile() { + configFile := man.command.PersistentFlags().Lookup("config").Value.String() + if configFile != "" { + man.viper.SetConfigFile(configFile) + } else { + man.viper.SetConfigName("kolide") + man.viper.AddConfigPath(".") + man.viper.AddConfigPath("$HOME") + man.viper.AddConfigPath("./tools/app") + man.viper.AddConfigPath("/etc/kolide") + } + + man.viper.SetConfigType("yaml") + + err := man.viper.ReadInConfig() + + fmt.Println("Using config file: ", man.viper.ConfigFileUsed()) + + if err != nil { + panic("Error reading config: " + err.Error()) } - setDefaultConfigValue("mysql.address", "localhost:3306") - setDefaultConfigValue("mysql.username", "kolide") - setDefaultConfigValue("mysql.password", "kolide") - setDefaultConfigValue("mysql.database", "kolide") - - setDefaultConfigValue("server.address", "0.0.0.0:8080") - - setDefaultConfigValue("app.web_address", "0.0.0.0:8080") - - setDefaultConfigValue("auth.jwt_key", "CHANGEME") - setDefaultConfigValue("auth.bcrypt_cost", 12) - setDefaultConfigValue("auth.salt_key_size", 24) - - setDefaultConfigValue("smtp.token_key_size", 24) - setDefaultConfigValue("smtp.address", "localhost:1025") - setDefaultConfigValue("smtp.pool_connections", 4) - - setDefaultConfigValue("session.key_size", 64) - setDefaultConfigValue("session.expiration_seconds", 60*60*24*90) - setDefaultConfigValue("session.cookie_name", "KolideSession") - - setDefaultConfigValue("osquery.node_key_size", 24) - setDefaultConfigValue("osquery.status_log_file", "/tmp/osquery_status") - setDefaultConfigValue("osquery.result_log_file", "/tmp/osquery_result") - setDefaultConfigValue("osquery.label_up_interval", 1*time.Minute) - - setDefaultConfigValue("logging.debug", false) - setDefaultConfigValue("logging.disable_banner", false) - - if viper.GetBool("logging.debug") { - logrus.SetLevel(logrus.DebugLevel) - } else { - logrus.SetLevel(logrus.WarnLevel) - } - - if viper.GetBool("logs.json") { - logrus.SetFormatter(&logrus.JSONFormatter{}) - } } diff --git a/glide.yaml b/glide.yaml index 0dee72a0b3..f09c89dd76 100644 --- a/glide.yaml +++ b/glide.yaml @@ -74,6 +74,7 @@ import: version: fd703108daeb23d77c124d12978e9b6c4f28f034 - package: github.com/spf13/cobra - package: github.com/spf13/viper +- package: github.com/spf13/cast - package: gopkg.in/natefinch/lumberjack.v2 version: v2.0 - package: github.com/golang/mock