-
Notifications
You must be signed in to change notification settings - Fork 3
/
path_creds.go
280 lines (241 loc) · 9.21 KB
/
path_creds.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
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
package ibmcloudsecrets
import (
"context"
"fmt"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/logical"
)
func secretServiceIDKey(b *ibmCloudSecretBackend) *framework.Secret {
return &framework.Secret{
Type: secretTypeKey,
Fields: map[string]*framework.FieldSchema{
apiKeyField: {
Type: framework.TypeString,
Description: "An API key",
},
},
Renew: b.secretKeyRenew,
Revoke: b.secretKeyRevoke,
}
}
func pathSecretServiceIDKey(b *ibmCloudSecretBackend) *framework.Path {
return &framework.Path{
Pattern: fmt.Sprintf("creds/%s", framework.GenericNameRegex(roleField)),
Fields: map[string]*framework.FieldSchema{
roleField: {
Type: framework.TypeString,
Description: "Required. Name of the role.",
},
},
ExistenceCheck: b.pathRoleExistenceCheck(roleField),
Operations: map[logical.Operation]framework.OperationHandler{
logical.ReadOperation: &framework.PathOperation{Callback: b.pathServiceIDKey},
logical.UpdateOperation: &framework.PathOperation{Callback: b.pathServiceIDKey},
},
HelpSynopsis: pathServiceIDKeySyn,
HelpDescription: pathServiceIDKeyDesc,
}
}
func (b *ibmCloudSecretBackend) pathServiceIDKey(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
roleName := d.Get(roleField).(string)
role, err := getRole(ctx, req.Storage, roleName)
if err != nil {
return nil, err
}
if role == nil {
return logical.ErrorResponse(fmt.Sprintf("role set '%s' does not exist", roleName)), nil
}
return b.getSecretKey(ctx, req.Storage, role, roleName)
}
func (b *ibmCloudSecretBackend) secretKeyRenew(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
roleName, ok := req.Secret.InternalData[roleNameField]
if !ok {
return nil, fmt.Errorf("invalid secret, internal data is missing role name")
}
role, err := getRole(ctx, req.Storage, roleName.(string))
if err != nil || role == nil {
return logical.ErrorResponse(fmt.Sprintf("could not find role '%v' for secret", roleName)), nil
}
bindingSum, ok := req.Secret.InternalData[roleBindingHashField]
if !ok {
return nil, fmt.Errorf("invalid secret, internal data is missing role binding checksum")
}
if role.BindingHash != bindingSum.(string) {
return logical.ErrorResponse(fmt.Sprintf("role '%v' access group or service ID bindings were updated since secret was generated, cannot renew", roleName)), nil
}
resp := &logical.Response{}
resp.Secret = req.Secret
resp.Secret.TTL = role.TTL
resp.Secret.MaxTTL = role.MaxTTL
return resp, nil
}
func (b *ibmCloudSecretBackend) secretKeyRevoke(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
adminToken, err := b.getAdminToken(ctx, req.Storage)
if err != nil {
b.Logger().Error("error obtaining the token for the configured API key", "error", err)
return nil, err
}
serviceIDRaw, dynamicIDSecret := req.Secret.InternalData[serviceIDField]
apiKeyIDRaw, staticIDSecret := req.Secret.InternalData[apiKeyID]
roleName, roleNameOK := req.Secret.InternalData[roleNameField]
iam, resp := b.getIAMHelper(ctx, req.Storage)
if resp != nil {
b.Logger().Error("failed to retrieve an IAM helper", "error", resp.Error())
return resp, nil
}
if dynamicIDSecret {
err = iam.DeleteServiceID(adminToken, serviceIDRaw.(string))
if err != nil {
if !roleNameOK {
roleName = "<Not Found>"
}
accountID := "<Not Found>"
config, errResp := b.getConfig(ctx, req.Storage)
if errResp == nil {
accountID = config.Account
}
b.Logger().Error("An error occurred removing a service ID while revoking a secret lease. "+
"The service ID may have been manually deleted in IBM Cloud. The administrator should verify the service ID "+
"is removed.", "serviceID", serviceIDRaw, "vaultRole", roleName, "accountID", accountID, "deleteError", err)
return nil, err
}
} else if staticIDSecret {
err = iam.DeleteAPIKey(adminToken, apiKeyIDRaw.(string))
if err != nil {
if !roleNameOK {
roleName = "<Not Found>"
}
accountID := "<Not Found>"
config, errResp := b.getConfig(ctx, req.Storage)
if errResp == nil {
accountID = config.Account
}
b.Logger().Error("An error occurred removing an API key while revoking a secret lease. "+
"The API key may have been manually deleted in IBM Cloud. The administrator should verify the API key "+
"is removed.", "apiKeyID", apiKeyIDRaw, "vaultRole", roleName, "accountID", accountID, "deleteError", err)
return nil, err
}
} else {
msg := "secret is missing service ID or API key ID internal data"
b.Logger().Error(msg)
return nil, fmt.Errorf(msg)
}
return nil, nil
}
func (b *ibmCloudSecretBackend) getSecretKey(ctx context.Context, s logical.Storage, role *ibmCloudRole, roleName string) (*logical.Response, error) {
config, errResp := b.getConfig(ctx, s)
if errResp != nil {
return errResp, nil
}
adminToken, err := b.getAdminToken(ctx, s)
if err != nil {
b.Logger().Error("error obtaining the token for the configured API key", "error", err)
return nil, err
}
var resp *logical.Response
if len(role.AccessGroupIDs) > 0 {
resp, err = b.getSecretDynamicServiceID(ctx, s, role, adminToken, roleName, config)
if err != nil {
return nil, err
}
} else if len(role.ServiceID) > 0 {
resp, err = b.getSecretStaticServiceID(ctx, s, role, adminToken, roleName, config)
if err != nil {
return nil, err
}
if resp.IsError() {
return resp, nil
}
} else {
return logical.ErrorResponse("role %s has neither access groups nor a service ID", roleName), nil
}
resp.Secret.Renewable = true
resp.Secret.MaxTTL = role.MaxTTL
resp.Secret.TTL = role.TTL
return resp, nil
}
func (b *ibmCloudSecretBackend) getSecretDynamicServiceID(ctx context.Context, s logical.Storage, role *ibmCloudRole, adminToken, roleName string, config *ibmCloudConfig) (*logical.Response, error) {
iam, resp := b.getIAMHelper(ctx, s)
if resp != nil {
b.Logger().Error("failed to retrieve an IAM helper", "error", resp.Error())
return resp, nil
}
// Create the service ID, which is the top level object to be tracked in the secret
// and deleted upon revocation. If any subsequent step fails, the service ID will be
// deleted as part of WAL rollback.
iamID, uniqueID, err := iam.CreateServiceID(adminToken, config.Account, roleName)
if err != nil {
return nil, err
}
// Write a WAL entry in case the access group assignments or API key creation process doesn't complete
walID, err := framework.PutWAL(ctx, s, walTypeServiceID, uniqueID)
if err != nil {
return nil, errwrap.Wrapf("error writing WAL: {{err}}", err)
}
// Add service ID to access groups
for _, group := range role.AccessGroupIDs {
err := iam.AddServiceIDToAccessGroup(adminToken, iamID, group)
if err != nil {
return nil, err
}
}
keyName := fmt.Sprintf("vault-generated-%s", roleName)
keyDescription := fmt.Sprintf("Generated by Vault's secret engine for IBM Cloud credentials using Vault role %s.", roleName)
// Create API key
apiKey, err := iam.CreateAPIKey(adminToken, iamID, config.Account, keyName, keyDescription)
if err != nil {
return nil, err
}
// Secret creation complete, delete the WAL
if err := framework.DeleteWAL(ctx, s, walID); err != nil {
return nil, errwrap.Wrapf("error deleting WAL: {{err}}", err)
}
secretD := map[string]interface{}{
apiKeyField: apiKey.APIKey,
}
internalD := map[string]interface{}{
serviceIDField: uniqueID,
roleNameField: roleName,
roleBindingHashField: role.BindingHash,
}
resp = b.Secret(secretTypeKey).Response(secretD, internalD)
return resp, nil
}
func (b *ibmCloudSecretBackend) getSecretStaticServiceID(ctx context.Context, s logical.Storage, role *ibmCloudRole, adminToken, roleName string, config *ibmCloudConfig) (*logical.Response, error) {
iam, resp := b.getIAMHelper(ctx, s)
if resp != nil {
b.Logger().Error("failed to retrieve an IAM helper", "error", resp.Error())
return resp, nil
}
// Fetch the serviceID's IAM ID
idInfo, resp := iam.CheckServiceIDAccount(adminToken, role.ServiceID, config.Account)
if resp != nil {
return resp, nil
}
keyName := fmt.Sprintf("vault-generated-%s", roleName)
keyDescription := fmt.Sprintf("Generated by Vault's secret engine for IBM Cloud credentials using Vault role %s.", roleName)
// Create API key
apiKey, err := iam.CreateAPIKey(adminToken, idInfo.IAMID, config.Account, keyName, keyDescription)
if err != nil {
return nil, err
}
secretD := map[string]interface{}{
apiKeyField: apiKey.APIKey,
}
internalD := map[string]interface{}{
apiKeyID: apiKey.ID,
roleNameField: roleName,
roleBindingHashField: role.BindingHash,
}
resp = b.Secret(secretTypeKey).Response(secretD, internalD)
return resp, nil
}
const pathServiceIDKeySyn = `Generate an API key under a specific role.`
const pathServiceIDKeyDesc = `
This path will generate a new service account and associated API key.
A role, binding IBM Cloud Access Groups, will be specified
by name - for example, if this backend is mounted at "ibmcloud", then "ibmcloud/creds/deploy"
would generate service account, add it to all the access groups listed on the "deploy" role,
generate an API key for the service account and return the API key.
`