Deployment guide

LemonLDAP NG 2.21

Overview

LemonLDAP::NG is a WebSSO portal and access manager. It authenticates your users once, then acts as an identity provider for the applications they go on to use. It speaks CAS, SAML 2.0 and OpenID Connect at the same time, from the same server.

That combination makes it a practical migration route for an organization moving off a legacy CAS deployment. You can keep answering CAS for the applications that still need it while issuing OIDC tokens and SAML assertions to everything new, without running three products. LemonLDAP::NG is free software published under the GNU General Public License, version 2 or later.

This guide covers the deployment and verification of LemonLDAP::NG 2.21.5 LTS on Microsoft Azure, using the SecureAnyCloud preconfigured virtual machine image from the Azure Marketplace.

What you get:

  • LemonLDAP::NG 2.21.5-1 from the official vendor repository, with the GPG signature verified. It is pinned to that exact version and held so that an unattended upgrade cannot move it.
  • nginx terminating TLS on port 443 with a certificate generated on your machine at first boot.
  • OpenLDAP as a co-located directory on the loopback interface. It holds your administrator and one ordinary demo user, so you can prove that a sign-in works within a minute of the machine booting.
  • CAS, SAML 2.0 and OpenID Connect issuers, all enabled, each with per-VM signing material.
  • A self-test OpenID Connect relying party and CAS service, both with loopback-only callbacks. The image also ships lemonldap-ng-sso-roundtrip.py, which drives a complete OIDC authorization code flow and a complete CAS ticket validation, and checks what comes back.
  • Ubuntu 24.04 LTS, fully patched at build time, with unattended security upgrades enabled.

Exposed ports: 22 and 443, and nothing else. The directory listens on 127.0.0.1:389 only. There is deliberately no port 80 listener, so there is no plaintext path to a sign-in form.

The one thing to know about a stock LemonLDAP::NG install

A default LemonLDAP::NG installation selects its Demo authentication backend. That backend is more than a weak password. It accepts any username whose password is the same as the username. The accounts it resolves (dwho, rtyler, msmith) include one that the shipped configuration grants the Manager, the full administration console. On a stock install, dwho / dwho is a working administrator.

This image does not ship that. At first boot, the authentication, user and password backends are all moved onto a directory created on your own machine. The demo accounts are gone, and a per-VM administrator is minted with a random password.

The image is verified before capture and again on a freshly launched VM. dwho, rtyler and msmith are all refused, while the administrator in your credentials file signs in and reaches the Manager. The check proves something in both directions rather than just failing everything.

Every secret is generated on your machine

That matters for an SSO server more than for most software. This server signs OpenID Connect ID tokens and SAML assertions with private keys, and every application that trusts this server trusts those keys. A key baked into the image would be shared by every customer who deployed it, and any of them could forge tokens against any other.

So first boot mints the following, on your machine and nowhere else:

  • The directory database and its administrator password.
  • The Manager administrator's password.
  • The session cipher key. Note that a stock configuration has no cipher key at all.
  • The SAML signing key and certificate.
  • The OpenID Connect signing key and its key ID.
  • The TLS certificate nginx serves, and the machine's SSH host keys.

Two VMs launched from this image were compared label by label. All fifteen of those values differ between them.

Why 2.21 and not the newest release

The vendor repository's newest release is in the 2.23 series, but 2.21 is the maintained LTS branch, and this image pins the current head of it.

On 10 August 2026, the 2.21 branch received the same two security fixes that went into 2.23.3 on the same day:

  • CVE-2026-19349, rated high: a GitHub/LinkedIn authentication state token could be created as an SSO session and replayed as a session cookie.
  • CVE-2026-12804: an unauthenticated open redirect in the SAML Common Domain Cookie endpoint.

The branch also carries the completed fix for CVE-2025-59518 and a fix for a handler access control bypass that affected the vendor's default nginx configuration. There is no security fix outstanding against this pin.

