From 7469b0828a8245cc99b4185a825a7b6d4009a345 Mon Sep 17 00:00:00 2001 From: Karel Date: Wed, 22 Mar 2023 16:01:58 +0100 Subject: [PATCH] Fix: Optionally reload x509 key-pair from disk on agent auto-auth (#19002) * Optionally reload x509 key-pair from disk * Document 'reload' config value * Added changelog release note --- changelog/19002.txt | 3 + command/agent/auth/cert/cert.go | 11 +++- command/agent/auth/cert/cert_test.go | 56 +++++++++++++++++++ .../docs/agent/autoauth/methods/cert.mdx | 3 + 4 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 changelog/19002.txt diff --git a/changelog/19002.txt b/changelog/19002.txt new file mode 100644 index 000000000..d1a1ff537 --- /dev/null +++ b/changelog/19002.txt @@ -0,0 +1,3 @@ +```release-note:improvement +agent: Added `reload` option to cert auth configuration in case of external renewals of local x509 key-pairs. +``` \ No newline at end of file diff --git a/command/agent/auth/cert/cert.go b/command/agent/auth/cert/cert.go index 96f43de69..ebcfca173 100644 --- a/command/agent/auth/cert/cert.go +++ b/command/agent/auth/cert/cert.go @@ -23,6 +23,7 @@ type certMethod struct { caCert string clientCert string clientKey string + reload bool // Client is the cached client to use if cert info was provided. client *api.Client @@ -76,6 +77,14 @@ func NewCertAuthMethod(conf *auth.AuthConfig) (auth.AuthMethod, error) { return nil, errors.New("could not convert 'cert_key' config value to string") } } + + reload, ok := conf.Config["reload"] + if ok { + c.reload, ok = reload.(bool) + if !ok { + return nil, errors.New("could not convert 'reload' config value to bool") + } + } } return c, nil @@ -111,7 +120,7 @@ func (c *certMethod) AuthClient(client *api.Client) (*api.Client, error) { if c.caCert != "" || (c.clientKey != "" && c.clientCert != "") { // Return cached client if present - if c.client != nil { + if c.client != nil && !c.reload { return c.client, nil } diff --git a/command/agent/auth/cert/cert_test.go b/command/agent/auth/cert/cert_test.go index a5d5e6a5f..005da3eaf 100644 --- a/command/agent/auth/cert/cert_test.go +++ b/command/agent/auth/cert/cert_test.go @@ -133,3 +133,59 @@ func TestCertAuthMethod_AuthClient_withCerts(t *testing.T) { t.Fatal("expected client from AuthClient to return back a cached client") } } + +func TestCertAuthMethod_AuthClient_withCertsReload(t *testing.T) { + clientCert, err := os.Open("./test-fixtures/keys/cert.pem") + if err != nil { + t.Fatal(err) + } + + defer clientCert.Close() + + clientKey, err := os.Open("./test-fixtures/keys/key.pem") + if err != nil { + t.Fatal(err) + } + + defer clientKey.Close() + + config := &auth.AuthConfig{ + Logger: hclog.NewNullLogger(), + MountPath: "cert-test", + Config: map[string]interface{}{ + "name": "with-certs-reloaded", + "client_cert": clientCert.Name(), + "client_key": clientKey.Name(), + "reload": true, + }, + } + + method, err := NewCertAuthMethod(config) + if err != nil { + t.Fatal(err) + } + + client, err := api.NewClient(nil) + if err != nil { + t.Fatal(err) + } + + clientToUse, err := method.(auth.AuthMethodWithClient).AuthClient(client) + if err != nil { + t.Fatal(err) + } + + if client == clientToUse { + t.Fatal("expected client from AuthClient to be different from original client") + } + + // Call AuthClient again to get back a new client with reloaded certificates + reloadedClient, err := method.(auth.AuthMethodWithClient).AuthClient(client) + if err != nil { + t.Fatal(err) + } + + if reloadedClient == clientToUse { + t.Fatal("expected client from AuthClient to return back a new client") + } +} diff --git a/website/content/docs/agent/autoauth/methods/cert.mdx b/website/content/docs/agent/autoauth/methods/cert.mdx index 73a7cefd3..369e68d2c 100644 --- a/website/content/docs/agent/autoauth/methods/cert.mdx +++ b/website/content/docs/agent/autoauth/methods/cert.mdx @@ -30,3 +30,6 @@ Stanza](/vault/docs/agent#vault-stanza). - `client_key` `(string: optional)` - Path on the local disk to a single PEM-encoded private key matching the client certificate from client_cert. + +- `reload` `(bool: optional, default: false)` - If true, causes the local x509 key-pair to be reloaded from disk on each authentication attempt. + This is useful in situations where client certificates are short-lived and automatically renewed.