vault/sdk/helper/certutil/cieps.go
Steven Clark 510cce5f81
Adjust the omitempty json flags within CIEPSResponse (#22305)
- Remove the omitempty flag from issuer_ref, and add it to the
   warnings and error fields
2023-08-11 20:17:00 +00:00

162 lines
5.7 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package certutil
import (
"crypto/x509"
"encoding/pem"
"fmt"
)
// Source of the issuance request: sign implies that the key material was
// generated by the user and submitted via a CSR request but only ACL level
// validation was applied; issue implies that Vault created the key material
// on behalf of the user with ACL level validation occurring; ACME implies
// that the user submitted a CSR and that additional ACME validation has
// occurred before sending the request to the external service for
// construction.
type CIEPSIssuanceMode string
const (
SignCIEPSMode = "sign"
IssueCIEPSMode = "issue"
ACMECIEPSMode = "acme"
ICACIEPSMode = "ica"
)
// Configuration of the issuer and mount at the time of this request;
// states the issuer's templated AIA information (falling back to the
// mount-global config if no per-issuer AIA info is set, the issuer's
// leaf_not_after_behavior (permit/truncate/err) for TTLs exceeding the
// issuer's validity period, and the mount's default and max TTL.
type CIEPSIssuanceConfig struct {
AIAValues *URLEntries `json:"aia_values"`
LeafNotAfterBehavior string `json:"leaf_not_after_behavior"`
MountDefaultTTL string `json:"mount_default_ttl"`
MountMaxTTL string `json:"mount_max_ttl"`
}
// Structured parameters sent by Vault or explicitly validated by Vault
// prior to sending.
type CIEPSVaultParams struct {
PolicyName string `json:"policy_name,omitempty"`
Mount string `json:"mount"`
Namespace string `json:"ns"`
// These indicate the type of the cluster node talking to the CIEPS
// service. When IsPerfStandby=true, setting StoreCert=true in the
// response will result in Vault forwarding the client's request
// up to the Performance Secondary's active node and re-trying the
// operation (including re-submitting the request to the CIEPS
// service).
//
// Any response returned by the CIEPS service in this case will be
// ignored and not signed by the CA's keys.
//
// IsPRSecondary is set to false when a local mount is used on a
// PR Secondary; in this scenario, PR Secondary nodes behave like
// PR Primary nodes. From a CIEPS service perspective, no behavior
// difference is expected between PR Primary and PR Secondary nodes;
// both will issue and store certificates on their active nodes.
// This information is included for audit tracking purposes.
IsPerfStandby bool `json:"vault_is_performance_standby"`
IsPRSecondary bool `json:"vault_is_performance_secondary"`
IssuanceMode CIEPSIssuanceMode `json:"issuance_mode"`
GeneratedKey bool `json:"vault_generated_private_key"`
IssuerName string `json:"requested_issuer_name"`
IssuerID string `json:"requested_issuer_id"`
IssuerCert string `json:"requested_issuer_cert"`
Config CIEPSIssuanceConfig `json:"requested_issuance_config"`
}
// Outer request object sent by Vault to the external CIEPS service.
//
// The top-level fields denote properties about the CIEPS request,
// with various request fields containing untrusted and trusted input
// respectively.
type CIEPSRequest struct {
Version int `json:"request_version"`
UUID string `json:"request_uuid"`
Sync bool `json:"synchronous"`
UserRequestKV map[string]interface{} `json:"user_request_key_values"`
IdentityRequestKV map[string]interface{} `json:"identity_request_key_values,omitempty"`
ACMERequestKV map[string]interface{} `json:"acme_request_key_values,omitempty"`
VaultRequestKV CIEPSVaultParams `json:"vault_request_values"`
// Vault guarantees that UserRequestKV will contain a csr parameter
// for all request types; this field is useful for engine implementations
// to have in parsed format. We assume that this is sent in PEM format,
// aligning with other Vault requests.
ParsedCSR *x509.CertificateRequest `json:"-"`
}
func (req *CIEPSRequest) ParseUserCSR() error {
csrValueRaw, present := req.UserRequestKV["csr"]
if !present {
return fmt.Errorf("missing expected 'csr' attribute on the request")
}
csrValue, ok := csrValueRaw.(string)
if !ok {
return fmt.Errorf("unexpected type of 'csr' attribute: %T", csrValueRaw)
}
if csrValue == "" {
return fmt.Errorf("unexpectedly empty 'csr' attribute on the request")
}
block, rest := pem.Decode([]byte(csrValue))
if len(rest) > 0 {
return fmt.Errorf("failed to decode 'csr': %v bytes of trailing data after PEM block", len(rest))
}
if block == nil {
return fmt.Errorf("failed to decode 'csr' PEM block")
}
csr, err := x509.ParseCertificateRequest(block.Bytes)
if err != nil {
return fmt.Errorf("failed to parse certificate request: %w", err)
}
req.ParsedCSR = csr
return nil
}
// Expected response object from the external CIEPS service.
//
// When parsing, Vault will disallow unknown fields, failing the
// parse if unknown fields are sent.
type CIEPSResponse struct {
UUID string `json:"request_uuid"`
Error string `json:"error,omitempty"`
Warnings []string `json:"warnings,omitempty"`
Certificate string `json:"certificate"`
ParsedCertificate *x509.Certificate `json:"-"`
IssuerRef string `json:"issuer_ref"`
StoreCert bool `json:"store_certificate"`
GenerateLease bool `json:"generate_lease"`
}
func (c *CIEPSResponse) MarshalCertificate() error {
if c.ParsedCertificate == nil || len(c.ParsedCertificate.Raw) == 0 {
return fmt.Errorf("no certificate present")
}
pem := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: c.ParsedCertificate.Raw,
})
if len(pem) == 0 {
return fmt.Errorf("failed to generate PEM: no body")
}
c.Certificate = string(pem)
return nil
}