Skip to content

Hardening

The first part of this page lists what PKIFactor protects by default, with the setting that controls each measure. The second part lists what the operator must do on a production installation; several protections stay inactive until the operator acts.

Hardened by default

Browser and interface

The frontend adds these headers to every response:

Header Value
Content-Security-Policy default-src 'self'; script-src 'self' 'nonce-<random per response>'; style-src 'self' 'unsafe-inline'; font-src 'self'; img-src 'self' data:; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'
X-Frame-Options DENY
X-Content-Type-Options nosniff
Referrer-Policy strict-origin-when-cross-origin
Strict-Transport-Security max-age=31536000; includeSubDomains
Cache-Control no-store, no-cache, must-revalidate on dynamic pages

The content security policy allows no inline script: each page carries a fresh nonce, and the interface contains no inline event handlers. CSP_STRICT=false puts 'unsafe-inline' back in script-src; it exists only as an escape hatch and should not be used. Inline styles are still allowed (style-src 'unsafe-inline').

Templates escape their content automatically, and the e-mail templates that administrators can edit are rendered in a sandbox.

Sessions and cookies

Cookie Content Flags
session Signed session token (HS256, issuer and audience checked, unique identifier per token) HttpOnly, SameSite=Lax, Secure when BASE_URL starts with https:// (forced with COOKIE_SECURE), lifetime set by Session Timeout (30 minutes by default, 5 to 480; JWT_EXPIRE_SECONDS as fallback), renewed while the user is active
zclm Interface session, carries the anti-CSRF token of the forms HttpOnly, SameSite=Lax, Secure by default (SESSION_COOKIE_SECURE)
  • Sign-out revokes the token on the server (deny-list by token identifier), for local and single sign-on sessions alike: a copied token cannot be replayed.
  • A token stops working as soon as the account is deactivated.
  • When a password is changed by its owner or reset by an administrator, every session opened before the change stops working, so the usual remediation after a compromise also signs out the intruder.
  • The anti-CSRF token is bound to the user and regenerated at sign-in.

Sign-in

Measure Default Setting
Rate limit on the sign-in API 10 attempts per minute per client address (since 1.8.29; before, shared by all interface users) Fixed
Account lockout 5 consecutive failures lock a local account for 15 minutes Failed attempts before lockout and Lockout duration (min) in the Administration workspace, System > Settings, section General; 0 attempts disables the lockout
Same answer and same duration whether the e-mail exists or not Always
Password policy 12 to 128 characters, four character classes, bcrypt-SHA256 See Users and roles
Single sign-on ID token signature checked against the provider's keys, with state, nonce and PKCE (S256)
Initial administrator Random password printed once in the log, change forced at first sign-in

Downloading a PKCS#12 (which contains a private key) is limited to 5 attempts per minute and recorded in the audit trail.

Separation of duties

  • Nobody can approve their own request, whatever their role, including through a delegation.
  • An administrator who is not an approver of an organization cannot decide on its requests by default. Approval decisions by a non-validator administrator (System > Settings, section General) can allow it to unblock a situation; the reason is then mandatory, recorded (APPROVAL_OVERRIDE) and the approvers are notified. Leave it on Forbidden (strict separation of duties) unless your organization needs it.
  • A validator can hand their approval authority to a substitute for a limited period (RA workspace, Approver Inbox > Delegations); the substitute must already hold the validator role, and a delegation cannot be passed on.

Start-up checks (fail-closed)

Check Behaviour
Data encryption key The backend refuses to start if db_encryption_key is missing or is not a valid 32-byte key
Session token key In production, the backend refuses to start with the public development value; it logs a critical message if the key is shorter than 32 characters
KMS or HSM If the KMS does not answer after its retries and no encrypted key cache exists, the backend refuses to start; it never falls back silently to a key stored next to the data (Encryption at rest, KMS and HSM)

