open-vault/builtin/logical/pki/path_fetch_keys.go
John-Michael Faircloth f9541a1c96
pki: add subject key identifier to read key response (#20642)
* pki: add subject key identifier to read key response

This will be helpful for the Terraform Vault Provider to detect
migration of pre-1.11 exported keys (from CA generation) into post-1.11
Vault.

* add changelog

* Update builtin/logical/pki/path_fetch_keys.go

Co-authored-by: Alexander Scheel <alex.scheel@hashicorp.com>

* check for managed key first

* Validate the SKID matches on root CAs

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

* Validate SKID matches on int CAs

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

* Fix formatting of tests

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

---------

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>
Co-authored-by: Alexander Scheel <alex.scheel@hashicorp.com>
2023-05-18 16:49:22 +00:00

401 lines
11 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package pki
import (
"context"
"crypto"
"fmt"
"net/http"
"github.com/hashicorp/vault/sdk/helper/certutil"
"github.com/hashicorp/vault/sdk/helper/errutil"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/logical"
)
func pathListKeys(b *backend) *framework.Path {
return &framework.Path{
Pattern: "keys/?$",
DisplayAttrs: &framework.DisplayAttributes{
OperationPrefix: operationPrefixPKI,
OperationSuffix: "keys",
},
Operations: map[logical.Operation]framework.OperationHandler{
logical.ListOperation: &framework.PathOperation{
Callback: b.pathListKeysHandler,
Responses: map[int][]framework.Response{
http.StatusOK: {{
Description: "OK",
Fields: map[string]*framework.FieldSchema{
"keys": {
Type: framework.TypeStringSlice,
Description: `A list of keys`,
Required: true,
},
"key_info": {
Type: framework.TypeMap,
Description: `Key info with issuer name`,
Required: false,
},
},
}},
},
ForwardPerformanceStandby: false,
ForwardPerformanceSecondary: false,
},
},
HelpSynopsis: pathListKeysHelpSyn,
HelpDescription: pathListKeysHelpDesc,
}
}
const (
pathListKeysHelpSyn = `Fetch a list of all issuer keys`
pathListKeysHelpDesc = `This endpoint allows listing of known backing keys, returning
their identifier and their name (if set).`
)
func (b *backend) pathListKeysHandler(ctx context.Context, req *logical.Request, _ *framework.FieldData) (*logical.Response, error) {
if b.useLegacyBundleCaStorage() {
return logical.ErrorResponse("Can not list keys until migration has completed"), nil
}
var responseKeys []string
responseInfo := make(map[string]interface{})
sc := b.makeStorageContext(ctx, req.Storage)
entries, err := sc.listKeys()
if err != nil {
return nil, err
}
config, err := sc.getKeysConfig()
if err != nil {
return nil, err
}
for _, identifier := range entries {
key, err := sc.fetchKeyById(identifier)
if err != nil {
return nil, err
}
responseKeys = append(responseKeys, string(identifier))
responseInfo[string(identifier)] = map[string]interface{}{
keyNameParam: key.Name,
"is_default": identifier == config.DefaultKeyId,
}
}
return logical.ListResponseWithInfo(responseKeys, responseInfo), nil
}
func pathKey(b *backend) *framework.Path {
pattern := "key/" + framework.GenericNameRegex(keyRefParam)
displayAttrs := &framework.DisplayAttributes{
OperationPrefix: operationPrefixPKI,
OperationSuffix: "key",
}
return buildPathKey(b, pattern, displayAttrs)
}
func buildPathKey(b *backend, pattern string, displayAttrs *framework.DisplayAttributes) *framework.Path {
return &framework.Path{
Pattern: pattern,
DisplayAttrs: displayAttrs,
Fields: map[string]*framework.FieldSchema{
keyRefParam: {
Type: framework.TypeString,
Description: `Reference to key; either "default" for the configured default key, an identifier of a key, or the name assigned to the key.`,
Default: defaultRef,
},
keyNameParam: {
Type: framework.TypeString,
Description: `Human-readable name for this key.`,
},
},
Operations: map[logical.Operation]framework.OperationHandler{
logical.ReadOperation: &framework.PathOperation{
Callback: b.pathGetKeyHandler,
Responses: map[int][]framework.Response{
http.StatusOK: {{
Description: "OK",
Fields: map[string]*framework.FieldSchema{
"key_id": {
Type: framework.TypeString,
Description: `Key Id`,
Required: true,
},
"key_name": {
Type: framework.TypeString,
Description: `Key Name`,
Required: true,
},
"key_type": {
Type: framework.TypeString,
Description: `Key Type`,
Required: true,
},
"subject_key_id": {
Type: framework.TypeString,
Description: `RFC 5280 Subject Key Identifier of the public counterpart`,
Required: false,
},
"managed_key_id": {
Type: framework.TypeString,
Description: `Managed Key Id`,
Required: false,
},
"managed_key_name": {
Type: framework.TypeString,
Description: `Managed Key Name`,
Required: false,
},
},
}},
},
ForwardPerformanceStandby: false,
ForwardPerformanceSecondary: false,
},
logical.UpdateOperation: &framework.PathOperation{
Callback: b.pathUpdateKeyHandler,
Responses: map[int][]framework.Response{
http.StatusNoContent: {{
Description: "OK",
Fields: map[string]*framework.FieldSchema{
"key_id": {
Type: framework.TypeString,
Description: `Key Id`,
Required: true,
},
"key_name": {
Type: framework.TypeString,
Description: `Key Name`,
Required: true,
},
"key_type": {
Type: framework.TypeString,
Description: `Key Type`,
Required: true,
},
},
}},
},
ForwardPerformanceStandby: true,
ForwardPerformanceSecondary: true,
},
logical.DeleteOperation: &framework.PathOperation{
Callback: b.pathDeleteKeyHandler,
Responses: map[int][]framework.Response{
http.StatusNoContent: {{
Description: "No Content",
}},
},
ForwardPerformanceStandby: true,
ForwardPerformanceSecondary: true,
},
},
HelpSynopsis: pathKeysHelpSyn,
HelpDescription: pathKeysHelpDesc,
}
}
const (
pathKeysHelpSyn = `Fetch a single issuer key`
pathKeysHelpDesc = `This allows fetching information associated with the underlying key.
:ref can be either the literal value "default", in which case /config/keys
will be consulted for the present default key, an identifier of a key,
or its assigned name value.
Writing to /key/:ref allows updating of the name field associated with
the certificate.
`
)
func (b *backend) pathGetKeyHandler(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
if b.useLegacyBundleCaStorage() {
return logical.ErrorResponse("Can not get keys until migration has completed"), nil
}
keyRef := data.Get(keyRefParam).(string)
if len(keyRef) == 0 {
return logical.ErrorResponse("missing key reference"), nil
}
sc := b.makeStorageContext(ctx, req.Storage)
keyId, err := sc.resolveKeyReference(keyRef)
if err != nil {
return nil, err
}
if keyId == "" {
return logical.ErrorResponse("unable to resolve key id for reference" + keyRef), nil
}
key, err := sc.fetchKeyById(keyId)
if err != nil {
return nil, err
}
respData := map[string]interface{}{
keyIdParam: key.ID,
keyNameParam: key.Name,
keyTypeParam: string(key.PrivateKeyType),
}
var pkForSkid crypto.PublicKey
if key.isManagedPrivateKey() {
managedKeyUUID, err := key.getManagedKeyUUID()
if err != nil {
return nil, errutil.InternalError{Err: fmt.Sprintf("failed extracting managed key uuid from key id %s (%s): %v", key.ID, key.Name, err)}
}
keyInfo, err := getManagedKeyInfo(ctx, b, managedKeyUUID)
if err != nil {
return nil, errutil.InternalError{Err: fmt.Sprintf("failed fetching managed key info from key id %s (%s): %v", key.ID, key.Name, err)}
}
pkForSkid, err = getManagedKeyPublicKey(sc.Context, sc.Backend, managedKeyUUID)
if err != nil {
return nil, err
}
// To remain consistent across the api responses (mainly generate root/intermediate calls), return the actual
// type of key, not that it is a managed key.
respData[keyTypeParam] = string(keyInfo.keyType)
respData[managedKeyIdArg] = string(keyInfo.uuid)
respData[managedKeyNameArg] = string(keyInfo.name)
} else {
pkForSkid, err = getPublicKeyFromBytes([]byte(key.PrivateKey))
if err != nil {
return nil, err
}
}
skid, err := certutil.GetSubjectKeyID(pkForSkid)
if err != nil {
return nil, err
}
respData[skidParam] = certutil.GetHexFormatted([]byte(skid), ":")
return &logical.Response{Data: respData}, nil
}
func (b *backend) pathUpdateKeyHandler(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
// Since we're planning on updating keys here, grab the lock so we've
// got a consistent view.
b.issuersLock.Lock()
defer b.issuersLock.Unlock()
if b.useLegacyBundleCaStorage() {
return logical.ErrorResponse("Can not update keys until migration has completed"), nil
}
keyRef := data.Get(keyRefParam).(string)
if len(keyRef) == 0 {
return logical.ErrorResponse("missing key reference"), nil
}
sc := b.makeStorageContext(ctx, req.Storage)
keyId, err := sc.resolveKeyReference(keyRef)
if err != nil {
return nil, err
}
if keyId == "" {
return logical.ErrorResponse("unable to resolve key id for reference" + keyRef), nil
}
key, err := sc.fetchKeyById(keyId)
if err != nil {
return nil, err
}
newName := data.Get(keyNameParam).(string)
if len(newName) > 0 && !nameMatcher.MatchString(newName) {
return logical.ErrorResponse("new key name outside of valid character limits"), nil
}
if newName != key.Name {
key.Name = newName
err := sc.writeKey(*key)
if err != nil {
return nil, err
}
}
resp := &logical.Response{
Data: map[string]interface{}{
keyIdParam: key.ID,
keyNameParam: key.Name,
keyTypeParam: key.PrivateKeyType,
},
}
if len(newName) == 0 {
resp.AddWarning("Name successfully deleted, you will now need to reference this key by it's Id: " + string(key.ID))
}
return resp, nil
}
func (b *backend) pathDeleteKeyHandler(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
// Since we're planning on updating issuers here, grab the lock so we've
// got a consistent view.
b.issuersLock.Lock()
defer b.issuersLock.Unlock()
if b.useLegacyBundleCaStorage() {
return logical.ErrorResponse("Can not delete keys until migration has completed"), nil
}
keyRef := data.Get(keyRefParam).(string)
if len(keyRef) == 0 {
return logical.ErrorResponse("missing key reference"), nil
}
sc := b.makeStorageContext(ctx, req.Storage)
keyId, err := sc.resolveKeyReference(keyRef)
if err != nil {
if keyId == KeyRefNotFound {
// We failed to lookup the key, we should ignore any error here and reply as if it was deleted.
return nil, nil
}
return nil, err
}
keyInUse, issuerId, err := sc.isKeyInUse(keyId.String())
if err != nil {
return nil, err
}
if keyInUse {
return logical.ErrorResponse(fmt.Sprintf("Failed to Delete Key. Key in Use by Issuer: %s", issuerId)), nil
}
wasDefault, err := sc.deleteKey(keyId)
if err != nil {
return nil, err
}
var response *logical.Response
if wasDefault {
msg := fmt.Sprintf("Deleted key %v (via key_ref %v); this was configured as the default key. Operations without an explicit key will not work until a new default is configured.", string(keyId), keyRef)
b.Logger().Error(msg)
response = &logical.Response{}
response.AddWarning(msg)
}
return response, nil
}