vault/builtin/logical/pkiext/pkiext_binary/pki_mount.go
Matt Schultz 8cc7be234a
Adds automated ACME tests using Caddy. (#21277)
* Adds automated ACME tests using Caddy.

* Do not use CheckSignatureFrom method to validate TLS-ALPN-01 challenges

* Uncomment TLS-ALPN test.

* Fix validation of tls-alpn-01 keyAuthz

Surprisingly, this failure was not caught by our earlier, but unmerged
acme.sh tests:

> 2023-06-07T19:35:27.6963070Z [32mPASS[0m builtin/logical/pkiext/pkiext_binary.Test_ACME/group/acme.sh_tls-alpn (33.06s)

from https://github.com/hashicorp/vault/pull/20987.

Notably, we had two failures:

 1. The extension's raw value is not used, but is instead an OCTET
    STRING encoded version:

    > The extension has the following ASN.1 [X.680] format :
    >
    > Authorization ::= OCTET STRING (SIZE (32))
    >
    > The extnValue of the id-pe-acmeIdentifier extension is the ASN.1
    > DER encoding [X.690] of the Authorization structure, which
    > contains the SHA-256 digest of the key authorization for the
    > challenge.
 2. Unlike DNS, the SHA-256 is directly embedded in the authorization,
    as evidenced by the `SIZE (32)` annotation in the quote above: we
    were instead expecting this to be url base-64 encoded, which would
    have a different size.

This failure was caught by Matt, testing with Caddy. :-)

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

* Quick gofmt run.

* Fix challenge encoding in TLS-ALPN-01 challenge tests

* Rename a PKI test helper that retrieves the Vault cluster listener's cert to distinguish it from the method that retrieves the PKI mount's CA cert. Combine a couple of Docker file copy commands into one.

---------

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>
Co-authored-by: Steve Clark <steven.clark@hashicorp.com>
Co-authored-by: Alexander Scheel <alex.scheel@hashicorp.com>
2023-06-15 20:44:09 +00:00

161 lines
5.1 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package pkiext_binary
import (
"context"
"encoding/base64"
"fmt"
"path"
"github.com/hashicorp/vault/api"
)
type VaultPkiMount struct {
*VaultPkiCluster
mount string
}
func (vpm *VaultPkiMount) UpdateClusterConfig(config map[string]interface{}) error {
defaultPath := "https://" + vpm.cluster.ClusterNodes[0].ContainerIPAddress + ":8200/v1/" + vpm.mount
defaults := map[string]interface{}{
"path": defaultPath,
"aia_path": defaultPath,
}
_, err := vpm.GetActiveNode().Logical().WriteWithContext(context.Background(),
vpm.mount+"/config/cluster", mergeWithDefaults(config, defaults))
return err
}
func (vpm *VaultPkiMount) UpdateClusterConfigLocalAddr() (string, error) {
basePath := fmt.Sprintf("https://%s/v1/%s", vpm.GetActiveContainerHostPort(), vpm.mount)
return basePath, vpm.UpdateClusterConfig(map[string]interface{}{
"path": basePath,
})
}
func (vpm *VaultPkiMount) UpdateAcmeConfig(enable bool, config map[string]interface{}) error {
defaults := map[string]interface{}{
"enabled": enable,
}
_, err := vpm.GetActiveNode().Logical().WriteWithContext(context.Background(),
vpm.mount+"/config/acme", mergeWithDefaults(config, defaults))
return err
}
func (vpm *VaultPkiMount) GenerateRootInternal(props map[string]interface{}) (*api.Secret, error) {
defaults := map[string]interface{}{
"common_name": "root-test.com",
"key_type": "ec",
"issuer_name": "root",
}
return vpm.GetActiveNode().Logical().WriteWithContext(context.Background(),
vpm.mount+"/root/generate/internal", mergeWithDefaults(props, defaults))
}
func (vpm *VaultPkiMount) GenerateIntermediateInternal(props map[string]interface{}) (*api.Secret, error) {
defaults := map[string]interface{}{
"common_name": "intermediary-test.com",
"key_type": "ec",
"issuer_name": "intermediary",
}
return vpm.GetActiveNode().Logical().WriteWithContext(context.Background(),
vpm.mount+"/intermediate/generate/internal", mergeWithDefaults(props, defaults))
}
func (vpm *VaultPkiMount) SignIntermediary(signingIssuer string, csr interface{}, props map[string]interface{}) (*api.Secret, error) {
defaults := map[string]interface{}{
"csr": csr,
}
return vpm.GetActiveNode().Logical().WriteWithContext(context.Background(),
vpm.mount+"/issuer/"+signingIssuer+"/sign-intermediate",
mergeWithDefaults(props, defaults))
}
func (vpm *VaultPkiMount) ImportBundle(pemBundle interface{}, props map[string]interface{}) (*api.Secret, error) {
defaults := map[string]interface{}{
"pem_bundle": pemBundle,
}
return vpm.GetActiveNode().Logical().WriteWithContext(context.Background(),
vpm.mount+"/issuers/import/bundle", mergeWithDefaults(props, defaults))
}
func (vpm *VaultPkiMount) UpdateDefaultIssuer(issuerId string, props map[string]interface{}) error {
defaults := map[string]interface{}{
"default": issuerId,
}
_, err := vpm.GetActiveNode().Logical().WriteWithContext(context.Background(),
vpm.mount+"/config/issuers", mergeWithDefaults(props, defaults))
return err
}
func (vpm *VaultPkiMount) UpdateIssuer(issuerRef string, props map[string]interface{}) error {
defaults := map[string]interface{}{}
_, err := vpm.GetActiveNode().Logical().JSONMergePatch(context.Background(),
vpm.mount+"/issuer/"+issuerRef, mergeWithDefaults(props, defaults))
return err
}
func (vpm *VaultPkiMount) UpdateRole(roleName string, config map[string]interface{}) error {
defaults := map[string]interface{}{}
_, err := vpm.GetActiveNode().Logical().WriteWithContext(context.Background(),
vpm.mount+"/roles/"+roleName, mergeWithDefaults(config, defaults))
return err
}
func (vpm *VaultPkiMount) GetEabKey(acmeDirectory string) (string, string, error) {
eabPath := path.Join(vpm.mount, acmeDirectory, "/new-eab")
resp, err := vpm.GetActiveNode().Logical().WriteWithContext(context.Background(), eabPath, map[string]interface{}{})
if err != nil {
return "", "", fmt.Errorf("failed fetching eab from %s: %w", eabPath, err)
}
eabId := resp.Data["id"].(string)
base64EabKey := resp.Data["key"].(string)
// just make sure we get something valid back from the server, we still want to pass back the base64 version
// to the caller...
_, err = base64.RawURLEncoding.DecodeString(base64EabKey)
if err != nil {
return "", "", fmt.Errorf("failed decoding key response field: %s: %w", base64EabKey, err)
}
return eabId, base64EabKey, nil
}
// GetCACertPEM retrieves the PKI mount's PEM-encoded CA certificate.
func (vpm *VaultPkiMount) GetCACertPEM() (string, error) {
caCertPath := path.Join(vpm.mount, "/cert/ca")
resp, err := vpm.GetActiveNode().Logical().ReadWithContext(context.Background(), caCertPath)
if err != nil {
return "", err
}
return resp.Data["certificate"].(string), nil
}
func mergeWithDefaults(config map[string]interface{}, defaults map[string]interface{}) map[string]interface{} {
myConfig := config
if myConfig == nil {
myConfig = map[string]interface{}{}
}
for key, value := range defaults {
if origVal, exists := config[key]; !exists {
myConfig[key] = value
} else {
myConfig[key] = origVal
}
}
return myConfig
}