Skip to content

Commit bf9e57b

Browse files
author
Chris Gilmer
authored
Merge pull request #66 from trussworks/cg_new_cli_lib
Switch jessevdk/go-flags for spf13/cobra
2 parents 2cc6d0f + f42530f commit bf9e57b

File tree

8 files changed

+1223
-634
lines changed

8 files changed

+1223
-634
lines changed

README.md

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,36 +27,42 @@ brew cask install aws-vault
2727

2828
Before running this tool, you will need to following pieces of information
2929

30-
* IAM role - This is the IAM Role with permissions allowing access to AWS APIs
31-
and services. This is usually something like `admin` or `engineer`.
32-
* IAM user name - This is your IAM username.
30+
* IAM role name - This is the IAM Role with permissions allowing access to AWS APIs
31+
and services. This is usually something like `admin` or `engineer`. Use the flag
32+
`--iam-role` with this value.
33+
* IAM user name - This is your IAM username. Use the flag `--iam-user` with this value.
3334
* AWS profile - This is the name that populates your `~/.aws/config` profile
3435
name. It is usually the name of the aws account alias you are trying to access.
36+
Use the flag name `--aws-profile` with this value.
3537
* AWS account Id - This is the 12-digit account number of the AWS account you
36-
are trying to access.
38+
are trying to access. Use the flag `--aws-account-id` with this value.
3739
* Temporary AWS access keys - These should be given to you by an administrator
3840
of the AWS account you are trying to access. The tool will prompt you for
3941
the access key id and secret access key.
4042

4143
## Running the tool
4244

43-
1. Run the setup-new-user - `setup-new-aws-user --role <IAM_ROLE> --iam-user <USER> --profile=<AWS_PROFILE> --account-id=<AWS_ACCOUNT_ID>`
45+
1. Run the setup-new-user script - `setup-new-aws-user setup --iam-role <IAM_ROLE> --iam-user <USER> --aws-profile=<AWS_PROFILE> --aws-account-id=<AWS_ACCOUNT_ID>`
4446
2. Enter the access keys generated when prompted.
45-
4647
3. The script will open a window with a QR code, which you will use to configure a temporary one time password (TOTP).
4748
4. You'll then need to create a new entry in your 1Password account configure it with a TOTP field.
4849
5. Use 1Password to scan the QR code and hit save. New TOTP tokens should generate every 30 seconds.
4950
6. From here the tool will prompt you for 3 unique TOTP tokens. **NOTE Take care not to use the same token more than once, as this will cause the process to fail.**
5051
7. Once the tool has completed, you should be able to access the AWS account. You can run the following command filling in the AWS_PROFILE value
5152

5253
```shell
53-
aws-vault exec AWS_PROFILE -- aws sts get-session
54+
aws-vault exec $AWS_PROFILE -- aws sts get-session
5455
```
5556

5657
## How this tool modifies your ~/.aws/config
5758

58-
While your AWS access keys are stored in a password protected keychain managed by `aws-vault`, the configuration for how you should access AWS accounts lives in ~/.aws/config. The setup-new-aws-user tool creates two profiles your `~/.aws/config`. The first is the base profile containing your long lived AWS Access Keys and is tied to your IAM user and MFA device. Since these keys are long lived, you should be rotating them regularly with `aws-vault rotate`. The second profile is the IAM role granting you elevated access to the AWS account. Typically these IAM roles are named `admin` or `engineer` and only uses temporary credentials leveraging AWS's Security Token Service (STS). Below is an example config generated from this tool.
59-
59+
While your AWS access keys are stored in a password protected keychain managed by `aws-vault`, the configuration for
60+
how you should access AWS accounts lives in ~/.aws/config. The setup-new-aws-user tool creates two profiles your
61+
`~/.aws/config`. The first is the base profile containing your long lived AWS Access Keys and is tied to your IAM user
62+
and MFA device. Since these keys are long lived, you should be rotating them regularly with `aws-vault rotate`.
63+
The second profile is the IAM role granting you elevated access to the AWS account. Typically these IAM roles are
64+
named `admin` or `engineer` and only uses temporary credentials leveraging AWS's Security Token Service (STS).
65+
Below is an example config generated from this tool.
6066

