mirror of
https://github.com/hashicorp/vault.git
synced 2026-05-05 20:36:26 +02:00
Add SDK CIEPS changes (#21974)
* OSS: Add standard CIEPS request/response structs Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * OSS: Add support for parsing TLS-related values Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> --------- Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>
This commit is contained in:
parent
245430215c
commit
6d9e181cf3
161
sdk/helper/certutil/cieps.go
Normal file
161
sdk/helper/certutil/cieps.go
Normal file
@ -0,0 +1,161 @@
|
||||
// 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 NotAfterBehavior `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"`
|
||||
IsDRSecondary bool `json:"vault_is_disaster_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"`
|
||||
Warnings []string `json:"warnings"`
|
||||
Certificate string `json:"certificate"`
|
||||
ParsedCertificate *x509.Certificate `json:"-"`
|
||||
IssuerRef string `json:"issuer_ref,omitempty"`
|
||||
StoreCert bool `json:"store_certificate"`
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@ -13,6 +13,7 @@ import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha1"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
@ -304,6 +305,18 @@ func ParsePEMBundle(pemBundle string) (*ParsedCertBundle, error) {
|
||||
return parsedBundle, nil
|
||||
}
|
||||
|
||||
func (p *ParsedCertBundle) ToTLSCertificate() tls.Certificate {
|
||||
var cert tls.Certificate
|
||||
cert.Certificate = append(cert.Certificate, p.CertificateBytes)
|
||||
cert.Leaf = p.Certificate
|
||||
cert.PrivateKey = p.PrivateKey
|
||||
for _, ca := range p.CAChain {
|
||||
cert.Certificate = append(cert.Certificate, ca.Bytes)
|
||||
}
|
||||
|
||||
return cert
|
||||
}
|
||||
|
||||
// GeneratePrivateKey generates a private key with the specified type and key bits.
|
||||
func GeneratePrivateKey(keyType string, keyBits int, container ParsedPrivateKeyContainer) error {
|
||||
return generatePrivateKey(keyType, keyBits, container, nil)
|
||||
@ -1292,7 +1305,7 @@ func NewCertPool(reader io.Reader) (*x509.CertPool, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certs, err := parseCertsPEM(pemBlock)
|
||||
certs, err := ParseCertsPEM(pemBlock)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading certs: %s", err)
|
||||
}
|
||||
@ -1303,9 +1316,9 @@ func NewCertPool(reader io.Reader) (*x509.CertPool, error) {
|
||||
return pool, nil
|
||||
}
|
||||
|
||||
// parseCertsPEM returns the x509.Certificates contained in the given PEM-encoded byte array
|
||||
// ParseCertsPEM returns the x509.Certificates contained in the given PEM-encoded byte array
|
||||
// Returns an error if a certificate could not be parsed, or if the data does not contain any certificates
|
||||
func parseCertsPEM(pemCerts []byte) ([]*x509.Certificate, error) {
|
||||
func ParseCertsPEM(pemCerts []byte) ([]*x509.Certificate, error) {
|
||||
ok := false
|
||||
certs := []*x509.Certificate{}
|
||||
for len(pemCerts) > 0 {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user