From 9eae132d1e8263f5a2cc6e2adb929dde9026423d Mon Sep 17 00:00:00 2001 From: Matthew John Date: Thu, 25 Jan 2024 06:51:30 +0000 Subject: [PATCH 1/8] Add okta profile configuration, in preparation to support multiple seperate configuration sections --- cmd/root/root.go | 7 ++++++ internal/config/config.go | 49 +++++++++++++++++++++++++-------------- 2 files changed, 38 insertions(+), 18 deletions(-) diff --git a/cmd/root/root.go b/cmd/root/root.go index 662ab41..3123d1d 100644 --- a/cmd/root/root.go +++ b/cmd/root/root.go @@ -101,6 +101,13 @@ func init() { usage: "AWS Profile", envVar: config.ProfileEnvVar, }, + { + name: config.OktaProfileFlag, + short: "t", + value: "", + usage: "Okta Config Profile", + envVar: config.OktaProfileEnvVar, + }, { name: config.FormatFlag, short: "f", diff --git a/internal/config/config.go b/internal/config/config.go index 0f3776f..13eb912 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -62,6 +62,8 @@ const ( OrgDomainFlag = "org-domain" // ProfileFlag cli flag const ProfileFlag = "profile" + // OktaProfileFlag cli flag const + OktaProfileFlag = "okta-profile" // QRCodeFlag cli flag const QRCodeFlag = "qr-code" // SessionDurationFlag cli flag const @@ -95,6 +97,8 @@ const ( OpenBrowserEnvVar = "OKTA_AWSCLI_OPEN_BROWSER" // ProfileEnvVar env var const ProfileEnvVar = "OKTA_AWSCLI_PROFILE" + // OktaProfileEnvVar env var const + OktaProfileEnvVar = "OKTA_AWSCLI_OKTA_PROFILE" // QRCodeEnvVar env var const QRCodeEnvVar = "OKTA_AWSCLI_QR_CODE" // WriteAWSCredentialsEnvVar env var const @@ -234,26 +238,35 @@ func NewConfig(attrs Attributes) (*Config, error) { return cfg, nil } +func getFlagNameFromProfile(oktaProfile string, flag string) string { + if oktaProfile == "" { + return flag + } + return fmt.Sprintf("%s.%s", oktaProfile, flag) +} + func readConfig() (Attributes, error) { + oktaProfile := viper.GetString(OktaProfileFlag) + attrs := Attributes{ - AWSCredentials: viper.GetString(AWSCredentialsFlag), - AWSIAMIdP: viper.GetString(AWSIAMIdPFlag), - AWSIAMRole: viper.GetString(AWSIAMRoleFlag), - AWSSessionDuration: viper.GetInt64(SessionDurationFlag), - Debug: viper.GetBool(DebugFlag), - DebugAPICalls: viper.GetBool(DebugAPICallsFlag), - DebugConfig: viper.GetBool(DebugConfigFlag), - FedAppID: viper.GetString(AWSAcctFedAppIDFlag), - Format: viper.GetString(FormatFlag), - LegacyAWSVariables: viper.GetBool(LegacyAWSVariablesFlag), - ExpiryAWSVariables: viper.GetBool(ExpiryAWSVariablesFlag), - CacheAccessToken: viper.GetBool(CacheAccessTokenFlag), - OIDCAppID: viper.GetString(OIDCClientIDFlag), - OpenBrowser: viper.GetBool(OpenBrowserFlag), - OrgDomain: viper.GetString(OrgDomainFlag), - Profile: viper.GetString(ProfileFlag), - QRCode: viper.GetBool(QRCodeFlag), - WriteAWSCredentials: viper.GetBool(WriteAWSCredentialsFlag), + AWSCredentials: viper.GetString(getFlagNameFromProfile(oktaProfile, AWSCredentialsFlag)), + AWSIAMIdP: viper.GetString(getFlagNameFromProfile(oktaProfile, AWSIAMIdPFlag)), + AWSIAMRole: viper.GetString(getFlagNameFromProfile(oktaProfile, AWSIAMRoleFlag)), + AWSSessionDuration: viper.GetInt64(getFlagNameFromProfile(oktaProfile, SessionDurationFlag)), + Debug: viper.GetBool(getFlagNameFromProfile(oktaProfile, DebugFlag)), + DebugAPICalls: viper.GetBool(getFlagNameFromProfile(oktaProfile, DebugAPICallsFlag)), + DebugConfig: viper.GetBool(getFlagNameFromProfile(oktaProfile, DebugConfigFlag)), + FedAppID: viper.GetString(getFlagNameFromProfile(oktaProfile, AWSAcctFedAppIDFlag)), + Format: viper.GetString(getFlagNameFromProfile(oktaProfile, FormatFlag)), + LegacyAWSVariables: viper.GetBool(getFlagNameFromProfile(oktaProfile, LegacyAWSVariablesFlag)), + ExpiryAWSVariables: viper.GetBool(getFlagNameFromProfile(oktaProfile, ExpiryAWSVariablesFlag)), + CacheAccessToken: viper.GetBool(getFlagNameFromProfile(oktaProfile, CacheAccessTokenFlag)), + OIDCAppID: viper.GetString(getFlagNameFromProfile(oktaProfile, OIDCClientIDFlag)), + OpenBrowser: viper.GetBool(getFlagNameFromProfile(oktaProfile, OpenBrowserFlag)), + OrgDomain: viper.GetString(getFlagNameFromProfile(oktaProfile, OrgDomainFlag)), + Profile: viper.GetString(getFlagNameFromProfile(oktaProfile, ProfileFlag)), + QRCode: viper.GetBool(getFlagNameFromProfile(oktaProfile, QRCodeFlag)), + WriteAWSCredentials: viper.GetBool(getFlagNameFromProfile(oktaProfile, WriteAWSCredentialsFlag)), } if attrs.Format == "" { attrs.Format = EnvVarFormat From 8f06f59240097160619532182e552e716be42236 Mon Sep 17 00:00:00 2001 From: Matthew John Date: Thu, 25 Jan 2024 06:56:49 +0000 Subject: [PATCH 2/8] Add support for yaml config in user's home directory, containing multiple okta profiles --- cmd/root/root.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/cmd/root/root.go b/cmd/root/root.go index 3123d1d..535dfb2 100644 --- a/cmd/root/root.go +++ b/cmd/root/root.go @@ -20,6 +20,7 @@ import ( "errors" "fmt" "os" + "os/user" "path/filepath" "strings" @@ -241,6 +242,28 @@ to collect a proper IAM role for the AWS CLI operator.`, _ = os.Setenv(awsRegionEnvVar, vipAwsRegion) } } + + // Check if .okta-aws-cli/conifg.yml exists + usr, err := user.Current() + if err == nil { + oktaConfig := filepath.Join(usr.HomeDir, ".okta-aws-cli", "config.yml") + if _, err := os.Stat(oktaConfig); err == nil || !errors.Is(err, os.ErrNotExist) { + viper.AddConfigPath(filepath.Join(usr.HomeDir, ".okta-aws-cli")) + viper.SetConfigName("config.yml") + viper.SetConfigType("yml") + + _ = viper.ReadInConfig() + + // After viper reads in the dotenv file check if AWS_REGION is set + // there. The value will be keyed by lower case name. If it is, set + // AWS_REGION as an ENV VAR if it hasn't already been. + awsRegionEnvVar := "AWS_REGION" + vipAwsRegion := viper.GetString(strings.ToLower(awsRegionEnvVar)) + if vipAwsRegion != "" && os.Getenv(awsRegionEnvVar) == "" { + _ = os.Setenv(awsRegionEnvVar, vipAwsRegion) + } + } + } viper.AutomaticEnv() // bind cli flags From 67f28be0d777d32c0ea96475d0e12831453be3b8 Mon Sep 17 00:00:00 2001 From: Matthew John Date: Thu, 25 Jan 2024 06:57:57 +0000 Subject: [PATCH 3/8] Update to only use .okta-aws-cli config if .env is not present in current directory to avoid mixing configurations --- cmd/root/root.go | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/cmd/root/root.go b/cmd/root/root.go index 535dfb2..60533f1 100644 --- a/cmd/root/root.go +++ b/cmd/root/root.go @@ -241,26 +241,26 @@ to collect a proper IAM role for the AWS CLI operator.`, if vipAwsRegion != "" && os.Getenv(awsRegionEnvVar) == "" { _ = os.Setenv(awsRegionEnvVar, vipAwsRegion) } - } - - // Check if .okta-aws-cli/conifg.yml exists - usr, err := user.Current() - if err == nil { - oktaConfig := filepath.Join(usr.HomeDir, ".okta-aws-cli", "config.yml") - if _, err := os.Stat(oktaConfig); err == nil || !errors.Is(err, os.ErrNotExist) { - viper.AddConfigPath(filepath.Join(usr.HomeDir, ".okta-aws-cli")) - viper.SetConfigName("config.yml") - viper.SetConfigType("yml") + } else { + // Check if .okta-aws-cli/conifg.yml exists + usr, err := user.Current() + if err == nil { + oktaConfig := filepath.Join(usr.HomeDir, ".okta-aws-cli", "config.yml") + if _, err := os.Stat(oktaConfig); err == nil || !errors.Is(err, os.ErrNotExist) { + viper.AddConfigPath(filepath.Join(usr.HomeDir, ".okta-aws-cli")) + viper.SetConfigName("config.yml") + viper.SetConfigType("yml") - _ = viper.ReadInConfig() + _ = viper.ReadInConfig() - // After viper reads in the dotenv file check if AWS_REGION is set - // there. The value will be keyed by lower case name. If it is, set - // AWS_REGION as an ENV VAR if it hasn't already been. - awsRegionEnvVar := "AWS_REGION" - vipAwsRegion := viper.GetString(strings.ToLower(awsRegionEnvVar)) - if vipAwsRegion != "" && os.Getenv(awsRegionEnvVar) == "" { - _ = os.Setenv(awsRegionEnvVar, vipAwsRegion) + // After viper reads in the dotenv file check if AWS_REGION is set + // there. The value will be keyed by lower case name. If it is, set + // AWS_REGION as an ENV VAR if it hasn't already been. + awsRegionEnvVar := "AWS_REGION" + vipAwsRegion := viper.GetString(strings.ToLower(awsRegionEnvVar)) + if vipAwsRegion != "" && os.Getenv(awsRegionEnvVar) == "" { + _ = os.Setenv(awsRegionEnvVar, vipAwsRegion) + } } } } From 201f375a1cef9aa156d36be1cd6a5aa7c015b41f Mon Sep 17 00:00:00 2001 From: Matthew John Date: Thu, 25 Jan 2024 19:01:36 +0000 Subject: [PATCH 4/8] Remove new okta profile and Update new YML config to be keyed by the AWS config. Search for available configurations based on AWS profile dict in config before falling back to the root version of the config. This also allows common configurations (such as Okta domain) to be specified in the config outside of a profile --- cmd/root/root.go | 7 ----- internal/config/config.go | 56 ++++++++++++++++++++------------------- 2 files changed, 29 insertions(+), 34 deletions(-) diff --git a/cmd/root/root.go b/cmd/root/root.go index 60533f1..68df4f9 100644 --- a/cmd/root/root.go +++ b/cmd/root/root.go @@ -102,13 +102,6 @@ func init() { usage: "AWS Profile", envVar: config.ProfileEnvVar, }, - { - name: config.OktaProfileFlag, - short: "t", - value: "", - usage: "Okta Config Profile", - envVar: config.OktaProfileEnvVar, - }, { name: config.FormatFlag, short: "f", diff --git a/internal/config/config.go b/internal/config/config.go index 13eb912..2bb7ac3 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -62,8 +62,8 @@ const ( OrgDomainFlag = "org-domain" // ProfileFlag cli flag const ProfileFlag = "profile" - // OktaProfileFlag cli flag const - OktaProfileFlag = "okta-profile" + // awsProfileFlag cli flag const + awsProfileFlag = "okta-profile" // QRCodeFlag cli flag const QRCodeFlag = "qr-code" // SessionDurationFlag cli flag const @@ -97,8 +97,8 @@ const ( OpenBrowserEnvVar = "OKTA_AWSCLI_OPEN_BROWSER" // ProfileEnvVar env var const ProfileEnvVar = "OKTA_AWSCLI_PROFILE" - // OktaProfileEnvVar env var const - OktaProfileEnvVar = "OKTA_AWSCLI_OKTA_PROFILE" + // awsProfileEnvVar env var const + awsProfileEnvVar = "OKTA_AWSCLI_OKTA_PROFILE" // QRCodeEnvVar env var const QRCodeEnvVar = "OKTA_AWSCLI_QR_CODE" // WriteAWSCredentialsEnvVar env var const @@ -238,35 +238,37 @@ func NewConfig(attrs Attributes) (*Config, error) { return cfg, nil } -func getFlagNameFromProfile(oktaProfile string, flag string) string { - if oktaProfile == "" { - return flag +func getFlagNameFromProfile(awsProfile string, flag string) string { + profileKey := fmt.Sprintf("%s.%s", awsProfile, flag) + if awsProfile != "" && viper.IsSet(profileKey) == true { + return profileKey } - return fmt.Sprintf("%s.%s", oktaProfile, flag) + return flag } func readConfig() (Attributes, error) { - oktaProfile := viper.GetString(OktaProfileFlag) + awsProfile := viper.GetString(ProfileFlag) + fmt.Printf("AWS PRofile: %v\n", awsProfile) attrs := Attributes{ - AWSCredentials: viper.GetString(getFlagNameFromProfile(oktaProfile, AWSCredentialsFlag)), - AWSIAMIdP: viper.GetString(getFlagNameFromProfile(oktaProfile, AWSIAMIdPFlag)), - AWSIAMRole: viper.GetString(getFlagNameFromProfile(oktaProfile, AWSIAMRoleFlag)), - AWSSessionDuration: viper.GetInt64(getFlagNameFromProfile(oktaProfile, SessionDurationFlag)), - Debug: viper.GetBool(getFlagNameFromProfile(oktaProfile, DebugFlag)), - DebugAPICalls: viper.GetBool(getFlagNameFromProfile(oktaProfile, DebugAPICallsFlag)), - DebugConfig: viper.GetBool(getFlagNameFromProfile(oktaProfile, DebugConfigFlag)), - FedAppID: viper.GetString(getFlagNameFromProfile(oktaProfile, AWSAcctFedAppIDFlag)), - Format: viper.GetString(getFlagNameFromProfile(oktaProfile, FormatFlag)), - LegacyAWSVariables: viper.GetBool(getFlagNameFromProfile(oktaProfile, LegacyAWSVariablesFlag)), - ExpiryAWSVariables: viper.GetBool(getFlagNameFromProfile(oktaProfile, ExpiryAWSVariablesFlag)), - CacheAccessToken: viper.GetBool(getFlagNameFromProfile(oktaProfile, CacheAccessTokenFlag)), - OIDCAppID: viper.GetString(getFlagNameFromProfile(oktaProfile, OIDCClientIDFlag)), - OpenBrowser: viper.GetBool(getFlagNameFromProfile(oktaProfile, OpenBrowserFlag)), - OrgDomain: viper.GetString(getFlagNameFromProfile(oktaProfile, OrgDomainFlag)), - Profile: viper.GetString(getFlagNameFromProfile(oktaProfile, ProfileFlag)), - QRCode: viper.GetBool(getFlagNameFromProfile(oktaProfile, QRCodeFlag)), - WriteAWSCredentials: viper.GetBool(getFlagNameFromProfile(oktaProfile, WriteAWSCredentialsFlag)), + AWSCredentials: viper.GetString(getFlagNameFromProfile(awsProfile, AWSCredentialsFlag)), + AWSIAMIdP: viper.GetString(getFlagNameFromProfile(awsProfile, AWSIAMIdPFlag)), + AWSIAMRole: viper.GetString(getFlagNameFromProfile(awsProfile, AWSIAMRoleFlag)), + AWSSessionDuration: viper.GetInt64(getFlagNameFromProfile(awsProfile, SessionDurationFlag)), + Debug: viper.GetBool(getFlagNameFromProfile(awsProfile, DebugFlag)), + DebugAPICalls: viper.GetBool(getFlagNameFromProfile(awsProfile, DebugAPICallsFlag)), + DebugConfig: viper.GetBool(getFlagNameFromProfile(awsProfile, DebugConfigFlag)), + FedAppID: viper.GetString(getFlagNameFromProfile(awsProfile, AWSAcctFedAppIDFlag)), + Format: viper.GetString(getFlagNameFromProfile(awsProfile, FormatFlag)), + LegacyAWSVariables: viper.GetBool(getFlagNameFromProfile(awsProfile, LegacyAWSVariablesFlag)), + ExpiryAWSVariables: viper.GetBool(getFlagNameFromProfile(awsProfile, ExpiryAWSVariablesFlag)), + CacheAccessToken: viper.GetBool(getFlagNameFromProfile(awsProfile, CacheAccessTokenFlag)), + OIDCAppID: viper.GetString(getFlagNameFromProfile(awsProfile, OIDCClientIDFlag)), + OpenBrowser: viper.GetBool(getFlagNameFromProfile(awsProfile, OpenBrowserFlag)), + OrgDomain: viper.GetString(getFlagNameFromProfile(awsProfile, OrgDomainFlag)), + Profile: awsProfile, + QRCode: viper.GetBool(getFlagNameFromProfile(awsProfile, QRCodeFlag)), + WriteAWSCredentials: viper.GetBool(getFlagNameFromProfile(awsProfile, WriteAWSCredentialsFlag)), } if attrs.Format == "" { attrs.Format = EnvVarFormat From 2a47fce72595bdab7c1570bcb3ee3a6e93eb77af Mon Sep 17 00:00:00 2001 From: Matthew John Date: Mon, 12 Feb 2024 15:11:47 +0000 Subject: [PATCH 5/8] Remove unused argument flags --- internal/config/config.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index a5347e2..e25285f 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -88,8 +88,6 @@ const ( KeyIDFlag = "key-id" // ProfileFlag cli flag const ProfileFlag = "profile" - // awsProfileFlag cli flag const - awsProfileFlag = "okta-profile" // QRCodeFlag cli flag const QRCodeFlag = "qr-code" // SessionDurationFlag cli flag const @@ -155,8 +153,6 @@ const ( KeyIDEnvVar = "OKTA_AWSCLI_KEY_ID" // ProfileEnvVar env var const ProfileEnvVar = "OKTA_AWSCLI_PROFILE" - // awsProfileEnvVar env var const - awsProfileEnvVar = "OKTA_AWSCLI_OKTA_PROFILE" // QRCodeEnvVar env var const QRCodeEnvVar = "OKTA_AWSCLI_QR_CODE" // WriteAWSCredentialsEnvVar env var const From 2bdd95a4e3dba3357674913185b6a651106c10e7 Mon Sep 17 00:00:00 2001 From: Matthew John Date: Mon, 12 Feb 2024 15:12:09 +0000 Subject: [PATCH 6/8] Remove debug --- internal/config/config.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/config/config.go b/internal/config/config.go index e25285f..14321bc 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -320,7 +320,6 @@ func getFlagNameFromProfile(awsProfile string, flag string) string { func readConfig() (Attributes, error) { awsProfile := viper.GetString(ProfileFlag) - fmt.Printf("AWS PRofile: %v\n", awsProfile) attrs := Attributes{ AllProfiles: viper.GetBool(getFlagNameFromProfile(awsProfile, AllProfilesFlag)), From b06a59f01be55057a16f79773128b7a8526746e4 Mon Sep 17 00:00:00 2001 From: Mike Mondragon Date: Tue, 13 Feb 2024 15:38:13 -0800 Subject: [PATCH 7/8] Multiple okta-aws-cli configurations in okta.yaml by AWS profile name. Closes #162 Closes #36 --- CHANGELOG.md | 3 + README.md | 36 ++++++++- internal/config/config.go | 119 ++++++++++++++++++++++++++++-- internal/flag/flag.go | 23 ------ internal/webssoauth/webssoauth.go | 6 +- 5 files changed, 154 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d13c5a0..410784a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ TBD ### ENHANCEMENTS +* Multiple okta-aws-cli configurations in `okta.yaml` by AWS profile name. + [#162](https://github.com/okta/okta-aws-cli/pull/162), thanks [@MatthewJohn](https://github.com/MatthewJohn)! + * Explicitly set AWS Region with CLI flag `--aws-region` [#174](https://github.com/okta/okta-aws-cli/pull/174), thanks [@euchen-circle](https://github.com/euchen-circle), [@igaskin](https://github.com/igaskin)! ### BUG FIXES diff --git a/README.md b/README.md index 0bab329..7714221 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,8 @@ format. - [Web command settings](#web-command-settings) - [M2M command settings](#m2m-command-settings) - [Friendly IdP and Role menu labels](#friendly-idp-and-role-menu-labels) + - [Configuration by profile name](#configuration-by-profile-name) + - [Debug okta.yaml](#debug-oktayaml) - [Installation](#installation) - [Recommendations](#recommendations) - [Operation](#operation) @@ -515,7 +517,39 @@ awscli: Ops ``` -#### Debug okta.yaml +### Configuration by profile name + +Multiple `okta-aws-cli` configurations can be saved in the `$HOME/.okta/okta.yaml` +file and are keyed by AWS profile name in the `awscli.profiles` section. This +allows the operator to save many `okta-aws-cli` configurations in the okta.yaml. + +``` +$ okta-aws-cli web --profile staging +``` + +#### Example `$HOME/.okta/okta.yaml` + +```yaml +--- +awscli: + profiles: + staging: + oidc-client-id: "0osabc" + org-domain: "org-stg.okata.com" + aws-iam-idp: "arn:aws:iam::123:saml-provider/MyIdP" + aws-iam-role: "arn:aws:iam::123:role/S3_Read" + write-aws-credentials: true + open-browser: true + production: + oidc-client-id: "0opabc" + org-domain: "org-prd.okata.com" + aws-iam-idp: "arn:aws:iam::456:saml-provider/MyIdP" + aws-iam-role: "arn:aws:iam::456:role/S3_Read" + write-aws-credentials: true + open-browser: true +``` + +## Debug okta.yaml okta-aws-cli has a debug option to check if the okta.yaml file is readable and in valid format. diff --git a/internal/config/config.go b/internal/config/config.go index 93e9920..11549b8 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -17,6 +17,7 @@ package config import ( + "bytes" "fmt" "net/http" "net/url" @@ -41,6 +42,10 @@ const ( // Version app version Version = "2.0.1" + //////////////////////////////////////////////////////////// + // FORMATS + //////////////////////////////////////////////////////////// + // AWSCredentialsFormat format const AWSCredentialsFormat = "aws-credentials" // EnvVarFormat format const @@ -50,6 +55,12 @@ const ( // NoopFormat format const NoopFormat = "noop" + //////////////////////////////////////////////////////////// + // FLAGS + // NOTE: if a new Flag value is added be sure to update the + // OktaYamlConfigProfile struct with that new value. + //////////////////////////////////////////////////////////// + // AllProfilesFlag cli flag const AllProfilesFlag = "all-profiles" // AuthzIDFlag cli flag const @@ -103,6 +114,10 @@ const ( // CacheAccessTokenFlag cli flag const CacheAccessTokenFlag = "cache-access-token" + //////////////////////////////////////////////////////////// + // ENV VARS + //////////////////////////////////////////////////////////// + // AllProfilesEnvVar env var const AllProfilesEnvVar = "OKTA_AWSCLI_ALL_PROFILES" // AuthzIDEnvVar env var const @@ -162,6 +177,10 @@ const ( // WriteAWSCredentialsEnvVar env var const WriteAWSCredentialsEnvVar = "OKTA_AWSCLI_WRITE_AWS_CREDENTIALS" + //////////////////////////////////////////////////////////// + // Other + //////////////////////////////////////////////////////////// + // CannotBeBlankErrMsg error message const CannotBeBlankErrMsg = "cannot be blank" // OrgDomainMsg error message const @@ -176,11 +195,42 @@ const ( // OktaYamlConfig represents config settings from $HOME/.okta/okta.yaml type OktaYamlConfig struct { AWSCLI struct { - IDPS map[string]string `yaml:"idps"` - ROLES map[string]string `yaml:"roles"` + IDPS map[string]string `yaml:"idps"` + ROLES map[string]string `yaml:"roles"` + PROFILES map[string]OktaYamlConfigProfile `yaml:"profiles"` } `yaml:"awscli"` } +// OktaYamlConfigProfile represents config settings that are indexed by profile name +type OktaYamlConfigProfile struct { + AllProfiles string `yaml:"all-profiles"` + AuthzID string `yaml:"authz-id"` + AWSAcctFedAppID string `yaml:"aws-acct-fed-app-id"` + AWSCredentials string `yaml:"aws-credentials"` + AWSIAMIdP string `yaml:"aws-iam-idp"` + AWSIAMRole string `yaml:"aws-iam-role"` + AWSRegion string `yaml:"aws-region"` + CustomScope string `yaml:"custom-scope"` + Debug string `yaml:"debug"` + DebugAPICalls string `yaml:"debug-api-calls"` + Exec string `yaml:"exec"` + Format string `yaml:"format"` + OIDCClientID string `yaml:"oidc-client-id"` + OpenBrowser string `yaml:"open-browser"` + OpenBrowserCommand string `yaml:"open-browser-command"` + OrgDomain string `yaml:"org-domain"` + PrivateKey string `yaml:"private-key"` + PrivateKeyFile string `yaml:"private-key-file"` + KeyID string `yaml:"key-id"` + Profile string `yaml:"profile"` + QRCode string `yaml:"qr-code"` + SessionDuration string `yaml:"session-duration"` + WriteAWSCredentials string `yaml:"write-aws-credentials"` + LegacyAWSVariables string `yaml:"legacy-aws-variables"` + ExpiryAWSVariables string `yaml:"expiry-aws-variables"` + CacheAccessToken string `yaml:"cache-access-token"` +} + // Clock interface to abstract time operations type Clock interface { Now() time.Time @@ -318,12 +368,36 @@ func NewConfig(attrs *Attributes) (*Config, error) { func getFlagNameFromProfile(awsProfile string, flag string) string { profileKey := fmt.Sprintf("%s.%s", awsProfile, flag) if awsProfile != "" && viper.IsSet(profileKey) == true { + // NOTE: If the flag was from a multiple profiles keyed by aws profile + // name i.e. `staging.oidc-client-id`, set the base value to that as + // well, `oidc-client-id`, such that input validation is satisfied. + v := viper.Get(profileKey) + viper.Set(flag, v) + return profileKey } return flag } func readConfig() (Attributes, error) { + // Side loading multiple profiles from okta.yaml file if it exists + if oktaConfig, err := OktaConfig(); err == nil { + profiles := oktaConfig.AWSCLI.PROFILES + viper.SetConfigType("yaml") + yamlData, err := yaml.Marshal(&profiles) + if err != nil { + path, _ := OktaConfigPath() + fmt.Fprintf(os.Stderr, "WARNING: error reading from %q: %+v.\n\n", path, err) + } + if err == nil { + r := bytes.NewReader(yamlData) + err = viper.MergeConfig(r) + if err != nil { + fmt.Fprintf(os.Stderr, "WARNING: error with okta.yaml %+v.\n\n", err) + } + } + } + awsProfile := viper.GetString(ProfileFlag) attrs := Attributes{ @@ -808,14 +882,25 @@ func (c *Config) SetQRCode(qrCode bool) error { return nil } -// OktaConfig returns an Okta YAML Config object representation of $HOME/.okta/okta.yaml -func (c *Config) OktaConfig() (config *OktaYamlConfig, err error) { - homeDir, err := os.UserHomeDir() +// OktaConfigPath returns OS specific path to the okta config file, for example +// $HOME/.okta/okta.yaml +func OktaConfigPath() (path string, err error) { + var homeDir string + homeDir, err = os.UserHomeDir() if err != nil { return } - configPath := filepath.Join(homeDir, DotOkta, OktaYaml) + path = filepath.Join(homeDir, DotOkta, OktaYaml) + return +} + +// OktaConfig returns an Okta YAML Config object representation of $HOME/.okta/okta.yaml +func OktaConfig() (config *OktaYamlConfig, err error) { + configPath, err := OktaConfigPath() + if err != nil { + return + } yamlConfig, err := os.ReadFile(configPath) if err != nil { return @@ -953,6 +1038,28 @@ awscli: fmt.Fprintf(os.Stderr, "okta.yaml \"awscli.roles\" section is a map of %d ARN string keys to friendly string label values\n", len(_roles)) + profiles, ok := _awscli["profiles"] + if !ok { + fmt.Fprintf(os.Stderr, "WARNING: okta.yaml missing \"awscli.profiles\" section\n") + return + } + if profiles == nil { + fmt.Fprintf(os.Stderr, "WARNING: okta.yaml \"awscli.profiles\" section has no values\n") + return + } + + _profiles, ok := profiles.(map[any]any) + if !ok { + fmt.Fprintf(os.Stderr, "WARNING: okta.yaml \"awscli.profiles\" section is not a map of separate config settings keyed by profile name\n") + return + } + if len(_profiles) == 0 { + fmt.Fprintf(os.Stderr, "WARNING: okta.yaml \"awscli.profiles\" section is an empty map of separate config settings keyed by profile name\n") + return + } + + fmt.Fprintf(os.Stderr, "okta.yaml \"awscli.profiles\" section is a map of %d separate config settings keyed by profile name\n", len(_profiles)) + fmt.Fprintf(os.Stderr, "okta.yaml is OK\n") return nil } diff --git a/internal/flag/flag.go b/internal/flag/flag.go index c6d12d1..10a771a 100644 --- a/internal/flag/flag.go +++ b/internal/flag/flag.go @@ -20,7 +20,6 @@ import ( "errors" "fmt" "os" - "os/user" "path/filepath" "strings" @@ -82,28 +81,6 @@ func MakeFlagBindings(cmd *cobra.Command, flags []Flag, persistent bool) { if vipAwsRegion != "" && os.Getenv(awsRegionEnvVar) == "" { _ = os.Setenv(awsRegionEnvVar, vipAwsRegion) } - } else { - // Check if .okta-aws-cli/conifg.yml exists - usr, err := user.Current() - if err == nil { - oktaConfig := filepath.Join(usr.HomeDir, ".okta-aws-cli", "config.yml") - if _, err := os.Stat(oktaConfig); err == nil || !errors.Is(err, os.ErrNotExist) { - viper.AddConfigPath(filepath.Join(usr.HomeDir, ".okta-aws-cli")) - viper.SetConfigName("config.yml") - viper.SetConfigType("yml") - - _ = viper.ReadInConfig() - - // After viper reads in the dotenv file check if AWS_REGION is set - // there. The value will be keyed by lower case name. If it is, set - // AWS_REGION as an ENV VAR if it hasn't already been. - awsRegionEnvVar := "AWS_REGION" - vipAwsRegion := viper.GetString(strings.ToLower(awsRegionEnvVar)) - if vipAwsRegion != "" && os.Getenv(awsRegionEnvVar) == "" { - _ = os.Setenv(awsRegionEnvVar, vipAwsRegion) - } - } - } } viper.AutomaticEnv() diff --git a/internal/webssoauth/webssoauth.go b/internal/webssoauth/webssoauth.go index 9c55a9e..74c8a27 100644 --- a/internal/webssoauth/webssoauth.go +++ b/internal/webssoauth/webssoauth.go @@ -268,7 +268,7 @@ func (w *WebSSOAuthentication) selectFedApp(apps []*okta.Application) (string, e choices := make([]string, len(apps)) var selected string var configIDPs map[string]string - oktaConfig, err := w.config.OktaConfig() + oktaConfig, err := config.OktaConfig() if err == nil { configIDPs = oktaConfig.AWSCLI.IDPS } @@ -463,7 +463,7 @@ func (w *WebSSOAuthentication) choiceFriendlyLabelRole(arn string, roles map[str // promptForRole prompt operator for the AWS Role ARN given a slice of Role ARNs func (w *WebSSOAuthentication) promptForRole(idp string, roleARNs []string) (roleARN string, err error) { - oktaConfig, err := w.config.OktaConfig() + oktaConfig, err := config.OktaConfig() var configRoles map[string]string if err == nil { configRoles = oktaConfig.AWSCLI.ROLES @@ -519,7 +519,7 @@ func (w *WebSSOAuthentication) promptForRole(idp string, roleARNs []string) (rol // to pretty print out the IdP name again. func (w *WebSSOAuthentication) promptForIDP(idpARNs []string) (idpARN string, err error) { var configIDPs map[string]string - if oktaConfig, cErr := w.config.OktaConfig(); cErr == nil { + if oktaConfig, cErr := config.OktaConfig(); cErr == nil { configIDPs = oktaConfig.AWSCLI.IDPS } From 022d17c305f67ad55b8203378303ba52dc262d19 Mon Sep 17 00:00:00 2001 From: Mike Mondragon Date: Tue, 13 Feb 2024 15:47:00 -0800 Subject: [PATCH 8/8] QC --- internal/config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/config/config.go b/internal/config/config.go index 11549b8..b7a70f4 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -367,7 +367,7 @@ func NewConfig(attrs *Attributes) (*Config, error) { func getFlagNameFromProfile(awsProfile string, flag string) string { profileKey := fmt.Sprintf("%s.%s", awsProfile, flag) - if awsProfile != "" && viper.IsSet(profileKey) == true { + if awsProfile != "" && viper.IsSet(profileKey) { // NOTE: If the flag was from a multiple profiles keyed by aws profile // name i.e. `staging.oidc-client-id`, set the base value to that as // well, `oidc-client-id`, such that input validation is satisfied.