diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ce429bb..0f19988d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,14 @@ ## [Unreleased](https://github.com/hashicorp/vault-benchmark/tree/HEAD) +### Features + +- Add benchmark for PKI secrets engine for generating keys [\#274](https://github.com/hashicorp/vault-benchmark/pull/274) + ### Improvements - Consolidate identity_group_read into identity as a workload option [\#268](https://github.com/hashicorp/vault-benchmark/pull/268) +- Update benchmarks for PKI issue and PKI sign to support ML-DSA [\#274](https://github.com/hashicorp/vault-benchmark/pull/274) ## [v0.4.1](https://github.com/hashicorp/vault-benchmark/tree/v0.4.1) (Dec 29, 2025) diff --git a/benchmarktests/target_secret_pki_generate_key.go b/benchmarktests/target_secret_pki_generate_key.go new file mode 100644 index 00000000..fa598e3c --- /dev/null +++ b/benchmarktests/target_secret_pki_generate_key.go @@ -0,0 +1,156 @@ +// Copyright IBM Corp. 2022, 2026 +// SPDX-License-Identifier: MPL-2.0 + +package benchmarktests + +import ( + "encoding/json" + "flag" + "fmt" + "net/http" + "path/filepath" + "time" + + "github.com/hashicorp/go-hclog" + "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/hcl/v2/gohcl" + "github.com/hashicorp/vault/api" + vegeta "github.com/tsenart/vegeta/v12/lib" +) + +const ( + PKIKeyGenerationTestType = "pki_generate_key" + PKIKeyGenerationTestMethod = "POST" +) + +func init() { + TestList[PKIKeyGenerationTestType] = func() BenchmarkBuilder { return &PKIGenerateKey{} } +} + +type PKIGenerateKey struct { + pathPrefix string + body []byte + header http.Header + config *PKIGenerateKeyConfig + logger hclog.Logger + mountPath string +} + +// PKIGenerateKeyConfig is the top-level HCL config block for this benchmark. +type PKIGenerateKeyConfig struct { + SetupDelay string `hcl:"setup_delay,optional"` + GenerateKeyConfig *PKIGenerateKeyBodyConfig `hcl:"generate_key,block"` +} + +// PKIGenerateKeyBodyConfig maps to POST /v1//keys/generate/ +type PKIGenerateKeyBodyConfig struct { + Type string `hcl:"type,optional"` + KeyType string `hcl:"key_type,optional"` + KeyBits int `hcl:"key_bits,optional"` + KeyName string `hcl:"key_name,optional"` + ManagedKeyName string `hcl:"managed_key_name,optional"` + ManagedKeyID string `hcl:"managed_key_id,optional"` + ParameterSet string `hcl:"parameter_set,optional"` +} + +func (p *PKIGenerateKey) ParseConfig(body hcl.Body) error { + testConfig := &struct { + Config *PKIGenerateKeyConfig `hcl:"config,block"` + }{ + Config: &PKIGenerateKeyConfig{ + SetupDelay: "1s", + GenerateKeyConfig: &PKIGenerateKeyBodyConfig{ + Type: "internal", + KeyType: "rsa", + KeyBits: 2048, + }, + }, + } + + diags := gohcl.DecodeBody(body, nil, testConfig) + if diags.HasErrors() { + return fmt.Errorf("error decoding to struct: %v", diags) + } + p.config = testConfig.Config + return nil +} + +func (p *PKIGenerateKey) Setup(client *api.Client, mountName string, topLevelConfig *TopLevelTargetConfig) (BenchmarkBuilder, error) { + p.logger = targetLogger.Named(PKIKeyGenerationTestType) + + mountPath, err := resolveMountPath(mountName, topLevelConfig.RandomMounts) + if err != nil { + return nil, err + } + p.logger = p.logger.Named(mountPath) + + p.logger.Trace(mountLogMessage("secrets", "pki", mountPath)) + err = client.Sys().Mount(mountPath, &api.MountInput{ + Type: "pki", + Config: api.MountConfigInput{ + MaxLeaseTTL: "87600h", + }, + }) + if err != nil { + return nil, fmt.Errorf("error mounting pki secrets engine: %v", err) + } + p.mountPath = mountPath + + // Brief delay to let the PKI mount finish initialising before the first + // write — mirrors the pattern used by pki_issue and pki_sign. + delay, err := time.ParseDuration(p.config.SetupDelay) + if err != nil { + return nil, fmt.Errorf("error parsing setup_delay: %v", err) + } + time.Sleep(delay) + + p.logger.Trace(parsingConfigLogMessage("generate key")) + bodyData, err := structToMap(p.config.GenerateKeyConfig) + if err != nil { + return nil, fmt.Errorf("error parsing generate_key config: %v", err) + } + // "type" is the URL path segment, not a body field. + delete(bodyData, "type") + + bodyBytes, err := json.Marshal(bodyData) + if err != nil { + return nil, fmt.Errorf("error marshaling generate_key config: %v", err) + } + + keyGenType := p.config.GenerateKeyConfig.Type + if keyGenType == "" { + keyGenType = "internal" + } + + return &PKIGenerateKey{ + // POST /v1//keys/generate/ where is "internal" or "exported" + pathPrefix: "/v1/" + filepath.Join(mountPath, "keys", "generate", keyGenType), + header: generateHeader(client), + body: bodyBytes, + config: p.config, + logger: p.logger, + mountPath: p.mountPath, + }, nil +} + +func (p *PKIGenerateKey) Target(client *api.Client) vegeta.Target { + return vegeta.Target{ + Method: PKIKeyGenerationTestMethod, + URL: client.Address() + p.pathPrefix, + Body: p.body, + Header: p.header, + } +} + +func (p *PKIGenerateKey) Cleanup(client *api.Client) error { + return cleanupMount(p.logger, client, "/v1/"+p.mountPath) +} + +func (p *PKIGenerateKey) GetTargetInfo() TargetInfo { + return TargetInfo{ + method: PKIKeyGenerationTestMethod, + pathPrefix: p.pathPrefix, + } +} + +func (p *PKIGenerateKey) Flags(fs *flag.FlagSet) {} diff --git a/benchmarktests/target_secret_pki_issue.go b/benchmarktests/target_secret_pki_issue.go index dde54f64..69207560 100644 --- a/benchmarktests/target_secret_pki_issue.go +++ b/benchmarktests/target_secret_pki_issue.go @@ -87,6 +87,7 @@ type PKIIssueRootConfig struct { PrivateKeyFormat string `hcl:"private_key_format,optional"` KeyType string `hcl:"key_type,optional"` KeyBits int `hcl:"key_bits,optional"` + ParameterSet string `hcl:"parameter_set,optional"` MaxPathLength int `hcl:"max_path_length,optional"` ExcludeCNFromSANS bool `hcl:"exclude_cn_from_sans,optional"` PermittedDNSDomains string `hcl:"permitted_dns_domains,optional"` @@ -120,6 +121,7 @@ type PKIIssueIntCSRConfig struct { PrivateKeyFormat string `hcl:"private_key_format,optional"` KeyType string `hcl:"key_type,optional"` KeyBits int `hcl:"key_bits,optional"` + ParameterSet string `hcl:"parameter_set,optional"` KeyName string `hcl:"key_name,optional"` KeyRef string `hcl:"key_ref,optional"` SignatureBits int `hcl:"signature_bits,optional"` @@ -197,6 +199,7 @@ type PKIIssueRoleConfig struct { EmailProtectionFlag bool `hcl:"email_protection_flag,optional"` KeyType string `hcl:"key_type,optional"` KeyBits int `hcl:"key_bits,optional"` + ParameterSet string `hcl:"parameter_set,optional"` SignatureBits int `hcl:"signature_bits,optional"` UsePSS bool `hcl:"use_pss,optional"` KeyUsage []string `hcl:"key_usage,optional"` diff --git a/benchmarktests/target_secret_pki_sign.go b/benchmarktests/target_secret_pki_sign.go index 3ca5aba5..071e7d43 100644 --- a/benchmarktests/target_secret_pki_sign.go +++ b/benchmarktests/target_secret_pki_sign.go @@ -93,6 +93,7 @@ type pkiSignRootConfig struct { PrivateKeyFormat string `hcl:"private_key_format,optional"` KeyType string `hcl:"key_type,optional"` KeyBits int `hcl:"key_bits,optional"` + ParameterSet string `hcl:"parameter_set,optional"` MaxPathLength int `hcl:"max_path_length,optional"` ExcludeCNFromSANS bool `hcl:"exclude_cn_from_sans,optional"` PermittedDNSDomains string `hcl:"permitted_dns_domains,optional"` @@ -126,6 +127,7 @@ type pkiSignIntCSRConfig struct { PrivateKeyFormat string `hcl:"private_key_format,optional"` KeyType string `hcl:"key_type,optional"` KeyBits int `hcl:"key_bits,optional"` + ParameterSet string `hcl:"parameter_set,optional"` KeyName string `hcl:"key_name,optional"` KeyRef string `hcl:"key_ref,optional"` SignatureBits int `hcl:"signature_bits,optional"` @@ -203,6 +205,7 @@ type pkiSignRoleConfig struct { EmailProtectionFlag bool `hcl:"email_protection_flag,optional"` KeyType string `hcl:"key_type,optional"` KeyBits int `hcl:"key_bits,optional"` + ParameterSet string `hcl:"parameter_set,optional"` SignatureBits int `hcl:"signature_bits,optional"` UsePSS bool `hcl:"use_pss,optional"` KeyUsage []string `hcl:"key_usage,optional"` diff --git a/docs/index.md b/docs/index.md index e124e17a..e361def1 100644 --- a/docs/index.md +++ b/docs/index.md @@ -88,6 +88,7 @@ Enable Vault debug logging](tests/auth-radius.md) - [MySQL Secret Benchmark `mysql_secret`](tests/secret-mysql.md) - [Nomad Secrets Engine Benchmark](tests/secret-nomad.md) - [Oracle Database Secrets Engine Benchmark (`oracle_secret`)](tests/secret-oracle.md) +- [PKI Generate Key Benchmark (`pki_generate_key`)](tests/secret-pki-generate-key.md) - [PKI Secret Configuration Options](tests/secret-pki-issue.md) - [PKI Sign Secret Configuration Options](tests/secret-pki-sign.md) - [Postgresql Secrets Engine Benchmark `postgresql_secret`](tests/secret-postgresql.md) diff --git a/docs/tests/secret-pki-generate-key.md b/docs/tests/secret-pki-generate-key.md new file mode 100644 index 00000000..01248c56 --- /dev/null +++ b/docs/tests/secret-pki-generate-key.md @@ -0,0 +1,70 @@ +# PKI Generate Key Benchmark (`pki_generate_key`) + +This benchmark tests the performance of PKI key generation operations. + +## Test Parameters + +### Generate Key Config `generate_key` + +- `type` `(string: "internal")` - Specifies the type of key to generate. If + `internal`, the private key is generated inside Vault and cannot be retrieved + later. If `exported`, the private key will be returned in the response. + +- `key_type` `(string: "rsa")` - Specifies the desired key type; must be `rsa`, + `ec`, `ed25519`, or `ml-dsa`. + +- `key_bits` `(int: 0)` - Specifies the number of bits to use for the generated + key. Allowed values are 0 (universal default); with `key_type=rsa`, allowed + values are: 2048 (default), 3072, or 4096; with `key_type=ec`, allowed values + are: 224, 256 (default), 384, or 521; ignored with `key_type=ed25519` and + `key_type=ml-dsa`. + +- `parameter_set` `(string: "")` - An ML-DSA key parameter set. Required when + `key_type=ml-dsa`. Allowed values are `"44"`, `"65"`, and `"87"`. + +- `key_name` `(string: "")` - Optionally specifies a name for the generated key. + The global ref `default` may not be used as a name. + +#### Managed Keys Parameters + +See [Managed Keys](https://developer.hashicorp.com/vault/api-docs/secret/pki#managed-keys) +for additional details. One of the following parameters must be set when +`type=kms`. + +- `managed_key_name` `(string: "")` - The managed key's configured name. + +- `managed_key_id` `(string: "")` - The managed key's UUID. + +## Example HCL Configuration + +```hcl +test "pki_generate_key" "pki_generate_key_test1" { + weight = 100 + config { + setup_delay = "2s" + + generate_key { + type = "internal" + key_type = "rsa" + key_bits = 2048 + } + } +} +``` + +### ML-DSA Example + +```hcl +test "pki_generate_key" "pki_generate_key_mldsa" { + weight = 100 + config { + setup_delay = "2s" + + generate_key { + type = "internal" + key_type = "ml-dsa" + parameter_set = "44" + } + } +} +``` diff --git a/docs/tests/secret-pki-issue.md b/docs/tests/secret-pki-issue.md index fc8c9af1..61e96bda 100644 --- a/docs/tests/secret-pki-issue.md +++ b/docs/tests/secret-pki-issue.md @@ -68,7 +68,7 @@ This benchmark tests the performance of PKI issue operations. field if `format=pem_bundle` parameter is specified. - `key_type` `(string: "rsa")` - Specifies the desired key type; must be `rsa`, `ed25519` - or `ec`. + `ec`, or `ml-dsa`. ~> **Note**: In FIPS 140-2 mode, the following algorithms are not certified and thus should not be used: `ed25519`. @@ -79,6 +79,9 @@ This benchmark tests the performance of PKI issue operations. 4096; with `key_type=ec`, allowed values are: 224, 256 (default), 384, or 521; ignored with `key_type=ed25519`. +- `parameter_set` `(string: "")` - An ML-DSA key parameter set. Required when + `key_type=ml-dsa`. Allowed values are `"44"`, `"65"`, and `"87"`. + - `max_path_length` `(int: -1)` - Specifies the maximum path length to encode in the generated certificate. `-1` means no limit. Unless the signing certificate has a maximum path length set, in which case the path length is set to one @@ -195,7 +198,7 @@ See [Managed Keys](https://developer.hashicorp.com/vault/api-docs/secret/pki#man field if `format=pem_bundle` parameter is specified. - `key_type` `(string: "rsa")` - Specifies the desired key type; must be `rsa`, `ed25519` - or `ec`. Not suitable for `type=existing` requests. + `ec` or `ml-dsa`. Not suitable for `type=existing` requests. ~> **Note**: In FIPS 140-2 mode, the following algorithms are not certified and thus should not be used: `ed25519`. @@ -210,6 +213,9 @@ See [Managed Keys](https://developer.hashicorp.com/vault/api-docs/secret/pki#man 384, or 521; ignored with `key_type=ed25519`. Not suitable for `type=existing` requests. +- `parameter_set` `(string: "")` - An ML-DSA key parameter set. Required when + `key_type=ml-dsa`. Allowed values are `"44"`, `"65"`, and `"87"`. + - `key_name` `(string: "")` - When a new key is created with this request, optionally specifies the name for this. The global ref `default` may not be used as a name. @@ -566,7 +572,7 @@ See [Managed Keys](https://developer.hashicorp.com/vault/api-docs/secret/pki#man - `key_type` `(string: "rsa")` - Specifies the type of key to generate for generated private keys and the type of key expected for submitted CSRs. - Currently, `rsa`, `ec`, and `ed25519` are supported, or when signing + Currently, `rsa`, `ec`, `ed25519`, or `ml-dsa` are supported, or when signing existing CSRs, `any` can be specified to allow keys of either type and with any bit size (subject to >=2048 bits for RSA keys or >= 224 for EC keys). When `any` is used, this role cannot generate certificates and can only @@ -582,6 +588,9 @@ See [Managed Keys](https://developer.hashicorp.com/vault/api-docs/secret/pki#man 384, or 521; ignored with `key_type=ed25519` or in signing operations when `key_type=any`. +- `parameter_set` `(string: "")` - An ML-DSA key parameter set. Required when + `key_type=ml-dsa`. Allowed values are `"44"`, `"65"`, and `"87"`. + - `signature_bits` `(int: 0)` - Specifies the number of bits to use in the signature algorithm; accepts 256 for SHA-2-256, 384 for SHA-2-384, and 512 for SHA-2-512. Defaults to 0 to automatically detect based diff --git a/docs/tests/secret-pki-sign.md b/docs/tests/secret-pki-sign.md index 73a369af..5102b6c0 100644 --- a/docs/tests/secret-pki-sign.md +++ b/docs/tests/secret-pki-sign.md @@ -68,7 +68,7 @@ This benchmark tests the performance of PKI signing operations. field if `format=pem_bundle` parameter is specified. - `key_type` `(string: "rsa")` - Specifies the desired key type; must be `rsa`, `ed25519` - or `ec`. + `ec`, or `ml-dsa`. ~> **Note**: In FIPS 140-2 mode, the following algorithms are not certified and thus should not be used: `ed25519`. @@ -79,6 +79,9 @@ This benchmark tests the performance of PKI signing operations. 4096; with `key_type=ec`, allowed values are: 224, 256 (default), 384, or 521; ignored with `key_type=ed25519`. +- `parameter_set` `(string: "")` - An ML-DSA key parameter set. Required when + `key_type=ml-dsa`. Allowed values are `"44"`, `"65"`, and `"87"`. + - `max_path_length` `(int: -1)` - Specifies the maximum path length to encode in the generated certificate. `-1` means no limit. Unless the signing certificate has a maximum path length set, in which case the path length is set to one @@ -195,7 +198,7 @@ See [Managed Keys](https://developer.hashicorp.com/vault/api-docs/secret/pki#man field if `format=pem_bundle` parameter is specified. - `key_type` `(string: "rsa")` - Specifies the desired key type; must be `rsa`, `ed25519` - or `ec`. Not suitable for `type=existing` requests. + , `ec`, or `ml-dsa`. Not suitable for `type=existing` requests. ~> **Note**: In FIPS 140-2 mode, the following algorithms are not certified and thus should not be used: `ed25519`. @@ -214,6 +217,9 @@ See [Managed Keys](https://developer.hashicorp.com/vault/api-docs/secret/pki#man optionally specifies the name for this. The global ref `default` may not be used as a name. +- `parameter_set` `(string: "")` - An ML-DSA key parameter set. Required when + `key_type=ml-dsa`. Allowed values are `"44"`, `"65"`, and `"87"`. + - `key_ref` `(string: "default")` - Specifies the key (either `default`, by name, or by identifier) to use for generating this request. Only suitable for `type=existing` requests. @@ -566,7 +572,7 @@ See [Managed Keys](https://developer.hashicorp.com/vault/api-docs/secret/pki#man - `key_type` `(string: "rsa")` - Specifies the type of key to generate for generated private keys and the type of key expected for submitted CSRs. - Currently, `rsa`, `ec`, and `ed25519` are supported, or when signing + Currently, `rsa`, `ec`, `ed25519`, `ml-dsa` are supported, or when signing existing CSRs, `any` can be specified to allow keys of either type and with any bit size (subject to >=2048 bits for RSA keys or >= 224 for EC keys). When `any` is used, this role cannot generate certificates and can only @@ -588,6 +594,9 @@ See [Managed Keys](https://developer.hashicorp.com/vault/api-docs/secret/pki#man on issuer's key length (SHA-2-256 for RSA keys, and matching the curve size for NIST P-Curves). +- `parameter_set` `(string: "")` - An ML-DSA key parameter set. Required when + `key_type=ml-dsa`. Allowed values are `"44"`, `"65"`, and `"87"`. + ~> **Note**: ECDSA and Ed25519 issuers do not follow configuration of the `signature_bits` value; only RSA issuers will change signature types based on this parameter.