-
Notifications
You must be signed in to change notification settings - Fork 4.2k
/
secret_creds.go
173 lines (150 loc) · 4.92 KB
/
secret_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
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package database
import (
"context"
"fmt"
"time"
v4 "github.com/hashicorp/vault/sdk/database/dbplugin"
v5 "github.com/hashicorp/vault/sdk/database/dbplugin/v5"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/logical"
)
const SecretCredsType = "creds"
func secretCreds(b *databaseBackend) *framework.Secret {
return &framework.Secret{
Type: SecretCredsType,
Fields: map[string]*framework.FieldSchema{},
Renew: b.secretCredsRenew(),
Revoke: b.secretCredsRevoke(),
}
}
func (b *databaseBackend) secretCredsRenew() framework.OperationFunc {
return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
// Get the username from the internal data
usernameRaw, ok := req.Secret.InternalData["username"]
if !ok {
return nil, fmt.Errorf("secret is missing username internal data")
}
username, ok := usernameRaw.(string)
if !ok {
return nil, fmt.Errorf("username not a string")
}
roleNameRaw, ok := req.Secret.InternalData["role"]
if !ok {
return nil, fmt.Errorf("could not find role with name: %q", req.Secret.InternalData["role"])
}
role, err := b.Role(ctx, req.Storage, roleNameRaw.(string))
if err != nil {
return nil, err
}
if role == nil {
return nil, fmt.Errorf("error during renew: could not find role with name %q", req.Secret.InternalData["role"])
}
// Get the Database object
dbi, err := b.GetConnection(ctx, req.Storage, role.DBName)
if err != nil {
return nil, err
}
dbi.RLock()
defer dbi.RUnlock()
// Make sure we increase the VALID UNTIL endpoint for this user.
ttl, _, err := framework.CalculateTTL(b.System(), req.Secret.Increment, role.DefaultTTL, 0, role.MaxTTL, 0, req.Secret.IssueTime)
if err != nil {
return nil, err
}
if ttl > 0 {
expireTime := time.Now().Add(ttl)
// Adding a small buffer since the TTL will be calculated again after this call
// to ensure the database credential does not expire before the lease
expireTime = expireTime.Add(5 * time.Second)
updateReq := v5.UpdateUserRequest{
Username: username,
Expiration: &v5.ChangeExpiration{
NewExpiration: expireTime,
Statements: v5.Statements{
Commands: role.Statements.Renewal,
},
},
}
_, err := dbi.database.UpdateUser(ctx, updateReq, false)
if err != nil {
b.CloseIfShutdown(dbi, err)
return nil, err
}
}
resp := &logical.Response{Secret: req.Secret}
resp.Secret.TTL = role.DefaultTTL
resp.Secret.MaxTTL = role.MaxTTL
return resp, nil
}
}
func (b *databaseBackend) secretCredsRevoke() framework.OperationFunc {
return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
// Get the username from the internal data
usernameRaw, ok := req.Secret.InternalData["username"]
if !ok {
return nil, fmt.Errorf("secret is missing username internal data")
}
username, ok := usernameRaw.(string)
if !ok {
return nil, fmt.Errorf("username not a string")
}
var resp *logical.Response
roleNameRaw, ok := req.Secret.InternalData["role"]
if !ok {
return nil, fmt.Errorf("no role name was provided")
}
var dbName string
var statements v4.Statements
role, err := b.Role(ctx, req.Storage, roleNameRaw.(string))
if err != nil {
return nil, err
}
if role != nil {
dbName = role.DBName
statements = role.Statements
} else {
dbNameRaw, ok := req.Secret.InternalData["db_name"]
if !ok {
return nil, fmt.Errorf("error during revoke: could not find role with name %q or embedded revocation db name data", req.Secret.InternalData["role"])
}
dbName = dbNameRaw.(string)
statementsRaw, ok := req.Secret.InternalData["revocation_statements"]
if !ok {
return nil, fmt.Errorf("error during revoke: could not find role with name %q or embedded revocation statement data", req.Secret.InternalData["role"])
}
// If we don't actually have any statements, because none were
// set in the role, we'll end up with an empty one and the
// default for the db type will be attempted
if statementsRaw != nil {
statementsSlice, ok := statementsRaw.([]interface{})
if !ok {
return nil, fmt.Errorf("error during revoke: could not find role with name %q and embedded reovcation data could not be read", req.Secret.InternalData["role"])
}
for _, v := range statementsSlice {
statements.Revocation = append(statements.Revocation, v.(string))
}
}
}
// Get our connection
dbi, err := b.GetConnection(ctx, req.Storage, dbName)
if err != nil {
return nil, err
}
dbi.RLock()
defer dbi.RUnlock()
deleteReq := v5.DeleteUserRequest{
Username: username,
Statements: v5.Statements{
Commands: statements.Revocation,
},
}
_, err = dbi.database.DeleteUser(ctx, deleteReq)
if err != nil {
b.CloseIfShutdown(dbi, err)
return nil, err
}
return resp, nil
}
}