fix(fxconfig)!: enable TLS by default to prevent plaintext fallback vulnerability - #138
Conversation
There was a problem hiding this comment.
Pull request overview
This PR addresses a security issue in fxconfig where gRPC clients could silently downgrade to plaintext when TLS was disabled or not explicitly configured, enabling a man-in-the-middle risk.
Changes:
- Flip
TLSConfig.Enableddefault fromfalsetotrueso TLS is enabled unless explicitly disabled. - Emit a warning log when TLS is disabled to improve visibility of insecure configurations.
- Update load tests to reflect the new secure default behavior.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| tools/fxconfig/internal/config/config.go | Changes TLS enabled default to true via struct tag. |
| tools/fxconfig/internal/client/client.go | Adds a warning log when TLS is disabled in createSecOpts. |
| tools/fxconfig/internal/config/load_test.go | Updates test expectation so Notifications TLS is enabled by default. |
Comments suppressed due to low confidence (1)
tools/fxconfig/internal/config/config.go:67
- The new default
default:"true"suggests TLS should be enabled whenEnabledis unset, butTLSConfig.Normalize()still setsEnabledtofalsewhen nil andIsEnabled()still returnsfalsewhenEnabledis nil. That means TLS can still silently fall back to plaintext if aTLSConfigis constructed without viper defaults (or if YAML explicitly setsenabled: null), which undermines the secure-by-default intent. Consider changingNormalize()/IsEnabled()to default totruewhenEnabledis nil (and update the related comments) so the behavior is consistent across all code paths.
Enabled *bool `mapstructure:"enabled" yaml:"enabled,omitempty" desc:"Enable/disable TLS" default:"true"`
ClientKeyPath string `mapstructure:"clientKey" yaml:"clientKey,omitempty" desc:"Path to TLS client private key"`
ClientCertPath string `mapstructure:"clientCert" yaml:"clientCert,omitempty" desc:"Path to TLS client certificate"`
RootCertPaths []string `mapstructure:"rootCerts" yaml:"rootCerts,omitempty" desc:"Paths to TLS root certificates"`
ServerNameOverride string `mapstructure:"serverNameOverride" yaml:"serverNameOverride,omitempty" desc:"Override TLS server name"`
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| // let's see if we use TLS | ||
| if !tlsConfig.IsEnabled() { | ||
| logger.Warn("TLS is disabled — connections will be unencrypted and unauthenticated. This is insecure and not recommended for production environments.") |
There was a problem hiding this comment.
This warning string appears to exceed the repository’s lll max line length (120) enforced by golangci-lint, which will likely fail CI. Consider splitting the message across concatenated string literals (or otherwise shortening it) so the line length stays within the configured limit.
| logger.Warn("TLS is disabled — connections will be unencrypted and unauthenticated. This is insecure and not recommended for production environments.") | |
| logger.Warn( | |
| "TLS is disabled — connections will be unencrypted and unauthenticated. " + | |
| "This is insecure and not recommended for production environments.", | |
| ) |
| assert.True(t, cfg.Orderer.TLS.IsEnabled()) | ||
| assert.Equal(t, []string{"/path/to/ca.pem"}, cfg.Orderer.TLS.RootCertPaths) | ||
| assert.False(t, cfg.Queries.TLS.IsEnabled()) | ||
| assert.False(t, cfg.Notifications.TLS.IsEnabled()) | ||
| assert.True(t, cfg.Notifications.TLS.IsEnabled()) |
There was a problem hiding this comment.
With the new secure default, this test now expects Notifications TLS to be enabled even though the YAML in this test does not provide any root certs for Notifications (and no global tls.rootCerts). That configuration would fail TLSConfig.Validate() (rootCertPaths must not be empty when TLS is enabled), so the test is no longer representative of a valid config. Consider adding a top-level tls.rootCerts (or notifications-specific rootCerts) to keep the test focused on the enabled flag while still producing a valid configuration.
There was a problem hiding this comment.
We need to update the tests now to disable TLS explicitly. Also update the documentation accordingly.
| assert.True(t, cfg.Orderer.TLS.IsEnabled()) | ||
| assert.Equal(t, []string{"/path/to/ca.pem"}, cfg.Orderer.TLS.RootCertPaths) | ||
| assert.False(t, cfg.Queries.TLS.IsEnabled()) | ||
| assert.False(t, cfg.Notifications.TLS.IsEnabled()) | ||
| assert.True(t, cfg.Notifications.TLS.IsEnabled()) |
There was a problem hiding this comment.
We need to update the tests now to disable TLS explicitly. Also update the documentation accordingly.
|
hi @MayankSharmaCSE it seems that the unit tests are still failing. |
|
@mbrandenburger, fixed the issue . |
mbrandenburger
left a comment
There was a problem hiding this comment.
Thank you @MayankSharmaCSE for working on this PR. We are making good progress here. I have a few more comments below.
Moreover, we should better indicate that this is a breaking change. Please make sure that the PR description highlights this. New users need to configure TLS correctly or disable it explicitly.
The commit message should therefore also contain fix(fxconfig)! per conventional commits.
| assert.False(t, cfg.Queries.TLS.IsEnabled()) | ||
| assert.False(t, cfg.Notifications.TLS.IsEnabled()) | ||
| assert.False(t, cfg.Notifications.TLS.IsEnabled(), "Notifications explicitly has enabled: false in config") |
There was a problem hiding this comment.
Should we add a similar comment to assert.False(t, cfg.Queries.TLS.IsEnabled()) as well?
There was a problem hiding this comment.
Yeh , I missed that during the refactor. i will add the assertion for cfg.Queries.TLS.IsEnabled() to ensure consistency across all three service configs in the test.
| logger.Warn("TLS is disabled — connections will be unencrypted and " + | ||
| "unauthenticated. This is insecure and not recommended for production.") |
There was a problem hiding this comment.
I am wondering if this is the right place to throw the logging as this would lead to multiple warning for each client (orderer, queries, notifications). Without additional information which service has TLS disabled.
There was a problem hiding this comment.
Maybe we should handle the warning somewhere else ... during config loading?
There was a problem hiding this comment.
Fair point. Logging in each client would produce redundant warnings without context. I agree a single warning during config load would be cleaner. However, to keep this PR focused, I did prefer to address this as a follow-up item rather than scope creep this fix.
| ### TLS Configuration | ||
|
|
||
| - **No TLS**: `enabled: false` or all TLS fields empty | ||
| - **No TLS**: `enabled: false` | ||
| - **Server TLS**: `enabled: true` with only `rootCerts` set (server authentication only) | ||
| - **Mutual TLS**: `enabled: true` with `clientKey`, `clientCert`, and `rootCerts` all set (mutual authentication) | ||
| - **Service-specific TLS**: Each service (orderer, queries, notifications) can override the parent `tls` section | ||
| - **SNI Override**: Use `serverNameOverride` for IP-based connections or custom hostname verification |
There was a problem hiding this comment.
I think we need to document this better and explain that by default TLS is enabled and requires at least a rootCert otherwise the user will get an error. Or the user explicitly turns off TLS.
There was a problem hiding this comment.
I will add clear documentation explaining:
- TLS is now enabled by default and requires TLS certificates.
- Users can set tls.enabled: false to disable (not recommended for production).
- The "rootCertPaths must not be empty" error indicates misconfiguration. This will be added to the README and/or inline comments in the config example file.
|
@mbrandenburger Thanks for the review you had! I have addressed all your comments:
|
mbrandenburger
left a comment
There was a problem hiding this comment.
Thank you @MayankSharmaCSE Great work! See my comments below.
| require.NoError(t, err) | ||
| require.NotNil(t, cfg) | ||
| assert.Equal(t, 30*time.Second, cfg.Orderer.ConnectionTimeout) | ||
| assert.Equal(t, 30*time.Second, cfg.Queries.ConnectionTimeout) | ||
| assert.Equal(t, 30*time.Second, cfg.Notifications.ConnectionTimeout) |
There was a problem hiding this comment.
Let's add TLS enabled as assertions as it's our new default.
There was a problem hiding this comment.
For instance:
assert.True(t, cfg.Orderer.TLS.IsEnabled())
// ... (and Queries/Notifications)There was a problem hiding this comment.
Makes sense. I will add assert.True for when TLS is enabled by default (not explicitly disabled).
|
|
||
| // Warn once if any service has TLS disabled | ||
| if !c.Orderer.TLS.IsEnabled() || !c.Queries.TLS.IsEnabled() || !c.Notifications.TLS.IsEnabled() { | ||
| configLogger.Warn("TLS is disabled for one or more services — " + | ||
| "connections will be unencrypted. This is insecure and not recommended for production.") | ||
| } |
There was a problem hiding this comment.
I have the feeling we should not do the logging here as the "application" should decide if this particular configuration should log a warning or not. Thus, checking the config and print a log could happen in
WDYT?There was a problem hiding this comment.
Agreed, the config package shouldn't dictate logging. I will move the TLS warning from config.goto root.go so the application controls when/how to log. Does that sound right to you?
|
@pasquale95 Can you please have a look at this PR, which changes the "default TLS behavior" for |
82ad38c to
f01ca25
Compare
- Change tls.enabled default from false to true - Normalize() sets Enabled=true when nil - Add TestLoad_TLSEnabledByDefault to assert default behavior - Move TLS warning to root.go (CLI layer) - Update integration tests with explicit tls.enabled: false - Improve assert messages for Queries TLS consistency - Document breaking change in README Signed-off-by: mayanksharmaCSE <mayanksharmacse1@gmail.com>
f01ca25 to
f5e8e5d
Compare
|
This PR was re-opened in #214 |
|
@mbrandenburger,yes,I have followed up on the changes in #214 because i messed up my git history with some wrong commands. |
Summary
Enable TLS by default across all service configurations (orderer, queries, notifications). Previously TLS was disabled by default.
Breaking Change
TLS is now enabled by default.
If you upgrade to this version and don't have TLS configured, you may see:
rootCertPaths must not be empty
New users must either:
rootCerts, ortls.enabled: falseExisting users can add
tls.enabled: falseto restore previous behavior.Changes
enabled: trueinstead offalseNormalize()setsEnabled=truewhen niltls.enabled: false(testcontainers use--insecureflag)assert.Falsemessage for Queries TLS assertion for consistencyMigration
Enable TLS (recommended for production):
Fixes #108
Test Plan