From a5c9164b91f680e966d23f196777dbf1460f5745 Mon Sep 17 00:00:00 2001 From: Johannes Liebermann Date: Tue, 20 Nov 2018 22:04:03 +0100 Subject: [PATCH] Refactor invalid session duration handling --- aws/sts.go | 53 +++++++++++++++++++++++++++---------------------- okta/get.go | 18 ++++++++--------- onelogin/get.go | 18 ++++++++--------- 3 files changed, 47 insertions(+), 42 deletions(-) diff --git a/aws/sts.go b/aws/sts.go index 07ee632..ee5b67d 100644 --- a/aws/sts.go +++ b/aws/sts.go @@ -2,32 +2,46 @@ package aws import ( "errors" - "fmt" - "strings" "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/sts" ) -var ErrInvalidSessionDuration = errors.New("InvalidSessionDuration") +const ( + // The error message STS returns when attempting to assume a role with a duration longer than + // the configured maximum for that role. + ErrInvalidSessionDuration = "The requested DurationSeconds exceeds the MaxSessionDuration set for this role." + // A custom error which indicates that the requested duration exceeded the configured maximum. + // TODO Replace this with a custom error type. + ErrDurationExceeded = "DurationExceeded" +) -// AssumeSAMLRole asumes a Role using the SAMLAssertion specified. If the duration cannot be meet -// it transperently lowers the duration to the minimal valid value of 3600 seconds and returns an -// error in parallel to the valid credentials. +// AssumeSAMLRole assumes an AWS IAM role using a SAML assertion. +// In cases where the requested session duration is higher than the maximum allowed on AWS, STS +// returns a specific error message to indicate that. In this case we return a custom error to the +// caller to allow special handling such as retrying with a lower duration. func AssumeSAMLRole(PrincipalArn, RoleArn, SAMLAssertion string, duration int64) (*Credentials, error) { - creds, err := assumeSAMLRole(PrincipalArn, RoleArn, SAMLAssertion, duration, false) - if err == ErrInvalidSessionDuration { - // the requested duration was invalid. So try again with a minimum of 3600s and return an - // EErrInvalidSessionDuration error, too. - return assumeSAMLRole(PrincipalArn, RoleArn, SAMLAssertion, 3600, true) + creds, err := assumeSAMLRole(PrincipalArn, RoleArn, SAMLAssertion, duration) + if err != nil { + // Verify error is an AWS error. + if awsErr, ok := err.(awserr.Error); ok { + // Check if error indicates exceeded duration. + if awsErr.Message() == ErrInvalidSessionDuration { + // Return a custom error to allow the caller to retry etc. + // TODO Return a custom error type instead of a special value: + // https://dave.cheney.net/2014/12/24/inspecting-errors + return nil, errors.New(ErrDurationExceeded) + } + } + return nil, err } return creds, nil } -func assumeSAMLRole(PrincipalArn, RoleArn, SAMLAssertion string, duration int64, roleDoesNotSupportDuration bool) (*Credentials, error) { - // Assume role +func assumeSAMLRole(PrincipalArn, RoleArn, SAMLAssertion string, duration int64) (*Credentials, error) { input := sts.AssumeRoleWithSAMLInput{ PrincipalArn: aws.String(PrincipalArn), RoleArn: aws.String(RoleArn), @@ -40,12 +54,7 @@ func assumeSAMLRole(PrincipalArn, RoleArn, SAMLAssertion string, duration int64, aResp, err := svc.AssumeRoleWithSAML(&input) if err != nil { - // The role might not yet support the requested duration, let's catch and return a specific - // error. There is - as of now - no other way than to do a string comparison. - if strings.HasPrefix(err.Error(), "ValidationError: The requested DurationSeconds exceeds the MaxSessionDuration set for this role") && duration > 3600 && duration <= 43200 { - return nil, ErrInvalidSessionDuration - } - return nil, fmt.Errorf("assuming role: %v", err) + return nil, err } keyID := *aResp.Credentials.AccessKeyId @@ -60,9 +69,5 @@ func assumeSAMLRole(PrincipalArn, RoleArn, SAMLAssertion string, duration int64, Expiration: expiration, } - if roleDoesNotSupportDuration { - err = ErrInvalidSessionDuration - } - - return &creds, err + return &creds, nil } diff --git a/okta/get.go b/okta/get.go index b15e65b..f046d13 100644 --- a/okta/get.go +++ b/okta/get.go @@ -2,12 +2,13 @@ package okta import ( "fmt" - "strings" + "log" "github.com/allcloud-io/clisso/aws" "github.com/allcloud-io/clisso/config" "github.com/allcloud-io/clisso/saml" "github.com/allcloud-io/clisso/spinner" + "github.com/fatih/color" "github.com/howeyc/gopass" ) @@ -104,14 +105,13 @@ func Get(app, provider string, duration int64) (*aws.Credentials, error) { creds, err := aws.AssumeSAMLRole(arn.Provider, arn.Role, *samlAssertion, duration) s.Stop() - // the default duration might be shorter than what is configured on AWS side. The code above - // selected the minimum duration. If more was requested print an info. - if err == aws.ErrInvalidSessionDuration { - fmt.Printf("The role does not support the requested duration of %v. To have a max session "+ - "duration for up to 12h run:\n", duration) - fmt.Printf("aws iam update-role --role-name %v --max-session-duration 43200 --profile %v\n", - arn.Role[strings.LastIndex(arn.Role, "/")+1:], app) - err = nil + if err != nil { + if err.Error() == aws.ErrDurationExceeded { + log.Println(color.YellowString("Requested duration exceeded allowed maximum. Falling back to 1 hour.")) + s.Start() + creds, err = aws.AssumeSAMLRole(arn.Provider, arn.Role, *samlAssertion, 3600) + s.Stop() + } } return creds, err diff --git a/onelogin/get.go b/onelogin/get.go index 95694b0..3ec5841 100644 --- a/onelogin/get.go +++ b/onelogin/get.go @@ -2,13 +2,14 @@ package onelogin import ( "fmt" - "strings" + "log" "time" "github.com/allcloud-io/clisso/aws" "github.com/allcloud-io/clisso/config" "github.com/allcloud-io/clisso/saml" "github.com/allcloud-io/clisso/spinner" + "github.com/fatih/color" "github.com/howeyc/gopass" ) @@ -187,14 +188,13 @@ func Get(app, provider string, duration int64) (*aws.Credentials, error) { creds, err := aws.AssumeSAMLRole(arn.Provider, arn.Role, rMfa.Data, duration) s.Stop() - // the default duration might be shorter than what is configured on AWS side. The code above - // selected the minimum duration. If more was requested print an info. - if err == aws.ErrInvalidSessionDuration { - fmt.Printf("The role does not support the requested duration of %v. To have a max session "+ - "duration for up to 12h run:\n", duration) - fmt.Printf("aws iam update-role --role-name %v --max-session-duration 43200 --profile %v\n", - arn.Role[strings.LastIndex(arn.Role, "/")+1:], app) - err = nil + if err != nil { + if err.Error() == aws.ErrDurationExceeded { + log.Println(color.YellowString("Requested duration exceeded allowed maximum. Falling back to 1 hour.")) + s.Start() + creds, err = aws.AssumeSAMLRole(arn.Provider, arn.Role, rMfa.Data, 3600) + s.Stop() + } } return creds, err