mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-30 18:17:55 +00:00 
			
		
		
		
	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:
		
							
								
								
									
										155
									
								
								command/healthcheck/pki_allow_acme_headers.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										155
									
								
								command/healthcheck/pki_allow_acme_headers.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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} | ||||||
|  | } | ||||||
							
								
								
									
										237
									
								
								command/healthcheck/pki_enable_acme_issuance.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										237
									
								
								command/healthcheck/pki_enable_acme_issuance.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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", | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Steven Clark
					Steven Clark