Vault ldap secret engine enterprise constraints

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

Looking over the docs, I think this will likely break in 2.x community edition

When you unseal Vault the first time after upgrading from 1.21.x or earlier