Skip to content
Draft
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
208 changes: 208 additions & 0 deletions benchmarktests/target_secret_influxdb.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package benchmarktests

import (
"flag"
"fmt"
"log"
"net/http"
"os"
"path/filepath"
"strings"

"github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-uuid"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/gohcl"
"github.com/hashicorp/vault/api"
vegeta "github.com/tsenart/vegeta/v12/lib"
)

// Constants for test
const (
InfluxDBSecretTestType = "influxdb_secret"
InfluxDBSecretTestMethod = "GET"
InfluxDBUsernameEnvVar = VaultBenchmarkEnvVarPrefix + "INFLUXDB_USERNAME"
InfluxDBPasswordEnvVar = VaultBenchmarkEnvVarPrefix + "INFLUXDB_PASSWORD"
)

func init() {
// "Register" this test to the main test registry
TestList[InfluxDBSecretTestType] = func() BenchmarkBuilder { return &InfluxDBSecret{} }
}

// InfluxDB Secret Test Struct
type InfluxDBSecret struct {
pathPrefix string
roleName string
header http.Header
config *InfluxDBSecretTestConfig
logger hclog.Logger
}

// Main Config Struct
type InfluxDBSecretTestConfig struct {
InfluxDBDBConfig *InfluxDBDBConfig `hcl:"db_connection,block"`
InfluxDBRoleConfig *InfluxDBRoleConfig `hcl:"role,block"`
}

// InfluxDB DB Config
type InfluxDBDBConfig struct {
Name string `hcl:"name,optional"`
PluginName string `hcl:"plugin_name,optional"`
PluginVersion string `hcl:"plugin_version,optional"`
VerifyConnection *bool `hcl:"verify_connection,optional"`
AllowedRoles []string `hcl:"allowed_roles,optional"`
RootRotationStatements []string `hcl:"root_rotation_statements,optional"`
PasswordPolicy string `hcl:"password_policy,optional"`
Host string `hcl:"host,optional"`
Port int `hcl:"port,optional"`
Username string `hcl:"username,optional"`
Password string `hcl:"password,optional"`
TLS bool `hcl:"tls,optional"`
InsecureTLS bool `hcl:"insecure_tls,optional"`
ConnectTimeout string `hcl:"connect_timeout,optional"`
UsernameTemplate string `hcl:"username_template,optional"`
}

// InfluxDB Role Config
type InfluxDBRoleConfig struct {
Name string `hcl:"name,optional"`
DBName string `hcl:"db_name,optional"`
DefaultTTL string `hcl:"default_ttl,optional"`
MaxTTL string `hcl:"max_ttl,optional"`
CreationStatements string `hcl:"creation_statements"`
RevocationStatements string `hcl:"revocation_statements,optional"`
RollbackStatements string `hcl:"rollback_statements,optional"`
RenewStatements string `hcl:"renew_statements,optional"`
}

// ParseConfig parses the passed in hcl.Body into Configuration structs for use during
// test configuration in Vault. Any default configuration definitions for required
// parameters will be set here.
func (i *InfluxDBSecret) ParseConfig(body hcl.Body) error {
// provide defaults
testConfig := &struct {
Config *InfluxDBSecretTestConfig `hcl:"config,block"`
}{
Config: &InfluxDBSecretTestConfig{
InfluxDBDBConfig: &InfluxDBDBConfig{
Name: "benchmark-influxdb",
AllowedRoles: []string{"benchmark-role"},
PluginName: "influxdb-database-plugin",
Username: os.Getenv(InfluxDBUsernameEnvVar),
Password: os.Getenv(InfluxDBPasswordEnvVar),
},
InfluxDBRoleConfig: &InfluxDBRoleConfig{
Name: "benchmark-role",
DBName: "benchmark-influxdb",
},
},
}

diags := gohcl.DecodeBody(body, nil, testConfig)
if diags.HasErrors() {
return fmt.Errorf("error decoding to struct: %v", diags)
}
i.config = testConfig.Config

if i.config.InfluxDBDBConfig.Username == "" {
return fmt.Errorf("no influxdb username provided but required")
}

if i.config.InfluxDBDBConfig.Password == "" {
return fmt.Errorf("no influxdb password provided but required")
}

return nil
}

