Files
cert-portal/docs/setup.md
T
2026-09-18 19:45:17 -07:00

3.6 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 string
  • PUBLIC_BASE_URL — the URL browsers use (the reverse-proxy origin)
  • OIDC_* — issuer, client id/secret, redirect https://<portal>/auth/callback
  • LDAP_* — ldaps:// URL, bind DN/password, base DN
  • CERTSRV_* — https://ca.example.com/certsrv, NTLM user, template common name
  • PROFILE_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 with OIDC_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.

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.