Files
vulncheck/.env.example
T
vulncheck b8e8870b29 feat(auth): LDAPS + OIDC + SAML 2.0 strategies and SSO routers
Phase 2-4 of multi-provider authentication. All three providers slot
into the AuthOrchestrator from phase 1; no further changes to
/auth/login are needed.

LDAPS (app/auth/strategies/ldap_strategy.py):
- ldap3 with strict TLS cert validation (CERT_REQUIRED + CA bundle)
- Service-account search-then-bind flow (DN never exposed to caller)
- Filter chars escaped via ldap3.utils.conv.escape_filter_chars
- AD-flavored defaults: sAMAccountName / memberOf / objectGUID
- Refuses if filter returns >1 entry (anti-impersonation safety)
- Bind password encrypted at rest (Fernet), bootstrapped from env on
  first start then stored in settings table

OIDC (app/auth/strategies/oidc_strategy.py + app/routers/auth_oidc.py):
- Authorization Code + PKCE (S256)
- Strict ID-Token validation via Authlib: signature (JWKS w/ auto-refresh
  on rotation), iss (essential), aud (essential, must == client_id),
  exp (essential), nonce (replay protection)
- State/PKCE-verifier/nonce stored in signed itsdangerous cookie (no
  server-side session store needed)
- Discovery + JWKS cached in-process; JWKS auto-refetched on key miss
- Groups merged from both id_token claims and userinfo endpoint
- Hardened: prompt=select_account to defeat silent IdP reuse

SAML 2.0 (app/auth/strategies/saml_strategy.py + app/routers/auth_saml.py):
- python3-saml (OneLogin) with strict=true; xmlsec1 handles signature
  validation. XSW attacks mitigated via strict assertion/response
  signature position checks plus wantAssertionsSigned=true
- SP-initiated (/auth/saml/login) + IdP-initiated (POST /auth/saml/acs)
- /auth/saml/metadata serves signed SP descriptor
- RelayState same-origin check to prevent open redirect
- IdP metadata loaded from URL or file at startup

Wiring:
- app/main.py imports auth_oidc/auth_saml routers behind try/except so
  the app still starts when authlib or python3-saml aren't installed
- Frontend login page fetches /auth/providers and renders matching
  redirect buttons (Sign in with Entra ID / SAML SSO) plus the local
  credentials form. Adds MFA second-step screen with 6-digit OTP input
  when /auth/login returns mfa_required=true.

.env.example: full provider config blocks with worked examples for
Entra ID, Okta, Keycloak, Google. Each block is commented with the
exact format the corresponding admin needs.
2026-05-12 19:11:32 +02:00

174 lines
6.8 KiB
Bash

# =============================================================================
# VulnCheck Environment Configuration
# =============================================================================
# Copy this file to .env and adjust values before starting:
# cp .env.example .env
#
# Designed to run behind a reverse proxy (Nginx Proxy Manager, Traefik, etc.)
# with HTTPS termination at the proxy level.
# =============================================================================
# --- Database ---
POSTGRES_USER=vulnmanager
POSTGRES_PASSWORD=changeme # Change this!
POSTGRES_DB=vulnmanager
POSTGRES_PORT=5432
# --- Application Ports ---
# These are the internal Docker ports. Map them in your reverse proxy.
BACKEND_PORT=8022
FRONTEND_PORT=3003
# --- 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
# --- Cookies ---
# Set to true when using HTTPS (recommended, required behind HTTPS proxy)
AUTH_COOKIE_SECURE=true
AUTH_COOKIE_SAMESITE=lax
# --- Reverse Proxy ---
# Enable if running behind a reverse proxy to trust X-Forwarded-For headers
# for correct client IP in rate limiting and audit logs.
TRUST_PROXY_HEADERS=true
# --- 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 ---
# Used in email notifications for links back to the dashboard.
# Set this to your external URL (behind reverse proxy).
# DASHBOARD_URL=https://vuln.example.com
# =============================================================================
# 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-vulncheck,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://vulncheck.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://vulncheck.company.com/auth/saml/metadata
SAML_SP_ACS_URL=https://vulncheck.company.com/auth/saml/acs
SAML_SP_SLO_URL=https://vulncheck.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=vulncheck.company.com"
SAML_SP_CERT_PATH=/etc/vulncheck/saml/sp.crt
SAML_SP_PRIVATE_KEY_PATH=/etc/vulncheck/saml/sp.key
# IdP metadata source — exactly one of:
SAML_IDP_METADATA_URL=
# SAML_IDP_METADATA_PATH=/etc/vulncheck/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