Add ACME health checks to pki health-check CLI (#20619)
* Add ACME health checks to pki health-check CLI - Verify we have the required header values listed within allowed_response_headers: 'Replay-Nonce', 'Link', 'Location' - Make sure the local cluster config path variable contains an URL with an https scheme * Split ACME health checks into two separate verifications - Promote ACME usage through the enable_acme_issuance check, if ACME is disabled currently - If ACME is enabled verify that we have a valid 'path' field within local cluster configuration as well as the proper response headers allowed. - Factor out response header verifications into a separate check mainly to work around possible permission issues. * Only recommend enabling ACME on mounts with intermediate issuers * Attempt to connect to the ACME directory based on the cluster path variable - Final health check is to attempt to connect to the ACME directory based on the cluster local 'path' value. Only if we successfully connect do we say ACME is healthy. * Fix broken unit test
This commit is contained in:
parent
5eb03f785e
commit
c8837f2010
|
@ -0,0 +1,155 @@
|
||||||
|
// 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}
|
||||||
|
}
|
|
@ -0,0 +1,237 @@
|
||||||
|
// Copyright (c) HashiCorp, Inc.
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package healthcheck
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/hashicorp/go-secure-stdlib/parseutil"
|
||||||
|
"github.com/hashicorp/vault/sdk/logical"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/acme"
|
||||||
|
)
|
||||||
|
|
||||||
|
type EnableAcmeIssuance struct {
|
||||||
|
Enabled bool
|
||||||
|
UnsupportedVersion bool
|
||||||
|
|
||||||
|
AcmeConfigFetcher *PathFetch
|
||||||
|
ClusterConfigFetcher *PathFetch
|
||||||
|
TotalIssuers int
|
||||||
|
RootIssuers int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEnableAcmeIssuance() Check {
|
||||||
|
return &EnableAcmeIssuance{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *EnableAcmeIssuance) Name() string {
|
||||||
|
return "enable_acme_issuance"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *EnableAcmeIssuance) IsEnabled() bool {
|
||||||
|
return h.Enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *EnableAcmeIssuance) DefaultConfig() map[string]interface{} {
|
||||||
|
return map[string]interface{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *EnableAcmeIssuance) 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 *EnableAcmeIssuance) 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.ClusterConfigFetcher, err = e.FetchIfNotFetched(logical.ReadOperation, "/{{mount}}/config/cluster")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.ClusterConfigFetcher.IsUnsupportedPathError() {
|
||||||
|
h.UnsupportedVersion = true
|
||||||
|
}
|
||||||
|
|
||||||
|
h.TotalIssuers, h.RootIssuers, err = doesMountContainOnlyRootIssuers(e)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func doesMountContainOnlyRootIssuers(e *Executor) (int, int, error) {
|
||||||
|
exit, _, issuers, err := pkiFetchIssuersList(e, func() {})
|
||||||
|
if exit || err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
totalIssuers := 0
|
||||||
|
rootIssuers := 0
|
||||||
|
|
||||||
|
for _, issuer := range issuers {
|
||||||
|
skip, _, cert, err := pkiFetchIssuer(e, issuer, func() {})
|
||||||
|
|
||||||
|
if skip || err != nil {
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
totalIssuers++
|
||||||
|
|
||||||
|
if !bytes.Equal(cert.RawSubject, cert.RawIssuer) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := cert.CheckSignatureFrom(cert); err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
rootIssuers++
|
||||||
|
}
|
||||||
|
|
||||||
|
return totalIssuers, rootIssuers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isAcmeEnabled(fetcher *PathFetch) (bool, error) {
|
||||||
|
isEnabledRaw, ok := fetcher.Secret.Data["enabled"]
|
||||||
|
if !ok {
|
||||||
|
return false, fmt.Errorf("enabled configuration field missing from acme config")
|
||||||
|
}
|
||||||
|
|
||||||
|
parseBool, err := parseutil.ParseBool(isEnabledRaw)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("failed parsing 'enabled' field from ACME config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseBool, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyLocalPathUrl(h *EnableAcmeIssuance) error {
|
||||||
|
localPathRaw, ok := h.ClusterConfigFetcher.Secret.Data["path"]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("'path' field missing from config")
|
||||||
|
}
|
||||||
|
|
||||||
|
localPath, err := parseutil.ParseString(localPathRaw)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed converting 'path' field from local config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if localPath == "" {
|
||||||
|
return fmt.Errorf("'path' field not configured within /{{mount}}/config/cluster")
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedUrl, err := url.Parse(localPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to parse URL from path config: %v: %w", localPathRaw, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if parsedUrl.Scheme != "https" {
|
||||||
|
return fmt.Errorf("the configured 'path' field in /{{mount}}/config/cluster was not using an https scheme")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoid issues with SSL certificates for this check, we just want to validate that we would
|
||||||
|
// hit an ACME server with the path they specified in configuration
|
||||||
|
tr := &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
|
}
|
||||||
|
client := &http.Client{Transport: tr}
|
||||||
|
acmeDirectoryUrl := parsedUrl.JoinPath("/acme/", "directory")
|
||||||
|
acmeClient := acme.Client{HTTPClient: client, DirectoryURL: acmeDirectoryUrl.String()}
|
||||||
|
_, err = acmeClient.Discover(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("using configured 'path' field ('%s') in /{{mount}}/config/cluster failed to reach the ACME"+
|
||||||
|
" directory: %s: %w", parsedUrl.String(), acmeDirectoryUrl.String(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *EnableAcmeIssuance) Evaluate(e *Executor) (results []*Result, err 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 this information, 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 {
|
||||||
|
if h.TotalIssuers == 0 {
|
||||||
|
ret := Result{
|
||||||
|
Status: ResultNotApplicable,
|
||||||
|
Endpoint: h.AcmeConfigFetcher.Path,
|
||||||
|
Message: "No issuers in mount, ACME is not required.",
|
||||||
|
}
|
||||||
|
return []*Result{&ret}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.TotalIssuers == h.RootIssuers {
|
||||||
|
ret := Result{
|
||||||
|
Status: ResultNotApplicable,
|
||||||
|
Endpoint: h.AcmeConfigFetcher.Path,
|
||||||
|
Message: "Mount contains only root issuers, ACME is not required.",
|
||||||
|
}
|
||||||
|
return []*Result{&ret}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := Result{
|
||||||
|
Status: ResultInformational,
|
||||||
|
Endpoint: h.AcmeConfigFetcher.Path,
|
||||||
|
Message: "Consider enabling ACME support to support a self-rotating PKI infrastructure.",
|
||||||
|
}
|
||||||
|
return []*Result{&ret}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.ClusterConfigFetcher.IsSecretPermissionsError() {
|
||||||
|
msg := "Without this information, this health check is unable to function."
|
||||||
|
return craftInsufficientPermissionResult(e, h.ClusterConfigFetcher.Path, msg), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
localPathIssue := verifyLocalPathUrl(h)
|
||||||
|
|
||||||
|
if localPathIssue != nil {
|
||||||
|
ret := Result{
|
||||||
|
Status: ResultWarning,
|
||||||
|
Endpoint: h.ClusterConfigFetcher.Path,
|
||||||
|
Message: "ACME enabled in config but not functional: " + localPathIssue.Error(),
|
||||||
|
}
|
||||||
|
return []*Result{&ret}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := Result{
|
||||||
|
Status: ResultOK,
|
||||||
|
Endpoint: h.ClusterConfigFetcher.Path,
|
||||||
|
Message: "ACME enabled and successfully connected to the ACME directory.",
|
||||||
|
}
|
||||||
|
return []*Result{&ret}, nil
|
||||||
|
}
|
|
@ -220,6 +220,8 @@ func (c *PKIHealthCheckCommand) Run(args []string) int {
|
||||||
executor.AddCheck(healthcheck.NewEnableAutoTidyCheck())
|
executor.AddCheck(healthcheck.NewEnableAutoTidyCheck())
|
||||||
executor.AddCheck(healthcheck.NewTidyLastRunCheck())
|
executor.AddCheck(healthcheck.NewTidyLastRunCheck())
|
||||||
executor.AddCheck(healthcheck.NewTooManyCertsCheck())
|
executor.AddCheck(healthcheck.NewTooManyCertsCheck())
|
||||||
|
executor.AddCheck(healthcheck.NewEnableAcmeIssuance())
|
||||||
|
executor.AddCheck(healthcheck.NewAllowAcmeHeaders())
|
||||||
if c.flagDefaultDisabled {
|
if c.flagDefaultDisabled {
|
||||||
executor.DefaultEnabled = false
|
executor.DefaultEnabled = false
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
@ -30,7 +31,7 @@ func TestPKIHC_AllGood(t *testing.T) {
|
||||||
AuditNonHMACRequestKeys: healthcheck.VisibleReqParams,
|
AuditNonHMACRequestKeys: healthcheck.VisibleReqParams,
|
||||||
AuditNonHMACResponseKeys: healthcheck.VisibleRespParams,
|
AuditNonHMACResponseKeys: healthcheck.VisibleRespParams,
|
||||||
PassthroughRequestHeaders: []string{"If-Modified-Since"},
|
PassthroughRequestHeaders: []string{"If-Modified-Since"},
|
||||||
AllowedResponseHeaders: []string{"Last-Modified"},
|
AllowedResponseHeaders: []string{"Last-Modified", "Replay-Nonce", "Link", "Location"},
|
||||||
MaxLeaseTTL: "36500d",
|
MaxLeaseTTL: "36500d",
|
||||||
},
|
},
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
|
@ -69,6 +70,21 @@ func TestPKIHC_AllGood(t *testing.T) {
|
||||||
t.Fatalf("failed to run tidy: %v", err)
|
t.Fatalf("failed to run tidy: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
path, err := url.Parse(client.Address())
|
||||||
|
require.NoError(t, err, "failed parsing client address")
|
||||||
|
|
||||||
|
if _, err := client.Logical().Write("pki/config/cluster", map[string]interface{}{
|
||||||
|
"path": path.JoinPath("/v1/", "pki/").String(),
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("failed to update local cluster: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := client.Logical().Write("pki/config/acme", map[string]interface{}{
|
||||||
|
"enabled": "true",
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("failed to update acme config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
_, _, results := execPKIHC(t, client, true)
|
_, _, results := execPKIHC(t, client, true)
|
||||||
|
|
||||||
validateExpectedPKIHC(t, expectedAllGood, results)
|
validateExpectedPKIHC(t, expectedAllGood, results)
|
||||||
|
@ -345,6 +361,11 @@ var expectedAllGood = map[string][]map[string]interface{}{
|
||||||
"status": "ok",
|
"status": "ok",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"allow_acme_headers": {
|
||||||
|
{
|
||||||
|
"status": "ok",
|
||||||
|
},
|
||||||
|
},
|
||||||
"allow_if_modified_since": {
|
"allow_if_modified_since": {
|
||||||
{
|
{
|
||||||
"status": "ok",
|
"status": "ok",
|
||||||
|
@ -355,6 +376,11 @@ var expectedAllGood = map[string][]map[string]interface{}{
|
||||||
"status": "ok",
|
"status": "ok",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"enable_acme_issuance": {
|
||||||
|
{
|
||||||
|
"status": "ok",
|
||||||
|
},
|
||||||
|
},
|
||||||
"enable_auto_tidy": {
|
"enable_auto_tidy": {
|
||||||
{
|
{
|
||||||
"status": "ok",
|
"status": "ok",
|
||||||
|
@ -406,6 +432,11 @@ var expectedAllBad = map[string][]map[string]interface{}{
|
||||||
"status": "critical",
|
"status": "critical",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"allow_acme_headers": {
|
||||||
|
{
|
||||||
|
"status": "not_applicable",
|
||||||
|
},
|
||||||
|
},
|
||||||
"allow_if_modified_since": {
|
"allow_if_modified_since": {
|
||||||
{
|
{
|
||||||
"status": "informational",
|
"status": "informational",
|
||||||
|
@ -503,6 +534,11 @@ var expectedAllBad = map[string][]map[string]interface{}{
|
||||||
"status": "informational",
|
"status": "informational",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"enable_acme_issuance": {
|
||||||
|
{
|
||||||
|
"status": "not_applicable",
|
||||||
|
},
|
||||||
|
},
|
||||||
"enable_auto_tidy": {
|
"enable_auto_tidy": {
|
||||||
{
|
{
|
||||||
"status": "informational",
|
"status": "informational",
|
||||||
|
@ -554,8 +590,18 @@ var expectedEmptyWithIssuer = map[string][]map[string]interface{}{
|
||||||
"status": "ok",
|
"status": "ok",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"allow_acme_headers": {
|
||||||
|
{
|
||||||
|
"status": "not_applicable",
|
||||||
|
},
|
||||||
|
},
|
||||||
"allow_if_modified_since": nil,
|
"allow_if_modified_since": nil,
|
||||||
"audit_visibility": nil,
|
"audit_visibility": nil,
|
||||||
|
"enable_acme_issuance": {
|
||||||
|
{
|
||||||
|
"status": "not_applicable",
|
||||||
|
},
|
||||||
|
},
|
||||||
"enable_auto_tidy": {
|
"enable_auto_tidy": {
|
||||||
{
|
{
|
||||||
"status": "informational",
|
"status": "informational",
|
||||||
|
@ -598,8 +644,18 @@ var expectedNoPerm = map[string][]map[string]interface{}{
|
||||||
"status": "critical",
|
"status": "critical",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"allow_acme_headers": {
|
||||||
|
{
|
||||||
|
"status": "insufficient_permissions",
|
||||||
|
},
|
||||||
|
},
|
||||||
"allow_if_modified_since": nil,
|
"allow_if_modified_since": nil,
|
||||||
"audit_visibility": nil,
|
"audit_visibility": nil,
|
||||||
|
"enable_acme_issuance": {
|
||||||
|
{
|
||||||
|
"status": "insufficient_permissions",
|
||||||
|
},
|
||||||
|
},
|
||||||
"enable_auto_tidy": {
|
"enable_auto_tidy": {
|
||||||
{
|
{
|
||||||
"status": "insufficient_permissions",
|
"status": "insufficient_permissions",
|
||||||
|
|
Loading…
Reference in New Issue