Add unified storage support to OCSP handler (#18788)
This commit is contained in:
parent
f3ce351e01
commit
d0453ed40b
|
@ -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),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 = `
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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), ":", "-")
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue