Skip to content

use OCSP files for stapling responses#3317

Open
mkf-sysangels wants to merge 62 commits into
bunkerity:devfrom
mkf-sysangels:OCSP-SSL-Stappling-#1592
Open

use OCSP files for stapling responses#3317
mkf-sysangels wants to merge 62 commits into
bunkerity:devfrom
mkf-sysangels:OCSP-SSL-Stappling-#1592

Conversation

@mkf-sysangels
Copy link
Copy Markdown

use OCSP files for stapling responses #1592

Lua needs an external OCSP status file to provide OCSP stapling without re-fetching the OCSP status from upstream PKI on every connect.

@mkf-sysangels
Copy link
Copy Markdown
Author

Hello @TheophileDiot,

I need a hint on why the job "ocsp-refresh" does not run.

Currently I need to start the job manually to feed the stapling files in the destination directories.

Thank you!

TODO: Check SSL_USE_OCSP_STAPLING variable when variable loading is available in ssl_certificate phase
Do not fetch OCSP response when TTL of current is more than 3 days
- add customcert processing
- use refresh TTL at 50% of lifetime
- job cleaned up existing directories
- run as main()
@mkf-sysangels
Copy link
Copy Markdown
Author

Hello @TheophileDiot - found out the reason: the py must call main() to run correctly as a job.

- migrate to native python cryptography module so we don't have openssl dependency
- add cleanup function when SSL_USE_OCSP_STAPLING=no
- add symlinks in case of multiple SAN entries
- fix race condition during service reload: use database for certs extraction
after successful order of a LE cert or a custom cert upload trigger an OCSP stapling refresh so we can use OCSP stapling instantly
Addressing the high-severity security issue where OCSP response signatures were not being cryptographically verified.
- SSRF checks when verifying the OCSP Issuer
- protections against Path Traversal via untrusted certificate Subject Alternative Names (SANs).
- Path traversal via tarball cert_name and DB restore cert
- Redundant import
- Debug log data leak, unbounded HTTP response reads
- Path Traversal Prevention
- Temporary File Permission Hardening
- Debug Logging Reduction
- Acquire a file-based lock for a specific certificate to prevent race conditions when multiple scheduler instances fetch OCSP responses concurrently.
- HTTP error code — OCSP responder returned an error
- End-of-job verification: ensure all OCSP files on disk match database checksums. Restores missing files from database to prevent OCSP stapling failures. This handles cases where OCSP cache directories are externally deleted or corrupted.
- before: each OCSP request blocked the database
- after: use a single insert into the database
- add certificate checksum check
- issue OCSP to new certs first, then changed certs, calculate TTL for the rest and run OCSP refresh if needed
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces OCSP stapling support to the SSL core by adding a scheduler job to fetch/refresh OCSP responses, caching them on disk/DB, and wiring NGINX’s ssl_certificate_by_lua phase to staple cached OCSP responses during TLS handshakes.

Changes:

  • Add SSL_USE_OCSP_STAPLING multisite setting and register a new hourly ocsp-refresh job in the SSL plugin.
  • Add a new ocsp-refresh.py job that fetches, verifies, caches, and restores OCSP responses (disk + database).
  • Trigger OCSP refresh after certificate issuance/renewal/custom-cert changes, and load/staple OCSP responses during TLS handshakes from cached ocsp.der.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 13 comments.

Show a summary per file
File Description
src/common/core/ssl/plugin.json Adds SSL_USE_OCSP_STAPLING setting and registers the new ocsp-refresh job.
src/common/core/ssl/jobs/ocsp-refresh.py New OCSP refresh job: fetch + verify OCSP, persist to DB/disk, manage locks, restore/verify cache.
src/common/core/letsencrypt/jobs/certbot-renew.py Adds certbot timeout handling and triggers OCSP refresh after successful renewals.
src/common/core/letsencrypt/jobs/certbot-new.py Triggers OCSP refresh after successful certificate issuance.
src/common/core/customcert/jobs/custom-cert.py Tracks changed domains and triggers OCSP refresh after custom cert updates.
src/common/confs/server-http/ssl-certificate-lua.conf Loads OCSP response from cache/disk and staples it via ngx.ocsp during TLS handshake.

