diff --git a/cmd/login.go b/cmd/login.go index 31a1607a..fda41a18 100644 --- a/cmd/login.go +++ b/cmd/login.go @@ -64,7 +64,8 @@ func loginRun(cmd *cobra.Command, args []string) error { return err } - if _, ok := profiles[profile]; !ok { + prof, ok := profiles[profile] + if !ok { return fmt.Errorf("Profile '%s' not found in your aws config", profile) } @@ -108,6 +109,31 @@ func loginRun(cmd *cobra.Command, args []string) error { return err } + if _, ok := prof["aws_saml_url"]; ok { + oktaLogin(p) + } else { + federatedLogin(p, profile, profiles) + } + + return nil +} + +func oktaLogin(p *lib.Provider) error { + loginURL, err := p.GetSAMLLoginURL() + if err != nil { + return err + } + + if Stdout { + fmt.Println(loginURL.String()) + } else if err := open.Run(loginURL.String()); err != nil { + return err + } + + return nil +} + +func federatedLogin(p *lib.Provider, profile string, profiles lib.Profiles) error { creds, err := p.Retrieve() if err != nil { return err diff --git a/lib/okta.go b/lib/okta.go index 5459c849..b7e6cf33 100644 --- a/lib/okta.go +++ b/lib/okta.go @@ -536,3 +536,39 @@ func (p *OktaProvider) Retrieve() (sts.Credentials, string, error) { return creds, oktaCreds.Username, err } + +func (p *OktaProvider) GetSAMLLoginURL() (*url.URL, error) { + item, err := p.Keyring.Get("okta-creds") + if err != nil { + log.Debugf("couldnt get okta creds from keyring: %s", err) + return &url.URL{}, err + } + + var oktaCreds OktaCreds + if err = json.Unmarshal(item.Data, &oktaCreds); err != nil { + return &url.URL{}, errors.New("Failed to get okta credentials from your keyring. Please make sure you have added okta credentials with `aws-okta add`") + } + + var samlURL string + + // maintain compatibility for deprecated creds.Organization + if oktaCreds.Domain == "" && oktaCreds.Organization != "" { + samlURL = fmt.Sprintf("%s.%s", oktaCreds.Organization, OktaServerDefault) + } else if oktaCreds.Domain != "" { + samlURL = oktaCreds.Domain + } else { + return &url.URL{}, errors.New("either oktaCreds.Organization (deprecated) or oktaCreds.Domain must be set, but not both. To remedy this, re-add your credentials with `aws-okta add`") + } + + fullSamlURL, err := url.Parse(fmt.Sprintf( + "https://%s/%s", + samlURL, + p.OktaAwsSAMLUrl, + )) + + if err != nil { + return &url.URL{}, err + } + + return fullSamlURL, nil +} diff --git a/lib/provider.go b/lib/provider.go index e5dc8a69..217616d7 100644 --- a/lib/provider.go +++ b/lib/provider.go @@ -3,6 +3,7 @@ package lib import ( "errors" "fmt" + "net/url" "time" log "github.com/sirupsen/logrus" @@ -183,6 +184,35 @@ func (p *Provider) getSamlSessionCreds() (sts.Credentials, error) { return creds, nil } +func (p *Provider) GetSAMLLoginURL() (*url.URL, error) { + source := sourceProfile(p.profile, p.profiles) + oktaAwsSAMLUrl, err := p.getSamlURL() + if err != nil { + return &url.URL{}, err + } + oktaSessionCookieKey := p.getOktaSessionCookieKey() + + profileARN, ok := p.profiles[source]["role_arn"] + if !ok { + return &url.URL{}, errors.New("Source profile must provide `role_arn`") + } + + provider := OktaProvider{ + MFADevice: p.ProviderOptions.MFADevice, + Keyring: p.keyring, + ProfileARN: profileARN, + SessionDuration: p.SessionDuration, + OktaAwsSAMLUrl: oktaAwsSAMLUrl, + OktaSessionCookieKey: oktaSessionCookieKey, + } + + loginURL, err := provider.GetSAMLLoginURL() + if err != nil { + return &url.URL{}, err + } + return loginURL, nil +} + // assumeRoleFromSession takes a session created with an okta SAML login and uses that to assume a role func (p *Provider) assumeRoleFromSession(creds sts.Credentials, roleArn string) (sts.Credentials, error) { client := sts.New(session.New(&aws.Config{Credentials: credentials.NewStaticCredentials(