4.0 KiB
Setup
The portal runs on Linux (bare metal, VM, or container). It needs outbound HTTPS to the IdP, LDAPS to a domain controller, and HTTPS to AD CS Web Enrollment.
1. Configuration
Copy .env.example to .env. Every variable is commented there. Minimum:
SESSION_SECRET— long random stringPUBLIC_BASE_URL— the URL browsers use (the reverse-proxy origin)OIDC_*— issuer, client id/secret, redirecthttps://<portal>/auth/callbackLDAP_*—ldaps://URL, bind DN/password, base DNCERTSRV_*—https://ca.example.com/certsrv, NTLM user, template common namePROFILE_SIGNING_P12_PATH/PROFILE_SIGNING_P12_PASSWORD
Set SESSION_HTTPS_ONLY=true behind HTTPS. For a local http://127.0.0.1:8000 lab only, set it false and use matching OIDC_REDIRECT_URI.
2. OpenID Connect client
Register a confidential web app at your IdP:
- Redirect URI:
{PUBLIC_BASE_URL}/auth/callback - Scopes:
openid email profile(override withOIDC_SCOPES) - PKCE: S256 (the portal always sends a code challenge)
OIDC_ISSUER must be the discovery origin (no trailing slash). The portal loads {issuer}/.well-known/openid-configuration.
OIDC_USERNAME_CLAIM (default email) is what LDAP searches for. If your IdP puts the AD UPN in preferred_username or upn, set the claim accordingly and keep LDAP_USER_FILTER in sync.
3. LDAP
Use LDAPS (ldaps://dc:636) in production. The bind account only needs to read users (and, if LDAP_REQUIRED_GROUP is set, group membership).
Default filter:
(&(objectClass=user)(|(userPrincipalName={username})(mail={username})))
LDAP_REQUIRED_GROUP may be a full DN or a CN. Nested members are included (LDAP_MATCHING_RULE_IN_CHAIN).
If the DC uses a private CA, set LDAP_CA_CERTS_FILE to a PEM bundle. LDAP_VERIFY_TLS=false is lab-only.
4. AD CS Web Enrollment
See adcs.md. From the Linux host you should be able to reach CERTSRV_URL over HTTPS. The portal uses NTLM (CERTSRV_DOMAIN + CERTSRV_USERNAME). If CERTSRV_USERNAME is already DOMAIN\user or user@upn, the domain field is ignored.
Install the issuing CA TLS cert into CERTSRV_CA_CERTS_FILE (or the system trust store). Do not disable CERTSRV_VERIFY_TLS except in a lab.
5. Profile signing material
Place a PKCS#12 on the host (for Docker, bind-mount it) and point PROFILE_SIGNING_P12_PATH at it. See profile-signing.md.
Optional: PROFILE_CA_CERT_PATH PEM/DER of the issuing CA to embed so devices that do not already trust it still accept the user cert.
6. Reverse proxy / TLS
Terminate TLS at Caddy, nginx, or another proxy. Forward Host, X-Forwarded-For, and X-Forwarded-Proto. The app is started with proxy_headers=True.
Example Caddy:
certs.example.com {
reverse_proxy 127.0.0.1:8000
}
7. Run
Local
python3.12 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -e ".[dev]"
python -m cert_portal
Listens on HOST/PORT (default 0.0.0.0:8000).
Docker
docker build -t cert-portal .
docker run --rm -p 8000:8000 --env-file .env \
-v /etc/cert-portal:/etc/cert-portal:ro \
cert-portal
The image runs as UID 1000. The signing P12 path inside the container must match PROFILE_SIGNING_P12_PATH.
CI on this Gitea publishes git.atlashorizon.net/nick/cert-portal (see gitea.md):
docker login git.atlashorizon.net
docker pull git.atlashorizon.net/nick/cert-portal:latest
docker run --rm -p 8000:8000 --env-file .env \
-v /etc/cert-portal:/etc/cert-portal:ro \
git.atlashorizon.net/nick/cert-portal:latest
8. OpenAPI
/docs, /redoc, and /openapi.json are off when ENVIRONMENT=production unless you set DOCS_ENABLED=true. They are on in development. Do not expose Swagger to the internet without additional access control.
Health
GET /health returns {"status":"ok","version":"..."} and does not require a session. Point your load balancer at it.