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

refactor prompts to mfa-token-providers #533

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
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.
j0hnsmith marked this conversation as resolved.
Show resolved Hide resolved

```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
j0hnsmith marked this conversation as resolved.
Show resolved Hide resolved
```

[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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
MfaTokenProvider 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)).
j0hnsmith marked this conversation as resolved.
Show resolved Hide resolved
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