mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-11-01 11:08:10 +00:00
Fix client clone with headers deadlock (#22410)
* Fix client clone with headers deadlock * Changelog * Typo
This commit is contained in:
@@ -998,7 +998,9 @@ func (c *Client) Namespace() string {
|
|||||||
func (c *Client) WithNamespace(namespace string) *Client {
|
func (c *Client) WithNamespace(namespace string) *Client {
|
||||||
c2 := *c
|
c2 := *c
|
||||||
c2.modifyLock = sync.RWMutex{}
|
c2.modifyLock = sync.RWMutex{}
|
||||||
c2.headers = c.Headers()
|
c.modifyLock.RLock()
|
||||||
|
c2.headers = c.headersInternal()
|
||||||
|
c.modifyLock.RUnlock()
|
||||||
if namespace == "" {
|
if namespace == "" {
|
||||||
c2.ClearNamespace()
|
c2.ClearNamespace()
|
||||||
} else {
|
} else {
|
||||||
@@ -1035,7 +1037,12 @@ func (c *Client) ClearToken() {
|
|||||||
func (c *Client) Headers() http.Header {
|
func (c *Client) Headers() http.Header {
|
||||||
c.modifyLock.RLock()
|
c.modifyLock.RLock()
|
||||||
defer c.modifyLock.RUnlock()
|
defer c.modifyLock.RUnlock()
|
||||||
|
return c.headersInternal()
|
||||||
|
}
|
||||||
|
|
||||||
|
// headersInternal gets the current set of headers used for requests. Must be called
|
||||||
|
// with the read modifyLock held.
|
||||||
|
func (c *Client) headersInternal() http.Header {
|
||||||
if c.headers == nil {
|
if c.headers == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -1183,24 +1190,28 @@ func (c *Client) CloneTLSConfig() bool {
|
|||||||
// the api.Config struct, such as policy override and wrapping function
|
// the api.Config struct, such as policy override and wrapping function
|
||||||
// behavior, must currently then be set as desired on the new client.
|
// behavior, must currently then be set as desired on the new client.
|
||||||
func (c *Client) Clone() (*Client, error) {
|
func (c *Client) Clone() (*Client, error) {
|
||||||
|
c.modifyLock.RLock()
|
||||||
|
defer c.modifyLock.RUnlock()
|
||||||
|
c.config.modifyLock.RLock()
|
||||||
|
defer c.config.modifyLock.RUnlock()
|
||||||
return c.clone(c.config.CloneHeaders)
|
return c.clone(c.config.CloneHeaders)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CloneWithHeaders creates a new client similar to Clone, with the difference
|
// CloneWithHeaders creates a new client similar to Clone, with the difference
|
||||||
// being that the headers are always cloned
|
// being that the headers are always cloned
|
||||||
func (c *Client) CloneWithHeaders() (*Client, error) {
|
func (c *Client) CloneWithHeaders() (*Client, error) {
|
||||||
|
c.modifyLock.RLock()
|
||||||
|
defer c.modifyLock.RUnlock()
|
||||||
|
c.config.modifyLock.RLock()
|
||||||
|
defer c.config.modifyLock.RUnlock()
|
||||||
return c.clone(true)
|
return c.clone(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// clone creates a new client, with the headers being cloned based on the
|
// clone creates a new client, with the headers being cloned based on the
|
||||||
// passed in cloneheaders boolean
|
// passed in cloneheaders boolean.
|
||||||
|
// Must be called with the read lock and config read lock held.
|
||||||
func (c *Client) clone(cloneHeaders bool) (*Client, error) {
|
func (c *Client) clone(cloneHeaders bool) (*Client, error) {
|
||||||
c.modifyLock.RLock()
|
|
||||||
defer c.modifyLock.RUnlock()
|
|
||||||
|
|
||||||
config := c.config
|
config := c.config
|
||||||
config.modifyLock.RLock()
|
|
||||||
defer config.modifyLock.RUnlock()
|
|
||||||
|
|
||||||
newConfig := &Config{
|
newConfig := &Config{
|
||||||
Address: config.Address,
|
Address: config.Address,
|
||||||
@@ -1230,7 +1241,7 @@ func (c *Client) clone(cloneHeaders bool) (*Client, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if cloneHeaders {
|
if cloneHeaders {
|
||||||
client.SetHeaders(c.Headers().Clone())
|
client.SetHeaders(c.headersInternal().Clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.CloneToken {
|
if config.CloneToken {
|
||||||
@@ -1261,6 +1272,7 @@ func (c *Client) NewRequest(method, requestPath string) *Request {
|
|||||||
mfaCreds := c.mfaCreds
|
mfaCreds := c.mfaCreds
|
||||||
wrappingLookupFunc := c.wrappingLookupFunc
|
wrappingLookupFunc := c.wrappingLookupFunc
|
||||||
policyOverride := c.policyOverride
|
policyOverride := c.policyOverride
|
||||||
|
headers := c.headersInternal()
|
||||||
c.modifyLock.RUnlock()
|
c.modifyLock.RUnlock()
|
||||||
|
|
||||||
host := addr.Host
|
host := addr.Host
|
||||||
@@ -1305,7 +1317,7 @@ func (c *Client) NewRequest(method, requestPath string) *Request {
|
|||||||
req.WrapTTL = DefaultWrappingLookupFunc(method, lookupPath)
|
req.WrapTTL = DefaultWrappingLookupFunc(method, lookupPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Headers = c.Headers()
|
req.Headers = headers
|
||||||
req.PolicyOverride = policyOverride
|
req.PolicyOverride = policyOverride
|
||||||
|
|
||||||
return req
|
return req
|
||||||
|
|||||||
@@ -738,6 +738,61 @@ func TestClone(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestCloneWithHeadersNoDeadlock confirms that the cloning of the client doesn't cause
|
||||||
|
// a deadlock.
|
||||||
|
// Raised in https://github.com/hashicorp/vault/issues/22393 -- there was a
|
||||||
|
// potential deadlock caused by running the problematicFunc() function in
|
||||||
|
// multiple goroutines.
|
||||||
|
func TestCloneWithHeadersNoDeadlock(t *testing.T) {
|
||||||
|
client, err := NewClient(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg := &sync.WaitGroup{}
|
||||||
|
|
||||||
|
problematicFunc := func() {
|
||||||
|
wg.Add(1)
|
||||||
|
client.SetCloneToken(true)
|
||||||
|
_, err := client.CloneWithHeaders()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
wg.Done()
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 1000; i++ {
|
||||||
|
go problematicFunc()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestCloneNoDeadlock is like TestCloneWithHeadersNoDeadlock but with
|
||||||
|
// Clone instead of CloneWithHeaders
|
||||||
|
func TestCloneNoDeadlock(t *testing.T) {
|
||||||
|
client, err := NewClient(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg := &sync.WaitGroup{}
|
||||||
|
|
||||||
|
problematicFunc := func() {
|
||||||
|
wg.Add(1)
|
||||||
|
client.SetCloneToken(true)
|
||||||
|
_, err := client.Clone()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
wg.Done()
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 1000; i++ {
|
||||||
|
go problematicFunc()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
func TestSetHeadersRaceSafe(t *testing.T) {
|
func TestSetHeadersRaceSafe(t *testing.T) {
|
||||||
client, err1 := NewClient(nil)
|
client, err1 := NewClient(nil)
|
||||||
if err1 != nil {
|
if err1 != nil {
|
||||||
|
|||||||
3
changelog/22410.txt
Normal file
3
changelog/22410.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
```release-note:bug
|
||||||
|
api/client: Fix deadlock in client.CloneWithHeaders when used alongside other client methods.
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user