Skip to content

Commit

Permalink
Sync from server repo (010214ba1ea)
Browse files Browse the repository at this point in the history
  • Loading branch information
cchen-vertica committed Jul 30, 2024
1 parent d70d186 commit 8cb9230
Show file tree
Hide file tree
Showing 12 changed files with 328 additions and 106 deletions.
63 changes: 43 additions & 20 deletions commands/cluster_command_launcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,15 @@ const defaultLogPath = "/opt/vertica/log/vcluster.log"
const defaultExecutablePath = "/opt/vertica/bin/vcluster"

const CLIVersion = "2.0.0"
const vclusterLogPathEnv = "VCLUSTER_LOG_PATH"
const vclusterKeyFileEnv = "VCLUSTER_KEY_FILE"
const vclusterCertFileEnv = "VCLUSTER_CERT_FILE"

// environment variables
const (
vclusterLogPathEnv = "VCLUSTER_LOG_PATH"
vclusterKeyFileEnv = "VCLUSTER_KEY_FILE"
vclusterCertFileEnv = "VCLUSTER_CERT_FILE"
vclusterCACertFileEnv = "VCLUSTER_CA_CERT_FILE"
vclusterTLSModeEnv = "VCLUSTER_TLS_MODE"
)

// *Flag is for the flag name, *Key is for viper key name
// They are bound together
Expand Down Expand Up @@ -67,6 +73,10 @@ const (
keyFileKey = "keyFile"
certFileFlag = "cert-file"
certFileKey = "certFile"
caCertFileFlag = "ca-cert-file"
caCertFileKey = "caCertFile"
tlsModeFlag = "tls-mode"
tlsModeKey = "tlsMode"
passwordFlag = "password"
passwordKey = "password"
passwordFileFlag = "password-file"
Expand Down Expand Up @@ -127,6 +137,8 @@ var flagKeyMap = map[string]string{
logPathFlag: logPathKey,
keyFileFlag: keyFileKey,
certFileFlag: certFileKey,
caCertFileFlag: caCertFileKey,
tlsModeFlag: tlsModeKey,
passwordFlag: passwordKey,
passwordFileFlag: passwordFileKey,
readPasswordFromPromptFlag: readPasswordFromPromptKey,
Expand All @@ -149,6 +161,15 @@ var targetFlagKeyMap = map[string]string{
targetPasswordFileFlag: targetPasswordFileKey,
}

// map of viper keys to environment variables
var keyEnvVarMap = map[string]string{
logPathKey: vclusterLogPathEnv,
keyFileKey: vclusterKeyFileEnv,
certFileKey: vclusterCertFileEnv,
caCertFileKey: vclusterCACertFileEnv,
tlsModeKey: vclusterTLSModeEnv,
}

const (
createDBSubCmd = "create_db"
stopDBSubCmd = "stop_db"
Expand Down Expand Up @@ -183,10 +204,12 @@ const (
// cmdGlobals holds global variables shared by multiple
// commands
type cmdGlobals struct {
verbose bool
file *os.File
keyFile string
certFile string
verbose bool
file *os.File
keyFile string
certFile string
caCertFile string
tlsMode string

// Global variables for targetDB are used for the replication subcommand
targetHosts []string
Expand Down Expand Up @@ -262,6 +285,8 @@ func initVcc(cmd *cobra.Command) vclusterops.VClusterCommands {
}

// setDBOptionsUsingViper can set the value of flag using the relevant key in viper
//
//nolint:gocyclo
func setDBOptionsUsingViper(flag string) error {
switch flag {
case dbNameFlag:
Expand All @@ -288,6 +313,10 @@ func setDBOptionsUsingViper(flag string) error {
globals.keyFile = viper.GetString(keyFileKey)
case certFileFlag:
globals.certFile = viper.GetString(certFileKey)
case caCertFileFlag:
globals.caCertFile = viper.GetString(caCertFileKey)
case tlsModeFlag:
globals.tlsMode = viper.GetString(tlsModeKey)
case verboseFlag:
globals.verbose = viper.GetBool(verboseKey)
default:
Expand Down Expand Up @@ -329,13 +358,13 @@ func configViper(cmd *cobra.Command, flagsInConfig []string) error {
}
// log-path is a flag that all the subcommands need
flagsInConfig = append(flagsInConfig, logPathFlag)
// cert-file and key-file are not available for
// TLS related flags are not available for
// - manage_config
// - manage_config show
// - create_connection
if cmd.CalledAs() != manageConfigSubCmd &&
cmd.CalledAs() != configShowSubCmd && cmd.CalledAs() != createConnectionSubCmd {
flagsInConfig = append(flagsInConfig, certFileFlag, keyFileFlag)
flagsInConfig = append(flagsInConfig, certFileFlag, keyFileFlag, caCertFileFlag, tlsModeFlag)
}

// bind viper keys to cobra flags
Expand Down Expand Up @@ -364,17 +393,11 @@ func configViper(cmd *cobra.Command, flagsInConfig []string) error {

// bind viper keys to env vars
func bindKeysToEnv() error {
err := viper.BindEnv(logPathKey, vclusterLogPathEnv)
if err != nil {
return fmt.Errorf("fail to bind viper key %q to environment variable %q: %w", logPathKey, vclusterLogPathEnv, err)
}
err = viper.BindEnv(keyFileKey, vclusterKeyFileEnv)
if err != nil {
return fmt.Errorf("fail to bind viper key %q to environment variable %q: %w", keyFileKey, vclusterKeyFileEnv, err)
}
err = viper.BindEnv(certFileKey, vclusterCertFileEnv)
if err != nil {
return fmt.Errorf("fail to bind viper key %q to environment variable %q: %w", certFileKey, vclusterCertFileEnv, err)
for key, envVar := range keyEnvVarMap {
err := viper.BindEnv(key, envVar)
if err != nil {
return fmt.Errorf("fail to bind viper key %q to environment variable %q: %w", key, envVar, err)
}
}
return nil
}
Expand Down
101 changes: 83 additions & 18 deletions commands/cmd_base.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ const (
defConfigParamFileName = "config_param.json"
)

// all three tls modes enable TLS and present (client) cert if requested
const (
tlsModeEnable = "enable" // skip validating peer (server) cert
tlsModeVerifyCA = "verify-ca" // validate peer cert signer chain, skipping hostname validation
tlsModeVerifyFull = "verify-full" // validate peer cert signer chain and hostname
)

/* CmdBase
*
* Basic/common fields of vcluster commands
Expand Down Expand Up @@ -62,6 +69,28 @@ func (c *CmdBase) ValidateParseBaseOptions(opt *vclusterops.DatabaseOptions) err
}
}

// parse TLS mode. vclusterops allows different behavior for NMA and HTTPS conns, but
// for simplicity and lack of use case outside k8s, vcluster does not.
if globals.tlsMode != "" {
switch tlsMode := strings.ToLower(globals.tlsMode); tlsMode {
case tlsModeEnable:
opt.DoVerifyHTTPSServerCert = false
opt.DoVerifyNMAServerCert = false
opt.DoVerifyPeerCertHostname = false
case tlsModeVerifyCA:
opt.DoVerifyHTTPSServerCert = true
opt.DoVerifyNMAServerCert = true
opt.DoVerifyPeerCertHostname = false
case tlsModeVerifyFull:
opt.DoVerifyHTTPSServerCert = true
opt.DoVerifyNMAServerCert = true
opt.DoVerifyPeerCertHostname = true
default:
return fmt.Errorf("unrecognized TLS mode: %s. Allowed values are: '%s', '%s'",
globals.tlsMode, tlsModeEnable, tlsModeVerifyCA)
}
}

return nil
}

Expand Down Expand Up @@ -96,26 +125,13 @@ func (c *CmdBase) setCommonFlags(cmd *cobra.Command, flags []string) {
false,
"Whether show the details of VCluster run in the console",
)
// keyFile and certFile are flags that all subcommands require,
// except for create_connection and manage_config show
if cmd.Name() != configShowSubCmd && cmd.Name() != createConnectionSubCmd {
cmd.Flags().StringVar(
&globals.keyFile,
keyFileFlag,
"",
fmt.Sprintf("Path to the key file, the default value is %s", filepath.Join(vclusterops.CertPathBase, "{username}.key")),
)
markFlagsFileName(cmd, map[string][]string{keyFileFlag: {"key"}})

cmd.Flags().StringVar(
&globals.certFile,
certFileFlag,
"",
fmt.Sprintf("Path to the cert file, the default value is %s", filepath.Join(vclusterops.CertPathBase, "{username}.pem")),
)
markFlagsFileName(cmd, map[string][]string{certFileFlag: {"pem", "crt"}})
cmd.MarkFlagsRequiredTogether(keyFileFlag, certFileFlag)
// TLS related flags are allowed by all subcommands,
// except for create_connection and manage_config show.
if cmd.Name() != configShowSubCmd && cmd.Name() != createConnectionSubCmd {
c.setTLSFlags(cmd)
}

if util.StringInArray(outputFileFlag, flags) {
cmd.Flags().StringVarP(
&c.output,
Expand Down Expand Up @@ -225,6 +241,47 @@ func (c *CmdBase) setConfigFlags(cmd *cobra.Command, flags []string) {
}
}

// setTLSFlags sets the TLS options in global variables for later processing
// into vclusterops options.
func (c *CmdBase) setTLSFlags(cmd *cobra.Command) {
// vcluster CLI reads certs into memory before calling vclusterops only
// if non-default values are specified, which is why the defaults here
// are "" despite being listed otherwise in the help messages.
// Those defaults are used by vclusterops if no in-memory certs are provided.
cmd.Flags().StringVar(
&globals.keyFile,
keyFileFlag,
"",
fmt.Sprintf("Path to the key file, the default value is %s", filepath.Join(vclusterops.CertPathBase, "{username}.key")),
)
markFlagsFileName(cmd, map[string][]string{keyFileFlag: {"key"}})

cmd.Flags().StringVar(
&globals.certFile,
certFileFlag,
"",
fmt.Sprintf("Path to the cert file, the default value is %s", filepath.Join(vclusterops.CertPathBase, "{username}.pem")),
)
markFlagsFileName(cmd, map[string][]string{certFileFlag: {"pem", "crt"}})
cmd.MarkFlagsRequiredTogether(keyFileFlag, certFileFlag)

cmd.Flags().StringVar(
&globals.caCertFile,
caCertFileFlag,
"",
fmt.Sprintf("Path to the trusted CA cert file, the default value is %s", filepath.Join(vclusterops.CertPathBase, "rootca.pem")),
)
markFlagsFileName(cmd, map[string][]string{caCertFileFlag: {"pem", "crt"}})

cmd.Flags().StringVar(
&globals.tlsMode,
tlsModeFlag,
"",
fmt.Sprintf("Mode for TLS validation. Allowed values '%s', '%s', and '%s'. Default value is '%s'.",
tlsModeEnable, tlsModeVerifyCA, tlsModeVerifyFull, tlsModeEnable),
)
}

func (c *CmdBase) initConfigParam() error {
// We need to find the path to the config param. The order of precedence is as follows:
// 1. Option
Expand Down Expand Up @@ -462,6 +519,7 @@ func (c *CmdBase) initCmdOutputFile() (*os.File, error) {

// getCertFilesFromPaths will update cert and key file from cert path options
func (c *CmdBase) getCertFilesFromCertPaths(opt *vclusterops.DatabaseOptions) error {
// TODO don't make this conditional on not using a PW for auth (see callers)
if globals.certFile != "" {
certData, err := os.ReadFile(globals.certFile)
if err != nil {
Expand All @@ -476,5 +534,12 @@ func (c *CmdBase) getCertFilesFromCertPaths(opt *vclusterops.DatabaseOptions) er
}
opt.Key = string(keyData)
}
if globals.caCertFile != "" {
caCertData, err := os.ReadFile(globals.caCertFile)
if err != nil {
return fmt.Errorf("failed to read trusted CA certificate file: %w", err)
}
opt.CaCert = string(caCertData)
}
return nil
}
1 change: 0 additions & 1 deletion commands/cmd_scrutinize.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,6 @@ func (c *CmdScrutinize) validateParse(logger vlog.Printer) error {
}
}

// parses host list and ipv6 - eon is irrelevant but handled
err := c.ValidateParseBaseOptions(&c.sOptions.DatabaseOptions)
if err != nil {
return err
Expand Down
27 changes: 20 additions & 7 deletions vclusterops/cluster_op.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ type clusterOp interface {
logExecute()
logFinalize()
setupBasicInfo()
loadCertsIfNeeded(tlsOptions opTLSOptions) error
applyTLSOptions(tlsOptions opTLSOptions) error
isSkipExecute() bool
filterUnreachableHosts(execContext *opEngineExecContext)
}
Expand Down Expand Up @@ -385,11 +385,13 @@ func (op *opBase) runExecute(execContext *opEngineExecContext) error {
type opTLSOptions interface {
hasCerts() bool
getCerts() *httpsCerts
getTLSModes() *tlsModes
}

// if found certs in the options, we add the certs to http requests of each instruction
func (op *opBase) loadCertsIfNeeded(tlsOptions opTLSOptions) error {
if tlsOptions == nil || !tlsOptions.hasCerts() {
// applyTLSOptions processes TLS options here, like in-memory certificates or TLS modes,
// rather than plumbing them through to every op.
func (op *opBase) applyTLSOptions(tlsOptions opTLSOptions) error {
if tlsOptions == nil {
return nil
}

Expand All @@ -399,14 +401,25 @@ func (op *opBase) loadCertsIfNeeded(tlsOptions opTLSOptions) error {
}

// retrieve certs once to avoid extra copies
certs := tlsOptions.getCerts()
if certs == nil {
return fmt.Errorf("[%s] is trying to use certificates, but none are set", op.name)
var certs *httpsCerts
if tlsOptions.hasCerts() {
certs = tlsOptions.getCerts()
if certs == nil {
return fmt.Errorf("[%s] is trying to use certificates, but none are set", op.name)
}
}

// always retrieve TLS modes
tlsModes := tlsOptions.getTLSModes()
if tlsModes == nil {
return fmt.Errorf("[%s] unable to retrieve TLS modes from interface", op.name)
}

// modify requests with TLS options
for host := range op.clusterHTTPRequest.RequestCollection {
request := op.clusterHTTPRequest.RequestCollection[host]
request.setCerts(certs)
request.setTLSMode(tlsModes)
op.clusterHTTPRequest.RequestCollection[host] = request
}
return nil
Expand Down
4 changes: 2 additions & 2 deletions vclusterops/cluster_op_engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,12 @@ func (opEngine *VClusterOpEngine) runInstruction(
// start the progress spinner
op.startSpinner()

err = op.loadCertsIfNeeded(opEngine.tlsOptions)
err = op.applyTLSOptions(opEngine.tlsOptions)
if err != nil {
// here we do not return an error as the spinner error does not
// affect the functionality
op.stopFailSpinnerWithMessage(err.Error())
return fmt.Errorf("loadCertsIfNeeded for %s failed, details: %w", op.getName(), err)
return fmt.Errorf("applying TLS options for %s failed, details: %w", op.getName(), err)
}

// execute an instruction
Expand Down
Loading

0 comments on commit 8cb9230

Please sign in to comment.