Skip to content

Commit

Permalink
Use AWS SDK to load sso-session sections enable multiple sso users (#530
Browse files Browse the repository at this point in the history
)

* Add granted_sso_user key to config to allow multiple sso users

* use SDK to load sso session sections

* remove unused
  • Loading branch information
JoshuaWilkes authored Nov 6, 2023
1 parent 19c2d06 commit 98f3908
Show file tree
Hide file tree
Showing 4 changed files with 13 additions and 131 deletions.
5 changes: 2 additions & 3 deletions pkg/cfaws/assumer_aws_sso.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,9 +170,8 @@ func (c *Profile) SSOLogin(ctx context.Context, configOpts ConfigOpts) (aws.Cred
if len(c.Parents) > 0 {
rootProfile = c.Parents[0]
}

ssoTokenKey := rootProfile.AWSConfig.SSOStartURL

ssoTokenKey := rootProfile.AWSConfig.SSOStartURL + c.AWSConfig.SSOSessionName
// if the profile has an sso user configured then suffix the sso token storage key to ensure unique logins
secureSSOTokenStorage := securestorage.NewSecureSSOTokenStorage()
cachedToken := secureSSOTokenStorage.GetValidSSOToken(ssoTokenKey)
var accessToken *string
Expand Down
19 changes: 10 additions & 9 deletions pkg/cfaws/granted_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,11 @@ func ParseGrantedSSOProfile(ctx context.Context, profile *Profile) (*config.Shar
cfg.SSOAccountID = item.Value()
item, err = profile.RawConfig.GetKey("granted_sso_region")
if err != nil {
if profile.SSOSession != nil && profile.SSOSession.SSORegion != "" {
cfg.SSORegion = profile.SSOSession.SSORegion
} else {
// the region may have been populated by an sso session section by the aws SDK
if cfg.SSOSession.SSORegion == "" {
return nil, err
} else {
cfg.SSORegion = cfg.SSOSession.SSORegion
}
} else {
cfg.SSORegion = item.Value()
Expand All @@ -45,10 +46,10 @@ func ParseGrantedSSOProfile(ctx context.Context, profile *Profile) (*config.Shar

item, err = profile.RawConfig.GetKey("granted_sso_start_url")
if err != nil {
if profile.SSOSession != nil && profile.SSOSession.SSORegion != "" {
cfg.SSOStartURL = profile.SSOSession.SSOStartURL
} else {
if cfg.SSOSession.SSOStartURL == "" {
return nil, err
} else {
cfg.SSOStartURL = cfg.SSOSession.SSOStartURL
}
} else {
cfg.SSOStartURL = item.Value()
Expand Down Expand Up @@ -101,11 +102,11 @@ func IsValidGrantedProfile(profile *Profile) error {
return fmt.Errorf("invalid aws config for granted login. '%s' field must be provided", value)
}
}
if profile.SSOSession != nil {
if profile.SSOSession.SSORegion == "" && !profile.RawConfig.HasKey("granted_sso_region") {
if profile.AWSConfig.SSOSession != nil {
if profile.AWSConfig.SSOSession.SSORegion == "" && !profile.RawConfig.HasKey("granted_sso_region") {
return fmt.Errorf("invalid aws config for granted login. '%s' field must be provided", "granted_sso_region")
}
if profile.SSOSession.SSOStartURL == "" && !profile.RawConfig.HasKey("granted_sso_start_url") {
if profile.AWSConfig.SSOSession.SSOStartURL == "" && !profile.RawConfig.HasKey("granted_sso_start_url") {
return fmt.Errorf("invalid aws config for granted login. '%s' field must be provided", "granted_sso_start_url")
}
}
Expand Down
68 changes: 0 additions & 68 deletions pkg/cfaws/granted_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@ package cfaws

import (
"testing"

"github.com/stretchr/testify/assert"
"gopkg.in/ini.v1"
)

func TestValidateCredentialProcess(t *testing.T) {
Expand Down Expand Up @@ -45,68 +42,3 @@ func TestValidateCredentialProcess(t *testing.T) {
})
}
}

// tests support for aws sso-session configuration
func TestSSOSessionSupport(t *testing.T) {
tests := []struct {
name string
file string
profileName string
wantSSOSession *SSOSession
}{
{
name: "valid argument with correct profile name",
file: `[profile testing]
sso_session = testing-sso
sso_account_id = 12345678912
sso_role_name = Test
region = ap-southeast-2
[sso-session testing-sso]
sso_start_url = https://d-12345678910.awsapps.com/start
sso_region = ap-southeast-2
`,
profileName: "testing",
wantSSOSession: &SSOSession{
SSORegion: "ap-southeast-2",
SSOStartURL: "https://d-12345678910.awsapps.com/start",
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
l := loader{fileString: tt.file}
profiles, err := loadProfiles(l, nooploader{})
if err != nil {
t.Fatal(err)
}
profile, err := profiles.Profile(tt.profileName)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, tt.wantSSOSession, profile.SSOSession)
})
}
}

type loader struct {
fileString string
}

func (l loader) Path() string { return "" }
func (l loader) Load() (*ini.File, error) {
testConfigFile, err := ini.LoadSources(ini.LoadOptions{}, []byte(l.fileString))
if err != nil {
return nil, err
}
return testConfigFile, nil
}

type nooploader struct {
}

func (l nooploader) Path() string { return "" }
func (l nooploader) Load() (*ini.File, error) {
return ini.Empty(), nil
}
52 changes: 1 addition & 51 deletions pkg/cfaws/profiles.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,6 @@ type ConfigOpts struct {
MFATokenCode string
}

type SSOSession struct {
SSORegion string
SSOStartURL string
}

type Profile struct {
// allows access to the raw values from the file
RawConfig *ini.Section
Expand All @@ -48,9 +43,6 @@ type Profile struct {
Initialised bool
LoadingError error
HasSecureStorageIAMCredentials bool

// AWS SDK doesn't support sso_session yet so we check for it manually
SSOSession *SSOSession
}

var ErrProfileNotInitialised error = errors.New("profile not initialised")
Expand All @@ -63,27 +55,6 @@ type Profiles struct {
profiles map[string]*Profile
}

func LoadSSOSessions(configFile *ini.File) (map[string]SSOSession, error) {
sessions := make(map[string]SSOSession)

// Iterate through the config sections
for _, section := range configFile.Sections() {
// the ini package adds an extra section called DEFAULT, but this is different to the AWS standard of 'default' so we ignore it and only look at 'default'
if strings.HasPrefix(section.Name(), "sso-session ") {
session := SSOSession{}
regionKey, err := section.GetKey("sso_region")
if err == nil {
session.SSORegion = regionKey.Value()
}
startURLKey, err := section.GetKey("sso_start_url")
if err == nil {
session.SSOStartURL = startURLKey.Value()
}
sessions[strings.TrimPrefix(section.Name(), "sso-session ")] = session
}
}
return sessions, nil
}
func (p *Profiles) HasProfile(profile string) bool {
_, ok := p.profiles[profile]
return ok
Expand Down Expand Up @@ -241,10 +212,7 @@ func (p *Profiles) loadConfigFile(loader ConfigFileLoader) error {
if err != nil {
return err
}
ssoSessions, err := LoadSSOSessions(configFile)
if err != nil {
return err
}

// Iterate through the config sections
for _, section := range configFile.Sections() {
// the ini package adds an extra section called DEFAULT, but this is different to the AWS standard of 'default' so we ignore it an only look at 'default'
Expand All @@ -254,15 +222,6 @@ func (p *Profiles) loadConfigFile(loader ConfigFileLoader) error {
name := strings.TrimPrefix(section.Name(), "profile ")
sectionPtr := section
profile := &Profile{RawConfig: sectionPtr, Name: name, File: loader.Path()}
if section.HasKey("sso_session") {
key, _ := section.GetKey("sso_session")
ssoSession, ok := ssoSessions[key.Value()]
if !ok {
clio.Errorf("failed to load config profile %s because it has an 'sso_session = %s' section but the [sso-session %s] section was not found", name, key.Value(), key.Value())
continue
}
profile.SSOSession = &ssoSession
}
p.ProfileNames = append(p.ProfileNames, name)
p.profiles[name] = profile
}
Expand Down Expand Up @@ -444,15 +403,6 @@ func (p *Profile) init(ctx context.Context, profiles *Profiles, depth int) error
return err
}

// set the sso session if it exists and not overridden on the profile
if p.SSOSession != nil {
if cfg.SSOStartURL == "" {
cfg.SSOStartURL = p.SSOSession.SSOStartURL
}
if cfg.SSORegion == "" {
cfg.SSORegion = p.SSOSession.SSORegion
}
}
p.AWSConfig = cfg

if depth < 10 {
Expand Down

0 comments on commit 98f3908

Please sign in to comment.