Skip to content

Commit

Permalink
refactor prompts to mfa-token-providers
Browse files Browse the repository at this point in the history
Prompts were simple prompts, mfa token providers have the `mfa_serial`
that the token is being requested for. It may simply be used to display
to the user, or could be passed to the store in order to lookup the
correct creds. This enables the interfacing of apps/stores that manage
multiple mfa creds/secrets, examples of such stores may be bitwarden,
ykman.

The --prompt flag becomes --mfa-token-provider and AWS_VAULT_PROMPT is
changed to AWS_VAULT_MFA_TOKEN_PROVIDER.
  • Loading branch information
j0hnsmith committed Feb 24, 2020
1 parent 181c09a commit 78573db
Show file tree
Hide file tree
Showing 24 changed files with 296 additions and 166 deletions.
16 changes: 8 additions & 8 deletions USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ role_arn = arn:aws:iam::22222222222:role/Administrator
To configure the default flag values of `aws-vault` and its subcommands:
* `AWS_VAULT_BACKEND`: Secret backend to use (see the flag `--backend`)
* `AWS_VAULT_KEYCHAIN_NAME`: Name of macOS keychain to use (see the flag `--keychain`)
* `AWS_VAULT_PROMPT`: Prompt driver to use (see the flag `--prompt`)
* `AWS_VAULT_MFA_TOKEN_PROVIDER`: MFA token provider to use (see the flag `--mfa-token-provider`)
* `AWS_VAULT_PASS_PASSWORD_STORE_DIR`: Pass password store directory (see the flag `--pass-dir`)
* `AWS_VAULT_PASS_CMD`: Name of the pass executable (see the flag `--pass-cmd`)
* `AWS_VAULT_PASS_PREFIX`: Prefix to prepend to the item path stored in pass (see the flag `--pass-prefix`)
Expand Down Expand Up @@ -249,12 +249,12 @@ This allows you to use credentials of multiple profiles at the same time.
credential_process = aws-vault exec home --json
```

if `mfa_serial` is set, please define the prompt driver (for example `osascript` for macOS), else the prompt will not show up.
if `mfa_serial` is set, please define the mfa token provider (for example `osascript` for macOS), else the prompt will not show up.

```ini
[profile work]
mfa_serial = arn:aws:iam::123456789012:mfa/jonsmith
credential_process = aws-vault exec work --json --prompt=osascript
credential_process = aws-vault exec work --json --mfa-token-provider=osascript
```

## Not using session credentials
Expand Down Expand Up @@ -418,24 +418,24 @@ find information about this process but it boils down to this.
5. Run this:

```bash
ykman oath add YOUR_YUBIKEY_PROFILE -t
ykman oath add 'AWS:{YOUR_MFA_ARN}' -t # eg AWS:arn:aws:iam::123456789:mfa/johnsmith
```
It will ask you for a base32 text. Here you can input the text you got in 3.

6. Run this command twice (wait 30 secs in between):
```bash
ykman oath code --single YOUR_YUBIKEY_PROFILE
ykman oath code --single AWS:{YOUR_MFA_ARN}
```

Input both values as tokens and your device should register as a virtual MFA.


7. Now if you want to run any aws-vault command you should run this:
```bash
aws-vault exec --mfa-token $(ykman oath code --single ${YOUR_YUBIKEY_PROFILE}) ${YOUR_AWS_VAULT_PROFILE} -- aws s3 ls
aws-vault exec --mfa-token-provider ykman ${YOUR_AWS_VAULT_PROFILE} -- aws s3 ls
```

[Here](https://gist.github.com/chtorr/0ecc8fca27a4c5e186c636c262cc4757) There're some helper scripts for this.
(See [`AWS_VAULT_MFA_TOKEN_PROVIDER` env var](#environment-variables)).



### An example config to switch profiles via environment variables
Expand Down
2 changes: 1 addition & 1 deletion cli/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func ConfigureExecCommand(app *kingpin.Application) {

cmd.Action(func(c *kingpin.ParseContext) error {
input.Keyring = keyringImpl
input.Config.MfaPromptMethod = GlobalFlags.PromptDriver
input.Config.MfaTokenProvider = GlobalFlags.MfaTokenProvider
input.Config.NonChainedGetSessionTokenDuration = input.SessionDuration
input.Config.AssumeRoleDuration = input.SessionDuration
app.FatalIfError(ExecCommand(input), "exec")
Expand Down
41 changes: 27 additions & 14 deletions cli/global.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"log"
"os"

"github.com/99designs/aws-vault/prompt"
"github.com/99designs/aws-vault/mfa"
"github.com/99designs/aws-vault/vault"
"github.com/99designs/keyring"
"golang.org/x/crypto/ssh/terminal"
Expand All @@ -18,20 +18,21 @@ const (
)

var (
keyringImpl keyring.Keyring
awsConfigFile *vault.ConfigFile
configLoader *vault.ConfigLoader
promptsAvailable = prompt.Available()
keyringImpl keyring.Keyring
awsConfigFile *vault.ConfigFile
configLoader *vault.ConfigLoader
mfaTokenProvidersAvailable = mfa.TokenProvidersAvailable()
)

var GlobalFlags struct {
Debug bool
Backend string
PromptDriver string
KeychainName string
PassDir string
PassCmd string
PassPrefix string
Debug bool
Backend string
MfaTokenProvider string
Prompt string
KeychainName string
PassDir string
PassCmd string
PassPrefix string
}

func ConfigureGlobals(app *kingpin.Application) {
Expand All @@ -47,10 +48,15 @@ func ConfigureGlobals(app *kingpin.Application) {
Envar("AWS_VAULT_BACKEND").
EnumVar(&GlobalFlags.Backend, backendsAvailable...)

app.Flag("prompt", fmt.Sprintf("Prompt driver to use %v", promptsAvailable)).
app.Flag("mfa-token-provider", fmt.Sprintf("Mfa token provider to use %v", mfaTokenProvidersAvailable)).
Default("terminal").
Envar("AWS_VAULT_MFA_TOKEN_PROVIDER").
EnumVar(&GlobalFlags.MfaTokenProvider, mfaTokenProvidersAvailable...)

app.Flag("prompt", fmt.Sprintf("Mfa token provider to use %v", mfaTokenProvidersAvailable)).
Hidden().
Envar("AWS_VAULT_PROMPT").
EnumVar(&GlobalFlags.PromptDriver, promptsAvailable...)
EnumVar(&GlobalFlags.Prompt, mfaTokenProvidersAvailable...)

app.Flag("keychain", "Name of macOS keychain to use, if it doesn't exist it will be created").
Default("aws-vault").
Expand All @@ -76,6 +82,13 @@ func ConfigureGlobals(app *kingpin.Application) {
keyring.Debug = true
}
log.Printf("aws-vault %s", app.Model().Version)

if GlobalFlags.Prompt != "" && GlobalFlags.MfaTokenProvider == "terminal" {
// --prompt is hidden but available for backwards compatibility, if we try to use --prompt to set
// GlobalFlags.MfaTokenProvider directly there's a race between --prompt and --mfa-token-provider so we set
// a different var then copy the value over
GlobalFlags.MfaTokenProvider = GlobalFlags.Prompt
}
if keyringImpl == nil {
var allowedBackends []keyring.BackendType
if GlobalFlags.Backend != "" {
Expand Down
2 changes: 1 addition & 1 deletion cli/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func ConfigureLoginCommand(app *kingpin.Application) {
StringVar(&input.ProfileName)

cmd.Action(func(c *kingpin.ParseContext) error {
input.Config.MfaPromptMethod = GlobalFlags.PromptDriver
input.Config.MfaTokenProvider = GlobalFlags.MfaTokenProvider
input.Config.NonChainedGetSessionTokenDuration = input.SessionDuration
input.Config.AssumeRoleDuration = input.SessionDuration
input.Config.GetFederationTokenDuration = input.SessionDuration
Expand Down
2 changes: 1 addition & 1 deletion cli/rotate.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func ConfigureRotateCommand(app *kingpin.Application) {
StringVar(&input.ProfileName)

cmd.Action(func(c *kingpin.ParseContext) error {
input.Config.MfaPromptMethod = GlobalFlags.PromptDriver
input.Config.MfaTokenProvider = GlobalFlags.MfaTokenProvider
input.Keyring = &vault.CredentialKeyring{Keyring: keyringImpl}
app.FatalIfError(RotateCommand(input), "rotate")
return nil
Expand Down
6 changes: 0 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs=
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4=
github.com/99designs/keyring v1.1.3 h1:mEV3iyZWjkxQ7R8ia8GcG97vCX5zQQ7n4o8R2BylwQY=
github.com/99designs/keyring v1.1.3/go.mod h1:657DQuMrBZRtuL/voxVyiyb6zpMehlm5vLB9Qwrv904=
github.com/99designs/keyring v1.1.4 h1:x0g0zQ9bQKgNsLo0XSXAy1H8Q1RG/td+5OXJt+Ci8b8=
github.com/99designs/keyring v1.1.4/go.mod h1:657DQuMrBZRtuL/voxVyiyb6zpMehlm5vLB9Qwrv904=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
Expand Down Expand Up @@ -56,8 +54,6 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191117063200-497ca9f6d64f h1:kz4KIr+xcPUsI3VMoqWfPMvtnJ6MGfiVwsWSVzphMO4=
golang.org/x/crypto v0.0.0-20191117063200-497ca9f6d64f/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200210222208-86ce3cb69678 h1:wCWoJcFExDgyYx2m2hpHgwz8W3+FPdfldvIgzqDIhyg=
golang.org/x/crypto v0.0.0-20200210222208-86ce3cb69678/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
Expand All @@ -67,8 +63,6 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 h1:LepdCS8Gf/MVejFIt8lsiexZATdoGVyp5bcyS+rYoUI=
golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191118133127-cf1e2d577169 h1:LPLFLulk2vyM7yI3CwNW64O6e8AxBmr9opfv14yI7HI=
golang.org/x/sys v0.0.0-20191118133127-cf1e2d577169/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
Expand Down
3 changes: 2 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ package main
import (
"os"

"github.com/99designs/aws-vault/cli"
"gopkg.in/alecthomas/kingpin.v2"

"github.com/99designs/aws-vault/cli"
)

// Version is provided at compile time
Expand Down
33 changes: 33 additions & 0 deletions mfa/kdialog.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package mfa

import (
"os/exec"
"strings"
)

func init() {
TokenProviders["kdialog"] = &KDialog{}
}

type KDialog struct {
Serial string
}

func (k *KDialog) GetToken() (string, error) {
cmd := exec.Command("kdialog", "--inputbox", defaultPrompt(k.Serial), "--title", "aws-vault")

out, err := cmd.Output()
if err != nil {
return "", err
}

return strings.TrimSpace(string(out)), nil
}

func (k *KDialog) SetSerial(mfaSerial string) {
k.Serial = mfaSerial
}

func (k *KDialog) GetSerial() string {
return k.Serial
}
18 changes: 18 additions & 0 deletions mfa/knowntoken.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package mfa

type KnownToken struct {
Token string
Serial string
}

func (k *KnownToken) GetToken() (string, error) {
return k.Token, nil
}

func (k *KnownToken) SetSerial(mfaSerial string) {
k.Serial = mfaSerial
}

func (k *KnownToken) GetSerial() string {
return k.Serial
}
38 changes: 38 additions & 0 deletions mfa/mfatokenprovider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package mfa

import (
"fmt"
)

// TokenProvider is an interface to provide an mfa token. It's intended that providers do whatever is necessary to get
// a token, eg prompt the use via the terminal or fetch it from a yubikey.
type TokenProvider interface {
GetToken() (string, error)
GetSerial() string
SetSerial(mfaSerial string)
}

func defaultPrompt(mfaSerial string) string {
return fmt.Sprintf("Enter token for %s: ", mfaSerial)
}

var TokenProviders = map[string]TokenProvider{
"terminal": &Terminal{},
}

func TokenProvidersAvailable() []string {
providers := []string{}
for k := range TokenProviders {
providers = append(providers, k)
}
return providers
}

func GetTokenProvider(s string) TokenProvider {
p, found := TokenProviders[s]
if !found {
panic(fmt.Sprintf("Prompt method %q doesn't exist", s))
}

return p
}
22 changes: 17 additions & 5 deletions prompt/osascript.go → mfa/osascript.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
package prompt
package mfa

import (
"fmt"
"os/exec"
"strings"
)

func OSAScriptPrompt(prompt string) (string, error) {
func init() {
TokenProviders["osascript"] = &OsaScript{}
}

type OsaScript struct {
Serial string
}

func (o *OsaScript) GetToken() (string, error) {
cmd := exec.Command("osascript", "-e", fmt.Sprintf(`
display dialog "%s" default answer "" buttons {"OK", "Cancel"} default button 1
text returned of the result
return result`,
prompt))
defaultPrompt(o.Serial)))

out, err := cmd.Output()
if err != nil {
Expand All @@ -21,6 +29,10 @@ func OSAScriptPrompt(prompt string) (string, error) {
return strings.TrimSpace(string(out)), nil
}

func init() {
Methods["osascript"] = OSAScriptPrompt
func (o *OsaScript) SetSerial(mfaSerial string) {
o.Serial = mfaSerial
}

func (o *OsaScript) GetSerial() string {
return o.Serial
}
21 changes: 21 additions & 0 deletions mfa/terminal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package mfa

import (
"github.com/99designs/aws-vault/prompt"
)

type Terminal struct {
Serial string
}

func (t *Terminal) GetToken() (string, error) {
return prompt.TerminalPrompt(defaultPrompt(t.Serial))
}

func (t *Terminal) SetSerial(mfaSerial string) {
t.Serial = mfaSerial
}

func (t *Terminal) GetSerial() string {
return t.Serial
}
45 changes: 45 additions & 0 deletions mfa/ykman.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package mfa

import (
"fmt"
"os/exec"
)

func init() {
TokenProviders["ykman"] = &YkMan{}
}

type YkMan struct {
Serial string
}

func (y *YkMan) GetToken() (otpToken string, err error) {
defer func() {
if err != nil {
fmt.Printf("unable to get otp from ykman: %s\n", err)

// something went wrong with getting a token from a ykman
// fall back to terminal prompt
tp := TokenProviders["terminal"]
tp.SetSerial(y.Serial)
otpToken, err = tp.GetToken()

}
}()

cmd := exec.Command("ykman", "oath", "code", "-s", y.Serial)
var out []byte
out, err = cmd.Output()
if err != nil {
return "", err
}
return string(out[:len(out)-1]), nil
}

func (y *YkMan) SetSerial(mfaSerial string) {
y.Serial = mfaSerial
}

func (y *YkMan) GetSerial() string {
return y.Serial
}
Loading

0 comments on commit 78573db

Please sign in to comment.