The Hyper-V lab has a domain controller, an enterprise CA, an NDES server, and a file server with some RDP hosting. What it didn’t have was a web application, just something simple hosted on-prem so that it could be published effectively and securely. And not just a placeholder page or a test IIS site either, but something with real logon and authentication, maybe even real TLS, and a reason to exist beyond “verify that port 443 works.”
I googled a bit (I still find that useful..), and found a web app that might fit: BookStack. It’s a self-hosted wiki built on Laravel, lightweight enough to run on a minimal Ubuntu VM, and it supports both LDAP and OIDC authentication. The plan: install it, get LDAP running, maybe even wire it up to Entra ID for SSO, and use it as the target application when testing Global Secure Access Private Access in a later post. The wiki itself earns its keep as lab documentation.
This post covers the VM, the installation, a brief detour through LDAP, TLS with the enterprise CA, and Entra ID OIDC configuration. Part 2 will cover publishing BookStack through GSA Private Access so it’s reachable from Entra-joined devices outside the lab network.
The VM
HAWKWEAVE-BOOKSTACK is a Generation 2 Hyper-V VM running Ubuntu 24.04 LTS Server. The specs are deliberately minimal: BookStack with Apache, MySQL, and PHP doesn’t need much.
| Property | Value |
|---|---|
| Hyper-V name | HAWKWEAVE-BOOKSTACK |
| Hostname | hw-bookstack |
| IP | 10.50.50.60 (static) |
| OS | Ubuntu 24.04 LTS Server |
| Generation | 2 |
| vCPU | 2 |
| Memory | 1 GB startup, dynamic (512 MB min, 2 GB max) |
| Disk | 32 GB |
| Network | LabNet-Lan (Private switch) |
New-VM -Name "HAWKWEAVE-BOOKSTACK" -Generation 2 -MemoryStartupBytes 1GB `
-NewVHDPath "C:\Hyper-V\VHDs\HAWKWEAVE-BOOKSTACK.vhdx" -NewVHDSizeBytes 32GB `
-SwitchName "LabNet-Lan"
Set-VM -Name "HAWKWEAVE-BOOKSTACK" -ProcessorCount 2 -DynamicMemory `
-MemoryMinimumBytes 512MB -MemoryMaximumBytes 2GB
Set-VMFirmware -VMName "HAWKWEAVE-BOOKSTACK" `
-SecureBootTemplate "MicrosoftUEFICertificateAuthority"
That last line is important because Linux Gen 2 VMs on Hyper-V require the “Microsoft UEFI Certificate Authority” Secure Boot template. The default Windows template won’t boot Linux very well.
Ubuntu installed with a static IP configuration (10.50.50.60/24, gateway 10.50.50.1, DNS pointing to HW-DC01 at 10.50.50.10). No snaps, and no extras, just a plain ol’ vanilla Ubuntu server – pretty lean!
Installing BookStack
BookStack provides an official install script for Ubuntu 24.04 that handles the full stack: Apache 2, MySQL 8, and PHP 8.3.
https://raw.githubusercontent.com/BookStackApp/devops/main/scripts/installation-ubuntu-24.04.sh
chmod +x installation-ubuntu-24.04.sh
sudo ./installation-ubuntu-24.04.sh
The script asks for the domain. At this point TLS wasn’t configured yet, so the initial entry was http://hw-bookstack.core.hawkweave.com. We’ll fix this later on.
A DNS A record on HW-DC01 makes the name resolvable on the lab network:
Add-DnsServerResourceRecordA -ZoneName "core.hawkweave.com" `
-Name "hw-bookstack" -IPv4Address "10.50.50.60"
After the script finishes, BookStack’s default login page is accessible at http://hw-bookstack.core.hawkweave.com. Default credentials are admin@admin.com / password. I suggest you change these immediately, even in a lab.
The LDAP Detour
The initial plan was LDAP authentication against the domain controller. The configuration in /var/www/bookstack/.env:
AUTH_METHOD=ldap
LDAP_SERVER=hw-dc01.core.hawkweave.com
LDAP_BASE_DN=OU=Users,OU=Hawkweave,DC=core,DC=hawkweave,DC=com
LDAP_DN=CN=svc_bookstack,OU=Service Accounts,OU=Hawkweave,DC=core,DC=hawkweave,DC=com
LDAP_PASS="<redacted>"
LDAP_USER_FILTER=(&(sAMAccountName=${user}))
LDAP_VERSION=3
LDAP_ID_ATTRIBUTE=objectGUID
LDAP_EMAIL_ATTRIBUTE=mail
LDAP_DISPLAY_NAME_ATTRIBUTE=cn
A few things came up during configuration worth noting.
BookStack’s default LDAP user filter uses uid, which doesn’t exist in Active Directory. Changing it to sAMAccountName is required for AD environments. The bind account password contained a !, and an unquoted exclamation mark in a .env file crashes the parser. Wrapping the value in double quotes fixed it.
The initial bind account was hwoperator (a regular domain user). Switching to a dedicated svc_bookstack service account in the Service Accounts OU is cleaner practice, even in a lab. The account only needs read access to the Users OU, which any domain user has by default.
LDAP worked. Users could type their AD username and password, BookStack performed a bind, and they were in. But that’s all it was: a username and password form. No SSO, no passkeys, no integration with the identity platform that everything else in the lab is built around. For a deployment that’s ultimately going to demonstrate Global Secure Access with Entra ID authentication, OIDC tells a much better story.
TLS with the Enterprise CA
Entra ID app registrations refuse redirect URIs that aren’t HTTPS (the only exception is http://localhost). So before OIDC could be configured, BookStack needed a certificate.
Generating the CSR
The first attempt produced a CSR with only a Common Name and no Subject Alternative Name. Modern browsers ignore the CN field entirely and only check SAN. The cert worked for Apache but every browser showed a name mismatch error.
The corrected CSR:
sudo openssl req -new -newkey rsa:2048 -nodes \
-keyout /etc/ssl/private/bookstack.key \
-out /tmp/bookstack.csr \
-subj "/CN=hw-bookstack.core.hawkweave.com" \
-addext "subjectAltName=DNS:hw-bookstack.core.hawkweave.com"
CA Prerequisite: Accepting SANs from CSRs
By default, the WebServer template on an ADCS Enterprise CA strips SANs from incoming CSRs. The CA needs the EDITF_ATTRIBUTESUBJECTALTNAME2 flag enabled to honor them. In this lab, the flag was already set from earlier NDES and SCEP configuration on HW-CA01. If your CA hasn’t had this enabled, it’s a one-time change:
certutil -setreg policy\EditFlags +EDITF_ATTRIBUTESUBJECTALTNAME2
Restart-Service CertSvc
You can check the current state with certutil -getreg policy\EditFlags. The command is idempotent, so running it again on a CA that already has the flag set changes nothing.
Submitting and Retrieving the Certificate
certreq -submit `
-attrib "CertificateTemplate:WebServer`nSAN:dns=hw-bookstack.core.hawkweave.com" `
C:\temp\bookstack.csr C:\temp\bookstack.cer
The root CA certificate is also needed for the chain:
certutil -ca.cert C:\temp\rootca.cer
Both files transferred to the Ubuntu box via SCP:
scp C:\temp\bookstack.cer haakon@10.50.50.60:/tmp/bookstack.cer
scp C:\temp\rootca.cer haakon@10.50.50.60:/tmp/rootca.cer
Then placed in the appropriate directories:
sudo cp /tmp/bookstack.cer /etc/ssl/certs/bookstack.cer
sudo cp /tmp/rootca.cer /etc/ssl/certs/rootca.cer
Apache SSL Configuration
Enable the SSL module and default SSL site:
sudo a2enmod ssl
sudo a2ensite default-ssl
The key settings in /etc/apache2/sites-available/default-ssl.conf:
ServerName hw-bookstack.core.hawkweave.com
DocumentRoot /var/www/bookstack/public
SSLCertificateFile /etc/ssl/certs/bookstack.cer
SSLCertificateKeyFile /etc/ssl/private/bookstack.key
SSLCertificateChainFile /etc/ssl/certs/rootca.cer
One critical step: the <Directory /var/www/bookstack/public/> block with all the rewrite rules from the port 80 vhost must be copied into the SSL vhost. Without it, BookStack’s Laravel routing breaks on HTTPS. Every URL except the root returns a 404 or a blank page.
HTTP-to-HTTPS Redirect
Replace the port 80 vhost entirely:
<VirtualHost *:80>
ServerName hw-bookstack.core.hawkweave.com
Redirect permanent / https://hw-bookstack.core.hawkweave.com/
</VirtualHost>
The APP_URL Error
After enabling HTTPS, BookStack loaded but the browser showed “connection not fully secure.” The page was served over HTTPS, but CSS and JavaScript were still being loaded over HTTP.
I had just forgotten to update the APP_URL in /var/www/bookstack/.env from http:// to https://. BookStack uses this value to generate asset URLs. A mismatched protocol here means mixed content of course.
TLS Troubleshooting Summary
The full sequence of issues encountered, in order:
- CSR had no SAN (CN only). Browser showed name mismatch. Regenerated with
-addext. - After regenerating the cert, forgot to copy it into
/etc/ssl/certs/. Apache was still using the old one. Then forgotsudoon the copy. Fix:sudo cp /tmp/bookstack.cer /etc/ssl/certs/bookstack.cer. - Forgot to update
APP_URL. Mixed content warnings.
Threeissues, none of them very hard, just time-consuming when you don’t know what to look for. And I just ain’t that well versed in PKI on Ubuntu.
Entra ID OIDC Authentication
With TLS in place, BookStack can be configured for OpenID Connect against Entra ID.
App Registration
Created in the Entra admin center:
| Property | Value |
|---|---|
| Name | BookStack |
| Supported account types | Single tenant |
| Redirect URI | https://hw-bookstack.core.hawkweave.com/oidc/callback |
Under API permissions, add delegated permissions for Microsoft Graph: openid, profile, email. Grant admin consent for the tenant.
Under Token configuration, add optional claims on the ID token: given_name and family_name. Accept the prompt to add the profile scope when it asks.
Create a client secret under Certificates & secrets. Copy the value immediately; it’s only displayed once.
BookStack Configuration
The OIDC settings in /var/www/bookstack/.env:
AUTH_METHOD=oidc
OIDC_NAME="Entra ID"
OIDC_CLIENT_ID=<application-client-id>
OIDC_CLIENT_SECRET="<client-secret>"
OIDC_ISSUER=https://login.microsoftonline.com/<tenant-id>/v2.0
OIDC_ISSUER_DISCOVER=true
OIDC_DISPLAY_NAME_CLAIMS=given_name|family_name
OIDC_END_SESSION_ENDPOINT=true
AUTH_AUTO_INITIATE=false
OIDC_ISSUER_DISCOVER=true means BookStack fetches the OpenID Connect metadata automatically from <issuer>/.well-known/openid-configuration. No need to manually specify authorization, token, or userinfo endpoints.
Set AUTH_AUTO_INITIATE=false initially. This keeps the BookStack login page visible with a “Login with Entra ID” button, which is useful for troubleshooting. Flip it to true after everything works to skip the login page entirely and redirect straight to Microsoft.
The Gotchas
Unquoted values with spaces crash the app. Setting OIDC_NAME=Entra ID (without quotes) produces a blank 500 error in the browser. The .env parser chokes on the whitespace. The actual error message is Failed to parse dotenv file. Encountered unexpected whitespace at [Entra ID], but you only see it if you run php artisan cache:clear from the command line. The web interface gives you nothing. Always quote values that contain spaces.
Laravel’s config cache is aggressive. After switching AUTH_METHOD from ldap to oidc, BookStack kept trying to perform an LDAP bind. The old configuration was cached. A full cache clear is required:
cd /var/www/bookstack
sudo php artisan config:clear
sudo php artisan cache:clear
sudo php artisan route:clear
sudo php artisan view:clear
sudo systemctl restart apache2
In stubborn cases, also delete the compiled cache files:
sudo rm /var/www/bookstack/bootstrap/cache/*.php
This is a pattern with Laravel applications. If you change something in .env and the app doesn’t seem to notice, clear the cache before looking for other causes.
Existing LDAP users don’t auto-link to OIDC. The External Authentication ID stored for LDAP users doesn’t match the OIDC sub claim. If you’ve been testing with LDAP accounts, the cleanest fix is to delete those users in BookStack and let OIDC auto-register them on first login.
The Result
Hit https://hw-bookstack.core.hawkweave.com, click “Login with Entra ID,” authenticate with a Microsoft account. BookStack auto-creates the user on first login using the given_name and family_name claims from the ID token. Passkeys registered in Entra ID work here too: no password prompt, just a biometric or security key tap.
The authentication flow is entirely Entra ID’s. BookStack doesn’t see credentials. The Primary Refresh Token on the Entra-joined device means that if you’ve already signed in to Windows with Windows Hello, you may blow right past the Microsoft login screen entirely.
At this point the wiki is functional on the lab network with enterprise TLS and Entra ID SSO. In Part 2, I’ll cover publishing it through Global Secure Access Private Access so it’s reachable from outside the lab, completing the stack: GSA for transport, enterprise CA for TLS, Entra ID for authentication.
What’s your experience with self-hosted apps in lab environments? I’m curious whether others are using BookStack (or similar wikis) as GSA test targets, or if most people just point GSA at file shares and call it done.
