Skip to content

fix: auto-renew TLS certificates without restart#1337

Merged
Mzack9999 merged 4 commits into
devfrom
fix/acme-cert-auto-renewal
Mar 25, 2026
Merged

fix: auto-renew TLS certificates without restart#1337
Mzack9999 merged 4 commits into
devfrom
fix/acme-cert-auto-renewal

Conversation

@dogancanbakir

Copy link
Copy Markdown
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

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

Copy link
Copy Markdown

Incremental Review Status: 5ad1e09f94dfd9

📊 Summary of Changes

This commit includes refactoring and test improvements:

  • Refactored cert_reloader.go to extract latestModTime() helper function
  • Added 8 comprehensive test cases in cert_reloader_test.go
  • Minor formatting changes in main.go

Files changed: 3 files (+20, -12)


🔍 Security Issues Status

❌ Issue #1: TOCTOU vulnerability in cert_reloader.go

Status: STILL PRESENT (unchanged)

Location: pkg/server/acme/cert_reloader.go:76-85

The refactoring extracted latestModTime() as a helper function but did not fix the underlying race condition. The TOCTOU window still exists:

  • Line 76: calls latestModTime() (performs os.Stat() on both files)
  • Line 85: calls tls.LoadX509KeyPair() (reads from file paths)

An attacker with filesystem access can still replace cert files with symlinks between these operations.


❌ Issue #2: Missing TLS MinVersion in custom cert config

Status: STILL PRESENT (unchanged)

Location: cmd/interactsh-server/main.go:302-305

The tls.Config for custom certificates still does not specify MinVersion. Clients can negotiate TLS 1.2.


❌ Issue #3: Missing TLS MinVersion in ACME config

Status: STILL PRESENT (unchanged)

Location: cmd/interactsh-server/main.go:324-327

The tls.Config for ACME certificates still does not specify MinVersion. Semgrep scanner confirms this issue at line 324.


✅ New Vulnerabilities Introduced

None detected. The changes are limited to code refactoring and test coverage improvements.


📝 Recommendation

The 3 previously reported security findings remain unaddressed. Consider implementing the suggested fixes:

  1. Use file descriptors with O_NOFOLLOW flag to eliminate TOCTOU
  2. Add MinVersion: tls.VersionTLS13 to both TLS configurations

The added test coverage in this commit is valuable for preventing regressions once these issues are fixed.

@coderabbitai

coderabbitai Bot commented Feb 24, 2026

Copy link
Copy Markdown

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.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: db99be72-8c92-40ec-940f-80f1ebc167c7

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
  • 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 thread cmd/interactsh-server/main.go Outdated
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)

Copy link
Copy Markdown

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{

Copy link
Copy Markdown

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,
}

Comment thread cmd/interactsh-server/main.go Outdated
certFiles = append(certFiles, files...)
}
}
tlsConfig = &tls.Config{

Copy link
Copy Markdown

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,
}

@Mzack9999 Mzack9999 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Marking with Request Changes to investigate needs of mutex

Comment thread pkg/server/acme/cert_reloader.go Outdated
}

func (r *CertReloader) tryReload() {
r.mu.Lock()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Isn't the tryReload() invoked sequentially without race condition already?

@dogancanbakir
dogancanbakir requested a review from Mzack9999 March 9, 2026 21:53
@Mzack9999
Mzack9999 merged commit 99ae107 into dev Mar 25, 2026
9 checks passed
@Mzack9999
Mzack9999 deleted the fix/acme-cert-auto-renewal branch March 25, 2026 16:33
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

3 participants