github.com/hashicorp/vault/sdk@v0.11.0/helper/certutil/cieps.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package certutil
     5  
     6  import (
     7  	"crypto/x509"
     8  	"encoding/pem"
     9  	"fmt"
    10  )
    11  
    12  // Source of the issuance request: sign implies that the key material was
    13  // generated by the user and submitted via a CSR request but only ACL level
    14  // validation was applied; issue implies that Vault created the key material
    15  // on behalf of the user with ACL level validation occurring; ACME implies
    16  // that the user submitted a CSR and that additional ACME validation has
    17  // occurred before sending the request to the external service for
    18  // construction.
    19  type CIEPSIssuanceMode string
    20  
    21  const (
    22  	SignCIEPSMode  = "sign"
    23  	IssueCIEPSMode = "issue"
    24  	ACMECIEPSMode  = "acme"
    25  	ICACIEPSMode   = "ica"
    26  )
    27  
    28  // Configuration of the issuer and mount at the time of this request;
    29  // states the issuer's templated AIA information (falling back to the
    30  // mount-global config if no per-issuer AIA info is set, the issuer's
    31  // leaf_not_after_behavior (permit/truncate/err) for TTLs exceeding the
    32  // issuer's validity period, and the mount's default and max TTL.
    33  type CIEPSIssuanceConfig struct {
    34  	AIAValues            *URLEntries `json:"aia_values"`
    35  	LeafNotAfterBehavior string      `json:"leaf_not_after_behavior"`
    36  	MountDefaultTTL      string      `json:"mount_default_ttl"`
    37  	MountMaxTTL          string      `json:"mount_max_ttl"`
    38  }
    39  
    40  // Structured parameters sent by Vault or explicitly validated by Vault
    41  // prior to sending.
    42  type CIEPSVaultParams struct {
    43  	PolicyName string `json:"policy_name,omitempty"`
    44  	Mount      string `json:"mount"`
    45  	Namespace  string `json:"ns"`
    46  
    47  	// These indicate the type of the cluster node talking to the CIEPS
    48  	// service. When IsPerfStandby=true, setting StoreCert=true in the
    49  	// response will result in Vault forwarding the client's request
    50  	// up to the Performance Secondary's active node and re-trying the
    51  	// operation (including re-submitting the request to the CIEPS
    52  	// service).
    53  	//
    54  	// Any response returned by the CIEPS service in this case will be
    55  	// ignored and not signed by the CA's keys.
    56  	//
    57  	// IsPRSecondary is set to false when a local mount is used on a
    58  	// PR Secondary; in this scenario, PR Secondary nodes behave like
    59  	// PR Primary nodes. From a CIEPS service perspective, no behavior
    60  	// difference is expected between PR Primary and PR Secondary nodes;
    61  	// both will issue and store certificates on their active nodes.
    62  	// This information is included for audit tracking purposes.
    63  	IsPerfStandby bool `json:"vault_is_performance_standby"`
    64  	IsPRSecondary bool `json:"vault_is_performance_secondary"`
    65  
    66  	IssuanceMode CIEPSIssuanceMode `json:"issuance_mode"`
    67  
    68  	GeneratedKey bool `json:"vault_generated_private_key"`
    69  
    70  	IssuerName string `json:"requested_issuer_name"`
    71  	IssuerID   string `json:"requested_issuer_id"`
    72  	IssuerCert string `json:"requested_issuer_cert"`
    73  
    74  	Config CIEPSIssuanceConfig `json:"requested_issuance_config"`
    75  }
    76  
    77  // Outer request object sent by Vault to the external CIEPS service.
    78  //
    79  // The top-level fields denote properties about the CIEPS request,
    80  // with various request fields containing untrusted and trusted input
    81  // respectively.
    82  type CIEPSRequest struct {
    83  	Version int    `json:"request_version"`
    84  	UUID    string `json:"request_uuid"`
    85  	Sync    bool   `json:"synchronous"`
    86  
    87  	UserRequestKV     map[string]interface{} `json:"user_request_key_values"`
    88  	IdentityRequestKV map[string]interface{} `json:"identity_request_key_values,omitempty"`
    89  	ACMERequestKV     map[string]interface{} `json:"acme_request_key_values,omitempty"`
    90  	VaultRequestKV    CIEPSVaultParams       `json:"vault_request_values"`
    91  
    92  	// Vault guarantees that UserRequestKV will contain a csr parameter
    93  	// for all request types; this field is useful for engine implementations
    94  	// to have in parsed format. We assume that this is sent in PEM format,
    95  	// aligning with other Vault requests.
    96  	ParsedCSR *x509.CertificateRequest `json:"-"`
    97  }
    98  
    99  func (req *CIEPSRequest) ParseUserCSR() error {
   100  	csrValueRaw, present := req.UserRequestKV["csr"]
   101  	if !present {
   102  		return fmt.Errorf("missing expected 'csr' attribute on the request")
   103  	}
   104  
   105  	csrValue, ok := csrValueRaw.(string)
   106  	if !ok {
   107  		return fmt.Errorf("unexpected type of 'csr' attribute: %T", csrValueRaw)
   108  	}
   109  
   110  	if csrValue == "" {
   111  		return fmt.Errorf("unexpectedly empty 'csr' attribute on the request")
   112  	}
   113  
   114  	block, rest := pem.Decode([]byte(csrValue))
   115  	if len(rest) > 0 {
   116  		return fmt.Errorf("failed to decode 'csr': %v bytes of trailing data after PEM block", len(rest))
   117  	}
   118  	if block == nil {
   119  		return fmt.Errorf("failed to decode 'csr' PEM block")
   120  	}
   121  
   122  	csr, err := x509.ParseCertificateRequest(block.Bytes)
   123  	if err != nil {
   124  		return fmt.Errorf("failed to parse certificate request: %w", err)
   125  	}
   126  
   127  	req.ParsedCSR = csr
   128  	return nil
   129  }
   130  
   131  // Expected response object from the external CIEPS service.
   132  //
   133  // When parsing, Vault will disallow unknown fields, failing the
   134  // parse if unknown fields are sent.
   135  type CIEPSResponse struct {
   136  	UUID              string            `json:"request_uuid"`
   137  	Error             string            `json:"error,omitempty"`
   138  	Warnings          []string          `json:"warnings,omitempty"`
   139  	Certificate       string            `json:"certificate"`
   140  	ParsedCertificate *x509.Certificate `json:"-"`
   141  	IssuerRef         string            `json:"issuer_ref"`
   142  	StoreCert         bool              `json:"store_certificate"`
   143  	GenerateLease     bool              `json:"generate_lease"`
   144  }
   145  
   146  func (c *CIEPSResponse) MarshalCertificate() error {
   147  	if c.ParsedCertificate == nil || len(c.ParsedCertificate.Raw) == 0 {
   148  		return fmt.Errorf("no certificate present")
   149  	}
   150  
   151  	pem := pem.EncodeToMemory(&pem.Block{
   152  		Type:  "CERTIFICATE",
   153  		Bytes: c.ParsedCertificate.Raw,
   154  	})
   155  	if len(pem) == 0 {
   156  		return fmt.Errorf("failed to generate PEM: no body")
   157  	}
   158  	c.Certificate = string(pem)
   159  
   160  	return nil
   161  }