Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Secret Engine for LDAP #119

Merged
merged 10 commits into from
Jun 6, 2023
1 change: 1 addition & 0 deletions benchmarktests/target_auth_ldap.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ type LDAPAuthConfig struct {
TokenNumUses int `hcl:"token_num_uses,optional"`
TokenPeriod string `hcl:"token_period,optional"`
TokenType string `hcl:"token_type,optional"`
MaxPageSize string `hcl:"max_page_size,optional"`
ltcarbonell marked this conversation as resolved.
Show resolved Hide resolved
}

type LDAPTestUserConfig struct {
Expand Down
181 changes: 181 additions & 0 deletions benchmarktests/target_secret_ldap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
// 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 (
LDAPSecretTestType = "ldap_secret"
LDAPSecretTestMethod = "GET"
LDAPSecretBindPassEnvVar = VaultBenchmarkEnvVarPrefix + "LDAP_BIND_PASS"
)

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

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

// Main Config Struct
type LDAPSecretTestConfig struct {
LDAPConfig *LDAPConfig `hcl:"secret,block"`
LDAPRoleConfig *LDAPRoleConfig `hcl:"role,block"`
}

type LDAPConfig struct {
BindDN string `hcl:"binddn"`
BindPass string `hcl:"bindpass,optional"`
elliesterner marked this conversation as resolved.
Show resolved Hide resolved
URL string `hcl:"url,optional"`
PasswordPolicy string `hcl:"password_policy,optional"`
Schema string `hcl:"schema,optional"`
UserDN string `hcl:"userdn,optional"`
UserAttr string `hcl:"userattr,optional"`
UPNDomain string `hcl:"upndomain,optional"`
ConnectionTimeout int `hcl:"connection_timeout,optional"`
RequestTimeout int `hcl:"request_timeout,optional"`
StartTLS bool `hcl:"starttls,optional"`
InsecureTLS bool `hcl:"insecure_tls,optional"`
Certificate string `hcl:"certificate,optional"`
ClientTLSCert string `hcl:"client_tls_cert,optional"`
ClientTLSKey string `hcl:"client_tls_key,optional"`
}

type LDAPRoleConfig struct {
RoleName string `hcl:"role_name,optional"`
CreationLDIF string `hcl:"creation_ldif"`
DeletionLDIF string `hcl:"deletion_ldif"`
RollbackLDIF string `hcl:"rollback_ldif,optional"`
UsernameTemplate string `hcl:"username_template,optional"`
DefaultTTL int `hcl:"default_ttl,optional"`
MaxTTL int `hcl:"max_ttl,optional"`
}

