Skip to content

Commit 946a9d3

Browse files
author
Chris Gilmer
authored
Merge pull request #68 from trussworks/cg_no_mfa
Allow users to assume MFA device is already configured
2 parents bf9e57b + 243655c commit 946a9d3

File tree

3 files changed

+70
-9
lines changed

3 files changed

+70
-9
lines changed

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,16 @@ region=us-west-2
7878
output=json
7979
```
8080

81+
### MFA Management
82+
83+
This tool will help create and enable a virtual MFA device. The interface for the MFA device is a QR code
84+
which will be shown to the user during setup. This QR code can be used with a password manager to provide the
85+
One Time Passwords (OTP) values asked for in the script.
86+
87+
In the case where the user has a virtual MFA device already set up they can choose not to provision a new one.
88+
This is done by issuing the `--no-mfa` flag on the command line in conjunction with the regular command from
89+
above.
90+
8191
## Development setup
8292

8393
1. First, install these packages: `brew install pre-commit direnv go`

cmd/cli.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ const (
2727
IAMRoleFlag string = "iam-role"
2828

2929
// OutputFlag is the Output Flag
30-
OutputFlag = "output"
30+
OutputFlag string = "output"
31+
32+
// NoMFAFlag indicates that no MFA device should be configured as one exists
33+
NoMFAFlag string = "no-mfa"
3134

3235
// VerboseFlag is the Verbose Flag
3336
VerboseFlag string = "verbose"

cmd/setup.go

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package main
22

33
import (
4+
"errors"
45
"fmt"
56
"io/ioutil"
67
"log"
@@ -47,8 +48,11 @@ func setupUserInitFlags(flag *pflag.FlagSet) {
4748
flag.String(IAMRoleFlag, "", "The IAM role name assigned to the user being setup")
4849
flag.String(OutputFlag, "json", "The AWS CLI output format")
4950

51+
// No MFA Setup
52+
flag.Bool(NoMFAFlag, false, "When present do not provision an MFA device, assume one exists")
53+
5054
// Verbose
51-
flag.BoolP(VerboseFlag, "v", false, "log messages at the debug level.")
55+
flag.BoolP(VerboseFlag, "v", false, "log messages at the debug level")
5256

5357
flag.SortFlags = false
5458
}
@@ -93,6 +97,7 @@ type User struct {
9397
SecretAccessKey string
9498
QrTempFile *os.File
9599
Keyring *keyring.Keyring
100+
NoMFA bool
96101
}
97102

98103
// Setup orchestrates the tasks to create the user's MFA and rotate access
@@ -108,14 +113,21 @@ func (u *User) Setup(logger *log.Logger) {
108113
logger.Fatal(err)
109114
}
110115

111-
err = u.CreateVirtualMFADevice(logger)
112-
if err != nil {
113-
logger.Fatal(err)
114-
}
116+
if u.NoMFA {
117+
err = u.GetMFADevice(logger)
118+
if err != nil {
119+
logger.Fatal(err)
120+
}
121+
} else {
122+
err = u.CreateVirtualMFADevice(logger)
123+
if err != nil {
124+
logger.Fatal(err)
125+
}
115126

116-
err = u.EnableVirtualMFADevice(logger)
117-
if err != nil {
118-
logger.Fatal(err)
127+
err = u.EnableVirtualMFADevice(logger)
128+
if err != nil {
129+
logger.Fatal(err)
130+
}
119131
}
120132

121133
err = u.UpdateAWSConfigFile(logger)
@@ -200,6 +212,40 @@ func (u *User) newMFASession(logger *log.Logger) (*session.Session, error) {
200212
return mfaSession, nil
201213
}
202214

215+
// GetMFADevice gets the user's existing virtual MFA device and updates the
216+
// MFA serial in the profile field.
217+
func (u *User) GetMFADevice(logger *log.Logger) error {
218+
logger.Println("Getting the existing MFA device...")
219+
220+
sess, err := u.newSession()
221+
if err != nil {
222+
return fmt.Errorf("unable to get new session: %w", err)
223+
}
224+
svc := iam.New(sess)
225+
226+
mfaDeviceInput := &iam.ListMFADevicesInput{
227+
UserName: aws.String(u.Name),
228+
}
229+
230+
mfaDeviceOutput, err := svc.ListMFADevices(mfaDeviceInput)
231+
if err != nil {
232+
return fmt.Errorf("unable to get MFA: %w", err)
233+
}
234+
235+
if len(mfaDeviceOutput.MFADevices) == 0 {
236+
return errors.New("no MFA devices registered")
237+
}
238+
if len(mfaDeviceOutput.MFADevices) > 1 {
239+
return errors.New("more than one MFA device registered, no way to choose")
240+
}
241+
mfaDevice := mfaDeviceOutput.MFADevices[0]
242+
243+
u.BaseProfile.MFASerial = *mfaDevice.SerialNumber
244+
u.RoleProfile.MFASerial = *mfaDevice.SerialNumber
245+
246+
return nil
247+
}
248+
203249
// CreateVirtualMFADevice creates the user's virtual MFA device and updates the
204250
// MFA serial in the profile field.
205251
func (u *User) CreateVirtualMFADevice(logger *log.Logger) error {
@@ -572,6 +618,7 @@ func setupUserFunction(cmd *cobra.Command, args []string) error {
572618
iamUser := v.GetString(IAMUserFlag)
573619
iamRole := v.GetString(IAMRoleFlag)
574620
output := v.GetString(OutputFlag)
621+
noMFA := v.GetBool(NoMFAFlag)
575622

576623
// Validator used to validate input options for MFA
577624
validate = validator.New()
@@ -628,6 +675,7 @@ func setupUserFunction(cmd *cobra.Command, args []string) error {
628675
Config: config,
629676
QrTempFile: tempfile,
630677
Keyring: keyring,
678+
NoMFA: noMFA,
631679
}
632680
err = checkExistingAWSProfile(baseProfile.Name, config, logger)
633681
if err != nil {

0 commit comments

Comments
 (0)