Skip to main content

Security configuration

Français : ../configuration.md

Source: services/control-plane/src/eai/config.py (EAI_ prefix, lists comma separated or JSON). General configuration is in operations/en/configuration.md.

EAI_* settings​

SettingDefaultProduction recommendation
EAI_ENVIRONMENTdevprod (enables the startup guard)
EAI_AUTH_DEVfalsefalse (mandatory)
EAI_AUTH_SPIFFEfalsetrue behind a trusted proxy
EAI_CORS_ORIGINSemptyexact portal origins, never *
EAI_ALLOWED_HOSTS*public API host names
EAI_TRUSTED_PROXIES127.0.0.1/32,::1/128gateway pod CIDR (e.g. 10.42.0.0/16), never 0.0.0.0/0
EAI_DOCS_ENABLEDtruefalse
EAI_SECURITY_HEADERS_ENABLEDtruetrue
EAI_HSTS_ENABLEDfalsetrue
EAI_HSTS_MAX_AGE_SECONDS3153600031536000
EAI_MAX_BODY_BYTES10485760 (10 MiB)10485760
EAI_MAX_UPLOAD_BYTES104857600 (100 MiB)depending on usage
EAI_MAX_JSON_DEPTH6464
EAI_RATE_LIMIT_ENABLEDtruetrue
EAI_RATE_LIMIT_PER_MINUTE1200tune, see runbooks.md
EAI_RATE_LIMIT_GATEWAY_PER_MINUTE600tune
EAI_RATE_LIMIT_AUTH_FAILURES_PER_MINUTE2020 or less
EAI_REDIS_URLempty (in memory counters)rediss:// as soon as there are several replicas, stored in OpenBao
EAI_JWT_ALGORITHMSRS256,RS384,RS512,PS256,PS384,ES256,ES384RS256,PS256,ES256; never HS* or none
EAI_JWT_LEEWAY_SECONDS3030
EAI_EGRESS_PRIVATE_ALLOWLISTemptyexact internal services host:port
EAI_EGRESS_ALLOWED_PORTS80,443,8443443
EAI_EGRESS_ALLOW_HTTPfalsefalse (mandatory)
EAI_EGRESS_DNS_TIMEOUT_SECONDS3.03.0
EAI_PROMPT_INJECTION_DEFAULT_MODEdetectblock
EAI_MASTER_KEY / EAI_OPENBAO_URLemptyone of them is mandatory; OpenBao recommended
EAI_DATABASE_URLlocalsslmode=verify-full with CA

Production startup guard​

eai.security.startup.enforce calls problems(settings) and refuses to start when EAI_ENVIRONMENT=prod, listing every problem (in staging it warns). It refuses:

  • EAI_AUTH_DEV on;
  • * in EAI_CORS_ORIGINS or EAI_ALLOWED_HOSTS;
  • EAI_EGRESS_ALLOW_HTTP on;
  • a too wide network in EAI_TRUSTED_PROXIES;
  • a symmetric algorithm or none in EAI_JWT_ALGORITHMS (symmetric_algorithms);
  • neither EAI_MASTER_KEY nor EAI_OPENBAO_URL;
  • EAI_DOCS_ENABLED on, EAI_HSTS_ENABLED off;
  • EAI_DATABASE_URL without sslmode=require, verify-ca or verify-full (database_tls);
  • EAI_LIVEKIT_URL set while EAI_LIVEKIT_API_KEY / EAI_LIVEKIT_API_SECRET are missing, equal to the LiveKit dev server values (devkey / secret) or with a secret shorter than 32 bytes. Outside dev and test, creating a realtime session is then refused (503 livekit_not_configured or livekit_secret_weak).

Tests: test_security_http.py::test_startup_guard_refuses_each_unsafe_prod_setting, test_security_http.py::test_startup_guard_lists_every_problem_and_warns_in_staging.

Tenant security settings​

eai.security.tenant_settings (defaults, merge, load, validate):

KeyDefaultValues
prompt_injection_modeEAI_PROMPT_INJECTION_DEFAULT_MODEoff, detect, block
untrusted_content_wrappingtrueboolean
output_linksstrip_untrustedallow, strip_untrusted
output_link_allowlist[]public host names (*.suffix allowed), at most 200
egress_allowed_hosts[]public host names; when not empty, tenant egress is restricted to this list

API: GET /v1/security/settings and PUT /v1/security/settings (roles tenant_admin or security_officer, change audited). Tests: test_security_api.py::test_settings_get_put_persist_and_audit, test_security_api.py::test_settings_validation, test_security_api.py::test_settings_roles, test_security_tenant_settings.py::test_validate_rejects_bad_values.

Posture API​

GET /v1/security/posture (roles security_officer or tenant_admin) returns active protections (eai.domains.security.posture.protections), checks (checks) and a summary (summary). Check ids: auth_dev_disabled, cors_restricted, allowed_hosts_restricted, hsts_enabled, docs_disabled, secrets_backend, oidc_configured, egress_http_disabled, egress_private_allowlist_narrow, rate_limit_enabled, trusted_proxies_restricted, spiffe_behind_proxy, database_tls, jwt_algorithms_asymmetric, prompt_injection_enabled. Tests: test_security_api.py::test_posture_roles_and_shape, test_security_api.py::test_posture_check_logic. Contract: control-plane-v1.md.

