Skip to content

Commit 0445163

Browse files
VAULT-38193 Add database observations to Vault (hashicorp#8727) (hashicorp#8802)
* VAULT-38193 database observations (WIP) * VAULT-38193 database observations * nil check * make it consistent * Clean up Co-authored-by: Violet Hynes <[email protected]>
1 parent ec0ca21 commit 0445163

File tree

7 files changed

+196
-5
lines changed

7 files changed

+196
-5
lines changed

builtin/logical/database/backend.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,36 @@ func (b *databaseBackend) dbEvent(ctx context.Context,
496496
}
497497
}
498498

499+
type AdditionalDatabaseMetadata struct {
500+
key string
501+
value interface{}
502+
}
503+
504+
func recordDatabaseObservation(ctx context.Context, b *databaseBackend, req *logical.Request, connectionName string, observationType string,
505+
additionalMetadata ...AdditionalDatabaseMetadata,
506+
) {
507+
metadata := map[string]interface{}{
508+
"path": req.Path,
509+
"client_id": req.ClientID,
510+
"entity_id": req.EntityID,
511+
"request_id": req.ID,
512+
}
513+
514+
if connectionName != "" {
515+
metadata["connection_name"] = connectionName
516+
}
517+
518+
for _, meta := range additionalMetadata {
519+
metadata[meta.key] = meta.value
520+
}
521+
522+
err := b.RecordObservation(ctx, observationType, metadata)
523+
524+
if err != nil && !errors.Is(err, framework.ErrNoObservations) {
525+
b.Logger().Error("error recording observation", "observationType", observationType, "error", err)
526+
}
527+
}
528+
499529
func (b *databaseBackend) getDatabaseConfigNameFromRotationID(path string) (string, error) {
500530
if !databaseConfigNameFromRotationIDRegex.MatchString(path) {
501531
return "", fmt.Errorf("no name found from rotation ID")
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package database
5+
6+
const (
7+
// Connection related observations:
8+
9+
ObservationTypeDatabaseConfigWrite = "database/connection/config/write"
10+
ObservationTypeDatabaseConfigDelete = "database/connection/config/delete"
11+
ObservationTypeDatabaseConfigRead = "database/connection/config/read"
12+
ObservationTypeDatabaseConnectionReset = "database/connection/reset"
13+
14+
// Reload related observations:
15+
// Note: the following three observations mean that, for any reload, Vault will emit
16+
// n+1 observations, where n is the number of connections that we attempt to reload.
17+
// database/plugin/reload will not contain a connection_name, as it will be a summary
18+
// database/connection/reload/* will be per-plugin, and will contain a connection_name
19+
20+
// ObservationTypeDatabaseReloadPlugin is emitted when a plugin reload is issued
21+
ObservationTypeDatabaseReloadPlugin = "database/plugin/reload"
22+
// ObservationTypeDatabaseReloadSuccess is emitted for each connection successfully reloaded
23+
ObservationTypeDatabaseReloadSuccess = "database/connection/reload/success"
24+
// ObservationTypeDatabaseReloadFail is emitted for each connection unsuccessfully reloaded
25+
ObservationTypeDatabaseReloadFail = "database/connection/reload/fail"
26+
27+
// Role related observations
28+
29+
ObservationTypeDatabaseRoleCreate = "database/role/create"
30+
ObservationTypeDatabaseRoleUpdate = "database/role/update"
31+
ObservationTypeDatabaseRoleRead = "database/role/read"
32+
// ObservationTypeDatabaseRoleDelete is emitted whenever a role is deleted.
33+
// Note that this observation does not include a connection_name, to avoid doing an
34+
// additional storage read.
35+
ObservationTypeDatabaseRoleDelete = "database/role/delete"
36+
37+
ObservationTypeDatabaseCredentialCreateSuccess = "database/credential/create/success"
38+
ObservationTypeDatabaseCredentialCreateFail = "database/credential/create/fail"
39+
ObservationTypeDatabaseCredentialRenew = "database/credential/renew"
40+
ObservationTypeDatabaseCredentialRevoke = "database/credential/revoke"
41+
42+
// Rotate-root observations
43+
44+
ObservationTypeDatabaseRotateRootSuccess = "database/rotate-root/success"
45+
ObservationTypeDatabaseRotateRootFailure = "database/rotate-root/fail"
46+
47+
// Static role observations
48+
49+
ObservationTypeDatabaseRotateStaticRoleSuccess = "database/static-role/rotate/success"
50+
ObservationTypeDatabaseRotateStaticRoleFailure = "database/static-role/rotate/fail"
51+
ObservationTypeDatabaseStaticRoleCreate = "database/static-role/create"
52+
ObservationTypeDatabaseStaticRoleUpdate = "database/static-role/update"
53+
ObservationTypeDatabaseStaticRoleRead = "database/static-role/read"
54+
// ObservationTypeDatabaseStaticRoleDelete is emitted whenever a static role is deleted.
55+
// Note that this observation does not include a connection_name, to avoid doing an
56+
// additional storage read.
57+
ObservationTypeDatabaseStaticRoleDelete = "database/static-role/delete"
58+
59+
ObservationTypeDatabaseStaticCredentialRead = "database/static-credential/read"
60+
)

builtin/logical/database/path_config_connection.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ func (b *databaseBackend) pathConnectionReset() framework.OperationFunc {
117117
}
118118

119119
b.dbEvent(ctx, "reset", req.Path, name, false)
120+
recordDatabaseObservation(ctx, b, req, name, ObservationTypeDatabaseConnectionReset)
120121
return nil, nil
121122
}
122123
}
@@ -194,15 +195,24 @@ func (b *databaseBackend) reloadPlugin() framework.OperationFunc {
194195
if err := b.reloadConnection(ctx, req.Storage, connName); err != nil {
195196
b.Logger().Error("failed to reload connection", "name", connName, "error", err)
196197
b.dbEvent(ctx, "reload-connection-fail", req.Path, "", false, "name", connName)
198+
recordDatabaseObservation(ctx, b, req, connName, ObservationTypeDatabaseReloadFail,
199+
AdditionalDatabaseMetadata{key: "plugin_name", value: pluginName})
197200
reloadFailed = append(reloadFailed, connName)
198201
} else {
199202
b.Logger().Debug("reloaded connection", "name", connName)
200203
b.dbEvent(ctx, "reload-connection", req.Path, "", true, "name", connName)
204+
recordDatabaseObservation(ctx, b, req, connName, ObservationTypeDatabaseReloadSuccess,
205+
AdditionalDatabaseMetadata{key: "plugin_name", value: pluginName})
201206
reloaded = append(reloaded, connName)
202207
}
203208
}
204209
}
205210

