diff --git a/cli/cmd/cluster.go b/cli/cmd/cluster.go index 554d8899a7..50886832d6 100644 --- a/cli/cmd/cluster.go +++ b/cli/cmd/cluster.go @@ -47,10 +47,14 @@ import ( ) var ( - _flagClusterEnv string - _flagClusterConfig string - _flagClusterInfoDebug bool - _flagClusterDisallowPrompt bool + _flagClusterEnv string + _flagClusterConfig string + _flagClusterInfoDebug bool + _flagClusterDisallowPrompt bool + _flagAWSAccessKeyID string + _flagAWSSecretAccessKey string + _flagClusterAWSAccessKeyID string + _flagClusterAWSSecretAccessKey string ) func clusterInit() { @@ -58,25 +62,31 @@ func clusterInit() { _upCmd.Flags().SortFlags = false addClusterConfigFlag(_upCmd) - _upCmd.Flags().StringVarP(&_flagClusterEnv, "env", "e", defaultEnv, "environment to configure") + addAWSCredentials(_upCmd) + _upCmd.Flags().StringVar(&_flagClusterAWSAccessKeyID, "cluster-aws-key", "", "aws access key id to be used by the cluster") + _upCmd.Flags().StringVar(&_flagClusterAWSSecretAccessKey, "cluster-aws-secret", "", "aws secret access key to be used by the cluster") + _upCmd.Flags().StringVarP(&_flagClusterEnv, "env", "e", defaultEnv, "environment to create") _upCmd.Flags().BoolVarP(&_flagClusterDisallowPrompt, "yes", "y", false, "skip prompts") _clusterCmd.AddCommand(_upCmd) _infoCmd.Flags().SortFlags = false addClusterConfigFlag(_infoCmd) - _infoCmd.Flags().StringVarP(&_flagClusterEnv, "env", "e", defaultEnv, "environment to configure") + addAWSCredentials(_infoCmd) + _infoCmd.Flags().StringVarP(&_flagClusterEnv, "env", "e", defaultEnv, "environment to update") _infoCmd.Flags().BoolVarP(&_flagClusterInfoDebug, "debug", "d", false, "save the current cluster state to a file") _infoCmd.Flags().BoolVarP(&_flagClusterDisallowPrompt, "yes", "y", false, "skip prompts") _clusterCmd.AddCommand(_infoCmd) _configureCmd.Flags().SortFlags = false addClusterConfigFlag(_configureCmd) - _configureCmd.Flags().StringVarP(&_flagClusterEnv, "env", "e", defaultEnv, "environment to configure") + addAWSCredentials(_configureCmd) + _configureCmd.Flags().StringVarP(&_flagClusterEnv, "env", "e", defaultEnv, "environment to update") _configureCmd.Flags().BoolVarP(&_flagClusterDisallowPrompt, "yes", "y", false, "skip prompts") _clusterCmd.AddCommand(_configureCmd) _downCmd.Flags().SortFlags = false addClusterConfigFlag(_downCmd) + addAWSCredentials(_downCmd) _downCmd.Flags().BoolVarP(&_flagClusterDisallowPrompt, "yes", "y", false, "skip prompts") _clusterCmd.AddCommand(_downCmd) } @@ -86,6 +96,11 @@ func addClusterConfigFlag(cmd *cobra.Command) { cmd.Flags().SetAnnotation("config", cobra.BashCompFilenameExt, _configFileExts) } +func addAWSCredentials(cmd *cobra.Command) { + cmd.Flags().StringVar(&_flagAWSAccessKeyID, "aws-key", "", "aws access key id") + cmd.Flags().StringVar(&_flagAWSSecretAccessKey, "aws-secret", "", "aws secret access key") +} + var _clusterCmd = &cobra.Command{ Use: "cluster", Short: "manage a cluster", @@ -110,7 +125,14 @@ var _upCmd = &cobra.Command{ promptForEmail() } - awsCreds, err := getAWSCredentials(_flagClusterConfig, _flagClusterEnv, _flagClusterDisallowPrompt) + if _flagClusterConfig != "" { + // Deprecation: specifying aws creds in cluster configuration is no longer supported + if err := detectAWSCredsInConfigFile(cmd.Use, _flagClusterConfig); err != nil { + exit.Error(err) + } + } + + awsCreds, err := awsCredentialsForCreatingCluster(_flagClusterDisallowPrompt) if err != nil { exit.Error(err) } @@ -254,8 +276,8 @@ var _upCmd = &cobra.Command{ Name: _flagClusterEnv, Provider: types.AWSProviderType, OperatorEndpoint: pointer.String("https://" + *loadBalancer.DNSName), - AWSAccessKeyID: pointer.String(awsCreds.CortexAWSAccessKeyID), - AWSSecretAccessKey: pointer.String(awsCreds.CortexAWSSecretAccessKey), + AWSAccessKeyID: pointer.String(awsCreds.ClusterAWSAccessKeyID), + AWSSecretAccessKey: pointer.String(awsCreds.ClusterAWSSecretAccessKey), } err = addEnvToCLIConfig(newEnvironment) @@ -263,6 +285,8 @@ var _upCmd = &cobra.Command{ exit.Error(errors.Append(err, fmt.Sprintf("unable to configure cli environment; you can attempt to resolve this issue and configure your CLI environment by running `cortex cluster info --env %s`", _flagClusterEnv))) } + cacheAWSCredentials(awsCreds, accessConfig) + fmt.Printf(console.Bold("\nan environment named \"%s\" has been configured for this cluster; append `--env %s` to cortex commands to connect to it (e.g. `cortex deploy --env %s`), or set it as your default with `cortex env default %s`\n"), _flagClusterEnv, _flagClusterEnv, _flagClusterEnv, _flagClusterEnv) }, } @@ -282,12 +306,19 @@ var _configureCmd = &cobra.Command{ exit.Error(err) } - awsCreds, err := getAWSCredentials(_flagClusterConfig, _flagClusterEnv, _flagClusterDisallowPrompt) + if _flagClusterConfig != "" { + // Deprecation: specifying aws creds in cluster configuration is no longer supported + if err := detectAWSCredsInConfigFile(cmd.Use, _flagClusterConfig); err != nil { + exit.Error(err) + } + } + + accessConfig, err := getClusterAccessConfig(_flagClusterDisallowPrompt) if err != nil { exit.Error(err) } - accessConfig, err := getClusterAccessConfig(_flagClusterDisallowPrompt) + awsCreds, err := awsCredentialsForManagingCluster(*accessConfig, _flagClusterDisallowPrompt) if err != nil { exit.Error(err) } @@ -324,6 +355,8 @@ var _configureCmd = &cobra.Command{ fmt.Println(helpStr) exit.Error(ErrorClusterConfigure(out + helpStr)) } + + cacheAWSCredentials(awsCreds, *accessConfig) }, } @@ -341,12 +374,19 @@ var _infoCmd = &cobra.Command{ exit.Error(err) } - awsCreds, err := getAWSCredentials(_flagClusterConfig, _flagClusterEnv, _flagClusterDisallowPrompt) + if _flagClusterConfig != "" { + // Deprecation: specifying aws creds in cluster configuration is no longer supported + if err := detectAWSCredsInConfigFile(cmd.Use, _flagClusterConfig); err != nil { + exit.Error(err) + } + } + + accessConfig, err := getClusterAccessConfig(_flagClusterDisallowPrompt) if err != nil { exit.Error(err) } - accessConfig, err := getClusterAccessConfig(_flagClusterDisallowPrompt) + awsCreds, err := awsCredentialsForManagingCluster(*accessConfig, _flagClusterDisallowPrompt) if err != nil { exit.Error(err) } @@ -356,6 +396,8 @@ var _infoCmd = &cobra.Command{ } else { cmdInfo(awsCreds, accessConfig, _flagClusterDisallowPrompt) } + + cacheAWSCredentials(awsCreds, *accessConfig) }, } @@ -370,12 +412,19 @@ var _downCmd = &cobra.Command{ exit.Error(err) } - awsCreds, err := getAWSCredentials(_flagClusterConfig, _flagClusterEnv, _flagClusterDisallowPrompt) + if _flagClusterConfig != "" { + // Deprecation: specifying aws creds in cluster configuration is no longer supported + if err := detectAWSCredsInConfigFile(cmd.Use, _flagClusterConfig); err != nil { + exit.Error(err) + } + } + + accessConfig, err := getClusterAccessConfig(_flagClusterDisallowPrompt) if err != nil { exit.Error(err) } - accessConfig, err := getClusterAccessConfig(_flagClusterDisallowPrompt) + awsCreds, err := awsCredentialsForManagingCluster(*accessConfig, _flagClusterDisallowPrompt) if err != nil { exit.Error(err) } @@ -480,6 +529,7 @@ var _downCmd = &cobra.Command{ cachedClusterConfigPath := cachedClusterConfigPath(*accessConfig.ClusterName, *accessConfig.Region) os.Remove(cachedClusterConfigPath) + uncacheAWSCredentials(*accessConfig) }, } diff --git a/cli/cmd/errors.go b/cli/cmd/errors.go index 8b0778bab5..b9ec41747a 100644 --- a/cli/cmd/errors.go +++ b/cli/cmd/errors.go @@ -58,6 +58,9 @@ const ( ErrAPINotReady = "cli.api_not_ready" ErrOneAWSEnvVarSet = "cli.one_aws_env_var_set" ErrOneAWSConfigFieldSet = "cli.one_aws_config_field_set" + ErrOneAWSConfigFlagSet = "cli.one_aws_config_flag_set" + ErrMissingAWSCredentials = "cli.missing_aws_credentials" + ErrCredentialsInClusterConfig = "cli.credentials_in_cluster_config" ErrClusterUp = "cli.cluster_up" ErrClusterConfigure = "cli.cluster_configure" ErrClusterInfo = "cli.cluster_info" @@ -65,7 +68,6 @@ const ( ErrClusterRefresh = "cli.cluster_refresh" ErrClusterDown = "cli.cluster_down" ErrDuplicateCLIEnvNames = "cli.duplicate_cli_env_names" - ErrAWSCredentialsRequired = "cli.aws_credentials_required" ErrClusterConfigOrPromptsRequired = "cli.cluster_config_or_prompts_required" ErrClusterAccessConfigOrPromptsRequired = "cli.cluster_access_config_or_prompts_required" ErrShellCompletionNotSupported = "cli.shell_completion_not_supported" @@ -213,6 +215,28 @@ func ErrorOneAWSConfigFieldSet(setConfigField string, missingConfigField string, }) } +func ErrorOneAWSFlagSet(setFlag string, missingFlag string) error { + return errors.WithStack(&errors.Error{ + Kind: ErrOneAWSConfigFlagSet, + Message: fmt.Sprintf("only flag %s was provided; please provide %s as well", setFlag, missingFlag), + }) +} + +func ErrorMissingAWSCredentials() error { + return errors.WithStack(&errors.Error{ + Kind: ErrMissingAWSCredentials, + Message: "unable to find aws credentials; please specify aws credentials using the flags --aws-key and --aws-secret", + }) +} + +// Deprecation: specifying aws creds in cluster configuration is no longer supported +func ErrorCredentialsInClusterConfig(cmd string, path string) error { + return errors.WithStack(&errors.Error{ + Kind: ErrCredentialsInClusterConfig, + Message: fmt.Sprintf("specifying credentials in the cluster configuration is no longer supported, please specify aws credentials using flags (e.g. cortex cluster %s --config %s --aws-key --aws-secret ) or set environment variables; see https://docs.cortex.dev/v/%s/miscellaneous/security#iam-permissions for more information", cmd, path, consts.CortexVersionMinor), + }) +} + func ErrorClusterUp(out string) error { return errors.WithStack(&errors.Error{ Kind: ErrClusterUp, @@ -261,13 +285,6 @@ func ErrorClusterDown(out string) error { }) } -func ErrorAWSCredentialsRequired() error { - return errors.WithStack(&errors.Error{ - Kind: ErrAWSCredentialsRequired, - Message: "AWS credentials are required; please set them in your cluster configuration file (if you're using one), your environment variables (i.e. AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY), or your AWS CLI (i.e. via `aws configure`)", - }) -} - func ErrorClusterConfigOrPromptsRequired() error { return errors.WithStack(&errors.Error{ Kind: ErrClusterConfigOrPromptsRequired, diff --git a/cli/cmd/lib_aws_creds.go b/cli/cmd/lib_aws_creds.go index 9235dd74b7..739e409ade 100644 --- a/cli/cmd/lib_aws_creds.go +++ b/cli/cmd/lib_aws_creds.go @@ -19,19 +19,29 @@ package cmd import ( "fmt" "os" + "path/filepath" "github.com/cortexlabs/cortex/pkg/lib/aws" - cr "github.com/cortexlabs/cortex/pkg/lib/configreader" "github.com/cortexlabs/cortex/pkg/lib/errors" + libjson "github.com/cortexlabs/cortex/pkg/lib/json" + "github.com/cortexlabs/cortex/pkg/lib/sets/strset" + s "github.com/cortexlabs/cortex/pkg/lib/strings" + + cr "github.com/cortexlabs/cortex/pkg/lib/configreader" + "github.com/cortexlabs/cortex/pkg/lib/files" "github.com/cortexlabs/cortex/pkg/lib/prompt" "github.com/cortexlabs/cortex/pkg/types/clusterconfig" ) type AWSCredentials struct { - AWSAccessKeyID string `json:"aws_access_key_id"` - AWSSecretAccessKey string `json:"aws_secret_access_key"` - CortexAWSAccessKeyID string `json:"cortex_aws_access_key_id"` - CortexAWSSecretAccessKey string `json:"cortex_aws_secret_access_key"` + AWSAccessKeyID string `json:"aws_access_key_id"` + AWSSecretAccessKey string `json:"aws_secret_access_key"` + ClusterAWSAccessKeyID string `json:"cluster_aws_access_key_id"` + ClusterAWSSecretAccessKey string `json:"cluster_aws_secret_access_key"` +} + +func (awsCreds *AWSCredentials) MaskedString() string { + return fmt.Sprintf("aws access key id ****%s and aws secret access key ****%s", s.LastNChars(awsCreds.AWSAccessKeyID, 4), s.LastNChars(awsCreds.AWSSecretAccessKey, 4)) } func newAWSClient(region string, awsCreds AWSCredentials) (*aws.Client, error) { @@ -78,36 +88,6 @@ func warnIfNotAdmin(awsClient *aws.Client) { } } -var _awsCredentialsValidation = &cr.StructValidation{ - AllowExtraFields: true, - StructFieldValidations: []*cr.StructFieldValidation{ - { - StructField: "AWSAccessKeyID", - StringValidation: &cr.StringValidation{ - AllowEmpty: true, - }, - }, - { - StructField: "AWSSecretAccessKey", - StringValidation: &cr.StringValidation{ - AllowEmpty: true, - }, - }, - { - StructField: "CortexAWSAccessKeyID", - StringValidation: &cr.StringValidation{ - AllowEmpty: true, - }, - }, - { - StructField: "CortexAWSSecretAccessKey", - StringValidation: &cr.StringValidation{ - AllowEmpty: true, - }, - }, - }, -} - var _awsCredentialsPromptValidation = &cr.PromptValidation{ PromptItemValidations: []*cr.PromptItemValidation{ { @@ -133,132 +113,251 @@ var _awsCredentialsPromptValidation = &cr.PromptValidation{ }, } -func readAWSCredsFromConfigFile(awsCreds *AWSCredentials, path string) error { - errs := cr.ParseYAMLFile(awsCreds, _awsCredentialsValidation, path) - if errors.HasError(errs) { - return errors.FirstError(errs...) +// Deprecation: specifying aws creds in cluster configuration is no longer supported; returns an error if aws credentials were found in cluster configuration yaml +func detectAWSCredsInConfigFile(cmd, path string) error { + credentialFieldKeys := strset.New("aws_access_key_id", "aws_secret_access_key", "cortex_aws_access_key_id", "cortex_aws_secret_access_key") + fieldMap, err := cr.ReadYAMLFileStrMap(path) + if err != nil { + return nil + } + + for key := range fieldMap { + if credentialFieldKeys.Has(key) { + return errors.Wrap(ErrorCredentialsInClusterConfig(cmd, path), path, key) + } } return nil } -// awsCreds is what was read from the cluster config YAML -func setInstallAWSCredentials(awsCreds *AWSCredentials, envName string, disallowPrompt bool) error { - // First check env vars - if os.Getenv("AWS_SESSION_TOKEN") != "" { - fmt.Println("warning: credentials requiring aws session tokens are not supported") +func awsCredentialsForCreatingCluster(disallowPrompt bool) (AWSCredentials, error) { + awsCredentials, err := awsCredentialsFromFlags() + if err != nil { + return AWSCredentials{}, err } - if os.Getenv("AWS_ACCESS_KEY_ID") != "" && os.Getenv("AWS_SECRET_ACCESS_KEY") != "" { - awsCreds.AWSAccessKeyID = os.Getenv("AWS_ACCESS_KEY_ID") - awsCreds.AWSSecretAccessKey = os.Getenv("AWS_SECRET_ACCESS_KEY") - return nil + if awsCredentials != nil { + return *awsCredentials, nil } - if os.Getenv("AWS_ACCESS_KEY_ID") == "" && os.Getenv("AWS_SECRET_ACCESS_KEY") != "" { - return ErrorOneAWSEnvVarSet("AWS_SECRET_ACCESS_KEY", "AWS_ACCESS_KEY_ID") + + awsCredentials, err = awsCredentialsFromEnvVars() + if err != nil { + return AWSCredentials{}, err } - if os.Getenv("AWS_ACCESS_KEY_ID") != "" && os.Getenv("AWS_SECRET_ACCESS_KEY") == "" { - return ErrorOneAWSEnvVarSet("AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY") + + if awsCredentials != nil { + fmt.Println(fmt.Sprintf("using %s found in environment variables (to use different credentials, specify the --aws-key and --aws-secret flags)\n", awsCredentials.MaskedString())) + return *awsCredentials, nil } - // Next check what was read from cluster config YAML - if awsCreds.AWSAccessKeyID != "" && awsCreds.AWSSecretAccessKey != "" { - return nil + awsCredentials, err = awsCredentialsFromSharedCreds() + if err != nil { + return AWSCredentials{}, errors.Append(err, "\n\nit may be possible to avoid this error by specifying the --aws-key and --aws-secret flags") } - if awsCreds.AWSAccessKeyID == "" && awsCreds.AWSSecretAccessKey != "" { - return ErrorOneAWSConfigFieldSet("aws_secret_access_key", "aws_access_key_id", _flagClusterConfig) + + if awsCredentials != nil { + fmt.Println(fmt.Sprintf("using %s from the \"default\" profile configured via `aws configure` (to use different credentials, specify the --aws-key and --aws-secret flags)\n", awsCredentials.MaskedString())) + return *awsCredentials, nil } - if awsCreds.AWSAccessKeyID != "" && awsCreds.AWSSecretAccessKey == "" { - return ErrorOneAWSConfigFieldSet("aws_access_key_id", "aws_secret_access_key", _flagClusterConfig) + + if disallowPrompt { + return AWSCredentials{}, ErrorMissingAWSCredentials() } - // Next check AWS CLI config file - accessKeyID, secretAccessKey, err := aws.GetCredentialsFromCLIConfigFile() - if err == nil { - awsCreds.AWSAccessKeyID = accessKeyID - awsCreds.AWSSecretAccessKey = secretAccessKey - return nil + awsCredentials, err = awsCredentialsPrompt() + if err != nil { + return AWSCredentials{}, errors.Append(err, "\n\nit may be possible to avoid this error by specifying the --aws-key and --aws-secret flags") } - // Next check Cortex CLI config file - env, err := readEnv(envName) - if err != nil && env != nil && env.AWSAccessKeyID != nil && env.AWSSecretAccessKey != nil { - awsCreds.AWSAccessKeyID = *env.AWSAccessKeyID - awsCreds.AWSSecretAccessKey = *env.AWSSecretAccessKey - return nil + return *awsCredentials, nil +} + +func awsCredentialsForManagingCluster(accessConfig clusterconfig.AccessConfig, disallowPrompt bool) (AWSCredentials, error) { + awsCredentials, err := awsCredentialsFromFlags() + if err != nil { + return AWSCredentials{}, err + } + + if awsCredentials != nil { + return *awsCredentials, nil + } + + awsCredentials, err = getAWSCredentialsFromCache(accessConfig) + if err != nil { + return AWSCredentials{}, errors.Append(err, "\n\nit may be possible to avoid this error by specifying the --aws-key and --aws-secret flags") + } + + if awsCredentials != nil { + fmt.Println(fmt.Sprintf("using cached %s (to use different credentials, specify the --aws-key and --aws-secret flags)\n", awsCredentials.MaskedString())) + return *awsCredentials, nil } - // Prompt if disallowPrompt { - return ErrorAWSCredentialsRequired() + return AWSCredentials{}, ErrorMissingAWSCredentials() } - err = cr.ReadPrompt(awsCreds, _awsCredentialsPromptValidation) + + awsCredentials, err = awsCredentialsPrompt() if err != nil { - return err + return AWSCredentials{}, errors.Append(err, "\n\nit may be possible to avoid this error by specifying the --aws-key and --aws-secret flags") } - return nil + return *awsCredentials, nil } -// awsCreds is what was read from the cluster config YAML, after being passed through setInstallAWSCredentials() (so those credentials should be set) -func setOperatorAWSCredentials(awsCreds *AWSCredentials) error { - // First check env vars - if os.Getenv("CORTEX_AWS_ACCESS_KEY_ID") != "" && os.Getenv("CORTEX_AWS_SECRET_ACCESS_KEY") != "" { - awsCreds.CortexAWSAccessKeyID = os.Getenv("CORTEX_AWS_ACCESS_KEY_ID") - awsCreds.CortexAWSSecretAccessKey = os.Getenv("CORTEX_AWS_SECRET_ACCESS_KEY") - return nil +// Returns true if the provided credentials match either the operator or the CLI credentials +func (awsCreds *AWSCredentials) ContainsCreds(accessKeyID string, secretAccessKey string) bool { + if awsCreds.AWSAccessKeyID == accessKeyID && awsCreds.AWSSecretAccessKey == secretAccessKey { + return true } - if os.Getenv("CORTEX_AWS_ACCESS_KEY_ID") == "" && os.Getenv("CORTEX_AWS_SECRET_ACCESS_KEY") != "" { - return ErrorOneAWSEnvVarSet("CORTEX_AWS_SECRET_ACCESS_KEY", "CORTEX_AWS_ACCESS_KEY_ID") + if awsCreds.ClusterAWSAccessKeyID == accessKeyID && awsCreds.ClusterAWSSecretAccessKey == secretAccessKey { + return true } - if os.Getenv("CORTEX_AWS_ACCESS_KEY_ID") != "" && os.Getenv("CORTEX_AWS_SECRET_ACCESS_KEY") == "" { - return ErrorOneAWSEnvVarSet("CORTEX_AWS_ACCESS_KEY_ID", "CORTEX_AWS_SECRET_ACCESS_KEY") + return false +} + +func awsCredentialsFromFlags() (*AWSCredentials, error) { + credentials := AWSCredentials{} + + if _flagAWSAccessKeyID == "" && _flagAWSSecretAccessKey == "" { + return nil, nil } - // Next check what was read from cluster config YAML - if awsCreds.CortexAWSAccessKeyID != "" && awsCreds.CortexAWSSecretAccessKey != "" { - return nil + if _flagAWSSecretAccessKey == "" { + return nil, ErrorOneAWSFlagSet("--aws-key", "--aws-secret") } - if awsCreds.CortexAWSAccessKeyID == "" && awsCreds.CortexAWSSecretAccessKey != "" { - return ErrorOneAWSConfigFieldSet("cortex_aws_secret_access_key", "cortex_aws_access_key_id", _flagClusterConfig) + if _flagAWSAccessKeyID == "" { + return nil, ErrorOneAWSFlagSet("--aws-secret", "--aws-key") } - if awsCreds.CortexAWSAccessKeyID != "" && awsCreds.CortexAWSSecretAccessKey == "" { - return ErrorOneAWSConfigFieldSet("cortex_aws_access_key_id", "cortex_aws_secret_access_key", _flagClusterConfig) + + credentials.AWSAccessKeyID = _flagAWSAccessKeyID + credentials.AWSSecretAccessKey = _flagAWSSecretAccessKey + + if _flagClusterAWSAccessKeyID != "" || _flagClusterAWSSecretAccessKey != "" { + if _flagClusterAWSAccessKeyID == "" { + return nil, ErrorOneAWSFlagSet("--cluster-aws-key", "--cluster-aws-secret") + } + if _flagClusterAWSSecretAccessKey == "" { + return nil, ErrorOneAWSFlagSet("--cluster-aws-secret", "--cluster-aws-key") + } + + credentials.ClusterAWSAccessKeyID = _flagClusterAWSAccessKeyID + credentials.ClusterAWSSecretAccessKey = _flagClusterAWSAccessKeyID + } else { + credentials.ClusterAWSAccessKeyID = credentials.AWSAccessKeyID + credentials.ClusterAWSSecretAccessKey = credentials.AWSSecretAccessKey } - // Default to primary AWS credentials - awsCreds.CortexAWSAccessKeyID = awsCreds.AWSAccessKeyID - awsCreds.CortexAWSSecretAccessKey = awsCreds.AWSSecretAccessKey - return nil + return &credentials, nil } -func getAWSCredentials(userClusterConfigPath string, envName string, disallowPrompt bool) (AWSCredentials, error) { - awsCreds := AWSCredentials{} +func awsCredentialsFromEnvVars() (*AWSCredentials, error) { + credentials := AWSCredentials{} - if userClusterConfigPath != "" { - readAWSCredsFromConfigFile(&awsCreds, userClusterConfigPath) + if os.Getenv("AWS_SESSION_TOKEN") != "" { + fmt.Println("warning: credentials requiring aws session tokens are not supported") } - err := setInstallAWSCredentials(&awsCreds, envName, disallowPrompt) + if os.Getenv("AWS_ACCESS_KEY_ID") == "" && os.Getenv("AWS_SECRET_ACCESS_KEY") == "" { + return nil, nil + } + + if os.Getenv("AWS_ACCESS_KEY_ID") == "" && os.Getenv("AWS_SECRET_ACCESS_KEY") != "" { + return nil, ErrorOneAWSEnvVarSet("AWS_SECRET_ACCESS_KEY", "AWS_ACCESS_KEY_ID") + } + if os.Getenv("AWS_ACCESS_KEY_ID") != "" && os.Getenv("AWS_SECRET_ACCESS_KEY") == "" { + return nil, ErrorOneAWSEnvVarSet("AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY") + } + + credentials.AWSAccessKeyID = os.Getenv("AWS_ACCESS_KEY_ID") + credentials.AWSSecretAccessKey = os.Getenv("AWS_SECRET_ACCESS_KEY") + + if os.Getenv("CLUSTER_AWS_ACCESS_KEY_ID") != "" || os.Getenv("CLUSTER_AWS_SECRET_ACCESS_KEY") != "" { + if os.Getenv("CLUSTER_AWS_ACCESS_KEY_ID") == "" && os.Getenv("CLUSTER_AWS_SECRET_ACCESS_KEY") != "" { + return nil, ErrorOneAWSEnvVarSet("CLUSTER_AWS_SECRET_ACCESS_KEY", "CLUSTER_AWS_ACCESS_KEY_ID") + } + if os.Getenv("CLUSTER_AWS_ACCESS_KEY_ID") != "" && os.Getenv("CLUSTER_AWS_SECRET_ACCESS_KEY") == "" { + return nil, ErrorOneAWSEnvVarSet("CLUSTER_AWS_ACCESS_KEY_ID", "CLUSTER_AWS_SECRET_ACCESS_KEY") + } + + credentials.ClusterAWSAccessKeyID = os.Getenv("CLUSTER_AWS_ACCESS_KEY_ID") + credentials.ClusterAWSSecretAccessKey = os.Getenv("CLUSTER_AWS_SECRET_ACCESS_KEY") + } else { + credentials.ClusterAWSAccessKeyID = credentials.AWSAccessKeyID + credentials.ClusterAWSSecretAccessKey = credentials.AWSSecretAccessKey + } + + return &credentials, nil +} + +// Read from "default" profile from credentials specified by AWS_SHARED_CREDENTIALS_FILE (default path: ~/.aws/credentials) +func awsCredentialsFromSharedCreds() (*AWSCredentials, error) { + credentials := AWSCredentials{} + accessKeyID, secretAccessKey, err := aws.GetCredentialsFromCLIConfigFile() if err != nil { - return AWSCredentials{}, err + return nil, err } - err = setOperatorAWSCredentials(&awsCreds) + credentials.AWSAccessKeyID = accessKeyID + credentials.AWSSecretAccessKey = secretAccessKey + credentials.ClusterAWSAccessKeyID = accessKeyID + credentials.ClusterAWSSecretAccessKey = secretAccessKey + return &credentials, nil +} + +func awsCredentialsPrompt() (*AWSCredentials, error) { + credentials := AWSCredentials{} + + err := cr.ReadPrompt(&credentials, _awsCredentialsPromptValidation) if err != nil { - return AWSCredentials{}, err + return nil, err } - return awsCreds, nil + credentials.ClusterAWSAccessKeyID = credentials.AWSAccessKeyID + credentials.ClusterAWSSecretAccessKey = credentials.AWSSecretAccessKey + + return &credentials, nil } -// Returns true if the provided credentials match either the operator or the CLI credentials -func (awsCreds *AWSCredentials) ContainsCreds(accessKeyID string, secretAccessKey string) bool { - if awsCreds.AWSAccessKeyID == accessKeyID && awsCreds.AWSSecretAccessKey == secretAccessKey { - return true +func credentialsCachePath(accessConfig clusterconfig.AccessConfig) string { + return filepath.Join(_credentialsCacheDir, fmt.Sprintf("%s-%s.json", *accessConfig.Region, *accessConfig.ClusterName)) +} + +func getAWSCredentialsFromCache(accessConfig clusterconfig.AccessConfig) (*AWSCredentials, error) { + credsPath := credentialsCachePath(accessConfig) + + if !files.IsFile(credsPath) { + return nil, nil } - if awsCreds.CortexAWSAccessKeyID == accessKeyID && awsCreds.CortexAWSSecretAccessKey == secretAccessKey { - return true + + jsonBytes, err := files.ReadFileBytes(credsPath) + if err != nil { + return nil, err } - return false + + credentials := AWSCredentials{} + + err = libjson.Unmarshal(jsonBytes, &credentials) + if err != nil { + return nil, err + } + + return &credentials, nil +} + +func cacheAWSCredentials(awsCreds AWSCredentials, accessConfig clusterconfig.AccessConfig) error { + jsonBytes, err := libjson.Marshal(awsCreds) + if err != nil { + return err + } + + err = files.WriteFile(jsonBytes, credentialsCachePath(accessConfig)) + if err != nil { + return err + } + + return nil +} + +func uncacheAWSCredentials(accessConfig clusterconfig.AccessConfig) error { + return os.Remove(credentialsCachePath(accessConfig)) } diff --git a/cli/cmd/lib_cluster_config.go b/cli/cmd/lib_cluster_config.go index 4b6cdd71aa..e2915b45c0 100644 --- a/cli/cmd/lib_cluster_config.go +++ b/cli/cmd/lib_cluster_config.go @@ -497,8 +497,8 @@ func clusterConfigConfirmationStr(clusterConfig clusterconfig.Config, awsCreds A var items table.KeyValuePairs items.Add("aws access key id", s.MaskString(awsCreds.AWSAccessKeyID, 4)) - if awsCreds.CortexAWSAccessKeyID != awsCreds.AWSAccessKeyID { - items.Add("aws access key id", s.MaskString(awsCreds.CortexAWSAccessKeyID, 4)+" (cortex)") + if awsCreds.ClusterAWSAccessKeyID != awsCreds.AWSAccessKeyID { + items.Add("aws access key id", s.MaskString(awsCreds.ClusterAWSAccessKeyID, 4)+" (cortex)") } items.Add(clusterconfig.RegionUserKey, clusterConfig.Region) if len(clusterConfig.AvailabilityZones) > 0 { diff --git a/cli/cmd/lib_manager.go b/cli/cmd/lib_manager.go index 9693d3c51b..22ad62eff1 100644 --- a/cli/cmd/lib_manager.go +++ b/cli/cmd/lib_manager.go @@ -189,8 +189,8 @@ func runManagerWithClusterConfig(entrypoint string, clusterConfig *clusterconfig "CORTEX_ENV_NAME=" + envName, "AWS_ACCESS_KEY_ID=" + awsCreds.AWSAccessKeyID, "AWS_SECRET_ACCESS_KEY=" + awsCreds.AWSSecretAccessKey, - "CORTEX_AWS_ACCESS_KEY_ID=" + awsCreds.CortexAWSAccessKeyID, - "CORTEX_AWS_SECRET_ACCESS_KEY=" + awsCreds.CortexAWSSecretAccessKey, + "CLUSTER_AWS_ACCESS_KEY_ID=" + awsCreds.ClusterAWSAccessKeyID, + "CLUSTER_AWS_SECRET_ACCESS_KEY=" + awsCreds.ClusterAWSSecretAccessKey, "CORTEX_TELEMETRY_DISABLE=" + os.Getenv("CORTEX_TELEMETRY_DISABLE"), "CORTEX_TELEMETRY_SENTRY_DSN=" + os.Getenv("CORTEX_TELEMETRY_SENTRY_DSN"), "CORTEX_TELEMETRY_SEGMENT_WRITE_KEY=" + os.Getenv("CORTEX_TELEMETRY_SEGMENT_WRITE_KEY"), @@ -228,8 +228,8 @@ func runManagerAccessCommand(entrypoint string, accessConfig clusterconfig.Acces "CORTEX_ENV_NAME=" + envName, "AWS_ACCESS_KEY_ID=" + awsCreds.AWSAccessKeyID, "AWS_SECRET_ACCESS_KEY=" + awsCreds.AWSSecretAccessKey, - "CORTEX_AWS_ACCESS_KEY_ID=" + awsCreds.CortexAWSAccessKeyID, - "CORTEX_AWS_SECRET_ACCESS_KEY=" + awsCreds.CortexAWSSecretAccessKey, + "CLUSTER_AWS_ACCESS_KEY_ID=" + awsCreds.ClusterAWSAccessKeyID, + "CLUSTER_AWS_SECRET_ACCESS_KEY=" + awsCreds.ClusterAWSSecretAccessKey, "CORTEX_CLUSTER_NAME=" + *accessConfig.ClusterName, "CORTEX_REGION=" + *accessConfig.Region, }, diff --git a/cli/cmd/root.go b/cli/cmd/root.go index 38e2f7d831..1c1d52c46a 100644 --- a/cli/cmd/root.go +++ b/cli/cmd/root.go @@ -38,13 +38,14 @@ var ( _configFileExts = []string{"yaml", "yml"} _flagOutput = flags.PrettyOutputType - _localDir string - _cliConfigPath string - _clientIDPath string - _emailPath string - _debugPath string - _cwd string - _homeDir string + _credentialsCacheDir string + _localDir string + _cliConfigPath string + _clientIDPath string + _emailPath string + _debugPath string + _cwd string + _homeDir string ) type commandType int @@ -76,6 +77,14 @@ func init() { exit.Error(err) } + // ~/.cortex/credentials/ + _credentialsCacheDir = filepath.Join(_localDir, "credentials") + err = os.MkdirAll(_credentialsCacheDir, os.ModePerm) + if err != nil { + err := errors.Wrap(err, "unable to write to home directory", _localDir) + exit.Error(err) + } + _cliConfigPath = filepath.Join(_localDir, "cli.yaml") _clientIDPath = filepath.Join(_localDir, "client-id.txt") _emailPath = filepath.Join(_localDir, "email.txt") diff --git a/dev/operator_local.sh b/dev/operator_local.sh index 4ad887ae89..b733b487f7 100755 --- a/dev/operator_local.sh +++ b/dev/operator_local.sh @@ -25,7 +25,10 @@ kill $(pgrep -f rerun) >/dev/null 2>&1 || true eval $(python3 $ROOT/manager/cluster_config_env.py "$ROOT/dev/config/cluster.yaml") -python3 $ROOT/dev/update_cli_config.py "$HOME/.cortex/cli.yaml" "aws" "http://localhost:8888" "$CORTEX_AWS_ACCESS_KEY_ID" "$CORTEX_AWS_SECRET_ACCESS_KEY" +export CLUSTER_AWS_ACCESS_KEY_ID="${CLUSTER_AWS_ACCESS_KEY_ID:-$AWS_ACCESS_KEY_ID}" +export CLUSTER_AWS_SECRET_ACCESS_KEY="${CLUSTER_AWS_SECRET_ACCESS_KEY:-$AWS_SECRET_ACCESS_KEY}" + +python3 $ROOT/dev/update_cli_config.py "$HOME/.cortex/cli.yaml" "aws" "http://localhost:8888" "$CLUSTER_AWS_ACCESS_KEY_ID" "$CLUSTER_AWS_SECRET_ACCESS_KEY" cp -r $ROOT/dev/config/cluster.yaml ~/.cortex/cluster-dev.yaml diff --git a/docs/cluster-management/config.md b/docs/cluster-management/config.md index 2ecc1ee344..3283cfb6a0 100644 --- a/docs/cluster-management/config.md +++ b/docs/cluster-management/config.md @@ -8,14 +8,6 @@ The Cortex cluster may be configured by providing a configuration file to `corte ```yaml # cluster.yaml -# AWS credentials (if not specified, ~/.aws/credentials will be checked) (can be overridden by $AWS_ACCESS_KEY_ID and $AWS_SECRET_ACCESS_KEY) -aws_access_key_id: *** -aws_secret_access_key: *** - -# optional AWS credentials for the operator which may be used to restrict its AWS access (defaults to the AWS credentials set above) -cortex_aws_access_key_id: *** -cortex_aws_secret_access_key: *** - # EKS cluster name for cortex (default: cortex) cluster_name: cortex diff --git a/docs/contributing/development.md b/docs/contributing/development.md index eeb0364afa..fb20ad5993 100644 --- a/docs/contributing/development.md +++ b/docs/contributing/development.md @@ -64,6 +64,8 @@ E.g. to install it globally, run: ```bash sudo python -m pip install awscli + +aws configure ``` ## Cortex dev environment @@ -129,12 +131,9 @@ export CLI_BUCKET_NAME="cortex-cli-" export CLI_BUCKET_REGION="us-west-2" ``` -Create `dev/config/cluster.yaml`. Paste the following config, and update `cortex_bucket`, `cortex_region`, `aws_access_key_id`, `aws_secret_access_key`, and all registry URLs accordingly: +Create `dev/config/cluster.yaml`. Paste the following config, and update `cortex_bucket`, `cortex_region`, and all registry URLs accordingly: ```yaml -aws_access_key_id: *** -aws_secret_access_key: *** - instance_type: m5.large min_instances: 2 max_instances: 5 @@ -168,6 +167,10 @@ Add this to your bash profile (e.g. `~/.bash_profile`, `~/.profile` or `~/.bashr export CORTEX_DEV_DEFAULT_PREDICTOR_IMAGE_REGISTRY="XXXXXXXX.dkr.ecr.us-west-2.amazonaws.com/cortexlabs" # set the default image for APIs export CORTEX_TELEMETRY_SENTRY_DSN="https://c334df915c014ffa93f2076769e5b334@sentry.io/1848098" # redirect analytics to our dev environment export CORTEX_TELEMETRY_SEGMENT_WRITE_KEY="0WvoJyCey9z1W2EW7rYTPJUMRYat46dl" # redirect error reporting to our dev environment + +export AWS_ACCESS_KEY_ID="" +export AWS_SECRET_ACCESS_KEY="" + alias cortex-dev='/bin/cortex' # replace with the path to the cortex repo that you cloned ``` diff --git a/docs/miscellaneous/cli.md b/docs/miscellaneous/cli.md index 76f2b2a4cf..cc0099ae15 100644 --- a/docs/miscellaneous/cli.md +++ b/docs/miscellaneous/cli.md @@ -99,10 +99,14 @@ Usage: cortex cluster up [flags] Flags: - -c, --config string path to a cluster configuration file - -e, --env string environment to configure (default "aws") - -y, --yes skip prompts - -h, --help help for up + -c, --config string path to a cluster configuration file + --aws-key string aws access key id + --aws-secret string aws secret access key + --cluster-aws-key string aws access key id to be used by the cluster + --cluster-aws-secret string aws secret access key to be used by the cluster + -e, --env string environment to create (default "aws") + -y, --yes skip prompts + -h, --help help for up ``` ## cluster info @@ -114,11 +118,13 @@ Usage: cortex cluster info [flags] Flags: - -c, --config string path to a cluster configuration file - -e, --env string environment to configure (default "aws") - -d, --debug save the current cluster state to a file - -y, --yes skip prompts - -h, --help help for info + -c, --config string path to a cluster configuration file + --aws-key string aws access key id + --aws-secret string aws secret access key + -e, --env string environment to update (default "aws") + -d, --debug save the current cluster state to a file + -y, --yes skip prompts + -h, --help help for info ``` ## cluster configure @@ -130,10 +136,12 @@ Usage: cortex cluster configure [flags] Flags: - -c, --config string path to a cluster configuration file - -e, --env string environment to configure (default "aws") - -y, --yes skip prompts - -h, --help help for configure + -c, --config string path to a cluster configuration file + --aws-key string aws access key id + --aws-secret string aws secret access key + -e, --env string environment to update (default "aws") + -y, --yes skip prompts + -h, --help help for configure ``` ## cluster down @@ -145,9 +153,11 @@ Usage: cortex cluster down [flags] Flags: - -c, --config string path to a cluster configuration file - -y, --yes skip prompts - -h, --help help for down + -c, --config string path to a cluster configuration file + --aws-key string aws access key id + --aws-secret string aws secret access key + -y, --yes skip prompts + -h, --help help for down ``` ## env configure diff --git a/docs/miscellaneous/environments.md b/docs/miscellaneous/environments.md index 9e70fcac4d..d1408ea489 100644 --- a/docs/miscellaneous/environments.md +++ b/docs/miscellaneous/environments.md @@ -87,13 +87,13 @@ On your new machine, run: cortex env configure ``` -This will prompt for the necessary configuration. Note that the AWS credentials that you use here do not need any IAM permissions attached. If you will be running any `cortex cluster` commands, you can configure the CLI environment to use credentials that have the `AdministratorAccess` IAM policy attached, or specify your admin credentials in your [cluster configuration](../cluster-management/config.md) file (e.g. set `aws_access_key_id` and `aws_secret_access_key` in `cluster.yaml`, and use it like `cortex cluster info --config cluster.yaml`). See [IAM permissions](security.md#iam-permissions) for more details. +This will prompt for the necessary configuration. Note that the AWS credentials that you use here do not need any IAM permissions attached. If you will be running any `cortex cluster` commands specify the preferred AWS credentials using cli flags `--aws-key AWS_ACCESS_KEY_ID --aws-secret AWS_SECRET_ACCESS_KEY`. See [IAM permissions](security.md#iam-permissions) for more details. ## Environments overview By default, the CLI ships with a single environment named `local`. This is the default environment for all Cortex commands (other than `cortex cluster` commands), which means that APIs will be deployed locally by default. -Some cortex commands (i.e. `cortex cluster` commands) only apply to cluster environments. Unless otherwise specified by the `-e`/`--env` flag, `cortex cluster` commands create/use an environment named `aws`. For example `cortex cluster up` will configure the `aws` environment to connect to your new cluster. You may interact with this cluster by appending `--env aws` to your `cortex` commands. +Some cortex commands (i.e. `cortex cluster` commands) only apply to cluster environments. Unless otherwise specified by the `-e`/`--env` flag, `cortex cluster` commands create/update an environment named `aws`. For example `cortex cluster up` will configure the `aws` environment to connect to your new cluster. You may interact with this cluster by appending `--env aws` to your `cortex` commands. If you accidentally delete or overwrite one of your cluster environments, running `cortex cluster info --env ENV_NAME` will automatically update the specified environment to interact with the cluster. diff --git a/docs/miscellaneous/security.md b/docs/miscellaneous/security.md index 6b5e4ca89b..089929a2c6 100644 --- a/docs/miscellaneous/security.md +++ b/docs/miscellaneous/security.md @@ -20,13 +20,19 @@ By default, the Cortex cluster operator's load balancer is internet-facing, and If you are not using a sensitive AWS account and do not have a lot of experience with IAM configuration, attaching the built-in `AdministratorAccess` policy to your IAM user will make getting started much easier. If you would like to limit IAM permissions, continue reading. +Cortex uses AWS credentials for 3 main purposes: + +1. Spinning up a cluster (credentials with `AdministratorAccess` is recommended) +2. Cluster runtime (see [operator policy](#operator)) +3. CLI authentication (no special permissions are required) + ### Cluster spin-up -Spinning up Cortex on your AWS account requires more permissions that Cortex needs once it's running. You can specify different credentials for each purpose in two ways: +Spinning up Cortex on your AWS account requires more permissions than Cortex needs once it's running. You can specify different credentials for each purpose in two ways (in order of precedence): -1. You can export the environment variables `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` which will be used to create your cluster, and export `CORTEX_AWS_ACCESS_KEY_ID` and `CORTEX_AWS_SECRET_ACCESS_KEY` which will be used by the cluster. +1. You can specify `--aws-key` and `--aws-secret` flags with the command `cortex cluster up` to indicate the credentials that will be used to create your cluster. Optionally, you can specify `--cluster-aws-key` and `--cluster-aws-secret` to specify credentials which will be used by the cluster. -2. If you are using a cluster configuration file (e.g. `cluster.yaml`), you can set the fields `aws_access_key_id` and `aws_secret_access_key` which will be used to create your cluster, and set `cortex_aws_access_key_id` and `cortex_aws_secret_access_key` which will be used by the cluster. +2. You can export the environment variables `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` which will be used to create your cluster. Optionally, you can export `CLUSTER_AWS_ACCESS_KEY_ID` and `CLUSTER_AWS_SECRET_ACCESS_KEY` to specify credentials which will be used by the cluster. In either case, the credentials used when spinning up the cluster will not be used by the cluster itself, and can be safely revoked after the cluster is running. You may need credentials with similar access to run other `cortex cluster` commands, such as `cortex cluster configure`, `cortex cluster info`, and `cortex cluster down`. @@ -34,6 +40,8 @@ It is recommended to use an IAM user with the `AdministratorAccess` policy to cr ### Operator +A process called the Cortex operator runs on your cluster and is responsible for deploying and managing your APIs on the cluster. The operator will use the designated cluster credentials (e.g. `--cluster-aws-key` or `$CLUSTER_AWS_ACCESS_KEY_ID`) if specified, otherwise it will default to using the credentials used to spin up the cluster (e.g. `--aws-key` or `$AWS_ACCESS_KEY_ID`). + The operator requires read permissions for any S3 bucket containing exported models, read/write permissions for the Cortex S3 bucket, read permissions for ECR, read permissions for ELB, read/write permissions for API Gateway, read/write permissions for CloudWatch metrics, and read/write permissions for the Cortex CloudWatch log group. The policy below may be used to restrict the Operator's access: ```json diff --git a/manager/install.sh b/manager/install.sh index 8831f07d0b..e73262d267 100755 --- a/manager/install.sh +++ b/manager/install.sh @@ -308,8 +308,8 @@ function setup_configmap() { function setup_secrets() { kubectl -n=default create secret generic 'aws-credentials' \ - --from-literal='AWS_ACCESS_KEY_ID'=$CORTEX_AWS_ACCESS_KEY_ID \ - --from-literal='AWS_SECRET_ACCESS_KEY'=$CORTEX_AWS_SECRET_ACCESS_KEY \ + --from-literal='AWS_ACCESS_KEY_ID'=$CLUSTER_AWS_ACCESS_KEY_ID \ + --from-literal='AWS_SECRET_ACCESS_KEY'=$CLUSTER_AWS_SECRET_ACCESS_KEY \ -o yaml --dry-run=client | kubectl apply -f - >/dev/null } diff --git a/pkg/lib/configreader/reader.go b/pkg/lib/configreader/reader.go index 26cc8ee20c..7f05e4ecbe 100644 --- a/pkg/lib/configreader/reader.go +++ b/pkg/lib/configreader/reader.go @@ -948,6 +948,18 @@ func ReadYAMLFile(filePath string) (interface{}, error) { return fileInterface, nil } +func ReadYAMLFileStrMap(filePath string) (map[string]interface{}, error) { + parsed, err := ReadYAMLFile(filePath) + if err != nil { + return nil, err + } + casted, ok := cast.InterfaceToStrInterfaceMap(parsed) + if !ok { + return nil, ErrorInvalidPrimitiveType(parsed, PrimTypeMap) + } + return casted, nil +} + func ReadYAMLBytes(yamlBytes []byte) (interface{}, error) { if len(yamlBytes) == 0 { return nil, nil diff --git a/pkg/lib/strings/operations.go b/pkg/lib/strings/operations.go index 9508adf603..1d0b22d299 100644 --- a/pkg/lib/strings/operations.go +++ b/pkg/lib/strings/operations.go @@ -88,6 +88,15 @@ func LastSplit(str string, chars string) string { return split[len(split)-1] } +// Returns the last n chars, or the entire string if the requested length is greater than the length of the string +func LastNChars(str string, n int) string { + if len(str) < n { + return str + } + + return str[len(str)-n:] +} + func LongestCommonPrefix(strs ...string) string { if len(strs) == 0 { return "" diff --git a/pkg/types/clusterconfig/clusterconfig.go b/pkg/types/clusterconfig/clusterconfig.go index 45b30ea541..2c527b874d 100644 --- a/pkg/types/clusterconfig/clusterconfig.go +++ b/pkg/types/clusterconfig/clusterconfig.go @@ -456,23 +456,6 @@ var UserValidation = &cr.StructValidation{ Validator: validateImageVersion, }, }, - // Extra keys that exist in the cluster config file - { - Key: "aws_access_key_id", - Nil: true, - }, - { - Key: "aws_secret_access_key", - Nil: true, - }, - { - Key: "cortex_aws_access_key_id", - Nil: true, - }, - { - Key: "cortex_aws_secret_access_key", - Nil: true, - }, }, }