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
```
This commit is contained in:
CptOfEvilMinions 2021-01-04 09:58:43 -06:00 committed by GitHub
parent 522bff0b82
commit 626429c38e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 68 additions and 8 deletions

View File

@ -1,3 +1,2 @@
* !build/binary-bundle/linux/fleet
!build/binary-bundle/linux/fleet !build/binary-bundle/linux/fleet
!build/binary-bundle/linux/fleetctl

View File

@ -163,14 +163,26 @@ the way that the Fleet server works.
os.Exit(1) 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) jwtKey, err := kolide.RandomText(24)
if err != nil { if err != nil {
initFatal(err, "generating sample jwt key") initFatal(err, "generating sample jwt key")
} }
fmt.Printf("################################################################################\n"+ fmt.Printf("################################################################################\n"+
"# ERROR:\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"+ "# session tokens for users.\n"+
"#\n"+ "#\n"+
"# Consider using the following randomly generated key:\n"+ "# Consider using the following randomly generated key:\n"+

View File

@ -174,7 +174,20 @@ The password to use when connecting to the MySQL instance.
password: kolide 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. 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 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. The bcrypt cost to use when hashing user passwords.

View File

@ -21,6 +21,7 @@ type MysqlConfig struct {
Address string Address string
Username string Username string
Password string Password string
PasswordPath string `yaml:"password_path"`
Database string Database string
TLSCert string `yaml:"tls_cert"` TLSCert string `yaml:"tls_cert"`
TLSKey string `yaml:"tls_key"` TLSKey string `yaml:"tls_key"`
@ -59,6 +60,7 @@ type ServerConfig struct {
// AuthConfig defines configs related to user authorization // AuthConfig defines configs related to user authorization
type AuthConfig struct { type AuthConfig struct {
JwtKey string `yaml:"jwt_key"` JwtKey string `yaml:"jwt_key"`
JwtKeyPath string `yaml:"jwt_key_path"`
BcryptCost int `yaml:"bcrypt_cost"` BcryptCost int `yaml:"bcrypt_cost"`
SaltKeySize int `yaml:"salt_key_size"` SaltKeySize int `yaml:"salt_key_size"`
} }
@ -168,8 +170,10 @@ func (man Manager) addConfigs() {
"MySQL server address (host:port)") "MySQL server address (host:port)")
man.addConfigString("mysql.username", "kolide", man.addConfigString("mysql.username", "kolide",
"MySQL server username") "MySQL server username")
man.addConfigString("mysql.password", "kolide", man.addConfigString("mysql.password", "",
"MySQL server password (prefer env variable for security)") "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", man.addConfigString("mysql.database", "kolide",
"MySQL database name") "MySQL database name")
man.addConfigString("mysql.tls_cert", "", man.addConfigString("mysql.tls_cert", "",
@ -213,6 +217,8 @@ func (man Manager) addConfigs() {
// Auth // Auth
man.addConfigString("auth.jwt_key", "", man.addConfigString("auth.jwt_key", "",
"JWT session token key (required)") "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, man.addConfigInt("auth.bcrypt_cost", 12,
"Bcrypt iterations") "Bcrypt iterations")
man.addConfigInt("auth.salt_key_size", 24, man.addConfigInt("auth.salt_key_size", 24,
@ -314,6 +320,7 @@ func (man Manager) LoadConfig() KolideConfig {
Address: man.getConfigString("mysql.address"), Address: man.getConfigString("mysql.address"),
Username: man.getConfigString("mysql.username"), Username: man.getConfigString("mysql.username"),
Password: man.getConfigString("mysql.password"), Password: man.getConfigString("mysql.password"),
PasswordPath: man.getConfigString("mysql.password_path"),
Database: man.getConfigString("mysql.database"), Database: man.getConfigString("mysql.database"),
TLSCert: man.getConfigString("mysql.tls_cert"), TLSCert: man.getConfigString("mysql.tls_cert"),
TLSKey: man.getConfigString("mysql.tls_key"), TLSKey: man.getConfigString("mysql.tls_key"),
@ -340,6 +347,7 @@ func (man Manager) LoadConfig() KolideConfig {
}, },
Auth: AuthConfig{ Auth: AuthConfig{
JwtKey: man.getConfigString("auth.jwt_key"), JwtKey: man.getConfigString("auth.jwt_key"),
JwtKeyPath: man.getConfigString("auth.jwt_key_path"),
BcryptCost: man.getConfigInt("auth.bcrypt_cost"), BcryptCost: man.getConfigInt("auth.bcrypt_cost"),
SaltKeySize: man.getConfigInt("auth.salt_key_size"), SaltKeySize: man.getConfigInt("auth.salt_key_size"),
}, },

View File

@ -8,6 +8,7 @@ import (
"io/ioutil" "io/ioutil"
"net/url" "net/url"
"regexp" "regexp"
"strings"
"time" "time"
"github.com/WatchBeam/clock" "github.com/WatchBeam/clock"
@ -109,6 +110,21 @@ func New(config config.MysqlConfig, c clock.Clock, opts ...DBOption) (*Datastore
setOpt(options) 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 != "" { if config.TLSConfig != "" {
err := registerTLS(config) err := registerTLS(config)
if err != nil { if err != nil {