| // Copyright (c) HashiCorp, Inc. |
| // SPDX-License-Identifier: MPL-2.0 |
| |
| package healthcheck |
| |
| import ( |
| "fmt" |
| "strings" |
| |
| "github.com/hashicorp/go-secure-stdlib/parseutil" |
| "github.com/hashicorp/go-secure-stdlib/strutil" |
| "github.com/hashicorp/vault/sdk/logical" |
| ) |
| |
| type AllowAcmeHeaders struct { |
| Enabled bool |
| UnsupportedVersion bool |
| |
| TuneFetcher *PathFetch |
| TuneData map[string]interface{} |
| |
| AcmeConfigFetcher *PathFetch |
| } |
| |
| func NewAllowAcmeHeaders() Check { |
| return &AllowAcmeHeaders{} |
| } |
| |
| func (h *AllowAcmeHeaders) Name() string { |
| return "allow_acme_headers" |
| } |
| |
| func (h *AllowAcmeHeaders) IsEnabled() bool { |
| return h.Enabled |
| } |
| |
| func (h *AllowAcmeHeaders) DefaultConfig() map[string]interface{} { |
| return map[string]interface{}{} |
| } |
| |
| func (h *AllowAcmeHeaders) LoadConfig(config map[string]interface{}) error { |
| 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 *AllowAcmeHeaders) FetchResources(e *Executor) error { |
| var err error |
| h.AcmeConfigFetcher, err = e.FetchIfNotFetched(logical.ReadOperation, "/{{mount}}/config/acme") |
| if err != nil { |
| return err |
| } |
| |
| if h.AcmeConfigFetcher.IsUnsupportedPathError() { |
| h.UnsupportedVersion = true |
| } |
| |
| _, h.TuneFetcher, h.TuneData, err = fetchMountTune(e, func() { |
| h.UnsupportedVersion = true |
| }) |
| if err != nil { |
| return err |
| } |
| |
| return nil |
| } |
| |
| func (h *AllowAcmeHeaders) Evaluate(e *Executor) ([]*Result, error) { |
| if h.UnsupportedVersion { |
| ret := Result{ |
| Status: ResultInvalidVersion, |
| Endpoint: h.AcmeConfigFetcher.Path, |
| Message: "This health check requires Vault 1.14+ but an earlier version of Vault Server was contacted, preventing this health check from running.", |
| } |
| return []*Result{&ret}, nil |
| } |
| |
| if h.AcmeConfigFetcher.IsSecretPermissionsError() { |
| msg := "Without read access to ACME configuration, this health check is unable to function." |
| return craftInsufficientPermissionResult(e, h.AcmeConfigFetcher.Path, msg), nil |
| } |
| |
| acmeEnabled, err := isAcmeEnabled(h.AcmeConfigFetcher) |
| if err != nil { |
| return nil, err |
| } |
| |
| if !acmeEnabled { |
| ret := Result{ |
| Status: ResultNotApplicable, |
| Endpoint: h.AcmeConfigFetcher.Path, |
| Message: "ACME is not enabled, no additional response headers required.", |
| } |
| return []*Result{&ret}, nil |
| } |
| |
| if h.TuneFetcher.IsSecretPermissionsError() { |
| msg := "Without access to mount tune information, this health check is unable to function." |
| return craftInsufficientPermissionResult(e, h.TuneFetcher.Path, msg), nil |
| } |
| |
| resp, err := StringList(h.TuneData["allowed_response_headers"]) |
| if err != nil { |
| return nil, fmt.Errorf("unable to parse value from server for allowed_response_headers: %w", err) |
| } |
| |
| requiredResponseHeaders := []string{"Replay-Nonce", "Link", "Location"} |
| foundResponseHeaders := []string{} |
| for _, param := range resp { |
| for _, reqHeader := range requiredResponseHeaders { |
| if strings.EqualFold(param, reqHeader) { |
| foundResponseHeaders = append(foundResponseHeaders, reqHeader) |
| break |
| } |
| } |
| } |
| |
| foundAllHeaders := strutil.EquivalentSlices(requiredResponseHeaders, foundResponseHeaders) |
| |
| if !foundAllHeaders { |
| ret := Result{ |
| Status: ResultWarning, |
| Endpoint: "/sys/mounts/{{mount}}/tune", |
| Message: "Mount hasn't enabled 'Replay-Nonce', 'Link', 'Location' response headers, these are required for ACME to function.", |
| } |
| return []*Result{&ret}, nil |
| } |
| |
| ret := Result{ |
| Status: ResultOK, |
| Endpoint: "/sys/mounts/{{mount}}/tune", |
| Message: "Mount has enabled 'Replay-Nonce', 'Link', 'Location' response headers.", |
| } |
| return []*Result{&ret}, nil |
| } |
| |
| func craftInsufficientPermissionResult(e *Executor, path, errorMsg string) []*Result { |
| ret := Result{ |
| Status: ResultInsufficientPermissions, |
| Endpoint: path, |
| Message: errorMsg, |
| } |
| |
| if e.Client.Token() == "" { |
| ret.Message = "No token available so unable read the tune endpoint for this mount. " + ret.Message |
| } else { |
| ret.Message = "This token lacks permission to read the tune endpoint for this mount. " + ret.Message |
| } |
| |
| return []*Result{&ret} |
| } |