Skip to content

Commit

Permalink
Update shared config logic to resolve sso section (#4868)
Browse files Browse the repository at this point in the history
* Merge logic of resolving sso section in shared config file

* Modify and Merge shared config unit test case

* Modify and Merge logic of shared config loaded from files

---------

Co-authored-by: Tianyi Wang <[email protected]>
  • Loading branch information
wty-Bryant and Tianyi Wang authored Jun 6, 2023
1 parent 29a8523 commit 3216ed5
Show file tree
Hide file tree
Showing 4 changed files with 183 additions and 34 deletions.
2 changes: 1 addition & 1 deletion aws/session/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const (

// ErrSharedConfigSourceCollision will be returned if a section contains both
// source_profile and credential_source
var ErrSharedConfigSourceCollision = awserr.New(ErrCodeSharedConfig, "only one credential type may be specified per profile: source profile, credential source, credential process, web identity token, or sso", nil)
var ErrSharedConfigSourceCollision = awserr.New(ErrCodeSharedConfig, "only one credential type may be specified per profile: source profile, credential source, credential process, web identity token", nil)

// ErrSharedConfigECSContainerEnvVarEmpty will be returned if the environment
// variables are empty and Environment was set as the credential source
Expand Down
168 changes: 135 additions & 33 deletions aws/session/shared_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ const (
roleSessionNameKey = `role_session_name` // optional
roleDurationSecondsKey = "duration_seconds" // optional

// Prefix to be used for SSO sections. These are supposed to only exist in
// the shared config file, not the credentials file.
ssoSectionPrefix = `sso-session `

// AWS Single Sign-On (AWS SSO) group
ssoSessionNameKey = "sso_session"

// AWS Single Sign-On (AWS SSO) group
ssoAccountIDKey = "sso_account_id"
ssoRegionKey = "sso_region"
Expand Down Expand Up @@ -99,6 +106,10 @@ type sharedConfig struct {
CredentialProcess string
WebIdentityTokenFile string

// SSO session options
SSOSessionName string
SSOSession *ssoSession

SSOAccountID string
SSORegion string
SSORoleName string
Expand Down Expand Up @@ -186,6 +197,20 @@ type sharedConfigFile struct {
IniData ini.Sections
}

// SSOSession provides the shared configuration parameters of the sso-session
// section.
type ssoSession struct {
Name string
SSORegion string
SSOStartURL string
}

func (s *ssoSession) setFromIniSection(section ini.Section) {
updateString(&s.Name, section, ssoSessionNameKey)
updateString(&s.SSORegion, section, ssoRegionKey)
updateString(&s.SSOStartURL, section, ssoStartURL)
}

// loadSharedConfig retrieves the configuration from the list of files using
// the profile provided. The order the files are listed will determine
// precedence. Values in subsequent files will overwrite values defined in
Expand Down Expand Up @@ -266,13 +291,13 @@ func (cfg *sharedConfig) setFromIniFiles(profiles map[string]struct{}, profile s
// profile only have credential provider options.
cfg.clearAssumeRoleOptions()
} else {
// First time a profile has been seen, It must either be a assume role
// credentials, or SSO. Assert if the credential type requires a role ARN,
// the ARN is also set, or validate that the SSO configuration is complete.
// First time a profile has been seen. Assert if the credential type
// requires a role ARN, the ARN is also set
if err := cfg.validateCredentialsConfig(profile); err != nil {
return err
}
}

profiles[profile] = struct{}{}

if err := cfg.validateCredentialType(); err != nil {
Expand Down Expand Up @@ -308,6 +333,30 @@ func (cfg *sharedConfig) setFromIniFiles(profiles map[string]struct{}, profile s
cfg.SourceProfile = srcCfg
}

// If the profile contains an SSO session parameter, the session MUST exist
// as a section in the config file. Load the SSO session using the name
// provided. If the session section is not found or incomplete an error
// will be returned.
if cfg.hasSSOTokenProviderConfiguration() {
skippedFiles = 0
for _, f := range files {
section, ok := f.IniData.GetSection(fmt.Sprintf(ssoSectionPrefix + strings.TrimSpace(cfg.SSOSessionName)))
if ok {
var ssoSession ssoSession
ssoSession.setFromIniSection(section)
ssoSession.Name = cfg.SSOSessionName
cfg.SSOSession = &ssoSession
break
}
skippedFiles++
}
if skippedFiles == len(files) {
// If all files were skipped because the sso session section is not found, return
// the sso section not found error.
return fmt.Errorf("failed to find SSO session section, %v", cfg.SSOSessionName)
}
}

return nil
}

Expand Down Expand Up @@ -363,6 +412,10 @@ func (cfg *sharedConfig) setFromIniFile(profile string, file sharedConfigFile, e
cfg.S3UsEast1RegionalEndpoint = sre
}

// AWS Single Sign-On (AWS SSO)
// SSO session options
updateString(&cfg.SSOSessionName, section, ssoSessionNameKey)

// AWS Single Sign-On (AWS SSO)
updateString(&cfg.SSOAccountID, section, ssoAccountIDKey)
updateString(&cfg.SSORegion, section, ssoRegionKey)
Expand Down Expand Up @@ -461,32 +514,20 @@ func (cfg *sharedConfig) validateCredentialType() error {
}

func (cfg *sharedConfig) validateSSOConfiguration() error {
if !cfg.hasSSOConfiguration() {
if cfg.hasSSOTokenProviderConfiguration() {
err := cfg.validateSSOTokenProviderConfiguration()
if err != nil {
return err
}
return nil
}

var missing []string
if len(cfg.SSOAccountID) == 0 {
missing = append(missing, ssoAccountIDKey)
}

if len(cfg.SSORegion) == 0 {
missing = append(missing, ssoRegionKey)
}

if len(cfg.SSORoleName) == 0 {
missing = append(missing, ssoRoleNameKey)
}

if len(cfg.SSOStartURL) == 0 {
missing = append(missing, ssoStartURL)
}

if len(missing) > 0 {
return fmt.Errorf("profile %q is configured to use SSO but is missing required configuration: %s",
cfg.Profile, strings.Join(missing, ", "))
if cfg.hasLegacySSOConfiguration() {
err := cfg.validateLegacySSOConfiguration()
if err != nil {
return err
}
}

return nil
}

Expand Down Expand Up @@ -525,15 +566,76 @@ func (cfg *sharedConfig) clearAssumeRoleOptions() {
}

func (cfg *sharedConfig) hasSSOConfiguration() bool {
switch {
case len(cfg.SSOAccountID) != 0:
case len(cfg.SSORegion) != 0:
case len(cfg.SSORoleName) != 0:
case len(cfg.SSOStartURL) != 0:
default:
return false
return cfg.hasSSOTokenProviderConfiguration() || cfg.hasLegacySSOConfiguration()
}

func (c *sharedConfig) hasSSOTokenProviderConfiguration() bool {
return len(c.SSOSessionName) > 0
}

func (c *sharedConfig) hasLegacySSOConfiguration() bool {
return len(c.SSORegion) > 0 || len(c.SSOAccountID) > 0 || len(c.SSOStartURL) > 0 || len(c.SSORoleName) > 0
}

func (c *sharedConfig) validateSSOTokenProviderConfiguration() error {
var missing []string

if len(c.SSOSessionName) == 0 {
missing = append(missing, ssoSessionNameKey)
}
return true

if c.SSOSession == nil {
missing = append(missing, ssoSectionPrefix)
} else {
if len(c.SSOSession.SSORegion) == 0 {
missing = append(missing, ssoRegionKey)
}

if len(c.SSOSession.SSOStartURL) == 0 {
missing = append(missing, ssoStartURL)
}
}

if len(missing) > 0 {
return fmt.Errorf("profile %q is configured to use SSO but is missing required configuration: %s",
c.Profile, strings.Join(missing, ", "))
}

if len(c.SSORegion) > 0 && c.SSORegion != c.SSOSession.SSORegion {
return fmt.Errorf("%s in profile %q must match %s in %s", ssoRegionKey, c.Profile, ssoRegionKey, ssoSectionPrefix)
}

if len(c.SSOStartURL) > 0 && c.SSOStartURL != c.SSOSession.SSOStartURL {
return fmt.Errorf("%s in profile %q must match %s in %s", ssoStartURL, c.Profile, ssoStartURL, ssoSectionPrefix)
}

return nil
}

func (c *sharedConfig) validateLegacySSOConfiguration() error {
var missing []string

if len(c.SSORegion) == 0 {
missing = append(missing, ssoRegionKey)
}

if len(c.SSOStartURL) == 0 {
missing = append(missing, ssoStartURL)
}

if len(c.SSOAccountID) == 0 {
missing = append(missing, ssoAccountIDKey)
}

if len(c.SSORoleName) == 0 {
missing = append(missing, ssoRoleNameKey)
}

if len(missing) > 0 {
return fmt.Errorf("profile %q is configured to use SSO but is missing required configuration: %s",
c.Profile, strings.Join(missing, ", "))
}
return nil
}

func oneOrNone(bs ...bool) bool {
Expand Down
30 changes: 30 additions & 0 deletions aws/session/shared_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,27 @@ func TestLoadSharedConfig(t *testing.T) {
UseFIPSEndpoint: endpoints.FIPSEndpointStateDisabled,
},
},
{
Filenames: []string{testConfigFilename},
Profile: "sso-session-success",
Expected: sharedConfig{
Profile: "sso-session-success",
Region: "us-east-1",
SSOAccountID: "123456789012",
SSORoleName: "testRole",
SSOSessionName: "sso-session-success-dev",
SSOSession: &ssoSession{
Name: "sso-session-success-dev",
SSORegion: "us-east-1",
SSOStartURL: "https://d-123456789a.awsapps.com/start",
},
},
},
{
Filenames: []string{testConfigFilename},
Profile: "sso-session-not-exist",
Err: fmt.Errorf("failed to find SSO session section, sso-session-lost"),
},
}

for i, c := range cases {
Expand Down Expand Up @@ -507,6 +528,15 @@ func TestLoadSharedConfigFromFile(t *testing.T) {
S3UseARNRegion: true,
},
},
{
Profile: "sso-session-success",
Expected: sharedConfig{
Region: "us-east-1",
SSOAccountID: "123456789012",
SSORoleName: "testRole",
SSOSessionName: "sso-session-success-dev",
},
},
}

for i, c := range cases {
Expand Down
17 changes: 17 additions & 0 deletions aws/session/testdata/shared_config
Original file line number Diff line number Diff line change
Expand Up @@ -187,3 +187,20 @@ use_fips_endpoint=False
[profile UseFIPSEndpointInvalid]
region = "us-west-2"
use_fips_endpoint=invalid

[profile sso-session-success]
region = us-east-1
sso_session = sso-session-success-dev
sso_account_id = 123456789012
sso_role_name = testRole

[sso-session sso-session-success-dev]
sso_region = us-east-1
sso_start_url = https://d-123456789a.awsapps.com/start
sso_registration_scopes = sso:account:access

[profile sso-session-not-exist]
region = us-east-1
sso_session = sso-session-lost
sso_account_id = 123456789012
sso_role_name = testRole

0 comments on commit 3216ed5

Please sign in to comment.