211+
recordDatabaseObservation(ctx, b, req, "", ObservationTypeDatabaseReloadPlugin,
212+
AdditionalDatabaseMetadata{key: "plugin_name", value: pluginName},
213+
AdditionalDatabaseMetadata{key: "reloaded", value: reloaded},
214+
AdditionalDatabaseMetadata{key: "reload_failed", value: reloadFailed})
215+
206216
resp := &logical.Response{
207217
Data: map[string]interface{}{
208218
"connections": reloaded,
@@ -423,6 +433,9 @@ func (b *databaseBackend) connectionReadHandler() framework.OperationFunc {
423433
// remove extra nested AutomatedRotationParams key
424434
// before returning response
425435
delete(resp.Data, "AutomatedRotationParams")
436+
437+
recordDatabaseObservation(ctx, b, req, name, ObservationTypeDatabaseConfigRead)
438+
426439
return resp, nil
427440
}
428441
}
@@ -445,6 +458,7 @@ func (b *databaseBackend) connectionDeleteHandler() framework.OperationFunc {
445458
}
446459

447460
b.dbEvent(ctx, "config-delete", req.Path, name, true)
461+
recordDatabaseObservation(ctx, b, req, name, ObservationTypeDatabaseConfigDelete)
448462
return nil, nil
449463
}
450464
}
@@ -658,6 +672,8 @@ available at https://www.snowflake.com/en/blog/blocking-single-factor-password-a
658672
}
659673

660674
b.dbEvent(ctx, "config-write", req.Path, name, true)
675+
recordDatabaseObservation(ctx, b, req, name, ObservationTypeDatabaseConfigWrite)
676+
661677
if len(resp.Warnings) == 0 {
662678
return nil, nil
663679
}