func (i *InfluxDBSecret) Target(client *api.Client) vegeta.Target {
return vegeta.Target{
Method: InfluxDBSecretTestMethod,
URL: client.Address() + i.pathPrefix + "/creds/" + i.roleName,
Header: i.header,
}
}

func (i *InfluxDBSecret) Cleanup(client *api.Client) error {
i.logger.Trace(cleanupLogMessage(i.pathPrefix))
_, err := client.Logical().Delete(strings.Replace(i.pathPrefix, "/v1/", "/sys/mounts/", 1))
if err != nil {
return fmt.Errorf("error cleaning up mount: %v", err)
}
return nil
}

func (i *InfluxDBSecret) GetTargetInfo() TargetInfo {
return TargetInfo{
method: InfluxDBSecretTestMethod,
pathPrefix: i.pathPrefix,
}
}

func (i *InfluxDBSecret) Setup(client *api.Client, mountName string, topLevelConfig *TopLevelTargetConfig) (BenchmarkBuilder, error) {
var err error
secretPath := mountName
i.logger = targetLogger.Named(InfluxDBSecretTestType)

if topLevelConfig.RandomMounts {
secretPath, err = uuid.GenerateUUID()
if err != nil {
log.Fatalf("can't create UUID")
}
}

// Create Database Secret Mount
i.logger.Trace(mountLogMessage("secrets", "database", secretPath))
err = client.Sys().Mount(secretPath, &api.MountInput{
Type: "database",
})
if err != nil {
return nil, fmt.Errorf("error mounting db secrets engine: %v", err)
}

setupLogger := i.logger.Named(secretPath)

// Decode DB Config struct into mapstructure to pass with request
setupLogger.Trace(parsingConfigLogMessage("db"))
dbData, err := structToMap(i.config.InfluxDBDBConfig)
if err != nil {
return nil, fmt.Errorf("error parsing db config from struct: %v", err)
}

// Set up db
setupLogger.Trace(writingLogMessage("influxdb db config"), "name", i.config.InfluxDBDBConfig.Name)
dbPath := filepath.Join(secretPath, "config", i.config.InfluxDBDBConfig.Name)
_, err = client.Logical().Write(dbPath, dbData)
if err != nil {
return nil, fmt.Errorf("error writing influxdb db config: %v", err)
}

// Decode Role Config struct into mapstructure to pass with request
setupLogger.Trace(parsingConfigLogMessage("role"))
roleData, err := structToMap(i.config.InfluxDBRoleConfig)
if err != nil {
return nil, fmt.Errorf("error parsing role config from struct: %v", err)
}

// Create Role
setupLogger.Trace(writingLogMessage("influxdb role"), "name", i.config.InfluxDBRoleConfig.Name)
rolePath := filepath.Join(secretPath, "roles", i.config.InfluxDBRoleConfig.Name)
_, err = client.Logical().Write(rolePath, roleData)
if err != nil {
return nil, fmt.Errorf("error writing influxdb role %q: %v", i.config.InfluxDBRoleConfig.Name, err)
}

return &InfluxDBSecret{
pathPrefix: "/v1/" + secretPath,
header: generateHeader(client),
roleName: i.config.InfluxDBRoleConfig.Name,
logger: i.logger,
}, nil

}

func (i *InfluxDBSecret) Flags(fs *flag.FlagSet) {}
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ Below is a list of all currently available benchmark tests
- [Elasticsearch Secrets Engine Benchmark (`elasticsearch_secret`)](tests/secret-elasticsearch.md)
- [GCP Secrets Engine Benchmark (`gcp_secret`)](tests/secret-gcp.md)
- [GCP Secrets Engine Benchmark (`gcp_secret`)](tests/secret-impersonate-gcp.md)
- [InfluxDB Secrets Engine Benchmark (`influxdb_secret`)](tests/secret-influxdb.md)
- [KVV1 and KVV2 Secret Benchmark](tests/secret-kv.md)
- [LDAP Dynamic Secret Benchmark `ldap_dynamic_secret`](tests/secret-ldap-dynamic.md)
- [LDAP Static Secret Benchmark `ldap_static_secret`](tests/secret-ldap-static.md)
Expand Down
61 changes: 61 additions & 0 deletions docs/tests/secret-influxdb.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# InfluxDB Secrets Engine Benchmark (`influxdb_secret`)
This benchmark will test the dynamic generation of InfluxDB credentials.

⚠️ **Important**: This benchmark requires InfluxDB 1.x for compatibility with Vault's database secret engine.