6167
```ini
6268
[profile corp-id-base]
@@ -86,6 +92,7 @@ output=json
8692
Run pre-commit and Go tests
8793

8894
```shell
95+
pre-commit run -a
8996
make test
9097
```
9198

@@ -102,7 +109,7 @@ use the real AWS account ID.
102109
Example:
103110

104111
```shell
105-
go run cmd/main.go --role engineer --iam-user testuser --account-id 123456789012 --profile test-profile-name
112+
go run ./cmd setup --iam-role engineer --iam-user testuser --aws-profile test-profile-name --aws-account-id 123456789012
106113
```
107114

108115
After running the script, try a command to ensure the new profile works as

cmd/cli.go

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"regexp"
6+
7+
"github.com/aws/aws-sdk-go/aws/endpoints"
8+
"github.com/spf13/viper"
9+
)
10+
11+
const (
12+
// AWSRegionFlag is the generic AWS Region Flag
13+
AWSRegionFlag string = "aws-region"
14+
// AWSAccountIDFlag is the AWS AccountID Flag
15+
AWSAccountIDFlag string = "aws-account-id"
16+
17+
// VaultAWSKeychainNameFlag is the aws-vault keychain name Flag
18+
VaultAWSKeychainNameFlag string = "aws-vault-keychain-name"
19+
// VaultAWSKeychainNameDefault is the aws-vault default keychain name
20+
VaultAWSKeychainNameDefault string = "login"
21+
// VaultAWSProfileFlag is the aws-vault profile name Flag
22+
VaultAWSProfileFlag string = "aws-profile"
23+
24+
// IAMUserFlag is the IAM User name Flag
25+
IAMUserFlag string = "iam-user"
26+
// IAMRoleFlag is the IAM Role name Flag
27+
IAMRoleFlag string = "iam-role"
28+
29+
// OutputFlag is the Output Flag
30+
OutputFlag = "output"
31+
32+
// VerboseFlag is the Verbose Flag
33+
VerboseFlag string = "verbose"
34+
)
35+
36+
func stringSliceContains(stringSlice []string, value string) bool {
37+
for _, x := range stringSlice {
38+
if value == x {
39+
return true
40+
}
41+
}
42+
return false
43+
}
44+
45+
type errInvalidKeychainName struct {
46+
KeychainName string
47+
}
48+
49+
func (e *errInvalidKeychainName) Error() string {
50+
return fmt.Sprintf("invalid keychain name '%s'", e.KeychainName)
51+
}
52+
53+
type errInvalidAWSProfile struct {
54+
Profile string
55+
}
56+
57+
func (e *errInvalidAWSProfile) Error() string {
58+
return fmt.Sprintf("invalid aws profile '%s'", e.Profile)
59+
}
60+
61+
type errInvalidVault struct {
62+
KeychainName string
63+
Profile string
64+
}
65+
66+
func (e *errInvalidVault) Error() string {
67+
return fmt.Sprintf("invalid keychain name %q or profile %q", e.KeychainName, e.Profile)
68+
}
69+
70+
func checkVault(v *viper.Viper) error {
71+
// Both keychain name and profile are required or both must be missing
72+
keychainName := v.GetString(VaultAWSKeychainNameFlag)
73+
keychainNames := []string{
74+
VaultAWSKeychainNameDefault,
75+
}
76+
if len(keychainName) > 0 && !stringSliceContains(keychainNames, keychainName) {
77+
return fmt.Errorf("%s is invalid, expected %v: %w", VaultAWSKeychainNameFlag, keychainNames, &errInvalidKeychainName{KeychainName: keychainName})
78+
}
79+
80+
awsProfile := v.GetString(VaultAWSProfileFlag)
81+
if len(awsProfile) == 0 {
82+
return fmt.Errorf("%s must not be empty: %w", VaultAWSProfileFlag, &errInvalidAWSProfile{Profile: awsProfile})
83+
}
84+
85+
return nil
86+
}
87+
88+
type errInvalidRegion struct {
89+
Region string
90+
}
91+
92+
func (e *errInvalidRegion) Error() string {
93+
return fmt.Sprintf("invalid region %q", e.Region)
94+
}
95+
96+
func checkRegion(v *viper.Viper) error {
97+
98+
r := v.GetString(AWSRegionFlag)
99+
if _, ok := endpoints.PartitionForRegion(endpoints.DefaultPartitions(), r); !ok {
100+
return fmt.Errorf("%s is invalid: %w", AWSRegionFlag, &errInvalidRegion{Region: r})
101+
}
102+
103+
return nil
104+
}
105+
106+
type errInvalidAccountID struct {
107+
AccountID string
108+
}
109+
110+
func (e *errInvalidAccountID) Error() string {
111+
return fmt.Sprintf("invalid Account ID %q", e.AccountID)
112+
}
113+
114+
func checkAccountID(v *viper.Viper) error {
115+
id := v.GetString(AWSAccountIDFlag)
116+
if matched, err := regexp.Match(`\d[12]`, []byte(id)); !matched || err != nil {
117+
return fmt.Errorf("%s must be a 12 digit number: %w", AWSAccountIDFlag, &errInvalidAccountID{AccountID: id})
118+
}
119+
120+
return nil
121+
}
122+
123+
type errInvalidIAMUser struct {
124+
IAMUser string
125+
}
126+
127+
func (e *errInvalidIAMUser) Error() string {
128+
return fmt.Sprintf("invalid output %q", e.IAMUser)
129+
}
130+
131+
func checkIAMUser(v *viper.Viper) error {
132+
133+
user := v.GetString(IAMUserFlag)
134+
if len(user) == 0 {
135+
return fmt.Errorf("%s is invalid: %w", IAMUserFlag, &errInvalidIAMUser{IAMUser: user})
136+
}
137+
138+
return nil
139+
}
140+
141+
type errInvalidIAMRole struct {
142+
IAMRole string
143+
}
144+
145+
func (e *errInvalidIAMRole) Error() string {
146+
return fmt.Sprintf("invalid output %q", e.IAMRole)
147+
}
148+
149+
func checkIAMRole(v *viper.Viper) error {
150+
151+
role := v.GetString(IAMRoleFlag)
152+
if len(role) == 0 {
153+
return fmt.Errorf("%s is invalid: %w", IAMRoleFlag, &errInvalidIAMRole{IAMRole: role})
154+
}
155+
156+
return nil
157+
}
158+
159+
type errInvalidOutput struct {
160+
Output string
161+
}
162+
163+
func (e *errInvalidOutput) Error() string {
164+
return fmt.Sprintf("invalid output %q", e.Output)
165+
}
166+
167+
func checkOutput(v *viper.Viper) error {
168+
169+
o := v.GetString(OutputFlag)
170+
outputTypes := []string{
171+
"text",
172+
"json",
173+
"yaml",
174+
"table",
175+
}
176+
if len(o) > 0 && !stringSliceContains(outputTypes, o) {
177+
return fmt.Errorf("%s is invalid, expected one of %v: %w", OutputFlag, outputTypes, &errInvalidOutput{Output: o})
178+
}
179+
180+
return nil
181+
}

0 commit comments

Comments
 (0)