1

Deploy the Virtual Machine

Option A: Azure Portal

  • In the Azure Portal, search the Marketplace for LemonLDAP::NG 2.21 LTS on Ubuntu 24.04 LTS by SecureAnyCloud and select Create.
  • Choose your subscription, resource group and region.
  • Choose Standard_B2s (2 vCPU, 4 GiB). It is enough for the portal, the directory and the three issuers. Increase it if you expect heavy concurrent sign-in traffic.
  • Under Administrator account, choose SSH public key and supply your own key. The image ships no user account and no password of any kind. Azure creates your account at provisioning time.
  • Under Inbound port rules, allow SSH (22) and HTTPS (443). Do not open anything else, because nothing else is listening.
  • Select Review + create.

Option B: Azure CLI

az vm create \
  --resource-group my-resource-group \
  --name my-lemonldap-ng \
  --image sac:lemonldap-ng-ubuntu-24-04:default:latest \
  --size Standard_B2s \
  --admin-username azureuser \
  --generate-ssh-keys \
  --public-ip-sku Standard
az vm open-port --resource-group my-resource-group --name my-lemonldap-ng --port 22 --priority 1001
az vm open-port --resource-group my-resource-group --name my-lemonldap-ng --port 443 --priority 1002

Optional: pin your DNS name before the first boot

LemonLDAP::NG is virtual host driven. The SAML entity ID and OpenID Connect issuer it publishes are built from the address it believes it answers on. These are permanent federation identifiers. If you change one later, you must update every service provider and relying party you registered.

If you already know the DNS name this server will answer on, set it before the machine ever boots, with cloud-init user data:

#cloud-config write_files: - path: /etc/sac/lemonldap-ng.conf permissions: '0644' content: | LLNG_HOSTNAME=sso.example.org LLNG_ADMIN_USER=llngadmin

Pass that file with --custom-data cloud-init.yaml on az vm create.

If you skip this, the server configures itself for the VM's public IP address. That is fine for evaluation, and you can change it later from the Manager.

2

Wait for the first boot to finish

First boot creates the directory, mints every key listed above and rewrites the configuration. It normally completes in under a minute. Connect and check:

ssh azureuser@<your-vm-ip>

Then, on the VM:

# the sentinel appears only when first boot has finished successfully
sudo test -e /var/lib/sac/data/lemonldap-ng-firstboot.done && echo "first boot complete"
# and the services it starts systemctl is-active slapd lemonldap-ng-fastcgi-server nginx

First boot wrote your credentials to a root-only file on this machine:

sudo cat /root/lemonldap-ng-credentials.txt
PORTAL_URL=https://<your-vm-ip>/ MANAGER_URL=https://<your-vm-ip>/manager/ LLNG_ADMIN_USERNAME=llngadmin LLNG_ADMIN_PASSWORD=<generated on this machine> DEMO_USERNAME=demo DEMO_PASSWORD=<generated on this machine> LDAP_ADMIN_DN=cn=admin,dc=sac,dc=local LDAP_ADMIN_PASSWORD=<generated on this machine> SELFTEST_OIDC_CLIENT_ID=sac-selftest SELFTEST_OIDC_CLIENT_SECRET=<generated on this machine>
3

Sign in

Open https://<your-vm-ip>/ in a browser. The certificate is self-signed and generated on your machine, so your browser warns on first visit. Replace it with a real certificate in Step 6.

Sign in with LLNG_ADMIN_USERNAME and LLNG_ADMIN_PASSWORD. You land on the portal menu, which lists the applications this server knows about.

Confirm that the demo accounts are gone. Each of these must be refused:

