🔥 feat: Externalize TLS configuration from app.Listen()#4019
🔥 feat: Externalize TLS configuration from app.Listen()#4019simonnix wants to merge 1 commit intogofiber:mainfrom
Conversation
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. WalkthroughAdds pluggable TLS provider/customizer APIs for server and client, implements ACME/server certificate and client certificate providers, and introduces VerifyPeer addon features (MutualTLS, CRL, OCSP) with tests, an OCSP responder test harness, PKI testdata, and documentation updates. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant Server
participant TLS_Provider as TLS Provider
participant TLS_Customizer as TLS Customizer
participant OCSP_CRL as OCSP/CRL Endpoint
Client->>Server: Start TLS handshake (may present client cert)
Server->>TLS_Provider: ProvideServerTLS()
TLS_Provider-->>Server: *tls.Config
Server->>TLS_Customizer: CustomizeServerTLS(config)
TLS_Customizer-->>Server: installs VerifyPeerCertificate callback
Server->>Client: Continue TLS handshake
TLS_Customizer->>OCSP_CRL: Fetch/verify OCSP or CRL (http/https/file)
OCSP_CRL-->>TLS_Customizer: OCSP/CRL response
TLS_Customizer-->>Server: validation result (accept/reject)
Server-->>Client: handshake success or failure
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Summary of ChangesHello @simonnix, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly refactors the TLS configuration mechanisms within the Fiber framework, providing a more modular and extensible approach for both server and client-side secure communication. By introducing interface-based providers and customizers, it moves away from rigid, hardcoded TLS settings, enabling developers to dynamically manage certificates, implement advanced client verification methods like CRL and OCSP, and integrate with various certificate sources more seamlessly. This change enhances the security and flexibility of TLS handling across the application. Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces a significant and valuable refactoring of the TLS configuration handling in Fiber. By externalizing the TLS configuration logic from app.Listen() into ServerTLSProvider and ServerTLSCustomizer interfaces, you've made the setup more modular, flexible, and easier to extend. The addition of a similar ClientTLSProvider for the client package is also a great improvement for consistency.
I've identified a few areas for improvement, including a critical issue that could lead to a panic, some redundant code, and a potential error that is being ignored. My review comments include specific suggestions to address these points. Overall, this is a well-thought-out feature that enhances Fiber's capabilities.
There was a problem hiding this comment.
Actionable comments posted: 8
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
🤖 Fix all issues with AI agents
In `@addon/verifypeer/cert.go`:
- Around line 43-66: ClientCertificate() on MutualTLSCustomizer can return (nil,
nil) which leads to a panic when CustomizeServerTLS calls
cfg.ClientCAs.AddCert(certificate); change the behavior to never return (nil,
nil): in MutualTLSCustomizer.ClientCertificate ensure that if no valid PEM block
of type "CERTIFICATE" is found you return a non-nil error (e.g., "tls: no client
certificate provided" or similar) instead of (nil, nil), and keep the existing
error paths for parse/read failures so callers like CustomizeServerTLS can
safely assume a non-nil *x509.Certificate on a nil error.
In `@addon/verifypeer/listen_test.go`:
- Around line 19-150: Add t.Parallel() at the start of Test_Listen_MutualTLS,
Test_Listen_MutualTLS_Deprecated, Test_Listen_MutualTLS_Prefork, and
Test_Listen_MutualTLS_Prefork_Deprecated; protect the global mutation of
testPreforkMaster by using a mutex (e.g., a new package-level sync.Mutex) around
assignments and reads and ensure each test sets testPreforkMaster under the lock
and schedules a reset via t.Cleanup() to restore previous value; finally fix the
path typo in Test_Listen_MutualTLS_Prefork by changing
"../../.github/testdate/template.html" to
"../../.github/testdata/template.html".
In `@addon/verifypeer/ocsp_test.go`:
- Around line 18-63: The test Test_OCSP_Server has a data race on the shared
variable network and uses flaky sleeps; fix it by adding t.Parallel(), replacing
the shared network string with two dedicated address channels (e.g., ocspAddrCh
and appAddrCh), have the stapler.Listen goroutine send its resolved addr into
ocspAddrCh and the app.Listen goroutine send into appAddrCh (use the
ListenerAddrFunc closures), then receive the addresses synchronously in the main
test to extract ocspPort and appPort (removing time.Sleep calls); ensure you
still assert errors from stapler.Listen and app.Listen and update references of
network to the values read from the channels.
In `@addon/verifypeer/ocsp.go`:
- Around line 82-90: The code dereferences m.TLSProvider for HTTPS OCSP
endpoints which can panic; in the OCSP client setup (where ocspURL is parsed and
you call m.TLSProvider.ProvideClientTLS and cc.SetTLSConfig) add a nil-check for
m.TLSProvider and return a clear error (e.g. "TLS provider required for https
OCSP endpoint") before calling ProvideClientTLS, or otherwise provide a fallback
TLS config, ensuring you only call ProvideClientTLS and cc.SetTLSConfig when
m.TLSProvider != nil.
- Around line 65-68: The current logic silently ignores validation errors for an
explicit OCSPServer override (m.OCSPServer) by only setting ocspServer when
validateOSCPServer returns nil; change this to fail fast: call
m.validateOSCPServer(m.OCSPServer) and if it returns an error, propagate/return
that error instead of continuing, otherwise assign ocspServer = m.OCSPServer;
update the code paths that call validateOSCPServer/ocspServer so they handle the
returned error appropriately.
In `@client/tls_provider.go`:
- Around line 42-46: The JSON tag for IncludeSystemCertificates is inverted
("ignore_system_certificates") and should match the field semantics; update the
struct tag on IncludeSystemCertificates to "include_system_certificates" to
align behavior, or if you must preserve backward compatibility implement a
custom UnmarshalJSON for the containing struct to accept either
"include_system_certificates" or the legacy "ignore_system_certificates" key and
map it to IncludeSystemCertificates. Ensure you update any related
marshaling/unmarshaling logic or docs to reflect the chosen key.
In `@docs/client/rest.md`:
- Around line 391-395: The docs example uses a non-existent field name; update
the ClientCertificateProvider struct literal to use the correct field name
RootCertificates instead of SetRootCertificates so the example compiles—locate
the example where ClientCertificateProvider is constructed in
docs/client/rest.md and replace SetRootCertificates with RootCertificates.
In `@tls_provider_test.go`:
- Around line 14-16: The test sets the package-level flag testPreforkMaster
(from prefork.go) to true but never restores it, causing state leakage across
tests; update each prefork-related test (e.g., the tests that set
testPreforkMaster in tls_provider_test.go) to capture the previous value before
changing it and restore it with a defer (save old := testPreforkMaster;
testPreforkMaster = true; defer func(){ testPreforkMaster = old }()), ensuring
the flag is reset after the test completes to avoid races when tests run in
parallel.
🟡 Minor comments (18)
addon/verifypeer/crl.go-114-114 (1)
114-114: Typo in error message: "lhe" should be "the".📝 Suggested fix
- return errors.New("tls: lhe certificate was revoked") + return errors.New("tls: the certificate was revoked")addon/verifypeer/crl.go-52-52 (1)
52-52: Fix doc comment: incorrect interface name.📝 Suggested fix
-// CustomizeServerTLS implements [fiber.ServerCertificateCustomizer] +// CustomizeServerTLS implements [fiber.ServerTLSCustomizer]addon/verifypeer/cert.go-27-28 (1)
27-28: Typo in doc comment."certiticate" should be "certificate".
📝 Suggested fix
-// It will add the certiticate to config.ClientCAs and set config.ClientAuth to tls.RequireAndVerifyClientCert +// It will add the certificate to config.ClientCAs and set config.ClientAuth to tls.RequireAndVerifyClientCertaddon/verifypeer/crl.go-19-19 (1)
19-19: Fix doc comment: incorrect interface name.Same as in cert.go, the comment references
[fiber.ServerCertificateCustomizer]but should befiber.ServerTLSCustomizer.📝 Suggested fix
-// MutualTLSWithCRLCustomizer is a struct implementing [fiber.ServerCertificateCustomizer]. +// MutualTLSWithCRLCustomizer is a struct implementing [fiber.ServerTLSCustomizer].addon/verifypeer/cert.go-14-14 (1)
14-14: Fix doc comment: incorrect interface name.The comment references
[fiber.ServerCertificateCustomizer]but the actual interface isfiber.ServerTLSCustomizer.📝 Suggested fix
-// MutualTLSCustomizer is a struct implementing [fiber.ServerCertificateCustomizer]. +// MutualTLSCustomizer is a struct implementing [fiber.ServerTLSCustomizer].docs/addon/verifypeer.md-7-12 (1)
7-12: Fix typos and interface naming in VerifyPeer docs.There are several spelling issues and outdated interface names (“ServerCertificateCustomizer”, “OCSP Stapler”).
📝 Suggested doc edits
-The VerifyPeer addon package aim is to provides aadditional `fiber.ServerTLSCustomizer` interface implementationsto add means -to configure a `*tls.Config` 's `VerifyPeerCertificate` field with additional validation for client mTLS connection. +The VerifyPeer addon package aims to provide additional `fiber.ServerTLSCustomizer` implementations to configure a +`*tls.Config`'s `VerifyPeerCertificate` field with extra validation for client mTLS connections. @@ -`MutualTLSCustomizer` is a struct implementing `fiber.ServerCertificateCustomizer`, providing MutualTLS configuration to a `tls.Config` object. +`MutualTLSCustomizer` is a struct implementing `fiber.ServerTLSCustomizer`, providing MutualTLS configuration to a `tls.Config` object. @@ -See [OpenSSL documenation](https://docs.openssl.org/3.5/man1/openssl-ca/#crl-options) for more information. +See [OpenSSL documentation](https://docs.openssl.org/3.5/man1/openssl-ca/#crl-options) for more information. @@ -## MutualTLS with OCSP Stapler +## MutualTLS with OCSP StaplingAlso applies to: 47-49, 75-79
addon/verifypeer/listen_test.go-86-92 (1)
86-92: Fix typo in testdata path.
testdatelooks like a typo; it likely should betestdata.✅ Suggested fix
- Certificate: "../../.github/testdate/template.html", + Certificate: "../../.github/testdata/template.html",client/tls_provider_test.go-50-52 (1)
50-52: Avoid deferring Close before ensuring os.Open succeeded.If
os.Openfails,fileis nil and the deferredClosewill panic.✅ Suggested fix
file, err := os.Open("../.github/testdata/pki/cacert.pem") - defer func() { require.NoError(t, file.Close()) }() require.NoError(t, err) + defer func() { require.NoError(t, file.Close()) }()addon/verifypeer/ocsp.go-19-40 (1)
19-40: Align OCSP customizer docs with actual behavior.The comments reference
ServerCertificateCustomizerand a 1-second default, but the code implementsServerTLSCustomizerand sets 10 seconds.📝 Suggested doc fix
-// MutualTLSWithOCSPCustomizer is a struct implementing [fiber.ServerCertificateCustomizer]. +// MutualTLSWithOCSPCustomizer is a struct implementing [fiber.ServerTLSCustomizer]. @@ - // Default: 1 * time.Seconds + // Default: 10 * time.Second Timeout time.Duration `json:"timeout"`docs/client/rest.md-376-383 (1)
376-383: Fix typo and hard tabs in the SetTLSProvider section.There’s a typo (“ths”) and markdownlint flags hard tabs in the interface snippet.
📝 Suggested fix
- Sets ths TLS configuration for the client, using the `ClientTLSProvider` interface. + Sets the TLS configuration for the client, using the `ClientTLSProvider` interface. @@ type ClientTLSProvider interface { - ProvideClientTLS() (*tls.Config, error) + ProvideClientTLS() (*tls.Config, error) }As per coding guidelines, run
make markdownafter updating this file.docs/whats_new.md-190-213 (1)
190-213: Tidy grammar and remove hard tabs in the TLS provider section.The section has a grammar slip and markdownlint reports hard tabs in the code blocks.
📝 Suggested fix
-We provider now two interfaces and implementations to let you configure TLS in `ListenConfig`: +We provide two interfaces and implementations to let you configure TLS in `ListenConfig`: @@ type ServerTLSProvider interface { - ProvideServerTLS() (*tls.Config, error) + ProvideServerTLS() (*tls.Config, error) } @@ type ServerTLSCustomizer interface { - CustomizeServerTLS(config *tls.Config) error + CustomizeServerTLS(config *tls.Config) error } @@ -It can be a string to file or its content directly : +It can be a file path or its content directly:As per coding guidelines, run
make markdownafter updating this file.tls_provider_test.go-13-17 (1)
13-17: Addt.Parallel()at the start of each test function.These tests lack the required parallel execution directive. According to the repository's coding guidelines, all test functions should call
t.Parallel()at the beginning to maximize concurrency.Suggested change (apply to each test)
func Test_ServerTLSProvider_Version(t *testing.T) { + t.Parallel() testPreforkMaster = trueApplies to: Test_ServerTLSProvider_Version (line 13), Test_ServerTLSProvider (line 57), Test_ServerTLSProvider_Deprecated (line 80), Test_ServerTLSProvider_Prefork (line 105), Test_ServerTLSProvider_Prefork_Deprecated (line 134).
addon/verifypeer/ocsp_responder_test.go-137-138 (1)
137-138: Incorrect format specifier usage in logging calls.The
log.Errorfandlog.Infocalls use incorrect format specifiers:
%wis forfmt.Errorferror wrapping, not for logging—use%vinsteadlog.Infoandlog.Warndon't use printf-style formatting; uselog.Infof/log.Warnffor formatted output- log.Errorf("%w", err) + log.Errorf("issuer verification failed: %v", err) return nil, err } // get the index entry, if it exists ent, err := o.getIndexEntry(req.SerialNumber) if err != nil { - log.Errorf("%w", err) + log.Errorf("index entry lookup failed: %v", err) status = ocsp.Unknown } else { - log.Info("Found entry %+v", ent) + log.Infof("Found entry %+v", ent)Similar fixes needed for
log.Warnon line 150 andlog.Infoon lines 154, 270.Also applies to: 144-147
docs/api/fiber.md-120-120 (1)
120-120: Fix typo: "rewiewing" → "reviewing"-| <Reference id="tlsconfigfunc">TLSConfigFunc</Reference> | `func(tlsConfig *tls.Config)` | Allows rewiewing and customizing `tls.Config` returned from `TLSProvider` as you want. `tlsConfig` may be `nil` if none is returned from `TLSProvider` | `nil` | +| <Reference id="tlsconfigfunc">TLSConfigFunc</Reference> | `func(tlsConfig *tls.Config)` | Allows reviewing and customizing `tls.Config` returned from `TLSProvider` as you want. `tlsConfig` may be `nil` if none is returned from `TLSProvider` | `nil` |tls_provider.go-12-21 (1)
12-21: Fix typo in interface documentation: "Implentations" → "Implementations"- // NOTE: Implentations may return (nil, nil) if no tls.Config can be provided and no error occurred. + // NOTE: Implementations may return (nil, nil) if no tls.Config can be provided and no error occurred.docs/api/fiber.md-173-173 (1)
173-173: Fix duplicate word: "also be also" → "also be"-`Certificate` can also be also a string containing what that file should contain. +`Certificate` can also be a string containing what that file should contain.listen.go-157-159 (1)
157-159: Logic inconsistent with documented behavior forTLSConfigFunc.The comment on lines 57-59 states that
TLSConfigFuncreceives a potentially niltlsConfigso the user can "react to this." However, the current conditioncfg.TLSProvider != nilpreventsTLSConfigFuncfrom being called when no provider is set.Consider whether
TLSConfigFuncshould be invoked unconditionally (or only when set), allowing users to handle cases where no TLS config exists:- if cfg.TLSProvider != nil && cfg.TLSConfigFunc != nil { + if cfg.TLSConfigFunc != nil { cfg.TLSConfigFunc(tlsConfig) }Alternatively, if the current behavior is intentional, update the documentation to clarify that
TLSConfigFuncis only called whenTLSProvideris set.addon/verifypeer/ocsp_responder_test.go-288-292 (1)
288-292: Potential nil pointer dereference if PEM decode fails.If the file doesn't contain valid PEM data,
pem.Decodereturnsnilforblock, causing a panic on line 289:block, _ := pem.Decode(ct) + if block == nil { + return nil, errors.New("failed to decode PEM block from certificate file") + } cert, err := x509.ParseCertificate(block.Bytes)The same issue exists in
parseKeyFileat lines 302-303.
🧹 Nitpick comments (10)
addon/verifypeer/cert_test.go (2)
64-66: Missingt.Parallel()call.Per coding guidelines, tests should invoke
t.Parallel()at the start to maximize concurrency.📝 Suggested fix
func Test_Cert_File(t *testing.T) { + t.Parallel() testCert(t, "../../.github/testdata/pki/intermediate/cacert.pem") }
68-72: Missingt.Parallel()call.Per coding guidelines, tests should invoke
t.Parallel()at the start to maximize concurrency.📝 Suggested fix
func Test_Cert_Content(t *testing.T) { + t.Parallel() cert, err := os.ReadFile(filepath.Clean("../../.github/testdata/pki/intermediate/cacert.pem")) require.NoError(t, err) testCert(t, string(cert)) }addon/verifypeer/crl.go (2)
109-119: Consider adding a defensive bounds check.While
verifiedChainsshould be non-empty whenVerifyPeerCertificateis called after chain verification withRequireAndVerifyClientCert, a defensive check improves robustness.♻️ Optional defensive check
if clientCRL != nil { config.VerifyPeerCertificate = func(_ [][]byte, verifiedChains [][]*x509.Certificate) error { + if len(verifiedChains) == 0 || len(verifiedChains[0]) == 0 { + return errors.New("tls: no verified certificate chain") + } cert := verifiedChains[0][0] for _, revokedCertificate := range clientCRL.RevokedCertificateEntries { if revokedCertificate.SerialNumber.Cmp(cert.SerialNumber) == 0 { - return errors.New("tls: lhe certificate was revoked") + return errors.New("tls: the certificate was revoked") } } return nil } }
88-97: Silent CRL fetch failures from distribution points.When
RevocationListis empty, CRL fetch failures fromCRLDistributionPointsare silently ignored. This might mask configuration issues. Consider logging a warning when all distribution point fetches fail.addon/verifypeer/crl_test.go (3)
66-68: Missingt.Parallel()call.Per coding guidelines, tests should invoke
t.Parallel()at the start to maximize concurrency.📝 Suggested fix
func Test_CRL_File(t *testing.T) { + t.Parallel() testCRL(t, "../../.github/testdata/pki/intermediate/crl.pem") }
70-75: Missingt.Parallel()call.📝 Suggested fix
func Test_CRL_Content(t *testing.T) { + t.Parallel() crlFile, err := os.ReadFile(filepath.Clean("../../.github/testdata/pki/intermediate/crl.pem")) require.NoError(t, err) testCRL(t, string(crlFile)) }
77-105: Missingt.Parallel()call.📝 Suggested fix
func Test_CRL_Server(t *testing.T) { + t.Parallel() var network stringtls_provider.go (2)
42-44: Consider avoiding mutation of receiver fields.
ProvideServerTLS()mutatesp.TLSMinVersionwhen defaulting. This side effect could surprise callers who inspect the provider after calling the method. Consider using a local variable instead:+ minVersion := p.TLSMinVersion + if minVersion == 0 { + minVersion = tls.VersionTLS12 + } - if p.TLSMinVersion == 0 { - p.TLSMinVersion = tls.VersionTLS12 - } - - if p.TLSMinVersion != tls.VersionTLS12 && p.TLSMinVersion != tls.VersionTLS13 { + if minVersion != tls.VersionTLS12 && minVersion != tls.VersionTLS13 { return nil, errors.New("tls: unsupported TLS version, please use tls.VersionTLS12 or tls.VersionTLS13") }The same pattern applies to
ServerCertificateProvider.ProvideServerTLS()at lines 99-101.
136-141: RedundantMinVersionassignment.Line 137 sets
tlsConfig.MinVersionagain, butsetCert()(lines 108-112) already sets it. Remove the redundant assignment:if tlsConfig != nil { - tlsConfig.MinVersion = p.TLSMinVersion tlsHandler := &TLSHandler{} tlsConfig.GetCertificate = tlsHandler.GetClientInfo return tlsConfig, nil }addon/verifypeer/ocsp_responder_test.go (1)
132-133: Unnecessary error wrapping without context.
fmt.Errorf("%w", err)adds no additional context. Either return the error directly or add a descriptive message:- return nil, fmt.Errorf("%w", err) + return nil, errOr with context:
- return nil, fmt.Errorf("%w", err) + return nil, fmt.Errorf("failed to parse OCSP request: %w", err)This pattern appears throughout the file (lines 187, 196, 207, 210, 223, 239, 279, 286, 291, 300, 305).
|
In my opinion this is "too much of a change" for something no one has requested on the |
We should have discussed implementation details in an issue before proceeding with a more complex approach for something that should have been straightforward. |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@addon/verifypeer/crl.go`:
- Around line 53-63: CustomizeServerTLS is fetching clientCACert via
mtls.ClientCertificate() but never configures the server to require client
certs, so VerifyPeerCertificate won't run; update the CustomizeServerTLS method
on MutualTLSWithCRLCustomizer to set config.ClientAuth =
tls.RequireAndVerifyClientCert (or appropriate auth mode) and config.ClientCAs =
x509.NewCertPool() then add clientCACert into that pool (use the returned
clientCACert), ensuring VerifyPeerCertificate remains assigned on config so CRL
checks execute; reference the CustomizeServerTLS method,
MutualTLSWithCRLCustomizer type, mtls.ClientCertificate(), and
VerifyPeerCertificate when making the change.
In `@addon/verifypeer/ocsp.go`:
- Around line 46-60: CustomizeServerTLS in MutualTLSWithOCSPCustomizer retrieves
a client CA cert but never wires mutual TLS into the provided tls.Config; after
calling mtls.ClientCertificate() grab the returned clientCACert and (1) ensure
config.ClientAuth is set to tls.RequireAndVerifyClientCert (or
tls.VerifyClientCertIfGiven if desired) and (2) add the client CA to
config.ClientCAs by creating or appending to a x509.CertPool and calling
AppendCertsFromPEM/ AddCert as appropriate; update the method to perform these
steps (referencing CustomizeServerTLS, MutualTLSWithOCSPCustomizer,
MutualTLSCustomizer, and ClientCertificate) so client certificates are actually
requested and verified.
♻️ Duplicate comments (2)
addon/verifypeer/ocsp.go (2)
64-67: Fail fast on invalid explicit OCSPServer configuration.When
m.OCSPServeris explicitly provided but invalid, the validation error is silently ignored, which could mask misconfiguration and weaken security. Return the error instead.🐛 Proposed fix
if m.OCSPServer != "" { - if err := m.validateOSCPServer(m.OCSPServer); err == nil { - ocspServer = m.OCSPServer + if err := m.validateOCSPServer(m.OCSPServer); err != nil { + return err } + ocspServer = m.OCSPServer } else {
83-89: Guard against nilTLSProviderfor HTTPS OCSP endpoints.When
ocspURL.Scheme == "https"andm.TLSProvideris nil, line 84 will panic with a nil pointer dereference. Add a nil check before callingProvideClientTLS.🐛 Proposed fix
if ocspURL.Scheme == "https" { + if m.TLSProvider == nil { + return errors.New("mtls: TLSProvider is required for https OCSP server") + } tlsConfig, err := m.TLSProvider.ProvideClientTLS() if err != nil { return err } cc.SetTLSConfig(tlsConfig) }
🧹 Nitpick comments (10)
tls_provider.go (3)
17-17: Typo in documentation: "Implentations" → "Implementations".- // NOTE: Implentations may return (nil, nil) if no tls.Config can be provided and no error occurred. + // NOTE: Implementations may return (nil, nil) if no tls.Config can be provided and no error occurred.
42-44: Avoid mutating the receiver's state.Directly assigning
p.TLSMinVersion = tls.VersionTLS12modifies the receiver, which could cause unexpected behavior if the provider is reused or inspected after callingProvideServerTLS(). Consider using a local variable instead.♻️ Proposed fix
func (p *ACMECertificateProvider) ProvideServerTLS() (*tls.Config, error) { var tlsConfig *tls.Config + minVersion := p.TLSMinVersion - if p.TLSMinVersion == 0 { - p.TLSMinVersion = tls.VersionTLS12 + if minVersion == 0 { + minVersion = tls.VersionTLS12 } - if p.TLSMinVersion != tls.VersionTLS12 && p.TLSMinVersion != tls.VersionTLS13 { + if minVersion != tls.VersionTLS12 && minVersion != tls.VersionTLS13 { return nil, errors.New("tls: unsupported TLS version, please use tls.VersionTLS12 or tls.VersionTLS13") } if p.AutoCertManager != nil { tlsConfig = &tls.Config{ //nolint:gosec // This is a user input - MinVersion: p.TLSMinVersion, + MinVersion: minVersion, GetCertificate: p.AutoCertManager.GetCertificate, NextProtos: []string{"http/1.1", "acme-tls/1"}, } }
99-101: Same mutation concern asACMECertificateProvider.This method also mutates
p.TLSMinVersiondirectly. Apply the same local-variable pattern for consistency.addon/verifypeer/ocsp.go (4)
19-19: Incorrect interface name in documentation.The comment references
ServerCertificateCustomizerbut the implemented interface isServerTLSCustomizer.-// MutualTLSWithOCSPCustomizer is a struct implementing [fiber.ServerCertificateCustomizer]. +// MutualTLSWithOCSPCustomizer is a struct implementing [fiber.ServerTLSCustomizer].
39-40: Documentation mismatch: default is 10 seconds, not 1.The comment says
1 * time.Secondsbut line 54 sets the default to10 * time.Second.- // Default: 1 * time.Seconds + // Default: 10 * time.Second Timeout time.Duration `json:"timeout"`
91-92: Add defensive bounds check onverifiedChains.Accessing
verifiedChains[0][0]without checking if the slice is non-empty could panic in edge cases. While the TLS library typically ensures at least one chain exists, a defensive check improves robustness.♻️ Proposed fix
config.VerifyPeerCertificate = func(_ [][]byte, verifiedChains [][]*x509.Certificate) error { + if len(verifiedChains) == 0 || len(verifiedChains[0]) == 0 { + return errors.New("mtls: no verified certificate chain") + } cert := verifiedChains[0][0]
132-132: Typo in method name: "OSCP" → "OCSP".The method name
validateOSCPServerhas a transposition error. Should bevalidateOCSPServerto match the standard OCSP (Online Certificate Status Protocol) acronym.addon/verifypeer/crl.go (3)
19-19: Incorrect interface name in documentation.Same issue as in
ocsp.go- the comment referencesServerCertificateCustomizerbut the implemented interface isServerTLSCustomizer.-// MutualTLSWithCRLCustomizer is a struct implementing [fiber.ServerCertificateCustomizer]. +// MutualTLSWithCRLCustomizer is a struct implementing [fiber.ServerTLSCustomizer].
46-47: Minor typo: "time.Seconds" → "time.Second".- // Default: 10 * time.Seconds + // Default: 10 * time.Second Timeout time.Duration `json:"timeout"`
109-110: Add defensive bounds check onverifiedChains.Same concern as in
ocsp.go- add a check before accessingverifiedChains[0][0].♻️ Proposed fix
if clientCRL != nil { config.VerifyPeerCertificate = func(_ [][]byte, verifiedChains [][]*x509.Certificate) error { + if len(verifiedChains) == 0 || len(verifiedChains[0]) == 0 { + return errors.New("tls: no verified certificate chain") + } cert := verifiedChains[0][0]
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@addon/verifypeer/crl.go`:
- Around line 106-112: After parsing crlBytes into crl via
x509.ParseRevocationList, validate the CRL signature before assigning to
clientCRL: call crl.CheckSignatureFrom(...) using the client CA certificate
object used for client cert verification (e.g., clientCACert or issuerCert) and
return an error if the signature check fails; only set clientCRL = crl when
CheckSignatureFrom succeeds to prevent using tampered revocation lists.
In `@client/tls_provider.go`:
- Around line 63-112: Change initTLS to return an error and have setCertificate
return an error too; inside initTLS, when p.IncludeSystemCertificates is true
propagate and return the error from x509.SystemCertPool() instead of falling
back to x509.NewCertPool(); update all callers (the conditional call guarded by
p.IncludeSystemCertificates, setCertificate, and appendRootAndReturn) to handle
and return the error up the stack; ensure tlsConfig remains initialized on
success and keep existing error wrapping for tls.X509KeyPair /
tls.LoadX509KeyPair but propagate those errors from the p.Certificate handling
so caller sees failures.
In `@docs/client/rest.md`:
- Around line 376-383: The docs for SetTLSProvider contain a typo ("ths" ->
"the") and use hard tabs that violate markdownlint; update the
heading/description to "Sets the TLS configuration for the client, using the
`ClientTLSProvider` interface." and replace any hard tabs with spaces in this
section (including the code block for the ClientTLSProvider interface and the
ProvideClientTLS() signature), then run `make markdown` to reformat and validate
the documentation.
|
Hi @gaby ,
I tried to discuss this on Discord and you replied to me I did not need to have anything else than files to setup TLS. I also did post a preliminary implementation I posted on Discord, without replies : https://github.com/simonnix/fbrtls .
I am sorry I told you I never posted a PR before on Fiber. I should have opened an issue maybe, but I preferred tried to make it. The change in usage is minimal. I think it is easier like that than having And it lets the developer configure the |
a522b7a to
eaa4adc
Compare
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Fix all issues with AI agents
In `@addon/verifypeer/crl.go`:
- Around line 46-47: The comment above the Timeout field is incorrect: change
the comment string from "Default: 10 * time.Seconds" to "Default: 10 *
time.Second" to reference the correct Go time constant used by the Timeout field
(Timeout time.Duration) in this file.
- Around line 162-170: The code calls cc.Get(distURL.String()) in the CRL fetch
path and then blindly passes resp.Body to m.decodePemCrl without validating the
HTTP response; update the logic in the function that uses cc.Get and
m.decodePemCrl (the CRL fetch routine using distURL) to check resp.StatusCode
(e.g., ensure 200 <= status < 300) before attempting to parse the body, close
resp.Body on all paths, and return a clear formatted error containing the URL
and the HTTP status (and optionally status text) when the response is not
successful so callers see the HTTP failure instead of a PEM parsing error.
In `@client/tls_provider.go`:
- Around line 13-22: Fix the typos in the ClientTLSProvider interface doc
comments: change the mention of "ProviderClientTLSConfig" to the actual method
name "ProvideClientTLS" and correct "Implentations" to "Implementations" in the
note describing return behavior; ensure the doc text matches the
ProvideClientTLS() method signature and reads clearly.
- Around line 34-45: Update the field comments for RootCertificates and
IncludeSystemCertificates in tls_provider.go to fix typos: change
"authentificate" to "authenticate" in the RootCertificates comment and remove
the extra space in "allows to inherit" so it reads "allows to inherit" in the
IncludeSystemCertificates comment; keep the rest of the documentation text and
tags unchanged.
🧹 Nitpick comments (2)
addon/verifypeer/crl.go (2)
117-127: Add defensive bounds check inVerifyPeerCertificatecallback.The callback directly accesses
verifiedChains[0][0]without verifying that the slices are non-empty. While withRequireAndVerifyClientCertthe chains should be populated, adding a defensive check prevents potential panics from unexpected edge cases.Proposed fix
if clientCRL != nil { config.VerifyPeerCertificate = func(_ [][]byte, verifiedChains [][]*x509.Certificate) error { + if len(verifiedChains) == 0 || len(verifiedChains[0]) == 0 { + return errors.New("tls: no verified certificate chains") + } cert := verifiedChains[0][0] for _, revokedCertificate := range clientCRL.RevokedCertificateEntries { if revokedCertificate.SerialNumber.Cmp(cert.SerialNumber) == 0 { return errors.New("tls: the certificate was revoked") } } return nil } }
93-102: Silent fallback when CRL distribution point fetches fail.When
RevocationListis not explicitly configured and fetching from allCRLDistributionPointsfails, the connection proceeds without any revocation checking. This fail-open behavior could allow revoked certificates to authenticate successfully.Consider either:
- Documenting this behavior explicitly in the struct/method comments
- Adding a
FailOnCRLUnavailable boolfield to optionally enforce fail-closed behaviorIs this fail-open behavior intentional? If users expect revocation checking when a CA has distribution points configured, silent failures could be a security gap.
eaa4adc to
6a1ab88
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@addon/verifypeer/crl.go`:
- Around line 19-22: The doc comments for MutualTLSWithCRLCustomizer incorrectly
reference fiber.ServerCertificateCustomizer; update the comments to reference
the correct interface name fiber.ServerTLSCustomizer wherever it appears
(including the comment block above the MutualTLSWithCRLCustomizer type and the
secondary comment near its methods) so documentation matches the actual
interface.
♻️ Duplicate comments (4)
client/tls_provider.go (2)
13-21: Fix typos inClientTLSProviderdocs.Line 15 and Line 18 still refer to
ProviderClientTLSConfigand “Implentations”. These should match the method name and correct spelling.📝 Proposed fix
- // ProviderClientTLSConfig provides possibly a *tls.Config object to be used by a client or + // ProvideClientTLS provides possibly a *tls.Config object to be used by a client or @@ - // NOTE: Implentations may return (nil, nil) if no tls.Config can be provided and no error occurred. + // NOTE: Implementations may return (nil, nil) if no tls.Config can be provided and no error occurred.
34-43: Correct wording in field comments.Line 37 uses “authentificate” and Line 42 has a double space. Please fix the wording.
📝 Proposed fix
- // permitting to authentificate a remote server. + // permitting to authenticate a remote server. @@ - // IncludeSystemCertificates allows to inherit CA certificates from the system. + // IncludeSystemCertificates allows to inherit CA certificates from the system.addon/verifypeer/crl.go (2)
46-47: Typo in default timeout comment.Use
time.Second, nottime.Seconds.📝 Proposed fix
- // Default: 10 * time.Seconds + // Default: 10 * time.Second
162-170: Check HTTP status before parsing the CRL response.Non-2xx responses should surface as HTTP errors, not PEM parse failures.
🐛 Proposed fix
resp, err := cc.Get(distURL.String()) if err != nil { return []byte{}, fmt.Errorf("tls: unable to fetch certificate revocation list from url=%q: %w", distURL.String(), err) } + if resp.StatusCode() < 200 || resp.StatusCode() >= 300 { + return []byte{}, fmt.Errorf("tls: failed to fetch CRL from url=%q: HTTP status %d", distURL.String(), resp.StatusCode()) + } + if bytes, ok := m.decodePemCrl(resp.Body()); ok { return bytes, nil }
🧹 Nitpick comments (1)
addon/verifypeer/crl.go (1)
117-126: Preserve any existingVerifyPeerCertificatehook.Setting
config.VerifyPeerCertificatehere overwrites any prior customizer (e.g., OCSP). Consider chaining the previous hook so multiple customizers compose instead of clobbering.♻️ Proposed refactor
if clientCRL != nil { - config.VerifyPeerCertificate = func(_ [][]byte, verifiedChains [][]*x509.Certificate) error { + prevVerify := config.VerifyPeerCertificate + config.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { + if prevVerify != nil { + if err := prevVerify(rawCerts, verifiedChains); err != nil { + return err + } + } cert := verifiedChains[0][0] for _, revokedCertificate := range clientCRL.RevokedCertificateEntries { if revokedCertificate.SerialNumber.Cmp(cert.SerialNumber) == 0 { return errors.New("tls: the certificate was revoked") } } return nil } }
0664e7b to
f1ecc57
Compare
|
Hi, every issues from AI bots have been resolved. Do you want me to squash my commits before your reviews ? Let me know. |
|
@simonnix You can squash them, but this solution is too convoluted and complex for something that was just a matter of adding few new fields in the config. |
f1ecc57 to
869c9b3
Compare
There was a problem hiding this comment.
Actionable comments posted: 12
🤖 Fix all issues with AI agents
In `@addon/verifypeer/cert_test.go`:
- Around line 64-72: Both Test_Cert_File and Test_Cert_Content are missing
t.Parallel(); add t.Parallel() as the first statement in each test function
(Test_Cert_File and Test_Cert_Content) so they run concurrently; ensure the call
appears before any setup or calls to testCert to follow Go testing concurrency
guidelines.
In `@addon/verifypeer/listen_test.go`:
- Around line 36-39: The test uses assert.NoError inside background goroutines
calling app.Shutdown(), which can miss failures; change each goroutine (the ones
invoking app.Shutdown() at the places using time.Sleep) to send the returned
error on a channel (or assign to a synced variable) and then receive that error
in the main test goroutine and call assert.NoError on it; update the instances
around app.Shutdown() (and their anonymous goroutines) so the shutdown result is
forwarded and asserted synchronously in the test instead of asserting inside the
goroutine.
In `@addon/verifypeer/ocsp.go`:
- Around line 53-55: The function currently applies the same default for
m.Timeout twice; remove the duplicate check/assignment so only the initial
defaulting (the first "if m.Timeout == 0 { m.Timeout = 10 * time.Second }"
block) remains and delete the later identical block that reassigns m.Timeout to
10 * time.Second to avoid redundant logic.
In `@client/tls_provider_test.go`:
- Around line 47-59: In Test_ClientTLSProvider_RootCertificatesFromString move
the defer that closes file so it runs only after confirming os.Open succeeded:
call require.NoError(t, err) (checking the error returned by os.Open) before
deferring file.Close(), so the defer uses a non-nil file; ensure the existing
require.NoError calls for ReadAll remain unchanged and reference the local
variable file used by the defer.
In `@docs/addon/verifypeer.md`:
- Line 48: Correct the typo in the markdown sentence "See [OpenSSL
documenation](https://docs.openssl.org/3.5/man1/openssl-ca/#crl-options) for
more information." by changing "documenation" to "documentation" so the link
text reads "OpenSSL documentation"; update the text/anchor where that exact
string appears (the link text "OpenSSL documenation") to preserve the same URL
but fix the spelling.
- Around line 7-8: Fix typos in the VerifyPeer addon introduction: change
"aadditional" to "additional" and "implementationsto" to "implementations to" in
the paragraph that describes the VerifyPeer package and its implementation of
fiber.ServerTLSCustomizer to configure a *tls.Config's VerifyPeerCertificate for
client mTLS validation; after editing the docs/addon/verifypeer.md run `make
markdown`.
- Line 12: The docs incorrectly reference the interface as
fiber.ServerCertificateCustomizer; update all occurrences to the correct
interface name fiber.ServerTLSCustomizer in the verifypeer.md documentation
where MutualTLSCustomizer is described, and ensure any mentions alongside
MutualTLSCustomizer and tls_provider.go reflect the corrected symbol name.
In `@docs/api/fiber.md`:
- Line 120: Typo in the docs: change the word "rewiewing" to "reviewing" in the
table row describing TLSConfigFunc; update the sentence for the <Reference
id="tlsconfigfunc">TLSConfigFunc</Reference> entry that references `TLSProvider`
and `tlsConfig` so it reads "Allows reviewing and customizing `tls.Config`
returned from `TLSProvider` as you want. `tlsConfig` may be `nil` if none is
returned from `TLSProvider`."
- Line 173: Fix the grammar in the sentence referencing `Certificate`: remove
the redundant word "also" so the phrase reads "Certificate can also be a string
containing what that file should contain." Locate the line containing the
backticked `Certificate` in docs/api/fiber.md and update the sentence
accordingly.
In `@docs/whats_new.md`:
- Line 192: Fix the typo in the sentence "We provider now two interfaces and
implementations to let you configure TLS in `ListenConfig`" by replacing
"provider" with "provide" so it reads "We provide now two interfaces and
implementations to let you configure TLS in `ListenConfig`." Ensure the updated
text preserves the backticks around `ListenConfig`.
In `@listen_test.go`:
- Around line 225-228: Replace the deprecated CertFile/CertKeyFile fields on
ServerCertificateProvider with the single Certificate field pointing to the
fullchain.pem file (which includes cert, key, and intermediates) and add
t.Parallel() as the first statement in the test function; specifically, update
the ServerCertificateProvider initializer to use Certificate:
"./.github/testdata/pki/intermediate/server/fullchain.pem" instead of
CertFile/CertKeyFile and insert t.Parallel() at the top of the test (the test
that configures ServerCertificateProvider).
In `@tls_provider.go`:
- Around line 14-20: Fix the documentation typo in the comment for
ProvideServerTLS: change "Implentations" to "Implementations" in the comment
block above the ProvideServerTLS method (the comment that mentions returning
"(nil, nil)" and examples about certificate paths) so the doc reads correctly.
🧹 Nitpick comments (8)
client/tls_provider.go (1)
56-107: LGTM with one minor suggestion.The TLS configuration logic is well-structured with proper error propagation. The approach of using the same file for both cert and key parameters in
X509KeyPair/LoadX509KeyPairis correct since theCertificatefield is documented to contain both certificate and key in PEM format.📝 Minor: Consider aligning error message with field name
The error messages reference "CertificateChain" but the field is named "Certificate":
- return nil, fmt.Errorf("clienttls: cannot load TLS key pair from CertificateChain: %w", err) + return nil, fmt.Errorf("clienttls: cannot load TLS key pair from Certificate: %w", err)- return nil, fmt.Errorf("clienttls: cannot load TLS key pair from CertificateChain: %w", err) + return nil, fmt.Errorf("clienttls: cannot load TLS key pair from Certificate: %w", err)addon/verifypeer/ocsp.go (1)
105-111: Consider adding a defensive bounds check on verifiedChains.While Go's TLS stack guarantees
verifiedChainsis non-empty whenRequireAndVerifyClientCertis set, a defensive check would make the code more robust against unexpected edge cases.♻️ Proposed defensive check
config.VerifyPeerCertificate = func(_ [][]byte, verifiedChains [][]*x509.Certificate) error { + if len(verifiedChains) == 0 || len(verifiedChains[0]) == 0 { + return errors.New("mtls: no verified certificate chain") + } cert := verifiedChains[0][0] opts := &ocsp.RequestOptions{Hash: crypto.SHA256}addon/verifypeer/ocsp_responder_test.go (1)
146-158: Use structured logging format for consistency.The
log.Infoandlog.Warncalls at lines 147, 150, and 270 use printf-style formatting (e.g.,log.Info("Found entry %+v", ent)), but Fiber's logger typically expects structured arguments. Consider usinglog.Infoforlog.Infowfor format strings.♻️ Proposed fix
- log.Info("Found entry %+v", ent) + log.Infof("Found entry %+v", ent) switch ent.Status { case StatusRevoked: - log.Warn("This certificate is revoked") + log.Warnf("This certificate is revoked")addon/verifypeer/cert_test.go (1)
29-47: Consider using a channel for server readiness instead of sleep.The
time.Sleep(500 * time.Millisecond)at line 45 is fragile—under heavy load, the server may not be ready in time. Consider using a channel signaled fromListenerAddrFuncto ensure the server is actually listening before proceeding with client requests.Additionally, the
assert.NoErrorin the goroutine (line 30) may not properly fail the test if the assertion fails.♻️ Proposed improvement
func testCert(t *testing.T, cert string) { t.Helper() - var network string + ready := make(chan string, 1) + listenErr := make(chan error, 1) // App app := fiber.New() app.Use(logger.New()) app.Get("/", func(c fiber.Ctx) error { return c.SendString("Hello") }) go func() { - assert.NoError(t, app.Listen(":0", fiber.ListenConfig{ + listenErr <- app.Listen(":0", fiber.ListenConfig{ DisableStartupMessage: true, ListenerNetwork: fiber.NetworkTCP4, ListenerAddrFunc: func(addr net.Addr) { - network = addr.String() + ready <- addr.String() }, // ... - })) + }) }() - time.Sleep(500 * time.Millisecond) - appPort, found := strings.CutPrefix(network, "0.0.0.0:") + network := <-ready + appPort, found := strings.CutPrefix(network, "0.0.0.0:") require.True(t, found)addon/verifypeer/crl_test.go (4)
66-68: Missingt.Parallel()call.Per coding guidelines, Go tests should invoke
t.Parallel()at the start of each test to maximize concurrency.Proposed fix
func Test_CRL_File(t *testing.T) { + t.Parallel() testCRL(t, "../../.github/testdata/pki/intermediate/crl.pem") }
70-75: Missingt.Parallel()call.Per coding guidelines, Go tests should invoke
t.Parallel()at the start of each test.Proposed fix
func Test_CRL_Content(t *testing.T) { + t.Parallel() crlFile, err := os.ReadFile(filepath.Clean("../../.github/testdata/pki/intermediate/crl.pem")) require.NoError(t, err) testCRL(t, string(crlFile)) }
77-105: Missingt.Parallel()call.Per coding guidelines, Go tests should invoke
t.Parallel()at the start of each test.Proposed fix
func Test_CRL_Server(t *testing.T) { + t.Parallel() var network string
47-49: Consider replacingtime.Sleepwith a synchronization primitive.Using
time.Sleepfor server startup synchronization can cause flaky tests. Consider using a channel orsync.WaitGroupto signal when the server is ready.Example using a channel
+ ready := make(chan struct{}) go func() { assert.NoError(t, app.Listen(":0", fiber.ListenConfig{ DisableStartupMessage: true, ListenerNetwork: fiber.NetworkTCP4, ListenerAddrFunc: func(addr net.Addr) { network = addr.String() + close(ready) }, TLSProvider: &fiber.ServerCertificateProvider{ Certificate: "../../.github/testdata/pki/intermediate/server/fullchain.pem", }, TLSCustomizer: &verifypeer.MutualTLSWithCRLCustomizer{ Certificate: "../../.github/testdata/pki/intermediate/cacert.pem", RevocationList: crl, }, })) }() - time.Sleep(500 * time.Millisecond) + <-ready
|
@gaby We can talk about it here. Should What would be great for you, maintainer, and for us developers too ? How should we proceed ? Let me know. |
869c9b3 to
1cc10b4
Compare
There was a problem hiding this comment.
Actionable comments posted: 10
🤖 Fix all issues with AI agents
In `@addon/verifypeer/cert_test.go`:
- Around line 29-43: The test spawns app.Listen in a goroutine and uses
assert.NoError(t, app.Listen(...)) there, which can miss failures because
assertions in goroutines don't fail the test reliably; modify the goroutine to
send the error returned by app.Listen to a channel (e.g., listenErrCh), replace
assert.NoError inside the goroutine with sending the error, and after triggering
shutdown (or after the test logic completes) receive from that channel and call
assert.NoError(t, listenErr) to propagate any Listen errors; apply this change
around the app.Listen call that configures ListenerAddrFunc, TLSProvider, and
TLSCustomizer (verifypeer.MutualTLSCustomizer) so Listen failures are observed
by the test.
In `@addon/verifypeer/crl.go`:
- Around line 162-173: The HTTP response from cc.Get in crl.go (resp) is never
closed, causing connection leaks; after the successful cc.Get(distURL.String())
and err check in the function that calls m.decodePemCrl, immediately ensure the
response body is closed in all return paths (e.g., add a defer resp.Body.Close()
right after obtaining resp) so that the subsequent StatusCode check, the call to
m.decodePemCrl(resp.Body), and any error returns still release the underlying
connection.
In `@addon/verifypeer/ocsp_responder_test.go`:
- Around line 136-144: The log.Errorf calls in ocsp_responder_test.go use the
fmt wrapping verb "%w" which is invalid for logging and produces garbage output;
update the logging in the error branches of verifyIssuer and getIndexEntry (the
calls currently using log.Errorf("%w", err)) to use a proper formatting verb
like "%v" or "%s" and include a short contextual message (e.g., "verifyIssuer
error: %v" and "getIndexEntry error: %v") so the actual error text is logged.
In `@addon/verifypeer/ocsp.go`:
- Around line 19-46: Update the struct and method comments to reference the
actual interface name fiber.ServerTLSCustomizer instead of
ServerCertificateCustomizer, and correct the Timeout default comment to match
the real value (use "1 * time.Second" or whatever the implementation uses) for
the Timeout field on MutualTLSWithOCSPCustomizer; ensure the top comment
mentions MutualTLSWithOCSPCustomizer implements fiber.ServerTLSCustomizer and
the CustomizeServerTLS method doc also references fiber.ServerTLSCustomizer, and
update the Timeout field's "Default" text to the exact time.Duration literal
used in code.
In `@client/client_test.go`:
- Around line 1891-1898: The test Test_Client_R currently asserts
reflect.TypeFor[Request]().Name() which only checks the Request type's name
rather than the runtime type of req; update the assertion to validate req's
concrete type returned by client.R()—for example use require.IsType(t,
&Request{}, req) or require.Equal(t, reflect.TypeOf(&Request{}).Elem(),
reflect.TypeOf(req).Elem()) so that req (from New() and R()) is actually a
Request instance; modify the assertions around New(), R(), and req accordingly.
In `@client/tls_provider.go`:
- Around line 95-104: The error messages in the TLS loading branch incorrectly
refer to "CertificateChain"; update the fmt.Errorf calls in the code paths that
call tls.X509KeyPair and tls.LoadX509KeyPair to mention p.Certificate (e.g.,
"clienttls: cannot load TLS key pair from Certificate") so the message correctly
references the Certificate field; locate the branches around p.Certificate,
tls.X509KeyPair, tls.LoadX509KeyPair and setCertificate and change the error
text accordingly.
In `@docs/addon/verifypeer.md`:
- Around line 75-83: Update the docs to use the standard term "OCSP Stapling"
instead of "OCSP Stapler": change the heading "MutualTLS with OCSP Stapler" and
any occurrences of "OCSP Stapler" in the description to "MutualTLS with OCSP
Stapling" / "OCSP Stapling", keeping references to the struct
MutualTLSWithOCSPCustomizer and fiber.ServerTLSCustomizer intact; then run make
markdown to regenerate the documentation outputs per guidelines.
In `@docs/api/fiber.md`:
- Around line 156-158: The TLS interface code blocks use hard tabs; replace tabs
with spaces in the ServerTLSProvider snippet (the type ServerTLSProvider
interface { ProvideServerTLS() (*tls.Config, error) }) and any other TLS
interface snippets (e.g., the other occurrence) so the code fences contain
spaces only, then run make markdown to reformat/update generated docs.
In `@tls_provider.go`:
- Line 38: The doc comment above ProvideServerTLS incorrectly references
[ServerTLSConfigProvider]; update the comment to reference the correct interface
name [ServerTLSProvider] (or remove the bracketed link if not needed) so the
comment matches the actual interface; locate the ProvideServerTLS function and
change its comment to mention ServerTLSProvider to keep docs and code
consistent.
- Line 95: Update the doc comment above ProvideServerTLS to reference the
correct interface name (replace the incorrect [ServerTLSConfigProvider] with the
actual interface implemented, e.g. [ServerTLSProvider]) so the comment
accurately documents that ProvideServerTLS implements the proper interface.
♻️ Duplicate comments (3)
docs/addon/verifypeer.md (1)
7-8: Fix grammar in the VerifyPeer introduction.The sentence is ungrammatical and has a spacing error around
*tls.Config's.✏️ Suggested edit
-The VerifyPeer addon package aim is to provides additional `fiber.ServerTLSCustomizer` interface implementations to add means -to configure a `*tls.Config` 's `VerifyPeerCertificate` field with additional validation for client mTLS connection. +The VerifyPeer addon package aims to provide additional `fiber.ServerTLSCustomizer` interface implementations to configure +a `*tls.Config`'s `VerifyPeerCertificate` field with additional validation for client mTLS connections.Please run
make markdownafter updating the docs. As per coding guidelines.addon/verifypeer/crl.go (1)
52-53: Fix interface name in CustomizeServerTLS docstring.✏️ Suggested edit
-// CustomizeServerTLS implements [fiber.ServerCertificateCustomizer] +// CustomizeServerTLS implements [fiber.ServerTLSCustomizer]addon/verifypeer/listen_test.go (1)
70-73: Assertions in goroutines may not fail the test properly.Using
assert.NoErrorinside a goroutine won't reliably fail the test because the goroutine may outlive the test function. The first test (Test_Listen_MutualTLS) correctly uses a channel pattern. Apply the same pattern here for consistency.♻️ Suggested fix
+ shutdownErr := make(chan error, 1) go func() { time.Sleep(1000 * time.Millisecond) - assert.NoError(t, app.Shutdown()) + shutdownErr <- app.Shutdown() }() require.NoError(t, app.Listen(":0", fiber.ListenConfig{ // ... })) + require.NoError(t, <-shutdownErr)The same issue exists in
Test_Listen_MutualTLS_Prefork(lines 109-112) andTest_Listen_MutualTLS_Prefork_Deprecated(lines 149-152).
🧹 Nitpick comments (10)
client/client.go (1)
247-257: Avoid holding the client lock during provider I/O and skip nil configs.
ProvideClientTLSmay perform I/O; holdingc.mucan block unrelated setters. Also, the interface allows(nil, nil)—currently that clears any existing TLS config. If that’s not intended, guardconfig != niland apply under the lock only after the provider call.♻️ Suggested refactor
func (c *Client) SetTLSProvider(provider ClientTLSProvider) *Client { - c.mu.Lock() - defer c.mu.Unlock() - if provider != nil { - if config, err := provider.ProvideClientTLS(); err != nil { - c.logger.Panicf("client: unable to set tlsConfig: %v", err) - } else { - c.applyTLSConfig(config) - } - } + if provider == nil { + return c + } + config, err := provider.ProvideClientTLS() + if err != nil { + c.logger.Panicf("client: unable to set tlsConfig: %v", err) + return c + } + if config == nil { + return c + } + c.mu.Lock() + defer c.mu.Unlock() + c.applyTLSConfig(config) return c }client/tls_provider.go (1)
56-76: Avoid mutating provider state inside ProvideClientTLS.Mutating
p.TLSMinVersioncan introduce data races if the provider is reused across goroutines. Prefer a local default.♻️ Suggested refactor
- if p.TLSMinVersion == 0 { - p.TLSMinVersion = tls.VersionTLS12 - } + minVersion := p.TLSMinVersion + if minVersion == 0 { + minVersion = tls.VersionTLS12 + } @@ - tlsConfig = &tls.Config{ //nolint:gosec // This is a user input - MinVersion: p.TLSMinVersion, + tlsConfig = &tls.Config{ //nolint:gosec // This is a user input + MinVersion: minVersion, }docs/client/rest.md (1)
376-406: Minor grammar fix needed.Line 399 is missing a comma for proper sentence structure.
-If the server can be authenticated using one of the system root certificates, you can also load them : +If the server can be authenticated using one of the system root certificates, you can also load them:Also, as per coding guidelines, please run
make markdownto lint all Markdown files after updating.tls_provider.go (1)
42-44: Consider avoiding mutation of receiver fields.Both
ACMECertificateProvider.ProvideServerTLS()andServerCertificateProvider.ProvideServerTLS()mutatep.TLSMinVersionwhen it's zero. This side effect could cause unexpected behavior if the provider instance is reused or inspected after callingProvideServerTLS().Consider using a local variable instead:
♻️ Suggested refactor for ACMECertificateProvider
func (p *ACMECertificateProvider) ProvideServerTLS() (*tls.Config, error) { var tlsConfig *tls.Config + tlsMinVersion := p.TLSMinVersion - if p.TLSMinVersion == 0 { - p.TLSMinVersion = tls.VersionTLS12 + if tlsMinVersion == 0 { + tlsMinVersion = tls.VersionTLS12 } - if p.TLSMinVersion != tls.VersionTLS12 && p.TLSMinVersion != tls.VersionTLS13 { + if tlsMinVersion != tls.VersionTLS12 && tlsMinVersion != tls.VersionTLS13 { return nil, errors.New("tls: unsupported TLS version, please use tls.VersionTLS12 or tls.VersionTLS13") } if p.AutoCertManager != nil { tlsConfig = &tls.Config{ - MinVersion: p.TLSMinVersion, + MinVersion: tlsMinVersion, GetCertificate: p.AutoCertManager.GetCertificate, NextProtos: []string{"http/1.1", "acme-tls/1"}, } }Apply a similar pattern to
ServerCertificateProvider.Also applies to: 99-101
addon/verifypeer/listen_test.go (1)
16-19: Usinggo:linknameto access internal test variables is fragile.While
go:linknameworks, it couples this test to the internal implementation of the fiber package. IftestPreforkMasteris renamed or removed, these tests will fail at link time with a cryptic error. Consider whether there's a cleaner way to test prefork behavior, such as exposing a test helper function.addon/verifypeer/cert_test.go (1)
45-47: Race condition:time.Sleepis unreliable for server startup synchronization.The test uses
time.Sleep(500ms)to wait for the server to start, butListenerAddrFuncis already being used to capture the address. Consider using a channel to signal when the listener is ready:♻️ Suggested fix
+ ready := make(chan struct{}) go func() { assert.NoError(t, app.Listen(":0", fiber.ListenConfig{ DisableStartupMessage: true, ListenerNetwork: fiber.NetworkTCP4, ListenerAddrFunc: func(addr net.Addr) { network = addr.String() + close(ready) }, // ... })) }() - time.Sleep(500 * time.Millisecond) + <-ready appPort, found := strings.CutPrefix(network, "0.0.0.0:")listen.go (1)
142-159: Consider warning or erroring when TLSCustomizer is set but TLSProvider returns nil.When
TLSProvider.ProvideServerTLS()returns(nil, nil)(which is valid per the interface contract), theTLSCustomizeris silently skipped because of theif tlsConfig != nilcheck. This could lead to confusing behavior where users set a customizer expecting it to run, but it never does.Consider logging a warning or returning an error when
TLSCustomizeris set buttlsConfigis nil:♻️ Suggested enhancement
if tlsConfig != nil { if cfg.TLSCustomizer != nil { if err := cfg.TLSCustomizer.CustomizeServerTLS(tlsConfig); err != nil { return err } } + } else if cfg.TLSCustomizer != nil { + log.Warn("TLSCustomizer is set but TLSProvider returned nil config; customizer will not be applied") }listen_test.go (1)
187-208: Consider addingt.Parallel()for test concurrency.This test binds to port
:0and uses an in-memory shutdown pattern, making it safe for parallel execution. Per coding guidelines, tests should invoket.Parallel()at the start.Suggested fix
func Test_App_Listener_TLS_Listener(t *testing.T) { + t.Parallel() // Create tls certificate cer, err := tls.LoadX509KeyPair("./.github/testdata/pki/intermediate/server/cert.pem", "./.github/testdata/pki/intermediate/server/key.pem")docs/whats_new.md (1)
192-192: Minor grammar improvement for readability.Consider reordering for natural English phrasing.
Suggested fix
-We provide now two interfaces and implementations to let you configure TLS in `ListenConfig`: +We now provide two interfaces and implementations to let you configure TLS in `ListenConfig`:addon/verifypeer/ocsp_responder_test.go (1)
131-133: Redundant error wrapping withfmt.Errorf("%w", err).Using
fmt.Errorf("%w", err)without additional context is redundant—it creates a new error that wraps the original without adding information. Either add meaningful context or return the error directly.Example fix for one occurrence
req, err := ocsp.ParseRequest(rawreq) if err != nil { - return nil, fmt.Errorf("%w", err) + return nil, fmt.Errorf("parse OCSP request: %w", err) }Or simply:
req, err := ocsp.ParseRequest(rawreq) if err != nil { - return nil, fmt.Errorf("%w", err) + return nil, err }Also applies to: 186-188, 195-197, 206-208, 209-211, 222-224, 238-240, 285-287, 290-292, 299-301, 304-306
| go func() { | ||
| assert.NoError(t, app.Listen(":0", fiber.ListenConfig{ | ||
| DisableStartupMessage: true, | ||
| ListenerNetwork: fiber.NetworkTCP4, | ||
| ListenerAddrFunc: func(addr net.Addr) { | ||
| network = addr.String() | ||
| }, | ||
| TLSProvider: &fiber.ServerCertificateProvider{ | ||
| Certificate: "../../.github/testdata/pki/intermediate/server/fullchain.pem", | ||
| }, | ||
| TLSCustomizer: &verifypeer.MutualTLSCustomizer{ | ||
| Certificate: cert, | ||
| }, | ||
| })) | ||
| }() |
There was a problem hiding this comment.
Assertions in goroutines won't fail the test properly.
assert.NoError(t, app.Listen(...)) inside a goroutine won't cause the test to fail if Listen returns an error, because the goroutine may complete after the test function returns.
Consider using a channel to capture and propagate errors:
♻️ Suggested fix
+ listenErr := make(chan error, 1)
go func() {
- assert.NoError(t, app.Listen(":0", fiber.ListenConfig{
+ listenErr <- app.Listen(":0", fiber.ListenConfig{
DisableStartupMessage: true,
ListenerNetwork: fiber.NetworkTCP4,
ListenerAddrFunc: func(addr net.Addr) {
network = addr.String()
},
TLSProvider: &fiber.ServerCertificateProvider{
Certificate: "../../.github/testdata/pki/intermediate/server/fullchain.pem",
},
TLSCustomizer: &verifypeer.MutualTLSCustomizer{
Certificate: cert,
},
- }))
+ })
}()Then check listenErr after shutdown.
🤖 Prompt for AI Agents
In `@addon/verifypeer/cert_test.go` around lines 29 - 43, The test spawns
app.Listen in a goroutine and uses assert.NoError(t, app.Listen(...)) there,
which can miss failures because assertions in goroutines don't fail the test
reliably; modify the goroutine to send the error returned by app.Listen to a
channel (e.g., listenErrCh), replace assert.NoError inside the goroutine with
sending the error, and after triggering shutdown (or after the test logic
completes) receive from that channel and call assert.NoError(t, listenErr) to
propagate any Listen errors; apply this change around the app.Listen call that
configures ListenerAddrFunc, TLSProvider, and TLSCustomizer
(verifypeer.MutualTLSCustomizer) so Listen failures are observed by the test.
| func Test_Client_R(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| client := New() | ||
| req := client.R() | ||
|
|
||
| require.Equal(t, "Request", reflect.TypeOf(req).Elem().Name()) | ||
| require.Equal(t, "Request", reflect.TypeFor[Request]().Name()) | ||
| require.Equal(t, client, req.Client()) |
There was a problem hiding this comment.
The assertion no longer validates the actual request object's type.
The change from reflect.TypeOf(req).Elem().Name() to reflect.TypeFor[Request]().Name() alters the test's behavior. The original code verified that req is of type Request, while the new code only checks that the type Request has name "Request" (a tautology that's always true).
If the intent is to verify req's type, consider:
- require.Equal(t, "Request", reflect.TypeFor[Request]().Name())
+ require.Equal(t, "Request", reflect.TypeOf(req).Elem().Name())Or if using Go 1.22+ idioms, you could use a type assertion:
require.IsType(t, &Request{}, req)🤖 Prompt for AI Agents
In `@client/client_test.go` around lines 1891 - 1898, The test Test_Client_R
currently asserts reflect.TypeFor[Request]().Name() which only checks the
Request type's name rather than the runtime type of req; update the assertion to
validate req's concrete type returned by client.R()—for example use
require.IsType(t, &Request{}, req) or require.Equal(t,
reflect.TypeOf(&Request{}).Elem(), reflect.TypeOf(req).Elem()) so that req (from
New() and R()) is actually a Request instance; modify the assertions around
New(), R(), and req accordingly.
- Externalize `*tls.Config` object creation logic from `app.Listen()`
using two interfaces and easy to use implementations.
- Add a similar interface and easy to use implementation for
configuring `*tls.Config` object for `client`.
Currently TLS configuration is strictly done from the parameters
`CertFile` and `CertKeyFile` (v2/v3) or from `AutoCertManager` (v3)
in `ListenConfig` inside `app.Listen()`
Mutual TLS is also strictly configured using `CertClientFile` (with
`CertFile` and `CertKeyFile` only).
Any other use cases, like using configuration files (containing the
certificate and key) or manually connecting to a configuration manager
(like Hashicorp Vault) and not relying on files present on host, are
complicated. You have to use `App.Listener()` which does not support
preforking.
Having a certificate's key in a file means it can be included in system
backups, and otherwise, easily retrieved.
This feature provides a way to externalize the TLS configuration
from `app.Listen()`, with two interfaces references in `ListenConfig()` :
1. `ServerTLSProvider` which defines a function that potentially returns a
`*tls.Config` object or an error.
2. `ServerTLSCustomizer` which defines a function to add functionnality
to an existing `*tls.Config` object, like MutualTLS.
Implementations for the first are provided in the `fiber` package to
provide similar current functionalities :
- `ServerCertificateProvider` : Loading certificate and key from file,
but now also from string. It can use a single parameter (certficate
and key can be in the same file) or use the current `CertFile` and
`CertKeyFile` parameters (marked as deprecated).
- `ACMECertificateProvider` : Loading certificate from an
`autocert.Manager`.
Implementations for the second are provided in the new
`fiber/addon/verifypeer` package which provides 3 implementations for :
- Mutual TLS.
- Mutual TLS with CRL check (from file, string or in a URL provided)
- mutual TLS with OCSP Stapling (using a URL provided).
Additionally this feature adds a similar interface `ClientTLSProvider`
and easy to use implementation to configure a `*tls.Config` object
for client use in the `client` package. Its usage has been added to the
"Mutual TLS with*" implementations and also could be used in `Storage`
providers instead of a `*tls.Config`. It supports :
- Loading a certificate/key from a file or string
- Loading Root certificates
- Loading system Root certificates
- Setting TLS minimum version
A `SetTLSProvider` function has been added to `Client` to use this
interface instead of the previous function `SetCertificates()`,
`SetRootCertificate()` and `SetRootCertificateFromString`.
Finally new certificates have been added, from an OpenSSL generated PKI :
- Root CA Certificate
- Expires in 30 years
- An `index.txt` file (signed certificate status list)
- Intermediate CA Certificate
- signed by Root CA (expires in 20 years)
- An `index.txt` file (signed certificate status list)
- Certificate Revocation List (v2, in PEM format)
- Test certificates (expired in 10 years) :
- server ( includes a SNI for 'localhost' )
- client
- revoked
- expired
Each test certificates have the corresponding files :
- `cert.pem` (certificate)
- `key.pem` (certificate's key, not encrypted)
- `fullchain.pem` (certificate, key and Intermediate CA certificate)
1cc10b4 to
b24a08b
Compare
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Fix all issues with AI agents
In `@addon/verifypeer/ocsp_responder_test.go`:
- Around line 283-305: Both parseCertFile and parseKeyFile assume pem.Decode
returned a non-nil block and will panic when block is nil; update these
functions (parseCertFile and parseKeyFile) to check the returned block from
pem.Decode and return a descriptive error (e.g., “invalid PEM block” or include
filename) when block == nil before accessing block.Bytes, then proceed to parse
only when block is valid.
In `@client/client.go`:
- Around line 247-259: SetTLSProvider currently panics on provider errors which
is inconsistent with other setters; change its signature to return (*Client,
error) so runtime TLS provider failures can be handled instead of terminating
the app. In the SetTLSProvider method call provider.ProvideClientTLS(), if err
!= nil return (c, err) (do not logger.Panicf), otherwise call
c.applyTLSConfig(config) as before and return (c, nil); update any callers to
handle the returned error. Use the function/method names SetTLSProvider,
ClientTLSProvider.
In `@docs/addon/verifypeer.md`:
- Around line 97-99: The OCSP example uses the wrong field name: update the
TLSProvider block that constructs a fiber.ServerCertificateProvider to use the
correct field name Certificate (not CertificateChain) to match the
ServerCertificateProvider struct in tls_provider.go; locate the TLSProvider
initializer and replace CertificateChain with Certificate so the example
compiles against the fiber.ServerCertificateProvider type.
In `@listen.go`:
- Around line 157-159: The implementation only invokes cfg.TLSConfigFunc when
cfg.TLSProvider is non-nil, which contradicts the doc comment that tlsConfig may
be nil and TLSConfigFunc can be called independently; modify the code to call
cfg.TLSConfigFunc(tlsConfig) whenever cfg.TLSConfigFunc != nil (regardless of
cfg.TLSProvider) so callers of TLSConfigFunc receive the expected tlsConfig
(possibly nil), and keep or update the doc comment to state that TLSConfigFunc
may be called with a nil tlsConfig.
In `@tls_provider_test.go`:
- Around line 13-16: Add t.Parallel() as the first statement in every test
function in tls_provider_test.go so they opt into parallel execution per project
convention; specifically insert t.Parallel() at the start of
Test_ServerTLSProvider_Version and likewise at the very beginning of the other
Test_* functions in this file (ensure it appears before any setup like modifying
testPreforkMaster or cleanup calls).
♻️ Duplicate comments (3)
docs/api/fiber.md (1)
218-222: Replace hard tab with spaces in interface code block.Markdownlint flags a hard tab at line 220. As noted in past reviews, please run
make markdownafter updating the docs.Proposed fix
```go title="Signature" type ServerTLSCustomizer interface { - CustomizeServerTLS(config *tls.Config) error + CustomizeServerTLS(config *tls.Config) error }</details> </blockquote></details> <details> <summary>docs/addon/verifypeer.md (1)</summary><blockquote> `7-8`: **Fix grammar in the introduction.** The sentence has grammatical issues: "aim is to provides" should be "aims to provide". <details> <summary>Proposed fix</summary> ```diff -The VerifyPeer addon package aim is to provides additional `fiber.ServerTLSCustomizer` interface implementations to add means -to configure a `*tls.Config` 's `VerifyPeerCertificate` field with additional validation for client mTLS connection. +The VerifyPeer addon package aims to provide additional `fiber.ServerTLSCustomizer` interface implementations +to configure a `*tls.Config`'s `VerifyPeerCertificate` field with additional validation for client mTLS connections.As per coding guidelines, please run
make markdownafter updating the docs.tls_provider_test.go (1)
108-109: RestoretestPreforkMasterafter prefork tests.
testPreforkMasteris a package-level flag; setting it without restoring leaks state across tests and can race once tests run in parallel. Capture the prior value and restore it viat.Cleanup().🔧 Suggested fix (apply to each prefork test)
func Test_ServerTLSProvider_Prefork(t *testing.T) { - testPreforkMaster = true + prevPreforkMaster := testPreforkMaster + testPreforkMaster = true + t.Cleanup(func() { testPreforkMaster = prevPreforkMaster })Also applies to: 137-138
🧹 Nitpick comments (14)
listen_test.go (1)
187-208: Consider addingt.Parallel()to this test.Per coding guidelines, tests should invoke
t.Parallel()at the start. This test doesn't have shared state that would prevent parallel execution.Suggested change
func Test_App_Listener_TLS_Listener(t *testing.T) { + t.Parallel() // Create tls certificate cer, err := tls.LoadX509KeyPair("./.github/testdata/pki/intermediate/server/cert.pem", "./.github/testdata/pki/intermediate/server/key.pem")addon/verifypeer/cert.go (2)
25-28: Fix typos in doc comments.Minor typos in the documentation: "parse" should be "parses" and "certiticate" should be "certificate".
Suggested fix
// CustomizeServerTLS implements [fiber.ServerTLSCustomizer]. // -// It parse the ClientCertificate, either a file path or its content, a PEM encoded CA certificate. -// It will add the certiticate to config.ClientCAs and set config.ClientAuth to tls.RequireAndVerifyClientCert +// It parses the ClientCertificate, either a file path or its content, a PEM encoded CA certificate. +// It will add the certificate to config.ClientCAs and set config.ClientAuth to tls.RequireAndVerifyClientCert
44-68:ClientCertificate()can return(nil, nil)silently.When
Certificateis empty, or the PEM block type is not"CERTIFICATE", this method returns(nil, nil). WhileCustomizeServerTLShandles this gracefully (returns early at line 33-34), callers usingClientCertificate()directly should be aware of this behavior.Consider returning an error for the case when the PEM block exists but has an unexpected type, as this likely indicates misconfiguration rather than an intentional empty value.
Suggested improvement
if block != nil && block.Type == "CERTIFICATE" { cert, err := x509.ParseCertificate(block.Bytes) if err != nil { return nil, fmt.Errorf("tls: cannot load client CA certificate: %w", err) } certificate = cert + } else if block != nil { + return nil, fmt.Errorf("tls: unexpected PEM block type %q, expected CERTIFICATE", block.Type) }addon/verifypeer/ocsp.go (2)
101-106: Guard against emptyverifiedChainsto prevent potential panic.While
VerifyPeerCertificateis typically called with verified chains whenClientAuthisRequireAndVerifyClientCert, accessingverifiedChains[0][0]without bounds checking could panic in edge cases.Defensive check
config.VerifyPeerCertificate = func(_ [][]byte, verifiedChains [][]*x509.Certificate) error { + if len(verifiedChains) == 0 || len(verifiedChains[0]) == 0 { + return errors.New("mtls: no verified certificate chain") + } cert := verifiedChains[0][0] opts := &ocsp.RequestOptions{Hash: crypto.SHA256}
142-148: Typo in function name:validateOSCPServershould bevalidateOCSPServer.The function name has a typo - "OSCP" should be "OCSP" (Online Certificate Status Protocol).
Fix typo
-func (*MutualTLSWithOCSPCustomizer) validateOSCPServer(ocspServer string) error { +func (*MutualTLSWithOCSPCustomizer) validateOCSPServer(ocspServer string) error {Also update the call sites at lines 71 and 77.
addon/verifypeer/crl.go (3)
52-53: Fix interface name in doc comment.The doc comment still references
fiber.ServerCertificateCustomizer, but the interface isfiber.ServerTLSCustomizer.Fix
-// CustomizeServerTLS implements [fiber.ServerCertificateCustomizer] +// CustomizeServerTLS implements [fiber.ServerTLSCustomizer]
117-127: Add bounds check forverifiedChainsto prevent potential panic.Same issue as in
ocsp.go- accessingverifiedChains[0][0]without checking bounds could panic.Defensive check
if clientCRL != nil { config.VerifyPeerCertificate = func(_ [][]byte, verifiedChains [][]*x509.Certificate) error { + if len(verifiedChains) == 0 || len(verifiedChains[0]) == 0 { + return errors.New("tls: no verified certificate chain") + } cert := verifiedChains[0][0] for _, revokedCertificate := range clientCRL.RevokedCertificateEntries {
93-101: Silent failure when fetching CRL from distribution points may mask issues.When
RevocationListis empty, the code attempts to fetch CRL fromCRLDistributionPointsbut silently ignores all failures. This could leave the server without CRL protection without any indication. Consider logging a warning when all distribution points fail.Add logging for visibility
+ var fetchErrors []error } else { for _, dist := range clientCACert.CRLDistributionPoints { if distURL, err := m.validateFetchURL(dist); err == nil { if crl, err := m.fetchCRL(distURL); err == nil { crlBytes = crl break + } else { + fetchErrors = append(fetchErrors, err) } } } + if len(crlBytes) == 0 && len(fetchErrors) > 0 { + // Consider logging: CRL fetch failed from all distribution points + } }addon/verifypeer/crl_test.go (3)
66-68: Addt.Parallel()to each test function.Per coding guidelines, all Go tests should invoke
t.Parallel()at the start to maximize concurrency.Proposed fix
func Test_CRL_File(t *testing.T) { + t.Parallel() testCRL(t, "../../.github/testdata/pki/intermediate/crl.pem") } func Test_CRL_Content(t *testing.T) { + t.Parallel() crlFile, err := os.ReadFile(filepath.Clean("../../.github/testdata/pki/intermediate/crl.pem")) require.NoError(t, err) testCRL(t, string(crlFile)) } func Test_CRL_Server(t *testing.T) { + t.Parallel() var network stringAlso applies to: 70-75, 77-105
30-45: Avoidassertinside goroutines; uset.Errorfor channel-based error propagation.Per retrieved learnings, the linter does not allow
requireorassertassertions from within goroutines. Consider capturing errors via a channel or usingt.Errorf.Proposed fix
+ errCh := make(chan error, 1) go func() { - assert.NoError(t, app.Listen(":0", fiber.ListenConfig{ + errCh <- app.Listen(":0", fiber.ListenConfig{ DisableStartupMessage: true, ListenerNetwork: fiber.NetworkTCP4, ListenerAddrFunc: func(addr net.Addr) { network = addr.String() }, TLSProvider: &fiber.ServerCertificateProvider{ Certificate: "../../.github/testdata/pki/intermediate/server/fullchain.pem", }, TLSCustomizer: &verifypeer.MutualTLSWithCRLCustomizer{ Certificate: "../../.github/testdata/pki/intermediate/cacert.pem", RevocationList: crl, }, - })) + }) }()
47-49: Replacetime.Sleepwith a synchronization primitive for server readiness.Using
time.Sleepfor waiting on server startup is flaky. Consider using a channel signaled fromListenerAddrFuncto wait until the listener is ready.Example approach
ready := make(chan struct{}) ListenerAddrFunc: func(addr net.Addr) { network = addr.String() close(ready) }, // ... <-ready // wait for listenerclient/tls_provider.go (1)
56-59: Consider avoiding mutation of the receiver struct.Setting
p.TLSMinVersiondirectly modifies the originalClientCertificateProviderinstance. If the same provider is reused with different expectations, this could cause confusion. Consider using a local variable instead.Proposed fix
func (p *ClientCertificateProvider) ProvideClientTLS() (*tls.Config, error) { - if p.TLSMinVersion == 0 { - p.TLSMinVersion = tls.VersionTLS12 - } + minVersion := p.TLSMinVersion + if minVersion == 0 { + minVersion = tls.VersionTLS12 + } var tlsConfig *tls.Config initTLS := func() error { if tlsConfig == nil { tlsConfig = &tls.Config{ //nolint:gosec // This is a user input - MinVersion: p.TLSMinVersion, + MinVersion: minVersion, }docs/whats_new.md (1)
195-206: Replace hard tabs with spaces in code blocks.Markdownlint flags hard tabs at lines 197 and 204. Please run
make markdownto fix formatting. As per coding guidelines.Proposed fix
```go title="Signature" type ServerTLSProvider interface { - ProvideServerTLS() (*tls.Config, error) + ProvideServerTLS() (*tls.Config, error) }
ServerTLSCustomizer: Add functionality to the*tls.Configobject, like MutualTLS.type ServerTLSCustomizer interface {CustomizeServerTLS(config *tls.Config) error
}CustomizeServerTLS(config *tls.Config) error</details> </blockquote></details> <details> <summary>addon/verifypeer/ocsp_test.go (1)</summary><blockquote> `25-33`: **Propagate `Listen` errors to avoid hangs.** If `Listen` fails before `ListenerAddrFunc` runs, the main goroutine blocks on the address channel forever. Consider wiring an error channel (or `select` with timeout) so failures surface deterministically and the test doesn’t hang. <details> <summary>♻️ Sketch of a safer pattern</summary> ```diff - staplerAddrCh := make(chan string, 1) + staplerAddrCh := make(chan string, 1) + staplerErrCh := make(chan error, 1) ... go func() { - assert.NoError(t, stapler.Listen(":0", fiber.ListenConfig{ + staplerErrCh <- stapler.Listen(":0", fiber.ListenConfig{ DisableStartupMessage: true, ListenerNetwork: fiber.NetworkTCP4, ListenerAddrFunc: func(addr net.Addr) { staplerAddrCh <- addr.String() }, - })) + }) }() - ocspAddr := <-staplerAddrCh + select { + case ocspAddr := <-staplerAddrCh: + // use ocspAddr + case err := <-staplerErrCh: + require.NoError(t, err) + }Also applies to: 45-60
| func parseCertFile(filename string) (*x509.Certificate, error) { | ||
| ct, err := os.ReadFile(filepath.Clean(filename)) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("%w", err) | ||
| } | ||
| block, _ := pem.Decode(ct) | ||
| cert, err := x509.ParseCertificate(block.Bytes) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("%w", err) | ||
| } | ||
| return cert, nil | ||
| } | ||
|
|
||
| // parses a PEM encoded PKCS8 private key (RSA only) | ||
| func parseKeyFile(filename string) (any, error) { | ||
| kt, err := os.ReadFile(filepath.Clean(filename)) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("%w", err) | ||
| } | ||
| block, _ := pem.Decode(kt) | ||
| key, err := x509.ParsePKCS8PrivateKey(block.Bytes) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("%w", err) |
There was a problem hiding this comment.
Guard against invalid PEM blocks to avoid panics.
pem.Decode can return nil on malformed input; accessing block.Bytes will panic. Return a clear error instead so tests fail predictably.
✅ Proposed fix
func parseCertFile(filename string) (*x509.Certificate, error) {
ct, err := os.ReadFile(filepath.Clean(filename))
if err != nil {
return nil, fmt.Errorf("%w", err)
}
block, _ := pem.Decode(ct)
+ if block == nil {
+ return nil, errors.New("tls: invalid PEM certificate")
+ }
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, fmt.Errorf("%w", err)
}
return cert, nil
}
func parseKeyFile(filename string) (any, error) {
kt, err := os.ReadFile(filepath.Clean(filename))
if err != nil {
return nil, fmt.Errorf("%w", err)
}
block, _ := pem.Decode(kt)
+ if block == nil {
+ return nil, errors.New("tls: invalid PEM key")
+ }
key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return nil, fmt.Errorf("%w", err)
}
return key, nil
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| func parseCertFile(filename string) (*x509.Certificate, error) { | |
| ct, err := os.ReadFile(filepath.Clean(filename)) | |
| if err != nil { | |
| return nil, fmt.Errorf("%w", err) | |
| } | |
| block, _ := pem.Decode(ct) | |
| cert, err := x509.ParseCertificate(block.Bytes) | |
| if err != nil { | |
| return nil, fmt.Errorf("%w", err) | |
| } | |
| return cert, nil | |
| } | |
| // parses a PEM encoded PKCS8 private key (RSA only) | |
| func parseKeyFile(filename string) (any, error) { | |
| kt, err := os.ReadFile(filepath.Clean(filename)) | |
| if err != nil { | |
| return nil, fmt.Errorf("%w", err) | |
| } | |
| block, _ := pem.Decode(kt) | |
| key, err := x509.ParsePKCS8PrivateKey(block.Bytes) | |
| if err != nil { | |
| return nil, fmt.Errorf("%w", err) | |
| func parseCertFile(filename string) (*x509.Certificate, error) { | |
| ct, err := os.ReadFile(filepath.Clean(filename)) | |
| if err != nil { | |
| return nil, fmt.Errorf("%w", err) | |
| } | |
| block, _ := pem.Decode(ct) | |
| if block == nil { | |
| return nil, errors.New("tls: invalid PEM certificate") | |
| } | |
| cert, err := x509.ParseCertificate(block.Bytes) | |
| if err != nil { | |
| return nil, fmt.Errorf("%w", err) | |
| } | |
| return cert, nil | |
| } | |
| // parses a PEM encoded PKCS8 private key (RSA only) | |
| func parseKeyFile(filename string) (any, error) { | |
| kt, err := os.ReadFile(filepath.Clean(filename)) | |
| if err != nil { | |
| return nil, fmt.Errorf("%w", err) | |
| } | |
| block, _ := pem.Decode(kt) | |
| if block == nil { | |
| return nil, errors.New("tls: invalid PEM key") | |
| } | |
| key, err := x509.ParsePKCS8PrivateKey(block.Bytes) | |
| if err != nil { | |
| return nil, fmt.Errorf("%w", err) | |
| } | |
| return key, nil | |
| } |
🤖 Prompt for AI Agents
In `@addon/verifypeer/ocsp_responder_test.go` around lines 283 - 305, Both
parseCertFile and parseKeyFile assume pem.Decode returned a non-nil block and
will panic when block is nil; update these functions (parseCertFile and
parseKeyFile) to check the returned block from pem.Decode and return a
descriptive error (e.g., “invalid PEM block” or include filename) when block ==
nil before accessing block.Bytes, then proceed to parse only when block is
valid.
| // SetTLSProvider lets you configure TLS through a [ClientTLSProvider] | ||
| func (c *Client) SetTLSProvider(provider ClientTLSProvider) *Client { | ||
| c.mu.Lock() | ||
| defer c.mu.Unlock() | ||
| if provider != nil { | ||
| if config, err := provider.ProvideClientTLS(); err != nil { | ||
| c.logger.Panicf("client: unable to set tlsConfig: %v", err) | ||
| } else { | ||
| c.applyTLSConfig(config) | ||
| } | ||
| }() | ||
|
|
||
| pem, err := io.ReadAll(file) | ||
| if err != nil { | ||
| c.logger.Panicf("client: %v", err) | ||
| } | ||
|
|
||
| config := c.TLSConfig() | ||
| if config.RootCAs == nil { | ||
| config.RootCAs = x509.NewCertPool() | ||
| } | ||
|
|
||
| if !config.RootCAs.AppendCertsFromPEM(pem) { | ||
| c.logger.Panicf("client: %v", ErrFailedToAppendCert) | ||
| } | ||
|
|
||
| return c | ||
| } | ||
|
|
||
| // SetRootCertificateFromString adds one or more root certificates from a string to the client's TLS configuration. | ||
| func (c *Client) SetRootCertificateFromString(pem string) *Client { | ||
| config := c.TLSConfig() | ||
|
|
||
| if config.RootCAs == nil { | ||
| config.RootCAs = x509.NewCertPool() | ||
| } | ||
|
|
||
| if !config.RootCAs.AppendCertsFromPEM([]byte(pem)) { | ||
| c.logger.Panicf("client: %v", ErrFailedToAppendCert) | ||
| } | ||
|
|
||
| return c | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's see the panic patterns in client.go
rg -n "Panicf|panic\(" client/client.go -B 2 -A 2Repository: gofiber/fiber
Length of output: 816
🏁 Script executed:
# Find the applyTLSConfig implementation to understand nil handling
rg -n "func.*applyTLSConfig" client/client.go -A 15Repository: gofiber/fiber
Length of output: 544
🏁 Script executed:
# Check the ClientTLSProvider interface definition
rg -n "type ClientTLSProvider" -A 5Repository: gofiber/fiber
Length of output: 872
🏁 Script executed:
# Look at NewWithClient to compare error handling patterns
rg -n "func NewWithClient" -A 30Repository: gofiber/fiber
Length of output: 2918
🏁 Script executed:
# Check if other setter methods in client.go return errors or panic
rg -n "func \(c \*Client\) Set" client/client.go -A 10 | head -50Repository: gofiber/fiber
Length of output: 1632
🏁 Script executed:
# Look for how TLS is typically set up in examples or tests
fd -e md client/ | head -10 | xargs grep -l "SetTLSProvider\|TLS" 2>/dev/null | head -3Repository: gofiber/fiber
Length of output: 390
🏁 Script executed:
# Check error handling patterns in other setter methods
rg -n "return.*error|Panicf|panic" client/client.go | grep -E "Set[A-Z]|func" | head -20Repository: gofiber/fiber
Length of output: 39
The nil config handling is safe; clarify if aggressive panic is intentional.
The applyTLSConfig(nil) call is safe—nil is a valid *tls.Config value, and the interface explicitly documents that implementations may return (nil, nil) if no configuration is needed. This concern can be removed.
However, SetTLSProvider is unique among setter methods in client.go (e.g., SetJSONMarshal, SetXMLMarshal, SetCBORMarshal)—all other setters simply assign and return without error handling. Panicking via logger.Panicf on provider errors is aggressive compared to this pattern.
If TLS provider failures during initialization are intended to be treated as startup failures (consistent with Fiber's design philosophy), this is acceptable and the approach aligns with how constructors handle validation errors. However, if SetTLSProvider is also used for runtime TLS reconfiguration, consider returning an error instead to avoid unexpected application termination.
🤖 Prompt for AI Agents
In `@client/client.go` around lines 247 - 259, SetTLSProvider currently panics on
provider errors which is inconsistent with other setters; change its signature
to return (*Client, error) so runtime TLS provider failures can be handled
instead of terminating the app. In the SetTLSProvider method call
provider.ProvideClientTLS(), if err != nil return (c, err) (do not
logger.Panicf), otherwise call c.applyTLSConfig(config) as before and return (c,
nil); update any callers to handle the returned error. Use the function/method
names SetTLSProvider, ClientTLSProvider.
| TLSProvider: &fiber.ServerCertificateProvider{ | ||
| CertificateChain: "./certificate.pem", | ||
| }, |
There was a problem hiding this comment.
Incorrect field name in OCSP example.
The example uses CertificateChain but the ServerCertificateProvider struct uses Certificate as the field name (per tls_provider.go).
Proposed fix
TLSProvider: &fiber.ServerCertificateProvider{
- CertificateChain: "./certificate.pem",
+ Certificate: "./certificate.pem",
},📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| TLSProvider: &fiber.ServerCertificateProvider{ | |
| CertificateChain: "./certificate.pem", | |
| }, | |
| TLSProvider: &fiber.ServerCertificateProvider{ | |
| Certificate: "./certificate.pem", | |
| }, |
🤖 Prompt for AI Agents
In `@docs/addon/verifypeer.md` around lines 97 - 99, The OCSP example uses the
wrong field name: update the TLSProvider block that constructs a
fiber.ServerCertificateProvider to use the correct field name Certificate (not
CertificateChain) to match the ServerCertificateProvider struct in
tls_provider.go; locate the TLSProvider initializer and replace CertificateChain
with Certificate so the example compiles against the
fiber.ServerCertificateProvider type.
| if cfg.TLSProvider != nil && cfg.TLSConfigFunc != nil { | ||
| cfg.TLSConfigFunc(tlsConfig) | ||
| } |
There was a problem hiding this comment.
Inconsistency between TLSConfigFunc documentation and implementation.
The doc comment (lines 57-59) states that tlsConfig may be nil, implying TLSConfigFunc could be called with a nil config. However, the implementation only calls TLSConfigFunc when TLSProvider != nil. This means if a user sets only TLSConfigFunc (without TLSProvider), it will never be invoked.
Consider either:
- Updating the docs to clarify
TLSConfigFuncis only called whenTLSProvideris set, or - Changing the condition to always call
TLSConfigFuncwhen it's set (and document thattlsConfigmay be nil).
🤖 Prompt for AI Agents
In `@listen.go` around lines 157 - 159, The implementation only invokes
cfg.TLSConfigFunc when cfg.TLSProvider is non-nil, which contradicts the doc
comment that tlsConfig may be nil and TLSConfigFunc can be called independently;
modify the code to call cfg.TLSConfigFunc(tlsConfig) whenever cfg.TLSConfigFunc
!= nil (regardless of cfg.TLSProvider) so callers of TLSConfigFunc receive the
expected tlsConfig (possibly nil), and keep or update the doc comment to state
that TLSConfigFunc may be called with a nil tlsConfig.
| func Test_ServerTLSProvider_Version(t *testing.T) { | ||
| prevPreforkMaster := testPreforkMaster | ||
| testPreforkMaster = true | ||
| t.Cleanup(func() { testPreforkMaster = prevPreforkMaster }) |
There was a problem hiding this comment.
Add t.Parallel() at the start of each test.
These new tests should opt into parallel execution per project convention. Please add t.Parallel() as the first line of each Test_* function in this file.
As per coding guidelines, please apply this to all tests here.
♻️ Example fix (apply similarly to all tests)
func Test_ServerTLSProvider_Version(t *testing.T) {
+ t.Parallel()
prevPreforkMaster := testPreforkMaster
testPreforkMaster = true
t.Cleanup(func() { testPreforkMaster = prevPreforkMaster })Also applies to: 59-60, 82-83, 107-109, 136-138
🤖 Prompt for AI Agents
In `@tls_provider_test.go` around lines 13 - 16, Add t.Parallel() as the first
statement in every test function in tls_provider_test.go so they opt into
parallel execution per project convention; specifically insert t.Parallel() at
the start of Test_ServerTLSProvider_Version and likewise at the very beginning
of the other Test_* functions in this file (ensure it appears before any setup
like modifying testPreforkMaster or cleanup calls).
|
Okay, I agree, that looks a bit overdeveloped. What problem are we trying to solve? If prefork is the problem, then we should solve it. Line 286 in 5ca0c1e Line 50 in 5ca0c1e Another option would be to extend Lines 44 to 127 in 5ca0c1e Currently, this is created based on the cert files -> but you could also make it so that tlsConfig is checked first, and if it is not available, it is created via cert files.Lines 170 to 211 in 5ca0c1e Depending on what the problem is that needs to be solved, A small side note on how prefork works: Lines 100 to 118 in 5ca0c1e |
|
solved by #4042 |
Summary
Externalize
*tls.Configobject creation logic fromapp.Listen()using two interfaces and easy to use implementations.Add a similar interface and easy to use implementation for configuring
*tls.Configobject forclient.Problem
Currently TLS configuration is strictly done from the parameters
CertFileandCertKeyFile(v2/v3) or fromAutoCertManager(v3) inListenConfiginsideapp.Listen()Mutual TLS is also strictly configured using
CertClientFile(withCertFileandCertKeyFileonly).Any other use cases, like using configuration files (containing the certificate and key) or manually connecting to a configuration manager (like Hashicorp Vault) and not relying on files present on host, are complicated. You have to use
App.Listener()which does not support preforking.Having a certificate's key in a file means it can be included in system backups, and otherwise, easily retrieved.
Solution
This feature provides a way to externalize the TLS configuration from
app.Listen(), with two interfaces references inListenConfig():ServerTLSProviderwhich defines a function that potentially returns a*tls.Configobject or an error.ServerTLSCustomizerwhich defines a function to add functionnality to an existing*tls.Configobject, like MutualTLS.Implementations for the first are provided in the
fiberpackage to provide similar current functionalities :ServerCertificateProvider: Loading certificate and key from file, but now also from string. It can use a single parameter (certficate and key can be in the same file) or use the currentCertFileandCertKeyFileparameters (marked as deprecated).ACMECertificateProvider: Loading certificate from anautocert.Manager.Implementations for the second are provided in the new
fiber/addon/verifypeerpackage which provides 3 implementations for :Additionally this feature adds a similar interface
ClientTLSProviderand easy to use implementation to configure a*tls.Configobject for client use in theclientpackage. Its usage has been added to the "Mutual TLS with*" implementations and also could be used inStorageproviders instead of a*tls.Config. It supports :A
SetTLSProviderfunction has been added toClientto use this interface instead of the previous functionSetCertificates(),SetRootCertificate()andSetRootCertificateFromString.Finally new certificates have been added, from an OpenSSL generated PKI :
index.txtfile (signed certificate status list)index.txtfile (signed certificate status list)Each test certificates have the corresponding files :
cert.pem(certificate)key.pem(certificate's key, not encrypted)fullchain.pem(certificate, key and Intermediate CA certificate)