forked from hashicorp/vault
-
Notifications
You must be signed in to change notification settings - Fork 0
/
path_user.go
239 lines (212 loc) · 7.16 KB
/
path_user.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
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
package aws
import (
"context"
"fmt"
"strings"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/iam"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/vault/helper/strutil"
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/logical/framework"
"github.com/mitchellh/mapstructure"
)
func pathUser(b *backend) *framework.Path {
return &framework.Path{
Pattern: "(creds|sts)/" + framework.GenericNameRegex("name"),
Fields: map[string]*framework.FieldSchema{
"name": &framework.FieldSchema{
Type: framework.TypeString,
Description: "Name of the role",
},
"role_arn": &framework.FieldSchema{
Type: framework.TypeString,
Description: "ARN of role to assume when credential_type is " + assumedRoleCred,
},
"ttl": &framework.FieldSchema{
Type: framework.TypeDurationSecond,
Description: "Lifetime of the returned credentials in seconds",
Default: 3600,
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.ReadOperation: b.pathCredsRead,
logical.UpdateOperation: b.pathCredsRead,
},
HelpSynopsis: pathUserHelpSyn,
HelpDescription: pathUserHelpDesc,
}
}
func (b *backend) pathCredsRead(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
roleName := d.Get("name").(string)
// Read the policy
role, err := b.roleRead(ctx, req.Storage, roleName, true)
if err != nil {
return nil, errwrap.Wrapf("error retrieving role: {{err}}", err)
}
if role == nil {
return logical.ErrorResponse(fmt.Sprintf(
"Role '%s' not found", roleName)), nil
}
ttl := int64(d.Get("ttl").(int))
roleArn := d.Get("role_arn").(string)
var credentialType string
switch {
case len(role.CredentialTypes) == 1:
credentialType = role.CredentialTypes[0]
// There is only one way for the CredentialTypes to contain more than one entry, and that's an upgrade path
// where it contains iamUserCred and federationTokenCred
// This ambiguity can be resolved based on req.Path, so resolve it assuming CredentialTypes only has those values
case len(role.CredentialTypes) > 1:
if strings.HasPrefix(req.Path, "creds") {
credentialType = iamUserCred
} else {
credentialType = federationTokenCred
}
// sanity check on the assumption above
if !strutil.StrListContains(role.CredentialTypes, credentialType) {
return logical.ErrorResponse(fmt.Sprintf("requested credential type %q not in allowed credential types %#v", credentialType, role.CredentialTypes)), nil
}
}
// creds requested through the sts path shouldn't be allowed to get iamUserCred type creds
// when the role is created from legacy data because they might have more privileges in AWS.
// See https://github.com/hashicorp/vault/issues/4229#issuecomment-380316788 for details.
if role.ProhibitFlexibleCredPath {
if credentialType == iamUserCred && strings.HasPrefix(req.Path, "sts") {
return logical.ErrorResponse(fmt.Sprintf("attempted to retrieve %s credentials through the sts path; this is not allowed for legacy roles", iamUserCred)), nil
}
if credentialType != iamUserCred && strings.HasPrefix(req.Path, "creds") {
return logical.ErrorResponse(fmt.Sprintf("attempted to retrieve %s credentials through the creds path; this is not allowed for legacy roles", credentialType)), nil
}
}
switch credentialType {
case iamUserCred:
return b.secretAccessKeysCreate(ctx, req.Storage, req.DisplayName, roleName, role)
case assumedRoleCred:
switch {
case roleArn == "":
if len(role.RoleArns) != 1 {
return logical.ErrorResponse("did not supply a role_arn parameter and unable to determine one"), nil
}
roleArn = role.RoleArns[0]
case !strutil.StrListContains(role.RoleArns, roleArn):
return logical.ErrorResponse(fmt.Sprintf("role_arn %q not in allowed role arns for Vault role %q", roleArn, roleName)), nil
}
return b.assumeRole(ctx, req.Storage, req.DisplayName, roleName, roleArn, role.PolicyDocument, ttl)
case federationTokenCred:
return b.secretTokenCreate(ctx, req.Storage, req.DisplayName, roleName, role.PolicyDocument, ttl)
default:
return logical.ErrorResponse(fmt.Sprintf("unknown credential_type: %q", credentialType)), nil
}
}
func pathUserRollback(ctx context.Context, req *logical.Request, _kind string, data interface{}) error {
var entry walUser
if err := mapstructure.Decode(data, &entry); err != nil {
return err
}
username := entry.UserName
// Get the client
client, err := clientIAM(ctx, req.Storage)
if err != nil {
return err
}
// Get information about this user
groupsResp, err := client.ListGroupsForUser(&iam.ListGroupsForUserInput{
UserName: aws.String(username),
MaxItems: aws.Int64(1000),
})
if err != nil {
return err
}
groups := groupsResp.Groups
// Inline (user) policies
policiesResp, err := client.ListUserPolicies(&iam.ListUserPoliciesInput{
UserName: aws.String(username),
MaxItems: aws.Int64(1000),
})
if err != nil {
return err
}
policies := policiesResp.PolicyNames
// Attached managed policies
manPoliciesResp, err := client.ListAttachedUserPolicies(&iam.ListAttachedUserPoliciesInput{
UserName: aws.String(username),
MaxItems: aws.Int64(1000),
})
if err != nil {
return err
}
manPolicies := manPoliciesResp.AttachedPolicies
keysResp, err := client.ListAccessKeys(&iam.ListAccessKeysInput{
UserName: aws.String(username),
MaxItems: aws.Int64(1000),
})
if err != nil {
return err
}
keys := keysResp.AccessKeyMetadata
// Revoke all keys
for _, k := range keys {
_, err = client.DeleteAccessKey(&iam.DeleteAccessKeyInput{
AccessKeyId: k.AccessKeyId,
UserName: aws.String(username),
})
if err != nil {
return err
}
}
// Detach managed policies
for _, p := range manPolicies {
_, err = client.DetachUserPolicy(&iam.DetachUserPolicyInput{
UserName: aws.String(username),
PolicyArn: p.PolicyArn,
})
if err != nil {
return err
}
}
// Delete any inline (user) policies
for _, p := range policies {
_, err = client.DeleteUserPolicy(&iam.DeleteUserPolicyInput{
UserName: aws.String(username),
PolicyName: p,
})
if err != nil {
return err
}
}
// Remove the user from all their groups
for _, g := range groups {
_, err = client.RemoveUserFromGroup(&iam.RemoveUserFromGroupInput{
GroupName: g.GroupName,
UserName: aws.String(username),
})
if err != nil {
return err
}
}
// Delete the user
_, err = client.DeleteUser(&iam.DeleteUserInput{
UserName: aws.String(username),
})
if err != nil {
return err
}
return nil
}
type walUser struct {
UserName string
}
const pathUserHelpSyn = `
Generate AWS credentials from a specific Vault role.
`
const pathUserHelpDesc = `
This path will generate new, never before used AWS credentials for
accessing AWS. The IAM policy used to back this key pair will be
the "name" parameter. For example, if this backend is mounted at "aws",
then "aws/creds/deploy" would generate access keys for the "deploy" role.
The access keys will have a lease associated with them. The access keys
can be revoked by using the lease ID when using the iam_user credential type.
When using AWS STS credential types (assumed_role or federation_token),
revoking the lease does not revoke the access keys.
`