vault/builtin/logical/database/credentials.go
hashicorp-copywrite[bot] 0b12cdcfd1
[COMPLIANCE] License changes (#22290)
* Adding explicit MPL license for sub-package.

This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository.

* Adding explicit MPL license for sub-package.

This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository.

* Updating the license from MPL to Business Source License.

Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at https://hashi.co/bsl-blog, FAQ at www.hashicorp.com/licensing-faq, and details of the license at www.hashicorp.com/bsl.

* add missing license headers

* Update copyright file headers to BUS-1.1

* Fix test that expected exact offset on hcl file

---------

Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com>
Co-authored-by: Sarah Thompson <sthompson@hashicorp.com>
Co-authored-by: Brian Kassouf <bkassouf@hashicorp.com>
2023-08-10 18:14:03 -07:00

392 lines
11 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package database
import (
"context"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"io"
"strings"
"time"
"github.com/hashicorp/vault/helper/random"
"github.com/hashicorp/vault/sdk/database/dbplugin/v5"
"github.com/hashicorp/vault/sdk/helper/certutil"
"github.com/hashicorp/vault/sdk/helper/template"
"github.com/mitchellh/mapstructure"
)
// passwordGenerator generates password credentials.
// A zero value passwordGenerator is usable.
type passwordGenerator struct {
// PasswordPolicy is the named password policy used to generate passwords.
// If empty (default), a random string of 20 characters will be generated.
PasswordPolicy string `mapstructure:"password_policy,omitempty"`
}
// newPasswordGenerator returns a new passwordGenerator using the given config.
// Default values will be set on the returned passwordGenerator if not provided
// in the config.
func newPasswordGenerator(config map[string]interface{}) (passwordGenerator, error) {
var pg passwordGenerator
if err := mapstructure.WeakDecode(config, &pg); err != nil {
return pg, err
}
return pg, nil
}
// Generate generates a password credential using the configured password policy.
// Returns the generated password or an error.
func (pg passwordGenerator) generate(ctx context.Context, b *databaseBackend, wrapper databaseVersionWrapper) (string, error) {
if !wrapper.isV5() && !wrapper.isV4() {
return "", fmt.Errorf("no underlying database specified")
}
// The database plugin generates the password if its interface is v4
if wrapper.isV4() {
password, err := wrapper.v4.GenerateCredentials(ctx)
if err != nil {
return "", err
}
return password, nil
}
if pg.PasswordPolicy == "" {
return random.DefaultStringGenerator.Generate(ctx, b.GetRandomReader())
}
return b.System().GeneratePasswordFromPolicy(ctx, pg.PasswordPolicy)
}
// configMap returns the configuration of the passwordGenerator
// as a map from string to string.
func (pg passwordGenerator) configMap() (map[string]interface{}, error) {
config := make(map[string]interface{})
if err := mapstructure.WeakDecode(pg, &config); err != nil {
return nil, err
}
return config, nil
}
// rsaKeyGenerator generates RSA key pair credentials.
// A zero value rsaKeyGenerator is usable.
type rsaKeyGenerator struct {
// Format is the output format of the generated private key.
// Options include: 'pkcs8' (default)
Format string `mapstructure:"format,omitempty"`
// KeyBits is the bit size of the RSA key to generate.
// Options include: 2048 (default), 3072, and 4096
KeyBits int `mapstructure:"key_bits,omitempty"`
}
// newRSAKeyGenerator returns a new rsaKeyGenerator using the given config.
// Default values will be set on the returned rsaKeyGenerator if not provided
// in the given config.
func newRSAKeyGenerator(config map[string]interface{}) (rsaKeyGenerator, error) {
var kg rsaKeyGenerator
if err := mapstructure.WeakDecode(config, &kg); err != nil {
return kg, err
}
switch strings.ToLower(kg.Format) {
case "":
kg.Format = "pkcs8"
case "pkcs8":
default:
return kg, fmt.Errorf("invalid format: %v", kg.Format)
}
switch kg.KeyBits {
case 0:
kg.KeyBits = 2048
case 2048, 3072, 4096:
default:
return kg, fmt.Errorf("invalid key_bits: %v", kg.KeyBits)
}
return kg, nil
}
// Generate generates an RSA key pair. Returns a PEM-encoded, PKIX marshaled
// public key and a PEM-encoded private key marshaled into the configuration
// format (in that order) or an error.
func (kg *rsaKeyGenerator) generate(r io.Reader) ([]byte, []byte, error) {
reader := rand.Reader
if r != nil {
reader = r
}
var keyBits int
switch kg.KeyBits {
case 0:
keyBits = 2048
case 2048, 3072, 4096:
keyBits = kg.KeyBits
default:
return nil, nil, fmt.Errorf("invalid key_bits: %v", kg.KeyBits)
}
key, err := rsa.GenerateKey(reader, keyBits)
if err != nil {
return nil, nil, err
}
public, err := x509.MarshalPKIXPublicKey(key.Public())
if err != nil {
return nil, nil, err
}
var private []byte
switch strings.ToLower(kg.Format) {
case "", "pkcs8":
private, err = x509.MarshalPKCS8PrivateKey(key)
if err != nil {
return nil, nil, err
}
default:
return nil, nil, fmt.Errorf("invalid format: %v", kg.Format)
}
publicBlock := &pem.Block{
Type: "PUBLIC KEY",
Bytes: public,
}
privateBlock := &pem.Block{
Type: "PRIVATE KEY",
Bytes: private,
}
return pem.EncodeToMemory(publicBlock), pem.EncodeToMemory(privateBlock), nil
}
// configMap returns the configuration of the rsaKeyGenerator
// as a map from string to string.
func (kg rsaKeyGenerator) configMap() (map[string]interface{}, error) {
config := make(map[string]interface{})
if err := mapstructure.WeakDecode(kg, &config); err != nil {
return nil, err
}
return config, nil
}
type ClientCertificateGenerator struct {
// CommonNameTemplate is username template to be used for the client certificate common name.
CommonNameTemplate string `mapstructure:"common_name_template,omitempty"`
// CAPrivateKey is the PEM-encoded private key for the given ca_cert.
CAPrivateKey string `mapstructure:"ca_private_key,omitempty"`
// CACert is the PEM-encoded CA certificate.
CACert string `mapstructure:"ca_cert,omitempty"`
// KeyType specifies the desired key type.
// Options include: 'rsa', 'ed25519', 'ec'.
KeyType string `mapstructure:"key_type,omitempty"`
// KeyBits is the number of bits to use for the generated keys.
// Options include: with key_type=rsa, 2048 (default), 3072, 4096;
// With key_type=ec, allowed values are: 224, 256 (default), 384, 521;
// Ignored with key_type=ed25519.
KeyBits int `mapstructure:"key_bits,omitempty"`
// SignatureBits is the number of bits to use in the signature algorithm.
// Options include: 256 (default), 384, 512.
SignatureBits int `mapstructure:"signature_bits,omitempty"`
parsedCABundle *certutil.ParsedCertBundle
cnProducer template.StringTemplate
}
// newClientCertificateGenerator returns a new ClientCertificateGenerator
// using the given config. Default values will be set on the returned
// ClientCertificateGenerator if not provided in the config.
func newClientCertificateGenerator(config map[string]interface{}) (ClientCertificateGenerator, error) {
var cg ClientCertificateGenerator
if err := mapstructure.WeakDecode(config, &cg); err != nil {
return cg, err
}
switch cg.KeyType {
case "rsa":
switch cg.KeyBits {
case 0:
cg.KeyBits = 2048
case 2048, 3072, 4096:
default:
return cg, fmt.Errorf("invalid key_bits")
}
case "ec":
switch cg.KeyBits {
case 0:
cg.KeyBits = 256
case 224, 256, 384, 521:
default:
return cg, fmt.Errorf("invalid key_bits")
}
case "ed25519":
// key_bits ignored
default:
return cg, fmt.Errorf("invalid key_type")
}
switch cg.SignatureBits {
case 0:
cg.SignatureBits = 256
case 256, 384, 512:
default:
return cg, fmt.Errorf("invalid signature_bits")
}
if cg.CommonNameTemplate == "" {
return cg, fmt.Errorf("missing required common_name_template")
}
// Validate the common name template
t, err := template.NewTemplate(template.Template(cg.CommonNameTemplate))
if err != nil {
return cg, fmt.Errorf("failed to create template: %w", err)
}
_, err = t.Generate(dbplugin.UsernameMetadata{})
if err != nil {
return cg, fmt.Errorf("invalid common_name_template: %w", err)
}
cg.cnProducer = t
if cg.CACert == "" {
return cg, fmt.Errorf("missing required ca_cert")
}
if cg.CAPrivateKey == "" {
return cg, fmt.Errorf("missing required ca_private_key")
}
parsedBundle, err := certutil.ParsePEMBundle(strings.Join([]string{cg.CACert, cg.CAPrivateKey}, "\n"))
if err != nil {
return cg, err
}
if parsedBundle.PrivateKey == nil {
return cg, fmt.Errorf("private key not found in the PEM bundle")
}
if parsedBundle.PrivateKeyType == certutil.UnknownPrivateKey {
return cg, fmt.Errorf("unknown private key found in the PEM bundle")
}
if parsedBundle.Certificate == nil {
return cg, fmt.Errorf("certificate not found in the PEM bundle")
}
if !parsedBundle.Certificate.IsCA {
return cg, fmt.Errorf("the given certificate is not marked for CA use")
}
if !parsedBundle.Certificate.BasicConstraintsValid {
return cg, fmt.Errorf("the given certificate does not meet basic constraints for CA use")
}
certBundle, err := parsedBundle.ToCertBundle()
if err != nil {
return cg, fmt.Errorf("error converting raw values into cert bundle: %w", err)
}
parsedCABundle, err := certBundle.ToParsedCertBundle()
if err != nil {
return cg, fmt.Errorf("failed to parse cert bundle: %w", err)
}
cg.parsedCABundle = parsedCABundle
return cg, nil
}
func (cg *ClientCertificateGenerator) generate(r io.Reader, expiration time.Time, userMeta dbplugin.UsernameMetadata) (*certutil.CertBundle, string, error) {
commonName, err := cg.cnProducer.Generate(userMeta)
if err != nil {
return nil, "", err
}
// Set defaults
keyBits := cg.KeyBits
signatureBits := cg.SignatureBits
switch cg.KeyType {
case "rsa":
if keyBits == 0 {
keyBits = 2048
}
if signatureBits == 0 {
signatureBits = 256
}
case "ec":
if keyBits == 0 {
keyBits = 256
}
if signatureBits == 0 {
if keyBits == 224 {
signatureBits = 256
} else {
signatureBits = keyBits
}
}
case "ed25519":
// key_bits ignored
if signatureBits == 0 {
signatureBits = 256
}
}
subject := pkix.Name{
CommonName: commonName,
// Additional subject DN options intentionally omitted for now
}
creation := &certutil.CreationBundle{
Params: &certutil.CreationParameters{
Subject: subject,
KeyType: cg.KeyType,
KeyBits: cg.KeyBits,
SignatureBits: cg.SignatureBits,
NotAfter: expiration,
KeyUsage: x509.KeyUsageDigitalSignature,
ExtKeyUsage: certutil.ClientAuthExtKeyUsage,
BasicConstraintsValidForNonCA: false,
NotBeforeDuration: 30 * time.Second,
URLs: &certutil.URLEntries{
IssuingCertificates: []string{},
CRLDistributionPoints: []string{},
OCSPServers: []string{},
},
},
SigningBundle: &certutil.CAInfoBundle{
ParsedCertBundle: *cg.parsedCABundle,
URLs: &certutil.URLEntries{
IssuingCertificates: []string{},
CRLDistributionPoints: []string{},
OCSPServers: []string{},
},
},
}
parsedClientBundle, err := certutil.CreateCertificateWithRandomSource(creation, r)
if err != nil {
return nil, "", fmt.Errorf("unable to generate client certificate: %w", err)
}
cb, err := parsedClientBundle.ToCertBundle()
if err != nil {
return nil, "", fmt.Errorf("error converting raw cert bundle to cert bundle: %w", err)
}
return cb, subject.String(), nil
}
// configMap returns the configuration of the ClientCertificateGenerator
// as a map from string to string.
func (cg ClientCertificateGenerator) configMap() (map[string]interface{}, error) {
config := make(map[string]interface{})
if err := mapstructure.WeakDecode(cg, &config); err != nil {
return nil, err
}
return config, nil
}