Skip to content

Commit 6d5e4c8

Browse files
hc-github-team-secure-vault-coreryancragunbrewgator
authored
Backport enos(ldap): always verify base DN connection before setup into ce/main
Refactor our connection checking into a new LDAP module that is capable of running a search and waiting for success. We now call this module while setting up the integration host and before enabling the LDAP secrets engine. We also fix two race conditions in the Agent and HA Seal scenarios where we might attempt to verify and/or test LDAP before the integration host has been set up. Signed-off-by: Ryan Cragun <me@ryan.ec> Co-authored-by: Ryan Cragun <me@ryan.ec> Co-authored-by: LT Carbonell <lt.carbonell@hashicorp.com>
1 parent ccbca20 commit 6d5e4c8

File tree

12 files changed

+339
-145
lines changed

12 files changed

+339
-145
lines changed

enos/enos-scenario-agent.hcl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -517,7 +517,8 @@ scenario "agent" {
517517
module = module.vault_verify_secrets_engines_create
518518
depends_on = [
519519
step.verify_vault_unsealed,
520-
step.get_vault_cluster_ips
520+
step.get_vault_cluster_ips,
521+
step.set_up_external_integration_target,
521522
]
522523

523524
providers = {

enos/enos-scenario-seal-ha.hcl

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,8 @@ scenario "seal_ha" {
472472
depends_on = [
473473
step.create_vault_cluster,
474474
step.get_vault_cluster_ips,
475-
step.verify_vault_unsealed
475+
step.verify_vault_unsealed,
476+
step.set_up_external_integration_target,
476477
]
477478

478479
providers = {
@@ -862,34 +863,6 @@ scenario "seal_ha" {
862863
}
863864
}
864865

865-
step "verify_log_secrets" {
866-
skip_step = !var.vault_enable_audit_devices || !var.verify_log_secrets
867-
868-
description = global.description.verify_log_secrets
869-
module = module.verify_log_secrets
870-
depends_on = [
871-
step.verify_secrets_engines_read,
872-
]
873-
874-
providers = {
875-
enos = local.enos_provider[matrix.distro]
876-
}
877-
878-
verifies = [
879-
quality.vault_audit_log_secrets,
880-
quality.vault_journal_secrets,
881-
quality.vault_radar_index_create,
882-
quality.vault_radar_scan_file,
883-
]
884-
885-
variables {
886-
audit_log_file_path = step.create_vault_cluster.audit_device_file_path
887-
leader_host = step.get_updated_cluster_ips.leader_host
888-
vault_addr = step.create_vault_cluster.api_addr_localhost
889-
vault_root_token = step.create_vault_cluster.root_token
890-
}
891-
}
892-
893866
step "verify_ui" {
894867
description = global.description.verify_ui
895868
module = module.vault_verify_ui
@@ -937,7 +910,6 @@ scenario "seal_ha" {
937910
depends_on = [
938911
step.wait_for_seal_rewrap,
939912
step.verify_secrets_engines_read,
940-
step.verify_log_secrets,
941913
]
942914

943915
providers = {
@@ -1140,6 +1112,34 @@ scenario "seal_ha" {
11401112
}
11411113
}
11421114

1115+
step "verify_log_secrets" {
1116+
skip_step = !var.vault_enable_audit_devices || !var.verify_log_secrets
1117+
1118+
description = global.description.verify_log_secrets
1119+
module = module.verify_log_secrets
1120+
depends_on = [
1121+
step.verify_secrets_engines_read_after_migration
1122+
]
1123+
1124+
providers = {
1125+
enos = local.enos_provider[matrix.distro]
1126+
}
1127+
1128+
verifies = [
1129+
quality.vault_audit_log_secrets,
1130+
quality.vault_journal_secrets,
1131+
quality.vault_radar_index_create,
1132+
quality.vault_radar_scan_file,
1133+
]
1134+
1135+
variables {
1136+
audit_log_file_path = step.create_vault_cluster.audit_device_file_path
1137+
leader_host = step.get_updated_cluster_ips.leader_host
1138+
vault_addr = step.create_vault_cluster.api_addr_localhost
1139+
vault_root_token = step.create_vault_cluster.root_token
1140+
}
1141+
}
1142+
11431143
output "audit_device_file_path" {
11441144
description = "The file path for the file audit device, if enabled"
11451145
value = step.create_vault_cluster.audit_device_file_path
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# Copyright IBM Corp. 2016, 2025
2+
# SPDX-License-Identifier: BUSL-1.1
3+
4+
terraform {
5+
required_providers {
6+
enos = {
7+
source = "registry.terraform.io/hashicorp-forge/enos"
8+
}
9+
}
10+
}
11+
12+
variable "hosts" {
13+
description = "The target machines to run the test query from"
14+
type = map(object({
15+
ipv6 = string
16+
private_ip = string
17+
public_ip = string
18+
}))
19+
}
20+
21+
variable "ldap_base_dn" {
22+
type = string
23+
description = "The LDAP base dn to search from"
24+
}
25+
26+
variable "ldap_bind_dn" {
27+
type = string
28+
description = "The LDAP bind dn"
29+
}
30+
31+
variable "ldap_host" {
32+
type = object({
33+
ipv6 = string
34+
private_ip = string
35+
public_ip = string
36+
})
37+
description = "The LDAP host"
38+
}
39+
40+
variable "ldap_password" {
41+
type = string
42+
description = "The LDAP password"
43+
}
44+
45+
variable "ldap_port" {
46+
type = string
47+
description = "The LDAP port"
48+
}
49+
50+
variable "ldap_query" {
51+
type = string
52+
description = "The LDAP query to use when testing the connection"
53+
default = null
54+
}
55+
56+
variable "retry_interval" {
57+
type = number
58+
description = "How many seconds to wait between each retry"
59+
default = 2
60+
}
61+
62+
variable "timeout" {
63+
type = number
64+
description = "The max number of seconds to wait before timing out"
65+
default = 60
66+
}
67+
68+
# Wait for the search to succeed
69+
resource "enos_remote_exec" "wait_for_search" {
70+
for_each = var.hosts
71+
72+
environment = {
73+
LDAP_BASE_DN = var.ldap_base_dn
74+
LDAP_BIND_DN = var.ldap_bind_dn
75+
LDAP_HOST = var.ldap_host.public_ip
76+
LDAP_PASSWORD = var.ldap_password
77+
LDAP_PORT = var.ldap_port
78+
LDAP_QUERY = var.ldap_query == null ? "" : var.ldap_query
79+
RETRY_INTERVAL = var.retry_interval
80+
TIMEOUT_SECONDS = var.timeout
81+
}
82+
scripts = [abspath("${path.module}/scripts/wait-for-search.sh")]
83+
84+
transport = {
85+
ssh = {
86+
host = each.value.public_ip
87+
}
88+
}
89+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#!/usr/bin/env bash
2+
# Copyright IBM Corp. 2026
3+
# SPDX-License-Identifier: BUSL-1.1
4+
5+
fail() {
6+
echo "$1" 1>&2
7+
exit 1
8+
}
9+
10+
[[ -z "$LDAP_BASE_DN" ]] && fail "LDAP_BASE_DN env variable has not been set"
11+
[[ -z "$LDAP_BIND_DN" ]] && fail "LDAP_BIND_DN env variable has not been set"
12+
[[ -z "$LDAP_HOST" ]] && fail "LDAP_HOST env variable has not been set"
13+
[[ -z "$LDAP_PORT" ]] && fail "LDAP_PORT env variable has not been set"
14+
[[ -z "$LDAP_PASSWORD" ]] && fail "LDAP_PASSWORD env variable has not been set"
15+
[[ -z "$RETRY_INTERVAL" ]] && fail "RETRY_INTERVAL env variable has not been set"
16+
[[ -z "$TIMEOUT_SECONDS" ]] && fail "TIMEOUT_SECONDS env variable has not been set"
17+
18+
# NOTE: An LDAP_QUERY is not technically required here as long as we have a base
19+
# DN and bind DN to search and bind to.
20+
21+
search="ldap://${LDAP_HOST}:${LDAP_PORT} -b ${LDAP_BASE_DN} -D ${LDAP_BIND_DN} -w ${LDAP_PASSWORD} -s base ${LDAP_QUERY}"
22+
safe_search="ldap://${LDAP_HOST}:${LDAP_PORT} -b ${LDAP_BASE_DN} -D ${LDAP_BIND_DN} -s base ${LDAP_QUERY}"
23+
echo "Running test search: ${safe_search}"
24+
25+
begin_time=$(date +%s)
26+
end_time=$((begin_time + TIMEOUT_SECONDS))
27+
test_search_out=""
28+
test_search_res=""
29+
declare -i tries=0
30+
while [ "$(date +%s)" -lt "$end_time" ]; do
31+
tries+=1
32+
if test_search_out=$(eval "ldapsearch -x -H ${search}" 2>&1); then
33+
test_search_res=0
34+
break
35+
fi
36+
37+
test_search_res=$?
38+
echo "Test search failed!, search: ${safe_search}, attempt: ${tries}, exit code: ${test_search_res}, error: ${test_search_out}, retrying..."
39+
sleep "$RETRY_INTERVAL"
40+
done
41+
42+
if [ "$test_search_res" -ne 0 ]; then
43+
echo "Timed out waiting for search!, search: ${safe_search}, attempt: ${tries}, exit code: ${test_search_res}, error: ${test_search_out}" 2>&1
44+
# Exit with the ldapsearch exit code so we bubble that up to error diagnostic
45+
exit "$test_search_res"
46+
fi

enos/modules/set_up_external_integration_target/main.tf

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ terraform {
1111

1212
locals {
1313
test_server_address = var.ip_version == "6" ? var.hosts[0].ipv6 : var.hosts[0].public_ip
14+
ldap_base_dn = join(",", formatlist("dc=%s", split(".", var.ldap_domain)))
1415
ldap_server = {
15-
domain = "enos.com"
16+
domain = var.ldap_domain
17+
base_dn = local.ldap_base_dn
1618
org = "hashicorp"
1719
admin_pw = "password1"
1820
version = var.ldap_version
@@ -66,19 +68,30 @@ resource "enos_remote_exec" "setup_openldap" {
6668
}
6769
}
6870

71+
// Wait for the base DN to be available before we populate it
72+
module "wait_for_ldap_base_dn" {
73+
depends_on = [enos_remote_exec.setup_openldap]
74+
source = "../ldap_wait_for_search"
75+
76+
hosts = var.hosts
77+
ldap_base_dn = local.ldap_base_dn
78+
ldap_bind_dn = "cn=admin,${local.ldap_base_dn}"
79+
ldap_host = local.ldap_server.host
80+
ldap_password = local.ldap_server.admin_pw
81+
ldap_port = local.ldap_server.port
82+
}
83+
6984
# Populate LDAP server with required users and organizational units
7085
resource "enos_remote_exec" "populate_ldap" {
71-
depends_on = [enos_remote_exec.setup_openldap]
86+
depends_on = [module.wait_for_ldap_base_dn]
7287

7388
scripts = [abspath("${path.module}/scripts/populate-ldap.sh")]
7489

7590
environment = {
76-
LDAP_SERVER = local.ldap_server.host.private_ip
77-
LDAP_PORT = local.ldap_server.port
78-
LDAP_ADMIN_PW = local.ldap_server.admin_pw
79-
LDAP_DOMAIN = local.ldap_server.domain
80-
RETRY_INTERVAL = var.retry_interval
81-
TIMEOUT_SECONDS = var.timeout
91+
LDAP_SERVER = local.ldap_server.host.private_ip
92+
LDAP_PORT = local.ldap_server.port
93+
LDAP_ADMIN_PW = local.ldap_server.admin_pw
94+
LDAP_BASE_DN = local.ldap_server.base_dn
8295
}
8396

8497
transport = {

0 commit comments

Comments
 (0)