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

Kitography/vault 5474 rebase #15150

Merged
merged 16 commits into from
Apr 28, 2022
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
kitography marked this conversation as resolved.
Show resolved Hide resolved
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{}
kitography marked this conversation as resolved.
Show resolved Hide resolved
response.AddWarning(msg)
}

return response, nil
}
Loading