Skip to content

Vault 37086 plugin quality configure plugin and test dynamic role api crud with enos #181

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

Draft
wants to merge 9 commits into
base: VAULT-37081-Plugin-Quality-Add-bootstrap-environment-setup-to-plugin-repo
Choose a base branch
from
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
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ plugin-configure:
.PHONY: configure
configure: plugin-build plugin-register plugin-enable plugin-configure

.PHONY: dynamic-role-test
dynamic-role-test:
cd enos/modules/dynamic_role_crud_api && ./scripts/dynamic-role.sh

.PHONY: teardown-env
teardown-env:
cd bootstrap && ./teardown-env.sh
8 changes: 8 additions & 0 deletions enos/enos-descriptions.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ globals {
egress traffic via the internet gateway.
EOF

dynamic_role_crud_api = <<-EOF
Tests the lifecycle of a dynamic role via the Vault CRUD API.
EOF

ec2_info = <<-EOF
Query various endpoints in AWS Ec2 to gather metadata we'll use later in our run when creating
infrastructure for the Vault cluster. This metadata includes:
Expand Down Expand Up @@ -118,6 +122,10 @@ globals {
start the Vault agent.
EOF

static_role_crud_api = <<-EOF
Tests the lifecycle of a static role via the Vault CRUD API.
EOF

stop_vault = <<-EOF
Stop the Vault cluster by stopping the vault service via systemctl.
EOF
Expand Down
8 changes: 8 additions & 0 deletions enos/enos-modules.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ module "choose_follower_host" {
source = "./modules/choose_follower_host"
}

module "dynamic_role_crud_api" {
source = "./modules/dynamic_role_crud_api"
}

module "ec2_info" {
source = "./modules/ec2_info"
}
Expand Down Expand Up @@ -152,6 +156,10 @@ module "start_vault" {
log_level = var.vault_log_level
}

module "static_role_crud_api" {
source = "./modules/static_role_crud_api"
}

module "stop_vault" {
source = "./modules/stop_vault"
}
Expand Down
56 changes: 52 additions & 4 deletions enos/enos-scenario-openldap.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -489,8 +489,8 @@ scenario "openldap" {
vault_leader_ip = step.get_leader_ip.leader_host.public_ip
vault_addr = step.create_vault_cluster.api_addr_localhost
vault_root_token = step.create_vault_cluster.root_token
go_os = step.create_vault_cluster_targets.plugin_os
go_arch = step.create_vault_cluster_targets.plugin_arch
go_os = step.create_vault_cluster_targets.plugin_os
go_arch = step.create_vault_cluster_targets.plugin_arch

# plugin_name = var.plugin_name
# plugin_dest_dir = var.plugin_dest_dir
Expand All @@ -508,9 +508,9 @@ scenario "openldap" {


plugin_name = "vault-plugin-secrets-openldap"
plugin_dest_dir = "/Users/USER/go/vault-plugins"
plugin_dest_dir = "/Users/hamzaelmokhtarshili/go/vault-plugins"
plugin_source_type = "local_build"
makefile_dir = "/Users/USER/hashicorp/plugins/vault-plugin-secrets-openldap/"
makefile_dir = "/Users/hamzaelmokhtarshili/hashicorp/plugins/vault-plugin-secrets-openldap/"
plugin_registry_url = "URL Placeholder"
plugin_local_path = "Local Path Placeholder"
plugin_dir_vault = "/etc/vault/plugins"
Expand All @@ -524,6 +524,54 @@ scenario "openldap" {
}
}

step "test_static_role_crud_api" {
description = global.description.static_role_crud_api
module = module.static_role_crud_api
depends_on = [step.setup_plugin]

providers = {
enos = local.enos_provider[matrix.distro]
}

variables {
vault_leader_ip = step.get_leader_ip.leader_host.public_ip
vault_addr = step.create_vault_cluster.api_addr_localhost
vault_root_token = step.create_vault_cluster.root_token

plugin_mount_path = "local-secrets-ldap"
ldap_host = step.create_ldap_server.ldap_ip_address
ldap_port = step.create_ldap_server.ldap_port
ldap_dn = "uid=mary.smith,ou=users,dc=example,dc=com"
ldap_username = "mary.smith"
ldap_old_password = "defaultpassword"
ldap_role_name = "mary"
}
}

step "test_dynamic_role_crud_api" {
description = global.description.dynamic_role_crud_api
module = module.dynamic_role_crud_api
depends_on = [step.setup_plugin]

providers = {
enos = local.enos_provider[matrix.distro]
}

variables {
vault_leader_ip = step.get_leader_ip.leader_host.public_ip
vault_addr = step.create_vault_cluster.api_addr_localhost
vault_root_token = step.create_vault_cluster.root_token
hosts = step.create_vault_cluster_targets.hosts

plugin_mount_path = "local-secrets-ldap"
ldap_host = step.create_ldap_server.ldap_ip_address
ldap_port = step.create_ldap_server.ldap_port
ldap_user_dn_tpl = "uid={{username}},ou=users,dc=example,dc=com"
ldif_path = "/tmp"
ldap_role_name = "adam"
}
}


step "verify_raft_auto_join_voter" {
description = global.description.verify_raft_cluster_all_nodes_are_voters
Expand Down
8 changes: 8 additions & 0 deletions enos/modules/backend_servers_setup/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,12 @@ output "state" {

output "ldap_url" {
value = "ldap://${local.ldap_server.ip_address}:${local.ldap_server.port}"
}

output "ldap_ip_address" {
value = local.ldap_server.ip_address
}

output "ldap_port" {
value = local.ldap_server.port
}
7 changes: 7 additions & 0 deletions enos/modules/dynamic_role_crud_api/ldif/creation.ldif
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
dn: uid={{.Username}},ou=users,dc=example,dc=com
objectClass: inetOrgPerson
uid: {{.Username}}
cn: {{.Username}}
sn: {{.Password | utf16le | base64}}
memberOf: cn=dev,ou=groups,dc=example,dc=com
userPassword: {{.Password}}
2 changes: 2 additions & 0 deletions enos/modules/dynamic_role_crud_api/ldif/deletion.ldif
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
dn: uid={{.Username}},ou=users,dc=example,dc=com
changetype: delete
2 changes: 2 additions & 0 deletions enos/modules/dynamic_role_crud_api/ldif/rollback.ldif
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
dn: uid={{.Username}},ou=users,dc=example,dc=com
changetype: delete
64 changes: 64 additions & 0 deletions enos/modules/dynamic_role_crud_api/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: BUSL-1.1
Copy link
Contributor

Choose a reason for hiding this comment

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

I think all files in this repo need to be MPL


terraform {
required_providers {
enos = {
source = "registry.terraform.io/hashicorp-forge/enos"
}
}
}

locals {
ldif_files = fileset("${path.module}/ldif", "*")
file_host_pairs = flatten([
for i in range(length(var.hosts)) : [
for file in local.ldif_files : {
host_index = i
public_ip = var.hosts[i].public_ip
file = file
}
]
])
file_host_map = {
for item in local.file_host_pairs :
"${item["host_index"]}_${item["file"]}" => item
}
}

# Copy LDIF files to the hosts
resource "enos_file" "ldif_files" {
for_each = local.file_host_map
source = abspath("${path.module}/ldif/${each.value["file"]}")
destination = "${var.ldif_path}/${each.value["file"]}"
transport = {
ssh = {
host = each.value["public_ip"]
}
}
}

# Execute the dynamic role CRUD API test script on the Vault leader
resource "enos_remote_exec" "dynamic_role_crud_api_test" {
depends_on = [enos_file.ldif_files]
scripts = ["${path.module}/scripts/dynamic-role.sh"]

environment = {
VAULT_ADDR = var.vault_addr
VAULT_TOKEN = var.vault_root_token
PLUGIN_PATH = var.plugin_mount_path
LDAP_HOST = var.ldap_host
LDAP_PORT = var.ldap_port

ROLE_NAME = var.ldap_role_name
LDAP_USER_DN_TPL = var.ldap_user_dn_tpl
LDIF_PATH = var.ldif_path
}

transport = {
ssh = {
host = var.vault_leader_ip
}
}

}
94 changes: 94 additions & 0 deletions enos/modules/dynamic_role_crud_api/scripts/dynamic-role.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#!/usr/bin/env bash
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: BUSL-1.1
set -e

# Test Vault LDAP Dynamic Role CRUD and credential lifecycle using provided LDIFs.
# Assumptions:
# - You have uploaded creation.ldif, deletion.ldif, and rollback.ldif to the server.
# - Vault CLI is authenticated and VAULT_ADDR and VAULT_TOKEN are set.
# - Required ENV vars:
# PLUGIN_PATH (e.g., local-secrets-ldap)
# ROLE_NAME (e.g., adam)
# LDAP_HOST
# LDAP_PORT
# LDAP_USER_DN_TPL (e.g., uid={{username}},ou=users,dc=example,dc=com)
# LDIF_PATH (path to directory containing creation.ldif, deletion.ldif, rollback.ldif)

fail() {
echo "$1" 1>&2
exit 1
}

[[ -z "$ROLE_NAME" ]] && fail "ROLE_NAME env variable has not been set"
[[ -z "$LDAP_HOST" ]] && fail "LDAP_HOST env variable has not been set"
[[ -z "$LDAP_PORT" ]] && fail "LDAP_PORT env variable has not been set"
[[ -z "$LDAP_USER_DN_TPL" ]] && fail "LDAP_USER_DN_TPL env variable has not been set"
[[ -z "$LDIF_PATH" ]] && fail "LDIF_PATH env variable has not been set"

export VAULT_ADDR
export VAULT_TOKEN

ROLE_PATH="${PLUGIN_PATH}/role/${ROLE_NAME}"

echo "==> Creating dynamic role: ${ROLE_NAME}"
vault write "${ROLE_PATH}" \
creation_ldif=@${LDIF_PATH}/creation.ldif \
deletion_ldif=@${LDIF_PATH}/deletion.ldif \
rollback_ldif=@${LDIF_PATH}/rollback.ldif \
default_ttl="2m" \
max_ttl="10m"

echo "==> Reading dynamic role"
vault read "${ROLE_PATH}"

echo "==> Listing dynamic roles"
vault list "${PLUGIN_PATH}/role"

echo "==> Requesting dynamic credentials"
CRED_PATH="${PLUGIN_PATH}/creds/${ROLE_NAME}"
DYNAMIC_CREDS="$(vault read -format=json "${CRED_PATH}")"
Copy link
Contributor

Choose a reason for hiding this comment

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

I believe this line won't error out even if the vault read command returns a non-zero status. The command substitution will cause this line to always return 0.

DYN_USERNAME=$(echo "${DYNAMIC_CREDS}" | jq -r .data.username)
DYN_PASSWORD=$(echo "${DYNAMIC_CREDS}" | jq -r .data.password)
LEASE_ID=$(echo "${DYNAMIC_CREDS}" | jq -r .lease_id)

echo "==> Got dynamic username: ${DYN_USERNAME}"
echo "==> Got dynamic password: ${DYN_PASSWORD}"
echo "==> Lease ID: ${LEASE_ID}"
Comment on lines +55 to +57
Copy link
Contributor

Choose a reason for hiding this comment

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

What happens if we don't get these values?


# Build the DN for the dynamic user
DYN_DN=${LDAP_USER_DN_TPL/\{\{username\}\}/$DYN_USERNAME}

echo "==> Verifying login with dynamic credentials"
if ldapwhoami -h "${LDAP_HOST}:${LDAP_PORT}" -x -w "${DYN_PASSWORD}" -D "${DYN_DN}"; then
echo "[OK] Dynamic user login succeeded."
else
echo "[ERROR] Dynamic user login failed!"
exit 1
fi

echo "==> Revoking dynamic credentials (deletes LDAP user)"
vault lease revoke "${LEASE_ID}"

sleep 2

echo "==> Verifying dynamic user is deleted"
if ldapwhoami -h "${LDAP_HOST}:${LDAP_PORT}" -x -w "${DYN_PASSWORD}" -D "${DYN_DN}"; then
echo "[ERROR] Dynamic user still exists after lease revoke!"
exit 1
else
echo "[OK] Dynamic user deleted as expected."
fi

echo "==> Deleting dynamic role"
vault delete "${ROLE_PATH}"

echo "==> Confirming dynamic role deletion"
if vault read "${ROLE_PATH}"; then
echo "[ERROR] Dynamic role still exists after deletion!"
exit 1
else
echo "[OK] Dynamic role deleted successfully."
fi

echo "==> Dynamic role CRUD and credential lifecycle test: SUCCESS"
Copy link
Contributor

Choose a reason for hiding this comment

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

can we test these crud APIs after a seal/unseal or leadership change?

54 changes: 54 additions & 0 deletions enos/modules/dynamic_role_crud_api/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
variable "hosts" {
description = "The target machines host addresses to use for the Vault cluster"
type = map(object({
ipv6 = string
private_ip = string
public_ip = string
}))
}

variable "vault_leader_ip" {
type = string
description = "Public IP of the Vault leader node"
}

variable "plugin_mount_path" {
type = string
description = "Mount path for the plugin"
}

# LDAP variables for configuration
variable "ldap_host" {
type = string
description = "LDAP IP or hostname"
}

variable "ldap_port" {
type = string
description = "LDAP port"
}

variable "ldap_user_dn_tpl" {
type = string
description = "LDAP user DN template"
}

variable "ldap_role_name" {
type = string
description = "LDAP role name to be created"
}

variable "ldif_path" {
type = string
description = "LDIF files path"
}

variable "vault_addr" {
type = string
description = "The Vault API address"
}

variable "vault_root_token" {
type = string
description = "The Vault cluster root token"
}
Loading
Loading