1package database
2
3import (
4	"context"
5	"fmt"
6	"time"
7
8	"github.com/hashicorp/vault/sdk/database/dbplugin"
9	"github.com/hashicorp/vault/sdk/framework"
10	"github.com/hashicorp/vault/sdk/logical"
11)
12
13const SecretCredsType = "creds"
14
15func secretCreds(b *databaseBackend) *framework.Secret {
16	return &framework.Secret{
17		Type:   SecretCredsType,
18		Fields: map[string]*framework.FieldSchema{},
19
20		Renew:  b.secretCredsRenew(),
21		Revoke: b.secretCredsRevoke(),
22	}
23}
24
25func (b *databaseBackend) secretCredsRenew() framework.OperationFunc {
26	return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
27		// Get the username from the internal data
28		usernameRaw, ok := req.Secret.InternalData["username"]
29		if !ok {
30			return nil, fmt.Errorf("secret is missing username internal data")
31		}
32		username, ok := usernameRaw.(string)
33
34		roleNameRaw, ok := req.Secret.InternalData["role"]
35		if !ok {
36			return nil, fmt.Errorf("could not find role with name: %q", req.Secret.InternalData["role"])
37		}
38
39		role, err := b.Role(ctx, req.Storage, roleNameRaw.(string))
40		if err != nil {
41			return nil, err
42		}
43		if role == nil {
44			return nil, fmt.Errorf("error during renew: could not find role with name %q", req.Secret.InternalData["role"])
45		}
46
47		// Get the Database object
48		db, err := b.GetConnection(ctx, req.Storage, role.DBName)
49		if err != nil {
50			return nil, err
51		}
52
53		db.RLock()
54		defer db.RUnlock()
55
56		// Make sure we increase the VALID UNTIL endpoint for this user.
57		ttl, _, err := framework.CalculateTTL(b.System(), req.Secret.Increment, role.DefaultTTL, 0, role.MaxTTL, 0, req.Secret.IssueTime)
58		if err != nil {
59			return nil, err
60		}
61		if ttl > 0 {
62			expireTime := time.Now().Add(ttl)
63			// Adding a small buffer since the TTL will be calculated again after this call
64			// to ensure the database credential does not expire before the lease
65			expireTime = expireTime.Add(5 * time.Second)
66			err := db.RenewUser(ctx, role.Statements, username, expireTime)
67			if err != nil {
68				b.CloseIfShutdown(db, err)
69				return nil, err
70			}
71		}
72		resp := &logical.Response{Secret: req.Secret}
73		resp.Secret.TTL = role.DefaultTTL
74		resp.Secret.MaxTTL = role.MaxTTL
75		return resp, nil
76	}
77}
78
79func (b *databaseBackend) secretCredsRevoke() framework.OperationFunc {
80	return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
81		// Get the username from the internal data
82		usernameRaw, ok := req.Secret.InternalData["username"]
83		if !ok {
84			return nil, fmt.Errorf("secret is missing username internal data")
85		}
86		username, ok := usernameRaw.(string)
87
88		var resp *logical.Response
89
90		roleNameRaw, ok := req.Secret.InternalData["role"]
91		if !ok {
92			return nil, fmt.Errorf("no role name was provided")
93		}
94
95		var dbName string
96		var statements dbplugin.Statements
97
98		role, err := b.Role(ctx, req.Storage, roleNameRaw.(string))
99		if err != nil {
100			return nil, err
101		}
102		if role != nil {
103			dbName = role.DBName
104			statements = role.Statements
105		} else {
106			if dbNameRaw, ok := req.Secret.InternalData["db_name"]; !ok {
107				return nil, fmt.Errorf("error during revoke: could not find role with name %q or embedded revocation db name data", req.Secret.InternalData["role"])
108			} else {
109				dbName = dbNameRaw.(string)
110			}
111			if statementsRaw, ok := req.Secret.InternalData["revocation_statements"]; !ok {
112				return nil, fmt.Errorf("error during revoke: could not find role with name %q or embedded revocation statement data", req.Secret.InternalData["role"])
113			} else {
114				// If we don't actually have any statements, because none were
115				// set in the role, we'll end up with an empty one and the
116				// default for the db type will be attempted
117				if statementsRaw != nil {
118					statementsSlice, ok := statementsRaw.([]interface{})
119					if !ok {
120						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"])
121					} else {
122						for _, v := range statementsSlice {
123							statements.Revocation = append(statements.Revocation, v.(string))
124						}
125					}
126				}
127			}
128		}
129
130		// Get our connection
131		db, err := b.GetConnection(ctx, req.Storage, dbName)
132		if err != nil {
133			return nil, err
134		}
135
136		db.RLock()
137		defer db.RUnlock()
138
139		if err := db.RevokeUser(ctx, statements, username); err != nil {
140			b.CloseIfShutdown(db, err)
141			return nil, err
142		}
143		return resp, nil
144	}
145}
146