Skip to content
This repository has been archived by the owner on Feb 21, 2023. It is now read-only.

Commit

Permalink
Allow choosing GUI pinentry as your auth prompt (#108)
Browse files Browse the repository at this point in the history
* Depend on pinentry-mac Homebrew formula

* Correct my preferred capitalisation if i'm here anyway.

* cmd/root.go: Add pinentry option.

* cli/login.go: Use pinentry, optionally.

Default to old password input, except with --pinentry mode.

We want to allow this option for when GUI apps call us in the background and we can't prompt for
password/TOTP via stdin/stdout as usual.  For now we're making it opt-in, so we don't "move too many
people's cheese".
  • Loading branch information
toothbrush authored Oct 15, 2021
1 parent 60ff68c commit c7bc309
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 7 deletions.
2 changes: 2 additions & 0 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ brews:
folder: Formula
homepage: https://github.com/redbubble/yak
description: A tool to log in to AWS through Okta
dependencies:
- name: pinentry-mac

test: |
system "#{bin}/yak --help"
Expand Down
45 changes: 39 additions & 6 deletions cli/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"syscall"
"time"

"github.com/gopasspw/pinentry"
"github.com/spf13/viper"
"golang.org/x/crypto/ssh/terminal"

Expand Down Expand Up @@ -313,12 +314,10 @@ func promptMFA(factor okta.AuthResponseFactor, stateToken string) (okta.OktaAuth
case "push":
authResponse, err = okta.VerifyPush(factor.Links.VerifyLink.Href, okta.PushRequest{stateToken})
case "token:software:totp":
fmt.Fprintf(os.Stderr, "Okta MFA token (from %s): ", okta.TotpFactorName(factor.Provider))
passCode, _ := getLine()
passCode, _ := promptOrPinentry(fmt.Sprintf("Okta MFA token (from %s): ", okta.TotpFactorName(factor.Provider)), false)
authResponse, err = okta.VerifyTotp(factor.Links.VerifyLink.Href, okta.TotpRequest{stateToken, passCode})
case "token:hardware":
fmt.Fprintf(os.Stderr, "Okta MFA token (from %s): ", okta.TotpFactorName(factor.Provider))
passCode, _ := getLine()
passCode, _ := promptOrPinentry(fmt.Sprintf("Okta MFA token (from %s): ", okta.TotpFactorName(factor.Provider)), false)
authResponse, err = okta.VerifyTotp(factor.Links.VerifyLink.Href, okta.TotpRequest{stateToken, passCode})
default:
err := errors.New("Unknown factor type selected. Exiting.")
Expand Down Expand Up @@ -365,8 +364,7 @@ func promptLogin() (okta.OktaAuthResponse, error) {
prompt = prompt + " (" + username + ")"
}

fmt.Fprintf(os.Stderr, "%s: ", prompt)
password, err = getPassword()
password, err = promptOrPinentry(fmt.Sprintf("%s: ", prompt), true)

if err != nil {
return authResponse, err
Expand Down Expand Up @@ -395,6 +393,41 @@ func CacheLoginRoles(roles []saml.LoginRole) {
cache.WriteDefault("aws:roles", data)
}

func promptOrPinentry(prompt string, secret bool) (string, error) {
// Whether to use pinentry for (GUI) password prompt, or the original way
if viper.GetBool("pinentry") {
return getPinentry(prompt, secret)
} else {
fmt.Fprintf(os.Stderr, prompt)
// If it's secret, don't echo the user's response.
if secret {
return getPassword()
} else {
return getLine()
}
}
}

func getPinentry(prompt string, secret bool) (string, error) {
p, err := pinentry.New()
if err != nil {
return "", fmt.Errorf("pinentry (%s) error: %w", pinentry.GetBinary(), err)
}
defer p.Close()

_ = p.Set("title", "yak")
_ = p.Set("desc", prompt)
_ = p.Set("prompt", "Input:")
_ = p.Set("ok", "OK")
pw, err := p.GetPin()
if err != nil {
return "", fmt.Errorf("pinentry (%s) error: %w", pinentry.GetBinary(), err)
}

pass := string(pw)
return pass, nil
}

func getPassword() (string, error) {
bytes, err := terminal.ReadPassword(int(syscall.Stdin))
fmt.Fprint(os.Stderr, "\n")
Expand Down
4 changes: 3 additions & 1 deletion cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ func init() {
rootCmd.PersistentFlags().Int64P("aws-session-duration", "d", 0, "The session duration to request from AWS (in seconds)")
rootCmd.PersistentFlags().Bool("no-cache", false, "Ignore cache for this request. Mutually exclusive with --cache-only")
rootCmd.PersistentFlags().Bool("cache-only", false, "Only use cache, do not make external requests. Mutually exclusive with --no-cache")
rootCmd.PersistentFlags().Bool("pinentry", false, "Use the pinentry to prompt for credentials, instead of terminal (useful for GUI applications)")
viper.BindPFlag("okta.username", rootCmd.PersistentFlags().Lookup("okta-username"))
viper.BindPFlag("okta.domain", rootCmd.PersistentFlags().Lookup("okta-domain"))
viper.BindPFlag("okta.aws_saml_endpoint", rootCmd.PersistentFlags().Lookup("okta-aws-saml-endpoint"))
Expand All @@ -152,6 +153,7 @@ func init() {
viper.BindPFlag("cache.no_cache", rootCmd.PersistentFlags().Lookup("no-cache"))
viper.BindPFlag("cache.cache_only", rootCmd.PersistentFlags().Lookup("cache-only"))
viper.BindPFlag("output.format", rootCmd.PersistentFlags().Lookup("output-format"))
viper.BindPFlag("pinentry", rootCmd.PersistentFlags().Lookup("pinentry"))
}

func versionCmd() {
Expand All @@ -166,7 +168,7 @@ IFYgICBWIFkgLwogICAgICAgfHxWdlZ2Vnx8CiAgICAgICB8fCAgICAgfHwK`)
}

func creditsCmd() {
contributors := []string{"Adam Thalhammer", "Amanda Koh", "Dave Schweisguth", "John Murphy", "Kaitlyn Mesa", "Lucas Wilson-Richter", "Michael Vigilante", "Nova Tan", "Paul David"}
contributors := []string{"Adam Thalhammer", "Amanda Koh", "Dave Schweisguth", "John Murphy", "Kaitlyn Mesa", "Lucas Wilson-Richter", "Michael Vigilante", "Nova Tan", "paul david"}

fmt.Println("Contributors:")

Expand Down
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
github.com/BurntSushi/toml v0.3.1 // indirect
github.com/aws/aws-sdk-go v1.35.5
github.com/fsnotify/fsnotify v1.4.7 // indirect
github.com/gopasspw/pinentry v0.0.0-00010101000000-000000000000
github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/magiconair/properties v1.7.6 // indirect
Expand All @@ -24,3 +25,5 @@ require (
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2
golang.org/x/net v0.0.0-20200202094626-16171245cfb2
)

replace github.com/gopasspw/pinentry => github.com/redbubble/pinentry v0.0.3-0.20211015012734-36081cf01f93
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ github.com/pelletier/go-toml v1.1.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/9
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/redbubble/pinentry v0.0.3-0.20211015012734-36081cf01f93 h1:eZbYwO7kGCBZMmErJVT8sDpA0gtxSDRTBx1/nZAoP4s=
github.com/redbubble/pinentry v0.0.3-0.20211015012734-36081cf01f93/go.mod h1:lR1WuNI96rXXBCgM601Ima3acnX3ZSPthIAuG6lHa68=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/spf13/afero v1.1.0 h1:bopulORc2JeYaxfHLvJa5NzxviA9PoWhpiiJkru7Ji4=
Expand Down

0 comments on commit c7bc309

Please sign in to comment.