Skip to content

Commit

Permalink
Add crl_validity_period health check
Browse files Browse the repository at this point in the history
Signed-off-by: Alexander Scheel <[email protected]>
  • Loading branch information
cipherboy committed Nov 1, 2022
1 parent 50ae871 commit 8c36298
Show file tree
Hide file tree
Showing 2 changed files with 192 additions and 0 deletions.
191 changes: 191 additions & 0 deletions command/healthcheck/pki_crl_validity_period.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
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 := pkiFetchIssuers(e, func() {
h.UnsupportedVersion = true
})
if exit {
return err
}

for _, issuer := range issuers {
exit, _, crl, err := pkiFetchIssuerCRL(e, issuer, false, func() {
h.UnsupportedVersion = true
})
if exit {
if err != nil {
return err
}
continue
}

h.CRLs[issuer] = crl

exit, _, delta, err := pkiFetchIssuerCRL(e, issuer, true, func() {
h.NoDeltas = true
})
if exit {
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()

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 so permission to read the CRL configuration for this mount. " + ret.Message
}

results = append(results, &ret)
}

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"))
}

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"))
}

results = append(results, &ret)
}

return
}
1 change: 1 addition & 0 deletions command/pki_health_check.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ func (c *PKIHealthCheckCommand) Run(args []string) int {
mount := sanitizePath(args[0])
executor := healthcheck.NewExecutor(client, mount)
executor.AddCheck(healthcheck.NewCAValidityPeriodCheck())
executor.AddCheck(healthcheck.NewCRLValidityPeriodCheck())
if c.flagDefaultDisabled {
executor.DefaultEnabled = false
}
Expand Down

0 comments on commit 8c36298

Please sign in to comment.