open-vault/builtin/logical/pki/path_config_ca.go
Alexander Scheel 06f30de35f
Optional automatic default issuer selection (#17824)
* Correctly preserve other issuer config params

When setting a new default issuer, our helper function would overwrite
other parameters in the issuer configuration entry. However, up until
now, there were none.

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

* Add new parameter to allow default to follow new

This parameter will allow operators to have the default issuer
automatically update when a new root is generated or a single issuer
with a key (potentially with others lacking key) is imported.

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

* Storage migration tests fail on new members

These internal members shouldn't be tested by the storage migration
code, and so should be elided from the test results.

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

* Follow new issuer on root generation, import

This updates the two places where issuers can be created (outside of
legacy CA bundle migration which already sets the default) to follow
newly created issuers when the config is set.

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

* Add changelog entry

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

* Add documentation

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

* Add test for new default-following behavior

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

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>
2022-11-08 14:40:29 -05:00

287 lines
9.1 KiB
Go

package pki
import (
"context"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/helper/errutil"
"github.com/hashicorp/vault/sdk/logical"
)
func pathConfigCA(b *backend) *framework.Path {
return &framework.Path{
Pattern: "config/ca",
Fields: map[string]*framework.FieldSchema{
"pem_bundle": {
Type: framework.TypeString,
Description: `PEM-format, concatenated unencrypted
secret key and certificate.`,
},
},
Operations: map[logical.Operation]framework.OperationHandler{
logical.UpdateOperation: &framework.PathOperation{
Callback: b.pathImportIssuers,
// Read more about why these flags are set in backend.go.
ForwardPerformanceStandby: true,
ForwardPerformanceSecondary: true,
},
},
HelpSynopsis: pathConfigCAHelpSyn,
HelpDescription: pathConfigCAHelpDesc,
}
}
const pathConfigCAHelpSyn = `
Set the CA certificate and private key used for generated credentials.
`
const pathConfigCAHelpDesc = `
This sets the CA information used for credentials generated by this
by this mount. This must be a PEM-format, concatenated unencrypted
secret key and certificate.
For security reasons, the secret key cannot be retrieved later.
`
func pathConfigIssuers(b *backend) *framework.Path {
return &framework.Path{
Pattern: "config/issuers",
Fields: map[string]*framework.FieldSchema{
defaultRef: {
Type: framework.TypeString,
Description: `Reference (name or identifier) to the default issuer.`,
},
"default_follows_latest_issuer": {
Type: framework.TypeBool,
Description: `Whether the default issuer should automatically follow the latest generated or imported issuer. Defaults to false.`,
Default: false,
},
},
Operations: map[logical.Operation]framework.OperationHandler{
logical.ReadOperation: &framework.PathOperation{
Callback: b.pathCAIssuersRead,
},
logical.UpdateOperation: &framework.PathOperation{
Callback: b.pathCAIssuersWrite,
// Read more about why these flags are set in backend.go.
ForwardPerformanceStandby: true,
ForwardPerformanceSecondary: true,
},
},
HelpSynopsis: pathConfigIssuersHelpSyn,
HelpDescription: pathConfigIssuersHelpDesc,
}
}
func pathReplaceRoot(b *backend) *framework.Path {
return &framework.Path{
Pattern: "root/replace",
Fields: map[string]*framework.FieldSchema{
"default": {
Type: framework.TypeString,
Description: `Reference (name or identifier) to the default issuer.`,
Default: "next",
},
},
Operations: map[logical.Operation]framework.OperationHandler{
logical.UpdateOperation: &framework.PathOperation{
Callback: b.pathCAIssuersWrite,
// Read more about why these flags are set in backend.go.
ForwardPerformanceStandby: true,
ForwardPerformanceSecondary: true,
},
},
HelpSynopsis: pathConfigIssuersHelpSyn,
HelpDescription: pathConfigIssuersHelpDesc,
}
}
func (b *backend) pathCAIssuersRead(ctx context.Context, req *logical.Request, _ *framework.FieldData) (*logical.Response, error) {
if b.useLegacyBundleCaStorage() {
return logical.ErrorResponse("Cannot read defaults until migration has completed"), nil
}
sc := b.makeStorageContext(ctx, req.Storage)
config, err := sc.getIssuersConfig()
if err != nil {
return logical.ErrorResponse("Error loading issuers configuration: " + err.Error()), nil
}
return b.formatCAIssuerConfigRead(config), nil
}
func (b *backend) formatCAIssuerConfigRead(config *issuerConfigEntry) *logical.Response {
return &logical.Response{
Data: map[string]interface{}{
defaultRef: config.DefaultIssuerId,
"default_follows_latest_issuer": config.DefaultFollowsLatestIssuer,
},
}
}
func (b *backend) pathCAIssuersWrite(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("Cannot update defaults until migration has completed"), nil
}
sc := b.makeStorageContext(ctx, req.Storage)
// Validate the new default reference.
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
}
parsedIssuer, err := sc.resolveIssuerReference(newDefault)
if err != nil {
return logical.ErrorResponse("Error resolving issuer reference: " + err.Error()), nil
return nil, errutil.UserError{Err: "Error resolving issuer reference: " + err.Error()}
}
entry, err := sc.fetchIssuerById(parsedIssuer)
if err != nil {
return logical.ErrorResponse("Unable to fetch issuer: " + err.Error()), nil
}
// Get the other new parameters. This doesn't exist on the /root/replace
// variant of this call.
var followIssuer bool
followIssuersRaw, followOk := data.GetOk("default_follows_latest_issuer")
if followOk {
followIssuer = followIssuersRaw.(bool)
}
// Update the config
config, err := sc.getIssuersConfig()
if err != nil {
return logical.ErrorResponse("Unable to fetch existing issuers configuration: " + err.Error()), nil
}
config.DefaultIssuerId = parsedIssuer
if followOk {
config.DefaultFollowsLatestIssuer = followIssuer
}
// Add our warning if necessary.
response := b.formatCAIssuerConfigRead(config)
if len(entry.KeyID) == 0 {
msg := "This selected default issuer has no key associated with it. Some operations like issuing certificates and signing CRLs will be unavailable with the requested default issuer until a key is imported or the default issuer is changed."
response.AddWarning(msg)
b.Logger().Error(msg)
}
if err := sc.setIssuersConfig(config); err != nil {
return logical.ErrorResponse("Error updating issuer configuration: " + err.Error()), nil
}
return response, nil
}
const pathConfigIssuersHelpSyn = `Read and set the default issuer certificate for signing.`
const pathConfigIssuersHelpDesc = `
This path allows configuration of issuer parameters.
Presently, the "default" parameter controls which issuer is the default,
accessible by the existing signing paths (/root/sign-intermediate,
/root/sign-self-issued, /sign-verbatim, /sign/:role, and /issue/:role).
The /root/replace path is aliased to this path, with default taking the
value of the issuer with the name "next", if it exists.
`
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, _ *framework.FieldData) (*logical.Response, error) {
if b.useLegacyBundleCaStorage() {
return logical.ErrorResponse("Cannot read key defaults until migration has completed"), nil
}
sc := b.makeStorageContext(ctx, req.Storage)
config, err := sc.getKeysConfig()
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) {
// 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("Cannot update key defaults until migration has completed"), nil
}
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
}
sc := b.makeStorageContext(ctx, req.Storage)
parsedKey, err := sc.resolveKeyReference(newDefault)
if err != nil {
return logical.ErrorResponse("Error resolving issuer reference: " + err.Error()), nil
}
err = sc.updateDefaultKeyId(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.
`