-
Notifications
You must be signed in to change notification settings - Fork 829
/
Copy pathrotate.go
199 lines (165 loc) · 5.5 KB
/
rotate.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
package cli
import (
"fmt"
"log"
"time"
"github.com/99designs/aws-vault/vault"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/iam"
"gopkg.in/alecthomas/kingpin.v2"
)
type RotateCommandInput struct {
NoSession bool
ProfileName string
Keyring *vault.CredentialKeyring
Config vault.Config
}
func ConfigureRotateCommand(app *kingpin.Application) {
input := RotateCommandInput{}
cmd := app.Command("rotate", "Rotates credentials")
cmd.Flag("no-session", "Use master credentials, no session or role used").
Short('n').
BoolVar(&input.NoSession)
cmd.Arg("profile", "Name of the profile").
Required().
HintAction(getProfileNames).
StringVar(&input.ProfileName)
cmd.Action(func(c *kingpin.ParseContext) error {
input.Config.MfaPromptMethod = GlobalFlags.PromptDriver
input.Keyring = &vault.CredentialKeyring{Keyring: keyringImpl}
app.FatalIfError(RotateCommand(input), "rotate")
return nil
})
}
func RotateCommand(input RotateCommandInput) error {
// Can't disable sessions completely, might need to use session for MFA-Protected API Access
vault.UseSession = !input.NoSession
vault.UseSessionCache = false
configLoader.BaseConfig = input.Config
configLoader.ActiveProfile = input.ProfileName
config, err := configLoader.LoadFromProfile(input.ProfileName)
if err != nil {
return err
}
masterCredentialsName, err := vault.MasterCredentialsFor(input.ProfileName, input.Keyring, config)
if err != nil {
return err
}
if input.NoSession {
fmt.Printf("Rotating credentials stored for profile '%s' using master credentials (takes 10-20 seconds)\n", masterCredentialsName)
} else {
fmt.Printf("Rotating credentials stored for profile '%s' using a session from profile '%s' (takes 10-20 seconds)\n", masterCredentialsName, input.ProfileName)
}
// Get the existing credentials access key ID
oldMasterCreds, err := vault.NewMasterCredentials(input.Keyring, masterCredentialsName).Get()
if err != nil {
return err
}
oldMasterCredsAccessKeyID := vault.FormatKeyForDisplay(oldMasterCreds.AccessKeyID)
log.Printf("Rotating access key %s\n", oldMasterCredsAccessKeyID)
fmt.Println("Creating a new access key")
// create a session to rotate the credentials
var sessCreds *credentials.Credentials
if input.NoSession {
sessCreds = vault.NewMasterCredentials(input.Keyring, config.ProfileName)
} else {
sessCreds, err = vault.NewTempCredentials(config, input.Keyring)
if err != nil {
return fmt.Errorf("Error getting temporary credentials: %w", err)
}
}
sess, err := vault.NewSession(sessCreds, config.Region)
if err != nil {
return err
}
// A username is needed for some IAM calls if the credentials have assumed a role
iamUserName, err := getUsernameIfAssumingRole(sess, config)
if err != nil {
return err
}
// Create a new access key
createOut, err := iam.New(sess).CreateAccessKey(&iam.CreateAccessKeyInput{
UserName: iamUserName,
})
if err != nil {
return err
}
fmt.Printf("Created new access key %s\n", vault.FormatKeyForDisplay(*createOut.AccessKey.AccessKeyId))
newMasterCreds := credentials.Value{
AccessKeyID: *createOut.AccessKey.AccessKeyId,
SecretAccessKey: *createOut.AccessKey.SecretAccessKey,
}
err = input.Keyring.Set(masterCredentialsName, newMasterCreds)
if err != nil {
return fmt.Errorf("Error storing new access key %s: %w", vault.FormatKeyForDisplay(newMasterCreds.AccessKeyID), err)
}
// Delete old sessions
sessions := input.Keyring.Sessions()
profileNames, err := getProfilesInChain(input.ProfileName, configLoader)
for _, profileName := range profileNames {
if n, _ := sessions.Delete(profileName); n > 0 {
fmt.Printf("Deleted %d sessions for %s\n", n, profileName)
}
}
// expire the cached credentials
sessCreds.Expire()
// Use new credentials to delete old access key
fmt.Printf("Deleting old access key %s\n", oldMasterCredsAccessKeyID)
err = retry(time.Second*20, time.Second*2, func() error {
_, err = iam.New(sess).DeleteAccessKey(&iam.DeleteAccessKeyInput{
AccessKeyId: &oldMasterCreds.AccessKeyID,
UserName: iamUserName,
})
return err
})
if err != nil {
return fmt.Errorf("Can't delete old access key %s: %w", oldMasterCredsAccessKeyID, err)
}
fmt.Printf("Deleted old access key %s\n", oldMasterCredsAccessKeyID)
fmt.Println("Finished rotating access key")
return nil
}
func retry(maxTime time.Duration, sleep time.Duration, f func() error) (err error) {
t0 := time.Now()
i := 0
for {
i++
err = f()
if err == nil {
return
}
elapsed := time.Since(t0)
if elapsed > maxTime {
return fmt.Errorf("After %d attempts, last error: %s", i, err)
}
time.Sleep(sleep)
log.Println("Retrying after error:", err)
}
}
func getUsernameIfAssumingRole(sess *session.Session, config *vault.Config) (*string, error) {
if config.RoleARN != "" {
n, err := vault.GetUsernameFromSession(sess)
if err != nil {
return nil, err
}
log.Printf("Found IAM username '%s'", n)
return &n, nil
}
return nil, nil
}
func getProfilesInChain(profileName string, configLoader *vault.ConfigLoader) (profileNames []string, err error) {
profileNames = append(profileNames, profileName)
config, err := configLoader.LoadFromProfile(profileName)
if err != nil {
return profileNames, err
}
if config.SourceProfile != nil {
newProfileNames, err := getProfilesInChain(config.SourceProfileName, configLoader)
if err != nil {
return profileNames, err
}
profileNames = append(profileNames, newProfileNames...)
}
return profileNames, nil
}