| // Copyright (c) HashiCorp, Inc. |
| // SPDX-License-Identifier: MPL-2.0 |
| |
| package configutil |
| |
| import ( |
| "context" |
| "crypto/rand" |
| "fmt" |
| "io" |
| "strings" |
| |
| "github.com/hashicorp/errwrap" |
| "github.com/hashicorp/go-hclog" |
| wrapping "github.com/hashicorp/go-kms-wrapping/v2" |
| aeadwrapper "github.com/hashicorp/go-kms-wrapping/wrappers/aead/v2" |
| "github.com/hashicorp/go-kms-wrapping/wrappers/alicloudkms/v2" |
| "github.com/hashicorp/go-kms-wrapping/wrappers/awskms/v2" |
| "github.com/hashicorp/go-kms-wrapping/wrappers/azurekeyvault/v2" |
| "github.com/hashicorp/go-kms-wrapping/wrappers/gcpckms/v2" |
| "github.com/hashicorp/go-kms-wrapping/wrappers/ocikms/v2" |
| "github.com/hashicorp/go-kms-wrapping/wrappers/transit/v2" |
| "github.com/hashicorp/go-multierror" |
| "github.com/hashicorp/go-secure-stdlib/parseutil" |
| "github.com/hashicorp/hcl" |
| "github.com/hashicorp/hcl/hcl/ast" |
| "github.com/hashicorp/vault/sdk/logical" |
| ) |
| |
| var ( |
| ConfigureWrapper = configureWrapper |
| CreateSecureRandomReaderFunc = createSecureRandomReader |
| ) |
| |
| // Entropy contains Entropy configuration for the server |
| type EntropyMode int |
| |
| const ( |
| EntropyUnknown EntropyMode = iota |
| EntropyAugmentation |
| ) |
| |
| type Entropy struct { |
| Mode EntropyMode |
| } |
| |
| // KMS contains KMS configuration for the server |
| type KMS struct { |
| UnusedKeys []string `hcl:",unusedKeys"` |
| Type string |
| // Purpose can be used to allow a string-based specification of what this |
| // KMS is designated for, in situations where we want to allow more than |
| // one KMS to be specified |
| Purpose []string `hcl:"-"` |
| |
| Disabled bool |
| Config map[string]string |
| } |
| |
| func (k *KMS) GoString() string { |
| return fmt.Sprintf("*%#v", *k) |
| } |
| |
| func parseKMS(result *[]*KMS, list *ast.ObjectList, blockName string, maxKMS int) error { |
| if len(list.Items) > maxKMS { |
| return fmt.Errorf("only two or less %q blocks are permitted", blockName) |
| } |
| |
| seals := make([]*KMS, 0, len(list.Items)) |
| for _, item := range list.Items { |
| key := blockName |
| if len(item.Keys) > 0 { |
| key = item.Keys[0].Token.Value().(string) |
| } |
| |
| // We first decode into a map[string]interface{} because purpose isn't |
| // necessarily a string. Then we migrate everything else over to |
| // map[string]string and error if it doesn't work. |
| var m map[string]interface{} |
| if err := hcl.DecodeObject(&m, item.Val); err != nil { |
| return multierror.Prefix(err, fmt.Sprintf("%s.%s:", blockName, key)) |
| } |
| |
| var purpose []string |
| var err error |
| if v, ok := m["purpose"]; ok { |
| if purpose, err = parseutil.ParseCommaStringSlice(v); err != nil { |
| return multierror.Prefix(fmt.Errorf("unable to parse 'purpose' in kms type %q: %w", key, err), fmt.Sprintf("%s.%s:", blockName, key)) |
| } |
| for i, p := range purpose { |
| purpose[i] = strings.ToLower(p) |
| } |
| delete(m, "purpose") |
| } |
| |
| var disabled bool |
| if v, ok := m["disabled"]; ok { |
| disabled, err = parseutil.ParseBool(v) |
| if err != nil { |
| return multierror.Prefix(err, fmt.Sprintf("%s.%s:", blockName, key)) |
| } |
| delete(m, "disabled") |
| } |
| |
| strMap := make(map[string]string, len(m)) |
| for k, v := range m { |
| s, err := parseutil.ParseString(v) |
| if err != nil { |
| return multierror.Prefix(err, fmt.Sprintf("%s.%s:", blockName, key)) |
| } |
| strMap[k] = s |
| } |
| |
| seal := &KMS{ |
| Type: strings.ToLower(key), |
| Purpose: purpose, |
| Disabled: disabled, |
| } |
| if len(strMap) > 0 { |
| seal.Config = strMap |
| } |
| seals = append(seals, seal) |
| } |
| |
| *result = append(*result, seals...) |
| |
| return nil |
| } |
| |
| func ParseKMSes(d string) ([]*KMS, error) { |
| // Parse! |
| obj, err := hcl.Parse(d) |
| if err != nil { |
| return nil, err |
| } |
| |
| // Start building the result |
| var result struct { |
| Seals []*KMS `hcl:"-"` |
| } |
| |
| if err := hcl.DecodeObject(&result, obj); err != nil { |
| return nil, err |
| } |
| |
| list, ok := obj.Node.(*ast.ObjectList) |
| if !ok { |
| return nil, fmt.Errorf("error parsing: file doesn't contain a root object") |
| } |
| |
| if o := list.Filter("seal"); len(o.Items) > 0 { |
| if err := parseKMS(&result.Seals, o, "seal", 3); err != nil { |
| return nil, fmt.Errorf("error parsing 'seal': %w", err) |
| } |
| } |
| |
| if o := list.Filter("kms"); len(o.Items) > 0 { |
| if err := parseKMS(&result.Seals, o, "kms", 3); err != nil { |
| return nil, fmt.Errorf("error parsing 'kms': %w", err) |
| } |
| } |
| |
| return result.Seals, nil |
| } |
| |
| func configureWrapper(configKMS *KMS, infoKeys *[]string, info *map[string]string, logger hclog.Logger, opts ...wrapping.Option) (wrapping.Wrapper, error) { |
| var wrapper wrapping.Wrapper |
| var kmsInfo map[string]string |
| var err error |
| |
| switch wrapping.WrapperType(configKMS.Type) { |
| case wrapping.WrapperTypeShamir: |
| return nil, nil |
| |
| case wrapping.WrapperTypeAead: |
| wrapper, kmsInfo, err = GetAEADKMSFunc(configKMS, opts...) |
| |
| case wrapping.WrapperTypeAliCloudKms: |
| wrapper, kmsInfo, err = GetAliCloudKMSFunc(configKMS, opts...) |
| |
| case wrapping.WrapperTypeAwsKms: |
| wrapper, kmsInfo, err = GetAWSKMSFunc(configKMS, opts...) |
| |
| case wrapping.WrapperTypeAzureKeyVault: |
| wrapper, kmsInfo, err = GetAzureKeyVaultKMSFunc(configKMS, opts...) |
| |
| case wrapping.WrapperTypeGcpCkms: |
| wrapper, kmsInfo, err = GetGCPCKMSKMSFunc(configKMS, opts...) |
| |
| case wrapping.WrapperTypeOciKms: |
| if keyId, ok := configKMS.Config["key_id"]; ok { |
| opts = append(opts, wrapping.WithKeyId(keyId)) |
| } |
| wrapper, kmsInfo, err = GetOCIKMSKMSFunc(configKMS, opts...) |
| case wrapping.WrapperTypeTransit: |
| wrapper, kmsInfo, err = GetTransitKMSFunc(configKMS, opts...) |
| |
| case wrapping.WrapperTypePkcs11: |
| return nil, fmt.Errorf("KMS type 'pkcs11' requires the Vault Enterprise HSM binary") |
| |
| default: |
| return nil, fmt.Errorf("Unknown KMS type %q", configKMS.Type) |
| } |
| |
| if err != nil { |
| return nil, err |
| } |
| |
| if infoKeys != nil && info != nil { |
| for k, v := range kmsInfo { |
| *infoKeys = append(*infoKeys, k) |
| (*info)[k] = v |
| } |
| } |
| |
| return wrapper, nil |
| } |
| |
| func GetAEADKMSFunc(kms *KMS, opts ...wrapping.Option) (wrapping.Wrapper, map[string]string, error) { |
| wrapper := aeadwrapper.NewWrapper() |
| wrapperInfo, err := wrapper.SetConfig(context.Background(), append(opts, wrapping.WithConfigMap(kms.Config))...) |
| if err != nil { |
| return nil, nil, err |
| } |
| info := make(map[string]string) |
| if wrapperInfo != nil { |
| str := "AEAD Type" |
| if len(kms.Purpose) > 0 { |
| str = fmt.Sprintf("%v %s", kms.Purpose, str) |
| } |
| info[str] = wrapperInfo.Metadata["aead_type"] |
| } |
| return wrapper, info, nil |
| } |
| |
| func GetAliCloudKMSFunc(kms *KMS, opts ...wrapping.Option) (wrapping.Wrapper, map[string]string, error) { |
| wrapper := alicloudkms.NewWrapper() |
| wrapperInfo, err := wrapper.SetConfig(context.Background(), append(opts, wrapping.WithConfigMap(kms.Config))...) |
| if err != nil { |
| // If the error is any other than logical.KeyNotFoundError, return the error |
| if !errwrap.ContainsType(err, new(logical.KeyNotFoundError)) { |
| return nil, nil, err |
| } |
| } |
| info := make(map[string]string) |
| if wrapperInfo != nil { |
| info["AliCloud KMS Region"] = wrapperInfo.Metadata["region"] |
| info["AliCloud KMS KeyID"] = wrapperInfo.Metadata["kms_key_id"] |
| if domain, ok := wrapperInfo.Metadata["domain"]; ok { |
| info["AliCloud KMS Domain"] = domain |
| } |
| } |
| return wrapper, info, nil |
| } |
| |
| var GetAWSKMSFunc = func(kms *KMS, opts ...wrapping.Option) (wrapping.Wrapper, map[string]string, error) { |
| wrapper := awskms.NewWrapper() |
| wrapperInfo, err := wrapper.SetConfig(context.Background(), append(opts, wrapping.WithConfigMap(kms.Config))...) |
| if err != nil { |
| // If the error is any other than logical.KeyNotFoundError, return the error |
| if !errwrap.ContainsType(err, new(logical.KeyNotFoundError)) { |
| return nil, nil, err |
| } |
| } |
| info := make(map[string]string) |
| if wrapperInfo != nil { |
| info["AWS KMS Region"] = wrapperInfo.Metadata["region"] |
| info["AWS KMS KeyID"] = wrapperInfo.Metadata["kms_key_id"] |
| if endpoint, ok := wrapperInfo.Metadata["endpoint"]; ok { |
| info["AWS KMS Endpoint"] = endpoint |
| } |
| } |
| return wrapper, info, nil |
| } |
| |
| func GetAzureKeyVaultKMSFunc(kms *KMS, opts ...wrapping.Option) (wrapping.Wrapper, map[string]string, error) { |
| wrapper := azurekeyvault.NewWrapper() |
| wrapperInfo, err := wrapper.SetConfig(context.Background(), append(opts, wrapping.WithConfigMap(kms.Config))...) |
| if err != nil { |
| // If the error is any other than logical.KeyNotFoundError, return the error |
| if !errwrap.ContainsType(err, new(logical.KeyNotFoundError)) { |
| return nil, nil, err |
| } |
| } |
| info := make(map[string]string) |
| if wrapperInfo != nil { |
| info["Azure Environment"] = wrapperInfo.Metadata["environment"] |
| info["Azure Vault Name"] = wrapperInfo.Metadata["vault_name"] |
| info["Azure Key Name"] = wrapperInfo.Metadata["key_name"] |
| } |
| return wrapper, info, nil |
| } |
| |
| func GetGCPCKMSKMSFunc(kms *KMS, opts ...wrapping.Option) (wrapping.Wrapper, map[string]string, error) { |
| wrapper := gcpckms.NewWrapper() |
| wrapperInfo, err := wrapper.SetConfig(context.Background(), append(opts, wrapping.WithConfigMap(kms.Config))...) |
| if err != nil { |
| // If the error is any other than logical.KeyNotFoundError, return the error |
| if !errwrap.ContainsType(err, new(logical.KeyNotFoundError)) { |
| return nil, nil, err |
| } |
| } |
| info := make(map[string]string) |
| if wrapperInfo != nil { |
| info["GCP KMS Project"] = wrapperInfo.Metadata["project"] |
| info["GCP KMS Region"] = wrapperInfo.Metadata["region"] |
| info["GCP KMS Key Ring"] = wrapperInfo.Metadata["key_ring"] |
| info["GCP KMS Crypto Key"] = wrapperInfo.Metadata["crypto_key"] |
| } |
| return wrapper, info, nil |
| } |
| |
| func GetOCIKMSKMSFunc(kms *KMS, opts ...wrapping.Option) (wrapping.Wrapper, map[string]string, error) { |
| wrapper := ocikms.NewWrapper() |
| wrapperInfo, err := wrapper.SetConfig(context.Background(), append(opts, wrapping.WithConfigMap(kms.Config))...) |
| if err != nil { |
| return nil, nil, err |
| } |
| info := make(map[string]string) |
| if wrapperInfo != nil { |
| info["OCI KMS KeyID"] = wrapperInfo.Metadata[ocikms.KmsConfigKeyId] |
| info["OCI KMS Crypto Endpoint"] = wrapperInfo.Metadata[ocikms.KmsConfigCryptoEndpoint] |
| info["OCI KMS Management Endpoint"] = wrapperInfo.Metadata[ocikms.KmsConfigManagementEndpoint] |
| info["OCI KMS Principal Type"] = wrapperInfo.Metadata["principal_type"] |
| } |
| return wrapper, info, nil |
| } |
| |
| var GetTransitKMSFunc = func(kms *KMS, opts ...wrapping.Option) (wrapping.Wrapper, map[string]string, error) { |
| wrapper := transit.NewWrapper() |
| wrapperInfo, err := wrapper.SetConfig(context.Background(), append(opts, wrapping.WithConfigMap(kms.Config))...) |
| if err != nil { |
| // If the error is any other than logical.KeyNotFoundError, return the error |
| if !errwrap.ContainsType(err, new(logical.KeyNotFoundError)) { |
| return nil, nil, err |
| } |
| } |
| info := make(map[string]string) |
| if wrapperInfo != nil { |
| info["Transit Address"] = wrapperInfo.Metadata["address"] |
| info["Transit Mount Path"] = wrapperInfo.Metadata["mount_path"] |
| info["Transit Key Name"] = wrapperInfo.Metadata["key_name"] |
| if namespace, ok := wrapperInfo.Metadata["namespace"]; ok { |
| info["Transit Namespace"] = namespace |
| } |
| } |
| return wrapper, info, nil |
| } |
| |
| func createSecureRandomReader(conf *SharedConfig, wrapper wrapping.Wrapper) (io.Reader, error) { |
| return rand.Reader, nil |
| } |