-
Notifications
You must be signed in to change notification settings - Fork 2k
server: Fix potential race condition in TLS reload and RPC handler #27167
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| ```release-note:bug | ||
| server: Fix a potential race condition when reloading the server agent when changing TLS configuration | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -105,8 +105,22 @@ const ( | |
| // Server is Nomad server which manages the job queues, | ||
| // schedulers, and notification bus for agents. | ||
| type Server struct { | ||
|
|
||
| // config holds the server configuration. No fields are not mutated after | ||
| // initialization except for the TLS config, so it is safe to access them | ||
| // without a lock. Any fields that become reloadable, must be carefully | ||
| // assessed for concurrent access. | ||
| config *Config | ||
|
|
||
| // rpcTLSEnabled and rpcTLSUpgradeMode are used by the RPC connection | ||
| // handler to determine TLS enforcement. The values can change at runtime | ||
| // due to config reloading. | ||
| // | ||
| // The setRPCTLSAtomics helper function should be used to set these values | ||
| // as it correctly handles nil configs. | ||
| rpcTLSEnabled atomic.Bool | ||
| rpcTLSUpgradeMode atomic.Bool | ||
|
|
||
| logger log.InterceptLogger | ||
|
|
||
| // Connection pool to other Nomad servers | ||
|
|
@@ -350,6 +364,8 @@ func NewServer(config *Config, consulCatalog consul.CatalogAPI, consulConfigFunc | |
| // Create the server | ||
| s := &Server{ | ||
| config: config, | ||
| rpcTLSEnabled: atomic.Bool{}, | ||
| rpcTLSUpgradeMode: atomic.Bool{}, | ||
|
Comment on lines
+367
to
+368
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. shouldn't these actually be pointer values to account for unset fields?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The zero value stored is false which is right for these two configuration parameters and they are always set by the It is possible to turn them into pointers, but the benefit seems that it would save us the space of 2 x uint32 (8 bytes) when they are not set while requiring additional logic to ensure we correctly handle the pointer objects. |
||
| consulCatalog: consulCatalog, | ||
| connPool: pool.NewPool(logger, serverRPCCache, serverMaxStreams, tlsWrap, config.RPCSessionConfig), | ||
| logger: logger, | ||
|
|
@@ -372,6 +388,10 @@ func NewServer(config *Config, consulCatalog consul.CatalogAPI, consulConfigFunc | |
| lockDelayTimer: lock.NewDelayTimer(), | ||
| } | ||
|
|
||
| // Store the server TLS RPC settings that will be used by the connection | ||
| // handler. | ||
| s.setRPCTLSAtomics(config.TLSConfig) | ||
|
|
||
| s.shutdownCtx, s.shutdownCancel = context.WithCancel(context.Background()) | ||
| s.shutdownCh = s.shutdownCtx.Done() | ||
|
|
||
|
|
@@ -690,16 +710,19 @@ func (s *Server) reloadTLSConnections(newTLSConfig *config.TLSConfig) error { | |
| s.tlsWrap = tlsWrap | ||
| s.tlsWrapLock.Unlock() | ||
|
|
||
| // Keeping configuration in sync is important for other places that require | ||
| // access to config information, such as rpc.go, where we decide on what kind | ||
| // of network connections to accept depending on the server configuration | ||
| // Ensure the config object is updated. This is done to keep things in sync | ||
| // only; all access to TLS enforcement should be done via the atomics which | ||
| // are updated below. | ||
| s.config.TLSConfig = newTLSConfig | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't we still have a race condition here if we read from this configuration? Even if nothing else in the server reads this config object, it's going to get read during serialization for the Query Self API Not having a RW mutex on the |
||
|
|
||
| // Update the RPC TLS atomics used for connection handling. | ||
| s.setRPCTLSAtomics(newTLSConfig) | ||
|
|
||
| // Kill any old listeners | ||
| s.rpcCancel() | ||
|
|
||
| // Update the authenticator, so any changes in TLS verification are applied. | ||
| s.auth.SetVerifyTLS(s.config.TLSConfig != nil && s.config.TLSConfig.EnableRPC && s.config.TLSConfig.VerifyServerHostname) | ||
| s.auth.SetVerifyTLS(s.config.TLSConfig != nil && s.rpcTLSEnabled.Load() && s.config.TLSConfig.VerifyServerHostname) | ||
|
|
||
| s.rpcTLS = incomingTLS | ||
| s.connPool.ReloadTLS(tlsWrap) | ||
|
|
@@ -2088,6 +2111,16 @@ func (s *Server) setReplyQueryMeta(stateStore *state.StateStore, table string, r | |
| return nil | ||
| } | ||
|
|
||
| // setRPCTLSAtomics is a helper function to set the atomic values for RPC TLS | ||
| // handling based on the provided config. | ||
| // | ||
| // This should be used whenever the RPC TLS config is changed to ensure as it | ||
| // correctly handles nil configs which might otherwise be missed. | ||
| func (s *Server) setRPCTLSAtomics(cfg *config.TLSConfig) { | ||
| s.rpcTLSEnabled.Store(cfg != nil && cfg.EnableRPC) | ||
| s.rpcTLSUpgradeMode.Store(cfg != nil && cfg.RPCUpgradeMode) | ||
| } | ||
|
|
||
| // Region returns the region of the server | ||
| func (s *Server) Region() string { | ||
| return s.config.Region | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.