5ee7cc5e6d
* Rename common.go->healthcheck.go Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Push handling of no resources to the health checks This allows us to better run on empty mounts. Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Exit when no issuers are found This makes health checks less useful. Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Add additional test criteria, refactor tests This will allow us to setup more tests. Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Add more OK statuses when checks are good Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Add test cases for all bad results The test for too-many-certs was elided for now due to being too hard to setup in CI. Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Add test for missing mount Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Add expected failure test on empty mount Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Add test for only having an issuer in the mount Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * More consistently perform permission checks Also return them to the caller when they're relevant. Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Add test without token Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Run health check tests in parallel Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Update command/healthcheck/healthcheck.go Co-authored-by: Steven Clark <steven.clark@hashicorp.com> * Update command/healthcheck/healthcheck.go Co-authored-by: Steven Clark <steven.clark@hashicorp.com> Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> Co-authored-by: Steven Clark <steven.clark@hashicorp.com>
186 lines
5.2 KiB
Go
186 lines
5.2 KiB
Go
package healthcheck
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/hashicorp/vault/sdk/logical"
|
|
|
|
"github.com/hashicorp/go-secure-stdlib/parseutil"
|
|
)
|
|
|
|
type RoleNoStoreFalse struct {
|
|
Enabled bool
|
|
UnsupportedVersion bool
|
|
NoPerms bool
|
|
|
|
AllowedRoles map[string]bool
|
|
|
|
CertCounts int
|
|
RoleEntryMap map[string]map[string]interface{}
|
|
CRLConfig *PathFetch
|
|
}
|
|
|
|
func NewRoleNoStoreFalseCheck() Check {
|
|
return &RoleNoStoreFalse{
|
|
AllowedRoles: make(map[string]bool),
|
|
RoleEntryMap: make(map[string]map[string]interface{}),
|
|
}
|
|
}
|
|
|
|
func (h *RoleNoStoreFalse) Name() string {
|
|
return "role_no_store_false"
|
|
}
|
|
|
|
func (h *RoleNoStoreFalse) IsEnabled() bool {
|
|
return h.Enabled
|
|
}
|
|
|
|
func (h *RoleNoStoreFalse) DefaultConfig() map[string]interface{} {
|
|
return map[string]interface{}{
|
|
"allowed_roles": []string{},
|
|
}
|
|
}
|
|
|
|
func (h *RoleNoStoreFalse) LoadConfig(config map[string]interface{}) error {
|
|
value, present := config["allowed_roles"].([]interface{})
|
|
if present {
|
|
for _, rawValue := range value {
|
|
h.AllowedRoles[rawValue.(string)] = true
|
|
}
|
|
}
|
|
|
|
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 *RoleNoStoreFalse) FetchResources(e *Executor) error {
|
|
exit, f, roles, err := pkiFetchRolesList(e, func() {
|
|
h.UnsupportedVersion = true
|
|
})
|
|
if exit || err != nil {
|
|
if f != nil && f.IsSecretPermissionsError() {
|
|
h.NoPerms = true
|
|
}
|
|
return err
|
|
}
|
|
|
|
for _, role := range roles {
|
|
skip, f, entry, err := pkiFetchRole(e, role, func() {
|
|
h.UnsupportedVersion = true
|
|
})
|
|
if skip || err != nil || entry == nil {
|
|
if f != nil && f.IsSecretPermissionsError() {
|
|
h.NoPerms = true
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
continue
|
|
}
|
|
|
|
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.
|
|
configRet, err := e.FetchIfNotFetched(logical.ReadOperation, "/{{mount}}/config/crl")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
h.CRLConfig = configRet
|
|
|
|
return nil
|
|
}
|
|
|
|
func (h *RoleNoStoreFalse) Evaluate(e *Executor) (results []*Result, err error) {
|
|
if h.UnsupportedVersion {
|
|
// Shouldn't happen; roles have been around forever.
|
|
ret := Result{
|
|
Status: ResultInvalidVersion,
|
|
Endpoint: "/{{mount}}/roles",
|
|
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
|
|
}
|
|
|
|
if h.NoPerms {
|
|
ret := Result{
|
|
Status: ResultInsufficientPermissions,
|
|
Endpoint: "/{{mount}}/roles",
|
|
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",
|
|
}
|
|
if e.Client.Token() == "" {
|
|
ret.Message = "No token available and so this health check " + ret.Message
|
|
} else {
|
|
ret.Message = "This token " + ret.Message
|
|
}
|
|
results = append(results, &ret)
|
|
}
|
|
|
|
crlAutoRebuild := false
|
|
if h.CRLConfig != nil {
|
|
if h.CRLConfig.IsSecretPermissionsError() {
|
|
ret := Result{
|
|
Status: ResultInsufficientPermissions,
|
|
Endpoint: "/{{mount}}/config/crl",
|
|
Message: "This prevents the health check from seeing if the CRL is set to auto_rebuild=true and lowering the severity of check results appropriately.",
|
|
}
|
|
|
|
if e.Client.Token() == "" {
|
|
ret.Message = "No token available so unable read authenticated CRL configuration for this mount. " + ret.Message
|
|
} else {
|
|
ret.Message = "This token lacks so permission to read the CRL configuration for this mount. " + ret.Message
|
|
}
|
|
|
|
results = append(results, &ret)
|
|
} else if h.CRLConfig.Secret != nil && h.CRLConfig.Secret.Data["auto_rebuild"] != nil {
|
|
crlAutoRebuild = h.CRLConfig.Secret.Data["auto_rebuild"].(bool)
|
|
}
|
|
}
|
|
|
|
for role, entry := range h.RoleEntryMap {
|
|
noStore := entry["no_store"].(bool)
|
|
if noStore {
|
|
continue
|
|
}
|
|
|
|
ret := Result{
|
|
Status: ResultWarning,
|
|
Endpoint: "/{{mount}}/role/" + role,
|
|
Message: "Role currently stores every issued certificate (no_store=false). Too many issued and/or revoked certificates can exceed Vault's storage limits and make operations slow. It is encouraged to enable auto-rebuild of CRLs to prevent every revocation from creating a new CRL, and to limit the number of certificates issued under roles with no_store=false: use shorter lifetimes and/or BYOC revocation instead.",
|
|
}
|
|
|
|
if crlAutoRebuild {
|
|
ret.Status = ResultInformational
|
|
ret.Message = "Role currently stores every issued certificate (no_store=false). With auto-rebuild CRL enabled, less performance impact occur on CRL rebuilding, but note that too many issued and/or revoked certificates can exceed Vault's storage limits and make operations slow. It is suggested to limit the number of certificates issued under roles with no_store=false: use shorter lifetimes to avoid revocation and/or BYOC revocation instead."
|
|
}
|
|
|
|
results = append(results, &ret)
|
|
}
|
|
|
|
if len(results) == 0 && len(h.RoleEntryMap) > 0 {
|
|
ret := Result{
|
|
Status: ResultOK,
|
|
Endpoint: "/{{mount}}/roles",
|
|
Message: "Roles follow best practices regarding certificate storage.",
|
|
}
|
|
|
|
results = append(results, &ret)
|
|
}
|
|
|
|
return
|
|
}
|