I am running Vault v1.20 and have enabled the ldap secrets engine to manage some service accounts I have in openldap. I have configured some static roles and am currently setting the rotation_period inside the static role to be shorter (tested with 60s) than the openldap password policy expiry 90d - this is all working and when I configure a test service using consul-template to read my credentials, I can verify that as vault does the rotation, consul-template reads the latest password, so I never have any access concerns.
What I am unsure on is - the documentation on the secret engine and the future vault v2.X release notes are a little confusing. They indicate that rotation_period for a static-role is an enterprise feature, however it is a feature that is required by the plugin and is working well in local tests without an enterprise subscription.
Can anyone confirm what the plans are for the feature and will the rotation_period option or even the secret engine be removed from support for non enterprise users?
I enable the secret engine with the following:
My .env
# Kubernetes
CONTEXT=rancher-desktop
NAMESPACE=openldap
# Vault (outside of my cluster)
VAULT_ADDR=http://vault.service.home.local
VAULT_TOKEN=xxxxx
# OpenLDAP
LDAP_BASE_DN="dc=home,dc=local"
LDAP_ADMIN_DN="cn=admin,${LDAP_BASE_DN}"
LDAP_URI="ldap://ldap.openldap.svc.cluster.local:389"
# Static LDAP role parameters
LDAP_ROTATION_PERIOD="60s"
STATIC_ROLE="access"
SERVICE_ACCOUNT="service-account"
With vault and openldap running locally I can configure the secrets engine
#!/bin/bash
# load the environment
source .env
# enable vault ldap secret engine
ENABLED=$(vault secrets list -format yaml | yq -r '.ldap/.type')
if [[ "${ENABLED}" != "ldap" ]]; then
vault secrets enable -path ldap ldap
else
echo "ldap secrets engine already enabled"
fi
# configure vault ldap secret engine
cat > ldap.json <<EOF
{
"schema": "openldap",
"binddn": "cn=admin,${LDAP_BASE_DN}",
"bindpass": "admin",
"url": "${LDAP_URI}",
"credential_type": "password"
}
EOF
vault write ldap/config @ldap.json
# clean-up
rm -f ldap.json
I seed my ldap data with the following - openldap running in my local k3s:
#!/bin/bash
# load the environment
source .env
# Execute
POD=$(kubectl --context ${CONTEXT} --namespace ${NAMESPACE} get pods -l service=ldap -o NAME | sed 's/pod\///')
echo "connecting to ${POD}"
kubectl --context ${CONTEXT} --namespace ${NAMESPACE} exec -it "${POD}" -- sh -c "\
ldapadd -x -H ldap://ldap.openldap.svc.cluster.local -D ${LDAP_ADMIN_DN} -w admin <<EOF
dn: ou=groups,${LDAP_BASE_DN}
objectClass: organizationalunit
objectClass: top
ou: groups
description: groups of users
dn: ou=users,${LDAP_BASE_DN}
objectClass: organizationalunit
objectClass: top
ou: users
description: users
dn: cn=service-account,ou=groups,${LDAP_BASE_DN}
objectClass: groupofnames
objectClass: top
description: testing group service-accounts
cn: service-account
member: cn=alice,ou=users,${LDAP_BASE_DN}
dn: cn=alice,ou=users,${LDAP_BASE_DN}
objectClass: person
objectClass: top
cn: learn
sn: learn
memberOf: cn=service-account,ou=groups,${LDAP_BASE_DN}
userPassword: 1LearnedVault
dn: cn=${SERVICE_ACCOUNT},ou=users,${LDAP_BASE_DN}
objectClass: person
objectClass: top
cn: ${SERVICE_ACCOUNT}
sn: ${SERVICE_ACCOUNT}
description: Account for ${SERVICE_ACCOUNT} user
memberOf: cn=service-account,ou=groups,${LDAP_BASE_DN}
userpassword: basicpassword
EOF
"
echo "exit code 68 is OK here if the user already exists"
I create my static role with the following
#!/bin/bash
# load the environment
source .env
cat > ldap-role.json <<EOF
{
"role_name": "${STATIC_ROLE}",
"username": "${SERVICE_ACCOUNT}",
"dn": "cn=${SERVICE_ACCOUNT},ou=users,${LDAP_BASE_DN}",
"rotation_period": "${LDAP_ROTATION_PERIOD}"
}
EOF
vault write ldap/static-role/${STATIC_ROLE} @ldap-role.json
rm -f ldap-role.json
# loxal test
vault read ldap/static-cred/${STATIC_ROLE}
My consul-template is configured in a test pod from a configMap with the following inside:
config.hcl: |
vault {
address = "http://vault.vault.svc.cluster.local:8200"
renew_token = false
lease_renewal_threshold = 0.90
default_lease_duration = "5m"
retry {
enabled = true
attempts = 0
backoff = "1s"
max_backoff = "30s"
}
}
template {
source = "/etc/consul-template/ldap-creds.tpl"
destination = "/creds/ldap.env"
perms = 0600
error_on_missing_key = true
command = "/usr/local/bin/verify-bind.sh"
command_timeout = "30s"
left_delimiter = "[["
right_delimiter = "]]"
}
ldap-creds.tpl: |
[[ with secret "ldap/static-cred/service-account" ]]LDAP_USERNAME=[[ .Data.username ]]
LDAP_PASSWORD=[[ .Data.password ]]
LDAP_ROTATION_TTL=[[ .Data.ttl ]][[ end ]]
The verify bind script referred to here has this:
#!/usr/bin/env bash
set -euo pipefail
source /creds/ldap.env
: "${LDAP_URL:?set LDAP_URL in the pod env}"
: "${LDAP_BIND_DN_TEMPLATE:?e.g. cn=%s,ou=svc,dc=example,dc=org}"
BIND_DN=$(printf "${LDAP_BIND_DN_TEMPLATE}" "${LDAP_USERNAME}")
echo "$(date -Iseconds) creds rendered for ${LDAP_USERNAME}, ttl to next rotation: ${LDAP_ROTATION_TTL}s"
if ldapwhoami -x -H "${LDAP_URL}" -D "${BIND_DN}" -w "${LDAP_PASSWORD}"; then
echo "$(date -Iseconds) BIND OK as ${BIND_DN} with ${LDAP_PASSWORD}"
else
echo "$(date -Iseconds) BIND FAILED as ${BIND_DN} with ${LDAP_PASSWORD}" >&2
exit 1
fi