LTB Self Service Password
Overview
Self Service Password is an open-source, web-based password self-service application for LDAP directories. The LDAP Tool Box project develops it and publishes it at ltb-project.org. It gives your users a browser page for the work that would otherwise mean a helpdesk ticket: changing a password they already know, and resetting one they have forgotten by answering security questions they registered earlier.
This guide covers the deployment and configuration of LTB Self Service Password on Ubuntu 24.04 on Azure, using SecureAnyCloud Azure Marketplace images. The image installs Self Service Password 1.8.1 from the upstream release, OpenLDAP 2.6.10 and nginx with PHP 8.3 from Ubuntu 24.04, and wires all three together.
A password portal is only useful when a directory sits behind it. The OpenLDAP directory server is therefore installed on the same virtual machine, pre-organized with People and Groups containers, a dedicated service account, a sample group and two demonstration user accounts. You can complete a real password change within minutes of launch, and repoint the portal at your own Active Directory or OpenLDAP server when you are ready. Unattended security upgrades keep the server patched on your running VM.
One appliance, three services. nginx and php8.3-fpm serve the portal on port 80, and slapd holds the directory. The directory is bound to 127.0.0.1:389 only and is never exposed to the network.
Security by design: no credential in this image is known to anyone. Rather than shipping default passwords and rotating them later, the image ships no working credential at all. On the first boot of every VM, a one-shot service generates four secrets unique to that instance. They are the OpenLDAP manager password, the service account password the portal uses to reset user passwords, the encryption key that protects stored security answers, and the demonstration account passwords. They are written to /root/ltb-self-service-password-credentials.txt (mode 0600, root-only).
The portal cannot serve an unprovisioned instance. The web server, the PHP runtime and the directory server are each gated on a bootstrap marker that first boot writes only after every secret is in place. Until that marker exists, systemd skips those units entirely, so there is no window in which a half-provisioned password appliance is reachable. The units are still enabled, so the appliance comes straight back after a reboot.
What you get:
- Self Service Password 1.8.1 served by nginx and PHP 8.3 FPM, rendering through Smarty 4 from the Ubuntu archive so the template engine receives security updates
- OpenLDAP 2.6.10 (
slapd.service) as the backing directory, bound to loopback, with People and Groups containers, ademoteamgroup and two demonstration accounts - A dedicated
cn=sspr-serviceaccount with exactly the directory rights the portal needs: reset a user password, and read and write the attribute that stores security answers - Change-your-password and reset-by-security-questions enabled and working out of the box, with reset by emailed token and reset by SMS included and ready to enable
- A password policy requiring 12 characters with mixed case, a digit and a special character, displayed to the user as they type
- A per-instance set of directory, service account and encryption secrets generated on first boot and documented in
/root/ltb-self-service-password-credentials.txt(0600)
Prerequisites
- An active Azure subscription, an SSH public key, and a VNet and subnet in the target region
- A subscription to the LTB Self Service Password listing on Azure Marketplace
- Network Security Group rules that allow TCP 22 (administration) and TCP 80 (the portal) from the networks that need them
- If you intend to use your own directory rather than the bundled one, network reachability from this VM to your Active Directory or OpenLDAP server, and a service account that can reset user passwords
Deploy from the Azure Portal
Search for LTB Self Service Password in Marketplace, select the SecureAnyCloud publisher, and choose Create.
Configure the Network Security Group to allow TCP 80 for the portal from the networks whose users need it, and TCP 22 for administration. The directory server stays on loopback and is never exposed.
Deploy from the Azure CLI
RG="identity-prod"; LOCATION="eastus"; VM_NAME="sspr1" IMAGE_URN="sac1647283583153:ltb-self-service-password:default:latest" SSH_KEY="$(cat ~/.ssh/id_rsa.pub)" az group create --name "$RG" --location "$LOCATION"
# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:ltb-self-service-password:default:latest
az vm create \
--resource-group "$RG" --name "$VM_NAME" \
--image "$IMAGE_URN" \
--size Standard_B2s \
--admin-username azureuser \
--ssh-key-values "$SSH_KEY" \
--public-ip-sku Standard
az vm open-port --resource-group "$RG" --name "$VM_NAME" --port 80 --priority 1010Standard_B2s (2 vCPU, 4 GiB) is the recommended size. It is comfortable for the portal and the bundled directory together.
First boot and your credentials
The first-boot bootstrap runs once, before anything starts serving. It typically completes within a minute of the VM reaching the running state. Connect over SSH and read the credentials it generated.
sudo cat /root/ltb-self-service-password-credentials.txtThe file lists the portal URL, the two demonstration accounts with their passwords, the OpenLDAP manager account and password, and the service account and password the portal binds with. It is mode 0600 and owned by root, so no other account on the VM can read it. Record the values somewhere safe, then consider deleting the file.
Confirm that the bootstrap completed.
sudo ls -l /var/lib/sac/data/Both ltb-sspr-firstboot.done (the run-once sentinel) and ltb-sspr-bootstrap-ready (the marker that releases the serving units) should be present.
Confirm the appliance is running
Check that all three services are active.
systemctl is-active slapd php8.3-fpm nginxAll three report active. Next, check what is listening.
ss -tlnp | grep -E ':80 |:389 'The portal is on port 80 on all interfaces. The directory server appears as 127.0.0.1:389 only, because it is deliberately not reachable from outside the VM. Then confirm that the portal answers.
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1/healthzThe request returns 200.
Sign in and change a password
Browse to http://<your-vm-public-ip>/. The change password form is the default view.
Enter one of the demonstration logins from the credentials file, its current password, and a new password twice. The policy panel updates live as you type. It turns each rule green as the candidate password satisfies it, so users are never left guessing why a password was refused.
Submit the form. The portal binds to the directory as the user, verifies the current password, applies the policy, and writes the new password.
You can prove the change reached the directory, and not only the browser. Substitute the login you used.
DEMO_LOGIN="$(sudo grep -E '^DEMO_USER_1_LOGIN=' /root/ltb-self-service-password-credentials.txt | cut -d= -f2-)" sudo ldapsearch -x -H ldap://127.0.0.1 \
-D "cn=admin,dc=sac,dc=local" \
-w "$(sudo grep -E '^LDAP_ADMIN_PASSWORD=' /root/ltb-self-service-password-credentials.txt | cut -d= -f2-)" \
-b "uid=${DEMO_LOGIN},ou=People,dc=sac,dc=local" -LLL uid cn mailThe entry is returned with its uid, cn and mail. The userPassword attribute is not shown, because the access control policy makes it invisible even to a successful search.
Reset a forgotten password with security questions
Choose Question in the menu bar, or browse to http://<your-vm-public-ip>/index.php?action=resetbyquestions.
Resetting by question requires the user to have registered an answer first, using the registration link on that page. The answer is encrypted with this instance's own encryption key before it is stored in the directory. The access control policy makes the attribute that holds it readable only to the user and the portal's service account, so one user can never harvest another user's reset answers.
Point the portal at your own directory
The bundled directory exists so the appliance works standalone and you can evaluate it immediately. To use your own Active Directory or OpenLDAP instead, edit the connection block in /usr/share/self-service-password/conf/config.inc.local.php. Take a backup first.
sudo cp -a /usr/share/self-service-password/conf/config.inc.local.php \
/usr/share/self-service-password/conf/config.inc.local.php.bakThen open that file with your preferred editor and change these settings:
$ldap_url: your directory, for exampleldap://dc01.example.internal:389. Useldaps://or set$ldap_starttls = truefor an encrypted connection, which you should do$ldap_binddn: a service account that can reset user passwords$ldap_bindpw: that account's password$ldap_base: the base DN to search, for exampledc=example,dc=internal$ldap_login_attribute:uidfor OpenLDAP, orsAMAccountNamefor Active Directory$ldap_type: leave asopenldap, or set toactivedirectory
For Active Directory, also set $ldap_use_exop_passwd = false and connect over LDAPS, because Active Directory refuses password changes over an unencrypted connection.
Apply the change and confirm that the portal still answers.
sudo systemctl reload php8.3-fpm curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1/healthzOnce you have confirmed that the portal works against your own directory, and only then, you can stop and disable the bundled one. This frees its memory and removes it from the appliance entirely.
sudo systemctl disable --now slapdLeave the bundled directory running until you are sure. Disabling it while the portal is still configured to use it takes the portal offline.
Enable reset by emailed token
Reset by emailed token is included but switched off in the shipped configuration, because it needs a working mail relay that a freshly launched VM does not have. Once you have a relay, configure the mail settings and turn the feature on in /usr/share/self-service-password/conf/config.inc.local.php.
$use_tokens = true; $mail_from = "no-reply@example.com"; $mail_smtp_host = "smtp.example.com"; $mail_smtp_port = 587; $mail_smtp_auth = true; $mail_smtp_user = "your-smtp-user"; $mail_smtp_pass = "your-smtp-password"; $mail_smtp_secure = "tls";Reset by SMS is available the same way through $use_sms, and needs an account with one of the supported SMS gateways. The full list of mail and SMS settings is in conf/config.inc.php.
How the first boot gate protects an unprovisioned instance
The three serving units each carry a systemd condition on a marker file that first boot writes only after every secret exists. Read the gate for nginx.
sudo cat /etc/systemd/system/nginx.service.d/sac-bootstrap-gate.confThe gate is a condition rather than an ordering dependency, on purpose. Ordering alone would still start the web server if the bootstrap failed or ran slowly. On a password management appliance, that would mean serving an instance whose secrets were not yet unique to it. With a condition, systemd simply skips the unit. The failure mode is a portal that does not answer, rather than one that answers insecurely.
All four units are enabled, so a reboot brings the appliance straight back. Confirm it.
systemctl is-enabled slapd php8.3-fpm nginx ltb-sspr-firstbootWhere the directory password is stored, and why it is not reachable
The portal's own configuration file holds the service account bind password and the encryption key. It lives at /usr/share/self-service-password/conf/config.inc.local.php, mode 0640 and owned by root:www-data, so PHP can read it and nothing else on the VM can.
More importantly, the file is not in a directory the web server can serve at all. The upstream layout puts the front controllers in a htdocs/ subdirectory, and nginx is rooted there. The conf/, lib/, templates/ and dependency directories therefore all sit outside anything reachable over HTTP. Request those paths to verify it.
grep -E '^\s+root ' /etc/nginx/sites-available/ltb-sspr for p in /conf/config.inc.local.php /lib/functions.inc.php /composer.json; do printf '%-32s -> %s\n' "$p" "$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1$p)" doneEvery request returns 404. The nginx configuration also denies those trees explicitly, as a second layer in case the document root is ever changed.
Confirm no default credential works
Every secret on this VM was generated on its first boot. Confirm that the upstream defaults are refused.
sudo ldapwhoami -x -H ldap://127.0.0.1 -D "cn=sspr-service,dc=sac,dc=local" -w 'secret' || echo "correctly refused"
sudo ldapsearch -x -H ldap://127.0.0.1 -b "ou=People,dc=sac,dc=local" uid | grep -c '^uid: ' || echo "0 entries visible anonymously"The first returns Invalid credentials (49). The second shows that an anonymous caller cannot enumerate directory users at all.
Security recommendations
- Put the portal behind TLS. It accepts passwords, so it should never run over plain HTTP on an untrusted network. Terminate TLS on an Azure Application Gateway in front of the VM, or install a certificate with
certbot --nginxif the VM has a public DNS name. - Restrict port 80 in the Network Security Group to the networks whose users need the portal, rather than the whole internet.
- Delete the demonstration accounts (
jsmithandagarcia) before real users rely on the portal. Remove the credentials file once you have recorded its contents. - Enable the captcha with
$use_captcha = trueif the portal is internet-facing, to slow down automated guessing against the reset flows. - Enable rate limiting with
$use_ratelimit = trueto cap repeated attempts per user and per IP address. - Consider
$use_pwnedpasswords = trueto reject passwords known from public breach corpora. This requires the VM to have outbound internet access to the Have I Been Pwned API. - Keep the VM patched. Unattended security upgrades are enabled by default.
Support and licensing
Self Service Password is distributed under the GNU General Public License version 3. OpenLDAP is distributed under the OpenLDAP Public License. This image bundles both unmodified.
SecureAnyCloud provides 24/7 technical support for this product by email (sales@secureanycloud.com) and live chat, covering deployment, connecting the portal to your own directory, password policy design, security question and token configuration, mail and SMS integration, and upgrades. Critical issues receive a one-hour average response time.
All product and company names are trademarks or registered trademarks of their respective holders. Use of them does not imply any affiliation with or endorsement by them.
Deploy on Azure
Launch LTB Self Service Password 1.8.1 on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.
View this image
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com