The bundled nginx met about half of the benchmark's Level 1 items: it answered for any Host, kept the 60-65 s default timeouts, allowed every method and 100 MB bodies everywhere, served config files 644 and sent no nosniff, Referrer-Policy or CSP on Next.js pages. Now nginx answers only for the names on the certificate (TLS_SERVER_NAMES, the DASHBOARD_URL host, localhost, 127.0.0.1, built by a new .envsh) and closes the connection for any other Host. No ssl_reject_handshake, since browsers send no SNI for an IP. TLS 1.3 only, with session tickets back on (4.1.11) and HSTS at two years. Timeouts are 10 s, methods are limited to what frontend and API use, bodies are 1 MB except 100 MB under /api/. /etc/nginx is 750/640, there is an error page without nginx branding, dot paths return 404 and X-Powered-By is hidden. The CSP is added only where the upstream sent none, so backend responses keep theirs. Clients without TLS 1.3 and Host names outside the list stop working. Both cases are documented in README and TROUBLESHOOTING. README.DEV.md maps every recommendation, including the Level 2 items not taken and why. Verified on the built image with nginx -t, the benchmark's curl audits and the login page under the CSP in a browser.
262 lines
12 KiB
Bash
262 lines
12 KiB
Bash
# =============================================================================
|
|
# TrueVuln Environment Configuration
|
|
# =============================================================================
|
|
# Copy this file to .env and adjust values before starting:
|
|
# cp .env.example .env
|
|
#
|
|
# HTTPS out of the box: the bundled nginx serves the UI with a certificate
|
|
# from a CA created for this installation (or one you upload in Settings >
|
|
# Security). An existing reverse proxy can sit in front and connect via HTTPS.
|
|
# =============================================================================
|
|
|
|
# --- Database ---
|
|
POSTGRES_USER=vulnmanager
|
|
POSTGRES_PASSWORD=changeme # Change this!
|
|
POSTGRES_DB=vulnmanager
|
|
POSTGRES_PORT=5432
|
|
|
|
# --- Application Ports ---
|
|
# Plain HTTP, bound to 127.0.0.1 only. Users connect to nginx (HTTPS below).
|
|
# Your own reverse proxy on this host instead: installed on the host, it uses
|
|
# 127.0.0.1:${FRONTEND_PORT}; running as a Docker container (Nginx Proxy
|
|
# Manager, OpenResty, ...), it joins <project>_vulnmanager-net and uses
|
|
# vulnmanager-frontend:3000 (the container port, not FRONTEND_PORT).
|
|
BACKEND_PORT=8022
|
|
FRONTEND_PORT=3003
|
|
|
|
# --- HTTPS (bundled nginx) ---
|
|
# The "tls" profile starts nginx. Remove it only when your own reverse proxy
|
|
# runs on this host (see Application Ports above). A proxy on
|
|
# another host connects to https://<this host>:${HTTPS_PORT} and trusts the
|
|
# TrueVuln CA (download in Settings > Security).
|
|
COMPOSE_PROFILES=tls
|
|
HTTPS_PORT=443
|
|
HTTP_PORT=80 # redirects to HTTPS only
|
|
# Names and IPs users type to reach TrueVuln, comma-separated. They go into
|
|
# the auto-generated certificate together with the DASHBOARD_URL host and
|
|
# localhost. The CA may only issue for these names; adding one later creates
|
|
# a new CA that has to be distributed again. nginx answers only for these
|
|
# names; any other Host gets the connection closed.
|
|
# e.g. TLS_SERVER_NAMES=truevuln.corp.example,10.0.0.5
|
|
TLS_SERVER_NAMES=
|
|
|
|
# --- Security (REQUIRED) ---
|
|
# Generate with: openssl rand -hex 32
|
|
JWT_SECRET_KEY=CHANGE-ME-GENERATE-WITH-openssl-rand-hex-32
|
|
|
|
# --- Environment ---
|
|
# 'production' disables Swagger docs and enables security headers.
|
|
# 'development' enables Swagger UI at /docs and relaxes some checks.
|
|
ENV=production
|
|
# Expose Swagger UI (/docs) + ReDoc (/redoc) on a production instance without
|
|
# switching to development — handy for ITSM/CMDB API integrators. Default off.
|
|
# The raw OpenAPI spec (/openapi.json) is always served regardless.
|
|
ENABLE_API_DOCS=false
|
|
|
|
# --- Cookies ---
|
|
# true: the session cookie never travels over plain HTTP. Keep it with the
|
|
# bundled nginx or any HTTPS proxy.
|
|
AUTH_COOKIE_SECURE=true
|
|
AUTH_COOKIE_SAMESITE=lax
|
|
|
|
# --- Reverse Proxy ---
|
|
# Trust X-Forwarded-For so audit log and login rate limit see the client IP,
|
|
# not the frontend container's. Trusted peers are loopback and the backend's
|
|
# own Docker subnet (found automatically). A reverse proxy on ANOTHER host in
|
|
# front of nginx goes into FORWARDED_ALLOW_IPS (IPs or CIDRs, added to that).
|
|
# Your own proxy must set X-Forwarded-For with $proxy_add_x_forwarded_for.
|
|
TRUST_PROXY_HEADERS=true
|
|
# FORWARDED_ALLOW_IPS=10.0.0.20
|
|
|
|
# --- Default Admin Account ---
|
|
# Created on first start only (if no admin exists in the database).
|
|
# The password MUST satisfy strength rules (>=8 chars, upper, lower, digit, special),
|
|
# otherwise the admin will NOT be created and login will be impossible.
|
|
# Change the password immediately after first login.
|
|
DEFAULT_ADMIN_USERNAME=admin
|
|
DEFAULT_ADMIN_PASSWORD=ChangeMe123!
|
|
DEFAULT_ADMIN_EMAIL=admin@vulnmanager.local
|
|
|
|
# Set to true to skip default admin creation entirely
|
|
# DISABLE_DEFAULT_ADMIN=false
|
|
|
|
# --- Emergency Admin Reset (optional) ---
|
|
# Uncomment and set a token to enable the /auth/setup-admin endpoint.
|
|
# SETUP_ADMIN_TOKEN=your-setup-token
|
|
# ALLOW_ADMIN_RESET=false
|
|
|
|
# --- Timezone ---
|
|
TIMEZONE=Europe/Zurich
|
|
|
|
# --- Dashboard URL ---
|
|
# Base URL used to build ALL links in email notifications (dashboard button,
|
|
# per-CVE / per-asset deep links, the CVE links inside the digest table).
|
|
# The backend can't know how your browser reaches the frontend, so you MUST set
|
|
# this to exactly how users open TrueVuln — otherwise links point at the
|
|
# fallback http://localhost:3000 and won't work.
|
|
# Its host also goes into the HTTPS certificate, and an uploaded certificate
|
|
# must cover it.
|
|
# e.g. https://truevuln.corp.example or https://10.0.0.5
|
|
# Set it, then restart the backend (docker compose up -d).
|
|
DASHBOARD_URL=https://localhost
|
|
|
|
# --- Microsoft Intune / Graph (optional) ---
|
|
# Configured in the UI: Settings → "Microsoft Intune (Graph API)" card
|
|
# (stored encrypted as the `intune_config` setting), NOT via env. Pulls
|
|
# Intune managed devices → assets + OS-EOL.
|
|
# App-only (client-credentials): register an Entra app, add the Application
|
|
# permission DeviceManagementManagedDevices.Read.All, grant admin consent,
|
|
# create a client secret. (Defender TVM phase: BOTH Machine.Read.All AND
|
|
# Vulnerability.Read.All on WindowsDefenderATP — the /api/machines call
|
|
# requires Machine.Read.All.) No env vars required.
|
|
|
|
# --- NVD API key (optional, recommended) ---
|
|
# Used to backfill each CVE's official published / lastModified date from
|
|
# the NVD CVE API (drives the "Newly Published" dashboard widget sort).
|
|
# Without a key NVD allows 5 requests / 30s; with one, 50 / 30s — so a
|
|
# fresh database fills in published dates ~10x faster. Free, request at:
|
|
# https://nvd.nist.gov/developers/request-an-api-key
|
|
# NVD_API_KEY=your-nvd-api-key
|
|
|
|
# --- VulnCheck KEV (optional) ---
|
|
# Third "actively exploited" catalog next to CISA KEV and ENISA EUVD. Carries
|
|
# ~5200 CVEs against CISA's ~1400 and usually lists them earlier; the KEV panel
|
|
# shows the lead time per CVE. Free community account -> API token at
|
|
# https://console.vulncheck.com/
|
|
# Free for commercial use as long as "VulnCheck KEV" stays attributed in the
|
|
# UI (the Advisories page does that automatically).
|
|
# Without a token the source stays silent and nothing changes.
|
|
# VULNCHECK_API_TOKEN=vulncheck_...
|
|
#
|
|
# To also alert on it, add "vulncheck" to the kev_alert_sources setting
|
|
# (default "cisa,euvd" — opting in is deliberate, it is ~4x the CVEs).
|
|
|
|
# --- OpenRouter AI remediation (optional) ---
|
|
# Enables the "AI Remediation" button on the CVE detail page — generates
|
|
# OS-aware fix steps/commands on demand. OpenAI-compatible; get a key at
|
|
# https://openrouter.ai/keys (free tier ~10 req/day across free models).
|
|
# Without a key the feature stays hidden.
|
|
# OPENROUTER_API_KEY=sk-or-...
|
|
# Model slug (OpenRouter uses dots, e.g. anthropic/claude-sonnet-4.6).
|
|
# Default openrouter/free auto-routes across available free models.
|
|
# OPENROUTER_MODEL=openrouter/free
|
|
# Optional comma-separated fallback models (route=fallback):
|
|
# OPENROUTER_FALLBACKS=openrouter/auto,anthropic/claude-sonnet-4.6
|
|
|
|
# --- Jev triage hint (optional, off by default) ---
|
|
# Nightly 06:00: asks TypeSafe Jev per open CVE whether it only applies with a
|
|
# non-default configuration; shown on the Patch Plan. Needs a key: a TypeSafe
|
|
# key goes straight to api.typesafe.ai, else OPENROUTER_API_KEY above is used.
|
|
# JEV_TRIAGE_ENABLED=true
|
|
# TYPESAFE_API_KEY=...
|
|
# Model pinned per endpoint: jev-1.13.0 (TypeSafe) / typesafe/jev-1.13 (OpenRouter).
|
|
|
|
|
|
# =============================================================================
|
|
# Multi-Provider Authentication (LDAP / OIDC / SAML / TOTP-MFA)
|
|
# =============================================================================
|
|
# Default config = local only (backwards compatible). Enable providers
|
|
# below by listing them in AUTH_PROVIDERS and filling in the matching block.
|
|
# =============================================================================
|
|
|
|
# Comma-separated list of enabled providers. Local is always recommended
|
|
# as fallback for emergency admin access.
|
|
AUTH_PROVIDERS=local
|
|
# AUTH_PROVIDERS=local,ldap,oidc,saml
|
|
|
|
# Order in which credential-based providers are tried for username/password
|
|
# login. SSO providers (saml/oidc) are not in this chain — they have their
|
|
# own redirect endpoints.
|
|
AUTH_LOOKUP_ORDER=local,ldap
|
|
|
|
# Auto-create local user stub on first SSO/LDAP login. Re-evaluates role
|
|
# from external groups on every subsequent login.
|
|
AUTH_JIT_PROVISIONING=true
|
|
|
|
# Fallback role when no group-mapping rule matches.
|
|
# Values: admin, editor, readonly
|
|
AUTH_JIT_DEFAULT_ROLE=readonly
|
|
|
|
# Fernet key for encrypting LDAP bind-pw and TOTP secrets at rest.
|
|
# Generate with:
|
|
# python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
|
|
# CHANGE THIS — losing the key invalidates all stored TOTP secrets.
|
|
AUTH_PROVIDER_CRYPTO_KEY=CHANGE-ME-FERNET-KEY
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# LDAPS (Active Directory by default)
|
|
# -----------------------------------------------------------------------------
|
|
LDAP_HOST=ldaps.company.internal
|
|
LDAP_PORT=636
|
|
LDAP_USE_SSL=true
|
|
LDAP_USE_STARTTLS=false # mutually exclusive with USE_SSL
|
|
# CA bundle that signed the LDAPS server cert (required for strict validation).
|
|
LDAP_CA_CERT_PATH=/etc/ssl/certs/company-ca.pem
|
|
LDAP_VALIDATE_CERT=true # NEVER set to false in production
|
|
|
|
# Service account for the search-then-bind flow.
|
|
LDAP_BIND_DN=cn=svc-truevuln,ou=ServiceAccounts,dc=company,dc=local
|
|
# Bootstrap password — loaded once, encrypted, stored in DB.
|
|
# Remove from env AFTER the first successful start.
|
|
LDAP_BIND_PASSWORD_BOOTSTRAP=
|
|
|
|
# User search
|
|
LDAP_USER_SEARCH_BASE=ou=Users,dc=company,dc=local
|
|
# {username} is replaced by the login form input (filter-escaped).
|
|
LDAP_USER_SEARCH_FILTER=(&(objectClass=user)(sAMAccountName={username}))
|
|
LDAP_USER_ATTR_USERNAME=sAMAccountName
|
|
LDAP_USER_ATTR_EMAIL=mail
|
|
LDAP_USER_ATTR_GROUPS=memberOf
|
|
LDAP_USER_ATTR_GUID=objectGUID # AD stable identifier
|
|
LDAP_USER_ATTR_DISPLAY=displayName
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# OIDC (OpenID Connect) — Entra ID / Okta / Keycloak / Google
|
|
# -----------------------------------------------------------------------------
|
|
OIDC_PROVIDER_NAME=Entra ID
|
|
# Examples:
|
|
# Entra ID: https://login.microsoftonline.com/<tenant-id>/v2.0/.well-known/openid-configuration
|
|
# Okta: https://<your-okta-domain>/.well-known/openid-configuration
|
|
# Keycloak: https://kc.company.com/realms/<realm>/.well-known/openid-configuration
|
|
# Google: https://accounts.google.com/.well-known/openid-configuration
|
|
OIDC_DISCOVERY_URL=
|
|
OIDC_CLIENT_ID=
|
|
OIDC_CLIENT_SECRET=
|
|
OIDC_REDIRECT_URI=https://truevuln.company.com/auth/oidc/callback
|
|
OIDC_SCOPES=openid profile email groups
|
|
|
|
# Claim names — defaults work for Entra ID / Keycloak. Adjust per IdP.
|
|
OIDC_CLAIM_USERNAME=preferred_username
|
|
OIDC_CLAIM_EMAIL=email
|
|
OIDC_CLAIM_GROUPS=groups
|
|
OIDC_CLAIM_DISPLAY=name
|
|
OIDC_CLAIM_SUBJECT=sub
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# SAML 2.0
|
|
# -----------------------------------------------------------------------------
|
|
SAML_PROVIDER_NAME=Single Sign-On
|
|
SAML_SP_ENTITY_ID=https://truevuln.company.com/auth/saml/metadata
|
|
SAML_SP_ACS_URL=https://truevuln.company.com/auth/saml/acs
|
|
SAML_SP_SLO_URL=https://truevuln.company.com/auth/saml/slo
|
|
|
|
# SP cert + key — generate a keypair specifically for this SP:
|
|
# openssl req -x509 -newkey rsa:2048 -nodes \
|
|
# -keyout sp.key -out sp.crt -days 730 \
|
|
# -subj "/CN=truevuln.company.com"
|
|
SAML_SP_CERT_PATH=/etc/truevuln/saml/sp.crt
|
|
SAML_SP_PRIVATE_KEY_PATH=/etc/truevuln/saml/sp.key
|
|
|
|
# IdP metadata source — exactly one of:
|
|
SAML_IDP_METADATA_URL=
|
|
# SAML_IDP_METADATA_PATH=/etc/truevuln/saml/idp-metadata.xml
|
|
|
|
# Attribute mapping (defaults work for most IdPs)
|
|
SAML_ATTR_USERNAME=urn:oid:0.9.2342.19200300.100.1.1
|
|
SAML_ATTR_EMAIL=urn:oid:1.2.840.113549.1.9.1
|
|
SAML_ATTR_GROUPS=http://schemas.xmlsoap.org/claims/Group
|
|
SAML_ATTR_DISPLAY=urn:oid:2.16.840.1.113730.3.1.241
|