builtin/logical/database/path_creds_create.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,19 @@ func (b *databaseBackend) pathCredsCreateRead() framework.OperationFunc {
8787
return logical.ErrorResponse(fmt.Sprintf("unknown role: %s", name)), nil
8888
}
8989

90+
defer func() {
91+
if err == nil && (resp == nil || !resp.IsError()) {
92+
recordDatabaseObservation(ctx, b, req, role.DBName, ObservationTypeDatabaseCredentialCreateSuccess,
93+
AdditionalDatabaseMetadata{key: "role_name", value: name},
94+
AdditionalDatabaseMetadata{key: "credential_type", value: role.CredentialType.String()})
95+
} else {
96+
b.dbEvent(ctx, "creds-create-fail", req.Path, name, modified)
97+
recordDatabaseObservation(ctx, b, req, role.DBName, ObservationTypeDatabaseCredentialCreateFail,
98+
AdditionalDatabaseMetadata{key: "role_name", value: name},
99+
AdditionalDatabaseMetadata{key: "credential_type", value: role.CredentialType.String()})
100+
}
101+
}()
102+
90103
dbConfig, err := b.DatabaseConfig(ctx, req.Storage, role.DBName)
91104
if err != nil {
92105
return nil, err
@@ -281,6 +294,10 @@ func (b *databaseBackend) pathStaticCredsRead() framework.OperationFunc {
281294
respData["rsa_private_key"] = string(role.StaticAccount.PrivateKey)
282295
}
283296

297+
recordDatabaseObservation(ctx, b, req, role.DBName, ObservationTypeDatabaseStaticCredentialRead,
298+
AdditionalDatabaseMetadata{key: "role_name", value: name},
299+
AdditionalDatabaseMetadata{key: "credential_type", value: role.CredentialType.String()})
300+
284301
return &logical.Response{
285302
Data: respData,
286303
}, nil

builtin/logical/database/path_roles.go

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,8 @@ func (b *databaseBackend) pathRoleDelete(ctx context.Context, req *logical.Reque
257257
return nil, err
258258
}
259259
b.dbEvent(ctx, "role-delete", req.Path, name, true)
260+
recordDatabaseObservation(ctx, b, req, "", ObservationTypeDatabaseRoleDelete,
261+
AdditionalDatabaseMetadata{key: "role_name", value: name})
260262
return nil, nil
261263
}
262264

@@ -298,11 +300,14 @@ func (b *databaseBackend) pathStaticRoleDelete(ctx context.Context, req *logical
298300
}
299301

300302
b.dbEvent(ctx, "static-role-delete", req.Path, name, true)
303+
recordDatabaseObservation(ctx, b, req, "", ObservationTypeDatabaseStaticRoleDelete,
304+
AdditionalDatabaseMetadata{key: "role_name", value: name})
301305
return nil, merr.ErrorOrNil()
302306
}
303307

304308
func (b *databaseBackend) pathStaticRoleRead(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
305-
role, err := b.StaticRole(ctx, req.Storage, d.Get("name").(string))
309+
roleName := d.Get("name").(string)
310+
role, err := b.StaticRole(ctx, req.Storage, roleName)
306311
if err != nil {
307312
return nil, err
308313
}
@@ -344,13 +349,17 @@ func (b *databaseBackend) pathStaticRoleRead(ctx context.Context, req *logical.R
344349
data["rotation_statements"] = []string{}
345350
}
346351

352+
recordDatabaseObservation(ctx, b, req, role.DBName, ObservationTypeDatabaseStaticRoleRead,
353+
AdditionalDatabaseMetadata{key: "role_name", value: roleName})
354+
347355
return &logical.Response{
348356
Data: data,
349357
}, nil
350358
}
351359

