Skip to content
This repository has been archived by the owner on May 18, 2021. It is now read-only.

Commit

Permalink
Implement support for choosing MFA Provider and FactorType
Browse files Browse the repository at this point in the history
Order of precedence:
* CLI flags
* Environment variables
* Current profile
* Source profile
* `okta` "profile"

Update README
  • Loading branch information
lsowen committed Feb 1, 2019
1 parent 1da0ebb commit a8a6322
Show file tree
Hide file tree
Showing 7 changed files with 100 additions and 27 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,14 @@ role_arn = arn:aws:iam::<account-id>:role/<okta-role-name>
assume_role_ttl = 12h
```

#### Multi-factor Authentication (MFA) configuration

If you have a single MFA factor configured, that factor will be automatically selected. By default, if you have multiple available MFA factors, then you will be prompted to select which one to use. However, if you have multiple factors and want to specify which factor to use, you can do one of the following:

* Specify on the command line with `--mfa-provider` and `--mfa-factor-type`
* Specify with environment variables `AWS_OKTA_MFA_PROVIDER` and `AWS_OKTA_MFA_FACTOR_TYPE`
* Specify in your aws config with `mfa_provider` and `mfa_factor_type`

## Backends

We use 99design's keyring package that they use in `aws-vault`. Because of this, you can choose between different pluggable secret storage backends just like in `aws-vault`. You can either set your backend from the command line as a flag, or set the `AWS_OKTA_BACKEND` environment variable.
Expand Down
5 changes: 5 additions & 0 deletions cmd/add.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ func add(cmd *cobra.Command, args []string) error {
Domain: oktaDomain,
}

// Profiles aren't parsed during `add`, but still want
// to centralize the MFA config logic
var dummyProfiles lib.Profiles
updateMfaConfig(cmd, dummyProfiles, "", &mfaConfig)

if err := creds.Validate(mfaConfig); err != nil {
log.Debugf("Failed to validate credentials: %s", err)
return ErrFailedToValidateCredentials
Expand Down
2 changes: 2 additions & 0 deletions cmd/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ func execRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("Profile '%s' not found in your aws config. Use list command to see configured profiles.", profile)
}

updateMfaConfig(cmd, profiles, profile, &mfaConfig)

// check for an assume_role_ttl in the profile if we don't have a more explicit one
if !cmd.Flags().Lookup("assume-role-ttl").Changed {
if err := updateDurationFromConfigProfile(profiles, profile, &assumeRoleTTL); err != nil {
Expand Down
2 changes: 2 additions & 0 deletions cmd/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ func loginRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("Profile '%s' not found in your aws config", profile)
}

updateMfaConfig(cmd, profiles, profile, &mfaConfig)

// check for an assume_role_ttl in the profile if we don't have a more explicit one
if !cmd.Flags().Lookup("assume-role-ttl").Changed {
if err := updateDurationFromConfigProfile(profiles, profile, &assumeRoleTTL); err != nil {
Expand Down
48 changes: 38 additions & 10 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,6 @@ func prerun(cmd *cobra.Command, args []string) {
}
}

if !cmd.Flags().Lookup("mfa-duo-device").Changed {
mfaDeviceFromEnv, ok := os.LookupEnv("AWS_OKTA_MFA_DUO_DEVICE")
if ok {
mfaConfig.DuoDevice = mfaDeviceFromEnv
} else {
mfaConfig.DuoDevice = DefaultMFADuoDevice
}
}

if debug {
log.SetLevel(log.DebugLevel)
}
Expand Down Expand Up @@ -112,7 +103,44 @@ func init() {
for _, backendType := range keyring.AvailableBackends() {
backendsAvailable = append(backendsAvailable, string(backendType))
}
RootCmd.PersistentFlags().StringVarP(&mfaConfig.DuoDevice, "mfa-duo-device", "m", "phone1", "Device to use phone1, phone2, u2f or token")
RootCmd.PersistentFlags().StringVarP(&mfaConfig.Provider, "mfa-provider", "", "", "MFA Provider to use (eg DUO, OKTA, GOOGLE)")
RootCmd.PersistentFlags().StringVarP(&mfaConfig.FactorType, "mfa-factor-type", "", "", "MFA Factor Type to use (eg push, token:software:totp)")
RootCmd.PersistentFlags().StringVarP(&mfaConfig.DuoDevice, "mfa-duo-device", "", "phone1", "Device to use phone1, phone2, u2f or token")
RootCmd.PersistentFlags().StringVarP(&backend, "backend", "b", "", fmt.Sprintf("Secret backend to use %s", backendsAvailable))
RootCmd.PersistentFlags().BoolVarP(&debug, "debug", "d", false, "Enable debug logging")
}

func updateMfaConfig(cmd *cobra.Command, profiles lib.Profiles, profile string, config *lib.MFAConfig) {
if !cmd.Flags().Lookup("mfa-duo-device").Changed {
mfaDeviceFromEnv, ok := os.LookupEnv("AWS_OKTA_MFA_DUO_DEVICE")
if ok {
config.DuoDevice = mfaDeviceFromEnv
} else {
config.DuoDevice = DefaultMFADuoDevice
}
}

if !cmd.Flags().Lookup("mfa-provider").Changed {
mfaProvider, ok := os.LookupEnv("AWS_OKTA_MFA_PROVIDER")
if ok {
config.Provider = mfaProvider
} else {
mfaProvider, _, err := profiles.GetValue(profile, "mfa_provider")
if err == nil {
config.Provider = mfaProvider
}
}
}

if !cmd.Flags().Lookup("mfa-factor-type").Changed {
mfaFactorType, ok := os.LookupEnv("AWS_OKTA_MFA_FACTOR_TYPE")
if ok {
config.FactorType = mfaFactorType
} else {
mfaFactorType, _, err := profiles.GetValue(profile, "mfa_factor_type")
if err == nil {
config.FactorType = mfaFactorType
}
}
}
}
60 changes: 44 additions & 16 deletions lib/okta.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,25 +238,53 @@ func (o *OktaClient) AuthenticateProfile(profileARN string, duration time.Durati
return *samlResp.Credentials, sessionCookie, nil
}

func selectMFADevice(factors []OktaUserAuthnFactor) (*OktaUserAuthnFactor, error) {
if len(factors) > 1 {
log.Info("Select a MFA from the following list")
for i, f := range factors {
log.Infof("%d: %s (%s)", i, f.Provider, f.FactorType)
}
i, err := Prompt("Select MFA method", false)
if err != nil {
return nil, err
}
factor, err := strconv.Atoi(i)
if err != nil {
return nil, err
func selectMFADeviceFromConfig(o *OktaClient) (*OktaUserAuthnFactor, error) {
log.Debugf("MFAConfig: %v\n", o.MFAConfig)
if o.MFAConfig.Provider == "" || o.MFAConfig.FactorType == "" {
return nil, nil
}

for _, f := range o.UserAuth.Embedded.Factors {
log.Debugf("%v\n", f)
if strings.EqualFold(f.Provider, o.MFAConfig.Provider) && strings.EqualFold(f.FactorType, o.MFAConfig.FactorType) {
log.Debugf("Using matching factor \"%v %v\" from config\n", f.Provider, f.FactorType)
return &f, nil
}
return &factors[factor], nil
}

return nil, fmt.Errorf("Failed to select MFA device with Provider = \"%s\", FactorType = \"%s\"", o.MFAConfig.Provider, o.MFAConfig.FactorType)
}

func (o *OktaClient) selectMFADevice() (*OktaUserAuthnFactor, error) {
factors := o.UserAuth.Embedded.Factors
if len(factors) == 0 {
return nil, errors.New("No available MFA Factors")
} else if len(factors) == 1 {
return &factors[0], nil
}
return nil, errors.New("Failed to select MFA device")

factor, err := selectMFADeviceFromConfig(o)
if err != nil {
return nil, err
}

if factor != nil {
return factor, nil
}

log.Info("Select a MFA from the following list")
for i, f := range factors {
log.Infof("%d: %s (%s)", i, f.Provider, f.FactorType)
}
i, err := Prompt("Select MFA method", false)
if err != nil {
return nil, err
}
factorIdx, err := strconv.Atoi(i)
if err != nil {
return nil, err
}
return &factors[factorIdx], nil
}

func (o *OktaClient) preChallenge(oktaFactorId, oktaFactorType string) ([]byte, error) {
Expand Down Expand Up @@ -361,7 +389,7 @@ func (o *OktaClient) challengeMFA() (err error) {
var oktaFactorType string

log.Debugf("%s", o.UserAuth.StateToken)
factor, err := selectMFADevice(o.UserAuth.Embedded.Factors)
factor, err := o.selectMFADevice()
if err != nil {
log.Debug("Failed to select MFA device")
return
Expand Down
2 changes: 1 addition & 1 deletion lib/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ func (p *Provider) GetSAMLLoginURL() (*url.URL, error) {
}

provider := OktaProvider{
MFADevice: p.ProviderOptions.MFADevice,
MFAConfig: p.ProviderOptions.MFAConfig,
Keyring: p.keyring,
ProfileARN: profileARN,
SessionDuration: p.SessionDuration,
Expand Down

0 comments on commit a8a6322

Please sign in to comment.