Skip to content

Commit d42aeed

Browse files
authored
397 - HashiCorp Vault support for database secrets (#398)
* Signed-off-by: Ilmar Kerm <ilmar.kerm@gmail.com> First edition of HashiCorp Vault support
1 parent 134ce60 commit d42aeed

File tree

5 files changed

+274
-0
lines changed

5 files changed

+274
-0
lines changed

collector/config.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"fmt"
88
"github.com/oracle/oracle-db-appdev-monitoring/azvault"
99
"github.com/oracle/oracle-db-appdev-monitoring/ocivault"
10+
"github.com/oracle/oracle-db-appdev-monitoring/hashivault"
1011
"github.com/prometheus/exporter-toolkit/web"
1112
"gopkg.in/yaml.v2"
1213
"log/slog"
@@ -57,6 +58,8 @@ type VaultConfig struct {
5758
OCI *OCIVault `yaml:"oci"`
5859
// Azure if present, Azure vault will be used to load username and/or password.
5960
Azure *AZVault `yaml:"azure"`
61+
// HashiCorp Vault if present. HashiCorp Vault will be used to fetch database credentials.
62+
HashiCorp *HashiCorpVault `yaml:"hashicorp"`
6063
}
6164

6265
type OCIVault struct {
@@ -71,6 +74,17 @@ type AZVault struct {
7174
PasswordSecret string `yaml:"passwordSecret"`
7275
}
7376

77+
type HashiCorpVault struct {
78+
Socket string `yaml:"proxySocket"`
79+
MountType string `yaml:"mountType"`
80+
MountName string `yaml:"mountName"`
81+
SecretPath string `yaml:"secretPath"`
82+
UsernameAttr string `yaml:"usernameAttribute"`
83+
PasswordAttr string `yaml:"passwordAttribute"`
84+
// Private to avoid making multiple calls
85+
fetchedSecert map[string]string
86+
}
87+
7488
type MetricsFilesConfig struct {
7589
Default string
7690
Custom []string
@@ -145,13 +159,42 @@ func (c ConnectConfig) GetQueryTimeout() int {
145159
return *c.QueryTimeout
146160
}
147161

162+
func (h HashiCorpVault) GetUsernameAttr() string {
163+
if h.UsernameAttr == "" {
164+
return "username"
165+
}
166+
return h.UsernameAttr
167+
}
168+
169+
func (h HashiCorpVault) GetPasswordAttr() string {
170+
if h.PasswordAttr == "" {
171+
return "password"
172+
}
173+
return h.PasswordAttr
174+
}
175+
176+
func (d DatabaseConfig) fetchHashiCorpVaultSecret() {
177+
if len(d.Vault.HashiCorp.fetchedSecert) > 0 {
178+
// Secret is already fetched, do nothing
179+
return
180+
}
181+
vc := hashivault.CreateVaultClient(slog.Default(), d.Vault.HashiCorp.Socket)
182+
// Set default username and password attribute values
183+
requiredKeys := []string{d.Vault.HashiCorp.GetUsernameAttr(), d.Vault.HashiCorp.GetPasswordAttr()}
184+
d.Vault.HashiCorp.fetchedSecert = vc.GetVaultSecret(d.Vault.HashiCorp.MountType, d.Vault.HashiCorp.MountName, d.Vault.HashiCorp.SecretPath, requiredKeys)
185+
}
186+
148187
func (d DatabaseConfig) GetUsername() string {
149188
if d.isOCIVault() && d.Vault.OCI.UsernameSecret != "" {
150189
return ocivault.GetVaultSecret(d.Vault.OCI.ID, d.Vault.OCI.UsernameSecret)
151190
}
152191
if d.isAzureVault() && d.Vault.Azure.UsernameSecret != "" {
153192
return azvault.GetVaultSecret(d.Vault.Azure.ID, d.Vault.Azure.UsernameSecret)
154193
}
194+
if d.isHashiCorpVault() && d.Vault.HashiCorp.MountType != "" && d.Vault.HashiCorp.MountName != "" && d.Vault.HashiCorp.SecretPath != "" {
195+
d.fetchHashiCorpVaultSecret()
196+
return d.Vault.HashiCorp.fetchedSecert[d.Vault.HashiCorp.GetUsernameAttr()]
197+
}
155198
return d.Username
156199
}
157200

@@ -170,6 +213,10 @@ func (d DatabaseConfig) GetPassword() string {
170213
if d.isAzureVault() && d.Vault.Azure.PasswordSecret != "" {
171214
return azvault.GetVaultSecret(d.Vault.Azure.ID, d.Vault.Azure.PasswordSecret)
172215
}
216+
if d.isHashiCorpVault() && d.Vault.HashiCorp.MountType != "" && d.Vault.HashiCorp.MountName != "" && d.Vault.HashiCorp.SecretPath != "" {
217+
d.fetchHashiCorpVaultSecret()
218+
return d.Vault.HashiCorp.fetchedSecert[d.Vault.HashiCorp.GetPasswordAttr()]
219+
}
173220
return d.Password
174221
}
175222

@@ -181,6 +228,10 @@ func (d DatabaseConfig) isAzureVault() bool {
181228
return d.Vault != nil && d.Vault.Azure != nil
182229
}
183230

231+
func (d DatabaseConfig) isHashiCorpVault() bool {
232+
return d.Vault != nil && d.Vault.HashiCorp != nil
233+
}
234+
184235
func LoadMetricsConfiguration(logger *slog.Logger, cfg *Config, path string, flags *web.FlagConfig) (*MetricsConfiguration, error) {
185236
m := &MetricsConfiguration{}
186237
if len(cfg.ConfigFile) > 0 {

go.mod

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,22 +23,37 @@ require (
2323
github.com/VictoriaMetrics/easyproto v0.1.4 // indirect
2424
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect
2525
github.com/beorn7/perks v1.0.1 // indirect
26+
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
2627
github.com/cespare/xxhash/v2 v2.3.0 // indirect
2728
github.com/coreos/go-systemd/v22 v22.6.0 // indirect
29+
github.com/go-jose/go-jose/v4 v4.1.1 // indirect
2830
github.com/go-logfmt/logfmt v0.6.0 // indirect
2931
github.com/godror/knownpb v0.3.0 // indirect
3032
github.com/gofrs/flock v0.8.1 // indirect
3133
github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
3234
github.com/google/uuid v1.6.0 // indirect
35+
github.com/hashicorp/errwrap v1.1.0 // indirect
36+
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
37+
github.com/hashicorp/go-multierror v1.1.1 // indirect
38+
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
39+
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
40+
github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 // indirect
41+
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect
42+
github.com/hashicorp/go-sockaddr v1.0.7 // indirect
43+
github.com/hashicorp/hcl v1.0.1-vault-7 // indirect
44+
github.com/hashicorp/vault/api v1.22.0 // indirect
3345
github.com/jpillora/backoff v1.0.0 // indirect
3446
github.com/kylelemons/godebug v1.1.0 // indirect
3547
github.com/mdlayher/socket v0.4.1 // indirect
3648
github.com/mdlayher/vsock v1.2.1 // indirect
49+
github.com/mitchellh/go-homedir v1.1.0 // indirect
50+
github.com/mitchellh/mapstructure v1.5.0 // indirect
3751
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
3852
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect
3953
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
4054
github.com/prometheus/client_model v0.6.2 // indirect
4155
github.com/prometheus/procfs v0.16.1 // indirect
56+
github.com/ryanuber/go-glob v1.0.0 // indirect
4257
github.com/sony/gobreaker v0.5.0 // indirect
4358
github.com/xhit/go-str2duration/v2 v2.1.0 // indirect
4459
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
@@ -50,5 +65,6 @@ require (
5065
golang.org/x/sync v0.16.0 // indirect
5166
golang.org/x/sys v0.35.0 // indirect
5267
golang.org/x/text v0.28.0 // indirect
68+
golang.org/x/time v0.12.0 // indirect
5369
google.golang.org/protobuf v1.36.8 // indirect
5470
)

go.sum

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAu
5252
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
5353
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
5454
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
55+
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
56+
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
5557
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
5658
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
5759
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
@@ -63,6 +65,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
6365
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
6466
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
6567
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
68+
github.com/go-jose/go-jose/v4 v4.1.1 h1:JYhSgy4mXXzAdF3nUx3ygx347LRXJRrpgyU3adRmkAI=
69+
github.com/go-jose/go-jose/v4 v4.1.1/go.mod h1:BdsZGqgdO3b6tTc6LSE56wcDbMMLuPsw5d4ZD5f94kA=
6670
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
6771
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
6872
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
@@ -97,6 +101,27 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
97101
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
98102
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
99103
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
104+
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
105+
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
106+
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
107+
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
108+
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
109+
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
110+
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
111+
github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48=
112+
github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw=
113+
github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
114+
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
115+
github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 h1:U+kC2dOhMFQctRfhK0gRctKAPTloZdMU5ZJxaesJ/VM=
116+
github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0/go.mod h1:Ll013mhdmsVDuoIXVfBtvgGJsXDYkTw1kooNcoCXuE0=
117+
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts=
118+
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4=
119+
github.com/hashicorp/go-sockaddr v1.0.7 h1:G+pTkSO01HpR5qCxg7lxfsFEZaG+C0VssTy/9dbT+Fw=
120+
github.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0YgQaK/JakXqGyWw=
121+
github.com/hashicorp/hcl v1.0.1-vault-7 h1:ag5OxFVy3QYTFTJODRzTKVZ6xvdfLLCA1cy/Y6xGI0I=
122+
github.com/hashicorp/hcl v1.0.1-vault-7/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM=
123+
github.com/hashicorp/vault/api v1.22.0 h1:+HYFquE35/B74fHoIeXlZIP2YADVboaPjaSicHEZiH0=
124+
github.com/hashicorp/vault/api v1.22.0/go.mod h1:IUZA2cDvr4Ok3+NtK2Oq/r+lJeXkeCrHRmqdyWfpmGM=
100125
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
101126
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
102127
github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs=
@@ -116,6 +141,10 @@ github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U
116141
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
117142
github.com/mdlayher/vsock v1.2.1 h1:pC1mTJTvjo1r9n9fbm7S1j04rCgCzhCOS5DY0zqHlnQ=
118143
github.com/mdlayher/vsock v1.2.1/go.mod h1:NRfCibel++DgeMD8z/hP+PPTjlNJsdPOmxcnENvE+SE=
144+
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
145+
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
146+
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
147+
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
119148
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
120149
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
121150
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU=
@@ -182,6 +211,8 @@ github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0
182211
github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI=
183212
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
184213
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
214+
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
215+
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
185216
github.com/sony/gobreaker v0.5.0 h1:dRCvqm0P490vZPmy7ppEk2qCnCieBooFJ+YoXGYB+yg=
186217
github.com/sony/gobreaker v0.5.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
187218
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -315,6 +346,8 @@ golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
315346
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
316347
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
317348
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
349+
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
350+
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
318351
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
319352
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
320353
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=

hashivault/hashivault.go

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
// Copyright (c) 2025, Oracle and/or its affiliates.
2+
// Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
3+
4+
package hashivault
5+
6+
import (
7+
"context"
8+
"strings"
9+
"errors"
10+
"net"
11+
"net/http"
12+
"time"
13+
"github.com/oracle/oci-go-sdk/v65/example/helpers"
14+
15+
"log/slog"
16+
vault "github.com/hashicorp/vault/api"
17+
)
18+
19+
var UnsupportedMountType = errors.New("Unsupported HashiCorp Vault mount type")
20+
var RequiredKeyMissing = errors.New("Required key missing from HashiCorp Vault secret")
21+
22+
type HashicorpVaultClient struct {
23+
client *vault.Client
24+
logger *slog.Logger
25+
}
26+
27+
// newUnixSocketVaultClient creates a custom HTTP client using a Unix socket
28+
func newUnixSocketVaultClient(socketPath string) (*vault.Client, error) {
29+
httpClient := &http.Client{
30+
Transport: &http.Transport{
31+
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
32+
return net.Dial("unix", socketPath)
33+
},
34+
},
35+
Timeout: 10 * time.Second,
36+
}
37+
38+
// Configure the Vault client
39+
config := &vault.Config{
40+
Address: "http://unix",
41+
HttpClient: httpClient,
42+
Timeout: 10 * time.Second,
43+
MinRetryWait: time.Millisecond * 1000,
44+
MaxRetryWait: time.Millisecond * 1500,
45+
MaxRetries: 2,
46+
}
47+
48+
return vault.NewClient(config)
49+
}
50+
51+
// createVaultClient connects to a vault client, using connection method specified with the parameters. Returns error if fails.
52+
func createVaultClient(logger *slog.Logger, socketPath string) (HashicorpVaultClient,error) {
53+
var vaultClient HashicorpVaultClient
54+
var err error
55+
56+
if socketPath != "" {
57+
// Create Vault client that uses Unix Socket
58+
vaultClient.client, err = newUnixSocketVaultClient(socketPath)
59+
}
60+
if err != nil {
61+
logger.Error("Failed to connect to HashiCorp Vault", "err", err)
62+
}
63+
vaultClient.logger = logger
64+
return vaultClient,err
65+
}
66+
67+
// CreateVaultClient connects to a vault client, using connection method specified with the parameters. Fatal if fails.
68+
func CreateVaultClient(logger *slog.Logger, socketPath string) HashicorpVaultClient {
69+
c,err := createVaultClient(logger, socketPath)
70+
helpers.FatalIfError(err)
71+
return c
72+
}
73+
74+
// getVaultSecret fetches secret from vault using specified mount type. Returns error on failure.
75+
func (c HashicorpVaultClient) getVaultSecret(mountType string, mount string, path string, requiredKeys []string) (map[string]string,error) {
76+
result := map[string]string{}
77+
var err error
78+
if mountType == "kvv2" || mountType == "kvv1" {
79+
// Handle simple key-value secrets
80+
var secret *vault.KVSecret
81+
c.logger.Info("Making call to HashiCorp Vault", "mountType", mountType, "mountName", mount, "secretPath", path, "expectedKeys", requiredKeys)
82+
if mountType == "kvv2" {
83+
secret, err = c.client.KVv2(mount).Get(context.TODO(), path)
84+
} else {
85+
secret, err = c.client.KVv1(mount).Get(context.TODO(), path)
86+
}
87+
if err != nil {
88+
c.logger.Error("Failed to fetch secret from HashiCorp Vault", "err", err)
89+
return result, err
90+
}
91+
// Expect simple one-level JSON, remap interface{} straight to string
92+
for key,val := range secret.Data {
93+
result[key] = strings.TrimRight(val.(string), "\r\n") // make sure a \r and/or \n didn't make it into the secret
94+
}
95+
} else {
96+
c.logger.Error(UnsupportedMountType.Error())
97+
return result, UnsupportedMountType
98+
}
99+
// Check that we have all required keys present
100+
for _, key := range requiredKeys {
101+
val, keyExists := result[key]
102+
if !keyExists || val == "" {
103+
c.logger.Error(RequiredKeyMissing.Error(), "key", key)
104+
return result, RequiredKeyMissing
105+
}
106+
}
107+
return result, nil
108+
}
109+
110+
// GetVaultSecret fetches secret from vault using specified mount type. Fatal on failure.
111+
func (c HashicorpVaultClient) GetVaultSecret(mountType string, mount string, path string, requiredKeys []string) map[string]string {
112+
// Public callable function that does not return an error, just exits instead. Like other vault code in this project.
113+
res,err := c.getVaultSecret(mountType, mount, path, requiredKeys)
114+
helpers.FatalIfError(err)
115+
return res
116+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
---
2+
title: HashiCorp Vault
3+
sidebar_position: 8
4+
---
5+
6+
# HashiCorp Vault
7+
8+
Securely load database credentials from HashiCorp Vault.
9+
10+
Each database in the config file may be configured to use HashiCorp Vault. To load the database username and/or password from HashiCorp Vault, set the `vault.hashicorp` property to contain the following information:
11+
12+
```yaml
13+
databases:
14+
mydb:
15+
vault:
16+
hashicorp:
17+
proxySocket: /var/run/vault/vault.sock
18+
mountType: secret engine type, currently either "kvv1" or "kvv2"
19+
mountName: secret engine mount path
20+
secretPath: path of the secret
21+
usernameAttribute: name of the JSON attribute, where to read the database username, if ommitted defaults to "username"
22+
passwordAttribute: name of the JSON attribute, where to read the database password, if ommitted defaults to "password"
23+
```
24+
25+
Example
26+
27+
```yaml
28+
databases:
29+
mydb:
30+
vault:
31+
hashicorp:
32+
proxySocket: /var/run/vault/vault.sock
33+
mountType: kvv2
34+
mountName: dev
35+
secretPath: oracle/mydb/monitoring
36+
```
37+
38+
### Authentication
39+
40+
In this first version it currently only supports queries via HashiCorp Vault Proxy configured to run on the local host and listening on a Unix socket. Currently also required use_auto_auth_token option to be set.
41+
Will expand the support for other methods in the future.
42+
43+
Example Vault Proxy configuration snippet:
44+
45+
```
46+
listener "unix" {
47+
address = "/var/run/vault/vault.sock"
48+
socket_mode = "0660"
49+
socket_user = "vault"
50+
socket_group = "vaultaccess"
51+
tls_disable = true
52+
}
53+
54+
api_proxy {
55+
# This always uses the auto_auth token when communicating with Vault server, even if client does not send a token
56+
use_auto_auth_token = true
57+
}
58+
```

0 commit comments

Comments
 (0)