fleet/server/config/config.go
Zachary Wasserman c8229cc0d6
Replace uses of the term "Kolide" with "Fleet" (#1999)
Almost two years ago, we began referring to the project as Fleet, but there are
many occurences of the term "Kolide" throughout the UI and documentation. This
PR attempts to clear up those uses where it is easily achievable.

The term "Kolide" is used throughout the code as well, but modifying this would
be more likely to introduce bugs.
2019-01-24 09:39:32 -08:00

470 lines
15 KiB
Go

package config
import (
"fmt"
"strings"
"time"
"github.com/spf13/cast"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
const (
envPrefix = "KOLIDE"
)
// MysqlConfig defines configs related to MySQL
type MysqlConfig struct {
Address string
Username string
Password string
Database string
TLSCert string `yaml:"tls_cert"`
TLSKey string `yaml:"tls_key"`
TLSCA string `yaml:"tls_ca"`
TLSServerName string `yaml:"tls_server_name"`
TLSConfig string `yaml:"tls_config"` //tls=customValue in DSN
MaxOpenConns int `yaml:"max_open_conns"`
MaxIdleConns int `yaml:"max_idle_conns"`
}
// RedisConfig defines configs related to Redis
type RedisConfig struct {
Address string
Password string
}
const (
TLSProfileKey = "server.tls_compatibility"
TLSProfileModern = "modern"
TLSProfileIntermediate = "intermediate"
TLSProfileOld = "old"
)
// ServerConfig defines configs related to the Fleet server
type ServerConfig struct {
Address string
Cert string
Key string
TLS bool
TLSProfile string
}
// AuthConfig defines configs related to user authorization
type AuthConfig struct {
JwtKey string `yaml:"jwt_key"`
BcryptCost int `yaml:"bcrypt_cost"`
SaltKeySize int `yaml:"salt_key_size"`
}
// AppConfig defines configs related to HTTP
type AppConfig struct {
TokenKeySize int `yaml:"token_key_size"`
InviteTokenValidityPeriod time.Duration `yaml:"invite_token_validity_period"`
}
// SessionConfig defines configs related to user sessions
type SessionConfig struct {
KeySize int `yaml:"key_size"`
Duration time.Duration
}
// OsqueryConfig defines configs related to osquery
type OsqueryConfig struct {
NodeKeySize int `yaml:"node_key_size"`
StatusLogFile string `yaml:"status_log_file"`
ResultLogFile string `yaml:"result_log_file"`
EnableLogRotation bool `yaml:"enable_log_rotation"`
LabelUpdateInterval time.Duration `yaml:"label_update_interval"`
}
// LoggingConfig defines configs related to logging
type LoggingConfig struct {
Debug bool
JSON bool
DisableBanner bool `yaml:"disable_banner"`
}
// 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
Redis RedisConfig
Server ServerConfig
Auth AuthConfig
App AppConfig
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",
"MySQL server address (host:port)")
man.addConfigString("mysql.username", "kolide",
"MySQL server username")
man.addConfigString("mysql.password", "kolide",
"MySQL server password (prefer env variable for security)")
man.addConfigString("mysql.database", "kolide",
"MySQL database name")
man.addConfigString("mysql.tls_cert", "",
"MySQL TLS client certificate path")
man.addConfigString("mysql.tls_key", "",
"MySQL TLS client key path")
man.addConfigString("mysql.tls_ca", "",
"MySQL TLS server CA")
man.addConfigString("mysql.tls_server_name", "",
"MySQL TLS server name")
man.addConfigString("mysql.tls_config", "",
"MySQL TLS config value. Use skip-verify, true, false or custom key.")
man.addConfigInt("mysql.max_open_conns", 50, "MySQL maximum open connection handles.")
man.addConfigInt("mysql.max_idle_conns", 50, "MySQL maximum idle connection handles.")
// Redis
man.addConfigString("redis.address", "localhost:6379",
"Redis server address (host:port)")
man.addConfigString("redis.password", "",
"Redis server password (prefer env variable for security)")
// Server
man.addConfigString("server.address", "0.0.0.0:8080",
"Fleet server address (host:port)")
man.addConfigString("server.cert", "./tools/osquery/kolide.crt",
"Fleet TLS certificate path")
man.addConfigString("server.key", "./tools/osquery/kolide.key",
"Fleet TLS key path")
man.addConfigBool("server.tls", true,
"Enable TLS (required for osqueryd communication)")
man.addConfigString(TLSProfileKey, TLSProfileModern,
fmt.Sprintf("TLS security profile choose one of %s, %s or %s",
TLSProfileModern, TLSProfileIntermediate, TLSProfileOld))
// Auth
man.addConfigString("auth.jwt_key", "",
"JWT session token key (required)")
man.addConfigInt("auth.bcrypt_cost", 12,
"Bcrypt iterations")
man.addConfigInt("auth.salt_key_size", 24,
"Size of salt for passwords")
// App
man.addConfigString("app.token_key", "CHANGEME",
"Secret key for generating invite and reset tokens")
man.addConfigDuration("app.invite_token_validity_period", 5*24*time.Hour,
"Duration invite tokens remain valid (i.e. 1h)")
man.addConfigInt("app.token_key_size", 24,
"Size of generated tokens")
// Session
man.addConfigInt("session.key_size", 64,
"Size of generated session keys")
man.addConfigDuration("session.duration", 24*90*time.Hour,
"Duration session keys remain valid (i.e. 24h)")
// Osquery
man.addConfigInt("osquery.node_key_size", 24,
"Size of generated osqueryd node keys")
man.addConfigString("osquery.status_log_file", "/tmp/osquery_status",
"Path for osqueryd status logs")
man.addConfigString("osquery.result_log_file", "/tmp/osquery_result",
"Path for osqueryd result logs")
man.addConfigDuration("osquery.label_update_interval", 1*time.Hour,
"Interval to update host label membership (i.e. 1h)")
man.addConfigBool("osquery.enable_log_rotation", false,
"Osquery log files will be automatically rotated")
// Logging
man.addConfigBool("logging.debug", false,
"Enable debug logging")
man.addConfigBool("logging.json", false,
"Log in JSON format")
man.addConfigBool("logging.disable_banner", false,
"Disable startup banner")
}
// 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"),
TLSCert: man.getConfigString("mysql.tls_cert"),
TLSKey: man.getConfigString("mysql.tls_key"),
TLSCA: man.getConfigString("mysql.tls_ca"),
TLSServerName: man.getConfigString("mysql.tls_server_name"),
TLSConfig: man.getConfigString("mysql.tls_config"),
MaxOpenConns: man.getConfigInt("mysql.max_open_conns"),
MaxIdleConns: man.getConfigInt("mysql.max_idle_conns"),
},
Redis: RedisConfig{
Address: man.getConfigString("redis.address"),
Password: man.getConfigString("redis.password"),
},
Server: ServerConfig{
Address: man.getConfigString("server.address"),
Cert: man.getConfigString("server.cert"),
Key: man.getConfigString("server.key"),
TLS: man.getConfigBool("server.tls"),
TLSProfile: man.getConfigTLSProfile(),
},
Auth: AuthConfig{
JwtKey: man.getConfigString("auth.jwt_key"),
BcryptCost: man.getConfigInt("auth.bcrypt_cost"),
SaltKeySize: man.getConfigInt("auth.salt_key_size"),
},
App: AppConfig{
TokenKeySize: man.getConfigInt("app.token_key_size"),
InviteTokenValidityPeriod: man.getConfigDuration("app.invite_token_validity_period"),
},
Session: SessionConfig{
KeySize: man.getConfigInt("session.key_size"),
Duration: man.getConfigDuration("session.duration"),
},
Osquery: OsqueryConfig{
NodeKeySize: man.getConfigInt("osquery.node_key_size"),
StatusLogFile: man.getConfigString("osquery.status_log_file"),
ResultLogFile: man.getConfigString("osquery.result_log_file"),
LabelUpdateInterval: man.getConfigDuration("osquery.label_update_interval"),
EnableLogRotation: man.getConfigBool("osquery.enable_log_rotation"),
},
Logging: LoggingConfig{
Debug: man.getConfigBool("logging.debug"),
JSON: man.getConfigBool("logging.json"),
DisableBanner: man.getConfigBool("logging.disable_banner"),
},
}
}
// IsSet determines whether a given config key has been explicitly set by any
// of the configuration sources. If false, the default value is being used.
func (man Manager) IsSet(key string) bool {
return man.viper.IsSet(key)
}
// 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 Fleet
// 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{}{},
}
man.addConfigs()
return man
}
// 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)
}
man.defaults[key] = defVal
}
func getFlagUsage(key string, usage string) string {
return fmt.Sprintf("Env: %s\n\t\t%s", envNameFromConfigKey(key), usage)
}
// 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, defVal, usage string) {
man.command.PersistentFlags().String(flagNameFromConfigKey(key), defVal, getFlagUsage(key, usage))
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 {
panic("Unable to cast to string for key " + key + ": " + err.Error())
}
return stringVal
}
// Custom handling for TLSProfile which can only accept specific values
// for the argument
func (man Manager) getConfigTLSProfile() string {
ival := man.getInterfaceVal(TLSProfileKey)
sval, err := cast.ToStringE(ival)
if err != nil {
panic(fmt.Sprintf("%s requires a string value: %s", TLSProfileKey, err.Error()))
}
switch sval {
case TLSProfileModern, TLSProfileIntermediate, TLSProfileOld:
default:
panic(fmt.Sprintf("%s must be one of %s, %s or %s", TLSProfileKey,
TLSProfileModern, TLSProfileIntermediate, TLSProfileOld))
}
return sval
}
// addConfigInt adds a int config to the config options
func (man Manager) addConfigInt(key string, defVal int, usage string) {
man.command.PersistentFlags().Int(flagNameFromConfigKey(key), defVal, getFlagUsage(key, usage))
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, usage string) {
man.command.PersistentFlags().Bool(flagNameFromConfigKey(key), defVal, getFlagUsage(key, usage))
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
}
// addConfigDuration adds a duration config to the config options
func (man Manager) addConfigDuration(key string, defVal time.Duration, usage string) {
man.command.PersistentFlags().Duration(flagNameFromConfigKey(key), defVal, getFlagUsage(key, usage))
man.viper.BindPFlag(key, man.command.PersistentFlags().Lookup(flagNameFromConfigKey(key)))
man.viper.BindEnv(key, envNameFromConfigKey(key))
// Add default
man.addDefault(key, defVal)
}
// getConfigDuration retrieves a duration from the loaded config
func (man Manager) getConfigDuration(key string) time.Duration {
interfaceVal := man.getInterfaceVal(key)
durationVal, err := cast.ToDurationE(interfaceVal)
if err != nil {
panic("Unable to cast to duration for key " + key + ": " + err.Error())
}
return durationVal
}
// loadConfigFile handles the loading of the config file.
func (man Manager) loadConfigFile() {
man.viper.SetConfigType("yaml")
configFile := man.command.PersistentFlags().Lookup("config").Value.String()
if configFile == "" {
// No config file set, only use configs from env
// vars/flags/defaults
return
}
man.viper.SetConfigFile(configFile)
err := man.viper.ReadInConfig()
fmt.Println("Using config file: ", man.viper.ConfigFileUsed())
if err != nil {
panic("Error reading config: " + err.Error())
}
}
// TestConfig returns a barebones configuration suitable for use in tests.
// Individual tests may want to override some of the values provided.
func TestConfig() KolideConfig {
return KolideConfig{
App: AppConfig{
TokenKeySize: 24,
InviteTokenValidityPeriod: 5 * 24 * time.Hour,
},
Auth: AuthConfig{
JwtKey: "CHANGEME",
BcryptCost: 6, // Low cost keeps tests fast
SaltKeySize: 24,
},
Session: SessionConfig{
KeySize: 64,
Duration: 24 * 90 * time.Hour,
},
Osquery: OsqueryConfig{
NodeKeySize: 24,
StatusLogFile: "/dev/null",
ResultLogFile: "/dev/null",
LabelUpdateInterval: 1 * time.Hour,
},
Logging: LoggingConfig{
Debug: true,
DisableBanner: true,
},
}
}