Add unified storage support to OCSP handler (#18788)

This commit is contained in:
Steven Clark 2023-01-23 10:49:07 -05:00 committed by GitHub
parent f3ce351e01
commit d0453ed40b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 130 additions and 55 deletions

View File

@ -91,9 +91,11 @@ func Backend(conf *logical.BackendConfig) *backend {
"issuer/+/pem",
"issuer/+/der",
"issuer/+/json",
"issuers/", // LIST operations append a '/' to the requested path
"ocsp", // OCSP POST
"ocsp/*", // OCSP GET
"issuers/", // LIST operations append a '/' to the requested path
"ocsp", // OCSP POST
"ocsp/*", // OCSP GET
"unified-ocsp", // Unified OCSP POST
"unified-ocsp/*", // Unified OCSP GET
},
LocalStorage: []string{
@ -187,6 +189,8 @@ func Backend(conf *logical.BackendConfig) *backend {
// OCSP APIs
buildPathOcspGet(&b),
buildPathOcspPost(&b),
buildPathUnifiedOcspGet(&b),
buildPathUnifiedOcspPost(&b),
// CRL Signing
pathResignCrls(&b),

View File

@ -13,6 +13,7 @@ import (
"io"
"math/big"
"net/http"
"strings"
"time"
"github.com/hashicorp/vault/sdk/helper/errutil"
@ -67,8 +68,16 @@ var (
)
func buildPathOcspGet(b *backend) *framework.Path {
return buildOcspGetWithPath(b, "ocsp/"+framework.MatchAllRegex(ocspReqParam))
}
func buildPathUnifiedOcspGet(b *backend) *framework.Path {
return buildOcspGetWithPath(b, "unified-ocsp/"+framework.MatchAllRegex(ocspReqParam))
}
func buildOcspGetWithPath(b *backend, pattern string) *framework.Path {
return &framework.Path{
Pattern: "ocsp/" + framework.MatchAllRegex(ocspReqParam),
Pattern: pattern,
Fields: map[string]*framework.FieldSchema{
ocspReqParam: {
Type: framework.TypeString,
@ -87,8 +96,16 @@ func buildPathOcspGet(b *backend) *framework.Path {
}
func buildPathOcspPost(b *backend) *framework.Path {
return buildOcspPostWithPath(b, "ocsp")
}
func buildPathUnifiedOcspPost(b *backend) *framework.Path {
return buildOcspPostWithPath(b, "unified-ocsp")
}
func buildOcspPostWithPath(b *backend, pattern string) *framework.Path {
return &framework.Path{
Pattern: "ocsp",
Pattern: pattern,
Operations: map[logical.Operation]framework.OperationHandler{
logical.UpdateOperation: &framework.PathOperation{
Callback: b.ocspHandler,
@ -103,7 +120,7 @@ func buildPathOcspPost(b *backend) *framework.Path {
func (b *backend) ocspHandler(ctx context.Context, request *logical.Request, data *framework.FieldData) (*logical.Response, error) {
sc := b.makeStorageContext(ctx, request.Storage)
cfg, err := b.crlBuilder.getConfigWithUpdate(sc)
if err != nil || cfg.OcspDisable {
if err != nil || cfg.OcspDisable || (isUnifiedOcspPath(request) && !cfg.UnifiedCRL) {
return OcspUnauthorizedResponse, nil
}
@ -117,7 +134,9 @@ func (b *backend) ocspHandler(ctx context.Context, request *logical.Request, dat
return OcspMalformedResponse, nil
}
ocspStatus, err := getOcspStatus(sc, request, ocspReq)
useUnifiedStorage := canUseUnifiedStorage(request, cfg)
ocspStatus, err := getOcspStatus(sc, ocspReq, useUnifiedStorage)
if err != nil {
return logAndReturnInternalError(b, err), nil
}
@ -154,6 +173,20 @@ func (b *backend) ocspHandler(ctx context.Context, request *logical.Request, dat
}, nil
}
func canUseUnifiedStorage(req *logical.Request, cfg *crlConfig) bool {
if isUnifiedOcspPath(req) {
return true
}
// We are operating on the existing /pki/ocsp path, both of these fields need to be enabled
// for us to use the unified path.
return cfg.UnifiedCRL && cfg.UnifiedCRLOnExistingPaths
}
func isUnifiedOcspPath(req *logical.Request) bool {
return strings.HasPrefix(req.Path, "unified-ocsp")
}
func generateUnknownResponse(cfg *crlConfig, sc *storageContext, ocspReq *ocsp.Request) *logical.Response {
// Generate an Unknown OCSP response, signing with the default issuer from the mount as we did
// not match the request's issuer. If no default issuer can be used, return with Unauthorized as there
@ -252,7 +285,7 @@ func logAndReturnInternalError(b *backend, err error) *logical.Response {
return OcspInternalErrorResponse
}
func getOcspStatus(sc *storageContext, request *logical.Request, ocspReq *ocsp.Request) (*ocspRespInfo, error) {
func getOcspStatus(sc *storageContext, ocspReq *ocsp.Request, useUnifiedStorage bool) (*ocspRespInfo, error) {
revEntryRaw, err := fetchCertBySerialBigInt(sc, revokedPath, ocspReq.SerialNumber)
if err != nil {
return nil, err
@ -272,6 +305,18 @@ func getOcspStatus(sc *storageContext, request *logical.Request, ocspReq *ocsp.R
info.ocspStatus = ocsp.Revoked
info.revocationTimeUTC = &revEntry.RevocationTimeUTC
info.issuerID = revEntry.CertificateIssuer // This might be empty if the CRL hasn't been rebuilt
} else if useUnifiedStorage {
dashSerial := normalizeSerialFromBigInt(ocspReq.SerialNumber)
unifiedEntry, err := getUnifiedRevocationBySerial(sc, dashSerial)
if err != nil {
return nil, err
}
if unifiedEntry != nil {
info.ocspStatus = ocsp.Revoked
info.revocationTimeUTC = &unifiedEntry.RevocationTimeUTC
info.issuerID = unifiedEntry.CertificateIssuer
}
}
return &info, nil

View File

@ -15,32 +15,34 @@ const latestCrlConfigVersion = 1
// CRLConfig holds basic CRL configuration information
type crlConfig struct {
Version int `json:"version"`
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"`
OcspExpiry string `json:"ocsp_expiry"`
EnableDelta bool `json:"enable_delta"`
DeltaRebuildInterval string `json:"delta_rebuild_interval"`
UseGlobalQueue bool `json:"cross_cluster_revocation"`
UnifiedCRL bool `json:"unified_crl"`
Version int `json:"version"`
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"`
OcspExpiry string `json:"ocsp_expiry"`
EnableDelta bool `json:"enable_delta"`
DeltaRebuildInterval string `json:"delta_rebuild_interval"`
UseGlobalQueue bool `json:"cross_cluster_revocation"`
UnifiedCRL bool `json:"unified_crl"`
UnifiedCRLOnExistingPaths bool `json:"unified_crl_on_existing_paths"`
}
// Implicit default values for the config if it does not exist.
var defaultCrlConfig = crlConfig{
Version: latestCrlConfigVersion,
Expiry: "72h",
Disable: false,
OcspDisable: false,
OcspExpiry: "12h",
AutoRebuild: false,
AutoRebuildGracePeriod: "12h",
EnableDelta: false,
DeltaRebuildInterval: "15m",
UseGlobalQueue: false,
UnifiedCRL: false,
Version: latestCrlConfigVersion,
Expiry: "72h",
Disable: false,
OcspDisable: false,
OcspExpiry: "12h",
AutoRebuild: false,
AutoRebuildGracePeriod: "12h",
EnableDelta: false,
DeltaRebuildInterval: "15m",
UseGlobalQueue: false,
UnifiedCRL: false,
UnifiedCRLOnExistingPaths: false,
}
func pathConfigCRL(b *backend) *framework.Path {
@ -97,6 +99,12 @@ also enabling unified versions of OCSP and CRLs if their respective features are
disable for CRLs and ocsp_disable for OCSP.`,
Default: "false",
},
"unified_crl_on_existing_paths": {
Type: framework.TypeBool,
Description: `If set to true,
existing CRL and OCSP paths will return the unified CRL instead of a response based on cluster-local data`,
Default: "false",
},
},
Operations: map[logical.Operation]framework.OperationHandler{
@ -123,20 +131,7 @@ func (b *backend) pathCRLRead(ctx context.Context, req *logical.Request, _ *fram
return nil, err
}
return &logical.Response{
Data: map[string]interface{}{
"expiry": config.Expiry,
"disable": config.Disable,
"ocsp_disable": config.OcspDisable,
"ocsp_expiry": config.OcspExpiry,
"auto_rebuild": config.AutoRebuild,
"auto_rebuild_grace_period": config.AutoRebuildGracePeriod,
"enable_delta": config.EnableDelta,
"delta_rebuild_interval": config.DeltaRebuildInterval,
"cross_cluster_revocation": config.UseGlobalQueue,
"unified_crl": config.UnifiedCRL,
},
}, nil
return genResponseFromCrlConfig(config), nil
}
func (b *backend) pathCRLWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
@ -209,6 +204,14 @@ func (b *backend) pathCRLWrite(ctx context.Context, req *logical.Request, d *fra
config.UnifiedCRL = unifiedCrlRaw.(bool)
}
if unifiedCrlOnExistingPathsRaw, ok := d.GetOk("unified_crl_on_existing_paths"); ok {
config.UnifiedCRLOnExistingPaths = unifiedCrlOnExistingPathsRaw.(bool)
}
if config.UnifiedCRLOnExistingPaths && !config.UnifiedCRL {
return logical.ErrorResponse("unified_crl_on_existing_paths cannot be enabled if unified_crl is disabled"), nil
}
expiry, _ := time.ParseDuration(config.Expiry)
if config.AutoRebuild {
gracePeriod, _ := time.ParseDuration(config.AutoRebuildGracePeriod)
@ -238,6 +241,10 @@ func (b *backend) pathCRLWrite(ctx context.Context, req *logical.Request, d *fra
return logical.ErrorResponse("Global, cross-cluster revocation queue can only be enabled on Vault Enterprise."), nil
}
if !constants.IsEnterprise && config.UnifiedCRL {
return logical.ErrorResponse("unified_crl can only be enabled on Vault Enterprise"), nil
}
entry, err := logical.StorageEntryJSON("config/crl", config)
if err != nil {
return nil, err
@ -264,20 +271,25 @@ func (b *backend) pathCRLWrite(ctx context.Context, req *logical.Request, d *fra
}
}
return genResponseFromCrlConfig(config), nil
}
func genResponseFromCrlConfig(config *crlConfig) *logical.Response {
return &logical.Response{
Data: map[string]interface{}{
"expiry": config.Expiry,
"disable": config.Disable,
"ocsp_disable": config.OcspDisable,
"ocsp_expiry": config.OcspExpiry,
"auto_rebuild": config.AutoRebuild,
"auto_rebuild_grace_period": config.AutoRebuildGracePeriod,
"enable_delta": config.EnableDelta,
"delta_rebuild_interval": config.DeltaRebuildInterval,
"cross_cluster_revocation": config.UseGlobalQueue,
"unified_crl": config.UnifiedCRL,
"expiry": config.Expiry,
"disable": config.Disable,
"ocsp_disable": config.OcspDisable,
"ocsp_expiry": config.OcspExpiry,
"auto_rebuild": config.AutoRebuild,
"auto_rebuild_grace_period": config.AutoRebuildGracePeriod,
"enable_delta": config.EnableDelta,
"delta_rebuild_interval": config.DeltaRebuildInterval,
"cross_cluster_revocation": config.UseGlobalQueue,
"unified_crl": config.UnifiedCRL,
"unified_crl_on_existing_paths": config.UnifiedCRLOnExistingPaths,
},
}, nil
}
}
const pathConfigCRLHelpSyn = `

View File

@ -11,6 +11,7 @@ import (
"time"
"github.com/hashicorp/go-uuid"
"github.com/hashicorp/vault/helper/constants"
"github.com/hashicorp/vault/sdk/helper/certutil"
"github.com/hashicorp/vault/sdk/helper/errutil"
"github.com/hashicorp/vault/sdk/logical"
@ -1279,6 +1280,15 @@ func (sc *storageContext) getRevocationConfig() (*crlConfig, error) {
result.Expiry = defaultCrlConfig.Expiry
}
if !constants.IsEnterprise && (result.UnifiedCRLOnExistingPaths || result.UnifiedCRL || result.UseGlobalQueue) {
// An end user must have had Enterprise, enabled the unified config args and then downgraded to OSS.
sc.Backend.Logger().Warn("Not running Vault Enterprise, " +
"disabling unified_crl, unified_crl_on_existing_paths and cross_cluster_revocation config flags.")
result.UnifiedCRLOnExistingPaths = false
result.UnifiedCRL = false
result.UseGlobalQueue = false
}
return &result, nil
}

View File

@ -43,6 +43,10 @@ func serialFromBigInt(serial *big.Int) string {
return strings.TrimSpace(certutil.GetHexFormatted(serial.Bytes(), ":"))
}
func normalizeSerialFromBigInt(serial *big.Int) string {
return strings.TrimSpace(certutil.GetHexFormatted(serial.Bytes(), "-"))
}
func normalizeSerial(serial string) string {
return strings.ReplaceAll(strings.ToLower(serial), ":", "-")
}