Skip to content

Commit

Permalink
Add console subcommand
Browse files Browse the repository at this point in the history
  • Loading branch information
RowdyLemon committed Aug 1, 2017
1 parent f85556b commit bd677cb
Show file tree
Hide file tree
Showing 11 changed files with 530 additions and 12 deletions.
29 changes: 29 additions & 0 deletions command.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ func parseArgs(args []string) (Command, error) {
case "add":
return parseAddArgs(commandArgs[1:])

case "console":
return parseConsoleArgs(commandArgs[1:])

case "cp", "copy":
return parseCopyArgs(commandArgs[1:])

Expand Down Expand Up @@ -185,6 +188,32 @@ func parseAddArgs(args []string) (Command, error) {
return e, nil
}

func parseConsoleArgs(args []string) (Command, error) {
flag := pflag.NewFlagSet("console", pflag.ContinueOnError)
flag.String("assume", "", "Role to assume")
flag.Usage = func() {}
err := flag.Parse(args)
if err != nil {
return nil, err
}

vaultName := ""
assume, _ := flag.GetString("assume")

if flag.NArg() > 1 {
return nil, ErrTooManyArguments
}

if flag.NArg() == 1 {
vaultName = flag.Arg(0)
}

c := &Console{}
c.VaultName = vaultName
c.Role = assume
return c, nil
}

func parseCopyArgs(args []string) (Command, error) {
flag := pflag.NewFlagSet("copy", pflag.ContinueOnError)
flag.Usage = func() {}
Expand Down
33 changes: 33 additions & 0 deletions command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,10 @@ var (
Args: []string{"help", "upgrade"},
Command: &Help{Subcommand: "upgrade"},
},
{
Args: []string{"help", "console"},
Command: &Help{Subcommand: "console"},
},
{
Args: []string{"-h"},
Command: &Help{},
Expand Down Expand Up @@ -374,6 +378,30 @@ var (
Args: []string{"-V"},
Command: &Version{},
},
// Console
{
Args: []string{"console", "one"},
Command: &Console{
VaultName: "one",
},
},
{
Args: []string{"console", "--assume", "arn:something:or:other"},
Command: &Console{
Role: "arn:something:or:other",
},
},
{
Args: []string{"console", "--assume", "arn:something:or:other", "one"},
Command: &Console{
VaultName: "one",
Role: "arn:something:or:other",
},
},
{
Args: []string{"console", "--help"},
Command: &Help{Subcommand: "console"},
},
}

badParseCases = []parseCase{
Expand Down Expand Up @@ -473,6 +501,11 @@ var (
Args: []string{"upgrade", "one"},
},

// Console
{
Args: []string{"Console", "one", "two"},
},

// Misc
{
Args: []string{},
Expand Down
133 changes: 133 additions & 0 deletions console.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
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("Minimum console duration must be at least 15 minutes.")
ErrNoCredentialsFound = errors.New("No credentials found. Console cannot be opened.")
)

const (
CONSOLE_URL = "https://console.aws.amazon.com/console/home"
SIGNIN_URL = "https://signin.aws.amazon.com/federation"

MinConsoleDuration = 15 * time.Minute
MaxConsoleDuration = 12 * time.Hour
)

type Console struct {
VaultName string
Role string
}

func (c *Console) Run(store vaulted.Store) error {
signinToken, err := c.getSigninToken(store)
if err != nil {
return err
}

// console signin
signinUrl, _ := url.Parse(SIGNIN_URL)
loginQuery := make(url.Values)
loginQuery.Set("Action", "login")
loginQuery.Set("SigninToken", signinToken)
loginQuery.Set("Destination", CONSOLE_URL)

signinUrl.RawQuery = loginQuery.Encode()
err = browser.OpenURL(signinUrl.String())
if err != nil {
return err
}

return nil
}

func (c *Console) getSigninToken(store vaulted.Store) (string, error) {
// Setup default values (may be overwritten by values from vault)
duration := 1 * time.Hour
var awsKey vaulted.AWSKey

// Override defaults with values from specified vault
if c.VaultName != "" {
v, _, err := store.OpenVault(c.VaultName)
if err != nil {
return "", err
}

duration = v.Duration

if v.AWSKey.Valid() {
awsKey = *v.AWSKey
if c.Role != "" {
awsKey.Role = c.Role
}
}
}

return c.getSigninTokenFromCreds(store, awsKey, duration)
}

func (c *Console) getSigninTokenFromCreds(store vaulted.Store, awsKey vaulted.AWSKey, duration time.Duration) (string, error) {
// Get creds from environment if no creds loaded from vault
creds, err := awsKey.AWSCredentials.WithLocalDefault()
if err != nil && err != credentials.ErrNoValidProvidersFoundInChain {
return "", err
} else if err == credentials.ErrNoValidProvidersFoundInChain || !creds.Valid() {
return "", ErrNoCredentialsFound
}

if creds.ValidSession() {
return "", ErrInvalidTemporaryCredentials
}

duration, err = capDuration(duration)
if err != nil {
return "", err
}

// assume provided role or get a federation token
if awsKey.Role != "" {
if awsKey.MFA != "" {
tokenCode, tokenErr := store.Steward().GetMFAToken(c.VaultName)
if tokenErr != nil {
return "", tokenErr
}
creds, err = creds.AssumeRoleWithMFA(awsKey.MFA, tokenCode, awsKey.Role, 15*time.Minute)
} else {
creds, err = creds.AssumeRole(awsKey.Role, 15*time.Minute)
}
if err != nil {
return "", err
}

return creds.GetSigninToken(&duration)

} else {
creds, err = creds.GetFederationToken(duration)
if err != nil {
return "", err
}
return creds.GetSigninToken(nil)
}
}

func capDuration(duration time.Duration) (time.Duration, error) {
if duration < MinConsoleDuration {
return time.Duration(0), ErrInvalidDuration
}
if duration > MaxConsoleDuration {
duration = MaxConsoleDuration
fmt.Println("Your vault duration is greater than the max console duration.\nCurrent console session duration set to 12 hours.")
}
return duration, nil
}
43 changes: 43 additions & 0 deletions console_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package main

import (
"testing"

"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{},
}

err = c.Run(store)
if err != ErrNoCredentialsFound {
t.Error("No credentials provided, should have cause an ErrNoCredentialsFound")
}

store.Vaults["one"].AWSKey.AWSCredentials = vaulted.AWSCredentials{
ID: "id",
Secret: "secret",
}
err = c.Run(store)
if err != ErrInvalidDuration {
t.Error("Should have caused an invalid duration error")
}

store.Vaults["one"].AWSKey.AWSCredentials = vaulted.AWSCredentials{
ID: "id",
Secret: "secret",
Token: "token",
}
err = c.Run(store)
if err != ErrInvalidTemporaryCredentials {
t.Log(err)
t.Error("Temporary session credentials provided, should have cause an invalid temp credentials error")
}
}
2 changes: 1 addition & 1 deletion doc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ gem install md2man
```

```sh
go get -u github.com/jteeuwen/go-bindata
go get -u github.com/jteeuwen/go-bindata/...
```

Generating Man Pages
Expand Down
30 changes: 30 additions & 0 deletions doc/man/vaulted-console.1
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
.TH vaulted\-console 1
.SH NAME
.PP
vaulted console \- Opens the AWS console in the default web browser
.SH SYNOPSIS
.PP
\fB\fCvaulted console\fR
\fB\fCvaulted console\fR \fIname\fP [\fIOPTIONS\fP]
.br
\fB\fCvaulted console \-\-assume\fR \fIarn\fP [\fIOPTIONS\fP]
.SH DESCRIPTION
.PP
Opens the AWS console in the default web browser. Uses either the credentials in the current environment or the credentials in the specified vault. Console sessions either use the provided vault's duration or defaults to 1 hour.
.PP
Durations must be at least 15 min and less than 12 hours.
.SH OPTIONS
.TP
\fB\fC\-\-assume\fR \fIarn\fP
Specifies the full ARN of the role to assume. See \fBASSUMING A ROLE\fP below
for details on how Vaulted assumes roles.
.IP
Role assumption may be performed without specifying a vault to spawn from.
When invoked this way, credentials are sourced from default locations (e.g.
environment, configuration files, instance profile, etc.).
.SH ASSUMING A ROLE
.PP
A role to assume can be specified either in a vault's configuration (via
\fB\fCvaulted edit\fR) or specified via the \fB\fC\-\-assume\fR option.
.PP
Vaulted first opens the specified vault to retrieve the appropriate credentials. If a role is specified in the vault's configuration it will use that unless a role is explicitly passed in through the \fB\fC\-\-assume\fR option.
41 changes: 41 additions & 0 deletions doc/vaulted-console.1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
vaulted-console 1
=============

NAME
----

vaulted console - Opens the AWS console in the default web browser

SYNOPSIS
--------

`vaulted console`
`vaulted console` *name* [*OPTIONS*]
`vaulted console --assume` *arn* [*OPTIONS*]

DESCRIPTION
-----------

Opens the AWS console in the default web browser. Uses either the credentials in the current environment or the credentials in the specified vault. Console sessions either use the provided vault's duration or defaults to 1 hour.

Durations must be at least 15 min and less than 12 hours.

OPTIONS
-------

`--assume` *arn*
Specifies the full ARN of the role to assume. See **ASSUMING A ROLE** below
for details on how Vaulted assumes roles.

Role assumption may be performed without specifying a vault to spawn from.
When invoked this way, credentials are sourced from default locations (e.g.
environment, configuration files, instance profile, etc.).


ASSUMING A ROLE
---------------

A role to assume can be specified either in a vault's configuration (via
`vaulted edit`) or specified via the `--assume` option.

Vaulted first opens the specified vault to retrieve the appropriate credentials. If a role is specified in the vault's configuration it will use that unless a role is explicitly passed in through the `--assume` option.
1 change: 1 addition & 0 deletions help.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ var (

HelpAliases = map[string]string{
"add": "add",
"console": "console",
"cp": "cp",
"copy": "cp",
"dump": "dump",
Expand Down
Loading

0 comments on commit bd677cb

Please sign in to comment.