| // 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 |
| } |