Skip to content
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

Detect Vault 1.11+ import, update default issuer #15253

Merged
Prev Previous commit
Next Next commit
Refactored setting default intermediate issuer
  • Loading branch information
Chris S. Kim committed Nov 15, 2022
commit 7074c985dd801979da0c47fb2f9f00718b9e84fb
101 changes: 71 additions & 30 deletions agent/connect/ca/provider_vault.go
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,8 @@ func (v *VaultProvider) GenerateIntermediateCSR() (string, error) {
"cannot generate an intermediate CSR")
}

return v.generateIntermediateCSR()
csr, _, err := v.generateIntermediateCSR()
return csr, err
}

func (v *VaultProvider) setupIntermediatePKIPath() error {
Expand Down Expand Up @@ -406,11 +407,13 @@ func (v *VaultProvider) setupIntermediatePKIPath() error {
return err
}

func (v *VaultProvider) generateIntermediateCSR() (string, error) {
// generateIntermediateCSR returns the CSR and key_id (only present in
// Vault 1.11+) or any errors encountered.
func (v *VaultProvider) generateIntermediateCSR() (string, string, error) {
// Generate a new intermediate CSR for the root to sign.
uid, err := connect.CompactUID()
if err != nil {
return "", err
return "", "", err
}
data, err := v.writeNamespaced(v.config.IntermediatePKINamespace, v.config.IntermediatePKIPath+"intermediate/generate/internal", map[string]interface{}{
"common_name": connect.CACN("vault", uid, v.clusterID, v.isPrimary),
Expand All @@ -419,17 +422,26 @@ func (v *VaultProvider) generateIntermediateCSR() (string, error) {
"uri_sans": v.spiffeID.URI().String(),
})
if err != nil {
return "", err
return "", "", err
}
if data == nil || data.Data["csr"] == "" {
return "", fmt.Errorf("got empty value when generating intermediate CSR")
return "", "", fmt.Errorf("got empty value when generating intermediate CSR")
}
csr, ok := data.Data["csr"].(string)
if !ok {
return "", fmt.Errorf("csr result is not a string")
return "", "", fmt.Errorf("csr result is not a string")
}

return csr, nil
// Vault 1.11+ will return a "key_id" field which helps
// identify the correct issuer to set as default.
// https://github.com/hashicorp/vault/blob/e445c8b4f58dc20a0316a7fd1b5725b401c3b17a/builtin/logical/pki/path_intermediate.go#L154
if rawkeyId, ok := data.Data["key_id"]; ok {
keyId, ok := rawkeyId.(string)
if !ok {
return "", "", fmt.Errorf("key_id is not a string")
}
return csr, keyId, nil
}
return csr, "", nil
}

// SetIntermediate writes the incoming intermediate and root certificates to the
Expand Down Expand Up @@ -531,7 +543,7 @@ func (v *VaultProvider) getCAChain(namespace, path string) (string, error) {
// necessary, then generates and signs a new CA CSR using the root PKI backend
// and updates the intermediate backend to use that new certificate.
func (v *VaultProvider) GenerateIntermediate() (string, error) {
csr, err := v.generateIntermediateCSR()
csr, keyId, err := v.generateIntermediateCSR()
if err != nil {
return "", err
}
Expand All @@ -558,35 +570,64 @@ func (v *VaultProvider) GenerateIntermediate() (string, error) {
return "", err
}

// Vault
if importResp != nil {
cipherboy marked this conversation as resolved.
Show resolved Hide resolved
// Assume we're on Vault 1.11+. We'll assume we only have one issuer
// with a key.
mapping := importResp.Data["mapping"].(map[string]string)
var intermediateId string
for issuer, key := range mapping {
if key != "" {
intermediateId = issuer

// Could be safe and check:
// importedIssuers := importResp.Data["imported_issuers"].([]string)
// and make sure this newly imported issuer is in the set of new unique
// issuers.
break
}
}

// Now post it to the default issuer.
_, err = v.writeNamespaced(v.config.IntermediatePKINamespace, v.config.IntermediatePKIPath+"config/issuers", map[string]interface{}{
"default": intermediateId,
})
err := v.setDefaultIntermediateIssuer(importResp, keyId)
if err != nil {
return "", err
return "", fmt.Errorf("failed to update default intermediate issuer: %w", err)
}
}

return v.ActiveIntermediate()
}

// setDefaultIntermediateIssuer updates the default issuer for
// intermediate CA since Vault, as part of its 1.11+ support for
// multiple issuers, no longer overwrites the default issuer when
// generateIntermediateCSR (intermediate/generate/internal) is called.
//
// The response we get from calling [/intermediate/set-signed]
// should contain a "mapping" data field we can use to cross-reference
// with the keyId returned when calling [/intermediate/generate/internal].
//
// [/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 {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good, like this refactor!

if vaultResp.Data["mapping"] == nil {
return fmt.Errorf("expected Vault response data to have a 'mapping' key")
}
if keyId == "" {
return fmt.Errorf("expected non-empty keyId")
}
mapping, ok := vaultResp.Data["mapping"].(map[string]string)
kisunji marked this conversation as resolved.
Show resolved Hide resolved
if !ok {
return fmt.Errorf("unexpected type for 'mapping' value in Vault response")
}
var intermediateId string
// The value in this KV pair is called "key"
for issuer, key := range mapping {
if key == keyId {
cipherboy marked this conversation as resolved.
Show resolved Hide resolved
// Expect to find the key_id we got from Vault when we
// generated the intermediate CSR.
intermediateId = issuer
break
}
}
if intermediateId == "" {
return fmt.Errorf("could not find key_id %q in response from vault", keyId)
}

// Post it as the default issuer.
_, err := v.writeNamespaced(v.config.IntermediatePKINamespace, v.config.IntermediatePKIPath+"config/issuers", map[string]interface{}{
"default": intermediateId,
kisunji marked this conversation as resolved.
Show resolved Hide resolved
})
if err != nil {
return err
}

return nil
}

// Sign calls the configured role in the intermediate PKI backend to issue
// a new leaf certificate based on the provided CSR, with the issuing
// intermediate CA cert attached.
Expand Down