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