Skip to content

Commit

Permalink
Vault CA provider clean up previous default issuers (#18773)
Browse files Browse the repository at this point in the history
(cherry picked from commit 4dfca64)
  • Loading branch information
Chris S. Kim committed Sep 13, 2023
1 parent ad19aac commit c269f40
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 6 deletions.
3 changes: 3 additions & 0 deletions .changelog/18773.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
ca: Vault provider now cleans up the previous Vault issuer and key when generating a new leaf signing certificate [[GH-18779](https://github.com/hashicorp/consul/issues/18779)]
```
45 changes: 45 additions & 0 deletions agent/connect/ca/provider_vault.go
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,9 @@ func (v *VaultProvider) GenerateIntermediate() (string, error) {
// should contain a "mapping" data field we can use to cross-reference
// with the keyId returned when calling [/intermediate/generate/internal].
//
// After a new default issuer is written, this function also cleans up
// the previous default issuer along with its associated key.
//
// [/intermediate/set-signed]: https://developer.hashicorp.com/vault/api-docs/secret/pki#import-ca-certificates-and-keys
// [/intermediate/generate/internal]: https://developer.hashicorp.com/vault/api-docs/secret/pki#generate-intermediate-csr
func (v *VaultProvider) setDefaultIntermediateIssuer(vaultResp *vaultapi.Secret, keyId string) error {
Expand Down Expand Up @@ -676,6 +679,15 @@ func (v *VaultProvider) setDefaultIntermediateIssuer(vaultResp *vaultapi.Secret,
return fmt.Errorf("could not read from /config/issuers: %w", err)
}
issuersConf := resp.Data
prevIssuer, ok := issuersConf["default"].(string)
if !ok {
return fmt.Errorf("unexpected type for 'default' value in Vault response from /pki/config/issuers")
}

if prevIssuer == intermediateId {
return nil
}

// Overwrite the default issuer
issuersConf["default"] = intermediateId

Expand All @@ -684,6 +696,34 @@ func (v *VaultProvider) setDefaultIntermediateIssuer(vaultResp *vaultapi.Secret,
return fmt.Errorf("could not write default issuer to /config/issuers: %w", err)
}

// Find the key_id of the previous issuer. In Consul, issuers have 1:1 relationship with
// keys so we can delete issuer first then the key.
resp, err = v.readNamespaced(v.config.IntermediatePKINamespace, v.config.IntermediatePKIPath+"issuer/"+prevIssuer)
if err != nil {
return fmt.Errorf("could not read issuer %q: %w", prevIssuer, err)
}
prevKeyId, ok := resp.Data["key_id"].(string)
if !ok {
return fmt.Errorf("unexpected type for 'key_id' value in Vault response")
}

// Delete the previously known default issuer to prevent the number of unused
// issuers from increasing too much.
_, err = v.deleteNamespaced(v.config.IntermediatePKINamespace, v.config.IntermediatePKIPath+"issuer/"+prevIssuer)
if err != nil {
v.logger.Warn("Could not delete previous issuer. Manually delete from Vault to prevent the list of issuers from growing too large.",
"prev_issuer_id", prevIssuer,
"error", err)
}

// Keys can only be deleted if there are no more issuers referencing them.
_, err = v.deleteNamespaced(v.config.IntermediatePKINamespace, v.config.IntermediatePKIPath+"key/"+prevKeyId)
if err != nil {
v.logger.Warn("Could not delete previous key. Manually delete from Vault to prevent the list of keys from growing too large.",
"prev_key_id", prevKeyId,
"error", err)
}

return nil
}

Expand Down Expand Up @@ -893,6 +933,11 @@ func (v *VaultProvider) writeNamespaced(namespace string, resource string, data
return v.client.Logical().Write(resource, data)
}

func (v *VaultProvider) deleteNamespaced(namespace string, resource string) (*vaultapi.Secret, error) {
defer v.setNamespace(namespace)()
return v.client.Logical().Delete(resource)
}

func (v *VaultProvider) setNamespace(namespace string) func() {
if namespace != "" {
v.clientMutex.Lock()
Expand Down
52 changes: 46 additions & 6 deletions agent/connect/ca/provider_vault_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/hashicorp/go-hclog"
vaultapi "github.com/hashicorp/vault/api"
vaultconst "github.com/hashicorp/vault/sdk/helper/consts"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/hashicorp/consul/agent/connect"
Expand Down Expand Up @@ -543,12 +544,6 @@ func TestVaultCAProvider_CrossSignCA(t *testing.T) {
run := func(t *testing.T, tc CASigningKeyTypes, withSudo, expectFailure bool) {
t.Parallel()

if tc.SigningKeyType != tc.CSRKeyType {
// TODO: uncomment since the bug is closed
// See https://github.com/hashicorp/vault/issues/7709
t.Skip("Vault doesn't support cross-signing different key types yet.")
}

testVault1 := NewTestVaultServer(t)

attr1 := &VaultTokenAttributes{
Expand Down Expand Up @@ -1125,6 +1120,51 @@ func TestVaultCAProvider_GenerateIntermediate(t *testing.T) {
require.NotEqual(t, orig, new)
}

func TestVaultCAProvider_DeletePreviousIssuerAndKey(t *testing.T) {
SkipIfVaultNotPresent(t)
t.Parallel()

testVault := NewTestVaultServer(t)
attr := &VaultTokenAttributes{
RootPath: "pki-root",
IntermediatePath: "pki-intermediate",
ConsulManaged: true,
}
token := CreateVaultTokenWithAttrs(t, testVault.client, attr)
provider := createVaultProvider(t, true, testVault.Addr, token,
map[string]any{
"RootPKIPath": "pki-root/",
"IntermediatePKIPath": "pki-intermediate/",
})
res, err := testVault.Client().Logical().List("pki-intermediate/issuers")
require.NoError(t, err)
// Why 2 issuers? There is always an initial issuer that
// gets created before we manage the lifecycle of issuers.
// Since we're asserting that the number doesn't grow
// this isn't too much of a concern.
//
// Note the key "keys" refers to the IDs of the resource based
// on the endpoint the response is from.
require.Len(t, res.Data["keys"], 2)

res, err = testVault.Client().Logical().List("pki-intermediate/keys")
require.NoError(t, err)
require.Len(t, res.Data["keys"], 1)

for i := 0; i < 3; i++ {
_, err := provider.GenerateLeafSigningCert()
require.NoError(t, err)

res, err := testVault.Client().Logical().List("pki-intermediate/issuers")
require.NoError(t, err)
require.Len(t, res.Data["keys"], 2)

res, err = testVault.Client().Logical().List("pki-intermediate/keys")
require.NoError(t, err)
assert.Len(t, res.Data["keys"], 1)
}
}

func TestVaultCAProvider_GenerateIntermediate_inSecondary(t *testing.T) {
SkipIfVaultNotPresent(t)

Expand Down

0 comments on commit c269f40

Please sign in to comment.