Skip to content

fix: auto-renew TLS certificates without restart#1337

Open
dogancanbakir wants to merge 2 commits intodevfrom
fix/acme-cert-auto-renewal
Open

fix: auto-renew TLS certificates without restart#1337
dogancanbakir wants to merge 2 commits intodevfrom
fix/acme-cert-auto-renewal

Conversation

@dogancanbakir
Copy link
Member

Summary

  • Use CertMagic's GetCertificate callback instead of static tls.Config.Certificates so ACME-renewed certs are served dynamically
  • Add CertReloader for custom cert files (--cert/--privkey) that polls for file changes and hot-swaps the certificate
  • Check interval configurable via CERT_CHECK_INTERVAL env variable (default 1h)

Fixes #1332

@neo-by-projectdiscovery-dev
Copy link

neo-by-projectdiscovery-dev bot commented Feb 24, 2026

Neo Security Audit

High: 1 · Medium: 2

Highlights

  • Consider using Go 1.24's os.Root API for traversal-resistant file operations in certificate loading
  • Add file integrity checks (hash verification) before loading renewed certificates
High (1)
  • TOCTOU vulnerability in certificate reload mechanismpkg/server/acme/cert_reloader.go:81
    The tryReload() function has a time-of-check-time-of-use (TOCTOU) race condition. It checks file modification time at line 81, then loads the certificate at line 90. An attacker with filesystem access could replace the certificate files with symlinks between these operations, causing the server to load certificates from an attacker-controlled location.
Medium (2)
  • Missing TLS MinVersion allows downgrade to TLS 1.2cmd/interactsh-server/main.go:302
    TLS configuration for custom certificates does not specify MinVersion, allowing clients to negotiate TLS 1.2. While Go 1.22+ defaults to TLS 1.2 minimum, explicitly setting TLS 1.3 provides defense-in-depth against future changes and protocol downgrade attacks.
  • Missing TLS MinVersion in ACME configurationcmd/interactsh-server/main.go:323
    TLS configuration for ACME certificates does not specify MinVersion, allowing clients to negotiate TLS 1.2. While Go 1.22+ defaults to TLS 1.2 minimum, explicitly setting TLS 1.3 provides defense-in-depth.
Security Impact

TOCTOU vulnerability in certificate reload mechanism (pkg/server/acme/cert_reloader.go:81):
Attacker with filesystem write access can redirect certificate loading to arbitrary files via symlink race, potentially causing the server to serve attacker-controlled certificates or crash by loading invalid certificate data

Missing TLS MinVersion allows downgrade to TLS 1.2 (cmd/interactsh-server/main.go:302):
Clients can negotiate TLS 1.2 which has known weaknesses (CBC mode ciphers vulnerable to Lucky13, BEAST). TLS 1.3 removes these legacy cipher suites and provides stronger security guarantees

Missing TLS MinVersion in ACME configuration (cmd/interactsh-server/main.go:323):
Clients can negotiate TLS 1.2 which has known weaknesses (CBC mode ciphers vulnerable to Lucky13, BEAST). TLS 1.3 removes these legacy cipher suites

Attack Examples

TOCTOU vulnerability in certificate reload mechanism (pkg/server/acme/cert_reloader.go:81):

1. Wait for tryReload() to call os.Stat() at line 81
2. Replace cert.pem with symlink to /tmp/attacker-cert.pem between lines 81-90
3. Server loads attacker's certificate at line 90 via tls.LoadX509KeyPair()
Suggested Fixes

TOCTOU vulnerability in certificate reload mechanism (pkg/server/acme/cert_reloader.go:81):

Use file descriptors instead of paths: open files with O_NOFOLLOW flag, fstat() the fd, then read from the fd. This prevents symlink following and eliminates the TOCTOU window. Example: fd := unix.Open(path, unix.O_RDONLY|unix.O_NOFOLLOW, 0); defer unix.Close(fd); fstat(fd); read certificate from fd

Missing TLS MinVersion allows downgrade to TLS 1.2 (cmd/interactsh-server/main.go:302):

Add MinVersion: tls.VersionTLS13 to the tls.Config at line 302:
tlsConfig = &tls.Config{
    GetCertificate: reloader.GetCertificate,
    NextProtos:     []string{"h2", "http/1.1"},
    MinVersion:     tls.VersionTLS13,
}

Missing TLS MinVersion in ACME configuration (cmd/interactsh-server/main.go:323):

