From 626429c38ec40f2c6af123dd336e593cb5f8cc2a Mon Sep 17 00:00:00 2001 From: CptOfEvilMinions <35814920+CptOfEvilMinions@users.noreply.github.com> Date: Mon, 4 Jan 2021 09:58:43 -0600 Subject: [PATCH] Added support to read jwt and mysql password from a file (#141) The current implementation of FleetDM doesn't support Docker secrets for supplying the MySQL password and JWT key. This PR provides the ability for a file path to read in secrets. The goal of this PR is to avoid storing secrets in a static config or in an environment variable. Example config for Docker: ```yaml mysql: address: mysql:3306 database: fleet username: fleet password_path: /run/secrets/mysql-fleetdm-password redis: address: redis:6379 server: address: 0.0.0.0:8080 cert: /run/secrets/fleetdm-tls-cert key: /run/secrets/fleetdm-tls-key auth: jwt_key_path: /run/secrets/fleetdm-jwt-key filesystem: status_log_file: /var/log/osquery/status.log result_log_file: /var/log/osquery/result.log enable_log_rotation: true logging: json: true ``` --- .dockerignore | 5 ++--- cmd/fleet/serve.go | 16 +++++++++++++-- docs/2-Deployment/2-Configuration.md | 29 ++++++++++++++++++++++++++-- server/config/config.go | 10 +++++++++- server/datastore/mysql/mysql.go | 16 +++++++++++++++ 5 files changed, 68 insertions(+), 8 deletions(-) diff --git a/.dockerignore b/.dockerignore index 877673e5d..5493daf12 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,2 @@ -* -!build/binary-bundle/linux/fleet -!build/binary-bundle/linux/fleetctl +!build/binary-bundle/linux/fleet +!build/binary-bundle/linux/fleet \ No newline at end of file diff --git a/cmd/fleet/serve.go b/cmd/fleet/serve.go index d547e6af7..413e472fd 100644 --- a/cmd/fleet/serve.go +++ b/cmd/fleet/serve.go @@ -163,14 +163,26 @@ the way that the Fleet server works. os.Exit(1) } - if config.Auth.JwtKey == "" { + if config.Auth.JwtKey != "" && config.Auth.JwtKeyPath != "" { + initFatal(err, "A JWT key and a JWT key file were provided - please specify only one") + } + + if config.Auth.JwtKeyPath != "" { + fileContents, err := ioutil.ReadFile(config.Auth.JwtKeyPath) + if err != nil { + initFatal(err, "Could not read the JWT Key file provided") + } + config.Auth.JwtKey = strings.TrimSpace(string(fileContents)) + } + + if config.Auth.JwtKey == "" && config.Auth.JwtKeyPath == "" { jwtKey, err := kolide.RandomText(24) if err != nil { initFatal(err, "generating sample jwt key") } fmt.Printf("################################################################################\n"+ "# ERROR:\n"+ - "# A value must be supplied for --auth_jwt_key. This value is used to create\n"+ + "# A value must be supplied for --auth_jwt_key or --auth_jwt_key_path. This value is used to create\n"+ "# session tokens for users.\n"+ "#\n"+ "# Consider using the following randomly generated key:\n"+ diff --git a/docs/2-Deployment/2-Configuration.md b/docs/2-Deployment/2-Configuration.md index 3998125e5..d233b3593 100644 --- a/docs/2-Deployment/2-Configuration.md +++ b/docs/2-Deployment/2-Configuration.md @@ -174,7 +174,20 @@ The password to use when connecting to the MySQL instance. password: kolide ``` -###### `mysql_tls_ca` +##### `mysql_password_path` + +File path to a file that contains the password to use when connecting to the MySQL instance. + +- Default value: `""` +- Config file format: + + ``` + mysql: + password_path: '/run/secrets/fleetdm-mysql-password + ``` + + +##### `mysql_tls_ca` The path to a PEM encoded certificate of MYSQL's CA for client certificate authentication. @@ -418,7 +431,19 @@ The [JWT](https://jwt.io/) key to use when signing and validating session keys. jwt_key: JVnKw7CaUdJjZwYAqDgUHVYP ``` -###### `auth_bcrypt_cost` +##### `auth_jwt_key_path` + +File path to a file that contains the [JWT](https://jwt.io/) key to use when signing and validating session keys. + +- Default value: `""` +- Config file format: + + ``` + auth: + jwt_key_path: '/run/secrets/fleetdm-jwt-token + ``` + +##### `auth_bcrypt_cost` The bcrypt cost to use when hashing user passwords. diff --git a/server/config/config.go b/server/config/config.go index ba95433a3..0664550b4 100644 --- a/server/config/config.go +++ b/server/config/config.go @@ -21,6 +21,7 @@ type MysqlConfig struct { Address string Username string Password string + PasswordPath string `yaml:"password_path"` Database string TLSCert string `yaml:"tls_cert"` TLSKey string `yaml:"tls_key"` @@ -59,6 +60,7 @@ type ServerConfig struct { // AuthConfig defines configs related to user authorization type AuthConfig struct { JwtKey string `yaml:"jwt_key"` + JwtKeyPath string `yaml:"jwt_key_path"` BcryptCost int `yaml:"bcrypt_cost"` SaltKeySize int `yaml:"salt_key_size"` } @@ -168,8 +170,10 @@ func (man Manager) addConfigs() { "MySQL server address (host:port)") man.addConfigString("mysql.username", "kolide", "MySQL server username") - man.addConfigString("mysql.password", "kolide", + man.addConfigString("mysql.password", "", "MySQL server password (prefer env variable for security)") + man.addConfigString("mysql.password_path", "", + "Path to file containg MySQL server password") man.addConfigString("mysql.database", "kolide", "MySQL database name") man.addConfigString("mysql.tls_cert", "", @@ -213,6 +217,8 @@ func (man Manager) addConfigs() { // Auth man.addConfigString("auth.jwt_key", "", "JWT session token key (required)") + man.addConfigString("auth.jwt_key_path", "", + "Path to file containg JWT session token key") man.addConfigInt("auth.bcrypt_cost", 12, "Bcrypt iterations") man.addConfigInt("auth.salt_key_size", 24, @@ -314,6 +320,7 @@ func (man Manager) LoadConfig() KolideConfig { Address: man.getConfigString("mysql.address"), Username: man.getConfigString("mysql.username"), Password: man.getConfigString("mysql.password"), + PasswordPath: man.getConfigString("mysql.password_path"), Database: man.getConfigString("mysql.database"), TLSCert: man.getConfigString("mysql.tls_cert"), TLSKey: man.getConfigString("mysql.tls_key"), @@ -340,6 +347,7 @@ func (man Manager) LoadConfig() KolideConfig { }, Auth: AuthConfig{ JwtKey: man.getConfigString("auth.jwt_key"), + JwtKeyPath: man.getConfigString("auth.jwt_key_path"), BcryptCost: man.getConfigInt("auth.bcrypt_cost"), SaltKeySize: man.getConfigInt("auth.salt_key_size"), }, diff --git a/server/datastore/mysql/mysql.go b/server/datastore/mysql/mysql.go index 9b3b74c8f..f2bb57f03 100644 --- a/server/datastore/mysql/mysql.go +++ b/server/datastore/mysql/mysql.go @@ -8,6 +8,7 @@ import ( "io/ioutil" "net/url" "regexp" + "strings" "time" "github.com/WatchBeam/clock" @@ -109,6 +110,21 @@ func New(config config.MysqlConfig, c clock.Clock, opts ...DBOption) (*Datastore setOpt(options) } + if config.PasswordPath != "" && config.Password != "" { + return nil, errors.New("A MySQL password and a MySQL password file were provided - please specify only one") + } + + // Check to see if the flag is populated + // Check if file exists on disk + // If file exists read contents + if config.PasswordPath != "" { + fileContents, err := ioutil.ReadFile(config.PasswordPath) + if err != nil { + return nil, err + } + config.Password = strings.TrimSpace(string(fileContents)) + } + if config.TLSConfig != "" { err := registerTLS(config) if err != nil {