// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package healthcheck import ( "bytes" "crypto/x509" "fmt" "github.com/hashicorp/go-secure-stdlib/parseutil" ) type HardwareBackedRoot struct { Enabled bool UnsupportedVersion bool FetchIssues map[string]*PathFetch IssuerKeyMap map[string]string KeyIsManaged map[string]string } func NewHardwareBackedRootCheck() Check { return &HardwareBackedRoot{ FetchIssues: make(map[string]*PathFetch), IssuerKeyMap: make(map[string]string), KeyIsManaged: make(map[string]string), } } func (h *HardwareBackedRoot) Name() string { return "hardware_backed_root" } func (h *HardwareBackedRoot) IsEnabled() bool { return h.Enabled } func (h *HardwareBackedRoot) DefaultConfig() map[string]interface{} { return map[string]interface{}{ "enabled": false, } } func (h *HardwareBackedRoot) 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 *HardwareBackedRoot) FetchResources(e *Executor) error { exit, _, issuers, err := pkiFetchIssuersList(e, func() { h.UnsupportedVersion = true }) if exit || err != nil { return err } for _, issuer := range issuers { skip, ret, entry, err := pkiFetchIssuerEntry(e, issuer, func() { h.UnsupportedVersion = true }) if skip || err != nil || entry == nil { if err != nil { return err } h.FetchIssues[issuer] = ret continue } // Ensure we only check Root CAs. cert := ret.ParsedCache["certificate"].(*x509.Certificate) if !bytes.Equal(cert.RawSubject, cert.RawIssuer) { continue } if err := cert.CheckSignatureFrom(cert); err != nil { continue } // Ensure we only check issuers with keys. keyId, present := entry["key_id"].(string) if !present || len(keyId) == 0 { continue } h.IssuerKeyMap[issuer] = keyId skip, ret, keyEntry, err := pkiFetchKeyEntry(e, keyId, func() { h.UnsupportedVersion = true }) if skip || err != nil || keyEntry == nil { if err != nil { return err } h.FetchIssues[issuer] = ret continue } uuid, present := keyEntry["managed_key_id"].(string) if present { h.KeyIsManaged[keyId] = uuid } } return nil } func (h *HardwareBackedRoot) 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 } for issuer, fetchPath := range h.FetchIssues { if fetchPath != nil && fetchPath.IsSecretPermissionsError() { delete(h.IssuerKeyMap, issuer) ret := Result{ Status: ResultInsufficientPermissions, Endpoint: fetchPath.Path, Message: "Without this information, this health check is unable to function.", } if e.Client.Token() == "" { ret.Message = "No token available so unable for the endpoint for this mount. " + ret.Message } else { ret.Message = "This token lacks permission for the endpoint for this mount. " + ret.Message } results = append(results, &ret) } } for name, keyId := range h.IssuerKeyMap { var ret Result ret.Status = ResultInformational ret.Endpoint = "/{{mount}}/issuer/" + name ret.Message = "Root issuer was created using Vault-backed software keys; for added safety of long-lived, important root CAs, you may wish to consider using a HSM or KSM Managed Key to store key material for this issuer." uuid, present := h.KeyIsManaged[keyId] if present { ret.Status = ResultOK ret.Message = fmt.Sprintf("Root issuer was backed by a HSM or KMS Managed Key: %v.", uuid) } results = append(results, &ret) } return }