# from the VM itself. The probe exits non-zero on a refusal, which is exactly
# what we want here, so this asserts that all three ARE refused. HOST="$(sudo grep -E '^PORTAL_URL=' /root/lemonldap-ng-credentials.txt | sed 's#.*https://##;s#/$##')" REFUSED=0 for u in dwho rtyler msmith; do OUT="$(sudo python3 /usr/local/sbin/llng-login-probe.py "$HOST" "$u" "$u" 2>/dev/null || true)" printf '%-8s -> %s\n' "$u" "$OUT" case "$OUT" in *LOGIN_REFUSED*)
REFUSED=$((REFUSED+1)) ;; esac done [ "$REFUSED" = 3 ] && echo "OK: all three demo accounts refused"

Each returns "verdict": "LOGIN_REFUSED".

4

Prove the single sign on actually works

A sign-in form that renders proves very little. The image ships a prover that drives two complete federation round trips and checks what comes back:

  • An OpenID Connect authorization code is exchanged for an ID token. The token's RS256 signature is verified against this machine's own published JWKS.
  • A CAS service ticket is validated back to a username.
C=/root/lemonldap-ng-credentials.txt
sudo python3 /usr/local/sbin/lemonldap-ng-sso-roundtrip.py \
  "$(sudo grep -E '^PORTAL_URL=' $C | sed 's#.*https://##;s#/$##')" \
  "$(sudo grep -E '^LLNG_ADMIN_USERNAME=' $C | cut -d= -f2-)" \
  "$(sudo grep -E '^LLNG_ADMIN_PASSWORD=' $C | cut -d= -f2-)" \
  "$(sudo grep -E '^SELFTEST_OIDC_CLIENT_ID=' $C | cut -d= -f2-)" \
  "$(sudo grep -E '^SELFTEST_OIDC_CLIENT_SECRET=' $C | cut -d= -f2-)"

You should see "id_token_signature_verifies_against_vm_jwks": true, then OIDC_SSO_ROUNDTRIP_OK and CAS_SSO_ROUNDTRIP_OK.

To be precise about what the check covers, OpenID Connect and CAS are each proven end to end. SAML is verified to the level of published metadata carrying this machine's own signing certificate. A full browser SAML round trip requires an external service provider to sign in against, so it is not part of this self-check. Register your own service provider (Step 5) to exercise it.

This server publishes these federation endpoints, each shown as a protocol and its endpoint:

OpenID Connect discovery: https://<host>/.well-known/openid-configuration

OpenID Connect JWKS: https://<host>/oauth2/jwks

SAML 2.0 metadata: https://<host>/saml/metadata

CAS: https://<host>/cas/login, https://<host>/cas/serviceValidate

5

Administer the server

The Manager is at https://<your-vm-ip>/manager/. It is restricted to members of the sac-admins group, and the ordinary demo user receives HTTP 403 there. It is where you register the applications that will trust this server.

To register an OpenID Connect application, open OpenID Connect Relying Parties and add one. Set its client ID, client secret and allowed redirect URIs. Register SAML service providers and CAS applications from their own branches in the same tree.

Remove the self test relying party

The image registers one OpenID Connect relying party and one CAS service called sac-selftest, so the server can prove itself on first boot. Both have callbacks on 127.0.0.1 and are not reachable from outside the machine. Once your own applications are registered, delete them:

  • Open OpenID Connect Relying Parties, select sac-selftest and remove it.
  • Do the same under CAS Applications.
  • Save the configuration.
6

Production hardening

Replace the self signed TLS certificate

sudo cp /path/to/fullchain.pem /etc/sac/tls/llng.crt
sudo cp /path/to/privkey.pem /etc/sac/tls/llng.key
sudo chmod 0600 /etc/sac/tls/llng.key
sudo nginx -t && sudo systemctl reload nginx

Point the server at your own directory

The bundled OpenLDAP directory is there so the machine is useful immediately. In production, you will almost always point LemonLDAP::NG at your existing directory instead.

In the Manager, open General Parameters > Authentication modules. Set the LDAP server, port, base DN and bind credentials to your own. You can then remove the demo user from the bundled directory, or disable the bundled directory entirely once nothing uses it.

Move to a real hostname

