Skip to content

Commit

Permalink
Kitography/vault 5474 rebase (#15150)
Browse files Browse the repository at this point in the history
* These parts work (put in signature so that backend wouldn't break, but missing fields, desc, etc.)

* Import and Generate API calls w/ needed additions to SDK.

* make fmt

* Add Help/Sync Text, fix some of internal/exported/kms code.

* Fix PEM/DER Encoding issue.

* make fmt

* Standardize keyIdParam, keyNameParam, keyTypeParam

* Add error response if key to be deleted is in use.

* replaces all instances of "default" in code with defaultRef

* Updates from Callbacks to Operations Function with explicit forwarding.

* Fixes a panic with names not being updated everywhere.

* add a logged error in addition to warning on deleting default key.

* Normalize whitespace upon importing keys.

Authored-by: Alexander Scheel <alexander.m.scheel@gmail.com>

* Fix isKeyInUse functionality.

* Fixes tests associated with newline at end of key pem.
  • Loading branch information
kitography authored Apr 28, 2022
1 parent 40417d1 commit b0ad729
Show file tree
Hide file tree
Showing 12 changed files with 568 additions and 11 deletions.
7 changes: 7 additions & 0 deletions builtin/logical/pki/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,13 @@ func Backend(conf *logical.BackendConfig) *backend {
pathIssuerGenerateIntermediate(&b),
pathConfigIssuers(&b),

// Key APIs
pathListKeys(&b),
pathKey(&b),
pathGenerateKey(&b),
pathImportKey(&b),
pathConfigKeys(&b),

// Fetch APIs have been lowered to favor the newer issuer API endpoints
pathFetchCA(&b),
pathFetchCAChain(&b),
Expand Down
8 changes: 6 additions & 2 deletions builtin/logical/pki/fields.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import "github.com/hashicorp/vault/sdk/framework"

const (
issuerRefParam = "issuer_ref"
keyNameParam = "key_name"
keyRefParam = "key_ref"
keyIdParam = "key_id"
keyTypeParam = "key_type"
)

// addIssueAndSignCommonFields adds fields common to both CA and non-CA issuing
Expand Down Expand Up @@ -375,7 +379,7 @@ func addKeyRefNameFields(fields map[string]*framework.FieldSchema) map[string]*f
}

func addKeyNameField(fields map[string]*framework.FieldSchema) map[string]*framework.FieldSchema {
fields["key_name"] = &framework.FieldSchema{
fields[keyNameParam] = &framework.FieldSchema{
Type: framework.TypeString,
Description: `Provide a name for the key that will be generated,
the name must be unique across all keys and not be the reserved value
Expand All @@ -386,7 +390,7 @@ the name must be unique across all keys and not be the reserved value
}

func addKeyRefField(fields map[string]*framework.FieldSchema) map[string]*framework.FieldSchema {
fields["key_ref"] = &framework.FieldSchema{
fields[keyRefParam] = &framework.FieldSchema{
Type: framework.TypeString,
Description: `Reference to a existing key; either "default"
for the configured default key, an identifier or the name assigned
Expand Down
78 changes: 75 additions & 3 deletions builtin/logical/pki/path_config_ca.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func pathConfigIssuers(b *backend) *framework.Path {
return &framework.Path{
Pattern: "config/issuers",
Fields: map[string]*framework.FieldSchema{
"default": {
defaultRef: {
Type: framework.TypeString,
Description: `Reference (name or identifier) to the default issuer.`,
},
Expand Down Expand Up @@ -79,13 +79,13 @@ func (b *backend) pathCAIssuersRead(ctx context.Context, req *logical.Request, d

return &logical.Response{
Data: map[string]interface{}{
"default": config.DefaultIssuerId,
defaultRef: config.DefaultIssuerId,
},
}, nil
}

func (b *backend) pathCAIssuersWrite(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
newDefault := data.Get("default").(string)
newDefault := data.Get(defaultRef).(string)
if len(newDefault) == 0 || newDefault == defaultRef {
return logical.ErrorResponse("Invalid issuer specification; must be non-empty and can't be 'default'."), nil
}
Expand Down Expand Up @@ -130,6 +130,78 @@ accessible by the existing signing paths (/root/sign-intermediate,
/root/sign-self-issued, /sign-verbatim, /sign/:role, and /issue/:role).
`

func pathConfigKeys(b *backend) *framework.Path {
return &framework.Path{
Pattern: "config/keys",
Fields: map[string]*framework.FieldSchema{
defaultRef: {
Type: framework.TypeString,
Description: `Reference (name or identifier) of the default key.`,
},
},

Operations: map[logical.Operation]framework.OperationHandler{
logical.UpdateOperation: &framework.PathOperation{
Callback: b.pathKeyDefaultWrite,
ForwardPerformanceStandby: true,
ForwardPerformanceSecondary: true,
},
logical.ReadOperation: &framework.PathOperation{
Callback: b.pathKeyDefaultRead,
ForwardPerformanceStandby: false,
ForwardPerformanceSecondary: false,
},
},

HelpSynopsis: pathConfigKeysHelpSyn,
HelpDescription: pathConfigKeysHelpDesc,
}
}

func (b *backend) pathKeyDefaultRead(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
config, err := getKeysConfig(ctx, req.Storage)
if err != nil {
return logical.ErrorResponse("Error loading keys configuration: " + err.Error()), nil
}

return &logical.Response{
Data: map[string]interface{}{
defaultRef: config.DefaultKeyId,
},
}, nil
}

func (b *backend) pathKeyDefaultWrite(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
newDefault := data.Get(defaultRef).(string)
if len(newDefault) == 0 || newDefault == defaultRef {
return logical.ErrorResponse("Invalid key specification; must be non-empty and can't be 'default'."), nil
}

parsedKey, err := resolveKeyReference(ctx, req.Storage, newDefault)
if err != nil {
return logical.ErrorResponse("Error resolving issuer reference: " + err.Error()), nil
}

err = updateDefaultKeyId(ctx, req.Storage, parsedKey)
if err != nil {
return logical.ErrorResponse("Error updating issuer configuration: " + err.Error()), nil
}

return &logical.Response{
Data: map[string]interface{}{
defaultRef: parsedKey,
},
}, nil
}

const pathConfigKeysHelpSyn = `Read and set the default key used for signing`

const pathConfigKeysHelpDesc = `
This path allows configuration of key parameters.
The "default" parameter controls which key is the default used by signing paths.
`

const pathConfigCAGenerateHelpSyn = `
Generate a new CA certificate and private key used for signing.
`
Expand Down
227 changes: 227 additions & 0 deletions builtin/logical/pki/path_fetch_keys.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
package pki

import (
"context"
"fmt"

"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/logical"
)

func pathListKeys(b *backend) *framework.Path {
return &framework.Path{
Pattern: "keys/?$",

Operations: map[logical.Operation]framework.OperationHandler{
logical.ListOperation: &framework.PathOperation{
Callback: b.pathListKeysHandler,
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, data *framework.FieldData) (*logical.Response, error) {
var responseKeys []string
responseInfo := make(map[string]interface{})

entries, err := listKeys(ctx, req.Storage)
if err != nil {
return nil, err
}

for _, identifier := range entries {
key, err := fetchKeyById(ctx, req.Storage, identifier)
if err != nil {
return nil, err
}

responseKeys = append(responseKeys, string(identifier))
responseInfo[string(identifier)] = map[string]interface{}{
keyNameParam: key.Name,
}

}
return logical.ListResponseWithInfo(responseKeys, responseInfo), nil
}

func pathKey(b *backend) *framework.Path {
pattern := "key/" + framework.GenericNameRegex(keyRefParam)
return buildPathKey(b, pattern)
}

func buildPathKey(b *backend, pattern string) *framework.Path {
return &framework.Path{
Pattern: pattern,

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,
ForwardPerformanceStandby: false,
ForwardPerformanceSecondary: false,
},
logical.UpdateOperation: &framework.PathOperation{
Callback: b.pathUpdateKeyHandler,
ForwardPerformanceStandby: true,
ForwardPerformanceSecondary: true,
},
logical.DeleteOperation: &framework.PathOperation{
Callback: b.pathDeleteKeyHandler,
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) {
keyRef := data.Get(keyRefParam).(string)
if len(keyRef) == 0 {
return logical.ErrorResponse("missing key reference"), nil
}

keyId, err := resolveKeyReference(ctx, req.Storage, keyRef)
if err != nil {
return nil, err
}
if keyId == "" {
return logical.ErrorResponse("unable to resolve key id for reference" + keyRef), nil
}

key, err := fetchKeyById(ctx, req.Storage, keyId)
if err != nil {
return nil, err
}

return &logical.Response{
Data: map[string]interface{}{
keyIdParam: key.ID,
keyNameParam: key.Name,
keyTypeParam: key.PrivateKeyType,
},
}, nil
}

func (b *backend) pathUpdateKeyHandler(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
keyRef := data.Get(keyRefParam).(string)
if len(keyRef) == 0 {
return logical.ErrorResponse("missing key reference"), nil
}

keyId, err := resolveKeyReference(ctx, req.Storage, keyRef)
if err != nil {
return nil, err
}
if keyId == "" {
return logical.ErrorResponse("unable to resolve key id for reference" + keyRef), nil
}

key, err := fetchKeyById(ctx, req.Storage, 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 := writeKey(ctx, req.Storage, *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) {
keyRef := data.Get(keyRefParam).(string)
if len(keyRef) == 0 {
return logical.ErrorResponse("missing key reference"), nil
}

keyId, err := resolveKeyReference(ctx, req.Storage, keyRef)
if err != nil {
return nil, err
}
if keyId == "" {
return logical.ErrorResponse("unable to resolve key id for reference" + keyRef), nil
}

keyInUse, issuerId, err := isKeyInUse(keyId.String(), ctx, req.Storage)
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 := deleteKey(ctx, req.Storage, 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
}
Loading

0 comments on commit b0ad729

Please sign in to comment.