209 lines
6.4 KiB
Go
209 lines
6.4 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package healthcheck
|
|
|
|
import (
|
|
"crypto/x509"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/hashicorp/vault/sdk/logical"
|
|
|
|
"github.com/hashicorp/go-secure-stdlib/parseutil"
|
|
)
|
|
|
|
type CRLValidityPeriod struct {
|
|
Enabled bool
|
|
|
|
CRLExpiryPercentage int
|
|
DeltaCRLExpiryPercentage int
|
|
|
|
UnsupportedVersion bool
|
|
NoDeltas bool
|
|
|
|
CRLs map[string]*x509.RevocationList
|
|
DeltaCRLs map[string]*x509.RevocationList
|
|
|
|
CRLConfig *PathFetch
|
|
}
|
|
|
|
func NewCRLValidityPeriodCheck() Check {
|
|
return &CRLValidityPeriod{
|
|
CRLs: make(map[string]*x509.RevocationList),
|
|
DeltaCRLs: make(map[string]*x509.RevocationList),
|
|
}
|
|
}
|
|
|
|
func (h *CRLValidityPeriod) Name() string {
|
|
return "crl_validity_period"
|
|
}
|
|
|
|
func (h *CRLValidityPeriod) IsEnabled() bool {
|
|
return h.Enabled
|
|
}
|
|
|
|
func (h *CRLValidityPeriod) DefaultConfig() map[string]interface{} {
|
|
return map[string]interface{}{
|
|
"crl_expiry_pct_critical": "95",
|
|
"delta_crl_expiry_pct_critical": "95",
|
|
}
|
|
}
|
|
|
|
func (h *CRLValidityPeriod) LoadConfig(config map[string]interface{}) error {
|
|
value, err := parseutil.SafeParseIntRange(config["crl_expiry_pct_critical"], 1, 99)
|
|
if err != nil {
|
|
return fmt.Errorf("error parsing %v.crl_expiry_pct_critical=%v: %w", h.Name(), config["crl_expiry_pct_critical"], err)
|
|
}
|
|
h.CRLExpiryPercentage = int(value)
|
|
|
|
value, err = parseutil.SafeParseIntRange(config["delta_crl_expiry_pct_critical"], 1, 99)
|
|
if err != nil {
|
|
return fmt.Errorf("error parsing %v.delta_crl_expiry_pct_critical=%v: %w", h.Name(), config["delta_crl_expiry_pct_critical"], err)
|
|
}
|
|
h.DeltaCRLExpiryPercentage = int(value)
|
|
|
|
enabled, err := parseutil.ParseBool(config["enabled"])
|
|
if err != nil {
|
|
return fmt.Errorf("error parsing %v.enabled: %w", h.Name(), err)
|
|
}
|
|
h.Enabled = enabled
|
|
|
|
return nil
|
|
}
|
|
|
|
func (h *CRLValidityPeriod) FetchResources(e *Executor) error {
|
|
exit, _, issuers, err := pkiFetchIssuersList(e, func() {
|
|
h.UnsupportedVersion = true
|
|
})
|
|
if exit || err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, issuer := range issuers {
|
|
exit, _, crl, err := pkiFetchIssuerCRL(e, issuer, false, func() {
|
|
h.UnsupportedVersion = true
|
|
})
|
|
if exit || err != nil {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
continue
|
|
}
|
|
|
|
h.CRLs[issuer] = crl
|
|
|
|
exit, _, delta, err := pkiFetchIssuerCRL(e, issuer, true, func() {
|
|
h.NoDeltas = true
|
|
})
|
|
if exit || err != nil {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
continue
|
|
}
|
|
|
|
h.DeltaCRLs[issuer] = delta
|
|
}
|
|
|
|
// Check if the issuer is fetched yet.
|
|
configRet, err := e.FetchIfNotFetched(logical.ReadOperation, "/{{mount}}/config/crl")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
h.CRLConfig = configRet
|
|
|
|
return nil
|
|
}
|
|
|
|
func (h *CRLValidityPeriod) Evaluate(e *Executor) (results []*Result, err error) {
|
|
if h.UnsupportedVersion {
|
|
ret := Result{
|
|
Status: ResultInvalidVersion,
|
|
Endpoint: "/{{mount}}/issuers",
|
|
Message: "This health check requires Vault 1.11+ but an earlier version of Vault Server was contacted, preventing this health check from running.",
|
|
}
|
|
return []*Result{&ret}, nil
|
|
}
|
|
|
|
now := time.Now()
|
|
crlDisabled := false
|
|
if h.CRLConfig != nil {
|
|
if h.CRLConfig.IsSecretPermissionsError() {
|
|
ret := Result{
|
|
Status: ResultInsufficientPermissions,
|
|
Endpoint: "/{{mount}}/config/crl",
|
|
Message: "This prevents the health check from seeing if the CRL is disabled and dropping the severity of this check accordingly.",
|
|
}
|
|
|
|
if e.Client.Token() == "" {
|
|
ret.Message = "No token available so unable read authenticated CRL configuration for this mount. " + ret.Message
|
|
} else {
|
|
ret.Message = "This token lacks permission to read the CRL configuration for this mount. " + ret.Message
|
|
}
|
|
|
|
results = append(results, &ret)
|
|
} else if h.CRLConfig.Secret != nil && h.CRLConfig.Secret.Data["disabled"] != nil {
|
|
crlDisabled = h.CRLConfig.Secret.Data["disabled"].(bool)
|
|
}
|
|
}
|
|
|
|
if h.NoDeltas && len(h.DeltaCRLs) == 0 {
|
|
ret := Result{
|
|
Status: ResultInvalidVersion,
|
|
Endpoint: "/{{mount}}/issuer/*/crl/delta",
|
|
Message: "This health check validates Delta CRLs on Vault 1.12+, but an earlier version of Vault was used. No results about delta CRL validity will be returned.",
|
|
}
|
|
results = append(results, &ret)
|
|
}
|
|
|
|
for name, crl := range h.CRLs {
|
|
var ret Result
|
|
ret.Status = ResultOK
|
|
ret.Endpoint = "/{{mount}}/issuer/" + name + "/crl"
|
|
ret.Message = fmt.Sprintf("CRL's validity (%v to %v) is OK.", crl.ThisUpdate.Format("2006-01-02"), crl.NextUpdate.Format("2006-01-02"))
|
|
|
|
used := now.Sub(crl.ThisUpdate)
|
|
total := crl.NextUpdate.Sub(crl.ThisUpdate)
|
|
ratio := time.Duration((int64(total) * int64(h.CRLExpiryPercentage)) / int64(100))
|
|
if used >= ratio {
|
|
expWhen := crl.ThisUpdate.Add(ratio)
|
|
ret.Status = ResultCritical
|
|
ret.Message = fmt.Sprintf("CRL's validity is outside of suggested rotation window: CRL's next update is expected at %v, but expires within %v%% of validity window (starting on %v and ending on %v). It is suggested to rotate this CRL and start propagating it to hosts to avoid any issues caused by stale CRLs.", crl.NextUpdate.Format("2006-01-02"), h.CRLExpiryPercentage, crl.ThisUpdate.Format("2006-01-02"), expWhen.Format("2006-01-02"))
|
|
|
|
if crlDisabled {
|
|
ret.Status = ResultInformational
|
|
ret.Message += " Because the CRL is disabled, this is less of a concern."
|
|
}
|
|
}
|
|
|
|
results = append(results, &ret)
|
|
}
|
|
|
|
for name, crl := range h.DeltaCRLs {
|
|
var ret Result
|
|
ret.Status = ResultOK
|
|
ret.Endpoint = "/{{mount}}/issuer/" + name + "/crl/delta"
|
|
ret.Message = fmt.Sprintf("Delta CRL's validity (%v to %v) is OK.", crl.ThisUpdate.Format("2006-01-02"), crl.NextUpdate.Format("2006-01-02"))
|
|
|
|
used := now.Sub(crl.ThisUpdate)
|
|
total := crl.NextUpdate.Sub(crl.ThisUpdate)
|
|
ratio := time.Duration((int64(total) * int64(h.DeltaCRLExpiryPercentage)) / int64(100))
|
|
if used >= ratio {
|
|
expWhen := crl.ThisUpdate.Add(ratio)
|
|
ret.Status = ResultCritical
|
|
ret.Message = fmt.Sprintf("Delta CRL's validity is outside of suggested rotation window: Delta CRL's next update is expected at %v, but expires within %v%% of validity window (starting on %v and ending on %v). It is suggested to rotate this Delta CRL and start propagating it to hosts to avoid any issues caused by stale CRLs.", crl.NextUpdate.Format("2006-01-02"), h.CRLExpiryPercentage, crl.ThisUpdate.Format("2006-01-02"), expWhen.Format("2006-01-02"))
|
|
|
|
if crlDisabled {
|
|
ret.Status = ResultInformational
|
|
ret.Message += " Because the CRL is disabled, this is less of a concern."
|
|
}
|
|
}
|
|
|
|
results = append(results, &ret)
|
|
}
|
|
|
|
return
|
|
}
|