Comment thread src/common/core/letsencrypt/jobs/certbot-renew.py Outdated
Comment thread src/common/core/letsencrypt/jobs/certbot-renew.py
Comment thread src/common/core/customcert/jobs/custom-cert.py Outdated
Comment thread src/common/core/customcert/jobs/custom-cert.py Outdated
Comment thread src/common/confs/server-http/ssl-certificate-lua.conf Outdated
Comment thread src/common/core/ssl/jobs/ocsp-refresh.py Outdated
Comment thread src/common/core/ssl/jobs/ocsp-refresh.py
Comment thread src/common/core/ssl/jobs/ocsp-refresh.py Outdated
Comment thread src/common/core/letsencrypt/jobs/certbot-new.py Outdated
Comment thread src/common/core/letsencrypt/jobs/certbot-renew.py
mkf-sysangels and others added 3 commits March 15, 2026 10:48
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@mkf-sysangels
Copy link
Copy Markdown
Author

Hello @TheophileDiot

Thank you for the audit. I published the fixes so we can rerun the audit again.

we need to verify that the OCSP file is for the current certificate in case the certificate changes but the old OCSP is still cached.

use resty.openssl with fallback to openssl

set debug level to messages so we don't fill up logs
… on OCSP errors

- Serial number uppercase comparison - Both certificate and OCSP response serials are converted to uppercase before comparison to ensure consistent matching

- Early certificate reading - Certificate is read immediately using ngx.ssl.cert_pem() at the start of set_ocsp_from_cache()

- OCSP responder URL extraction - New get_ocsp_responder_url() function that:
-- Tries native resty.openssl first (fast)
-- Falls back to openssl x509 -ocsp_uri command
-- Returns the OCSP responder URL if found
-- Skip OCSP if no responder URL - If certificate doesn't have an OCSP responder URL, function returns early without loading OCSP files

- Any error at any stage is logged but doesn't break the TLS handshake. The connection always continues with native TLS if OCSP fails for any reason. The certificate is already served before OCSP is even attempted.
on fast concurrent connections the filenames could create a race condition
- load modules with fail safe
- check if certificate has OCSP Must-Staple extension
- remove redis for a simple config
- add option to force a fresh fetch of the OCSP status files.
- cleanup old files in case of a previous script run error
use cert fingerprint for storing and loading

use native lua optimizations

optimize TTL for OCSP responses
@mkf-sysangels
Copy link
Copy Markdown
Author

Hi @TheophileDiot

The OCSP-stapling PR is ready for code audit and public preview

set this variable to enable the new OCSP module:
SSL_USE_OCSP_STAPLING=yes

@mkf-sysangels
Copy link
Copy Markdown
Author

running the ocsp-refresh.py alone:

LOG_LEVEL=debug python3 /usr/share/bunkerweb/core/ssl/jobs/ocsp-refresh.py ; chown -R nginx:nginx /var/cache/bunkerweb/ssl/ && chown -R nginx:nginx /var/cache/bunkerweb/ssl/

- verify ocsp response file vailidity using ngx.ocsp.validate_ocsp_response()
- optimize ocsp lookup logic (leaf cert first)
- add more debug information
- add test scenarios
- optimize orphan cleanup
- add hash collision check (a collision "should" never happen but we need to catch that situation)
@TheophileDiot
Copy link
Copy Markdown
Member

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 10, 2026

✅ Actions performed

Full review triggered.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 10, 2026

Warning

Rate limit exceeded

@TheophileDiot has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 2 minutes and 12 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 2 minutes and 12 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9540b995-6c9d-49fe-be3a-b5673d4cd810

📥 Commits

Reviewing files that changed from the base of the PR and between 3ec65a2 and 0e2d186.

📒 Files selected for processing (8)
  • src/common/confs/server-http/ssl-certificate-lua.conf
  • src/common/core/customcert/customcert.lua
  • src/common/core/customcert/jobs/custom-cert.py
  • src/common/core/letsencrypt/jobs/certbot-new.py
  • src/common/core/letsencrypt/jobs/certbot-renew.py
  • src/common/core/letsencrypt/letsencrypt.lua
  • src/common/core/ssl/jobs/ocsp-refresh.py
  • src/common/core/ssl/plugin.json

Comment @coderabbitai help to get the list of available commands and usage tips.

@TheophileDiot
Copy link
Copy Markdown
Member

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 10, 2026

✅ Actions performed

Full review triggered.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants