A modern web interface for Dogtag PKI (upstream of Red Hat Certificate System), built with React 18, PatternFly 6, and Redux Toolkit.
- Dashboard with certificate summary cards, expiring-soon alerts, and quick actions
- Certificate management — browse, search by Subject DN or SAN, view details, revoke
- SPIRE SVID visibility — filter certs by spiffe:// URI SAN, cross-reference with SPIRE entries
- Code-signing dashboard — filter by code-signing EKU, Rekor transparency log cross-reference
- Trust chain visualization — interactive CA hierarchy view
- Enrollment — submit certificate signing requests via configurable profiles
- Request workflow — approve, reject, or cancel pending requests
- Profile management — view, clone, and edit certificate profiles (including custom svidCert and codeSigningCert profiles)
- Authorities — view sub-CA hierarchy
- Audit log viewer
- Role-based access control — three roles (Administrator, Agent, Auditor) with server-side enforcement
- Dual auth — username/password or client certificate (mTLS)
- Container-ready — multi-stage UBI 10 container build with nginx + Fastify backend
┌─────────────────────────────────────────────────┐
│ Browser │
│ React 18 + PatternFly 6 + Redux Toolkit │
└───────────────────┬─────────────────────────────┘
│ HTTPS + optional client cert (mTLS)
▼
┌─────────────────────────────────────────────────┐
│ nginx (TLS termination, mTLS handler) │
│ - ssl_verify_client optional_no_ca │
│ - X-SSL-Client-Cert/Verify/S-DN headers │
│ - Security headers (CSP, HSTS, etc.) │
└───────────────────┬─────────────────────────────┘
│ HTTP → localhost:3000
▼
┌─────────────────────────────────────────────────┐
│ Fastify Backend (unprivileged, no standing │
│ credentials) │
│ ┌─────────────┐ ┌──────────────────────────┐ │
│ │ Auth routes │ │ Per-user CA proxy │ │
│ │ - password │ │ - session relay │ │
│ │ - cert login │ │ - RBAC enforcement │ │
│ │ - sessions │ │ - Dogtag cookie mgmt │ │
│ └─────────────┘ └──────────────────────────┘ │
│ ┌─────────────┐ ┌──────────────────────────┐ │
│ │ Rekor proxy │ │ SPA static files │ │
│ │ (passthru) │ │ (dist/) │ │
│ └─────────────┘ └──────────────────────────┘ │
└───────────────────┬─────────────────────────────┘
│ per-user credentials
▼
┌─────────────────────────────────────────────────┐
│ Dogtag CA (port 8443) │
│ REST API: /ca/rest/* │
│ Authenticated as the actual user │
└─────────────────────────────────────────────────┘
Key design decisions:
- No standing credentials. The container runs with zero mounted agent or admin certs. Each user authenticates with their own credentials (password or client certificate), and the Fastify backend relays their Dogtag session. Dogtag's native RBAC governs what each user can do.
- Dual auth. Users can authenticate via username/password (forwarded to Dogtag via Basic auth) or via client certificate (mTLS at the nginx layer, cert forwarded to Dogtag for session establishment).
- Server-side RBAC. The backend checks the user's session roles against a URL pattern map before proxying to Dogtag. Client-side nav filtering is cosmetic only — the server enforces access.
- In-memory sessions. Per-user Dogtag sessions are stored in server memory with 30-minute TTL, 5-minute role re-validation, and automatic expiry sweep. No credentials are written to disk.
- Node.js >= 18
- A running Dogtag CA instance (default:
https://localhost:8443)
# Install dependencies
npm install
# (Optional) Copy and edit environment config
cp .env.example .env
# Start the dev server (frontend only, with Vite auth middleware)
npm run dev
# Or start the full stack (Fastify backend + Vite frontend)
npm run build:server
VITE_BACKEND_URL=http://localhost:3000 npm run dev &
CA_TARGET_URL=https://your-ca:8443 npm run dev:server
# Open http://localhost:5173
# Demo login: caadmin / Secret.123# Build
podman build -t dogtag-webui .
# HTTP only (password login only — no mTLS without TLS)
podman run -d -p 8080:8080 \
-e CA_TARGET_URL=https://ca.example.com:8443 \
dogtag-webui
# With TLS (password login + client certificate mTLS)
# Mount the server's TLS cert/key — these are NOT agent certs.
# nginx terminates TLS on 8443 and handles mTLS client cert negotiation.
# HTTP on 8080 redirects to HTTPS.
podman run -d -p 8080:8080 -p 8443:8443 \
-e CA_TARGET_URL=https://ca.example.com:8443 \
-v /path/to/tls.crt:/etc/nginx/certs/tls.crt:ro,z \
-v /path/to/tls.key:/etc/nginx/certs/tls.key:ro,z \
dogtag-webui
# With TLS + client CA verification (validates client certs at nginx).
# CLIENT_CA_CERT accepts a PEM bundle — concatenate multiple CA certs
# to trust clients from different root CAs.
podman run -d -p 8080:8080 -p 8443:8443 \
-e CA_TARGET_URL=https://ca.example.com:8443 \
-e CLIENT_CA_CERT=/etc/nginx/certs/client-ca-bundle.pem \
-v /path/to/tls.crt:/etc/nginx/certs/tls.crt:ro,z \
-v /path/to/tls.key:/etc/nginx/certs/tls.key:ro,z \
-v /path/to/client-ca-bundle.pem:/etc/nginx/certs/client-ca-bundle.pem:ro,z \
dogtag-webui
# With Rekor transparency log (add to any of the above)
podman run -d -p 8080:8080 -p 8443:8443 \
-e CA_TARGET_URL=https://ca.example.com:8443 \
-e REKOR_URL=http://rekor.example.com:3000 \
-v /path/to/tls.crt:/etc/nginx/certs/tls.crt:ro,z \
-v /path/to/tls.key:/etc/nginx/certs/tls.key:ro,z \
dogtag-webuiNote: Client certificate authentication (mTLS) requires TLS. The HTTP-only mode (port 8080, no TLS certs mounted) supports password login only. To enable mTLS, mount server TLS certs and expose port 8443.
The container uses a multi-stage build: UBI 10 with Node.js 24 for the build stage, UBI 10 with nginx + Node.js 24 for runtime. It runs as non-root (UID 1001). The entrypoint starts the Fastify backend, waits for its health check, then starts nginx as PID 1.
Full provisioning playbooks for 389 DS + Dogtag CA and the WebUI container are in a separate repo: ansible-dogtagpki.
| Command | Description |
|---|---|
npm run dev |
Start Vite dev server with API proxy and auth |
npm run dev:server |
Start Fastify backend in dev mode |
npm run build |
Type-check and build SPA for production |
npm run build:server |
Bundle Fastify backend with esbuild |
npm run start:server |
Run production Fastify backend |
npm run lint |
Run ESLint |
npm test |
Run all tests (128 tests across server + client) |
| Variable | Default | Description |
|---|---|---|
CA_TARGET_URL |
https://localhost:8443 |
Dogtag CA backend URL |
REKOR_URL |
(unset) | Rekor transparency log URL (enables /rekor/api/v1/ proxy) |
BACKEND_PORT |
3000 |
Fastify backend listen port |
CA_TLS_REJECT_UNAUTHORIZED |
false |
Set true for production (validates CA's TLS cert) |
CA_BUNDLE |
(optional) | Path to CA chain PEM for verifying Dogtag's TLS cert |
CLIENT_CA_CERT |
(optional) | PEM bundle of CA certs trusted for client authentication (supports multiple CAs) |
LDAP_URL |
(unset = Dogtag-only auth) | LDAP server URL for fallback auth |
LDAP_BASE_DN |
o=pki-tomcat-CA |
LDAP base DN |
LDAP_BIND_DN |
(optional) | DN for LDAP search bind |
LDAP_BIND_PASSWORD |
(optional) | Password for LDAP search bind |
LDAP_USER_SEARCH_BASE |
ou=people,{baseDn} |
Base DN for user lookups |
LDAP_GROUP_SEARCH_BASE |
ou=groups,{baseDn} |
Base DN for group lookups |
LDAP_TLS_REJECT_UNAUTHORIZED |
true |
Set false for self-signed DS certs |
LDAP_TLS_CA_CERT |
(optional) | Path to CA cert for verifying DS server cert |
LDAP_STARTTLS |
false |
Use STARTTLS on plain LDAP port (389) |
| Variable | Default | Description |
|---|---|---|
VITE_CA_TARGET_URL |
https://localhost:8443 |
Dogtag CA backend URL (dev proxy mode) |
VITE_CA_CERT_PATH |
certs/agent.cert |
Client cert for dev proxy (legacy mode) |
VITE_CA_KEY_PATH |
certs/agent.key |
Client key for dev proxy (legacy mode) |
VITE_BACKEND_URL |
(unset) | If set, Vite proxies to Fastify backend instead of direct to CA |
VITE_DEV_HOST |
localhost |
Dev server bind address |
VITE_LDAP_URL |
(unset = demo mode) | LDAP URL for Vite auth middleware |
The production backend authenticates users in two ways:
Password login:
- User submits username/password
- Backend authenticates to Dogtag via Basic auth (
/ca/rest/account/login) - Dogtag returns a session cookie (JSESSIONID) and account info (roles)
- Backend stores the Dogtag session, returns a WebUI session cookie
- Subsequent API calls use the stored Dogtag session
Client certificate login (mTLS):
- User's browser presents a client cert during TLS handshake
- nginx passes the cert PEM via
X-SSL-Client-Certheader - Login page detects the cert and shows "Log in with certificate"
- Backend uses the cert to establish a Dogtag session
- Same session relay as password flow
Both flows result in per-user Dogtag sessions — the backend holds no standing credentials.
When LDAP_URL is set, the backend falls back to LDAP validation if Dogtag Basic auth fails. Three connection modes are supported:
| Mode | URL | Extra Config |
|---|---|---|
| Plain LDAP | ldap://host:389 |
(not recommended for production) |
| LDAPS | ldaps://host:636 |
Set LDAP_TLS_CA_CERT if DS uses internal CA |
| STARTTLS | ldap://host:389 |
Set LDAP_STARTTLS=true |
| Page | Administrator | Agent | Auditor |
|---|---|---|---|
| Dashboard | Y | Y | Y |
| Certificates | Y | Y | Y |
| Authorities | Y | Y | Y |
| SPIRE SVIDs | Y | Y | Y |
| Code Signing | Y | Y | Y |
| Trust Chain | Y | Y | Y |
| Enroll | Y | Y | - |
| Requests | Y | Y | - |
| Profiles | Y | Y | - |
| Create Profile | Y | - | - |
| Users / Groups | Y | - | - |
| Audit Log | Y | - | Y |
Demo accounts for development (Vite auth middleware only):
| Username | Password | Roles |
|---|---|---|
caadmin |
Secret.123 |
Administrator, Agent |
agent1 |
agent123 |
Agent |
auditor1 |
auditor123 |
Auditor |
src/
app/ App shell (PatternFly Page layout, masthead, logout)
auth/ Role definitions and helpers
components/ Shared UI (ProtectedRoute, RekorVerificationBadge)
navigation/ Route definitions, sidebar nav, role-based filtering
pages/ Page components (Certificates, SpireSvids, CodeSigning, TrustChain, ...)
services/ RTK Query API definitions (Dogtag REST, Rekor)
store/ Redux store, auth slice, typed hooks
utils/ Certificate parsing helpers
server/
app.ts Fastify application (auth routes, CA proxy, Rekor proxy, static files)
index.ts Server entry point
sessionStore.ts In-memory per-user session store
dogtagAuth.ts Dogtag authentication (basic auth + client cert)
caProxy.ts Per-user CA REST API proxy
authMiddleware.ts Auth plugin for Vite dev server (sessions, RBAC, rate limiting)
ldapBackend.ts LDAP authentication backend
nginx/
container.conf Container nginx config (TLS termination, mTLS, security headers)
docker-entrypoint.sh Starts Fastify backend + nginx
scripts/
create-test-certs.sh Enroll test SVID + code-signing certs
demo-walkthrough.yml Demo-recorder YAML for CLI+WebUI walkthrough
SCREENSHOT-LIST.md Screenshot capture checklist
docs/
design-*.md Integration design documents (SPIRE, Fulcio, Rekor)
certs/ Client certs for dev proxy (not tracked in git)
See SECURITY.md for the full security audit results.
Key security features:
- Per-user authentication relay (no standing credentials in container)
- Dual auth: password + client certificate (mTLS)
- In-memory session store with 30-minute TTL and automatic expiry sweep
- Periodic role re-validation (every 5 minutes via Dogtag session check)
- Login rate limiting (5 attempts per IP per 15 minutes)
- API write rate limiting (30 requests per minute per session)
- Structured audit logging for all auth events (JSON to stdout)
- CSRF protection via SameSite=Strict cookies
- Server-side RBAC enforcement on all
/ca/rest/routes - Configurable TLS validation on backend-to-CA connections (
CA_TLS_REJECT_UNAUTHORIZED,CA_BUNDLE) - Optional client CA verification at nginx layer (
CLIENT_CA_CERT) - XFF spoofing protection (nginx overwrites, Fastify trusts only 127.0.0.1)
- Clear-Site-Data header on logout
- Security headers (CSP, HSTS, X-Frame-Options, etc.)
- Error message sanitization (no stack traces, 200-char cap)
- Non-root container runtime (UID 1001)
GPL-3.0-or-later