diff --git a/collector/config.go b/collector/config.go index 8a66539..c7756a0 100644 --- a/collector/config.go +++ b/collector/config.go @@ -8,6 +8,7 @@ import ( "github.com/godror/godror/dsn" "github.com/oracle/oracle-db-appdev-monitoring/azvault" "github.com/oracle/oracle-db-appdev-monitoring/ocivault" + "github.com/oracle/oracle-db-appdev-monitoring/hashivault" "github.com/prometheus/exporter-toolkit/web" "gopkg.in/yaml.v2" "log/slog" @@ -58,6 +59,8 @@ type VaultConfig struct { OCI *OCIVault `yaml:"oci"` // Azure if present, Azure vault will be used to load username and/or password. Azure *AZVault `yaml:"azure"` + // HashiCorp Vault if present. HashiCorp Vault will be used to fetch database credentials. + HashiCorp *HashiCorpVault `yaml:"hashicorp"` } type OCIVault struct { @@ -72,6 +75,17 @@ type AZVault struct { PasswordSecret string `yaml:"passwordSecret"` } +type HashiCorpVault struct { + Socket string `yaml:"proxySocket"` + MountType string `yaml:"mountType"` + MountName string `yaml:"mountName"` + SecretPath string `yaml:"secretPath"` + UsernameAttr string `yaml:"usernameAttribute"` + PasswordAttr string `yaml:"passwordAttribute"` + // Private to avoid making multiple calls + fetchedSecert map[string]string +} + type MetricsFilesConfig struct { Default string Custom []string @@ -146,6 +160,31 @@ func (c ConnectConfig) GetQueryTimeout() int { return *c.QueryTimeout } +func (h HashiCorpVault) GetUsernameAttr() string { + if h.UsernameAttr == "" { + return "username" + } + return h.UsernameAttr +} + +func (h HashiCorpVault) GetPasswordAttr() string { + if h.PasswordAttr == "" { + return "password" + } + return h.PasswordAttr +} + +func (d DatabaseConfig) fetchHashiCorpVaultSecret() { + if len(d.Vault.HashiCorp.fetchedSecert) > 0 { + // Secret is already fetched, do nothing + return + } + vc := hashivault.CreateVaultClient(slog.Default(), d.Vault.HashiCorp.Socket) + // Set default username and password attribute values + requiredKeys := []string{d.Vault.HashiCorp.GetUsernameAttr(), d.Vault.HashiCorp.GetPasswordAttr()} + d.Vault.HashiCorp.fetchedSecert = vc.GetVaultSecret(d.Vault.HashiCorp.MountType, d.Vault.HashiCorp.MountName, d.Vault.HashiCorp.SecretPath, requiredKeys) +} + func (d DatabaseConfig) GetUsername() string { if d.isOCIVault() && d.Vault.OCI.UsernameSecret != "" { return ocivault.GetVaultSecret(d.Vault.OCI.ID, d.Vault.OCI.UsernameSecret) @@ -153,6 +192,10 @@ func (d DatabaseConfig) GetUsername() string { if d.isAzureVault() && d.Vault.Azure.UsernameSecret != "" { return azvault.GetVaultSecret(d.Vault.Azure.ID, d.Vault.Azure.UsernameSecret) } + if d.isHashiCorpVault() && d.Vault.HashiCorp.MountType != "" && d.Vault.HashiCorp.MountName != "" && d.Vault.HashiCorp.SecretPath != "" { + d.fetchHashiCorpVaultSecret() + return d.Vault.HashiCorp.fetchedSecert[d.Vault.HashiCorp.GetUsernameAttr()] + } return d.Username } @@ -171,6 +214,10 @@ func (d DatabaseConfig) GetPassword() string { if d.isAzureVault() && d.Vault.Azure.PasswordSecret != "" { return azvault.GetVaultSecret(d.Vault.Azure.ID, d.Vault.Azure.PasswordSecret) } + if d.isHashiCorpVault() && d.Vault.HashiCorp.MountType != "" && d.Vault.HashiCorp.MountName != "" && d.Vault.HashiCorp.SecretPath != "" { + d.fetchHashiCorpVaultSecret() + return d.Vault.HashiCorp.fetchedSecert[d.Vault.HashiCorp.GetPasswordAttr()] + } return d.Password } @@ -182,6 +229,10 @@ func (d DatabaseConfig) isAzureVault() bool { return d.Vault != nil && d.Vault.Azure != nil } +func (d DatabaseConfig) isHashiCorpVault() bool { + return d.Vault != nil && d.Vault.HashiCorp != nil +} + func LoadMetricsConfiguration(logger *slog.Logger, cfg *Config, path string, flags *web.FlagConfig) (*MetricsConfiguration, error) { m := &MetricsConfiguration{} if len(cfg.ConfigFile) > 0 { diff --git a/go.mod b/go.mod index 58984c2..6b618e7 100644 --- a/go.mod +++ b/go.mod @@ -23,22 +23,37 @@ require ( github.com/VictoriaMetrics/easyproto v0.1.4 // indirect github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect github.com/beorn7/perks v1.0.1 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/coreos/go-systemd/v22 v22.6.0 // indirect + github.com/go-jose/go-jose/v4 v4.1.1 // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/godror/knownpb v0.3.0 // indirect github.com/gofrs/flock v0.8.1 // indirect github.com/golang-jwt/jwt/v5 v5.3.0 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-retryablehttp v0.7.8 // indirect + github.com/hashicorp/go-rootcerts v1.0.2 // indirect + github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 // indirect + github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect + github.com/hashicorp/go-sockaddr v1.0.7 // indirect + github.com/hashicorp/hcl v1.0.1-vault-7 // indirect + github.com/hashicorp/vault/api v1.22.0 // indirect github.com/jpillora/backoff v1.0.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/mdlayher/socket v0.4.1 // indirect github.com/mdlayher/vsock v1.2.1 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/procfs v0.16.1 // indirect + github.com/ryanuber/go-glob v1.0.0 // indirect github.com/sony/gobreaker v0.5.0 // indirect github.com/xhit/go-str2duration/v2 v2.1.0 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect @@ -50,5 +65,6 @@ require ( golang.org/x/sync v0.16.0 // indirect golang.org/x/sys v0.35.0 // indirect golang.org/x/text v0.28.0 // indirect + golang.org/x/time v0.12.0 // indirect google.golang.org/protobuf v1.36.8 // indirect ) diff --git a/go.sum b/go.sum index b46fff5..b55e7a8 100644 --- a/go.sum +++ b/go.sum @@ -52,6 +52,8 @@ github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAu github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 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 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/go-jose/go-jose/v4 v4.1.1 h1:JYhSgy4mXXzAdF3nUx3ygx347LRXJRrpgyU3adRmkAI= +github.com/go-jose/go-jose/v4 v4.1.1/go.mod h1:BdsZGqgdO3b6tTc6LSE56wcDbMMLuPsw5d4ZD5f94kA= github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= 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 github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48= +github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw= +github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 h1:U+kC2dOhMFQctRfhK0gRctKAPTloZdMU5ZJxaesJ/VM= +github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0/go.mod h1:Ll013mhdmsVDuoIXVfBtvgGJsXDYkTw1kooNcoCXuE0= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= +github.com/hashicorp/go-sockaddr v1.0.7 h1:G+pTkSO01HpR5qCxg7lxfsFEZaG+C0VssTy/9dbT+Fw= +github.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0YgQaK/JakXqGyWw= +github.com/hashicorp/hcl v1.0.1-vault-7 h1:ag5OxFVy3QYTFTJODRzTKVZ6xvdfLLCA1cy/Y6xGI0I= +github.com/hashicorp/hcl v1.0.1-vault-7/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= +github.com/hashicorp/vault/api v1.22.0 h1:+HYFquE35/B74fHoIeXlZIP2YADVboaPjaSicHEZiH0= +github.com/hashicorp/vault/api v1.22.0/go.mod h1:IUZA2cDvr4Ok3+NtK2Oq/r+lJeXkeCrHRmqdyWfpmGM= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= 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 github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= github.com/mdlayher/vsock v1.2.1 h1:pC1mTJTvjo1r9n9fbm7S1j04rCgCzhCOS5DY0zqHlnQ= github.com/mdlayher/vsock v1.2.1/go.mod h1:NRfCibel++DgeMD8z/hP+PPTjlNJsdPOmxcnENvE+SE= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 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 github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= +github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/sony/gobreaker v0.5.0 h1:dRCvqm0P490vZPmy7ppEk2qCnCieBooFJ+YoXGYB+yg= github.com/sony/gobreaker v0.5.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= 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= golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= +golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= diff --git a/hashivault/hashivault.go b/hashivault/hashivault.go new file mode 100644 index 0000000..e7dbf8c --- /dev/null +++ b/hashivault/hashivault.go @@ -0,0 +1,116 @@ +// Copyright (c) 2025, Oracle and/or its affiliates. +// Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +package hashivault + +import ( + "context" + "strings" + "errors" + "net" + "net/http" + "time" + "github.com/oracle/oci-go-sdk/v65/example/helpers" + + "log/slog" + vault "github.com/hashicorp/vault/api" +) + +var UnsupportedMountType = errors.New("Unsupported HashiCorp Vault mount type") +var RequiredKeyMissing = errors.New("Required key missing from HashiCorp Vault secret") + +type HashicorpVaultClient struct { + client *vault.Client + logger *slog.Logger +} + +// newUnixSocketVaultClient creates a custom HTTP client using a Unix socket +func newUnixSocketVaultClient(socketPath string) (*vault.Client, error) { + httpClient := &http.Client{ + Transport: &http.Transport{ + DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { + return net.Dial("unix", socketPath) + }, + }, + Timeout: 10 * time.Second, + } + + // Configure the Vault client + config := &vault.Config{ + Address: "http://unix", + HttpClient: httpClient, + Timeout: 10 * time.Second, + MinRetryWait: time.Millisecond * 1000, + MaxRetryWait: time.Millisecond * 1500, + MaxRetries: 2, + } + + return vault.NewClient(config) +} + +// createVaultClient connects to a vault client, using connection method specified with the parameters. Returns error if fails. +func createVaultClient(logger *slog.Logger, socketPath string) (HashicorpVaultClient,error) { + var vaultClient HashicorpVaultClient + var err error + + if socketPath != "" { + // Create Vault client that uses Unix Socket + vaultClient.client, err = newUnixSocketVaultClient(socketPath) + } + if err != nil { + logger.Error("Failed to connect to HashiCorp Vault", "err", err) + } + vaultClient.logger = logger + return vaultClient,err +} + +// CreateVaultClient connects to a vault client, using connection method specified with the parameters. Fatal if fails. +func CreateVaultClient(logger *slog.Logger, socketPath string) HashicorpVaultClient { + c,err := createVaultClient(logger, socketPath) + helpers.FatalIfError(err) + return c +} + +// getVaultSecret fetches secret from vault using specified mount type. Returns error on failure. +func (c HashicorpVaultClient) getVaultSecret(mountType string, mount string, path string, requiredKeys []string) (map[string]string,error) { + result := map[string]string{} + var err error + if mountType == "kvv2" || mountType == "kvv1" { + // Handle simple key-value secrets + var secret *vault.KVSecret + c.logger.Info("Making call to HashiCorp Vault", "mountType", mountType, "mountName", mount, "secretPath", path, "expectedKeys", requiredKeys) + if mountType == "kvv2" { + secret, err = c.client.KVv2(mount).Get(context.TODO(), path) + } else { + secret, err = c.client.KVv1(mount).Get(context.TODO(), path) + } + if err != nil { + c.logger.Error("Failed to fetch secret from HashiCorp Vault", "err", err) + return result, err + } + // Expect simple one-level JSON, remap interface{} straight to string + for key,val := range secret.Data { + result[key] = strings.TrimRight(val.(string), "\r\n") // make sure a \r and/or \n didn't make it into the secret + } + } else { + c.logger.Error(UnsupportedMountType.Error()) + return result, UnsupportedMountType + } + // Check that we have all required keys present + for _, key := range requiredKeys { + val, keyExists := result[key] + if !keyExists || val == "" { + c.logger.Error(RequiredKeyMissing.Error(), "key", key) + return result, RequiredKeyMissing + } + } + return result, nil +} + +// GetVaultSecret fetches secret from vault using specified mount type. Fatal on failure. +func (c HashicorpVaultClient) GetVaultSecret(mountType string, mount string, path string, requiredKeys []string) map[string]string { + // Public callable function that does not return an error, just exits instead. Like other vault code in this project. + res,err := c.getVaultSecret(mountType, mount, path, requiredKeys) + helpers.FatalIfError(err) + return res +} diff --git a/site/docs/configuration/hashicorp-vault.md b/site/docs/configuration/hashicorp-vault.md new file mode 100644 index 0000000..4939927 --- /dev/null +++ b/site/docs/configuration/hashicorp-vault.md @@ -0,0 +1,58 @@ +--- +title: HashiCorp Vault +sidebar_position: 8 +--- + +# HashiCorp Vault + +Securely load database credentials from HashiCorp Vault. + +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: + +```yaml +databases: + mydb: + vault: + hashicorp: + proxySocket: /var/run/vault/vault.sock + mountType: secret engine type, currently either "kvv1" or "kvv2" + mountName: secret engine mount path + secretPath: path of the secret + usernameAttribute: name of the JSON attribute, where to read the database username, if ommitted defaults to "username" + passwordAttribute: name of the JSON attribute, where to read the database password, if ommitted defaults to "password" +``` + +Example + +```yaml +databases: + mydb: + vault: + hashicorp: + proxySocket: /var/run/vault/vault.sock + mountType: kvv2 + mountName: dev + secretPath: oracle/mydb/monitoring +``` + +### Authentication + +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. +Will expand the support for other methods in the future. + +Example Vault Proxy configuration snippet: + +``` +listener "unix" { + address = "/var/run/vault/vault.sock" + socket_mode = "0660" + socket_user = "vault" + socket_group = "vaultaccess" + tls_disable = true +} + +api_proxy { + # This always uses the auto_auth token when communicating with Vault server, even if client does not send a token + use_auto_auth_token = true +} +```