Helm keys (deploy/helm/enterprise-ai/values-prod.yaml)​

  • global.environment: prod, global.networkPolicy: cilium, global.secretsMode: external-secrets.
  • controlPlane.config: EAI_ENVIRONMENT, EAI_AUTH_DEV, EAI_AUTH_SPIFFE, EAI_ALLOWED_HOSTS, EAI_TRUSTED_PROXIES, EAI_DOCS_ENABLED, EAI_HSTS_ENABLED, EAI_MAX_BODY_BYTES, EAI_MAX_UPLOAD_BYTES, EAI_RATE_LIMIT_ENABLED, EAI_JWT_ALGORITHMS, EAI_EGRESS_PRIVATE_ALLOWLIST, EAI_EGRESS_ALLOWED_PORTS, EAI_EGRESS_ALLOW_HTTP, EAI_PROMPT_INJECTION_DEFAULT_MODE, TLS URLs of data services.
  • controlPlane.secrets.keys: sensitive values read from OpenBao (EAI_DATABASE_URL, EAI_REDIS_URL, EAI_MASTER_KEY, ...).
  • controlPlane.secretFormats: required formats (EAI_REDIS_URL as ^rediss://, EAI_DATABASE_URL with sslmode=verify-full, EAI_NATS_URL as ^tls://).

Tests: test_platform_security_manifests.py::test_prod_security_settings, test_platform_security_manifests.py::test_prod_configmap_renders_security_settings.

Identity, policy and key settings​

Defaults read in services/control-plane/src/eai/config.py.

SettingDefaultProduction recommendation
EAI_OIDC_ISSUER / EAI_OIDC_AUDIENCEemptyissuer of the Keycloak realm; client audience (makes aud mandatory)
EAI_OIDC_JWKS_URLempty (discovered from the issuer)empty unless the JWKS is served elsewhere
EAI_OIDC_GROUP_SYNCtruetrue (group mappings applied at each sign in)
EAI_OIDC_PLATFORM_ADMIN_GROUPSnavin-platform-adminsrestricted directory group
EAI_OIDC_TENANT_ADMIN_GROUP_PREFIXnavin-admins-prefix reserved to administrators
EAI_LOCAL_AUTH_ENABLEDtruefalse when everything goes through Keycloak
EAI_LOCAL_AUTH_MAX_FAILURES / EAI_LOCAL_AUTH_LOCKOUT_SECONDS5 / 9005 / 900 or stricter
EAI_LOCAL_PASSWORD_MIN_LENGTH1212 or more
EAI_LOCAL_SESSION_TTL_SECONDS28800per the session policy
EAI_OPA_URLempty (builtin engine only)TLS URL of OPA
EAI_OPA_DELIVERYpushbundle with EAI_POLICY_BUNDLE_SIGNING_KEY_FILE
EAI_POLICY_DEFAULTallowdeny to refuse what no rule covers
EAI_OPENFGA_URL / EAI_OPENFGA_STORE_ID / EAI_OPENFGA_MODEL_IDempty (builtin tuples)dedicated OpenFGA store, with EAI_OPENFGA_API_TOKEN
EAI_KMS_PROVIDERnoneopenbao or pkcs11
EAI_CONTEXT_REQUIREDfalsetrue (mandatory)
EAI_CONTEXT_SIGNING_KEY_REFemptyOpenBao reference (mandatory)
EAI_BREAK_GLASS_MAX_SECONDS36003600 or less
EAI_APPROVAL_TTL_SECONDS86400 (read by eai.domains.approvals.service)per decision delays
EAI_FILE_SCAN_MODEdevenforce (mandatory)
EAI_METRICS_TOKENempty32 characters minimum (mandatory)

Other startup guard refusals​

On top of the list above, eai.security.startup.problems refuses in production:

  • EAI_METRICS_TOKEN missing or shorter than 32 characters;
  • EAI_RUNTIME_DATABASE_URL without TLS, EAI_REDIS_URL without rediss://, Redis without EAI_REDIS_PASSWORD_FILE;
  • EAI_OPENBAO_URL and EAI_OPENBAO_TOKEN missing or EAI_AUDIT_SIGNING=hmac;
  • EAI_CONTEXT_REQUIRED off, a clear EAI_CONTEXT_SIGNING_KEY without EAI_CONTEXT_SIGNING_KEY_REF, or no key at all;
  • EAI_AUDIT_SEAL_SIGNING_KEY_REF missing, EAI_AUDIT_PAYLOAD_KEK other than openbao-transit, EAI_AUDIT_CLOCK_SKEW_SECONDS outside 0 to 3600;
  • EAI_OPA_DELIVERY other than push or bundle, or bundle without EAI_POLICY_BUNDLE_SIGNING_KEY_FILE;
  • an unknown EAI_KMS_PROVIDER, or pkcs11 without EAI_KMS_PKCS11_MODULE, EAI_KMS_PKCS11_TOKEN_LABEL and EAI_KMS_PKCS11_PIN_FILE;
  • EAI_FILE_SCAN_MODE other than enforce, a ClamAV or YARA-X engine outside https:// or without a Bearer key;
  • EAI_REALTIME_CONSENT_REQUIRED=false;
  • EAI_SOC_WEBHOOK_SECRET shorter than 32 characters, EAI_SOC_CORRELATION_WINDOW_SECONDS outside 60 to 86400;
  • EAI_STORAGE_PRESIGN_MAX_SECONDS outside 1 to 86400, EAI_STORAGE_CAPABILITY_MAX_AGE_SECONDS outside 60 to 604800, an unreadable EAI_STORAGE_CA_BUNDLE;
  • a weak EAI_BOOTSTRAP_ADMIN_PASSWORD while local accounts are enabled.

The full hardening procedure is in hardening.md.