06f30de35f
* 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>
287 lines
9.1 KiB
Go
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.
|
|
`
|