Files
nick 7967dbf9f0
Container / test (push) Successful in 38s
Container / docker (push) Failing after 37s
gitea actions
2026-09-18 20:08:29 -07:00

109 lines
4.0 KiB
Markdown

# 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`](../.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](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](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**
```bash
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**
```bash
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](gitea.md)):
```bash
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.