16 KiB
Configuration Reference
This is the complete configuration reference for the saltcorn-idp plugin. It
covers the environment variables the plugin reads, the tunable constants baked
into lib/constants.js and lib/crypto.js, every _idp_* database table and
its columns, and the admin UI pages. Every value below is grounded in the plugin
source; file and constant names are given so they can be verified.
For protocol-specific detail see the sibling docs:
./architecture.md (integration model and multi-tenancy
overview), ./oidc.md, ./ldap.md,
./saml.md, ./operations.md, and
./security.md.
Environment variables
The plugin itself reads only the three variables below. They are read in
lib/crypto.js (SALTCORN_SESSION_SECRET) and lib/ldap/server.js /
lib/constants.js (the two LDAP variables).
| Variable | Required | Default | Effect |
|---|---|---|---|
SALTCORN_SESSION_SECRET |
Yes (see note) | None. Falls back to Saltcorn's session_secret config when unset; if neither is available, deriving the KEK throws "saltcorn-idp: SALTCORN_SESSION_SECRET not available; cannot derive KEK". |
Source material for two derivations: (1) the at-rest KEK (AES-256-GCM, via HKDF-SHA256) that seals RSA signing keys, the SAML private key, OIDC client secrets, and the LDAP service-account password; (2) the deterministic oidc-provider cookie-signing keys (so interaction/session cookies survive a restart). Rotating this value re-derives the KEK and invalidates all previously sealed data. |
SALTCORN_IDP_LDAP_PORT |
No | Unset. When unset, startLdap() returns early and no LDAP listener is started. |
Enables the in-process LDAPS listener and sets its TCP port. Parsed with parseInt(..., 10); a non-finite value is ignored (no listener). The listener binds only in the cluster primary process. Constant name: LDAP_PORT_ENV. |
SALTCORN_IDP_LDAP_HOST |
No | 127.0.0.1 (constant LDAP_DEFAULT_HOST). An empty or whitespace-only value also falls back to the default. |
Interface the LDAPS listener binds to. Loopback by default so LDAP is not network-exposed; set to 0.0.0.0 to serve LDAP clients on other hosts. Binding beyond loopback logs a network-exposure warning. Constant name: LDAP_HOST_ENV. |
Notes:
SALTCORN_SESSION_SECRET"required" means the plugin cannot seal/unseal data without it. In a normal Saltcorn deployment thesession_secretconfig provides the fallback, so the explicit environment variable is required only where that config is not present (for example, code paths outside a request context).SALTCORN_JWT_SECRETis not read by this plugin. It appears in the Postgres dev instance'senv.shbut is consumed by Saltcorn core, not bysaltcorn-idp. (Verified: no reference toSALTCORN_JWT_SECRETexists in the plugin source.)SALTCORN_MULTI_TENANTis likewise not read directly by the plugin. The plugin detects multi-tenancy by calling Saltcorn'sdb.is_it_multi_tenant()(used inlib/ldap/tenant.js), which reflects Saltcorn's own configuration.
Related Saltcorn configuration (not plugin env vars)
These are Saltcorn config values (read/written through getState().getConfig /
setConfig), not environment variables, but they affect plugin behavior:
| Config key | Effect |
|---|---|
base_url |
Preferred source for OIDC issuer and SAML entityID derivation (lib/oidc/discovery.js). When unset, the issuer is derived from the request Host header and a warning is logged: "base_url not set; deriving issuer from request Host ... Set base_url to prevent Host-header issuer poisoning." Set base_url in any multi-tenant or proxied deployment. |
session_secret |
Fallback source for the KEK when SALTCORN_SESSION_SECRET is unset (lib/crypto.js). |
disable_csrf_routes |
The plugin appends /idp/ to this list during onLoad so the machine-driven /idp endpoints are CSRF-exempt (index.js). Admin pages under /admin/idp remain CSRF-protected. |
Tunable constants
All values below are defined in lib/constants.js unless marked otherwise.
Crypto byte sizes live in lib/crypto.js.
Signing
| Constant | Value | Effect |
|---|---|---|
SIGNING_ALG |
RS256 |
Algorithm for OIDC id_token signing (and the required SAML signature is RSA-SHA256, below). |
RSA_MODULUS_BITS |
2048 |
RSA key size for generated signing keypairs. |
KEY_STATUS.ACTIVE |
"active" |
Key currently used to sign new tokens. |
KEY_STATUS.RETIRING |
"retiring" |
Key no longer signing but still published in JWKS for verification. |
KEY_STATUS.RETIRED |
"retired" |
Key fully retired (not in JWKS). |
Crypto byte sizes (lib/crypto.js)
| Constant | Value | Effect |
|---|---|---|
GCM_ALGORITHM |
aes-256-gcm |
At-rest sealing cipher. |
HKDF_HASH |
sha256 |
Hash for HKDF key derivation and the session-secret cache hash. |
IV_BYTES |
12 |
AES-GCM IV length (96-bit). |
KEK_BYTES |
32 |
Derived KEK length (256-bit). |
KID_BYTES |
16 |
Random bytes for a generated signing key id (kid), hex-encoded. |
KEK_INFO |
saltcorn-idp:at-rest:aes-gcm-key:v1 |
HKDF info for KEK derivation (domain separation). |
KEK_SALT |
saltcorn-idp:at-rest:aes-gcm-salt:v1 |
HKDF salt for KEK derivation. |
LDAP
| Constant | Value | Effect |
|---|---|---|
LDAP_BASE_DN |
dc=saltcorn,dc=local |
Directory base DN. Multi-tenant DNs add a dc=<tenant> component before the base. |
LDAP_PEOPLE_OU |
ou=people,dc=saltcorn,dc=local |
OU for user entries. |
LDAP_GROUPS_OU |
ou=groups,dc=saltcorn,dc=local |
OU for group entries. |
LDAP_MAX_MSG_BYTES |
262144 (256 * 1024) |
DoS guard: cap on inbound bytes per LDAP message (the BER parser has no size limit). The byte counter is reset on each parsed-message boundary, so this is a per-message cap, not a connection-lifetime quota -- a per-connection counter would let an attacker kill a legitimate long-lived connection after a few normal operations. The connection is destroyed if a single message exceeds the cap. Enforced in lib/ldap/vendor.js; see ./security.md for the full rationale. |
LDAP_MAX_FILTER_DEPTH |
32 |
DoS guard: maximum search-filter nesting depth. |
LDAP_BIND_MAX_ATTEMPTS |
5 |
Max listener bind attempts on a transient failure (EADDRINUSE/EACCES). |
LDAP_BIND_RETRY_BASE_MS |
500 |
Bind retry backoff base; delay = base * attempt (linear). |
The LDAP simple-bind handler also enforces a credential length cap, defined
locally in lib/ldap/bind.js rather than in lib/constants.js:
| Constant | Value | Effect |
|---|---|---|
MAX_CRED_LEN (lib/ldap/bind.js) |
1024 |
DoS/abuse guard on the bind handler: a bind with an empty DN, an empty password, or a password longer than this many characters is rejected as invalid credentials (and counts as a failure for the per-IP lockout) before any bcrypt or constant-time comparison runs. |
Additional LDAP runtime values set in lib/ldap/server.js:
| Setting | Value | Effect |
|---|---|---|
server.maxConnections |
256 |
Maximum concurrent LDAP connections. |
| self-signed cert | RSA 2048-bit, SHA256 | Generated per startup for LDAPS (dev); production supplies a cert via config. |
SAML
| Constant | Value | Effect |
|---|---|---|
SAML_MAX_MSG_B64_BYTES |
65536 (64 * 1024) |
DoS guard: cap on the base64 SAMLRequest/SAMLResponse accepted before decoding. |
SAML_MAX_XML_BYTES |
262144 (256 * 1024) |
DoS guard: cap on inflated XML size (DEFLATE can expand roughly 1000:1; prevents a memory bomb). |
SAML_AUTHN_CONTEXT |
urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport |
AuthnContext class advertised for password login. |
SAML_SIG_ALG |
http://www.w3.org/2001/04/xmldsig-more#rsa-sha256 |
Required signature algorithm (RSA-SHA256) on signed SAML messages. |
XML_CRYPTO_MIN |
6.1.2 |
Minimum acceptable xml-crypto version (2025 SAML-signature CVEs patched); asserted at load. |
Plugin metadata and path namespaces
| Constant | Value |
|---|---|
PLUGIN_NAME |
saltcorn-idp |
PLUGIN_VERSION |
0.0.1 |
IDP_BASE_PATH |
/idp |
ADMIN_BASE_PATH |
/admin/idp |
The admin role gate is ADMIN_ROLE_ID = 1, defined in lib/adminUi.js (admin
pages require req.user.role_id === 1).
Database tables
All tables are prefixed _idp_ and created idempotently during onLoad by
createAllTables() in lib/schema.js. Raw DDL is schema-qualified with
db.getTenantSchemaPrefix() so each Postgres tenant gets its own tables; on
SQLite the prefix is empty. Sealed key/secret material is stored as hex TEXT.
The auto-increment primary key on _idp_groups uses integer on SQLite and
serial on Postgres.
_idp_env
Singleton per-tenant row tracking first-run bootstrap state and an instance label.
| Column | Type | Notes |
|---|---|---|
env_id |
TEXT PRIMARY KEY |
UUID assigned on first init (crypto.randomUUID()). |
env_label |
TEXT |
Optional instance label; initialized to NULL. |
created_at |
TEXT NOT NULL |
ISO 8601 timestamp. |
bootstrapped_at |
TEXT |
ISO 8601 timestamp set by markBootstrapped; NULL until first bootstrap. |
_idp_keys
Per-tenant RSA signing keypairs and lifecycle. Index: _idp_keys_status on
status.
| Column | Type | Notes |
|---|---|---|
kid |
TEXT PRIMARY KEY |
Key id (hex). |
alg |
TEXT NOT NULL |
Signing algorithm (RS256). |
public_jwk |
TEXT NOT NULL |
Public JWK, JSON-stringified. |
private_ciphertext |
TEXT NOT NULL |
Sealed private key PEM (hex). |
private_iv |
TEXT NOT NULL |
AES-GCM IV (hex). |
private_tag |
TEXT NOT NULL |
AES-GCM auth tag (hex). |
status |
TEXT NOT NULL DEFAULT 'active' |
One of active, retiring, retired. |
created_at |
TEXT NOT NULL |
ISO 8601 timestamp. |
retire_after |
TEXT |
Optional retire-after timestamp. |
_idp_oidc_store
Single table backing the oidc-provider storage adapter (all model types).
Indexes: _idp_oidc_store_uid on uid, _idp_oidc_store_grant on grant_id,
_idp_oidc_store_usercode on user_code.
| Column | Type | Notes |
|---|---|---|
model |
TEXT NOT NULL |
oidc-provider model type. Part of PK. |
id |
TEXT NOT NULL |
Instance id. Part of PK. |
payload |
TEXT NOT NULL |
Serialized model state (JSON). |
uid |
TEXT |
Interaction uid (nullable). |
grant_id |
TEXT |
Grant reference (nullable). |
user_code |
TEXT |
Device user code (nullable). |
expires_at |
INTEGER |
Unix epoch seconds (nullable). |
| PRIMARY KEY | (model, id) |
Composite. |
_idp_groups
Custom groups (in addition to Saltcorn roles).
| Column | Type | Notes |
|---|---|---|
id |
integer/serial PRIMARY KEY |
Auto-increment (SQLite integer, Postgres serial). |
name |
TEXT NOT NULL UNIQUE |
Group name. |
description |
TEXT |
Optional description. |
created_at |
TEXT NOT NULL |
ISO 8601 timestamp. |
_idp_group_members
User-to-group junction. Index: _idp_group_members_user on user_id.
| Column | Type | Notes |
|---|---|---|
group_id |
INTEGER NOT NULL |
Part of PK. |
user_id |
INTEGER NOT NULL |
Saltcorn user id. Part of PK. |
| PRIMARY KEY | (group_id, user_id) |
Composite. |
_idp_clients
Registered OIDC relying parties. Confidential clients' secrets are sealed at
rest (hex columns); public clients (token_auth_method = 'none') have none.
| Column | Type | Notes |
|---|---|---|
client_id |
TEXT PRIMARY KEY |
Client identifier. |
label |
TEXT |
Display name. |
token_auth_method |
TEXT NOT NULL DEFAULT 'none' |
none, client_secret_basic, or client_secret_post. |
redirect_uris |
TEXT NOT NULL |
JSON array. |
grant_types |
TEXT NOT NULL |
JSON array. |
response_types |
TEXT NOT NULL |
JSON array. |
scope |
TEXT |
Space-separated scope string. |
secret_ciphertext |
TEXT |
Sealed secret (hex); NULL for public clients. |
secret_iv |
TEXT |
AES-GCM IV (hex); NULL for public clients. |
secret_tag |
TEXT |
AES-GCM auth tag (hex); NULL for public clients. |
created_at |
TEXT NOT NULL |
ISO 8601 timestamp. |
_idp_saml
Per-tenant SAML signing material (singleton row): a self-signed X.509 cert advertised in IdP metadata plus its sealed private key used to sign assertions.
| Column | Type | Notes |
|---|---|---|
id |
TEXT PRIMARY KEY |
Fixed row id. |
cert |
TEXT NOT NULL |
X.509 certificate (PEM, plaintext). |
private_ciphertext |
TEXT NOT NULL |
Sealed private key (hex). |
private_iv |
TEXT NOT NULL |
AES-GCM IV (hex). |
private_tag |
TEXT NOT NULL |
AES-GCM auth tag (hex). |
created_at |
TEXT NOT NULL |
ISO 8601 timestamp. |
_idp_saml_sps
Registered SAML service providers. The IdP only issues an assertion to a
registered SP and only to one of its allow-listed ACS URLs. The optional
signing_cert (public, not sealed) enables AuthnRequest signature
verification; want_authn_requests_signed gates enforcement.
| Column | Type | Notes |
|---|---|---|
entity_id |
TEXT PRIMARY KEY |
SP entityID. |
label |
TEXT |
Display name. |
acs_urls |
TEXT NOT NULL |
JSON array of allow-listed ACS URLs. |
signing_cert |
TEXT |
Public X.509 cert (PEM); nullable. |
want_authn_requests_signed |
INTEGER NOT NULL DEFAULT 0 |
0/1 boolean; gates signature enforcement. |
created_at |
TEXT NOT NULL |
ISO 8601 timestamp. |
_idp_ldap_service
LDAP service-account credentials for the search-then-bind flow (single row). The password is sealed at rest (hex columns), like client secrets.
| Column | Type | Notes |
|---|---|---|
dn |
TEXT |
Service bind DN. |
secret_ciphertext |
TEXT |
Sealed password (hex). |
secret_iv |
TEXT |
AES-GCM IV (hex). |
secret_tag |
TEXT |
AES-GCM auth tag (hex). |
created_at |
TEXT NOT NULL |
ISO 8601 timestamp. |
Admin UI pages
The admin UI is defined in lib/adminUi.js. All pages live under
/admin/idp (constant ADMIN_BASE_PATH), are gated to role_id = 1, and use
CSRF-protected browser forms (a hidden _csrf field is rendered via
req.csrfToken()). Non-admins receive HTTP 403 admin only.
| Path | Method | Page / action |
|---|---|---|
/admin/idp |
GET | Dashboard: env id, bootstrapped-at, issuer, active signing kid, signing alg, published JWKS key count, discovery and JWKS links. |
/admin/idp/ |
GET | Dashboard (alias). |
/admin/idp/clients |
GET | List OIDC clients and the registration form. |
/admin/idp/clients/create |
POST | Register a new OIDC client (returns the one-time secret for confidential clients). |
/admin/idp/clients/delete |
POST | Delete an OIDC client by client_id. |
/admin/idp/groups |
GET | List custom groups with members and add/remove member controls. |
/admin/idp/groups/create |
POST | Create a custom group by name. |
/admin/idp/groups/delete |
POST | Delete a group by id. |
/admin/idp/groups/addmember |
POST | Add a user to a group by email. |
/admin/idp/groups/removemember |
POST | Remove a user from a group. |
/admin/idp/saml-sps |
GET | List SAML service providers and the registration form. |
/admin/idp/saml-sps/create |
POST | Register a SAML SP (rejects empty entityID or empty ACS list). |
/admin/idp/saml-sps/delete |
POST | Delete a SAML SP by entity_id. |
/admin/idp/ldap |
GET | Show the configured LDAP service DN and set/clear forms (password never displayed). |
/admin/idp/ldap/service |
POST | Set the LDAP service-account DN and password (password sealed at rest). |
/admin/idp/ldap/service/clear |
POST | Clear the LDAP service account. |
The shared navigation bar links: Dashboard, Clients, Groups, SAML SPs, LDAP.