func (r *LDAPSecretTest) ParseConfig(body hcl.Body) error {
testConfig := &struct {
Config *LDAPSecretTestConfig `hcl:"config,block"`
}{
Config: &LDAPSecretTestConfig{
LDAPConfig: &LDAPConfig{
BindPass: os.Getenv(LDAPAuthBindPassEnvVar),
},
LDAPRoleConfig: &LDAPRoleConfig{
RoleName: "benchmark-role",
},
},
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing error message for environment variable and config not set, error out.

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

r.config = testConfig.Config
return nil
}

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

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

func (r *LDAPSecretTest) GetTargetInfo() TargetInfo {
return TargetInfo{
method: LDAPSecretTestMethod,
pathPrefix: r.pathPrefix,
}
}

func (r *LDAPSecretTest) Setup(client *api.Client, randomMountName bool, mountName string) (BenchmarkBuilder, error) {
var err error
secretPath := mountName
r.logger = targetLogger.Named(LDAPSecretTestType)

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

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

setupLogger := r.logger.Named(secretPath)

// Decode LDAP Connection Config
setupLogger.Trace(parsingConfigLogMessage("ldap secret"))
connectionConfigData, err := structToMap(r.config.LDAPConfig)
if err != nil {
return nil, fmt.Errorf("error parsing ldap secret config from struct: %v", err)
}

// Write connection config
setupLogger.Trace(writingLogMessage("ldap secret config"))
_, err = client.Logical().Write(secretPath+"/config", connectionConfigData)
if err != nil {
return nil, fmt.Errorf("error writing ldap secret config: %v", err)
}

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

// Create Role
setupLogger.Trace(writingLogMessage("ldap secret role"), "name", r.config.LDAPRoleConfig.RoleName)
_, err = client.Logical().Write(secretPath+"/role/"+r.config.LDAPRoleConfig.RoleName, roleConfigData)
if err != nil {
return nil, fmt.Errorf("error writing ldap secret role: %v", err)
}

return &LDAPSecretTest{
pathPrefix: "/v1/" + secretPath,
header: generateHeader(client),
roleName: r.config.LDAPRoleConfig.RoleName,
logger: r.logger,
}, nil
}

func (m *LDAPSecretTest) Flags(fs *flag.FlagSet) {}
5 changes: 5 additions & 0 deletions docs/tests/auth-ldap.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,11 @@ This benchmark will test LDAP Authentication to Vault. The primary required fiel
additional possibilities: `default-service` and `default-batch` which specify
the type to return unless the client requests a different type at generation
time.
- `max_page_size` `(integer: 0)` - If set to a value greater than 0, the LDAP
backend will use the LDAP server's paged search control to request pages of
up to the given size. This can be used to avoid hitting the LDAP server's
maximum result size limit. Otherwise, the LDAP backend will not use the
paged search control.

### Test User Config `role`

Expand Down
64 changes: 64 additions & 0 deletions docs/tests/secret-ldap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# LDAP Auth Benchmark `ldap_secret`

This benchmark will test the dynamic generation of LDAP credentials.

## Test Parameters

### Secret Configuration `secret`

- `binddn` `(string: <required>)` - Distinguished name (DN) of object to bind for managing user entries. For example, `cn=vault,ou=Users,dc=hashicorp,dc=com`.
- `bindpass` `(string: <required>)` - Password to use along with `binddn` for managing user entries. This can also be provided via the `VAULT_BENCHMARK_LDAP_BIND_PASS` environment variable.
- `url` `(string: "ldap://127.0.0.1")` - The LDAP server to connect to. Examples: `ldaps://ldap.myorg.com`, `ldaps://ldap.myorg.com:636`. This can also be a comma-delineated list of URLs, e.g. `ldaps://ldap.myorg.com, ldaps://ldap.myorg.com:636`, in which case the servers will be tried in-order if there are errors during the connection process.`.
- `password_policy` `(string: <optional>)` - The name of the [password policy](https://developer.hashicorp.com/vault/docs/concepts/password-policies) to use to generate passwords. Note that this accepts the name of the policy, not the policy itself.
- `schema` `(string: "openldap")` - The LDAP schema to use when storing entry passwords. Valid schemas include `openldap`, `ad`, and `racf`.
- `userdn` `(string: <optional>)` - The base DN under which to perform user search in [library management](https://developer.hashicorp.com/vault/api-docs/secret/ldap#library-management) and [static roles](https://developer.hashicorp.com/vault/api-docs/secret/ldap#static-roles). For example, `ou=Users,dc=hashicorp,dc=com`.
- `userattr` `(string: <optional>)` – The attribute field name used to perform user search in [library management](https://developer.hashicorp.com/vault/api-docs/secret/ldap#library-management) and [static roles](https://developer.hashicorp.com/vault/api-docs/secret/ldap#static-roles). Defaults to `cn` for the `openldap` schema, `userPrincipalName` for the `ad` schema, and `racfid` for the `racf` schema.
- `upndomain` (string: `optional`) - The domain (userPrincipalDomain) used to construct a UPN string for authentication. The constructed UPN will appear as `[binddn]@[upndomain]`. For example, if `upndomain=example.com` and `binddn=admin`, the UPN string `admin@example.com` will be used to log in to Active Directory.
- `connection_timeout` `(integer: 30)` - Timeout, in seconds, when attempting to connect to the LDAP server before trying the next URL in the configuration.
- `request_timeout` `(integer: 90)` - Timeout, in seconds, for the connection when making requests against the server before returning back an error.
- `starttls` `(bool: <optional>)` - If true, issues a `StartTLS` command after establishing an unencrypted connection. - `insecure_tls` `(bool: <optional>)` - If true, skips LDAP server SSL certificate verification - insecure, use with caution!
- `certificate` `(string: <optional>)` - CA certificate to use when verifying LDAP server certificate, must be x509 PEM encoded.
- `client_tls_cert` `(string: <optional>)` - Client certificate to provide to the LDAP server, must be x509 PEM encoded.
- `client_tls_key` `(string: <optional>)` - Client key to provide to the LDAP server, must be x509 PEM encoded.

### Role Configuration `role`

- `role_name` `(string: "benchmark-role")` - The name of the dynamic role.
- `creation_ldif` `(string: <required>)` - A templatized LDIF string used to create a user account. This may contain multiple LDIF entries. The `creation_ldif` can also be used to add the user account to an **_existing_** group. All LDIF entries are performed in order. If Vault encounters an error while executing the `creation_ldif` it will stop at the first error and not execute any remaining LDIF entries. If an error occurs and `rollback_ldif` is specified, the LDIF entries in `rollback_ldif` will be executed. See `rollback_ldif` for more details. This field may optionally be provided as a base64 encoded string.
- `deletion_ldif` `(string: <required>)` - A templatized LDIF string used to delete the user account once its TTL has expired. This may contain multiple LDIF entries. All LDIF entries are performed in order. If Vault encounters an error while executing an entry in the `deletion_ldif` it will attempt to continue executing any remaining entries. This field may optionally be provided as a base64 encoded string.
- `rollback_ldif` `(string: <not required but recommended>)` - A templatized LDIF string used to attempt to rollback any changes in the event that execution of the `creation_ldif` results in an error. This may contain multiple LDIF entries. All LDIF entries are performed in order. If Vault encounters an error while executing an entry in the `rollback_ldif` it will attempt to continue executing any remaining entries. This field may optionally be provided as a base64 encoded string.
- `username_template` `(string: <optional>)` - A template used to generate a dynamic username. This will be used to fill in the `.Username` field within the `creation_ldif` string.
- `default_ttl` `(int: <optional>)` - Specifies the TTL for the leases associated with this role. Defaults to system/engine default TTL time.
- `max_ttl` `(int: <optional>)` - Specifies the maximum TTL for the leases associated with this role. Defaults to system/mount 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.

## Example HCL

```hcl
test "ldap_secret" "ldap_secret_test1" {
weight = 100
config {
secret {
url = "ldap://localhost"
binddn = "cn=admin,dc=hashicorp,dc=com"
bindpass = "admin"
}
role {
creation_ldif = "ZG46IGNuPXt7LlVzZXJuYW1lfX0sb3U9dXNlcnMsZGM9aGFzaGljb3JwLGRjPWNvbQpvYmplY3RDbGFzczogcGVyc29uCm9iamVjdENsYXNzOiB0b3AKY246IGxlYXJuCnNuOiB7ey5QYXNzd29yZCB8IHV0ZjE2bGUgfCBiYXNlNjR9fQptZW1iZXJPZjogY249ZGV2LG91PWdyb3VwcyxkYz1oYXNoaWNvcnAsZGM9Y29tCnVzZXJQYXNzd29yZDoge3suUGFzc3dvcmR9fQo="
deletion_ldif = "ZG46IGNuPXt7LlVzZXJuYW1lfX0sb3U9dXNlcnMsZGM9bGVhcm4sZGM9ZXhhbXBsZQpjaGFuZ2V0eXBlOiBkZWxldGUK"
rollback_ldif = "ZG46IGNuPXt7LlVzZXJuYW1lfX0sb3U9dXNlcnMsZGM9bGVhcm4sZGM9ZXhhbXBsZQpjaGFuZ2V0eXBlOiBkZWxldGUK"
}
}
}
```

## Example Usage

```bash
$ vault-benchmark run -config=config.hcl
2023-04-26T18:11:50.901-0500 [INFO] vault-benchmark: setting up targets
2023-04-26T18:11:50.918-0500 [INFO] vault-benchmark: starting benchmarks: duration=2s
2023-04-26T18:11:52.920-0500 [INFO] vault-benchmark: benchmark complete
Target: http://localhost:8200
op count rate throughput mean 95th% 99th% successRatio
ldap_secret_test1 13345 6671.750122 0.000000 1.495695ms 2.128745ms 3.542841ms 100.00%
```
Loading