~> We highly recommended that you use a Vault-specific user rather than the admin user
in your database when configuring the plugin. This user will be used to
create/update/delete users within the database so it will need to have the appropriate
permissions to do so.

## Benchmark Configuration Parameters

### DB Connection Configuration (`db_connection`)

- `name` `(string: "benchmark-influxdb")` - Name for this database connection. This is specified as part of the URL.
- `plugin_name` `(string: "influxdb-database-plugin")` - Specifies the name of the plugin to use for this connection.
- `plugin_version` `(string: "")` - Specifies the semantic version of the plugin to use for this connection.
- `verify_connection` `(bool: true)` - Specifies if the connection is verified during initial configuration. Defaults to true.
- `allowed_roles` `(list: ["benchmark-role"])` - List of the roles allowed to use this connection.
- `root_rotation_statements` `(list: [])` - Specifies the database statements to be executed to rotate the root user's credentials.
- `password_policy` `(string: "")` - The name of the [password policy](https://developer.hashicorp.com/vault/docs/concepts/password-policies) to use when generating passwords for this database. If not specified, this will use a default policy defined as: 20 characters with at least 1 uppercase, 1 lowercase, 1 number, and 1 dash character.
- `host` `(string: "localhost")` - Specifies the host to connect to.
- `port` `(int: 8086)` - Specifies the port to connect to.
- `username` `(string: <required>)` - Specifies the username for Vault to use. This can also be provided via the `VAULT_BENCHMARK_INFLUXDB_USERNAME` environment variable.
- `password` `(string: <required>)` - Specifies the password corresponding to the given username. This can also be provided via the `VAULT_BENCHMARK_INFLUXDB_PASSWORD` environment variable.
- `tls` `(bool: false)` - Whether to use TLS when connecting to InfluxDB.
- `insecure_tls` `(bool: false)` - Whether to skip verification of the server certificate when using TLS.
- `connect_timeout` `(string: "5s")` - The connection timeout to use.
- `username_template` `(string: "")` - [Template](https://developer.hashicorp.com/vault/docs/concepts/username-templating) describing how dynamic usernames are generated.

### Role Configuration (`role`)

- `name` `(string: "benchmark-role")` – Specifies the name of the role to create. This is specified as part of the URL.
- `db_name` `(string: "benchmark-influxdb")` - The name of the database connection to use for this role.
- `default_ttl` `(string: "1h")` – Specifies the TTL for the leases associated with this role. Accepts time suffixed strings (`1h`) or an integer number of seconds. Defaults to `sys/mounts`'s default TTL time; this value is allowed to be less than the mount max TTL (or, if not set, the system max TTL), but it is not allowed to be longer.
- `max_ttl` `(string: "24h")` – Specifies the maximum TTL for the leases associated with this role. Accepts time suffixed strings (`1h`) or an integer number of seconds. Defaults to `sys/mounts`'s default TTL time; this value is allowed to be less than the mount max TTL (or, if not set, the system max TTL), but it is not allowed to be longer.
- `creation_statements` `(string: <required>)` – Specifies the database statements executed to create and configure a user. Must be a semicolon-separated string, a base64-encoded semicolon-separated string, a serialized JSON string array, or a base64-encoded serialized JSON string array. The `{{name}}` and `{{password}}` values will be substituted.
- `revocation_statements` `(string: "")` – Specifies the database statements to be executed to revoke a user. Must be a semicolon-separated string, a base64-encoded semicolon-separated string, a serialized JSON string array, or a base64-encoded serialized JSON string array. The `{{name}}` value will be substituted.
- `rollback_statements` `(string: "")` – Specifies the database statements to be executed to rollback a create operation in the event of an error. Not every plugin type will support this functionality.
- `renew_statements` `(string: "")` – Specifies the database statements to be executed to renew a user. Not every plugin type will support this functionality.

## Example HCL

```hcl
test "influxdb_secret" "influxdb_test_1" {
weight = 100
config {
db_connection {
host = "localhost"
port = 8086
username = "admin"
password = "password"
tls = false
}
role {
creation_statements = "CREATE USER \"{{name}}\" WITH PASSWORD '{{password}}'; GRANT ALL ON \"mydb\" TO \"{{name}}\";"
revocation_statements = "DROP USER \"{{name}}\";"
}
}
}
```
Loading