-
Notifications
You must be signed in to change notification settings - Fork 30
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 console subcommand #93
base: master
Are you sure you want to change the base?
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 |
---|---|---|
@@ -0,0 +1,197 @@ | ||
package main | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"net/url" | ||
"time" | ||
|
||
"github.com/aws/aws-sdk-go/aws/credentials" | ||
"github.com/miquella/vaulted/lib" | ||
"github.com/pkg/browser" | ||
) | ||
|
||
var ( | ||
ErrInvalidTemporaryCredentials = errors.New("Temporary session credentials found. Console cannot be opened with temporary credentials.\nIf the AWS Key is set in a vault, the permanent credentials can be used by specifying the vault name.") | ||
ErrInvalidDuration = errors.New("Console duration must be between 15m and 12h.") | ||
ErrNoCredentialsFound = errors.New("No credentials found. Console cannot be opened.") | ||
) | ||
|
||
const ( | ||
ConsoleURL = "https://console.aws.amazon.com/console/home" | ||
ConsoleFederationSigninURL = "https://signin.aws.amazon.com/federation" | ||
|
||
ConsoleMinDuration = 15 * time.Minute | ||
ConsoleMaxDuration = 12 * time.Hour | ||
ConsoleDefaultDuration = 1 * time.Hour | ||
) | ||
|
||
type Console struct { | ||
VaultName string | ||
Role string | ||
Duration time.Duration | ||
} | ||
|
||
type TokenParams struct { | ||
awsKey *vaulted.AWSKey | ||
duration time.Duration | ||
} | ||
|
||
func (c *Console) Run(store vaulted.Store) error { | ||
signinToken, err := c.getSigninToken(store) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return openConsole(signinToken) | ||
} | ||
|
||
func (c *Console) getSigninToken(store vaulted.Store) (string, error) { | ||
params, err := c.getTokenParams(store) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
if c.Role != "" { | ||
return c.getAssumeRoleToken(store, params) | ||
} else { | ||
return c.getFederationToken(params) | ||
} | ||
} | ||
|
||
func (c *Console) getTokenParams(store vaulted.Store) (TokenParams, error) { | ||
vault, err := c.getVault(store) | ||
if err != nil { | ||
return TokenParams{}, err | ||
} | ||
|
||
awsKey := c.validateAWSKey(vault.AWSKey) | ||
|
||
duration, err := c.chooseDuration(vault.Duration) | ||
if err != nil { | ||
return TokenParams{}, err | ||
} | ||
|
||
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. why you new line here? 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. done |
||
params := TokenParams{ | ||
awsKey: awsKey, | ||
duration: duration, | ||
} | ||
return params, nil | ||
} | ||
|
||
func (c *Console) getVault(store vaulted.Store) (*vaulted.Vault, error) { | ||
vault := &vaulted.Vault{} | ||
var err error | ||
if c.VaultName != "" { | ||
vault, _, err = store.OpenVault(c.VaultName) | ||
if err != nil { | ||
return nil, err | ||
} | ||
} | ||
return vault, nil | ||
} | ||
|
||
func (c *Console) validateAWSKey(awsKey *vaulted.AWSKey) *vaulted.AWSKey { | ||
key := &vaulted.AWSKey{} | ||
|
||
if awsKey != nil && awsKey.Valid() { | ||
key = awsKey | ||
} | ||
|
||
if c.Role != "" { | ||
key.Role = c.Role | ||
} | ||
return key | ||
} | ||
|
||
func (c *Console) chooseDuration(vaultDuration time.Duration) (time.Duration, error) { | ||
duration := ConsoleDefaultDuration | ||
|
||
if vaultDuration != 0 { | ||
duration = vaultDuration | ||
} | ||
|
||
if c.Duration != 0 { | ||
duration = c.Duration | ||
} | ||
|
||
return capDuration(duration) | ||
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. Even if we've validated the Duration when we received the command, it still feels like we shouldn't be calling capDuration on the command flag duration. capDuration really only belongs with the Vault duration. |
||
} | ||
|
||
func capDuration(duration time.Duration) (time.Duration, error) { | ||
if duration < ConsoleMinDuration { | ||
return time.Duration(0), ErrInvalidDuration | ||
} | ||
if duration > ConsoleMaxDuration { | ||
duration = ConsoleMaxDuration | ||
fmt.Println("Your vault duration is greater than the max console duration.\nCurrent console session duration set to 12 hours.") | ||
} | ||
return duration, nil | ||
} | ||
|
||
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. nit: line gap 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. done |
||
func (c *Console) getAssumeRoleToken(store vaulted.Store, params TokenParams) (string, error) { | ||
var err error | ||
awsCreds, err := c.getCredentials(params.awsKey) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
if params.awsKey.MFA != "" { | ||
tokenCode, err := store.Steward().GetMFAToken(c.VaultName) | ||
if err != nil { | ||
return "", err | ||
} | ||
awsCreds, err = awsCreds.AssumeRoleWithMFA(params.awsKey.MFA, tokenCode, params.awsKey.Role, ConsoleMinDuration) | ||
} else { | ||
awsCreds, err = awsCreds.AssumeRole(params.awsKey.Role, ConsoleMinDuration) | ||
} | ||
if err != nil { | ||
return "", err | ||
} | ||
return awsCreds.GetSigninToken(¶ms.duration) | ||
} | ||
|
||
func (c *Console) getFederationToken(params TokenParams) (string, error) { | ||
awsCreds, err := c.getCredentials(params.awsKey) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
awsCreds, err = awsCreds.GetFederationToken(params.duration) | ||
if err != nil { | ||
return "", err | ||
} | ||
return awsCreds.GetSigninToken(nil) | ||
} | ||
|
||
func (c *Console) getCredentials(awsKey *vaulted.AWSKey) (*vaulted.AWSCredentials, error) { | ||
awsCreds, err := awsKey.AWSCredentials.WithLocalDefault() | ||
if err != nil { | ||
if err != credentials.ErrNoValidProvidersFoundInChain { | ||
return nil, err | ||
} else if err == credentials.ErrNoValidProvidersFoundInChain { | ||
return nil, ErrNoCredentialsFound | ||
} | ||
} | ||
|
||
if awsCreds.ValidSession() { | ||
return nil, ErrInvalidTemporaryCredentials | ||
} | ||
|
||
return awsCreds, nil | ||
} | ||
|
||
func openConsole(signinToken string) error { | ||
signinURL, _ := url.Parse(ConsoleFederationSigninURL) | ||
loginQuery := url.Values{ | ||
"Action": []string{"login"}, | ||
"SigninToken": []string{signinToken}, | ||
"Destination": []string{ConsoleURL}, | ||
} | ||
signinURL.RawQuery = loginQuery.Encode() | ||
err := browser.OpenURL(signinURL.String()) | ||
if err != nil { | ||
return err | ||
} | ||
return nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package main | ||
|
||
import ( | ||
"testing" | ||
"time" | ||
|
||
"github.com/miquella/vaulted/lib" | ||
) | ||
|
||
func TestConsole(t *testing.T) { | ||
var c Console = Console{ | ||
VaultName: "one", | ||
} | ||
var err error | ||
store := NewTestStore() | ||
store.Vaults["one"] = &vaulted.Vault{ | ||
AWSKey: &vaulted.AWSKey{}, | ||
} | ||
store.Vaults["one"].Duration = ConsoleMinDuration | ||
err = c.Run(store) | ||
if err != ErrNoCredentialsFound { | ||
t.Error("No credentials provided, should have caused an ErrNoCredentialsFound") | ||
} | ||
|
||
store.Vaults["one"].AWSKey.AWSCredentials = vaulted.AWSCredentials{ | ||
ID: "id", | ||
Secret: "secret", | ||
} | ||
store.Vaults["one"].Duration = 10 * time.Minute | ||
err = c.Run(store) | ||
if err != ErrInvalidDuration { | ||
t.Error("Invalid vault duration, should have caused an ErrInvalidDuration") | ||
} | ||
|
||
store.Vaults["one"].AWSKey.AWSCredentials = vaulted.AWSCredentials{ | ||
ID: "id", | ||
Secret: "secret", | ||
Token: "token", | ||
} | ||
store.Vaults["one"].Duration = ConsoleMinDuration | ||
err = c.Run(store) | ||
if err != ErrInvalidTemporaryCredentials { | ||
t.Error("Temporary session credentials provided, should have caused an invalid temp credentials error") | ||
} | ||
} |
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.
Console*Duration
constants aren't specific to the console, but to the underlying Role. These should probably be named/refactored accordingly.