If you deployed against an IP address and later move to a DNS name, take these steps:

  • Change the portal URL in General Parameters > Portal.
  • Update the virtual host entry under Virtual Hosts.
  • Re-issue your TLS certificate for the new name.

The SAML entity ID and the OpenID Connect issuer change with the name, so you must update every registered application too. This is why pinning the name before first boot is worth the effort.

A note on cookies and addresses

If you reach this server by IP address, the session cookie is deliberately left as a host-only cookie. LemonLDAP::NG would otherwise try to set a cookie for a domain derived from the address. Browsers reject a domain cookie on an IP literal, so the sign-in would fail with no visible error. The same applies to a single-label hostname.

Once you move to a real multi-label DNS name, the cookie is scoped to your parent domain as normal. That is what makes single sign-on work across app1.example.org and app2.example.org.

7

Monitoring and patching

Unattended security upgrades are enabled, and they cover LemonLDAP::NG itself. The packages are not held. Instead, an apt preference pins the 2.21 LTS series. A 2.21.x security backport is installed automatically, and a 2.22 or 2.23 feature release is never selected:

cat /etc/apt/preferences.d/lemonldap-ng-lts.pref apt-cache policy lemonldap-ng
# Candidate should be 2.21.x

When you are ready to move to a newer feature series, remove that preference file and upgrade deliberately:

# Run this ONLY when you have decided to leave the 2.21 LTS series.
sudo rm /etc/apt/preferences.d/lemonldap-ng-lts.pref
sudo apt-get update && sudo apt-get install --only-upgrade lemonldap-ng

Useful checks:

# the portal is answering curl -sk -o /dev/null -w '%{http_code}\n' -H "Host: <your-host>" https://127.0.0.1/
# off-box listening ports are exactly 22 and 443
sudo /usr/local/sbin/lemonldap-ng-port-check.sh
# service logs
sudo journalctl -u lemonldap-ng-fastcgi-server -n 50 --no-pager

Troubleshooting

The portal returns HTTP 500 "Unable to protect this server". The message names the Perl class that failed to load. This is almost always a missing module after a manual package change. Reinstall the runtime dependencies the image ships with, then restart lemonldap-ng-fastcgi-server.

The sign-in form accepts the password but nothing happens. Your browser is not storing the session cookie. Check that the portal URL in the Manager matches the address you are using. See the note on cookies and addresses above.

The Manager returns 403 for a user you believe is an administrator. Manager access is granted to the sac-admins group. Add the user to cn=sac-admins,ou=groups,dc=sac,dc=local, or change the rule under Virtual Hosts.

Everything returns 403 after changing the hostname. The access rules are keyed on the virtual host. If the host you are reaching does not match a configured virtual host, the handler fails closed, which is intended. Update the virtual host entry in the Manager.

First boot did not finish. Run sudo journalctl -u lemonldap-ng-firstboot -n 80 --no-pager and sudo cat /var/log/sac/sac-firstboot.log to see where it stopped.

Support

SecureAnyCloud images are maintained and patched on a regular cycle. For image-specific questions, contact SecureAnyCloud support through the Azure Marketplace listing.

For questions about LemonLDAP::NG itself, the upstream project publishes documentation at lemonldap-ng.org. A local copy is also installed on this machine and is reachable from the Manager's help pane.

License

LemonLDAP::NG is free software licensed under the GNU General Public License, version 2 or later. The license text that governs the packages on this image is installed at /usr/share/doc/liblemonldap-ng-portal-perl/copyright. The full GPL text is at /usr/share/common-licenses/GPL-2.

SecureAnyCloud charges for the packaging, testing and maintenance of this image, not for LemonLDAP::NG, which remains free software you may obtain and use independently. SecureAnyCloud is not affiliated with, endorsed by, or sponsored by the LemonLDAP::NG project or the OW2 consortium.

Deploy on Azure

Launch LemonLDAP::NG 2.21 LTS 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