backport of commit d08bf5616d (#19347)

Co-authored-by: Steven Clark <steven.clark@hashicorp.com>
This commit is contained in:
hc-github-team-secure-vault-core
2023-02-24 14:11:43 -05:00
committed by GitHub
parent 8545876076
commit c496011eed
9 changed files with 187 additions and 45 deletions

View File

@@ -119,6 +119,10 @@ func (e *Executor) Execute() (map[string][]*Result, error) {
return nil, fmt.Errorf("failed to evaluate %v: %w", checker.Name(), err) return nil, fmt.Errorf("failed to evaluate %v: %w", checker.Name(), err)
} }
if results == nil {
results = []*Result{}
}
for _, result := range results { for _, result := range results {
result.Endpoint = e.templatePath(result.Endpoint) result.Endpoint = e.templatePath(result.Endpoint)
result.StatusDisplay = ResultStatusNameMap[result.Status] result.StatusDisplay = ResultStatusNameMap[result.Status]

View File

@@ -13,12 +13,14 @@ type HardwareBackedRoot struct {
UnsupportedVersion bool UnsupportedVersion bool
FetchIssues map[string]*PathFetch
IssuerKeyMap map[string]string IssuerKeyMap map[string]string
KeyIsManaged map[string]string KeyIsManaged map[string]string
} }
func NewHardwareBackedRootCheck() Check { func NewHardwareBackedRootCheck() Check {
return &HardwareBackedRoot{ return &HardwareBackedRoot{
FetchIssues: make(map[string]*PathFetch),
IssuerKeyMap: make(map[string]string), IssuerKeyMap: make(map[string]string),
KeyIsManaged: make(map[string]string), KeyIsManaged: make(map[string]string),
} }
@@ -64,6 +66,7 @@ func (h *HardwareBackedRoot) FetchResources(e *Executor) error {
if err != nil { if err != nil {
return err return err
} }
h.FetchIssues[issuer] = ret
continue continue
} }
@@ -83,13 +86,15 @@ func (h *HardwareBackedRoot) FetchResources(e *Executor) error {
} }
h.IssuerKeyMap[issuer] = keyId h.IssuerKeyMap[issuer] = keyId
skip, _, keyEntry, err := pkiFetchKeyEntry(e, keyId, func() { skip, ret, keyEntry, err := pkiFetchKeyEntry(e, keyId, func() {
h.UnsupportedVersion = true h.UnsupportedVersion = true
}) })
if skip || err != nil || keyEntry == nil { if skip || err != nil || keyEntry == nil {
if err != nil { if err != nil {
return err return err
} }
h.FetchIssues[issuer] = ret
continue continue
} }
@@ -112,6 +117,25 @@ func (h *HardwareBackedRoot) Evaluate(e *Executor) (results []*Result, err error
return []*Result{&ret}, nil 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 { for name, keyId := range h.IssuerKeyMap {
var ret Result var ret Result
ret.Status = ResultInformational ret.Status = ResultInformational

View File

@@ -10,13 +10,15 @@ import (
type RoleAllowsGlobWildcards struct { type RoleAllowsGlobWildcards struct {
Enabled bool Enabled bool
UnsupportedVersion bool UnsupportedVersion bool
NoPerms bool
RoleListFetchIssue *PathFetch
RoleFetchIssues map[string]*PathFetch
RoleEntryMap map[string]map[string]interface{} RoleEntryMap map[string]map[string]interface{}
} }
func NewRoleAllowsGlobWildcardsCheck() Check { func NewRoleAllowsGlobWildcardsCheck() Check {
return &RoleAllowsGlobWildcards{ return &RoleAllowsGlobWildcards{
RoleFetchIssues: make(map[string]*PathFetch),
RoleEntryMap: make(map[string]map[string]interface{}), RoleEntryMap: make(map[string]map[string]interface{}),
} }
} }
@@ -49,7 +51,7 @@ func (h *RoleAllowsGlobWildcards) FetchResources(e *Executor) error {
}) })
if exit || err != nil { if exit || err != nil {
if f != nil && f.IsSecretPermissionsError() { if f != nil && f.IsSecretPermissionsError() {
h.NoPerms = true h.RoleListFetchIssue = f
} }
return err return err
} }
@@ -60,7 +62,7 @@ func (h *RoleAllowsGlobWildcards) FetchResources(e *Executor) error {
}) })
if skip || err != nil || entry == nil { if skip || err != nil || entry == nil {
if f != nil && f.IsSecretPermissionsError() { if f != nil && f.IsSecretPermissionsError() {
h.NoPerms = true h.RoleFetchIssues[role] = f
} }
if err != nil { if err != nil {
return err return err
@@ -84,19 +86,38 @@ func (h *RoleAllowsGlobWildcards) Evaluate(e *Executor) (results []*Result, err
} }
return []*Result{&ret}, nil return []*Result{&ret}, nil
} }
if h.NoPerms { if h.RoleListFetchIssue != nil && h.RoleListFetchIssue.IsSecretPermissionsError() {
ret := Result{ ret := Result{
Status: ResultInsufficientPermissions, Status: ResultInsufficientPermissions,
Endpoint: "/{{mount}}/roles", Endpoint: h.RoleListFetchIssue.Path,
Message: "lacks permission either to list the roles or to read a specific role. This may restrict the ability to fully execute this health check.", Message: "lacks permission either to list the roles. This restricts the ability to fully execute this health check.",
} }
if e.Client.Token() == "" { if e.Client.Token() == "" {
ret.Message = "No token available and so this health check " + ret.Message ret.Message = "No token available and so this health check " + ret.Message
} else { } else {
ret.Message = "This token " + ret.Message ret.Message = "This token " + ret.Message
} }
return []*Result{&ret}, nil
}
for role, fetchPath := range h.RoleFetchIssues {
if fetchPath != nil && fetchPath.IsSecretPermissionsError() {
delete(h.RoleEntryMap, role)
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 the endpoint for this mount. " + ret.Message
}
results = append(results, &ret) results = append(results, &ret)
} }
}
for role, entry := range h.RoleEntryMap { for role, entry := range h.RoleEntryMap {
allowsWildcards, present := entry["allow_wildcard_certificates"] allowsWildcards, present := entry["allow_wildcard_certificates"]

View File

@@ -9,13 +9,15 @@ import (
type RoleAllowsLocalhost struct { type RoleAllowsLocalhost struct {
Enabled bool Enabled bool
UnsupportedVersion bool UnsupportedVersion bool
NoPerms bool
RoleListFetchIssue *PathFetch
RoleFetchIssues map[string]*PathFetch
RoleEntryMap map[string]map[string]interface{} RoleEntryMap map[string]map[string]interface{}
} }
func NewRoleAllowsLocalhostCheck() Check { func NewRoleAllowsLocalhostCheck() Check {
return &RoleAllowsLocalhost{ return &RoleAllowsLocalhost{
RoleFetchIssues: make(map[string]*PathFetch),
RoleEntryMap: make(map[string]map[string]interface{}), RoleEntryMap: make(map[string]map[string]interface{}),
} }
} }
@@ -48,7 +50,7 @@ func (h *RoleAllowsLocalhost) FetchResources(e *Executor) error {
}) })
if exit || err != nil { if exit || err != nil {
if f != nil && f.IsSecretPermissionsError() { if f != nil && f.IsSecretPermissionsError() {
h.NoPerms = true h.RoleListFetchIssue = f
} }
return err return err
} }
@@ -59,7 +61,7 @@ func (h *RoleAllowsLocalhost) FetchResources(e *Executor) error {
}) })
if skip || err != nil || entry == nil { if skip || err != nil || entry == nil {
if f != nil && f.IsSecretPermissionsError() { if f != nil && f.IsSecretPermissionsError() {
h.NoPerms = true h.RoleFetchIssues[role] = f
} }
if err != nil { if err != nil {
return err return err
@@ -83,19 +85,39 @@ func (h *RoleAllowsLocalhost) Evaluate(e *Executor) (results []*Result, err erro
} }
return []*Result{&ret}, nil return []*Result{&ret}, nil
} }
if h.NoPerms {
if h.RoleListFetchIssue != nil && h.RoleListFetchIssue.IsSecretPermissionsError() {
ret := Result{ ret := Result{
Status: ResultInsufficientPermissions, Status: ResultInsufficientPermissions,
Endpoint: "/{{mount}}/roles", Endpoint: h.RoleListFetchIssue.Path,
Message: "lacks permission either to list the roles or to read a specific role. This may restrict the ability to fully execute this health check", Message: "lacks permission either to list the roles. This restricts the ability to fully execute this health check.",
} }
if e.Client.Token() == "" { if e.Client.Token() == "" {
ret.Message = "No token available and so this health check " + ret.Message ret.Message = "No token available and so this health check " + ret.Message
} else { } else {
ret.Message = "This token " + ret.Message ret.Message = "This token " + ret.Message
} }
return []*Result{&ret}, nil
}
for role, fetchPath := range h.RoleFetchIssues {
if fetchPath != nil && fetchPath.IsSecretPermissionsError() {
delete(h.RoleEntryMap, role)
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 the endpoint for this mount. " + ret.Message
}
results = append(results, &ret) results = append(results, &ret)
} }
}
for role, entry := range h.RoleEntryMap { for role, entry := range h.RoleEntryMap {
allowsLocalhost := entry["allow_localhost"].(bool) allowsLocalhost := entry["allow_localhost"].(bool)

View File

@@ -11,17 +11,18 @@ import (
type RoleNoStoreFalse struct { type RoleNoStoreFalse struct {
Enabled bool Enabled bool
UnsupportedVersion bool UnsupportedVersion bool
NoPerms bool
AllowedRoles map[string]bool AllowedRoles map[string]bool
CertCounts int RoleListFetchIssue *PathFetch
RoleFetchIssues map[string]*PathFetch
RoleEntryMap map[string]map[string]interface{} RoleEntryMap map[string]map[string]interface{}
CRLConfig *PathFetch CRLConfig *PathFetch
} }
func NewRoleNoStoreFalseCheck() Check { func NewRoleNoStoreFalseCheck() Check {
return &RoleNoStoreFalse{ return &RoleNoStoreFalse{
RoleFetchIssues: make(map[string]*PathFetch),
AllowedRoles: make(map[string]bool), AllowedRoles: make(map[string]bool),
RoleEntryMap: make(map[string]map[string]interface{}), RoleEntryMap: make(map[string]map[string]interface{}),
} }
@@ -64,7 +65,7 @@ func (h *RoleNoStoreFalse) FetchResources(e *Executor) error {
}) })
if exit || err != nil { if exit || err != nil {
if f != nil && f.IsSecretPermissionsError() { if f != nil && f.IsSecretPermissionsError() {
h.NoPerms = true h.RoleListFetchIssue = f
} }
return err return err
} }
@@ -75,7 +76,7 @@ func (h *RoleNoStoreFalse) FetchResources(e *Executor) error {
}) })
if skip || err != nil || entry == nil { if skip || err != nil || entry == nil {
if f != nil && f.IsSecretPermissionsError() { if f != nil && f.IsSecretPermissionsError() {
h.NoPerms = true h.RoleFetchIssues[role] = f
} }
if err != nil { if err != nil {
return err return err
@@ -86,14 +87,6 @@ func (h *RoleNoStoreFalse) FetchResources(e *Executor) error {
h.RoleEntryMap[role] = entry h.RoleEntryMap[role] = entry
} }
exit, _, leaves, err := pkiFetchLeavesList(e, func() {
h.UnsupportedVersion = true
})
if exit || err != nil {
return err
}
h.CertCounts = len(leaves)
// Check if the issuer is fetched yet. // Check if the issuer is fetched yet.
configRet, err := e.FetchIfNotFetched(logical.ReadOperation, "/{{mount}}/config/crl") configRet, err := e.FetchIfNotFetched(logical.ReadOperation, "/{{mount}}/config/crl")
if err != nil { if err != nil {
@@ -116,19 +109,38 @@ func (h *RoleNoStoreFalse) Evaluate(e *Executor) (results []*Result, err error)
return []*Result{&ret}, nil return []*Result{&ret}, nil
} }
if h.NoPerms { if h.RoleListFetchIssue != nil && h.RoleListFetchIssue.IsSecretPermissionsError() {
ret := Result{ ret := Result{
Status: ResultInsufficientPermissions, Status: ResultInsufficientPermissions,
Endpoint: "/{{mount}}/roles", Endpoint: h.RoleListFetchIssue.Path,
Message: "lacks permission either to list the roles or to read a specific role. This may restrict the ability to fully execute this health check", Message: "lacks permission either to list the roles. This restricts the ability to fully execute this health check.",
} }
if e.Client.Token() == "" { if e.Client.Token() == "" {
ret.Message = "No token available and so this health check " + ret.Message ret.Message = "No token available and so this health check " + ret.Message
} else { } else {
ret.Message = "This token " + ret.Message ret.Message = "This token " + ret.Message
} }
return []*Result{&ret}, nil
}
for role, fetchPath := range h.RoleFetchIssues {
if fetchPath != nil && fetchPath.IsSecretPermissionsError() {
delete(h.RoleEntryMap, role)
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 the endpoint for this mount. " + ret.Message
}
results = append(results, &ret) results = append(results, &ret)
} }
}
crlAutoRebuild := false crlAutoRebuild := false
if h.CRLConfig != nil { if h.CRLConfig != nil {

View File

@@ -14,12 +14,14 @@ type RootIssuedLeaves struct {
CertsToFetch int CertsToFetch int
FetchIssues map[string]*PathFetch
RootCertMap map[string]*x509.Certificate RootCertMap map[string]*x509.Certificate
LeafCertMap map[string]*x509.Certificate LeafCertMap map[string]*x509.Certificate
} }
func NewRootIssuedLeavesCheck() Check { func NewRootIssuedLeavesCheck() Check {
return &RootIssuedLeaves{ return &RootIssuedLeaves{
FetchIssues: make(map[string]*PathFetch),
RootCertMap: make(map[string]*x509.Certificate), RootCertMap: make(map[string]*x509.Certificate),
LeafCertMap: make(map[string]*x509.Certificate), LeafCertMap: make(map[string]*x509.Certificate),
} }
@@ -64,9 +66,10 @@ func (h *RootIssuedLeaves) FetchResources(e *Executor) error {
} }
for _, issuer := range issuers { for _, issuer := range issuers {
skip, _, cert, err := pkiFetchIssuer(e, issuer, func() { skip, pathFetch, cert, err := pkiFetchIssuer(e, issuer, func() {
h.UnsupportedVersion = true h.UnsupportedVersion = true
}) })
h.FetchIssues[issuer] = pathFetch
if skip || err != nil { if skip || err != nil {
if err != nil { if err != nil {
return err return err
@@ -85,10 +88,15 @@ func (h *RootIssuedLeaves) FetchResources(e *Executor) error {
h.RootCertMap[issuer] = cert h.RootCertMap[issuer] = cert
} }
exit, _, leaves, err := pkiFetchLeavesList(e, func() { exit, f, leaves, err := pkiFetchLeavesList(e, func() {
h.UnsupportedVersion = true h.UnsupportedVersion = true
}) })
if exit || err != nil { if exit || err != nil {
if f != nil && f.IsSecretPermissionsError() {
for _, issuer := range issuers {
h.FetchIssues[issuer] = f
}
}
return err return err
} }
@@ -130,6 +138,25 @@ func (h *RootIssuedLeaves) Evaluate(e *Executor) (results []*Result, err error)
return []*Result{&ret}, nil return []*Result{&ret}, nil
} }
for issuer, fetchPath := range h.FetchIssues {
if fetchPath != nil && fetchPath.IsSecretPermissionsError() {
delete(h.RootCertMap, 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)
}
}
issuerHasLeaf := make(map[string]bool) issuerHasLeaf := make(map[string]bool)
for serial, leaf := range h.LeafCertMap { for serial, leaf := range h.LeafCertMap {
if len(issuerHasLeaf) == len(h.RootCertMap) { if len(issuerHasLeaf) == len(h.RootCertMap) {

View File

@@ -14,6 +14,7 @@ type TooManyCerts struct {
CountWarning int CountWarning int
CertCounts int CertCounts int
FetchIssue *PathFetch
} }
func NewTooManyCertsCheck() Check { func NewTooManyCertsCheck() Check {
@@ -60,7 +61,9 @@ func (h *TooManyCerts) FetchResources(e *Executor) error {
exit, leavesRet, _, err := pkiFetchLeavesList(e, func() { exit, leavesRet, _, err := pkiFetchLeavesList(e, func() {
h.UnsupportedVersion = true h.UnsupportedVersion = true
}) })
if exit { h.FetchIssue = leavesRet
if exit || err != nil {
return err return err
} }
@@ -80,6 +83,23 @@ func (h *TooManyCerts) Evaluate(e *Executor) (results []*Result, err error) {
return []*Result{&ret}, nil return []*Result{&ret}, nil
} }
if h.FetchIssue != nil && h.FetchIssue.IsSecretPermissionsError() {
ret := Result{
Status: ResultInsufficientPermissions,
Endpoint: h.FetchIssue.Path,
Message: "Without this information, this health check is unable to function.",
}
if e.Client.Token() == "" {
ret.Message = "No token available so unable to list the endpoint for this mount. " + ret.Message
} else {
ret.Message = "This token lacks permission to list the endpoint for this mount. " + ret.Message
}
results = append(results, &ret)
return
}
ret := Result{ ret := Result{
Status: ResultOK, Status: ResultOK,
Endpoint: "/{{mount}}/certs", Endpoint: "/{{mount}}/certs",

View File

@@ -223,7 +223,15 @@ func (c *PKIHealthCheckCommand) Run(args []string) int {
// Handle listing, if necessary. // Handle listing, if necessary.
if c.flagList { if c.flagList {
uiFormat := Format(c.UI)
if uiFormat == "yaml" {
c.UI.Error("YAML output format is not supported by the --list command")
return pkiRetUsage
}
if uiFormat != "json" {
c.UI.Output("Default health check config:") c.UI.Output("Default health check config:")
}
config := map[string]map[string]interface{}{} config := map[string]map[string]interface{}{}
for _, checker := range executor.Checkers { for _, checker := range executor.Checkers {
config[checker.Name()] = checker.DefaultConfig() config[checker.Name()] = checker.DefaultConfig()

View File

@@ -271,6 +271,8 @@ func testPKIHealthCheckCommand(tb testing.TB) (*cli.MockUi, *PKIHealthCheckComma
} }
func execPKIHC(t *testing.T, client *api.Client, ok bool) (int, string, map[string][]map[string]interface{}) { func execPKIHC(t *testing.T, client *api.Client, ok bool) (int, string, map[string][]map[string]interface{}) {
t.Helper()
stdout := bytes.NewBuffer(nil) stdout := bytes.NewBuffer(nil)
stderr := bytes.NewBuffer(nil) stderr := bytes.NewBuffer(nil)
runOpts := &RunOptions{ runOpts := &RunOptions{
@@ -295,6 +297,8 @@ func execPKIHC(t *testing.T, client *api.Client, ok bool) (int, string, map[stri
} }
func validateExpectedPKIHC(t *testing.T, expected, results map[string][]map[string]interface{}) { func validateExpectedPKIHC(t *testing.T, expected, results map[string][]map[string]interface{}) {
t.Helper()
for test, subtest := range expected { for test, subtest := range expected {
actual, ok := results[test] actual, ok := results[test]
require.True(t, ok, fmt.Sprintf("expected top-level test %v to be present", test)) require.True(t, ok, fmt.Sprintf("expected top-level test %v to be present", test))
@@ -615,7 +619,7 @@ var expectedNoPerm = map[string][]map[string]interface{}{
}, },
"root_issued_leaves": { "root_issued_leaves": {
{ {
"status": "ok", "status": "insufficient_permissions",
}, },
}, },
"tidy_last_run": { "tidy_last_run": {
@@ -625,7 +629,7 @@ var expectedNoPerm = map[string][]map[string]interface{}{
}, },
"too_many_certs": { "too_many_certs": {
{ {
"status": "ok", "status": "insufficient_permissions",
}, },
}, },
} }