Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 30 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -802,7 +802,21 @@ If the `databases` array is empty or not provided for a metric, that metric will

### Using OCI Vault

The exporter will read the password from a secret stored in OCI Vault if you set these two environment variables:
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:

```yaml
databases:
mydb:
vault:
oci:
id: <VAULT OCID>
usernameSecret: <Secret containing DB username>
passwordSecret: <Secret containing DB password>
```

#### OCI Vault CLI Configuration

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:

- `OCI_VAULT_ID` should be set to the OCID of the OCI vault that you wish to use
- `OCI_VAULT_SECRET_NAME` should be set to the name of the secret in the OCI vault which contains the database password
Expand All @@ -811,7 +825,21 @@ The exporter will read the password from a secret stored in OCI Vault if you set

### Using Azure Vault

The exporter will read the database username and password from secrets stored in Azure Key Vault if you set these environment variables:
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:

```yaml
databases:
mydb:
vault:
azure:
id: <VAULT ID>
usernameSecret: <Secret containing DB username>
passwordSecret: <Secret containing DB password>
```

#### Azure Vault CLI Configuration

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:

- `AZ_VAULT_ID` should be set to the ID of the Azure Key Vault that you wish to use
- `AZ_VAULT_USERNAME_SECRET` should be set to the name of the secret in the Azure Key Vault which contains the database username
Expand Down
108 changes: 69 additions & 39 deletions collector/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"github.com/oracle/oracle-db-appdev-monitoring/ocivault"
"gopkg.in/yaml.v2"
"log/slog"
"maps"
"os"
"strings"
"time"
Expand All @@ -27,6 +26,7 @@ type DatabaseConfig struct {
Password string
URL string `yaml:"url"`
ConnectConfig `yaml:",inline"`
Vault *VaultConfig `yaml:"vault,omitempty"`
}

