2018-05-01 22:58:53 +00:00
package main
import (
2021-11-22 14:13:26 +00:00
"errors"
2018-05-04 16:53:21 +00:00
"fmt"
"io/ioutil"
2018-05-01 22:58:53 +00:00
"os"
2018-05-04 16:53:21 +00:00
"path/filepath"
"strconv"
2022-06-07 20:00:09 +00:00
"strings"
2018-05-01 22:58:53 +00:00
2021-08-24 12:50:03 +00:00
"github.com/fleetdm/fleet/v4/pkg/secure"
2018-05-04 16:53:21 +00:00
"github.com/ghodss/yaml"
2021-03-13 00:42:38 +00:00
"github.com/urfave/cli/v2"
2018-05-01 22:58:53 +00:00
)
2018-05-09 00:04:38 +00:00
const (
configFilePerms = 0600
)
2018-05-04 16:53:21 +00:00
type configFile struct {
Contexts map [ string ] Context ` json:"contexts" `
}
type Context struct {
2022-06-07 20:00:09 +00:00
Address string ` json:"address" `
Email string ` json:"email" `
Token string ` json:"token" `
TLSSkipVerify bool ` json:"tls-skip-verify" `
RootCA string ` json:"rootca" `
URLPrefix string ` json:"url-prefix" `
CustomHeaders map [ string ] string ` json:"custom-headers" `
2018-05-04 16:53:21 +00:00
}
func configFlag ( ) cli . Flag {
2020-11-18 00:02:14 +00:00
homeDir , err := os . UserHomeDir ( )
if err != nil {
homeDir = "~"
}
defaultConfigPath := filepath . Join ( homeDir , ".fleet" , "config" )
2021-03-13 00:42:38 +00:00
return & cli . StringFlag {
Name : "config" ,
Value : defaultConfigPath ,
EnvVars : [ ] string { "CONFIG" } ,
Usage : "Path to the fleetctl config file" ,
2018-05-04 16:53:21 +00:00
}
}
func contextFlag ( ) cli . Flag {
2021-03-13 00:42:38 +00:00
return & cli . StringFlag {
Name : "context" ,
Value : "default" ,
EnvVars : [ ] string { "CONTEXT" } ,
Usage : "Name of fleetctl config context to use" ,
2018-05-04 16:53:21 +00:00
}
}
func makeConfigIfNotExists ( fp string ) error {
2021-08-03 20:02:15 +00:00
if _ , err := os . Stat ( filepath . Dir ( fp ) ) ; errors . Is ( err , os . ErrNotExist ) {
2021-08-11 14:02:22 +00:00
if err := secure . MkdirAll ( filepath . Dir ( fp ) , 0700 ) ; err != nil {
2018-05-04 16:53:21 +00:00
return err
}
}
2021-08-17 12:41:56 +00:00
f , err := secure . OpenFile ( fp , os . O_RDONLY | os . O_CREATE , configFilePerms )
if err == nil {
f . Close ( )
}
2018-05-04 16:53:21 +00:00
return err
}
2021-02-13 16:41:46 +00:00
func readConfig ( fp string ) ( configFile , error ) {
var c configFile
2018-05-04 16:53:21 +00:00
b , err := ioutil . ReadFile ( fp )
if err != nil {
2021-02-13 16:41:46 +00:00
return c , err
2018-05-04 16:53:21 +00:00
}
2021-02-13 16:41:46 +00:00
if err := yaml . Unmarshal ( b , & c ) ; err != nil {
2021-11-22 14:13:26 +00:00
return c , fmt . Errorf ( "unmarshal config: %w" , err )
2021-02-13 16:41:46 +00:00
}
2018-05-04 16:53:21 +00:00
if c . Contexts == nil {
c . Contexts = map [ string ] Context {
2021-11-22 14:13:26 +00:00
"default" : { } ,
2018-05-04 16:53:21 +00:00
}
}
2021-02-13 16:41:46 +00:00
return c , nil
2018-05-04 16:53:21 +00:00
}
func writeConfig ( fp string , c configFile ) error {
b , err := yaml . Marshal ( c )
if err != nil {
return err
}
2018-05-09 00:04:38 +00:00
return ioutil . WriteFile ( fp , b , configFilePerms )
2018-05-04 16:53:21 +00:00
}
2021-01-29 01:15:38 +00:00
func getConfigValue ( configPath , context , key string ) ( interface { } , error ) {
if err := makeConfigIfNotExists ( configPath ) ; err != nil {
2021-11-22 14:13:26 +00:00
return nil , fmt . Errorf ( "error verifying that config exists at %s: %w" , configPath , err )
2018-05-04 16:53:21 +00:00
}
2021-01-29 01:15:38 +00:00
config , err := readConfig ( configPath )
2018-05-04 16:53:21 +00:00
if err != nil {
2021-11-22 14:13:26 +00:00
return nil , fmt . Errorf ( "error reading config at %s: %w" , configPath , err )
2018-05-04 16:53:21 +00:00
}
2021-01-29 01:15:38 +00:00
currentContext , ok := config . Contexts [ context ]
2018-05-04 16:53:21 +00:00
if ! ok {
2021-01-29 01:15:38 +00:00
fmt . Printf ( "[+] Context %q not found, creating it with default values\n" , context )
2018-05-04 16:53:21 +00:00
currentContext = Context { }
}
switch key {
case "address" :
return currentContext . Address , nil
case "email" :
return currentContext . Email , nil
case "token" :
return currentContext . Token , nil
2018-10-01 22:23:46 +00:00
case "rootca" :
return currentContext . RootCA , nil
2018-05-17 22:52:38 +00:00
case "tls-skip-verify" :
if currentContext . TLSSkipVerify {
2018-05-04 16:53:21 +00:00
return true , nil
}
2021-08-24 17:35:03 +00:00
return false , nil
2019-10-16 23:40:45 +00:00
case "url-prefix" :
return currentContext . URLPrefix , nil
2022-06-07 20:00:09 +00:00
case "custom-headers" :
return currentContext . CustomHeaders , nil
2018-05-04 16:53:21 +00:00
default :
return nil , fmt . Errorf ( "%q is an invalid key" , key )
}
}
2022-06-07 20:00:09 +00:00
func setConfigValue ( configPath , context , key string , value interface { } ) error {
2021-01-29 01:15:38 +00:00
if err := makeConfigIfNotExists ( configPath ) ; err != nil {
2021-11-22 14:13:26 +00:00
return fmt . Errorf ( "error verifying that config exists at %s: %w" , configPath , err )
2018-05-04 16:53:21 +00:00
}
2021-01-29 01:15:38 +00:00
config , err := readConfig ( configPath )
2018-05-04 16:53:21 +00:00
if err != nil {
2021-11-22 14:13:26 +00:00
return fmt . Errorf ( "error reading config at %s: %w" , configPath , err )
2018-05-04 16:53:21 +00:00
}
2021-01-29 01:15:38 +00:00
currentContext , ok := config . Contexts [ context ]
2018-05-04 16:53:21 +00:00
if ! ok {
2021-01-29 01:15:38 +00:00
fmt . Printf ( "[+] Context %q not found, creating it with default values\n" , context )
2018-05-04 16:53:21 +00:00
currentContext = Context { }
}
2022-06-07 20:00:09 +00:00
var strVal string
switch key {
case "address" , "email" , "token" , "rootca" , "tls-skip-verify" , "url-prefix" :
s , ok := value . ( string )
if ! ok {
return fmt . Errorf ( "error setting %q, string value expected, got %T" , key , value )
}
strVal = s
}
2018-05-04 16:53:21 +00:00
switch key {
case "address" :
2022-06-07 20:00:09 +00:00
currentContext . Address = strVal
2018-05-04 16:53:21 +00:00
case "email" :
2022-06-07 20:00:09 +00:00
currentContext . Email = strVal
2018-05-04 16:53:21 +00:00
case "token" :
2022-06-07 20:00:09 +00:00
currentContext . Token = strVal
2018-10-01 22:23:46 +00:00
case "rootca" :
2022-06-07 20:00:09 +00:00
currentContext . RootCA = strVal
2018-05-17 22:52:38 +00:00
case "tls-skip-verify" :
2022-06-07 20:00:09 +00:00
boolValue , err := strconv . ParseBool ( strVal )
2018-05-04 16:53:21 +00:00
if err != nil {
2021-11-22 14:13:26 +00:00
return fmt . Errorf ( "error parsing %q as bool: %w" , value , err )
2018-05-04 16:53:21 +00:00
}
2018-05-17 22:52:38 +00:00
currentContext . TLSSkipVerify = boolValue
2019-10-16 23:40:45 +00:00
case "url-prefix" :
2022-06-07 20:00:09 +00:00
currentContext . URLPrefix = strVal
case "custom-headers" :
vals , ok := value . ( [ ] string )
if ! ok {
return fmt . Errorf ( "error setting %q, []string value expected, got %T" , key , value )
}
hdrs := make ( map [ string ] string , len ( vals ) )
for _ , v := range vals {
parts := strings . SplitN ( v , ":" , 2 )
if len ( parts ) < 2 {
parts = append ( parts , "" )
}
hdrs [ parts [ 0 ] ] = parts [ 1 ]
}
currentContext . CustomHeaders = hdrs
2018-05-04 16:53:21 +00:00
default :
return fmt . Errorf ( "%q is an invalid option" , key )
}
2021-01-29 01:15:38 +00:00
config . Contexts [ context ] = currentContext
2018-05-04 16:53:21 +00:00
2021-01-29 01:15:38 +00:00
if err := writeConfig ( configPath , config ) ; err != nil {
2021-11-22 14:13:26 +00:00
return fmt . Errorf ( "error saving config file: %w" , err )
2018-05-04 16:53:21 +00:00
}
return nil
}
2021-03-13 00:42:38 +00:00
func configSetCommand ( ) * cli . Command {
2018-05-04 16:53:21 +00:00
var (
2018-05-17 22:52:38 +00:00
flAddress string
flEmail string
flToken string
flTLSSkipVerify bool
2018-10-01 22:23:46 +00:00
flRootCA string
2019-10-16 23:40:45 +00:00
flURLPrefix string
2022-06-07 20:00:09 +00:00
flCustomHeaders cli . StringSlice
2018-05-01 22:58:53 +00:00
)
2021-03-13 00:42:38 +00:00
return & cli . Command {
2018-05-04 16:53:21 +00:00
Name : "set" ,
Usage : "Set config options" ,
UsageText : ` fleetctl config set [options] ` ,
2018-05-01 22:58:53 +00:00
Flags : [ ] cli . Flag {
2018-05-04 16:53:21 +00:00
configFlag ( ) ,
contextFlag ( ) ,
2021-03-13 00:42:38 +00:00
& cli . StringFlag {
2018-05-01 22:58:53 +00:00
Name : "address" ,
2021-03-13 00:42:38 +00:00
EnvVars : [ ] string { "ADDRESS" } ,
2018-05-01 22:58:53 +00:00
Value : "" ,
Destination : & flAddress ,
2018-05-09 23:54:07 +00:00
Usage : "Address of the Fleet server" ,
2018-05-04 16:53:21 +00:00
} ,
2021-03-13 00:42:38 +00:00
& cli . StringFlag {
2018-05-04 16:53:21 +00:00
Name : "email" ,
2021-03-13 00:42:38 +00:00
EnvVars : [ ] string { "EMAIL" } ,
2018-05-04 16:53:21 +00:00
Value : "" ,
Destination : & flEmail ,
2018-05-09 23:54:07 +00:00
Usage : "Email to use when connecting to the Fleet server" ,
2018-05-04 16:53:21 +00:00
} ,
2021-03-13 00:42:38 +00:00
& cli . StringFlag {
2018-05-04 16:53:21 +00:00
Name : "token" ,
2021-03-13 00:42:38 +00:00
EnvVars : [ ] string { "TOKEN" } ,
2018-05-04 16:53:21 +00:00
Value : "" ,
Destination : & flToken ,
2018-05-09 23:54:07 +00:00
Usage : "Fleet API token" ,
2018-05-04 16:53:21 +00:00
} ,
2021-03-13 00:42:38 +00:00
& cli . BoolFlag {
2018-05-17 22:52:38 +00:00
Name : "tls-skip-verify" ,
2021-03-13 00:42:38 +00:00
EnvVars : [ ] string { "INSECURE" } ,
2018-05-17 22:52:38 +00:00
Destination : & flTLSSkipVerify ,
Usage : "Skip TLS certificate validation" ,
2018-05-01 22:58:53 +00:00
} ,
2021-03-13 00:42:38 +00:00
& cli . StringFlag {
2018-10-01 22:23:46 +00:00
Name : "rootca" ,
2021-03-13 00:42:38 +00:00
EnvVars : [ ] string { "ROOTCA" } ,
2018-10-01 22:23:46 +00:00
Value : "" ,
Destination : & flRootCA ,
2022-02-24 16:58:12 +00:00
Usage : "Specify RootCA chain used to communicate with Fleet" ,
2018-10-01 22:23:46 +00:00
} ,
2021-03-13 00:42:38 +00:00
& cli . StringFlag {
2019-10-16 23:40:45 +00:00
Name : "url-prefix" ,
2021-03-13 00:42:38 +00:00
EnvVars : [ ] string { "URL_PREFIX" } ,
2019-10-16 23:40:45 +00:00
Value : "" ,
Destination : & flURLPrefix ,
Usage : "Specify URL Prefix to use with Fleet server (copy from server configuration)" ,
} ,
2022-06-07 20:00:09 +00:00
& cli . StringSliceFlag {
Name : "custom-header" ,
EnvVars : [ ] string { "CUSTOM_HEADER" } ,
Value : nil ,
Destination : & flCustomHeaders ,
Usage : "Specify a custom header as 'Header:Value' to be set on every request to the Fleet server (can be specified multiple times for multiple headers, note that this replaces any existing custom headers). Note that when using the environment variable to set this option, it must be set like so: 'CUSTOM_HEADER=Header:Value,Header:Value', and the value cannot contain commas." ,
} ,
2018-05-01 22:58:53 +00:00
} ,
2018-05-04 16:53:21 +00:00
Action : func ( c * cli . Context ) error {
set := false
2021-01-29 01:15:38 +00:00
configPath , context := c . String ( "config" ) , c . String ( "context" )
2018-05-04 16:53:21 +00:00
if flAddress != "" {
set = true
2021-01-29 01:15:38 +00:00
if err := setConfigValue ( configPath , context , "address" , flAddress ) ; err != nil {
2021-11-22 14:13:26 +00:00
return fmt . Errorf ( "error setting address: %w" , err )
2018-05-04 16:53:21 +00:00
}
fmt . Printf ( "[+] Set the address config key to %q in the %q context\n" , flAddress , c . String ( "context" ) )
}
if flEmail != "" {
set = true
2021-01-29 01:15:38 +00:00
if err := setConfigValue ( configPath , context , "email" , flEmail ) ; err != nil {
2021-11-22 14:13:26 +00:00
return fmt . Errorf ( "error setting email: %w" , err )
2018-05-04 16:53:21 +00:00
}
fmt . Printf ( "[+] Set the email config key to %q in the %q context\n" , flEmail , c . String ( "context" ) )
}
if flToken != "" {
set = true
2021-01-29 01:15:38 +00:00
if err := setConfigValue ( configPath , context , "token" , flToken ) ; err != nil {
2021-11-22 14:13:26 +00:00
return fmt . Errorf ( "error setting token: %w" , err )
2018-05-04 16:53:21 +00:00
}
fmt . Printf ( "[+] Set the token config key to %q in the %q context\n" , flToken , c . String ( "context" ) )
}
2018-05-17 22:52:38 +00:00
if flTLSSkipVerify {
2018-05-04 16:53:21 +00:00
set = true
2021-01-29 01:15:38 +00:00
if err := setConfigValue ( configPath , context , "tls-skip-verify" , "true" ) ; err != nil {
2021-11-22 14:13:26 +00:00
return fmt . Errorf ( "error setting tls-skip-verify: %w" , err )
2018-05-04 16:53:21 +00:00
}
2018-05-17 22:52:38 +00:00
fmt . Printf ( "[+] Set the tls-skip-verify config key to \"true\" in the %q context\n" , c . String ( "context" ) )
2018-05-04 16:53:21 +00:00
}
2018-10-01 22:23:46 +00:00
if flRootCA != "" {
set = true
2021-01-29 01:15:38 +00:00
if err := setConfigValue ( configPath , context , "rootca" , flRootCA ) ; err != nil {
2021-11-22 14:13:26 +00:00
return fmt . Errorf ( "error setting rootca: %w" , err )
2018-10-01 22:23:46 +00:00
}
fmt . Printf ( "[+] Set the rootca config key to %q in the %q context\n" , flRootCA , c . String ( "context" ) )
}
2019-10-16 23:40:45 +00:00
if flURLPrefix != "" {
set = true
2021-01-29 01:15:38 +00:00
if err := setConfigValue ( configPath , context , "url-prefix" , flURLPrefix ) ; err != nil {
2021-11-22 14:13:26 +00:00
return fmt . Errorf ( "error setting URL Prefix: %w" , err )
2019-10-16 23:40:45 +00:00
}
fmt . Printf ( "[+] Set the url-prefix config key to %q in the %q context\n" , flURLPrefix , c . String ( "context" ) )
}
2022-06-07 20:00:09 +00:00
if len ( flCustomHeaders . Value ( ) ) > 0 {
set = true
if err := setConfigValue ( configPath , context , "custom-headers" , flCustomHeaders . Value ( ) ) ; err != nil {
return fmt . Errorf ( "error setting custom headers: %w" , err )
}
fmt . Printf ( "[+] Set the custom-headers config key to %v in the %q context\n" , flCustomHeaders . Value ( ) , c . String ( "context" ) )
}
2018-05-04 16:53:21 +00:00
if ! set {
return cli . ShowCommandHelp ( c , "set" )
}
2018-05-01 22:58:53 +00:00
return nil
} ,
}
}
2021-03-13 00:42:38 +00:00
func configGetCommand ( ) * cli . Command {
return & cli . Command {
2018-05-04 16:53:21 +00:00
Name : "get" ,
Usage : "Get a config option" ,
UsageText : ` fleetctl config get [options] ` ,
2018-05-01 22:58:53 +00:00
Flags : [ ] cli . Flag {
2018-05-04 16:53:21 +00:00
configFlag ( ) ,
contextFlag ( ) ,
2018-05-01 22:58:53 +00:00
} ,
2018-05-04 16:53:21 +00:00
Action : func ( c * cli . Context ) error {
2021-03-13 00:42:38 +00:00
if c . Args ( ) . Len ( ) != 1 {
2018-05-04 16:53:21 +00:00
return cli . ShowCommandHelp ( c , "get" )
}
2021-03-13 00:42:38 +00:00
key := c . Args ( ) . Get ( 0 )
2018-05-04 16:53:21 +00:00
// validate key
switch key {
2022-06-07 20:00:09 +00:00
case "address" , "email" , "token" , "tls-skip-verify" , "rootca" , "url-prefix" , "custom-headers" :
2018-05-04 16:53:21 +00:00
default :
return cli . ShowCommandHelp ( c , "get" )
}
2021-01-29 01:15:38 +00:00
configPath , context := c . String ( "config" ) , c . String ( "context" )
value , err := getConfigValue ( configPath , context , key )
2018-05-04 16:53:21 +00:00
if err != nil {
2021-11-22 14:13:26 +00:00
return fmt . Errorf ( "error getting config value: %w" , err )
2018-05-04 16:53:21 +00:00
}
2022-06-07 20:00:09 +00:00
fmt . Printf ( " %s.%s => %v\n" , c . String ( "context" ) , key , value )
2018-05-04 16:53:21 +00:00
2018-05-01 22:58:53 +00:00
return nil
} ,
}
}