open-vault/builtin/logical/pki/path_config_crl.go
Alexander Scheel cacb23bda6
Enable periodic, automatic rebuilding of CRLs (#16762)
* Allow automatic rebuilding of CRLs

When enabled, periodic rebuilding of CRLs will improve PKI mounts in two
way:

 1. Reduced load during periods of high (new) revocations, as the CRL
    isn't rebuilt after each revocation but instead on a fixed schedule.
 2. Ensuring the CRL is never stale as long as the cluster remains up,
    by checking for next CRL expiry and regenerating CRLs before that
    happens. This may increase cluster load when operators have large
    CRLs that they'd prefer to let go stale, rather than regenerating
    fresh copies.

In particular, we set a grace period before expiration of CRLs where,
when the periodic function triggers (about once a minute), we check
upcoming CRL expirations and check if we need to rebuild the CRLs.

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

* Add changelog entry

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

* Add documentation on periodic rebuilding

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

* Allow modification of rollback period for testing

When testing backends that use the periodic func, and specifically,
testing the behavior of that periodic func, waiting for the usual 1m
interval can lead to excessively long test execution. By switching to a
shorter period--strictly for testing--we can make these tests execute
faster.

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

* Add tests for auto-rebuilding of CRLs

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

* Remove non-updating getConfig variant

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

* Avoid double reload of config

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

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>
2022-08-23 13:27:15 -04:00

178 lines
5.4 KiB
Go

package pki
import (
"context"
"fmt"
"time"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/helper/errutil"
"github.com/hashicorp/vault/sdk/logical"
)
// CRLConfig holds basic CRL configuration information
type crlConfig struct {
Expiry string `json:"expiry"`
Disable bool `json:"disable"`
OcspDisable bool `json:"ocsp_disable"`
AutoRebuild bool `json:"auto_rebuild"`
AutoRebuildGracePeriod string `json:"auto_rebuild_grace_period"`
}
// Implicit default values for the config if it does not exist.
var defaultCrlConfig = crlConfig{
Expiry: "72h",
Disable: false,
OcspDisable: false,
AutoRebuild: false,
AutoRebuildGracePeriod: "12h",
}
func pathConfigCRL(b *backend) *framework.Path {
return &framework.Path{
Pattern: "config/crl",
Fields: map[string]*framework.FieldSchema{
"expiry": {
Type: framework.TypeString,
Description: `The amount of time the generated CRL should be
valid; defaults to 72 hours`,
Default: "72h",
},
"disable": {
Type: framework.TypeBool,
Description: `If set to true, disables generating the CRL entirely.`,
},
"ocsp_disable": {
Type: framework.TypeBool,
Description: `If set to true, ocsp unauthorized responses will be returned.`,
},
"auto_rebuild": {
Type: framework.TypeBool,
Description: `If set to true, enables automatic rebuilding of the CRL`,
},
"auto_rebuild_grace_period": {
Type: framework.TypeString,
Description: `The time before the CRL expires to automatically rebuild it, when enabled. Must be shorter than the CRL expiry. Defaults to 12h.`,
Default: "12h",
},
},
Operations: map[logical.Operation]framework.OperationHandler{
logical.ReadOperation: &framework.PathOperation{
Callback: b.pathCRLRead,
},
logical.UpdateOperation: &framework.PathOperation{
Callback: b.pathCRLWrite,
// Read more about why these flags are set in backend.go.
ForwardPerformanceStandby: true,
ForwardPerformanceSecondary: true,
},
},
HelpSynopsis: pathConfigCRLHelpSyn,
HelpDescription: pathConfigCRLHelpDesc,
}
}
func (b *backend) pathCRLRead(ctx context.Context, req *logical.Request, _ *framework.FieldData) (*logical.Response, error) {
sc := b.makeStorageContext(ctx, req.Storage)
config, err := sc.getRevocationConfig()
if err != nil {
return nil, err
}
return &logical.Response{
Data: map[string]interface{}{
"expiry": config.Expiry,
"disable": config.Disable,
"ocsp_disable": config.OcspDisable,
"auto_rebuild": config.AutoRebuild,
"auto_rebuild_grace_period": config.AutoRebuildGracePeriod,
},
}, nil
}
func (b *backend) pathCRLWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
sc := b.makeStorageContext(ctx, req.Storage)
config, err := sc.getRevocationConfig()
if err != nil {
return nil, err
}
if expiryRaw, ok := d.GetOk("expiry"); ok {
expiry := expiryRaw.(string)
_, err := time.ParseDuration(expiry)
if err != nil {
return logical.ErrorResponse(fmt.Sprintf("given expiry could not be decoded: %s", err)), nil
}
config.Expiry = expiry
}
oldDisable := config.Disable
if disableRaw, ok := d.GetOk("disable"); ok {
config.Disable = disableRaw.(bool)
}
if ocspDisableRaw, ok := d.GetOk("ocsp_disable"); ok {
config.OcspDisable = ocspDisableRaw.(bool)
}
oldAutoRebuild := config.AutoRebuild
if autoRebuildRaw, ok := d.GetOk("auto_rebuild"); ok {
config.AutoRebuild = autoRebuildRaw.(bool)
}
if autoRebuildGracePeriodRaw, ok := d.GetOk("auto_rebuild_grace_period"); ok {
autoRebuildGracePeriod := autoRebuildGracePeriodRaw.(string)
if _, err := time.ParseDuration(autoRebuildGracePeriod); err != nil {
return logical.ErrorResponse(fmt.Sprintf("given auto_rebuild_grace_period could not be decoded: %s", err)), nil
}
config.AutoRebuildGracePeriod = autoRebuildGracePeriod
}
if config.AutoRebuild {
expiry, _ := time.ParseDuration(config.Expiry)
gracePeriod, _ := time.ParseDuration(config.AutoRebuildGracePeriod)
if gracePeriod >= expiry {
return logical.ErrorResponse(fmt.Sprintf("CRL auto-rebuilding grace period (%v) must be strictly shorter than CRL expiry (%v) value when auto-rebuilding of CRLs is enabled", config.AutoRebuildGracePeriod, config.Expiry)), nil
}
}
entry, err := logical.StorageEntryJSON("config/crl", config)
if err != nil {
return nil, err
}
err = req.Storage.Put(ctx, entry)
if err != nil {
return nil, err
}
b.crlBuilder.markConfigDirty()
b.crlBuilder.reloadConfigIfRequired(sc)
if oldDisable != config.Disable || (oldAutoRebuild && !config.AutoRebuild) {
// It wasn't disabled but now it is (or equivalently, we were set to
// auto-rebuild and we aren't now), so rotate the CRL.
crlErr := b.crlBuilder.rebuild(ctx, b, req, true)
if crlErr != nil {
switch crlErr.(type) {
case errutil.UserError:
return logical.ErrorResponse(fmt.Sprintf("Error during CRL building: %s", crlErr)), nil
default:
return nil, fmt.Errorf("error encountered during CRL building: %w", crlErr)
}
}
}
return nil, nil
}
const pathConfigCRLHelpSyn = `
Configure the CRL expiration.
`
const pathConfigCRLHelpDesc = `
This endpoint allows configuration of the CRL lifetime.
`