352360
func (b *databaseBackend) pathRoleRead(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
353-
role, err := b.Role(ctx, req.Storage, d.Get("name").(string))
361+
roleName := d.Get("name").(string)
362+
role, err := b.Role(ctx, req.Storage, roleName)
354363
if err != nil {
355364
return nil, err
356365
}
@@ -384,6 +393,9 @@ func (b *databaseBackend) pathRoleRead(ctx context.Context, req *logical.Request
384393
data["renew_statements"] = []string{}
385394
}
386395

396+
recordDatabaseObservation(ctx, b, req, role.DBName, ObservationTypeDatabaseRoleRead,
397+
AdditionalDatabaseMetadata{key: "role_name", value: roleName})
398+
387399
return &logical.Response{
388400
Data: data,
389401
}, nil
@@ -427,6 +439,7 @@ func (b *databaseBackend) pathRoleCreateUpdate(ctx context.Context, req *logical
427439
createOperation := (req.Operation == logical.CreateOperation)
428440

429441
// DB Attributes
442+
var credentialType string
430443
{
431444
if dbNameRaw, ok := data.GetOk("db_name"); ok {
432445
role.DBName = dbNameRaw.(string)
@@ -438,7 +451,7 @@ func (b *databaseBackend) pathRoleCreateUpdate(ctx context.Context, req *logical
438451
}
439452

440453
if credentialTypeRaw, ok := data.GetOk("credential_type"); ok {
441-
credentialType := credentialTypeRaw.(string)
454+
credentialType = credentialTypeRaw.(string)
442455
if err := role.setCredentialType(credentialType); err != nil {
443456
return logical.ErrorResponse(err.Error()), nil
444457
}
@@ -515,6 +528,21 @@ func (b *databaseBackend) pathRoleCreateUpdate(ctx context.Context, req *logical
515528
}
516529

517530
b.dbEvent(ctx, fmt.Sprintf("role-%s", req.Operation), req.Path, name, true)
531+
532+
if createOperation {
533+
recordDatabaseObservation(ctx, b, req, role.DBName, ObservationTypeDatabaseRoleCreate,
534+
AdditionalDatabaseMetadata{key: "role_name", value: name},
535+
AdditionalDatabaseMetadata{key: "credential_type", value: credentialType},
536+
AdditionalDatabaseMetadata{key: "default_ttl", value: role.DefaultTTL},
537+
AdditionalDatabaseMetadata{key: "max_ttl", value: role.MaxTTL})
538+
} else {
539+
recordDatabaseObservation(ctx, b, req, role.DBName, ObservationTypeDatabaseRoleUpdate,
540+
AdditionalDatabaseMetadata{key: "role_name", value: name},
541+
AdditionalDatabaseMetadata{key: "credential_type", value: credentialType},
542+
AdditionalDatabaseMetadata{key: "default_ttl", value: role.DefaultTTL},
543+
AdditionalDatabaseMetadata{key: "max_ttl", value: role.MaxTTL})
544+
}
545+
518546
return nil, nil
519547
}
520548

@@ -636,8 +664,9 @@ func (b *databaseBackend) pathStaticRoleCreateUpdate(ctx context.Context, req *l
636664
role.Statements.Rotation = data.Get("rotation_statements").([]string)
637665
}
638666

667+
var credentialType string
639668
if credentialTypeRaw, ok := data.GetOk("credential_type"); ok {
640-
credentialType := credentialTypeRaw.(string)
669+
credentialType = credentialTypeRaw.(string)
641670
if err := role.setCredentialType(credentialType); err != nil {
642671
return logical.ErrorResponse(err.Error()), nil
643672
}
@@ -799,6 +828,20 @@ func (b *databaseBackend) pathStaticRoleCreateUpdate(ctx context.Context, req *l
799828
}
800829
b.dbEvent(ctx, fmt.Sprintf("static-role-%s", req.Operation), req.Path, name, true)
801830

831+
if req.Operation == logical.CreateOperation {
832+
recordDatabaseObservation(ctx, b, req, role.DBName, ObservationTypeDatabaseStaticRoleCreate,
833+
AdditionalDatabaseMetadata{key: "role_name", value: name},
834+
AdditionalDatabaseMetadata{key: "credential_type", value: credentialType},
835+
AdditionalDatabaseMetadata{key: "default_ttl", value: role.DefaultTTL},
836+
AdditionalDatabaseMetadata{key: "max_ttl", value: role.MaxTTL})
837+
} else {
838+
recordDatabaseObservation(ctx, b, req, role.DBName, ObservationTypeDatabaseStaticRoleUpdate,
839+
AdditionalDatabaseMetadata{key: "role_name", value: name},
840+
AdditionalDatabaseMetadata{key: "credential_type", value: credentialType},
841+
AdditionalDatabaseMetadata{key: "default_ttl", value: role.DefaultTTL},
842+
AdditionalDatabaseMetadata{key: "max_ttl", value: role.MaxTTL})
843+
}
844+
802845
if len(response.Warnings) == 0 {
803846
return nil, nil
804847
}

builtin/logical/database/path_rotate_credentials.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,10 @@ func (b *databaseBackend) rotateRootCredentials(ctx context.Context, req *logica
9696
defer func() {
9797
if err == nil {
9898
b.dbEvent(ctx, "rotate-root", req.Path, name, modified)
99+
recordDatabaseObservation(ctx, b, req, name, ObservationTypeDatabaseRotateRootSuccess)
99100
} else {
100101
b.dbEvent(ctx, "rotate-root-fail", req.Path, name, modified)
102+
recordDatabaseObservation(ctx, b, req, name, ObservationTypeDatabaseRotateRootFailure)
101103
}
102104
}()
103105

@@ -221,6 +223,20 @@ func (b *databaseBackend) pathRotateRoleCredentialsUpdate() framework.OperationF
221223
return logical.ErrorResponse("no static role found for role name"), nil
222224
}
223225

226+
// We defer after we've found that the static role exists, otherwise it's not really fair to say
227+
// that the rotation failed.
228+
defer func() {
229+
if err == nil {
230+
recordDatabaseObservation(ctx, b, req, role.DBName, ObservationTypeDatabaseRotateStaticRoleSuccess,
231+
AdditionalDatabaseMetadata{key: "role_name", value: name},
232+
AdditionalDatabaseMetadata{key: "credential_type", value: role.CredentialType.String()})
233+
} else {
234+
recordDatabaseObservation(ctx, b, req, role.DBName, ObservationTypeDatabaseRotateStaticRoleFailure,
235+
AdditionalDatabaseMetadata{key: "role_name", value: name},
236+
AdditionalDatabaseMetadata{key: "credential_type", value: role.CredentialType.String()})
237+
}
238+
}()
239+
224240
// In create/update of static accounts, we only care if the operation
225241
// err'd , and this call does not return credentials
226242
item, err := b.popFromRotationQueueByKey(name)

builtin/logical/database/secret_creds.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,10 @@ func (b *databaseBackend) secretCredsRenew() framework.OperationFunc {
8989
resp := &logical.Response{Secret: req.Secret}
9090
resp.Secret.TTL = role.DefaultTTL
9191
resp.Secret.MaxTTL = role.MaxTTL
92+
93+
recordDatabaseObservation(ctx, b, req, role.DBName, ObservationTypeDatabaseCredentialRenew,
94+
AdditionalDatabaseMetadata{key: "role_name", value: role.Name},
95+
AdditionalDatabaseMetadata{key: "credential_type", value: role.CredentialType.String()})
9296
return resp, nil
9397
}
9498
}
@@ -111,11 +115,12 @@ func (b *databaseBackend) secretCredsRevoke() framework.OperationFunc {
111115
if !ok {
112116
return nil, fmt.Errorf("no role name was provided")
113117
}
118+
roleNameString := roleNameRaw.(string)
114119

115120
var dbName string
116121
var statements v4.Statements
117122

118-
role, err := b.Role(ctx, req.Storage, roleNameRaw.(string))
123+
role, err := b.Role(ctx, req.Storage, roleNameString)
119124
if err != nil {
120125
return nil, err
121126
}
@@ -168,6 +173,10 @@ func (b *databaseBackend) secretCredsRevoke() framework.OperationFunc {
168173
b.CloseIfShutdown(dbi, err)
169174
return nil, err
170175
}
176+
177+
recordDatabaseObservation(ctx, b, req, dbName, ObservationTypeDatabaseCredentialRevoke,
178+
AdditionalDatabaseMetadata{key: "role_name", value: roleNameString})
179+
171180
return resp, nil
172181
}
173182
}

0 commit comments

Comments
 (0)