diff --git a/command/server/config.go b/command/server/config.go index 34e4848004..53ce91672c 100644 --- a/command/server/config.go +++ b/command/server/config.go @@ -494,13 +494,17 @@ func CheckConfig(c *Config, e error) (*Config, error) { return c, e } - if len(c.Seals) == 2 { - switch { - case c.Seals[0].Disabled && c.Seals[1].Disabled: - return nil, errors.New("seals: two seals provided but both are disabled") - case !c.Seals[0].Disabled && !c.Seals[1].Disabled: - return nil, errors.New("seals: two seals provided but neither is disabled") + if err := c.checkSealConfig(); err != nil { + return nil, err + } + + sealMap := make(map[string]*configutil.KMS) + for _, seal := range c.Seals { + if _, ok := sealMap[seal.Name]; ok { + return nil, errors.New("seals: seal names must be unique") } + + sealMap[seal.Name] = seal } return c, nil diff --git a/command/server/config_test_helpers.go b/command/server/config_test_helpers.go index f5136449ce..cc653af320 100644 --- a/command/server/config_test_helpers.go +++ b/command/server/config_test_helpers.go @@ -102,18 +102,22 @@ func testLoadConfigFile_topLevel(t *testing.T, entropy *configutil.Entropy) { Seals: []*configutil.KMS{ { Type: "nopurpose", + Name: "nopurpose", }, { Type: "stringpurpose", Purpose: []string{"foo"}, + Name: "stringpurpose", }, { Type: "commastringpurpose", Purpose: []string{"foo", "bar"}, + Name: "commastringpurpose", }, { Type: "slicepurpose", Purpose: []string{"zip", "zap"}, + Name: "slicepurpose", }, }, }, @@ -777,6 +781,7 @@ func testConfig_Sanitized(t *testing.T) { map[string]interface{}{ "disabled": false, "type": "awskms", + "name": "awskms", }, }, "storage": map[string]interface{}{ @@ -1086,6 +1091,7 @@ func testParseSeals(t *testing.T) { "default_hmac_key_label": "vault-hsm-hmac-key", "generate_key": "true", }, + Name: "pkcs11", }, { Type: "pkcs11", @@ -1102,6 +1108,7 @@ func testParseSeals(t *testing.T) { "default_hmac_key_label": "vault-hsm-hmac-key", "generate_key": "true", }, + Name: "pkcs11", }, }, }, diff --git a/command/server/config_util.go b/command/server/config_util.go index 3570b9a59b..852d55564a 100644 --- a/command/server/config_util.go +++ b/command/server/config_util.go @@ -6,6 +6,9 @@ package server import ( + "errors" + "fmt" + "github.com/hashicorp/hcl/hcl/ast" ) @@ -23,3 +26,30 @@ func (ec entConfig) Merge(ec2 entConfig) entConfig { func (ec entConfig) Sanitized() map[string]interface{} { return nil } + +func (c *Config) checkSealConfig() error { + if len(c.Seals) == 0 { + return nil + } + + if len(c.Seals) > 2 { + return fmt.Errorf("seals: at most 2 seals can be provided: received %d", len(c.Seals)) + } + + disabledSeals := 0 + for _, seal := range c.Seals { + if seal.Disabled { + disabledSeals++ + } + } + + if len(c.Seals) > 1 && disabledSeals == len(c.Seals) { + return errors.New("seals: seals provided but all are disabled") + } + + if disabledSeals < len(c.Seals)-1 { + return errors.New("seals: only one seal can be enabled") + } + + return nil +} diff --git a/internalshared/configutil/config.go b/internalshared/configutil/config.go index 99777229f0..161dd0bd3b 100644 --- a/internalshared/configutil/config.go +++ b/internalshared/configutil/config.go @@ -99,7 +99,7 @@ func ParseConfig(d string) (*SharedConfig, error) { if o := list.Filter("seal"); len(o.Items) > 0 { result.found("seal", "Seal") - if err := parseKMS(&result.Seals, o, "seal", 3); err != nil { + if err := parseKMS(&result.Seals, o, "seal", 5); err != nil { return nil, fmt.Errorf("error parsing 'seal': %w", err) } } @@ -215,7 +215,12 @@ func (c *SharedConfig) Sanitized() map[string]interface{} { cleanSeal := map[string]interface{}{ "type": s.Type, "disabled": s.Disabled, + "name": s.Name, } + if s.Priority > 0 { + cleanSeal["priority"] = s.Priority + } + sanitizedSeals = append(sanitizedSeals, cleanSeal) } result["seals"] = sanitizedSeals diff --git a/internalshared/configutil/env_var_util.go b/internalshared/configutil/env_var_util.go new file mode 100644 index 0000000000..0e1dfc768c --- /dev/null +++ b/internalshared/configutil/env_var_util.go @@ -0,0 +1,63 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package configutil + +var ( + AliCloudKMSEnvVars = map[string]string{ + "ALICLOUD_REGION": "region", + "ALICLOUD_DOMAIN": "domain", + "ALICLOUD_ACCESS_KEY": "access_key", + "ALICLOUD_SECRET_KEY": "secret_key", + "VAULT_ALICLOUDKMS_SEAL_KEY_ID": "kms_key_id", + } + + AWSKMSEnvVars = map[string]string{ + "AWS_REGION": "region", + "AWS_DEFAULT_REGION": "region", + "AWS_ACCESS_KEY_ID": "access_key", + "AWS_SESSION_TOKEN": "session_token", + "AWS_SECRET_ACCESS_KEY": "secret_key", + "VAULT_AWSKMS_SEAL_KEY_ID": "kms_key_id", + "AWS_KMS_ENDPOINT": "endpoint", + } + + AzureEnvVars = map[string]string{ + "AZURE_TENANT_ID": "tenant_id", + "AZURE_CLIENT_ID": "client_id", + "AZURE_CLIENT_SECRET": "client_secret", + "AZURE_ENVIRONMENT": "environment", + "VAULT_AZUREKEYVAULT_VAULT_NAME": "vault_name", + "VAULT_AZUREKEYVAULT_KEY_NAME": "key_name", + "AZURE_AD_RESOURCE": "resource", + } + + GCPCKMSEnvVars = map[string]string{ + "GOOGLE_CREDENTIALS": "credentials", + "GOOGLE_APPLICATION_CREDENTIALS": "credentials", + "GOOGLE_PROJECT": "project", + "GOOGLE_REGION": "region", + "VAULT_GCPCKMS_SEAL_KEY_RING": "key_ring", + "VAULT_GCPCKMS_SEAL_CRYPTO_KEY": "crypto_key", + } + + OCIKMSEnvVars = map[string]string{ + "VAULT_OCIKMS_SEAL_KEY_ID": "key_id", + "VAULT_OCIKMS_CRYPTO_ENDPOINT": "crypto_endpoint", + "VAULT_OCIKMS_MANAGEMENT_ENDPOINT": "management_endpoint", + } + + TransitEnvVars = map[string]string{ + "VAULT_ADDR": "address", + "VAULT_TOKEN": "token", + "VAULT_TRANSIT_SEAL_KEY_NAME": "key_name", + "VAULT_TRANSIT_SEAL_MOUNT_PATH": "mount_path", + "VAULT_NAMESPACE": "namespace", + "VAULT_TRANSIT_SEAL_DISABLE_RENEWAL": "disable_renewal", + "VAULT_CACERT": "tls_ca_cert", + "VAULT_CLIENT_CERT": "tls_client_cert", + "VAULT_CLIENT_KEY": "tls_client_key", + "VAULT_TLS_SERVER_NAME": "tls_server_name", + "VAULT_SKIP_VERIFY": "tls_skip_verify", + } +) diff --git a/internalshared/configutil/kms.go b/internalshared/configutil/kms.go index 0250181249..333d7d07a0 100644 --- a/internalshared/configutil/kms.go +++ b/internalshared/configutil/kms.go @@ -8,6 +8,7 @@ import ( "crypto/rand" "fmt" "io" + "os" "strings" "github.com/hashicorp/errwrap" @@ -30,6 +31,7 @@ import ( var ( ConfigureWrapper = configureWrapper CreateSecureRandomReaderFunc = createSecureRandomReader + GetEnvConfigFunc = getEnvConfig ) // Entropy contains Entropy configuration for the server @@ -55,6 +57,9 @@ type KMS struct { Disabled bool Config map[string]string + + Priority int `hcl:"priority"` + Name string `hcl:"name"` } func (k *KMS) GoString() string { @@ -63,7 +68,7 @@ func (k *KMS) GoString() string { 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) + return fmt.Errorf("only %d or less %q blocks are permitted", maxKMS, blockName) } seals := make([]*KMS, 0, len(list.Items)) @@ -102,6 +107,28 @@ func parseKMS(result *[]*KMS, list *ast.ObjectList, blockName string, maxKMS int delete(m, "disabled") } + var priority int + if v, ok := m["priority"]; ok { + priority, err = parseutil.SafeParseInt(v) + if err != nil { + return multierror.Prefix(fmt.Errorf("unable to parse 'priority' in kms type %q: %w", key, err), fmt.Sprintf("%s.%s", blockName, key)) + } + delete(m, "priority") + + if priority < 1 { + return multierror.Prefix(fmt.Errorf("invalid priority in kms type %q: %d", key, priority), fmt.Sprintf("%s.%s", blockName, key)) + } + } + + name := strings.ToLower(key) + if v, ok := m["name"]; ok { + name, ok = v.(string) + if !ok { + return multierror.Prefix(fmt.Errorf("unable to parse 'name' in kms type %q: unexpected type %T", key, v), fmt.Sprintf("%s.%s", blockName, key)) + } + delete(m, "name") + } + strMap := make(map[string]string, len(m)) for k, v := range m { s, err := parseutil.ParseString(v) @@ -115,6 +142,8 @@ func parseKMS(result *[]*KMS, list *ast.ObjectList, blockName string, maxKMS int Type: strings.ToLower(key), Purpose: purpose, Disabled: disabled, + Priority: priority, + Name: name, } if len(strMap) > 0 { seal.Config = strMap @@ -168,6 +197,11 @@ func configureWrapper(configKMS *KMS, infoKeys *[]string, info *map[string]strin var kmsInfo map[string]string var err error + envConfig := GetEnvConfigFunc(configKMS) + for name, val := range envConfig { + configKMS.Config[name] = val + } + switch wrapping.WrapperType(configKMS.Type) { case wrapping.WrapperTypeShamir: return nil, nil @@ -235,7 +269,7 @@ func GetAEADKMSFunc(kms *KMS, opts ...wrapping.Option) (wrapping.Wrapper, map[st 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))...) + wrapperInfo, err := wrapper.SetConfig(context.Background(), append(opts, wrapping.WithDisallowEnvVars(true), 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)) { @@ -255,7 +289,7 @@ func GetAliCloudKMSFunc(kms *KMS, opts ...wrapping.Option) (wrapping.Wrapper, ma 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))...) + wrapperInfo, err := wrapper.SetConfig(context.Background(), append(opts, wrapping.WithDisallowEnvVars(true), 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)) { @@ -275,7 +309,7 @@ var GetAWSKMSFunc = func(kms *KMS, opts ...wrapping.Option) (wrapping.Wrapper, m 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))...) + wrapperInfo, err := wrapper.SetConfig(context.Background(), append(opts, wrapping.WithDisallowEnvVars(true), 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)) { @@ -293,7 +327,7 @@ func GetAzureKeyVaultKMSFunc(kms *KMS, opts ...wrapping.Option) (wrapping.Wrappe 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))...) + wrapperInfo, err := wrapper.SetConfig(context.Background(), append(opts, wrapping.WithDisallowEnvVars(true), 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)) { @@ -312,7 +346,7 @@ func GetGCPCKMSKMSFunc(kms *KMS, opts ...wrapping.Option) (wrapping.Wrapper, map 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))...) + wrapperInfo, err := wrapper.SetConfig(context.Background(), append(opts, wrapping.WithDisallowEnvVars(true), wrapping.WithConfigMap(kms.Config))...) if err != nil { return nil, nil, err } @@ -328,7 +362,7 @@ func GetOCIKMSKMSFunc(kms *KMS, opts ...wrapping.Option) (wrapping.Wrapper, map[ 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))...) + wrapperInfo, err := wrapper.SetConfig(context.Background(), append(opts, wrapping.WithDisallowEnvVars(true), 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)) { @@ -350,3 +384,34 @@ var GetTransitKMSFunc = func(kms *KMS, opts ...wrapping.Option) (wrapping.Wrappe func createSecureRandomReader(conf *SharedConfig, wrapper wrapping.Wrapper) (io.Reader, error) { return rand.Reader, nil } + +func getEnvConfig(kms *KMS) map[string]string { + envValues := make(map[string]string) + + var wrapperEnvVars map[string]string + switch wrapping.WrapperType(kms.Type) { + case wrapping.WrapperTypeAliCloudKms: + wrapperEnvVars = AliCloudKMSEnvVars + case wrapping.WrapperTypeAwsKms: + wrapperEnvVars = AWSKMSEnvVars + case wrapping.WrapperTypeAzureKeyVault: + wrapperEnvVars = AzureEnvVars + case wrapping.WrapperTypeGcpCkms: + wrapperEnvVars = GCPCKMSEnvVars + case wrapping.WrapperTypeOciKms: + wrapperEnvVars = OCIKMSEnvVars + case wrapping.WrapperTypeTransit: + wrapperEnvVars = TransitEnvVars + default: + return nil + } + + for envVar, configName := range wrapperEnvVars { + val := os.Getenv(envVar) + if val != "" { + envValues[configName] = val + } + } + + return envValues +} diff --git a/internalshared/configutil/kms_test.go b/internalshared/configutil/kms_test.go new file mode 100644 index 0000000000..b5a14f6f15 --- /dev/null +++ b/internalshared/configutil/kms_test.go @@ -0,0 +1,102 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package configutil + +import ( + "os" + "reflect" + "testing" +) + +func Test_getEnvConfig(t *testing.T) { + tests := []struct { + name string + kms *KMS + envVars map[string]string + want map[string]string + }{ + { + "AliCloud wrapper", + &KMS{ + Type: "alicloudkms", + Priority: 1, + }, + map[string]string{"ALICLOUD_REGION": "test_region", "ALICLOUD_DOMAIN": "test_domain", "ALICLOUD_ACCESS_KEY": "test_access_key", "ALICLOUD_SECRET_KEY": "test_secret_key", "VAULT_ALICLOUDKMS_SEAL_KEY_ID": "test_key_id"}, + map[string]string{"region": "test_region", "domain": "test_domain", "access_key": "test_access_key", "secret_key": "test_secret_key", "kms_key_id": "test_key_id"}, + }, + { + "AWS KMS wrapper", + &KMS{ + Type: "awskms", + Priority: 1, + }, + map[string]string{"AWS_REGION": "test_region", "AWS_ACCESS_KEY_ID": "test_access_key", "AWS_SECRET_ACCESS_KEY": "test_secret_key", "VAULT_AWSKMS_SEAL_KEY_ID": "test_key_id"}, + map[string]string{"region": "test_region", "access_key": "test_access_key", "secret_key": "test_secret_key", "kms_key_id": "test_key_id"}, + }, + { + "Azure KeyVault wrapper", + &KMS{ + Type: "azurekeyvault", + Priority: 1, + }, + map[string]string{"AZURE_TENANT_ID": "test_tenant_id", "AZURE_CLIENT_ID": "test_client_id", "AZURE_CLIENT_SECRET": "test_client_secret", "AZURE_ENVIRONMENT": "test_environment", "VAULT_AZUREKEYVAULT_VAULT_NAME": "test_vault_name", "VAULT_AZUREKEYVAULT_KEY_NAME": "test_key_name"}, + map[string]string{"tenant_id": "test_tenant_id", "client_id": "test_client_id", "client_secret": "test_client_secret", "environment": "test_environment", "vault_name": "test_vault_name", "key_name": "test_key_name"}, + }, + { + "GCP CKMS wrapper", + &KMS{ + Type: "gcpckms", + Priority: 1, + }, + map[string]string{"GOOGLE_CREDENTIALS": "test_credentials", "GOOGLE_PROJECT": "test_project", "GOOGLE_REGION": "test_region", "VAULT_GCPCKMS_SEAL_KEY_RING": "test_key_ring", "VAULT_GCPCKMS_SEAL_CRYPTO_KEY": "test_crypto_key"}, + map[string]string{"credentials": "test_credentials", "project": "test_project", "region": "test_region", "key_ring": "test_key_ring", "crypto_key": "test_crypto_key"}, + }, + { + "OCI KMS wrapper", + &KMS{ + Type: "ocikms", + Priority: 1, + }, + map[string]string{"VAULT_OCIKMS_SEAL_KEY_ID": "test_key_id", "VAULT_OCIKMS_CRYPTO_ENDPOINT": "test_crypto_endpoint", "VAULT_OCIKMS_MANAGEMENT_ENDPOINT": "test_management_endpoint"}, + map[string]string{"key_id": "test_key_id", "crypto_endpoint": "test_crypto_endpoint", "management_endpoint": "test_management_endpoint"}, + }, + { + "Transit wrapper", + &KMS{ + Type: "transit", + Priority: 1, + }, + map[string]string{"VAULT_ADDR": "test_address", "VAULT_TOKEN": "test_token", "VAULT_TRANSIT_SEAL_KEY_NAME": "test_key_name", "VAULT_TRANSIT_SEAL_MOUNT_PATH": "test_mount_path"}, + map[string]string{"address": "test_address", "token": "test_token", "key_name": "test_key_name", "mount_path": "test_mount_path"}, + }, + { + "Environment vars not set", + &KMS{ + Type: "awskms", + Priority: 1, + }, + map[string]string{}, + map[string]string{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + for envName, envVal := range tt.envVars { + if err := os.Setenv(envName, envVal); err != nil { + t.Errorf("error setting environment vars for test: %s", err) + } + } + + if got := GetEnvConfigFunc(tt.kms); !reflect.DeepEqual(got, tt.want) { + t.Errorf("getEnvConfig() = %v, want %v", got, tt.want) + } + + for env := range tt.envVars { + if err := os.Unsetenv(env); err != nil { + t.Errorf("error unsetting environment vars for test: %s", err) + } + } + }) + } +}