Skip to content

Commit 3c5a1d4

Browse files
authored
Merge pull request #175 from okta/pr_162_MatthewJohn
Bringing in MatthewJohn's PR #162 - multiple config profiles in okta.yaml
2 parents d6f4461 + 022d17c commit 3c5a1d4

File tree

5 files changed

+191
-36
lines changed

5 files changed

+191
-36
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ TBD
66

77
### ENHANCEMENTS
88

9+
* Multiple okta-aws-cli configurations in `okta.yaml` by AWS profile name.
10+
[#162](https://github.com/okta/okta-aws-cli/pull/162), thanks [@MatthewJohn](https://github.com/MatthewJohn)!
11+
912
* 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)!
1013

1114
### BUG FIXES

README.md

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ format.
6161
- [Web command settings](#web-command-settings)
6262
- [M2M command settings](#m2m-command-settings)
6363
- [Friendly IdP and Role menu labels](#friendly-idp-and-role-menu-labels)
64+
- [Configuration by profile name](#configuration-by-profile-name)
65+
- [Debug okta.yaml](#debug-oktayaml)
6466
- [Installation](#installation)
6567
- [Recommendations](#recommendations)
6668
- [Operation](#operation)
@@ -515,7 +517,39 @@ awscli:
515517
Ops
516518
```
517519

518-
#### Debug okta.yaml
520+
### Configuration by profile name
521+
522+
Multiple `okta-aws-cli` configurations can be saved in the `$HOME/.okta/okta.yaml`
523+
file and are keyed by AWS profile name in the `awscli.profiles` section. This
524+
allows the operator to save many `okta-aws-cli` configurations in the okta.yaml.
525+
526+
```
527+
$ okta-aws-cli web --profile staging
528+
```
529+
530+
#### Example `$HOME/.okta/okta.yaml`
531+
532+
```yaml
533+
---
534+
awscli:
535+
profiles:
536+
staging:
537+
oidc-client-id: "0osabc"
538+
org-domain: "org-stg.okata.com"
539+
aws-iam-idp: "arn:aws:iam::123:saml-provider/MyIdP"
540+
aws-iam-role: "arn:aws:iam::123:role/S3_Read"
541+
write-aws-credentials: true
542+
open-browser: true
543+
production:
544+
oidc-client-id: "0opabc"
545+
org-domain: "org-prd.okata.com"
546+
aws-iam-idp: "arn:aws:iam::456:saml-provider/MyIdP"
547+
aws-iam-role: "arn:aws:iam::456:role/S3_Read"
548+
write-aws-credentials: true
549+
open-browser: true
550+
```
551+
552+
## Debug okta.yaml
519553
520554
okta-aws-cli has a debug option to check if the okta.yaml file is readable and
521555
in valid format.

internal/config/config.go

Lines changed: 149 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package config
1818

1919
import (
20+
"bytes"
2021
"fmt"
2122
"net/http"
2223
"net/url"
@@ -41,6 +42,10 @@ const (
4142
// Version app version
4243
Version = "2.0.1"
4344

45+
////////////////////////////////////////////////////////////
46+
// FORMATS
47+
////////////////////////////////////////////////////////////
48+
4449
// AWSCredentialsFormat format const
4550
AWSCredentialsFormat = "aws-credentials"
4651
// EnvVarFormat format const
@@ -50,6 +55,12 @@ const (
5055
// NoopFormat format const
5156
NoopFormat = "noop"
5257

58+
////////////////////////////////////////////////////////////
59+
// FLAGS
60+
// NOTE: if a new Flag value is added be sure to update the
61+
// OktaYamlConfigProfile struct with that new value.
62+
////////////////////////////////////////////////////////////
63+
5364
// AllProfilesFlag cli flag const
5465
AllProfilesFlag = "all-profiles"
5566
// AuthzIDFlag cli flag const
@@ -103,6 +114,10 @@ const (
103114
// CacheAccessTokenFlag cli flag const
104115
CacheAccessTokenFlag = "cache-access-token"
105116

117+
////////////////////////////////////////////////////////////
118+
// ENV VARS
119+
////////////////////////////////////////////////////////////
120+
106121
// AllProfilesEnvVar env var const
107122
AllProfilesEnvVar = "OKTA_AWSCLI_ALL_PROFILES"
108123
// AuthzIDEnvVar env var const
@@ -162,6 +177,10 @@ const (
162177
// WriteAWSCredentialsEnvVar env var const
163178
WriteAWSCredentialsEnvVar = "OKTA_AWSCLI_WRITE_AWS_CREDENTIALS"
164179

180+
////////////////////////////////////////////////////////////
181+
// Other
182+
////////////////////////////////////////////////////////////
183+
165184
// CannotBeBlankErrMsg error message const
166185
CannotBeBlankErrMsg = "cannot be blank"
167186
// OrgDomainMsg error message const
@@ -176,11 +195,42 @@ const (
176195
// OktaYamlConfig represents config settings from $HOME/.okta/okta.yaml
177196
type OktaYamlConfig struct {
178197
AWSCLI struct {
179-
IDPS map[string]string `yaml:"idps"`
180-
ROLES map[string]string `yaml:"roles"`
198+
IDPS map[string]string `yaml:"idps"`
199+
ROLES map[string]string `yaml:"roles"`
200+
PROFILES map[string]OktaYamlConfigProfile `yaml:"profiles"`
181201
} `yaml:"awscli"`
182202
}
183203

204+
// OktaYamlConfigProfile represents config settings that are indexed by profile name
205+
type OktaYamlConfigProfile struct {
206+
AllProfiles string `yaml:"all-profiles"`
207+
AuthzID string `yaml:"authz-id"`
208+
AWSAcctFedAppID string `yaml:"aws-acct-fed-app-id"`
209+
AWSCredentials string `yaml:"aws-credentials"`
210+
AWSIAMIdP string `yaml:"aws-iam-idp"`
211+
AWSIAMRole string `yaml:"aws-iam-role"`
212+
AWSRegion string `yaml:"aws-region"`
213+
CustomScope string `yaml:"custom-scope"`
214+
Debug string `yaml:"debug"`
215+
DebugAPICalls string `yaml:"debug-api-calls"`
216+
Exec string `yaml:"exec"`
217+
Format string `yaml:"format"`
218+
OIDCClientID string `yaml:"oidc-client-id"`
219+
OpenBrowser string `yaml:"open-browser"`
220+
OpenBrowserCommand string `yaml:"open-browser-command"`
221+
OrgDomain string `yaml:"org-domain"`
222+
PrivateKey string `yaml:"private-key"`
223+
PrivateKeyFile string `yaml:"private-key-file"`
224+
KeyID string `yaml:"key-id"`
225+
Profile string `yaml:"profile"`
226+
QRCode string `yaml:"qr-code"`
227+
SessionDuration string `yaml:"session-duration"`
228+
WriteAWSCredentials string `yaml:"write-aws-credentials"`
229+
LegacyAWSVariables string `yaml:"legacy-aws-variables"`
230+
ExpiryAWSVariables string `yaml:"expiry-aws-variables"`
231+
CacheAccessToken string `yaml:"cache-access-token"`
232+
}
233+
184234
// Clock interface to abstract time operations
185235
type Clock interface {
186236
Now() time.Time
@@ -315,34 +365,68 @@ func NewConfig(attrs *Attributes) (*Config, error) {
315365
return cfg, nil
316366
}
317367

368+
func getFlagNameFromProfile(awsProfile string, flag string) string {
369+
profileKey := fmt.Sprintf("%s.%s", awsProfile, flag)
370+
if awsProfile != "" && viper.IsSet(profileKey) {
371+
// NOTE: If the flag was from a multiple profiles keyed by aws profile
372+
// name i.e. `staging.oidc-client-id`, set the base value to that as
373+
// well, `oidc-client-id`, such that input validation is satisfied.
374+
v := viper.Get(profileKey)
375+
viper.Set(flag, v)
376+
377+
return profileKey
378+
}
379+
return flag
380+
}
381+
318382
func readConfig() (Attributes, error) {
383+
// Side loading multiple profiles from okta.yaml file if it exists
384+
if oktaConfig, err := OktaConfig(); err == nil {
385+
profiles := oktaConfig.AWSCLI.PROFILES
386+
viper.SetConfigType("yaml")
387+
yamlData, err := yaml.Marshal(&profiles)
388+
if err != nil {
389+
path, _ := OktaConfigPath()
390+
fmt.Fprintf(os.Stderr, "WARNING: error reading from %q: %+v.\n\n", path, err)
391+
}
392+
if err == nil {
393+
r := bytes.NewReader(yamlData)
394+
err = viper.MergeConfig(r)
395+
if err != nil {
396+
fmt.Fprintf(os.Stderr, "WARNING: error with okta.yaml %+v.\n\n", err)
397+
}
398+
}
399+
}
400+
401+
awsProfile := viper.GetString(ProfileFlag)
402+
319403
attrs := Attributes{
320-
AllProfiles: viper.GetBool(AllProfilesFlag),
321-
AuthzID: viper.GetString(AuthzIDFlag),
322-
AWSCredentials: viper.GetString(AWSCredentialsFlag),
323-
AWSIAMIdP: viper.GetString(AWSIAMIdPFlag),
324-
AWSIAMRole: viper.GetString(AWSIAMRoleFlag),
325-
AWSSessionDuration: viper.GetInt64(SessionDurationFlag),
326-
AWSRegion: viper.GetString(AWSRegionFlag),
327-
CustomScope: viper.GetString(CustomScopeFlag),
328-
Debug: viper.GetBool(DebugFlag),
329-
DebugAPICalls: viper.GetBool(DebugAPICallsFlag),
330-
Exec: viper.GetBool(ExecFlag),
331-
FedAppID: viper.GetString(AWSAcctFedAppIDFlag),
332-
Format: viper.GetString(FormatFlag),
333-
LegacyAWSVariables: viper.GetBool(LegacyAWSVariablesFlag),
334-
ExpiryAWSVariables: viper.GetBool(ExpiryAWSVariablesFlag),
335-
CacheAccessToken: viper.GetBool(CacheAccessTokenFlag),
336-
OIDCAppID: viper.GetString(OIDCClientIDFlag),
337-
OpenBrowser: viper.GetBool(OpenBrowserFlag),
338-
OpenBrowserCommand: viper.GetString(OpenBrowserCommandFlag),
339-
OrgDomain: viper.GetString(OrgDomainFlag),
340-
PrivateKey: viper.GetString(PrivateKeyFlag),
341-
PrivateKeyFile: viper.GetString(PrivateKeyFileFlag),
342-
KeyID: viper.GetString(KeyIDFlag),
343-
Profile: viper.GetString(ProfileFlag),
344-
QRCode: viper.GetBool(QRCodeFlag),
345-
WriteAWSCredentials: viper.GetBool(WriteAWSCredentialsFlag),
404+
AllProfiles: viper.GetBool(getFlagNameFromProfile(awsProfile, AllProfilesFlag)),
405+
AuthzID: viper.GetString(getFlagNameFromProfile(awsProfile, AuthzIDFlag)),
406+
AWSCredentials: viper.GetString(getFlagNameFromProfile(awsProfile, AWSCredentialsFlag)),
407+
AWSIAMIdP: viper.GetString(getFlagNameFromProfile(awsProfile, AWSIAMIdPFlag)),
408+
AWSIAMRole: viper.GetString(getFlagNameFromProfile(awsProfile, AWSIAMRoleFlag)),
409+
AWSRegion: viper.GetString(getFlagNameFromProfile(awsProfile, AWSRegionFlag)),
410+
AWSSessionDuration: viper.GetInt64(getFlagNameFromProfile(awsProfile, SessionDurationFlag)),
411+
CustomScope: viper.GetString(getFlagNameFromProfile(awsProfile, CustomScopeFlag)),
412+
Debug: viper.GetBool(getFlagNameFromProfile(awsProfile, DebugFlag)),
413+
DebugAPICalls: viper.GetBool(getFlagNameFromProfile(awsProfile, DebugAPICallsFlag)),
414+
Exec: viper.GetBool(getFlagNameFromProfile(awsProfile, ExecFlag)),
415+
FedAppID: viper.GetString(getFlagNameFromProfile(awsProfile, AWSAcctFedAppIDFlag)),
416+
Format: viper.GetString(getFlagNameFromProfile(awsProfile, FormatFlag)),
417+
LegacyAWSVariables: viper.GetBool(getFlagNameFromProfile(awsProfile, LegacyAWSVariablesFlag)),
418+
ExpiryAWSVariables: viper.GetBool(getFlagNameFromProfile(awsProfile, ExpiryAWSVariablesFlag)),
419+
CacheAccessToken: viper.GetBool(getFlagNameFromProfile(awsProfile, CacheAccessTokenFlag)),
420+
OIDCAppID: viper.GetString(getFlagNameFromProfile(awsProfile, OIDCClientIDFlag)),
421+
OpenBrowser: viper.GetBool(getFlagNameFromProfile(awsProfile, OpenBrowserFlag)),
422+
OpenBrowserCommand: viper.GetString(getFlagNameFromProfile(awsProfile, OpenBrowserCommandFlag)),
423+
OrgDomain: viper.GetString(getFlagNameFromProfile(awsProfile, OrgDomainFlag)),
424+
PrivateKey: viper.GetString(getFlagNameFromProfile(awsProfile, PrivateKeyFlag)),
425+
PrivateKeyFile: viper.GetString(getFlagNameFromProfile(awsProfile, PrivateKeyFileFlag)),
426+
KeyID: viper.GetString(getFlagNameFromProfile(awsProfile, KeyIDFlag)),
427+
Profile: viper.GetString(getFlagNameFromProfile(awsProfile, ProfileFlag)),
428+
QRCode: viper.GetBool(getFlagNameFromProfile(awsProfile, QRCodeFlag)),
429+
WriteAWSCredentials: viper.GetBool(getFlagNameFromProfile(awsProfile, WriteAWSCredentialsFlag)),
346430
}
347431
if attrs.Format == "" {
348432
attrs.Format = EnvVarFormat
@@ -798,14 +882,25 @@ func (c *Config) SetQRCode(qrCode bool) error {
798882
return nil
799883
}
800884

801-
// OktaConfig returns an Okta YAML Config object representation of $HOME/.okta/okta.yaml
802-
func (c *Config) OktaConfig() (config *OktaYamlConfig, err error) {
803-
homeDir, err := os.UserHomeDir()
885+
// OktaConfigPath returns OS specific path to the okta config file, for example
886+
// $HOME/.okta/okta.yaml
887+
func OktaConfigPath() (path string, err error) {
888+
var homeDir string
889+
homeDir, err = os.UserHomeDir()
804890
if err != nil {
805891
return
806892
}
807893

808-
configPath := filepath.Join(homeDir, DotOkta, OktaYaml)
894+
path = filepath.Join(homeDir, DotOkta, OktaYaml)
895+
return
896+
}
897+
898+
// OktaConfig returns an Okta YAML Config object representation of $HOME/.okta/okta.yaml
899+
func OktaConfig() (config *OktaYamlConfig, err error) {
900+
configPath, err := OktaConfigPath()
901+
if err != nil {
902+
return
903+
}
809904
yamlConfig, err := os.ReadFile(configPath)
810905
if err != nil {
811906
return
@@ -943,6 +1038,28 @@ awscli:
9431038

9441039
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))
9451040

1041+
profiles, ok := _awscli["profiles"]
1042+
if !ok {
1043+
fmt.Fprintf(os.Stderr, "WARNING: okta.yaml missing \"awscli.profiles\" section\n")
1044+
return
1045+
}
1046+
if profiles == nil {
1047+
fmt.Fprintf(os.Stderr, "WARNING: okta.yaml \"awscli.profiles\" section has no values\n")
1048+
return
1049+
}
1050+
1051+
_profiles, ok := profiles.(map[any]any)
1052+
if !ok {
1053+
fmt.Fprintf(os.Stderr, "WARNING: okta.yaml \"awscli.profiles\" section is not a map of separate config settings keyed by profile name\n")
1054+
return
1055+
}
1056+
if len(_profiles) == 0 {
1057+
fmt.Fprintf(os.Stderr, "WARNING: okta.yaml \"awscli.profiles\" section is an empty map of separate config settings keyed by profile name\n")
1058+
return
1059+
}
1060+
1061+
fmt.Fprintf(os.Stderr, "okta.yaml \"awscli.profiles\" section is a map of %d separate config settings keyed by profile name\n", len(_profiles))
1062+
9461063
fmt.Fprintf(os.Stderr, "okta.yaml is OK\n")
9471064
return nil
9481065
}

internal/flag/flag.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ func MakeFlagBindings(cmd *cobra.Command, flags []Flag, persistent bool) {
8282
_ = os.Setenv(awsRegionEnvVar, vipAwsRegion)
8383
}
8484
}
85+
8586
viper.AutomaticEnv()
8687

8788
// bind cli flags

internal/webssoauth/webssoauth.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ func (w *WebSSOAuthentication) selectFedApp(apps []*okta.Application) (string, e
268268
choices := make([]string, len(apps))
269269
var selected string
270270
var configIDPs map[string]string
271-
oktaConfig, err := w.config.OktaConfig()
271+
oktaConfig, err := config.OktaConfig()
272272
if err == nil {
273273
configIDPs = oktaConfig.AWSCLI.IDPS
274274
}
@@ -463,7 +463,7 @@ func (w *WebSSOAuthentication) choiceFriendlyLabelRole(arn string, roles map[str
463463

464464
// promptForRole prompt operator for the AWS Role ARN given a slice of Role ARNs
465465
func (w *WebSSOAuthentication) promptForRole(idp string, roleARNs []string) (roleARN string, err error) {
466-
oktaConfig, err := w.config.OktaConfig()
466+
oktaConfig, err := config.OktaConfig()
467467
var configRoles map[string]string
468468
if err == nil {
469469
configRoles = oktaConfig.AWSCLI.ROLES
@@ -519,7 +519,7 @@ func (w *WebSSOAuthentication) promptForRole(idp string, roleARNs []string) (rol
519519
// to pretty print out the IdP name again.
520520
func (w *WebSSOAuthentication) promptForIDP(idpARNs []string) (idpARN string, err error) {
521521
var configIDPs map[string]string
522-
if oktaConfig, cErr := w.config.OktaConfig(); cErr == nil {
522+
if oktaConfig, cErr := config.OktaConfig(); cErr == nil {
523523
configIDPs = oktaConfig.AWSCLI.IDPS
524524
}
525525

0 commit comments

Comments
 (0)