Skip to content

Commit

Permalink
Add Nomad Secret Engine Tests (#125)
Browse files Browse the repository at this point in the history
* Add secret engine for Nomad

* add docs

* cleanup docs
  • Loading branch information
ltcarbonell committed Jun 2, 2023
1 parent 35b87fa commit c916879
Show file tree
Hide file tree
Showing 2 changed files with 227 additions and 0 deletions.
173 changes: 173 additions & 0 deletions benchmarktests/target_secret_nomad.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package benchmarktests

import (
"flag"
"fmt"
"log"
"net/http"
"os"
"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 (
NomadSecretTestType = "nomad_secret"
NomadSecretTestMethod = "GET"
NomadTokenEnvVar = VaultBenchmarkEnvVarPrefix + "NOMAD_TOKEN"
)

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

type NomadTest struct {
pathPrefix string
header http.Header
roleName string
config *NomadSecretTestConfig
logger hclog.Logger
}

type NomadSecretTestConfig struct {
NomadConfig *NomadConfig `hcl:"nomad,block"`
NomadRoleConfig *NomadRoleConfig `hcl:"role,block"`
}

type NomadConfig struct {
Address string `hcl:"address"`
Token string `hcl:"token,optional"`
MaxTokenNameLength int `hcl:"max_token_name_length,optional"`
CaCert string `hcl:"ca_cert,optional"`
ClientCert string `hcl:"client_cert,optional"`
ClientKey string `hcl:"client_key,optional"`
}

type NomadRoleConfig struct {
Name string `hcl:"name,optional"`
Policies []string `hcl:"policies,optional"`
Global bool `hcl:"global,optional"`
Type string `hcl:"type,optional"`
}

func (c *NomadTest) ParseConfig(body hcl.Body) error {
testConfig := &struct {
Config *NomadSecretTestConfig `hcl:"config,block"`
}{
Config: &NomadSecretTestConfig{
NomadConfig: &NomadConfig{
Token: os.Getenv(NomadTokenEnvVar),
},
NomadRoleConfig: &NomadRoleConfig{
Name: "benchmark-role",
},
},
}

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

// Ensure that the token has been set by either the environment variable or the config
if c.config.NomadConfig.Token == "" {
return fmt.Errorf("nomad token must be set")
}
return nil
}

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

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

func (c *NomadTest) GetTargetInfo() TargetInfo {
return TargetInfo{
method: NomadSecretTestMethod,
pathPrefix: c.pathPrefix,
}
}

func (c *NomadTest) Setup(client *api.Client, randomMountName bool, mountName string) (BenchmarkBuilder, error) {
var err error
secretPath := mountName
config := c.config
c.logger = targetLogger.Named(NomadSecretTestType)

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

c.logger.Trace(mountLogMessage("secrets", "nomad", secretPath))
err = client.Sys().Mount(secretPath, &api.MountInput{
Type: "nomad",
})
if err != nil {
return nil, fmt.Errorf("error mounting nomad: %v", err)
}

setupLogger := c.logger.Named(secretPath)

// Decode Nomad Config
setupLogger.Trace(parsingConfigLogMessage("nomad"))
nomadConfigData, err := structToMap(config.NomadConfig)
if err != nil {
return nil, fmt.Errorf("error parsing nomad config from struct: %v", err)
}

// Write Nomad config
setupLogger.Trace(writingLogMessage("nomad config"))
_, err = client.Logical().Write(secretPath+"/config/access", nomadConfigData)
if err != nil {
return nil, fmt.Errorf("error writing nomad config: %v", err)
}

// Decode Role Config
setupLogger.Trace(parsingConfigLogMessage("role"))
nomadRoleConfigData, err := structToMap(config.NomadRoleConfig)
if err != nil {
return nil, fmt.Errorf("error parsing role config from struct: %v", err)
}

// Create Role
setupLogger.Trace(writingLogMessage("nomad role"), "name", config.NomadRoleConfig.Name)
_, err = client.Logical().Write(secretPath+"/role/"+config.NomadRoleConfig.Name, nomadRoleConfigData)
if err != nil {
return nil, fmt.Errorf("error writing nomad role: %v", err)
}

return &NomadTest{
pathPrefix: "/v1/" + secretPath,
header: generateHeader(client),
roleName: config.NomadRoleConfig.Name,
logger: c.logger,
}, nil
}

func (c *NomadTest) Flags(fs *flag.FlagSet) {}
54 changes: 54 additions & 0 deletions docs/tests/secret-nomad.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Nomad Secrets Engine Benchmark

This benchmark will test the dynamic generation of Nomad credentials.

## Test Parameters

### Nomad Database Configuration `nomad`

#### NOTE: Ensure that the Nomad system has a limit high enough to support the number of roles you are creating. More information can be found in the [Nomad Documentation](https://developer.hashicorp.com/nomad/docs/configuration#limits)

- `address` `(string: "")` – Specifies the address of the Nomad instance, provided as `"protocol://host:port"` like `"http://127.0.0.1:4646"`.
- `token` `(string: "")` – Specifies the Nomad Management token to use. This value can also be provided on individual calls with the NOMAD_TOKEN environment variable. This can also be provided via the `VAULT_BENCHMARK_NOMAD_TOKEN` environment variable.
- `max_token_name_length` `(int: <optional>)` – Specifies the maximum length to use for the name of the Nomad token generated with [Generate Credential](https://developer.hashicorp.com/vault/api-docs/secret/nomad#generate-credential). If omitted, `0` is used and ignored, defaulting to the max value allowed by the Nomad version. For Nomad versions 0.8.3 and earlier, the default is `64`. For Nomad version 0.8.4 and later, the default is `256`.
- `ca_cert` `(string: "")` - CA certificate to use when verifying Nomad server certificate, must be x509 PEM encoded.
- `client_cert` `(string: "")` - Client certificate used for Nomad's TLS communication, must be x509 PEM encoded and if this is set you need to also set client_key.
- `client_key` `(string: "")` - Client key used for Nomad's TLS communication, must be x509 PEM encoded and if this is set you need to also set client_cert.

### Role Config `role`

- `name` `(string: "benchmark-role")` – Specifies the name of an existing role against which to create this Nomad tokens. This is part of the request URL.
- `policies` `(string: "")` – Comma separated list of Nomad policies the token is going to be created against. These need to be created beforehand in Nomad.
- `global` `(bool: "false")` – Specifies if the token should be global, as defined in the [Nomad Documentation](https://developer.hashicorp.com/nomad/tutorials/access-control#acl-tokens).
- `type` `(string: "client")` - Specifies the type of token to create when using this role. Valid values are `"client"` or `"management"`.

## Example Configuration

```hcl
test "nomad_secret" "nomad_test_1" {
weight = 100
config {
nomad {
address = "http://127.0.0.1:4646"
token = "NOMAD_TOKEN"
}
role {
global = true
type = "management"
}
}
}
```

### Example Usage

```bash
$ vault-benchmark run -config=config.hcl
2023-06-01T09:41:27.096-0500 [INFO] vault-benchmark: setting up targets
2023-06-01T09:41:27.102-0500 [INFO] vault-benchmark: starting benchmarks: duration=5s
2023-06-01T09:41:32.311-0500 [INFO] vault-benchmark: benchmark complete
Target: http://127.0.0.1:8200
op count rate throughput mean 95th% 99th% successRatio
nomad_test_1 177 35.057995 33.990891 290.850018ms 375.292712ms 451.573602ms 100.00%
```

0 comments on commit c916879

Please sign in to comment.