deb.open-vault/command/healthcheck/pki_allow_acme_headers.go

156 lines
4.1 KiB
Go

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