@@ -9,13 +9,18 @@ import (
9
9
10
10
"github.com/aws/aws-sdk-go/aws"
11
11
"github.com/hashicorp/vault/sdk/framework"
12
+ "github.com/hashicorp/vault/sdk/helper/automatedrotationutil"
12
13
"github.com/hashicorp/vault/sdk/helper/pluginidentityutil"
13
14
"github.com/hashicorp/vault/sdk/helper/pluginutil"
14
15
"github.com/hashicorp/vault/sdk/logical"
16
+ "github.com/hashicorp/vault/sdk/rotation"
15
17
)
16
18
17
19
// A single default template that supports both the different credential types (IAM/STS) that are capped at differing length limits (64 chars/32 chars respectively)
18
- const defaultUserNameTemplate = `{{ if (eq .Type "STS") }}{{ printf "vault-%s-%s" (unix_time) (random 20) | truncate 32 }}{{ else }}{{ printf "vault-%s-%s-%s" (printf "%s-%s" (.DisplayName) (.PolicyName) | truncate 42) (unix_time) (random 20) | truncate 64 }}{{ end }}`
20
+ const (
21
+ defaultUserNameTemplate = `{{ if (eq .Type "STS") }}{{ printf "vault-%s-%s" (unix_time) (random 20) | truncate 32 }}{{ else }}{{ printf "vault-%s-%s-%s" (printf "%s-%s" (.DisplayName) (.PolicyName) | truncate 42) (unix_time) (random 20) | truncate 64 }}{{ end }}`
22
+ rootRotationJobName = "aws-root-creds"
23
+ )
19
24
20
25
func pathConfigRoot (b * backend ) * framework.Path {
21
26
p := & framework.Path {
@@ -95,6 +100,7 @@ func pathConfigRoot(b *backend) *framework.Path {
95
100
HelpDescription : pathConfigRootHelpDesc ,
96
101
}
97
102
pluginidentityutil .AddPluginIdentityTokenFields (p .Fields )
103
+ automatedrotationutil .AddAutomatedRotationFields (p .Fields )
98
104
99
105
return p
100
106
}
@@ -103,20 +109,14 @@ func (b *backend) pathConfigRootRead(ctx context.Context, req *logical.Request,
103
109
b .clientMutex .RLock ()
104
110
defer b .clientMutex .RUnlock ()
105
111
106
- entry , err := req . Storage . Get (ctx , "config/root" )
112
+ config , exists , err := getConfigFromStorage (ctx , req )
107
113
if err != nil {
108
114
return nil , err
109
115
}
110
- if entry == nil {
116
+ if ! exists {
111
117
return nil , nil
112
118
}
113
119
114
- var config rootConfig
115
-
116
- if err := entry .DecodeJSON (& config ); err != nil {
117
- return nil , err
118
- }
119
-
120
120
configData := map [string ]interface {}{
121
121
"access_key" : config .AccessKey ,
122
122
"region" : config .Region ,
@@ -131,6 +131,8 @@ func (b *backend) pathConfigRootRead(ctx context.Context, req *logical.Request,
131
131
}
132
132
133
133
config .PopulatePluginIdentityTokenData (configData )
134
+ config .PopulateAutomatedRotationData (configData )
135
+
134
136
return & logical.Response {
135
137
Data : configData ,
136
138
}, nil
@@ -158,6 +160,12 @@ func (b *backend) pathConfigRootWrite(ctx context.Context, req *logical.Request,
158
160
b .clientMutex .Lock ()
159
161
defer b .clientMutex .Unlock ()
160
162
163
+ // check for existing config
164
+ previousCfg , previousCfgExists , err := getConfigFromStorage (ctx , req )
165
+ if err != nil {
166
+ return nil , err
167
+ }
168
+
161
169
rc := rootConfig {
162
170
AccessKey : data .Get ("access_key" ).(string ),
163
171
SecretKey : data .Get ("secret_key" ).(string ),
@@ -174,6 +182,9 @@ func (b *backend) pathConfigRootWrite(ctx context.Context, req *logical.Request,
174
182
if err := rc .ParsePluginIdentityTokenFields (data ); err != nil {
175
183
return logical .ErrorResponse (err .Error ()), nil
176
184
}
185
+ if err := rc .ParseAutomatedRotationFields (data ); err != nil {
186
+ return logical .ErrorResponse (err .Error ()), nil
187
+ }
177
188
178
189
if rc .IdentityTokenAudience != "" && rc .AccessKey != "" {
179
190
return logical .ErrorResponse ("only one of 'access_key' or 'identity_token_audience' can be set" ), nil
@@ -195,12 +206,54 @@ func (b *backend) pathConfigRootWrite(ctx context.Context, req *logical.Request,
195
206
}
196
207
}
197
208
198
- entry , err := logical .StorageEntryJSON ("config/root" , rc )
199
- if err != nil {
200
- return nil , err
209
+ // Save the initial config only if it does not already exist
210
+ if ! previousCfgExists {
211
+ if err := putConfigToStorage (ctx , req , & rc ); err != nil {
212
+ return nil , err
213
+ }
201
214
}
202
215
203
- if err := req .Storage .Put (ctx , entry ); err != nil {
216
+ // Now that the root config is set up, register the rotation job if it required
217
+ if rc .ShouldRegisterRotationJob () {
218
+ cfgReq := & rotation.RotationJobConfigureRequest {
219
+ Name : rootRotationJobName ,
220
+ MountPoint : req .MountPoint ,
221
+ ReqPath : req .Path ,
222
+ RotationSchedule : rc .RotationSchedule ,
223
+ RotationWindow : rc .RotationWindow ,
224
+ RotationPeriod : rc .RotationPeriod ,
225
+ }
226
+
227
+ rotationJob , err := rotation .ConfigureRotationJob (cfgReq )
228
+ if err != nil {
229
+ return logical .ErrorResponse ("error configuring rotation job: %s" , err ), nil
230
+ }
231
+
232
+ b .Logger ().Debug ("Registering rotation job" , "mount" , req .MountPoint + req .Path )
233
+ rotationID , err := b .System ().RegisterRotationJob (ctx , rotationJob )
234
+ if err != nil {
235
+ return logical .ErrorResponse ("error registering rotation job: %s" , err ), nil
236
+ }
237
+
238
+ rc .RotationID = rotationID
239
+ }
240
+
241
+ // Disable Automated Rotation and Deregister credentials if required
242
+ if rc .DisableAutomatedRotation {
243
+ // Ensure de-registering only occurs on updates and if
244
+ // a credential has actually been registered
245
+ if previousCfgExists && previousCfg .RotationID != "" {
246
+ err := b .System ().DeregisterRotationJob (ctx , previousCfg .RotationID )
247
+ if err != nil {
248
+ return logical .ErrorResponse ("error de-registering rotation job: %s" , err ), nil
249
+ }
250
+
251
+ rc .RotationID = ""
252
+ }
253
+ }
254
+
255
+ // update config entry with rotation ID
256
+ if err := putConfigToStorage (ctx , req , & rc ); err != nil {
204
257
return nil , err
205
258
}
206
259
@@ -212,8 +265,40 @@ func (b *backend) pathConfigRootWrite(ctx context.Context, req *logical.Request,
212
265
return nil , nil
213
266
}
214
267
268
+ func getConfigFromStorage (ctx context.Context , req * logical.Request ) (* rootConfig , bool , error ) {
269
+ entry , err := req .Storage .Get (ctx , "config/root" )
270
+ if err != nil {
271
+ return nil , false , err
272
+ }
273
+ if entry == nil {
274
+ return nil , false , nil
275
+ }
276
+
277
+ var config rootConfig
278
+
279
+ if err := entry .DecodeJSON (& config ); err != nil {
280
+ return nil , false , err
281
+ }
282
+
283
+ return & config , true , nil
284
+ }
285
+
286
+ func putConfigToStorage (ctx context.Context , req * logical.Request , rc * rootConfig ) error {
287
+ entry , err := logical .StorageEntryJSON ("config/root" , rc )
288
+ if err != nil {
289
+ return err
290
+ }
291
+
292
+ if err := req .Storage .Put (ctx , entry ); err != nil {
293
+ return err
294
+ }
295
+
296
+ return nil
297
+ }
298
+
215
299
type rootConfig struct {
216
300
pluginidentityutil.PluginIdentityTokenParams
301
+ automatedrotationutil.AutomatedRotationParams
217
302
218
303
AccessKey string `json:"access_key"`
219
304
SecretKey string `json:"secret_key"`
0 commit comments