Add MinVersion: tls.VersionTLS13 to the tls.Config at line 323:
tlsConfig = &tls.Config{
    GetCertificate: cfg.GetCertificate,
    NextProtos:     []string{"h2", "http/1.1"},
    MinVersion:     tls.VersionTLS13,
}
Hardening Notes
  • Consider using Go 1.24's os.Root API for traversal-resistant file operations in certificate loading
  • Add file integrity checks (hash verification) before loading renewed certificates
  • Log certificate reload events with file paths and modification times for audit trail
  • Consider rate-limiting certificate reload attempts to prevent DoS via rapid file modifications

Comment @neo help for available commands. · Open in Neo

@coderabbitai
Copy link

coderabbitai bot commented Feb 24, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/acme-cert-auto-renewal

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

@dogancanbakir dogancanbakir linked an issue Feb 24, 2026 that may be closed by this pull request
Comment on lines 314 to 315
domainCerts, certFiles, cmCfg, acmeErr = acme.HandleWildcardCertificates(fmt.Sprintf("*.%s", trimmedDomain), hostmaster, acmeStore, cliOptions.Debug, cliOptions.Resolvers)
if acmeErr != nil {
Copy link
Member

Choose a reason for hiding this comment

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

Each loop iteration overwrites cmCfg, domainCerts, and certFiles. If multiple domains are configured, only the last domain's certificates and config survive.

Copy link
Member

Choose a reason for hiding this comment

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

either we can handle the multi-domain case properly, or support only single domain

Split HandleWildcardCertificates into NewCertmagicConfig + per-domain
cert obtaining so a single certmagic.Config is shared across all domains.
The ACME loop now appends to domainCerts/certFiles instead of replacing
them on each iteration, fixing multi-domain certificate handling.
r.mu.Lock()
defer r.mu.Unlock()

mt, err := latestModTime(r.certPath, r.keyPath)

Choose a reason for hiding this comment

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

🟠 TOCTOU vulnerability in certificate reload mechanism (CWE-367) — The tryReload() function has a time-of-check-time-of-use (TOCTOU) race condition. It checks file modification time at line 81, then loads the certificate at line 90. An attacker with filesystem access could replace the certificate files with symlinks between these operations, causing the server to load certificates from an attacker-controlled location.

Attack Example
1. Wait for tryReload() to call os.Stat() at line 81
2. Replace cert.pem with symlink to /tmp/attacker-cert.pem between lines 81-90
3. Server loads attacker's certificate at line 90 via tls.LoadX509KeyPair()
Suggested Fix
Use file descriptors instead of paths: open files with O_NOFOLLOW flag, fstat() the fd, then read from the fd. This prevents symlink following and eliminates the TOCTOU window. Example: fd := unix.Open(path, unix.O_RDONLY|unix.O_NOFOLLOW, 0); defer unix.Close(fd); fstat(fd); read certificate from fd

} else {
tlsConfig = acmeManagerTLS
go reloader.Start(context.Background())
tlsConfig = &tls.Config{

Choose a reason for hiding this comment

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

🟡 Missing TLS MinVersion allows downgrade to TLS 1.2 (CWE-327) — TLS configuration for custom certificates does not specify MinVersion, allowing clients to negotiate TLS 1.2. While Go 1.22+ defaults to TLS 1.2 minimum, explicitly setting TLS 1.3 provides defense-in-depth against future changes and protocol downgrade attacks.

Suggested Fix
Add MinVersion: tls.VersionTLS13 to the tls.Config at line 302:
tlsConfig = &tls.Config{
    GetCertificate: reloader.GetCertificate,
    NextProtos:     []string{"h2", "http/1.1"},
    MinVersion:     tls.VersionTLS13,
}

certFiles = append(certFiles, files...)
}
}
tlsConfig = &tls.Config{

Choose a reason for hiding this comment

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

🟡 Missing TLS MinVersion in ACME configuration (CWE-327) — TLS configuration for ACME certificates does not specify MinVersion, allowing clients to negotiate TLS 1.2. While Go 1.22+ defaults to TLS 1.2 minimum, explicitly setting TLS 1.3 provides defense-in-depth.

Suggested Fix
Add MinVersion: tls.VersionTLS13 to the tls.Config at line 323:
tlsConfig = &tls.Config{
    GetCertificate: cfg.GetCertificate,
    NextProtos:     []string{"h2", "http/1.1"},
    MinVersion:     tls.VersionTLS13,
}

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.

Support ACME certificate refresh without restart

2 participants