Skip to content
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

Continue work on login command #997

Merged
merged 3 commits into from
Jul 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@
* No longer show help for sub-commands by default
* Warnings about invalid accounts/roles in config.yaml are now Debug messages #980
* Default ProfileFormat is now the `Friendly` format #992
* `config`, `config-profiles` and `completions` are now sub-commands of `setup` #975
* Refactor commands under `setup`: #975
* `config` is now `setup wizard` and `ConfigProfilesUrlAction` config option is no longer used
* `config-profiles` is now `setup profiles`
* `completions` is now `setup completions`
* Make `--url-action` and `--sts-refresh` command specific options
* Refactor `ecs ssl` commands to be just flags.
* Remove `--open` option from `process` command #291
* Only the and `cache` command will auto-update the contents of `~/.aws/config` #974
* `tags` command no longer supports the `--force-update` option
* Change default log level from `warn` to `info`
Expand Down
3 changes: 1 addition & 2 deletions cmd/aws-sso/cache_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,7 @@ func (cc *CacheCmd) Run(ctx *RunContext) error {
// should we update our config??
if !ctx.Cli.Cache.NoConfigCheck && ctx.Settings.AutoConfigCheck {
if ctx.Settings.ConfigProfilesUrlAction != url.ConfigProfilesUndef {
action, _ := url.NewAction(string(ctx.Settings.ConfigProfilesUrlAction))
err := awsconfig.UpdateAwsConfig(ctx.Settings, action, "", true, false)
err := awsconfig.UpdateAwsConfig(ctx.Settings, "", true, false)
if err != nil {
log.Errorf("Unable to auto-update aws config file: %s", err.Error())
}
Expand Down
21 changes: 16 additions & 5 deletions cmd/aws-sso/console_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/service/sts"

// "github.com/davecgh/go-spew/spew"
"github.com/synfinatic/aws-sso-cli/internal/storage"
"github.com/synfinatic/aws-sso-cli/internal/url"
Expand All @@ -42,9 +43,11 @@ import (

type ConsoleCmd struct {
// Console actually should honor the --region flag
Duration int32 `kong:"short='d',help='AWS Session duration in minutes (default 60)'"` // default stored in DEFAULT_CONFIG
Prompt bool `kong:"short='P',help='Force interactive prompt to select role'"`
Region string `kong:"help='AWS Region',env='AWS_DEFAULT_REGION',predictor='region'"`
Duration int32 `kong:"short='d',help='AWS Session duration in minutes (default 60)'"` // default stored in DEFAULT_CONFIG
Prompt bool `kong:"short='P',help='Force interactive prompt to select role'"`
Region string `kong:"help='AWS Region',env='AWS_DEFAULT_REGION',predictor='region'"`
STSRefresh bool `kong:"help='Force refresh of STS Token Credentials'"`
UrlAction string `kong:"short='u',help='How to handle URLs [clip|exec|open|print|printurl|granted-containers|open-url-in-container] (default: open)'"`

Arn string `kong:"short='a',help='ARN of role to assume',env='AWS_SSO_ROLE_ARN',predictor='arn'"`
AccountId int64 `kong:"name='account',short='A',help='AWS AccountID of role to assume',env='AWS_SSO_ACCOUNT_ID',predictor='accountId'"`
Expand Down Expand Up @@ -235,7 +238,7 @@ func openConsole(ctx *RunContext, accountid int64, role string) error {
log.WithError(err).Warnf("Unable to update cache")
}

creds := GetRoleCredentials(ctx, AwsSSO, accountid, role)
creds := GetRoleCredentials(ctx, AwsSSO, ctx.Cli.Console.STSRefresh, accountid, role)
return openConsoleAccessKey(ctx, creds, duration, region, accountid, role)
}

Expand Down Expand Up @@ -287,7 +290,15 @@ func openConsoleAccessKey(ctx *RunContext, creds *storage.RoleCredentials,
SigninToken: loginResponse.SigninToken,
}

urlOpener := url.NewHandleUrl(ctx.Settings.UrlAction, login.GetUrl(),
action, err := url.NewAction(ctx.Cli.Console.UrlAction)
if err != nil {
log.Fatalf("Invalid --url-action %s", ctx.Cli.Console.UrlAction)
}
if action == "" {
action = ctx.Settings.UrlAction
}

urlOpener := url.NewHandleUrl(action, login.GetUrl(),
ctx.Settings.Browser, ctx.Settings.UrlExecCommand)

urlOpener.ContainerSettings(containerParams(ctx, accountId, role))
Expand Down
2 changes: 1 addition & 1 deletion cmd/aws-sso/credentials_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func (cc *CredentialsCmd) Run(ctx *RunContext) error {
return err
}

pCreds := GetRoleCredentials(ctx, AwsSSO, roleFlat.AccountId, roleFlat.RoleName)
pCreds := GetRoleCredentials(ctx, AwsSSO, ctx.Cli.Console.STSRefresh, roleFlat.AccountId, roleFlat.RoleName)

creds = append(creds, awsconfig.ProfileCredentials{
Profile: profile,
Expand Down
11 changes: 6 additions & 5 deletions cmd/aws-sso/ecs_client_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,11 @@ import (

type EcsLoadCmd struct {
// AWS Params
Arn string `kong:"short='a',help='ARN of role to load',env='AWS_SSO_ROLE_ARN',predictor='arn'"`
AccountId int64 `kong:"name='account',short='A',help='AWS AccountID of role to load',env='AWS_SSO_ACCOUNT_ID',predictor='accountId',xor='account'"`
Role string `kong:"short='R',help='Name of AWS Role to load',env='AWS_SSO_ROLE_NAME',predictor='role',xor='role'"`
Profile string `kong:"short='p',help='Name of AWS Profile to load',predictor='profile',xor='account,role'"`
Arn string `kong:"short='a',help='ARN of role to load',env='AWS_SSO_ROLE_ARN',predictor='arn'"`
AccountId int64 `kong:"name='account',short='A',help='AWS AccountID of role to load',env='AWS_SSO_ACCOUNT_ID',predictor='accountId',xor='account'"`
Role string `kong:"short='R',help='Name of AWS Role to load',env='AWS_SSO_ROLE_NAME',predictor='role',xor='role'"`
Profile string `kong:"short='p',help='Name of AWS Profile to load',predictor='profile',xor='account,role'"`
STSRefresh bool `kong:"help='Force refresh of STS Token Credentials'"`

// Other params
Server string `kong:"help='Endpoint of aws-sso ECS Server',env='AWS_SSO_ECS_SERVER',default='localhost:4144'"`
Expand Down Expand Up @@ -82,7 +83,7 @@ func (cc *EcsProfileCmd) Run(ctx *RunContext) error {
func ecsLoadCmd(ctx *RunContext, accountId int64, role string) error {
c := newClient(ctx.Cli.Ecs.Load.Server, ctx)

creds := GetRoleCredentials(ctx, AwsSSO, accountId, role)
creds := GetRoleCredentials(ctx, AwsSSO, ctx.Cli.Ecs.Load.STSRefresh, accountId, role)

cache := ctx.Settings.Cache.GetSSO()
rFlat, err := cache.Roles.GetRole(accountId, role)
Expand Down
52 changes: 21 additions & 31 deletions cmd/aws-sso/ecs_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,54 +62,44 @@ func (cc *EcsAuthCmd) Run(ctx *RunContext) error {
}

type EcsSSLCmd struct {
Delete EcsSSLDeleteCmd `kong:"cmd,help='Delete the current SSL certificate/private key'"`
Print EcsSSLPrintCmd `kong:"cmd,help='Print the current SSL certificate'"`
Save EcsSSLSaveCmd `kong:"cmd,help='Save a new SSL certificate/private key'"`
}

type EcsSSLSaveCmd struct {
Certificate string `kong:"short=c,type='existingfile',help='Path to certificate chain PEM file',predictor='allFiles',required"`
PrivateKey string `kong:"short=p,type='existingfile',help='Path to private key file PEM file',predictor='allFiles'"`
Delete bool `kong:"short=d,help='Disable SSL and delete the current SSL cert/key',xor='flag,cert,key'"`
Print bool `kong:"short=p,help='Print the current SSL certificate',xor='flag,cert,key'"`
Certificate string `kong:"short=c,type='existingfile',help='Path to certificate chain PEM file',predictor='allFiles',group='add-ssl',xor='cert'"`
PrivateKey string `kong:"short=k,type='existingfile',help='Path to private key file PEM file',predictor='allFiles',group='add-ssl',xor='key'"`
Force bool `kong:"hidden,help='Force loading the certificate'"`
}

type EcsSSLDeleteCmd struct{}

func (cc *EcsSSLDeleteCmd) Run(ctx *RunContext) error {
return ctx.Store.DeleteEcsSslKeyPair()
}

type EcsSSLPrintCmd struct{}

func (cc *EcsSSLPrintCmd) Run(ctx *RunContext) error {
cert, err := ctx.Store.GetEcsSslCert()
if err != nil {
return err
}
if cert == "" {
return fmt.Errorf("no certificate found")
func (cc *EcsSSLCmd) Run(ctx *RunContext) error {
if ctx.Cli.Ecs.SSL.Delete {
return ctx.Store.DeleteEcsSslKeyPair()
} else if ctx.Cli.Ecs.SSL.Print {
cert, err := ctx.Store.GetEcsSslCert()
if err != nil {
return err
}
if cert == "" {
return fmt.Errorf("no certificate found")
}
fmt.Println(cert)
return nil
}
fmt.Println(cert)
return nil
}

func (cc *EcsSSLSaveCmd) Run(ctx *RunContext) error {
var privateKey, certChain []byte
var err error

if !ctx.Cli.Ecs.SSL.Save.Force {
if !ctx.Cli.Ecs.SSL.Force {
log.Warn("This feature is experimental and may not work as expected.")
log.Warn("Please read https://github.com/synfinatic/aws-sso-cli/issues/936 before contiuing.")
log.Fatal("Use `--force` to continue anyways.")
}

certChain, err = os.ReadFile(ctx.Cli.Ecs.SSL.Save.Certificate)
certChain, err = os.ReadFile(ctx.Cli.Ecs.SSL.Certificate)
if err != nil {
return fmt.Errorf("failed to read certificate chain file: %w", err)
}

if ctx.Cli.Ecs.SSL.Save.PrivateKey != "" {
privateKey, err = os.ReadFile(ctx.Cli.Ecs.SSL.Save.PrivateKey)
if ctx.Cli.Ecs.SSL.PrivateKey != "" {
privateKey, err = os.ReadFile(ctx.Cli.Ecs.SSL.PrivateKey)
if err != nil {
return fmt.Errorf("failed to read private key file: %w", err)
}
Expand Down
13 changes: 7 additions & 6 deletions cmd/aws-sso/exec_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,12 @@ import (

type ExecCmd struct {
// AWS Params
Arn string `kong:"short='a',help='ARN of role to assume',env='AWS_SSO_ROLE_ARN',predictor='arn'"`
AccountId int64 `kong:"name='account',short='A',help='AWS AccountID of role to assume',env='AWS_SSO_ACCOUNT_ID',predictor='accountId'"`
Role string `kong:"short='R',help='Name of AWS Role to assume',env='AWS_SSO_ROLE_NAME',predictor='role'"`
Profile string `kong:"short='p',help='Name of AWS Profile to assume',predictor='profile'"`
NoRegion bool `kong:"short='n',help='Do not set AWS_DEFAULT_REGION from config.yaml'"`
Arn string `kong:"short='a',help='ARN of role to assume',env='AWS_SSO_ROLE_ARN',predictor='arn'"`
AccountId int64 `kong:"name='account',short='A',help='AWS AccountID of role to assume',env='AWS_SSO_ACCOUNT_ID',predictor='accountId'"`
Role string `kong:"short='R',help='Name of AWS Role to assume',env='AWS_SSO_ROLE_NAME',predictor='role'"`
Profile string `kong:"short='p',help='Name of AWS Profile to assume',predictor='profile'"`
NoRegion bool `kong:"short='n',help='Do not set AWS_DEFAULT_REGION from config.yaml'"`
STSRefresh bool `kong:"help='Force refresh of STS Token Credentials'"`

// Exec Params
Cmd string `kong:"arg,optional,name='command',help='Command to execute',env='SHELL'"`
Expand Down Expand Up @@ -93,7 +94,7 @@ func execCmd(ctx *RunContext, accountid int64, role string) error {

func execShellEnvs(ctx *RunContext, accountid int64, role, region string) map[string]string {
var err error
credsPtr := GetRoleCredentials(ctx, AwsSSO, accountid, role)
credsPtr := GetRoleCredentials(ctx, AwsSSO, ctx.Cli.Exec.STSRefresh, accountid, role)
creds := *credsPtr

ssoName, _ := ctx.Settings.GetSelectedSSOName(ctx.Cli.SSO)
Expand Down
13 changes: 11 additions & 2 deletions cmd/aws-sso/login_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ package main

import (
"github.com/synfinatic/aws-sso-cli/internal/sso"
"github.com/synfinatic/aws-sso-cli/internal/url"
)

type LoginCmd struct {
Threads int `kong:"help='Override number of threads for talking to AWS',default=${DEFAULT_THREADS}"`
UrlAction string `kong:"short='u',help='How to handle URLs [clip|exec|open|print|printurl|granted-containers|open-url-in-container] (default: open)'"`
Threads int `kong:"help='Override number of threads for talking to AWS',default=${DEFAULT_THREADS}"`
}

func (cc *LoginCmd) Run(ctx *RunContext) error {
Expand Down Expand Up @@ -55,7 +57,14 @@ func doAuth(ctx *RunContext) {
return
}

err := AwsSSO.Authenticate(ctx.Settings.UrlAction, ctx.Settings.Browser)
action, err := url.NewAction(ctx.Cli.Login.UrlAction)
if err != nil {
log.Fatalf("Invalid --url-action %s", ctx.Cli.Login.UrlAction)
}
if action == "" {
action = ctx.Settings.UrlAction
}
err = AwsSSO.Authenticate(action, ctx.Settings.Browser)
if err != nil {
log.WithError(err).Fatalf("Unable to authenticate")
}
Expand Down
46 changes: 26 additions & 20 deletions cmd/aws-sso/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,27 +107,27 @@ type CLI struct {
ConfigFile string `kong:"name='config',default='${CONFIG_FILE}',help='Config file',env='AWS_SSO_CONFIG',predict='allFiles'"`
LogLevel string `kong:"short='L',name='level',help='Logging level [error|warn|info|debug|trace] (default: info)'"`
Lines bool `kong:"help='Print line number in logs'"`
UrlAction string `kong:"short='u',help='How to handle URLs [clip|exec|open|print|printurl|granted-containers|open-url-in-container] (default: open)'"`
SSO string `kong:"short='S',help='Override default AWS SSO Instance',env='AWS_SSO',predictor='sso'"`
STSRefresh bool `kong:"help='Force refresh of STS Token Credentials'"`

// Commands
Cache CacheCmd `kong:"cmd,help='Force reload of cached AWS SSO role info and config.yaml'"`
Setup SetupCmd `kong:"cmd,help='Setup Wizard, Completions, etc'"`
Console ConsoleCmd `kong:"cmd,help='Open AWS Console using specificed AWS role/profile'"`
Credentials CredentialsCmd `kong:"cmd,help='Generate static AWS credentials for use with AWS CLI'"`
Default DefaultCmd `kong:"cmd,hidden,default='1'"` // list command without args
Ecs EcsCmd `kong:"cmd,help='ECS server/client commands'"`
Eval EvalCmd `kong:"cmd,help='Print AWS environment vars for use with eval $(aws-sso eval ...)'"`
Exec ExecCmd `kong:"cmd,help='Execute command using specified IAM role in a new shell'"`
List ListCmd `kong:"cmd,help='List all accounts / roles (default command)'"`
Login LoginCmd `kong:"cmd,help='Login to an AWS Identity Center instance'"`
Logout LogoutCmd `kong:"cmd,help='Logout from an AWS Identity Center instance and invalidate all credentials'"`
ListSSORoles ListSSORolesCmd `kong:"cmd,hidden,help='List AWS SSO Roles (debugging)'"`
Process ProcessCmd `kong:"cmd,help='Generate JSON for credential_process in ~/.aws/config'"`
Setup SetupCmd `kong:"cmd,help='Setup Wizard, Completions, Profiles, etc'"`
Tags TagsCmd `kong:"cmd,help='List tags'"`
Time TimeCmd `kong:"cmd,help='Print how much time before current STS Token expires'"`
Version VersionCmd `kong:"cmd,help='Print version and exit'"`

// Login Commands
Cache CacheCmd `kong:"cmd,help='Force reload of cached AWS SSO role info and config.yaml',group='login-required'"`
Console ConsoleCmd `kong:"cmd,help='Open AWS Console using specificed AWS role/profile',group='login-required'"`
Credentials CredentialsCmd `kong:"cmd,help='Generate static AWS credentials for use with AWS CLI',group='login-required'"`
Eval EvalCmd `kong:"cmd,help='Print AWS environment vars for use with eval $(aws-sso eval ...)',group='login-required'"`
Exec ExecCmd `kong:"cmd,help='Execute command using specified IAM role in a new shell',group='login-required'"`
Logout LogoutCmd `kong:"cmd,help='Logout from an AWS Identity Center instance and invalidate all credentials',group='login-required'"`
Process ProcessCmd `kong:"cmd,help='Generate JSON for AWS SDK credential_process command',group='login-required'"`
}

func main() {
Expand Down Expand Up @@ -162,7 +162,7 @@ func main() {
log.Fatalf("%s", err.Error())
}
return
case "ecs server":
case "ecs server", "ecs list", "ecs unload", "ecs profile":
// side-step the rest of the setup...
if err = ctx.Run(&runCtx); err != nil {
log.Fatalf("%s", err.Error())
Expand Down Expand Up @@ -192,7 +192,7 @@ func main() {
}

switch ctx.Command() {
case "list", "login", "ecs list", "ecs unload", "ecs profile":
case "list", "login", "tags":
// Initialize our AwsSSO variable & SecureStore
c := &runCtx
s, err := c.Settings.GetSelectedSSO(c.Cli.SSO)
Expand Down Expand Up @@ -259,12 +259,24 @@ func parseArgs(cli *CLI) (*kong.Context, sso.OverrideSettings) {
NoExpandSubcommands: true,
}

groups := []kong.Group{
{
Title: "Commands requiring login:",
Key: "login-required",
},
{
Title: "Add SSL Certificate/Key:",
Key: "add-ssl",
},
}

parser := kong.Must(
cli,
kong.Name("aws-sso"),
kong.Description("Securely manage temporary AWS API Credentials issued via AWS SSO"),
kong.ConfigureHelp(help),
vars,
kong.ExplicitGroups(groups),
)

p := predictor.NewPredictor(config.InsecureCacheFile(true), config.ConfigFile(true))
Expand All @@ -287,11 +299,6 @@ func parseArgs(cli *CLI) (*kong.Context, sso.OverrideSettings) {
ctx, err := parser.Parse(os.Args[1:])
parser.FatalIfErrorf(err)

action, err := url.NewAction(cli.UrlAction)
if err != nil {
log.Fatalf("Invalid --url-action %s", cli.UrlAction)
}

threads := 0
if cli.Cache.Threads != DEFAULT_THREADS {
threads = cli.Cache.Threads
Expand All @@ -305,7 +312,6 @@ func parseArgs(cli *CLI) (*kong.Context, sso.OverrideSettings) {
LogLevel: cli.LogLevel,
LogLines: cli.Lines,
Threads: threads, // must be > 0 to override config
UrlAction: action,
}

log.SetFormatter(&logrus.TextFormatter{
Expand All @@ -331,13 +337,13 @@ func (cc *VersionCmd) Run(ctx *RunContext) error {
}

// Get our RoleCredentials from the secure store or from AWS SSO
func GetRoleCredentials(ctx *RunContext, awssso *sso.AWSSSO, accountid int64, role string) *storage.RoleCredentials {
func GetRoleCredentials(ctx *RunContext, awssso *sso.AWSSSO, refreshSTS bool, accountid int64, role string) *storage.RoleCredentials {
creds := storage.RoleCredentials{}

// First look for our creds in the secure store, if we're not forcing a refresh
arn := utils.MakeRoleARN(accountid, role)
log.Debugf("Getting role credentials for %s", arn)
if !ctx.Cli.STSRefresh {
if !refreshSTS {
if roleFlat, err := ctx.Settings.Cache.GetRole(arn); err == nil {
if !roleFlat.IsExpired() {
if err := ctx.Store.GetRoleCredentials(arn, &creds); err == nil {
Expand Down
Loading
Loading