Security Azure

LTB Self Service Password on Ubuntu 24.04 on Azure User Guide

| Product: LTB Self Service Password 1.8.1 on Ubuntu 24.04 LTS on Azure

Overview

This guide covers the deployment and configuration of LTB Self Service Password on Ubuntu 24.04 on Azure using cloudimg Azure Marketplace images. Self Service Password is an open source, web based password self service application for LDAP directories, developed by the LDAP Tool Box project and published 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.

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. Because a password portal is only useful when a directory sits behind it, the OpenLDAP directory server is installed on the same virtual machine and pre organised with People and Groups containers, a dedicated service account, a sample group and two demonstration user accounts. You can therefore 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 are configured to 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.

The three services report active, Self Service Password 1.8.1, slapd 2.6.10, PHP 8.3.6, nginx 1.24.0 and Smarty 4.3.1 report their versions, and ss shows the portal listening on port 80 on all interfaces while the directory server is bound to 127.0.0.1 port 389 only

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 very first boot of every VM a one shot service generates four secrets unique to that instance: 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 is included:

  • 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; 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

  • Active Azure subscription, SSH public key, VNet and subnet in the target region

  • Subscription to the LTB Self Service Password listing on Azure Marketplace

  • Network Security Group rules allowing 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

Step 1: Deploy from the Azure Portal

Search LTB Self Service Password in Marketplace, select the cloudimg publisher, and click 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.

Step 2: Deploy from the Azure CLI

RG="identity-prod"; LOCATION="eastus"; VM_NAME="sspr1"
GALLERY_IMAGE_ID="/subscriptions/<sub-id>/resourceGroups/azure-cloudimg/providers/Microsoft.Compute/galleries/cloudimgGallery/images/ltb-self-service-password/versions/<version>"
SSH_KEY="$(cat ~/.ssh/id_rsa.pub)"
az group create --name "$RG" --location "$LOCATION"
az vm create \
  --resource-group "$RG" --name "$VM_NAME" \
  --image "$GALLERY_IMAGE_ID" \
  --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 and is comfortable for the portal and the bundled directory together.

Step 3: First boot and your credentials

The first boot bootstrap runs once, before anything starts serving, and 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 and then consider deleting the file.

You can confirm the bootstrap completed:

sudo ls -l /var/lib/cloudimg/

Both ltb-sspr-firstboot.done (the run-once sentinel) and ltb-sspr-bootstrap-ready (the marker that releases the serving units) should be present.

Step 4: Confirm the appliance is running

systemctl is-active slapd php8.3-fpm nginx

All three report active. 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 — it is deliberately not reachable from outside the VM. Confirm the portal answers:

curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1/healthz

This returns 200.

Step 5: Sign in and change a password

Browse to http://<your-vm-public-ip>/. The change password form is the default view.

The Self Service Password change form showing the LDAP Tool Box branding and the password policy panel listing the minimum length of 12 characters, minimum lowercase, uppercase, digit and special character counts, and the rules that a new password may not match the old password or the login

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, turning each rule green as the candidate password satisfies it, so users are never left guessing why a password was refused.

The change form filled in for the jsmith account, with the password policy panel above showing the lowercase rule satisfied in green while the length, uppercase, digit and special character rules remain unsatisfied in red for a short candidate password

Submit the form. The portal binds to the directory as the user, verifies the current password, applies the policy, and writes the new password.

The Self Service Password confirmation page showing the message Your password was changed

You can prove the change reached the directory rather than 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=cloudimg,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=cloudimg,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.

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

The reset your password page explaining that the user must choose a question and answer it, with a note that this requires an answer to have been registered already, above the same password policy panel

Resetting by question requires the user to have registered an answer first, using the registered an answer link on that page. The answer is encrypted with this instance's own encryption key before it is stored in the directory, and the access control policy makes the attribute holding it readable to nobody except the user and the portal's service account — so one user can never harvest another user's reset answers.

Step 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)
  • $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_attributeuid 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 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 the portal is working against your own directory — and only then — you can stop and disable the bundled one, which frees its memory and removes it from the appliance entirely:

sudo systemctl disable --now slapd

Leave it running until you are sure, because disabling it while the portal is still configured to use it will take the portal offline.

Step 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 it 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 via $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.

Step 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:

sudo cat /etc/systemd/system/nginx.service.d/cloudimg-bootstrap-gate.conf

The systemd drop in for nginx showing ConditionPathExists on the cloudimg bootstrap ready marker, the same condition applied to php8.3-fpm and slapd, the marker and first boot sentinel present in /var/lib/cloudimg, and all four units reported as enabled so they survive a reboot

This 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, which on a password management appliance would mean serving an instance whose secrets were not yet unique to it. With a condition, systemd simply skips the unit, so 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:

systemctl is-enabled slapd php8.3-fpm nginx ltb-sspr-firstboot

Step 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 it 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 — so conf/, lib/, templates/ and the dependency tree all sit outside anything reachable over HTTP:

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 one of those returns 404. The nginx configuration additionally denies those trees explicitly, as a second layer in case the document root is ever changed.

The configuration file listed as mode 640 owned by root and www-data, the nginx root pointing at the htdocs subdirectory, six sensitive paths including the configuration file and the dependency autoloader each returning 404, and the interface's own bootstrap and jQuery assets returning 200

Step 11: Confirm no default credential works

Every secret on this VM was generated on its first boot. You can confirm that the upstream defaults are refused:

sudo ldapwhoami -x -H ldap://127.0.0.1 -D "cn=sspr-service,dc=cloudimg,dc=local" -w 'secret' || echo "correctly refused"
sudo ldapsearch -x -H ldap://127.0.0.1 -b "ou=People,dc=cloudimg,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.

The credentials file listed as mode 600 owned by root root, then the directory rejecting the upstream default bind password with Invalid credentials 49, rejecting the upstream example bind DN, rejecting an incorrect password for a real account, and showing zero entries visible to an anonymous caller

Step 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 production use, and 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.

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

cloudimg provides 24/7 technical support for this product by email (support@cloudimg.co.uk) 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.