Deployment guide

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, a demoteam group and two demonstration accounts
  • A dedicated cn=sspr-service account 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
1

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.

2

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 1010

Standard_B2s (2 vCPU, 4 GiB) is the recommended size. It is comfortable for the portal and the bundled directory together.

3

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.txt

The 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.

4

Confirm the appliance is running

Check that all three services are active.

systemctl is-active slapd php8.3-fpm nginx

All 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/healthz

The request returns 200.

5

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 mail

The 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.

6

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.

7

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.bak

Then open that file with your preferred editor and change these settings:

  • $ldap_url: your directory, for example ldap://dc01.example.internal:389. Use ldaps:// or set $ldap_starttls = true for 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 example dc=example,dc=internal
  • $ldap_login_attribute: uid for OpenLDAP, or sAMAccountName for Active Directory
  • $ldap_type: leave as openldap, or set to activedirectory

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/healthz

Once 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 slapd

Leave the bundled directory running until you are sure. Disabling it while the portal is still configured to use it takes the portal offline.

8

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.

9

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.conf

The 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-firstboot
10

Where 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)" done

Every request returns 404. The nginx configuration also denies those trees explicitly, as a second layer in case the document root is ever changed.

11

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.

12

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 --nginx if 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 (jsmith and agarcia) before real users rely on the portal. Remove the credentials file once you have recorded its contents.
  • Enable the captcha with $use_captcha = true if the portal is internet-facing, to slow down automated guessing against the reset flows.
  • Enable rate limiting with $use_ratelimit = true to cap repeated attempts per user and per IP address.
  • Consider $use_pwnedpasswords = true to 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.
13

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

Deploy on Azure