Network entry point (nginx)

  • Only nginx publishes ports. TLS 1.2 and 1.3 only, with ECDHE and AES-GCM suites.
  • server_tokens off: the nginx version is not disclosed.
  • Request rate limits per client address: 60 per second (burst 120) on the interface and the API, 10 per second (burst 20) on each gateway.
  • Request body limits: 10 MB in general, 1 MB on CMP and SCEP.

Enrolment gateways

Measure Default Setting
Rate limit per client 30 enrolments per minute ENROLL_RATE_LIMIT
Body size at application level (EST, SCEP, CMP) 256 KiB ENROLL_MAX_BODY_BYTES
EST basic authentication Only dedicated enrolment credentials per profile (bcrypt, lockout after 5 failures for 15 minutes); PKIFactor accounts are refused EST_ALLOW_APPLOGIN_FALLBACK (transition only)
EST identity binding When the client authenticates with a certificate (enrolment or re-enrolment), the CSR must keep the common name of that certificate and may request only names it already contains; a CSR with several common names is always refused
EST basic authentication proof The backend refuses an EST request in basic mode that the gateway has not validated first, and records EST_BASIC_UNVERIFIED EST_STRICT_CREDENTIAL_CHECK
SCEP message encryption Single DES refused; 3DES and SHA-1 accepted for older devices SCEP_STRICT_CRYPTO also refuses 3DES and SHA-1
SCEP challenges Stored in the database, single use
CMP message protection Refused when it cannot be checked against a trust anchor CMP_ALLOW_INSECURE_PROTECTION (laboratories only)
ACME HTTP-01 validation Private, loopback, link-local and shared (100.64.0.0/10) addresses refused unless listed in the allowed networks (also settable in the interface); redirects not followed; connection made to the address that was checked ACME_ALLOWED_NETWORKS
Client certificate headers and client address Accepted only with the proof added by nginx (shared secret), or, without secret, from the trusted proxy networks MTLS_PROXY_SECRET, EST_PROXY_SECRET, TRUSTED_PROXY_NETWORKS

Outbound connections

  • Connectors verify the server certificate by default: Vault, AD CS (verify_cert must be set explicitly to disable it), SIEM over TLS, Vault Transit (KEK_VAULT_SKIP_VERIFY must be set explicitly).
  • The Vault connector never fetches the Vault CA without verification unless VAULT_INSECURE_CA_AUTOFETCH is set.
  • E-mail over SMTP with TLS verifies the mail server certificate (unless SMTP_TLS_INSECURE is set).
  • Single sign-on provider addresses must use HTTPS; plain HTTP is accepted only for a loopback address or when SSO_ALLOW_HTTP is set.

High availability

  • etcd members talk over mutual TLS.
  • Administrator actions forwarded to the other nodes (rolling restart) carry the administrator's session: they are refused (HTTP 412) unless HA_PEER_CA points to the CA of the nodes' certificates. HA_PEER_INSECURE=1 disables the check and is meant for laboratories.
  • The PostgreSQL superuser accounts of the cluster have their own passwords (PATRONI_SUPERUSER_PASSWORD, PATRONI_ADMIN_PASSWORD), distinct from the application role.

Containers and files

Item Default
Frontend, gateways Run as the non-root user appuser
Backend and worker Start as root to fix the ownership of their volumes, then drop to appuser
nginx Root in the appliance Compose file (to read the TLS keys); non-root in its image (OpenShift)
Database Not published; applications connect as clm_app without superuser rights (Security architecture)
Secret files /opt/pkifactor/secrets/, directory 750 root:pkifactor; each service mounts only the files it needs
.env Mode 640
Appliance backups Mode 600

Flags that weaken a protection

These variables exist for laboratories or transitions. None of them is set on a new installation; none should be set in production.

