-
Notifications
You must be signed in to change notification settings - Fork 84
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add sso auto run #847
Add sso auto run #847
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,13 @@ | ||
package aws | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"errors" | ||
"fmt" | ||
"math" | ||
"math/rand" | ||
"os/exec" | ||
"path" | ||
"strings" | ||
"time" | ||
|
@@ -1718,7 +1720,7 @@ func getSessionWithMaxRetries(ctx context.Context, d *plugin.QueryData, region s | |
} | ||
|
||
// If seesion was not in cache - create a session and save to cache | ||
|
||
var isSSO bool = false | ||
// get aws config info | ||
awsConfig := GetConfig(d.Connection) | ||
|
||
|
@@ -1737,6 +1739,10 @@ func getSessionWithMaxRetries(ctx context.Context, d *plugin.QueryData, region s | |
|
||
if awsConfig.Profile != nil { | ||
sessionOptions.Profile = *awsConfig.Profile | ||
// When profile is set but not access or secret key, logically this means it will be SSO login | ||
if awsConfig.AccessKey == nil || awsConfig.SecretKey == nil { | ||
isSSO = true | ||
} | ||
} | ||
|
||
if awsConfig.AccessKey != nil && awsConfig.SecretKey == nil { | ||
|
@@ -1754,7 +1760,14 @@ func getSessionWithMaxRetries(ctx context.Context, d *plugin.QueryData, region s | |
) | ||
} | ||
} | ||
|
||
// If we are using SSO, check if credintals are valid, if not trigger aws sso login | ||
// *awsConfig.Profile is a risk of being nil, however isSSO will be false in that case, so we are protected | ||
if isSSO == true { | ||
validAwsCredActive := checkAWSCallerIdent(ctx, *awsConfig.Profile) | ||
if !validAwsCredActive && awsConfig.SessionToken == nil && (awsConfig.AccessKey == nil || awsConfig.SecretKey == nil) { | ||
runAWSCLISSOLogin(ctx, *awsConfig.Profile) | ||
} | ||
} | ||
sess, err := session.NewSessionWithOptions(sessionOptions) | ||
if err != nil { | ||
plugin.Logger(ctx).Error("getSessionWithMaxRetries", "new_session_with_options", err) | ||
|
@@ -1767,6 +1780,61 @@ func getSessionWithMaxRetries(ctx context.Context, d *plugin.QueryData, region s | |
return sess, nil | ||
} | ||
|
||
// checkAWSCallerIdent returns boolean if currently logged in to AWS CLI | ||
func checkAWSCallerIdent(ctx context.Context, profile string) bool { | ||
plugin.Logger(ctx).Trace("getSessionWithMaxRetries", "checkAWSCallerIdent", "Starting for "+profile) | ||
commandInput := strings.Fields("aws sts get-caller-identity --profile " + profile) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the user doesn't have the proper STS permissions to run this particular command, do you know if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you don't have St's permission the command will false return false and the code would assume you logger out as it can't verify it. Given STS is the AWS best practice way to check if your logged in. I think you trying to code for a bad practice that is going against aws standards when you ask if STS permissions are missing. |
||
plugin.Logger(ctx).Trace("getSessionWithMaxRetries", "checkAWSCallerIdent", "CommandInput was for "+strings.Join(commandInput, " ")) | ||
cmd := exec.Command(commandInput[0], commandInput[1:]...) | ||
var buf bytes.Buffer | ||
cmd.Stdout = &buf | ||
|
||
if err := cmd.Run(); err != nil { | ||
plugin.Logger(ctx).Trace("getSessionWithMaxRetries", "checkAWSCallerIdent", err) | ||
return false | ||
} | ||
plugin.Logger(ctx).Trace("getSessionWithMaxRetries", "checkAWSCallerIdent", buf.String()) | ||
return true | ||
} | ||
|
||
// runAWSCLISSOLogin returns boolean if currently logged in to AWS CLI | ||
func runAWSCLISSOLogin(ctx context.Context, profile string) bool { | ||
commandInput := strings.Fields("aws sso login --profile " + profile) | ||
cmd := exec.Command(commandInput[0], commandInput[1:]...) | ||
var buf bytes.Buffer | ||
cmd.Stdout = &buf | ||
if err := cmd.Start(); err != nil { | ||
plugin.Logger(ctx).Error("getSessionWithMaxRetries", "runAWSCLISSOLogin", err) | ||
return false | ||
} | ||
done := make(chan error) | ||
go func() { done <- cmd.Wait() }() | ||
// Start a timer | ||
timeout := time.After(30 * time.Second) | ||
// The select statement allows us to execute based on which channel | ||
// we get a message from first. | ||
select { | ||
case <-timeout: | ||
// Timeout happened first, kill the process and print a message. | ||
err := cmd.Process.Kill() | ||
plugin.Logger(ctx).Error("getSessionWithMaxRetries", "runAWSCLISSOLogin", "Killed due to timeout!") | ||
if err != nil { | ||
return false | ||
} | ||
return false | ||
case err := <-done: | ||
// Command completed before timeout. Print output and error if it exists. | ||
plugin.Logger(ctx).Trace("getSessionWithMaxRetries", "runAWSCLISSOLogin", buf.String()) | ||
|
||
if err != nil { | ||
plugin.Logger(ctx).Error("getSessionWithMaxRetries", "runAWSCLISSOLogin", err) | ||
return false | ||
} | ||
return true | ||
} | ||
|
||
} | ||
|
||
// GetDefaultAwsRegion returns the default region for AWS partiton | ||
// if not set by Env variable or in aws profile | ||
func GetDefaultAwsRegion(d *plugin.QueryData) string { | ||
|
@@ -1836,7 +1904,7 @@ func GetDefaultAwsRegion(d *plugin.QueryData) string { | |
|
||
// Function from https://github.com/panther-labs/panther/blob/v1.16.0/pkg/awsretry/connection_retryer.go | ||
func NewConnectionErrRetryer(maxRetries int, ctx context.Context) *ConnectionErrRetryer { | ||
var minRetryDelay time.Duration = 25 * time.Millisecond | ||
var minRetryDelay = 25 * time.Millisecond | ||
rand.Seed(time.Now().UnixNano()) // reseting state of rand to generate different random values | ||
return &ConnectionErrRetryer{ | ||
ctx: ctx, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dbmurphy Is this always true? If I have a connection using my
default
profile (or any other named profile):Then have my
~/.aws/credentials
file setup so mydefault
profile uses an AWS access key pair:The
awsConfig.AccessKey
andawsConfig.SecretKey
values will still benil
, as they're not explicitly defined in the config file. Similarly, there are a few other ways you can authenticate with the AWS plugin that do not require an access or secret key explicitly set in the Steampipe AWS config file, like using AssumeRole creds, AWS Vault creds, and EC2 instance profile creds. There are a few examples of each sample connection configuration from https://hub.steampipe.io/plugins/turbot/aws.Have you tried this code while using any of the above authentication methods?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have tried some but not all, this is rather complicated, and why I made mention that the auth system really needs some TLC, so we can do things like
if isSSO
if isKey
...
However this means you would need to refactor the Env, SPC, and .aws layer to get a materialized view of what the SDK itself would see. In the current form of this code, it's possible if you had a key type auth and it tried this is would call sts and sso butt hey would fail as no SSO URL is defined in the .aws/config. In this case, it would simply continue and the SDK would still use the key/secret you have provided.
As such, I feel this is a solution that works but could be made cleaner, but such clean is a major refactor of the auth code. Similar to how it should only call SSO login/sts checks once but as steampipe does not have a hierarchical order and all copies of the AWS plugin as started at the same time it calls N open browser or log prints for authorizing in your SSO system.