forked from hashicorp/vault
-
Notifications
You must be signed in to change notification settings - Fork 0
/
path_roletag_blacklist.go
257 lines (213 loc) · 7.75 KB
/
path_roletag_blacklist.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
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
package awsauth
import (
"encoding/base64"
"time"
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/logical/framework"
)
func pathRoletagBlacklist(b *backend) *framework.Path {
return &framework.Path{
Pattern: "roletag-blacklist/(?P<role_tag>.*)",
Fields: map[string]*framework.FieldSchema{
"role_tag": &framework.FieldSchema{
Type: framework.TypeString,
Description: `Role tag to be blacklisted. The tag can be supplied as-is. In order
to avoid any encoding problems, it can be base64 encoded.`,
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.UpdateOperation: b.pathRoletagBlacklistUpdate,
logical.ReadOperation: b.pathRoletagBlacklistRead,
logical.DeleteOperation: b.pathRoletagBlacklistDelete,
},
HelpSynopsis: pathRoletagBlacklistSyn,
HelpDescription: pathRoletagBlacklistDesc,
}
}
// Path to list all the blacklisted tags.
func pathListRoletagBlacklist(b *backend) *framework.Path {
return &framework.Path{
Pattern: "roletag-blacklist/?",
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.ListOperation: b.pathRoletagBlacklistsList,
},
HelpSynopsis: pathListRoletagBlacklistHelpSyn,
HelpDescription: pathListRoletagBlacklistHelpDesc,
}
}
// Lists all the blacklisted role tags.
func (b *backend) pathRoletagBlacklistsList(
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
b.blacklistMutex.RLock()
defer b.blacklistMutex.RUnlock()
tags, err := req.Storage.List("blacklist/roletag/")
if err != nil {
return nil, err
}
// Tags are base64 encoded before indexing to avoid problems
// with the path separators being present in the tag.
// Reverse it before returning the list response.
for i, keyB64 := range tags {
if key, err := base64.StdEncoding.DecodeString(keyB64); err != nil {
return nil, err
} else {
// Overwrite the result with the decoded string.
tags[i] = string(key)
}
}
return logical.ListResponse(tags), nil
}
// Fetch an entry from the role tag blacklist for a given tag.
// This method takes a role tag in its original form and not a base64 encoded form.
func (b *backend) lockedBlacklistRoleTagEntry(s logical.Storage, tag string) (*roleTagBlacklistEntry, error) {
b.blacklistMutex.RLock()
defer b.blacklistMutex.RUnlock()
return b.nonLockedBlacklistRoleTagEntry(s, tag)
}
func (b *backend) nonLockedBlacklistRoleTagEntry(s logical.Storage, tag string) (*roleTagBlacklistEntry, error) {
entry, err := s.Get("blacklist/roletag/" + base64.StdEncoding.EncodeToString([]byte(tag)))
if err != nil {
return nil, err
}
if entry == nil {
return nil, nil
}
var result roleTagBlacklistEntry
if err := entry.DecodeJSON(&result); err != nil {
return nil, err
}
return &result, nil
}
// Deletes an entry from the role tag blacklist for a given tag.
func (b *backend) pathRoletagBlacklistDelete(
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
b.blacklistMutex.Lock()
defer b.blacklistMutex.Unlock()
tag := data.Get("role_tag").(string)
if tag == "" {
return logical.ErrorResponse("missing role_tag"), nil
}
return nil, req.Storage.Delete("blacklist/roletag/" + base64.StdEncoding.EncodeToString([]byte(tag)))
}
// If the given role tag is blacklisted, returns the details of the blacklist entry.
// Returns 'nil' otherwise.
func (b *backend) pathRoletagBlacklistRead(
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
tag := data.Get("role_tag").(string)
if tag == "" {
return logical.ErrorResponse("missing role_tag"), nil
}
entry, err := b.lockedBlacklistRoleTagEntry(req.Storage, tag)
if err != nil {
return nil, err
}
if entry == nil {
return nil, nil
}
return &logical.Response{
Data: map[string]interface{}{
"creation_time": entry.CreationTime.Format(time.RFC3339Nano),
"expiration_time": entry.ExpirationTime.Format(time.RFC3339Nano),
},
}, nil
}
// pathRoletagBlacklistUpdate is used to blacklist a given role tag.
// Before a role tag is blacklisted, the correctness of the plaintext part
// in the role tag is verified using the associated HMAC.
func (b *backend) pathRoletagBlacklistUpdate(
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
// The role_tag value provided, optionally can be base64 encoded.
tagInput := data.Get("role_tag").(string)
if tagInput == "" {
return logical.ErrorResponse("missing role_tag"), nil
}
tag := ""
// Try to base64 decode the value.
tagBytes, err := base64.StdEncoding.DecodeString(tagInput)
if err != nil {
// If the decoding failed, use the value as-is.
tag = tagInput
} else {
// If the decoding succeeded, use the decoded value.
tag = string(tagBytes)
}
// Parse and verify the role tag from string form to a struct form and verify it.
rTag, err := b.parseAndVerifyRoleTagValue(req.Storage, tag)
if err != nil {
return nil, err
}
if rTag == nil {
return logical.ErrorResponse("failed to verify the role tag and parse it"), nil
}
// Get the entry for the role mentioned in the role tag.
roleEntry, err := b.lockedAWSRole(req.Storage, rTag.Role)
if err != nil {
return nil, err
}
if roleEntry == nil {
return logical.ErrorResponse("role entry not found"), nil
}
b.blacklistMutex.Lock()
defer b.blacklistMutex.Unlock()
// Check if the role tag is already blacklisted. If yes, update it.
blEntry, err := b.nonLockedBlacklistRoleTagEntry(req.Storage, tag)
if err != nil {
return nil, err
}
if blEntry == nil {
blEntry = &roleTagBlacklistEntry{}
}
currentTime := time.Now()
// Check if this is a creation of blacklist entry.
if blEntry.CreationTime.IsZero() {
// Set the creation time for the blacklist entry.
// This should not be updated after setting it once.
// If blacklist operation is invoked more than once, only update the expiration time.
blEntry.CreationTime = currentTime
}
// Decide the expiration time based on the max_ttl values. Since this is
// restricting access, use the greatest duration, not the least.
maxDur := rTag.MaxTTL
if roleEntry.MaxTTL > maxDur {
maxDur = roleEntry.MaxTTL
}
if b.System().MaxLeaseTTL() > maxDur {
maxDur = b.System().MaxLeaseTTL()
}
blEntry.ExpirationTime = currentTime.Add(maxDur)
entry, err := logical.StorageEntryJSON("blacklist/roletag/"+base64.StdEncoding.EncodeToString([]byte(tag)), blEntry)
if err != nil {
return nil, err
}
// Store the blacklist entry.
if err := req.Storage.Put(entry); err != nil {
return nil, err
}
return nil, nil
}
type roleTagBlacklistEntry struct {
CreationTime time.Time `json:"creation_time" structs:"creation_time" mapstructure:"creation_time"`
ExpirationTime time.Time `json:"expiration_time" structs:"expiration_time" mapstructure:"expiration_time"`
}
const pathRoletagBlacklistSyn = `
Blacklist a previously created role tag.
`
const pathRoletagBlacklistDesc = `
Blacklist a role tag so that it cannot be used by any EC2 instance to perform further
logins. This can be used if the role tag is suspected or believed to be possessed by
an unintended party.
By default, a cron task will periodically look for expired entries in the blacklist
and deletes them. The duration to periodically run this, is one hour by default.
However, this can be configured using the 'config/tidy/roletags' endpoint. This tidy
action can be triggered via the API as well, using the 'tidy/roletags' endpoint.
Also note that delete operation is supported on this endpoint to remove specific
entries from the blacklist.
`
const pathListRoletagBlacklistHelpSyn = `
Lists the blacklisted role tags.
`
const pathListRoletagBlacklistHelpDesc = `
Lists all the entries present in the blacklist. This will show both the valid
entries and the expired entries in the blacklist. Use 'tidy/roletags' endpoint
to clean-up the blacklist of role tags based on expiration time.
`