type ConnectConfig struct {
Expand All @@ -41,6 +41,25 @@ type ConnectConfig struct {
QueryTimeout *int `yaml:"queryTimeout"`
}

type VaultConfig struct {
// OCI if present, OCI vault will be used to load username and/or password.
OCI *OCIVault `yaml:"oci"`
// Azure if present, Azure vault will be used to load username and/or password.
Azure *AZVault `yaml:"azure"`
}

type OCIVault struct {
ID string `yaml:"id"`
UsernameSecret string `yaml:"usernameSecret"`
PasswordSecret string `yaml:"passwordSecret"`
}

type AZVault struct {
ID string `yaml:"id"`
UsernameSecret string `yaml:"usernameSecret"`
PasswordSecret string `yaml:"passwordSecret"`
}

type MetricsFilesConfig struct {
Default string
Custom []string
Expand Down Expand Up @@ -115,6 +134,32 @@ func (c ConnectConfig) GetQueryTimeout() int {
return *c.QueryTimeout
}

func (d DatabaseConfig) GetUsername() string {
if d.Vault == nil {
return d.Username
}
if d.Vault.OCI != nil {
return ocivault.GetVaultSecret(d.Vault.OCI.ID, d.Vault.OCI.UsernameSecret)
}
if d.Vault.Azure != nil {
return azvault.GetVaultSecret(d.Vault.Azure.ID, d.Vault.Azure.UsernameSecret)
}
return ""
}

func (d DatabaseConfig) GetPassword() string {
if d.Vault == nil {
return d.Password
}
if d.Vault.OCI != nil {
return ocivault.GetVaultSecret(d.Vault.OCI.ID, d.Vault.OCI.PasswordSecret)
}
if d.Vault.Azure != nil {
return azvault.GetVaultSecret(d.Vault.Azure.ID, d.Vault.Azure.PasswordSecret)
}
return ""
}

func LoadMetricsConfiguration(logger *slog.Logger, cfg *Config, path string) (*MetricsConfiguration, error) {
m := &MetricsConfiguration{}
if len(cfg.ConfigFile) > 0 {
Expand All @@ -127,16 +172,12 @@ func LoadMetricsConfiguration(logger *slog.Logger, cfg *Config, path string) (*M
return m, yerr
}
} else {
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")
m.Databases = make(map[string]DatabaseConfig)
m.Databases["default"] = m.defaultDatabase(cfg)
}

m.merge(cfg, path)

// TODO: rework vault support for multi-database.
// Currently, the vault user/password is applied for every database.
// It must be configurable at the database level for true multi-database support.
m.setKeyVaultUserPassword(logger)
return m, nil
}

Expand Down Expand Up @@ -172,8 +213,10 @@ func (m *MetricsConfiguration) mergeMetricsConfig(cfg *Config) {
}
}

// defaultDatabase creates a database named "default" if CLI arguments are used. It is for backwards compatibility when the exporter
// was only configurable through CLI arguments for a single database instance.
func (m *MetricsConfiguration) defaultDatabase(cfg *Config) DatabaseConfig {
return DatabaseConfig{
dbconfig := DatabaseConfig{
Username: cfg.User,
Password: cfg.Password,
URL: cfg.ConnectString,
Expand All @@ -189,38 +232,25 @@ func (m *MetricsConfiguration) defaultDatabase(cfg *Config) DatabaseConfig {
QueryTimeout: &cfg.QueryTimeout,
},
}
}

func (m *MetricsConfiguration) setKeyVaultUserPassword(logger *slog.Logger) {
if user, password, ok := getKeyVaultUserPassword(logger); ok {
for dbname := range maps.Keys(m.Databases) {
db := m.Databases[dbname]
db.Password = password
if len(user) > 0 {
db.Username = user
}
m.Databases[dbname] = db
// Vault ID lookup through environment variables is the historic method of loading vault metadata.
// These semantics are preserved if the "default" database from CLI config is requested.
if ociVaultID, useOciVault := os.LookupEnv("OCI_VAULT_ID"); useOciVault {
dbconfig.Vault = &VaultConfig{
OCI: &OCIVault{
ID: ociVaultID,
// For the CLI, only the password may be loaded from a secret. If you need to load
// both the username and password from OCI Vault, use the exporter configuration file.
PasswordSecret: os.Getenv("OCI_VAULT_SECRET_NAME"),
},
}
} else if azVaultID, useAzVault := os.LookupEnv("AZ_VAULT_ID"); useAzVault {
dbconfig.Vault = &VaultConfig{
Azure: &AZVault{
ID: azVaultID,
UsernameSecret: os.Getenv("AZ_VAULT_USERNAME_SECRET"),
PasswordSecret: os.Getenv("AZ_VAULT_PASSWORD_SECRET"),
},
}
}
}

func getKeyVaultUserPassword(logger *slog.Logger) (user string, password string, ok bool) {
ociVaultID, useOciVault := os.LookupEnv("OCI_VAULT_ID")
if useOciVault {

logger.Info("OCI_VAULT_ID env var is present so using OCI Vault", "vaultOCID", ociVaultID)
password = ocivault.GetVaultSecret(ociVaultID, os.Getenv("OCI_VAULT_SECRET_NAME"))
return "", password, true
}

azVaultID, useAzVault := os.LookupEnv("AZ_VAULT_ID")
if useAzVault {

logger.Info("AZ_VAULT_ID env var is present so using Azure Key Vault", "VaultID", azVaultID)
logger.Info("Using the environment variables AZURE_TENANT_ID, AZURE_CLIENT_ID, and AZURE_CLIENT_SECRET to authentication with Azure.")
user = azvault.GetVaultSecret(azVaultID, os.Getenv("AZ_VAULT_USERNAME_SECRET"))
password = azvault.GetVaultSecret(azVaultID, os.Getenv("AZ_VAULT_PASSWORD_SECRET"))
return user, password, true
}
return user, password, ok
return dbconfig
}
8 changes: 5 additions & 3 deletions collector/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,10 @@ func connect(logger *slog.Logger, dbname string, dbconfig DatabaseConfig) (*sql.
logger.Debug("Launching connection to "+maskDsn(dbconfig.URL), "database", dbname)

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

if dbconfig.GetPoolIncrement() > 0 {
logger.Debug(fmt.Sprintf("set pool increment to %d", dbconfig.PoolIncrement), "database", dbname)
Expand Down
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ func main() {
}
}()
}

// start the main server thread
server := &http.Server{}
if err := web.ListenAndServe(server, toolkitFlags, logger); err != nil {
Expand Down
14 changes: 0 additions & 14 deletions ocivault/ocivault.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,34 +8,20 @@ import (
b64 "encoding/base64"
"strings"

"github.com/prometheus/common/promslog"

"github.com/oracle/oci-go-sdk/v65/common"
"github.com/oracle/oci-go-sdk/v65/example/helpers"
"github.com/oracle/oci-go-sdk/v65/secrets"
)

func GetVaultSecret(vaultId string, secretName string) string {
promLogConfig := &promslog.Config{}
logger := promslog.New(promLogConfig)

client, err := secrets.NewSecretsClientWithConfigurationProvider(common.DefaultConfigProvider())
helpers.FatalIfError(err)

tenancyID, err := common.DefaultConfigProvider().TenancyOCID()
helpers.FatalIfError(err)
region, err := common.DefaultConfigProvider().Region()
helpers.FatalIfError(err)
logger.Info("OCI_VAULT_ID env var is present so using OCI Vault", "Region", region)
logger.Info("OCI_VAULT_ID env var is present so using OCI Vault", "tenancyOCID", tenancyID)

req := secrets.GetSecretBundleByNameRequest{
SecretName: common.String(secretName),
VaultId: common.String(vaultId)}

resp, err := client.GetSecretBundleByName(context.Background(), req)
helpers.FatalIfError(err)

rawSecret := getSecretFromBase64(resp)
return strings.TrimRight(rawSecret, "\r\n") // make sure a \r and/or \n didn't make it into the secret
}
Expand Down