// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package healthcheck import ( "fmt" "time" "github.com/hashicorp/vault/sdk/logical" "github.com/hashicorp/go-secure-stdlib/parseutil" ) type TidyLastRun struct { Enabled bool UnsupportedVersion bool LastRunCritical time.Duration LastRunWarning time.Duration TidyStatus *PathFetch } func NewTidyLastRunCheck() Check { return &TidyLastRun{} } func (h *TidyLastRun) Name() string { return "tidy_last_run" } func (h *TidyLastRun) IsEnabled() bool { return h.Enabled } func (h *TidyLastRun) DefaultConfig() map[string]interface{} { return map[string]interface{}{ "last_run_critical": "7d", "last_run_warning": "2d", } } func (h *TidyLastRun) LoadConfig(config map[string]interface{}) error { var err error h.LastRunCritical, err = parseutil.ParseDurationSecond(config["last_run_critical"]) if err != nil { return fmt.Errorf("failed to parse parameter %v.%v=%v: %w", h.Name(), "last_run_critical", config["last_run_critical"], err) } h.LastRunWarning, err = parseutil.ParseDurationSecond(config["last_run_warning"]) if err != nil { return fmt.Errorf("failed to parse parameter %v.%v=%v: %w", h.Name(), "last_run_warning", config["last_run_warning"], err) } 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 *TidyLastRun) FetchResources(e *Executor) error { var err error h.TidyStatus, err = e.FetchIfNotFetched(logical.ReadOperation, "/{{mount}}/tidy-status") if err != nil { return fmt.Errorf("failed to fetch mount's tidy-status value: %v", err) } if h.TidyStatus.IsUnsupportedPathError() { h.UnsupportedVersion = true } return nil } func (h *TidyLastRun) Evaluate(e *Executor) (results []*Result, err error) { if h.UnsupportedVersion { // Shouldn't happen; roles have been around forever. ret := Result{ Status: ResultInvalidVersion, Endpoint: "/{{mount}}/tidy-status", Message: "This health check requires Vault 1.10+ but an earlier version of Vault Server was contacted, preventing this health check from running.", } return []*Result{&ret}, nil } if h.TidyStatus == nil { return nil, nil } if h.TidyStatus.IsSecretPermissionsError() { ret := Result{ Status: ResultInsufficientPermissions, Endpoint: "/{{mount}}/tidy-status", Message: "Without this information, this health check is unable to function.", } if e.Client.Token() == "" { ret.Message = "No token available so unable read tidy status endpoint for this mount. " + ret.Message } else { ret.Message = "This token lacks permission to read the tidy status endpoint for this mount. " + ret.Message } results = append(results, &ret) } baseMsg := "Tidy hasn't run in the last %v; this can point to problems with the mount's auto-tidy configuration or an external tidy executor; this can impact PKI's and Vault's performance if not run regularly." if h.TidyStatus.Secret != nil && h.TidyStatus.Secret.Data != nil { ret := Result{ Status: ResultOK, Endpoint: "/{{mount}}/tidy-status", Message: "Tidy has run recently on this mount.", } when := h.TidyStatus.Secret.Data["time_finished"] if when == nil { ret.Status = ResultCritical ret.Message = "Tidy hasn't run since this mount was created; this can point to problems with the mount's auto-tidy configuration or an external tidy executor; this can impact PKI's and Vault's performance if not run regularly. It is suggested to enable auto-tidy on this mount." } else { now := time.Now() lastRunCritical := now.Add(-1 * h.LastRunCritical) lastRunWarning := now.Add(-1 * h.LastRunWarning) whenT, err := parseutil.ParseAbsoluteTime(when) if err != nil { return nil, fmt.Errorf("error parsing time value (%v): %w", when, err) } if whenT.Before(lastRunCritical) { ret.Status = ResultCritical ret.Message = fmt.Sprintf(baseMsg, h.LastRunCritical) } else if whenT.Before(lastRunWarning) { ret.Status = ResultWarning ret.Message = fmt.Sprintf(baseMsg, h.LastRunWarning) } } results = append(results, &ret) } return }