Skip to content

Commit f74798c

Browse files
Chris S. Kimjmurret
authored andcommitted
Fix Vault managed intermediate PKI bug (#15525)
1 parent 1e398ce commit f74798c

File tree

3 files changed

+156
-8
lines changed

3 files changed

+156
-8
lines changed

.changelog/15525.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:bug
2+
ca: Fixed issue where using Vault as Connect CA with Vault-managed policies would error on start-up if the intermediate PKI mount existed but was empty
3+
```

agent/connect/ca/provider_vault.go

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,13 @@ type VaultProvider struct {
7070
clusterID string
7171
spiffeID *connect.SpiffeIDSigning
7272
logger hclog.Logger
73+
74+
// isConsulMountedIntermediate is used to determine if we should tune the
75+
// mount if the VaultProvider is ever reconfigured. This is at most a
76+
// "best guess" to determine whether this instance of Consul created the
77+
// intermediate mount but will not be able to tell if an existing mount
78+
// was created by Consul (in a previous running instance) or was external.
79+
isConsulMountedIntermediate bool
7380
}
7481

7582
func NewVaultProvider(logger hclog.Logger) *VaultProvider {
@@ -310,9 +317,10 @@ func (v *VaultProvider) GenerateRoot() (RootResult, error) {
310317
},
311318
})
312319
if err != nil {
313-
return RootResult{}, err
320+
return RootResult{}, fmt.Errorf("failed to mount root CA backend: %w", err)
314321
}
315322

323+
// We want to initialize afterwards
316324
fallthrough
317325
case ErrBackendNotInitialized:
318326
uid, err := connect.CompactUID()
@@ -326,7 +334,7 @@ func (v *VaultProvider) GenerateRoot() (RootResult, error) {
326334
"key_bits": v.config.PrivateKeyBits,
327335
})
328336
if err != nil {
329-
return RootResult{}, err
337+
return RootResult{}, fmt.Errorf("failed to initialize root CA: %w", err)
330338
}
331339
var ok bool
332340
rootPEM, ok = resp.Data["certificate"].(string)
@@ -336,7 +344,7 @@ func (v *VaultProvider) GenerateRoot() (RootResult, error) {
336344

337345
default:
338346
if err != nil {
339-
return RootResult{}, err
347+
return RootResult{}, fmt.Errorf("unexpected error while setting root PKI backend: %w", err)
340348
}
341349
}
342350

@@ -381,19 +389,51 @@ func (v *VaultProvider) setupIntermediatePKIPath() error {
381389
Config: mountConfig,
382390
})
383391
if err != nil {
384-
return err
392+
return fmt.Errorf("failed to mount intermediate PKI backend: %w", err)
385393
}
394+
// Required to determine if we should tune the mount
395+
// if the VaultProvider is ever reconfigured.
396+
v.isConsulMountedIntermediate = true
397+
398+
} else if err == ErrBackendNotInitialized {
399+
// If this is the first time calling setupIntermediatePKIPath, the backend
400+
// will not have been initialized. Since the mount is ready we can suppress
401+
// this error.
386402
} else {
387-
return err
403+
return fmt.Errorf("unexpected error while fetching intermediate CA: %w", err)
388404
}
389405
} else {
406+
v.logger.Info("Found existing Intermediate PKI path mount",
407+
"namespace", v.config.IntermediatePKINamespace,
408+
"path", v.config.IntermediatePKIPath,
409+
)
410+
411+
// This codepath requires the Vault policy:
412+
//
413+
// path "/sys/mounts/<intermediate_pki_path>/tune" {
414+
// capabilities = [ "update" ]
415+
// }
416+
//
390417
err := v.tuneMountNamespaced(v.config.IntermediatePKINamespace, v.config.IntermediatePKIPath, &mountConfig)
391418
if err != nil {
392-
v.logger.Warn("Could not update intermediate PKI mount settings", "path", v.config.IntermediatePKIPath, "error", err)
419+
if v.isConsulMountedIntermediate {
420+
v.logger.Warn("Intermediate PKI path was mounted by Consul but could not be tuned",
421+
"namespace", v.config.IntermediatePKINamespace,
422+
"path", v.config.IntermediatePKIPath,
423+
"error", err,
424+
)
425+
} else {
426+
v.logger.Debug("Failed to tune Intermediate PKI mount. 403 Forbidden is expected if Consul does not have tune capabilities for the Intermediate PKI mount (i.e. using Vault-managed policies)",
427+
"namespace", v.config.IntermediatePKINamespace,
428+
"path", v.config.IntermediatePKIPath,
429+
"error", err,
430+
)
431+
}
432+
393433
}
394434
}
395435

396-
// Create the role for issuing leaf certs if it doesn't exist yet
436+
// Create the role for issuing leaf certs
397437
rolePath := v.config.IntermediatePKIPath + "roles/" + VaultCALeafCertRole
398438
_, err = v.writeNamespaced(v.config.IntermediatePKINamespace, rolePath, map[string]interface{}{
399439
"allow_any_name": true,
@@ -710,7 +750,7 @@ func (v *VaultProvider) SignIntermediate(csr *x509.CertificateRequest) (string,
710750
func (v *VaultProvider) CrossSignCA(cert *x509.Certificate) (string, error) {
711751
rootPEM, err := v.getCA(v.config.RootPKINamespace, v.config.RootPKIPath)
712752
if err != nil {
713-
return "", err
753+
return "", fmt.Errorf("failed to get root CA: %w", err)
714754
}
715755
rootCert, err := connect.ParseCert(rootPEM)
716756
if err != nil {

agent/connect/ca/provider_vault_test.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -924,6 +924,111 @@ func TestVaultCAProvider_GenerateIntermediate(t *testing.T) {
924924
require.NotEqual(t, orig, new)
925925
}
926926

927+
func TestVaultCAProvider_VaultManaged(t *testing.T) {
928+
929+
SkipIfVaultNotPresent(t)
930+
931+
const vaultManagedPKIPolicy = `
932+
path "/pki-root/" {
933+
capabilities = [ "read" ]
934+
}
935+
936+
path "/pki-root/root/sign-intermediate" {
937+
capabilities = [ "update" ]
938+
}
939+
940+
path "/pki-intermediate/*" {
941+
capabilities = [ "create", "read", "update", "delete", "list" ]
942+
}
943+
944+
path "auth/token/renew-self" {
945+
capabilities = [ "update" ]
946+
}
947+
948+
path "auth/token/lookup-self" {
949+
capabilities = [ "read" ]
950+
}
951+
`
952+
953+
testVault, err := runTestVault(t)
954+
if err != nil {
955+
t.Fatalf("err: %v", err)
956+
}
957+
958+
testVault.WaitUntilReady(t)
959+
960+
client := testVault.Client()
961+
962+
client.SetToken("root")
963+
964+
// Mount pki root externally
965+
require.NoError(t, client.Sys().Mount("pki-root", &vaultapi.MountInput{
966+
Type: "pki",
967+
Description: "root CA backend for Consul Connect",
968+
Config: vaultapi.MountConfigInput{
969+
MaxLeaseTTL: "12m",
970+
},
971+
}))
972+
_, err = client.Logical().Write("pki-root/root/generate/internal", map[string]interface{}{
973+
"common_name": "testconsul",
974+
})
975+
require.NoError(t, err)
976+
977+
// Mount pki intermediate externally
978+
require.NoError(t, client.Sys().Mount("pki-intermediate", &vaultapi.MountInput{
979+
Type: "pki",
980+
Description: "intermediate CA backend for Consul Connect",
981+
Config: vaultapi.MountConfigInput{
982+
MaxLeaseTTL: "6m",
983+
},
984+
}))
985+
986+
// Generate a policy and token for the VaultProvider to use
987+
require.NoError(t, client.Sys().PutPolicy("consul-ca", vaultManagedPKIPolicy))
988+
tcr := &vaultapi.TokenCreateRequest{
989+
Policies: []string{"consul-ca"},
990+
}
991+
secret, err := testVault.client.Auth().Token().Create(tcr)
992+
require.NoError(t, err)
993+
providerToken := secret.Auth.ClientToken
994+
995+
// We want to test the provider.Configure() step
996+
_, err = createVaultProvider(t, true, testVault.Addr, providerToken, nil)
997+
require.NoError(t, err)
998+
}
999+
1000+
func TestVaultCAProvider_ConsulManaged(t *testing.T) {
1001+
1002+
SkipIfVaultNotPresent(t)
1003+
1004+
testVault, err := runTestVault(t)
1005+
if err != nil {
1006+
t.Fatalf("err: %v", err)
1007+
}
1008+
1009+
testVault.WaitUntilReady(t)
1010+
1011+
client := testVault.Client()
1012+
1013+
client.SetToken("root")
1014+
1015+
// We do not configure any mounts and instead let Consul
1016+
// be responsible for mounting root and intermediate PKI
1017+
1018+
// Generate a policy and token for the VaultProvider to use
1019+
require.NoError(t, client.Sys().PutPolicy("consul-ca", pkiTestPolicy))
1020+
tcr := &vaultapi.TokenCreateRequest{
1021+
Policies: []string{"consul-ca"},
1022+
}
1023+
secret, err := testVault.client.Auth().Token().Create(tcr)
1024+
require.NoError(t, err)
1025+
providerToken := secret.Auth.ClientToken
1026+
1027+
// We want to test the provider.Configure() step
1028+
_, err = createVaultProvider(t, true, testVault.Addr, providerToken, nil)
1029+
require.NoError(t, err)
1030+
}
1031+
9271032
func getIntermediateCertTTL(t *testing.T, caConf *structs.CAConfiguration) time.Duration {
9281033
t.Helper()
9291034

0 commit comments

Comments
 (0)