Skip to content

Commit 4c488ea

Browse files
Vault key multi database (#240)
OCI/Azure Vault support for multi-database --------- Signed-off-by: Anders Swanson <anders.swanson@oracle.com>
1 parent a67a6b2 commit 4c488ea

File tree

5 files changed

+105
-59
lines changed

5 files changed

+105
-59
lines changed

‎README.md

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -802,7 +802,21 @@ If the `databases` array is empty or not provided for a metric, that metric will
802802
803803
### Using OCI Vault
804804
805-
The exporter will read the password from a secret stored in OCI Vault if you set these two environment variables:
805+
Each database in the config file may be configured to use OCI Vault. To load the database username and/or password from OCI Vault, set the `vault.oci` property to contain the OCI Vault OCID, and secret names for the database username/password:
806+
807+
```yaml
808+
databases:
809+
mydb:
810+
vault:
811+
oci:
812+
id: <VAULT OCID>
813+
usernameSecret: <Secret containing DB username>
814+
passwordSecret: <Secret containing DB password>
815+
```
816+
817+
#### OCI Vault CLI Configuration
818+
819+
If using the default database with CLI parameters, the exporter will read the password from a secret stored in OCI Vault if you set these two environment variables:
806820
807821
- `OCI_VAULT_ID` should be set to the OCID of the OCI vault that you wish to use
808822
- `OCI_VAULT_SECRET_NAME` should be set to the name of the secret in the OCI vault which contains the database password
@@ -811,7 +825,21 @@ The exporter will read the password from a secret stored in OCI Vault if you set
811825
812826
### Using Azure Vault
813827
814-
The exporter will read the database username and password from secrets stored in Azure Key Vault if you set these environment variables:
828+
Each database in the config file may be configured to use Azure Vault. To load the database username and/or password from Azure Vault, set the `vault.azure` property to contain the Azure Vault ID, and secret names for the database username/password:
829+
830+
```yaml
831+
databases:
832+
mydb:
833+
vault:
834+
azure:
835+
id: <VAULT ID>
836+
usernameSecret: <Secret containing DB username>
837+
passwordSecret: <Secret containing DB password>
838+
```
839+
840+
#### Azure Vault CLI Configuration
841+
842+
If using the default database with CLI parameters, the exporter will read the database username and password from secrets stored in Azure Key Vault if you set these environment variables:
815843
816844
- `AZ_VAULT_ID` should be set to the ID of the Azure Key Vault that you wish to use
817845
- `AZ_VAULT_USERNAME_SECRET` should be set to the name of the secret in the Azure Key Vault which contains the database username

‎collector/config.go

Lines changed: 69 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import (
99
"github.com/oracle/oracle-db-appdev-monitoring/ocivault"
1010
"gopkg.in/yaml.v2"
1111
"log/slog"
12-
"maps"
1312
"os"
1413
"strings"
1514
"time"
@@ -27,6 +26,7 @@ type DatabaseConfig struct {
2726
Password string
2827
URL string `yaml:"url"`
2928
ConnectConfig `yaml:",inline"`
29+
Vault *VaultConfig `yaml:"vault,omitempty"`
3030
}
3131

3232
type ConnectConfig struct {
@@ -41,6 +41,25 @@ type ConnectConfig struct {
4141
QueryTimeout *int `yaml:"queryTimeout"`
4242
}
4343

44+
type VaultConfig struct {
45+
// OCI if present, OCI vault will be used to load username and/or password.
46+
OCI *OCIVault `yaml:"oci"`
47+
// Azure if present, Azure vault will be used to load username and/or password.
48+
Azure *AZVault `yaml:"azure"`
49+
}
50+
51+
type OCIVault struct {
52+
ID string `yaml:"id"`
53+
UsernameSecret string `yaml:"usernameSecret"`
54+
PasswordSecret string `yaml:"passwordSecret"`
55+
}
56+
57+
type AZVault struct {
58+
ID string `yaml:"id"`
59+
UsernameSecret string `yaml:"usernameSecret"`
60+
PasswordSecret string `yaml:"passwordSecret"`
61+
}
62+
4463
type MetricsFilesConfig struct {
4564
Default string
4665
Custom []string
@@ -115,6 +134,32 @@ func (c ConnectConfig) GetQueryTimeout() int {
115134
return *c.QueryTimeout
116135
}
117136

137+
func (d DatabaseConfig) GetUsername() string {
138+
if d.Vault == nil {
139+
return d.Username
140+
}
141+
if d.Vault.OCI != nil {
142+
return ocivault.GetVaultSecret(d.Vault.OCI.ID, d.Vault.OCI.UsernameSecret)
143+
}
144+
if d.Vault.Azure != nil {
145+
return azvault.GetVaultSecret(d.Vault.Azure.ID, d.Vault.Azure.UsernameSecret)
146+
}
147+
return ""
148+
}
149+
150+
func (d DatabaseConfig) GetPassword() string {
151+
if d.Vault == nil {
152+
return d.Password
153+
}
154+
if d.Vault.OCI != nil {
155+
return ocivault.GetVaultSecret(d.Vault.OCI.ID, d.Vault.OCI.PasswordSecret)
156+
}
157+
if d.Vault.Azure != nil {
158+
return azvault.GetVaultSecret(d.Vault.Azure.ID, d.Vault.Azure.PasswordSecret)
159+
}
160+
return ""
161+
}
162+
118163
func LoadMetricsConfiguration(logger *slog.Logger, cfg *Config, path string) (*MetricsConfiguration, error) {
119164
m := &MetricsConfiguration{}
120165
if len(cfg.ConfigFile) > 0 {
@@ -127,16 +172,12 @@ func LoadMetricsConfiguration(logger *slog.Logger, cfg *Config, path string) (*M
127172
return m, yerr
128173
}
129174
} else {
175+
logger.Warn("Configuring default database from CLI parameters is deprecated. Use of the '--config.file' argument is preferred. See https://github.com/oracle/oracle-db-appdev-monitoring?tab=readme-ov-file#standalone-binary")
130176
m.Databases = make(map[string]DatabaseConfig)
131177
m.Databases["default"] = m.defaultDatabase(cfg)
132178
}
133179

134180
m.merge(cfg, path)
135-
136-
// TODO: rework vault support for multi-database.
137-
// Currently, the vault user/password is applied for every database.
138-
// It must be configurable at the database level for true multi-database support.
139-
m.setKeyVaultUserPassword(logger)
140181
return m, nil
141182
}
142183

@@ -172,8 +213,10 @@ func (m *MetricsConfiguration) mergeMetricsConfig(cfg *Config) {
172213
}
173214
}
174215

216+
// defaultDatabase creates a database named "default" if CLI arguments are used. It is for backwards compatibility when the exporter
217+
// was only configurable through CLI arguments for a single database instance.
175218
func (m *MetricsConfiguration) defaultDatabase(cfg *Config) DatabaseConfig {
176-
return DatabaseConfig{
219+
dbconfig := DatabaseConfig{
177220
Username: cfg.User,
178221
Password: cfg.Password,
179222
URL: cfg.ConnectString,
@@ -189,38 +232,25 @@ func (m *MetricsConfiguration) defaultDatabase(cfg *Config) DatabaseConfig {
189232
QueryTimeout: &cfg.QueryTimeout,
190233
},
191234
}
192-
}
193-
194-
func (m *MetricsConfiguration) setKeyVaultUserPassword(logger *slog.Logger) {
195-
if user, password, ok := getKeyVaultUserPassword(logger); ok {
196-
for dbname := range maps.Keys(m.Databases) {
197-
db := m.Databases[dbname]
198-
db.Password = password
199-
if len(user) > 0 {
200-
db.Username = user
201-
}
202-
m.Databases[dbname] = db
235+
// Vault ID lookup through environment variables is the historic method of loading vault metadata.
236+
// These semantics are preserved if the "default" database from CLI config is requested.
237+
if ociVaultID, useOciVault := os.LookupEnv("OCI_VAULT_ID"); useOciVault {
238+
dbconfig.Vault = &VaultConfig{
239+
OCI: &OCIVault{
240+
ID: ociVaultID,
241+
// For the CLI, only the password may be loaded from a secret. If you need to load
242+
// both the username and password from OCI Vault, use the exporter configuration file.
243+
PasswordSecret: os.Getenv("OCI_VAULT_SECRET_NAME"),
244+
},
245+
}
246+
} else if azVaultID, useAzVault := os.LookupEnv("AZ_VAULT_ID"); useAzVault {
247+
dbconfig.Vault = &VaultConfig{
248+
Azure: &AZVault{
249+
ID: azVaultID,
250+
UsernameSecret: os.Getenv("AZ_VAULT_USERNAME_SECRET"),
251+
PasswordSecret: os.Getenv("AZ_VAULT_PASSWORD_SECRET"),
252+
},
203253
}
204254
}
205-
}
206-
207-
func getKeyVaultUserPassword(logger *slog.Logger) (user string, password string, ok bool) {
208-
ociVaultID, useOciVault := os.LookupEnv("OCI_VAULT_ID")
209-
if useOciVault {
210-
211-
logger.Info("OCI_VAULT_ID env var is present so using OCI Vault", "vaultOCID", ociVaultID)
212-
password = ocivault.GetVaultSecret(ociVaultID, os.Getenv("OCI_VAULT_SECRET_NAME"))
213-
return "", password, true
214-
}
215-
216-
azVaultID, useAzVault := os.LookupEnv("AZ_VAULT_ID")
217-
if useAzVault {
218-
219-
logger.Info("AZ_VAULT_ID env var is present so using Azure Key Vault", "VaultID", azVaultID)
220-
logger.Info("Using the environment variables AZURE_TENANT_ID, AZURE_CLIENT_ID, and AZURE_CLIENT_SECRET to authentication with Azure.")
221-
user = azvault.GetVaultSecret(azVaultID, os.Getenv("AZ_VAULT_USERNAME_SECRET"))
222-
password = azvault.GetVaultSecret(azVaultID, os.Getenv("AZ_VAULT_PASSWORD_SECRET"))
223-
return user, password, true
224-
}
225-
return user, password, ok
255+
return dbconfig
226256
}

‎collector/database.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,10 @@ func connect(logger *slog.Logger, dbname string, dbconfig DatabaseConfig) (*sql.
6767
logger.Debug("Launching connection to "+maskDsn(dbconfig.URL), "database", dbname)
6868

6969
var P godror.ConnectionParams
70-
// If password is not specified, externalAuth will be true and we'll ignore user input
71-
dbconfig.ExternalAuth = dbconfig.Password == ""
70+
password := dbconfig.GetPassword()
71+
username := dbconfig.GetUsername()
72+
// If password is not specified, externalAuth will be true, and we'll ignore user input
73+
dbconfig.ExternalAuth = password == ""
7274
logger.Debug(fmt.Sprintf("external authentication set to %t", dbconfig.ExternalAuth), "database", dbname)
7375
msg := "Using Username/Password Authentication."
7476
if dbconfig.ExternalAuth {
@@ -80,7 +82,7 @@ func connect(logger *slog.Logger, dbname string, dbconfig DatabaseConfig) (*sql.
8082
Bool: dbconfig.ExternalAuth,
8183
Valid: true,
8284
}
83-
P.Username, P.Password, P.ConnectString, P.ExternalAuth = dbconfig.Username, godror.NewPassword(dbconfig.Password), dbconfig.URL, externalAuth
85+
P.Username, P.Password, P.ConnectString, P.ExternalAuth = username, godror.NewPassword(password), dbconfig.URL, externalAuth
8486

8587
if dbconfig.GetPoolIncrement() > 0 {
8688
logger.Debug(fmt.Sprintf("set pool increment to %d", dbconfig.PoolIncrement), "database", dbname)

‎main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ func main() {
198198
}
199199
}()
200200
}
201-
201+
202202
// start the main server thread
203203
server := &http.Server{}
204204
if err := web.ListenAndServe(server, toolkitFlags, logger); err != nil {

‎ocivault/ocivault.go

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,34 +8,20 @@ import (
88
b64 "encoding/base64"
99
"strings"
1010

11-
"github.com/prometheus/common/promslog"
12-
1311
"github.com/oracle/oci-go-sdk/v65/common"
1412
"github.com/oracle/oci-go-sdk/v65/example/helpers"
1513
"github.com/oracle/oci-go-sdk/v65/secrets"
1614
)
1715

1816
func GetVaultSecret(vaultId string, secretName string) string {
19-
promLogConfig := &promslog.Config{}
20-
logger := promslog.New(promLogConfig)
21-
2217
client, err := secrets.NewSecretsClientWithConfigurationProvider(common.DefaultConfigProvider())
2318
helpers.FatalIfError(err)
2419

25-
tenancyID, err := common.DefaultConfigProvider().TenancyOCID()
26-
helpers.FatalIfError(err)
27-
region, err := common.DefaultConfigProvider().Region()
28-
helpers.FatalIfError(err)
29-
logger.Info("OCI_VAULT_ID env var is present so using OCI Vault", "Region", region)
30-
logger.Info("OCI_VAULT_ID env var is present so using OCI Vault", "tenancyOCID", tenancyID)
31-
3220
req := secrets.GetSecretBundleByNameRequest{
3321
SecretName: common.String(secretName),
3422
VaultId: common.String(vaultId)}
35-
3623
resp, err := client.GetSecretBundleByName(context.Background(), req)
3724
helpers.FatalIfError(err)
38-
3925
rawSecret := getSecretFromBase64(resp)
4026
return strings.TrimRight(rawSecret, "\r\n") // make sure a \r and/or \n didn't make it into the secret
4127
}

0 commit comments

Comments
 (0)