Variable Effect when set
CSP_STRICT=false Allows inline scripts
KMS_ALLOW_ENV_FALLBACK=true Starts on the local key when the KMS is down (encrypted writes are then blocked)
CMP_ALLOW_INSECURE_PROTECTION Accepts CMP messages whose protection cannot be checked
VAULT_INSECURE_CA_AUTOFETCH Fetches the Vault CA without verification
EST_ALLOW_APPLOGIN_FALLBACK Accepts PKIFactor accounts for EST basic authentication
EST_STRICT_CREDENTIAL_CHECK=false Accepts EST basic requests that the gateway did not validate (the event is still recorded)
SCEP_ALLOW_LEGACY_CRYPTO Accepts single DES in SCEP messages
SMTP_TLS_INSECURE Disables verification of the mail server certificate
SSO_ALLOW_HTTP Accepts single sign-on provider addresses in plain HTTP
HA_PEER_INSECURE=1 Forwards administrator sessions between nodes without TLS verification
KEK_VAULT_SKIP_VERIFY Disables TLS verification towards Vault Transit

What the operator should do

TLS certificate of the platform

The setup wizard can generate a self-signed certificate for a first start. Replace it with a certificate from your PKI before production, from Administration workspace, Infrastructure > Service certificates (which also renews it) or on the appliance:

sudo pkifactor-ctl tls update /tmp/clm.crt /tmp/clm.key /tmp/ca-chain.pem
sudo pkifactor-ctl tls show

See Service certificates.

Restrict administrative access

  • Filter port 443 at the network level so that only user and client networks reach it; expose the gateways only to the networks of their clients.
  • Keep the number of administrators small (PKIFactor requires at least two active ones); prefer single sign-on with your identity provider's multi-factor authentication (Single sign-on).
  • Restrict SSH access to the appliance host: root there can read every secret.
  • Give the auditor role to people who only need to read.

Protect the secrets

  • Do not copy secret values into .env; keep them in /opt/pkifactor/secrets/ (Secrets and key management).
  • Move the root of the encryption to Vault Transit or an HSM for production (Encryption at rest, KMS and HSM).
  • Bind the Vault AppRole to the PKIFactor addresses (secret_id_bound_cidrs, token_bound_cidrs) and give PKIFactor its own HSM slot.

Encrypt and move the backups

  • Leave Encrypt backups on for application backups, and store them on SFTP on another system.
  • The appliance archive (pkifactor-ctl backup) contains every secret of the platform. It is encrypted with a passphrase kept in /etc/pkifactor/backup.pass: copy that passphrase to a vault off the host, and never store it next to the archives (Backup and restore).

Activate the enrolment protections

  • Set the allowed domains on each profile used by ACME, EST, SCEP or CMP: without them, a gateway client can request any name the authority accepts.
  • Create dedicated enrolment credentials for each EST profile.

High availability

  • Set HA_PEER_CA on every node.
  • On a cluster created before the separate superuser passwords existed, change the passwords in PostgreSQL, then set PATRONI_SUPERUSER_PASSWORD and PATRONI_ADMIN_PASSWORD.

Logging

Forward the audit trail to your SIEM: local log files are kept 7 days by default (Logging and incident handling).

Update cadence

Install each PKIFactor release, including its security fixes, with pkifactor-ctl upgrade (a backup is taken first). Read the release notes for the fixes each version contains, and keep the host operating system and Docker up to date with your usual patch process. See Upgrade.

Checklist

Check Where
Platform certificate issued by your PKI, not self-signed pkifactor-ctl tls show
No secret value in .env other than the database passwords /opt/pkifactor/.env
KMS mode is vault-transit or pkcs11-hsm Infrastructure > Encryption, or kms_admin status
No weakening flag set .env
Approval decisions by a non-validator administrator left on Forbidden unless needed System > Settings
Allowed domains set on enrolment profiles Certificate profiles
Audit trail forwarded to the SIEM System > Settings, section SIEM Integration
HA_PEER_CA set (HA only) .env of each node
Appliance backup encrypted and stored off the host Your backup procedure
Restore tested in the last six months Your test record