Skip to content

Commit

Permalink
Add kms_library configuration stanza (hashicorp#13352)
Browse files Browse the repository at this point in the history
- Add the kms_library configuration stanza to Vault's command/server
 - Provide validation of keys and general configuration.
 - Add initial kms_library configuration documentation
 - Attempt at startup to verify we can read the configured HSM Library
 - Hook in KmsLibrary config into the Validate to detect typo/unused keys
  • Loading branch information
stevendpclark authored and heppu committed Jan 13, 2022
1 parent e45fc0e commit a467c9a
Show file tree
Hide file tree
Showing 5 changed files with 195 additions and 0 deletions.
105 changes: 105 additions & 0 deletions command/server/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@ import (
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"

wrapping "github.com/hashicorp/go-kms-wrapping"

"github.com/hashicorp/go-multierror"
"github.com/hashicorp/go-secure-stdlib/parseutil"
"github.com/hashicorp/hcl"
Expand All @@ -23,6 +26,8 @@ var entConfigValidate = func(_ *Config, _ string) []configutil.ConfigError {
return nil
}

var kmsLibraryValidator = defaultKmsLibraryValidator

// Config is the configuration for the vault server.
type Config struct {
UnusedKeys configutil.UnusedKeyMap `hcl:",unusedKeyPositions"`
Expand Down Expand Up @@ -82,6 +87,8 @@ type Config struct {

License string `hcl:"-"`
LicensePath string `hcl:"license_path"`

KmsLibraries map[string]*KMSLibrary `hcl:"-"`
}

const (
Expand All @@ -99,6 +106,9 @@ func (c *Config) Validate(sourceFilePath string) []configutil.ConfigError {
for _, l := range c.Listeners {
results = append(results, l.Validate(sourceFilePath)...)
}
for _, kmslibrary := range c.KmsLibraries {
results = append(results, kmslibrary.Validate(sourceFilePath)...)
}
results = append(results, c.validateEnt(sourceFilePath)...)
return results
}
Expand Down Expand Up @@ -168,6 +178,24 @@ func (b *ServiceRegistration) GoString() string {
return fmt.Sprintf("*%#v", *b)
}

// KMSLibrary is a per-server configuration that will be further augmented with managed key configuration to
// build up a KMS wrapper type to delegate encryption operations to HSMs
type KMSLibrary struct {
UnusedKeys configutil.UnusedKeyMap `hcl:",unusedKeyPositions"`
FoundKeys []string `hcl:",decodedFields"`
Type string `hcl:"-"`
Name string `hcl:"name"`
Library string `hcl:"library"`
}

func (k *KMSLibrary) Validate(source string) []configutil.ConfigError {
return configutil.ValidateUnusedFields(k.UnusedKeys, source)
}

func (k *KMSLibrary) GoString() string {
return fmt.Sprintf("*%#v", *k)
}

func NewConfig() *Config {
return &Config{
SharedConfig: new(configutil.SharedConfig),
Expand Down Expand Up @@ -528,6 +556,14 @@ func ParseConfig(d, source string) (*Config, error) {
}
}

// Parse KMSLibraries sections if any
if o := list.Filter("kms_library"); len(o.Items) > 0 {
delete(result.UnusedKeys, "kms_library")
if err := parseKmsLibraries(result, o); err != nil {
return nil, fmt.Errorf("error parsing 'kms_library': %w", err)
}
}

entConfig := &(result.entConfig)
if err := entConfig.parseConfig(list); err != nil {
return nil, fmt.Errorf("error parsing enterprise config: %w", err)
Expand Down Expand Up @@ -794,6 +830,75 @@ func parseServiceRegistration(result *Config, list *ast.ObjectList, name string)
return nil
}

func parseKmsLibraries(result *Config, list *ast.ObjectList) error {
result.KmsLibraries = make(map[string]*KMSLibrary, len(list.Items))

for _, item := range list.Items {
library, err := decodeKmsLibrary(item)
if err != nil {
return err
}

if err := validateKmsLibrary(library); err != nil {
return err
}

if _, ok := result.KmsLibraries[library.Name]; ok {
return fmt.Errorf("duplicated kms_library configuration sections with name %s", library.Name)
}

result.KmsLibraries[library.Name] = library
}
return nil
}

func decodeKmsLibrary(item *ast.ObjectItem) (*KMSLibrary, error) {
library := &KMSLibrary{}
if err := hcl.DecodeObject(&library, item.Val); err != nil {
return nil, multierror.Prefix(err, "kms_library")
}

if len(item.Keys) != 1 {
return nil, errors.New("kms_library section was missing a type")
}

library.Type = strings.ToLower(item.Keys[0].Token.Value().(string))
library.Name = strings.ToLower(library.Name)

return library, nil
}

func defaultKmsLibraryValidator(kms *KMSLibrary) error {
switch kms.Type {
case wrapping.PKCS11:
return fmt.Errorf("KMS type 'pkcs11' requires the Vault Enterprise HSM binary")

default:
return fmt.Errorf("unknown KMS type %q", kms.Type)
}
}

func validateKmsLibrary(kmsConfig *KMSLibrary) error {
if kmsConfig.Library == "" {
return fmt.Errorf("library key can not be blank within kms_library type: %s", kmsConfig.Type)
}

if kmsConfig.Name == "" {
return fmt.Errorf("name key can not be blank within kms_library type: %s", kmsConfig.Type)
}

nameRegex := regexp.MustCompile("^[\\w._-]+$")
if !nameRegex.MatchString(kmsConfig.Name) {
return fmt.Errorf("value ('%s') for name field contained invalid characters within kms_library type: %s", kmsConfig.Name, kmsConfig.Type)
}

if err := kmsLibraryValidator(kmsConfig); err != nil {
return err
}

return nil
}

// Sanitized returns a copy of the config with all values that are considered
// sensitive stripped. It also strips all `*Raw` values that are mainly
// used for parsing.
Expand Down
4 changes: 4 additions & 0 deletions command/server/config_oss_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,7 @@ func TestLoadConfigFile_json2(t *testing.T) {
func TestParseEntropy(t *testing.T) {
testParseEntropy(t, true)
}

func TestKmsLibraryFailsForNonHSMBinary(t *testing.T) {
testKmsLibraryFailsForNonHSMBinary(t)
}
23 changes: 23 additions & 0 deletions command/server/config_test_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -1063,3 +1063,26 @@ func testConfigRaftAutopilot(t *testing.T) {
t.Fatal(diff)
}
}

func testKmsLibraryFailsForNonHSMBinary(t *testing.T) {
config := `
ui = false
storage "file" {
path = "/tmp/test"
}
listener "tcp" {
address = "0.0.0.0:8200"
tls_cert_file = "/opt/vault/tls/tls.crt"
tls_key_file = "/opt/vault/tls/tls.key"
}
kms_library "pkcs11" {
name="Logical1"
library="a library"
}
`
_, err := ParseConfig(config, "")
require.Error(t, err)
require.Contains(t, err.Error(), "requires the Vault Enterprise HSM binary")
}
59 changes: 59 additions & 0 deletions website/content/docs/configuration/kms-library.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
layout: docs
page_title: Kms Library - Configuration
description: >-
The kms_library stanza allows node specific configuration for access to
KMS access libraries
---

# `kms_library` Stanza

The `kms_library` stanza isolates platform specific configuration for managed keys.
It defines logical names that are referenced within an API configuration keeping cluster
and node specific details separated along with deployment concerns for each.

At the moment managed keys are only available as a feature set within Vault Enterprise HSM edition.

## Requirements

The following software packages are required for Vault Enterprise HSM:

- PKCS#11 compatible HSM integration library. Vault targets version 2.2 or
higher of PKCS#11. Depending on any given HSM, some functions (such as key
generation) may have to be performed manually.
- The [GNU libltdl
library](https://www.gnu.org/software/libtool/manual/html_node/Using-libltdl.html)
— ensure that it is installed for the correct architecture of your servers

## Configuration

Multiple kms_library stanza's can be defined with the only limitation that the value for the
name key needs to be a unique value across all the stanza definitions in a case-insensitive
manner.

The type argument only supports "pkcs11" at the moment.

Example `kms_library` stanza:

```hcl
kms_library [TYPE] {
name = "<logical name>"
# ...
}
```

## `pkcs11` Parameters

These parameters apply to the `kms_library` stanza of type `pkcs11` in the Vault configuration file:

- `name` `(string: <required>)`: The logical name to be referenced by a managed key
- `library` `(string: <required>)`: The path to the PKCS#11 library shared object file.

For example:

```hcl
kms_library "pkcs11" {
name = "hsm1"
library = "/usr/lib/Cryptoki.so"
}
```
4 changes: 4 additions & 0 deletions website/data/docs-nav-data.json
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,10 @@
{
"title": "<code>Entropy Augmentation</code> <sup>ENT</sup>",
"path": "configuration/entropy-augmentation"
},
{
"title": "<code>kms_library</code> <sup>ENT</sup>",
"path": "configuration/kms-library"
}
]
},
Expand Down

0 comments on commit a467c9a

Please sign in to comment.