mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-11-01 19:17:58 +00:00
Re-add upsert into transit. Defaults to off and a new endpoint /config
can be used to turn it on for a given mount.
This commit is contained in:
@@ -19,9 +19,10 @@ func Backend() *backend {
|
||||
var b backend
|
||||
b.Backend = &framework.Backend{
|
||||
Paths: []*framework.Path{
|
||||
b.pathConfig(),
|
||||
// Rotate/Config needs to come before Keys
|
||||
// as the handler is greedy
|
||||
b.pathConfig(),
|
||||
b.pathKeysConfig(),
|
||||
b.pathRotate(),
|
||||
b.pathRewrap(),
|
||||
b.pathKeys(),
|
||||
|
||||
@@ -111,6 +111,29 @@ func TestBackend_rotation(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestBackend_upsert(t *testing.T) {
|
||||
decryptData := make(map[string]interface{})
|
||||
logicaltest.Test(t, logicaltest.TestCase{
|
||||
Factory: Factory,
|
||||
Steps: []logicaltest.TestStep{
|
||||
testAccStepReadPolicy(t, "test", true, false),
|
||||
testAccStepEncryptExpectFailure(t, "test", testPlaintext, decryptData),
|
||||
testAccStepReadPolicy(t, "test", true, false),
|
||||
testAccStepConfigUpsert(t, true),
|
||||
testAccStepEncrypt(t, "test", testPlaintext, decryptData),
|
||||
testAccStepReadPolicy(t, "test", false, false),
|
||||
testAccStepDecrypt(t, "test", testPlaintext, decryptData),
|
||||
testAccStepConfigUpsert(t, false),
|
||||
testAccStepReadPolicy(t, "test2", true, false),
|
||||
testAccStepEncryptExpectFailure(t, "test2", testPlaintext, decryptData),
|
||||
testAccStepReadPolicy(t, "test2", true, false),
|
||||
testAccStepEnableDeletion(t, "test"),
|
||||
testAccStepDeletePolicy(t, "test"),
|
||||
testAccStepReadPolicy(t, "test", true, false),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestBackend_basic_derived(t *testing.T) {
|
||||
decryptData := make(map[string]interface{})
|
||||
logicaltest.Test(t, logicaltest.TestCase{
|
||||
@@ -268,6 +291,24 @@ func testAccStepEncrypt(
|
||||
}
|
||||
}
|
||||
|
||||
func testAccStepEncryptExpectFailure(
|
||||
t *testing.T, name, plaintext string, decryptData map[string]interface{}) logicaltest.TestStep {
|
||||
return logicaltest.TestStep{
|
||||
Operation: logical.UpdateOperation,
|
||||
Path: "encrypt/" + name,
|
||||
Data: map[string]interface{}{
|
||||
"plaintext": base64.StdEncoding.EncodeToString([]byte(plaintext)),
|
||||
},
|
||||
ErrorOk: true,
|
||||
Check: func(resp *logical.Response) error {
|
||||
if !resp.IsError() {
|
||||
return fmt.Errorf("expected error")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func testAccStepEncryptContext(
|
||||
t *testing.T, name, plaintext, context string, decryptData map[string]interface{}) logicaltest.TestStep {
|
||||
return logicaltest.TestStep{
|
||||
@@ -488,6 +529,16 @@ func testAccStepDecryptDatakey(t *testing.T, name string,
|
||||
}
|
||||
}
|
||||
|
||||
func testAccStepConfigUpsert(t *testing.T, upsert bool) logicaltest.TestStep {
|
||||
return logicaltest.TestStep{
|
||||
Operation: logical.UpdateOperation,
|
||||
Path: "config",
|
||||
Data: map[string]interface{}{
|
||||
"allow_upsert": upsert,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyUpgrade(t *testing.T) {
|
||||
p := &Policy{
|
||||
Name: "test",
|
||||
|
||||
@@ -1,34 +1,26 @@
|
||||
package transit
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/fatih/structs"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
"github.com/hashicorp/vault/logical/framework"
|
||||
)
|
||||
|
||||
func (b *backend) pathConfig() *framework.Path {
|
||||
return &framework.Path{
|
||||
Pattern: "keys/" + framework.GenericNameRegex("name") + "/config",
|
||||
Pattern: "config",
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"name": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Description: "Name of the key",
|
||||
},
|
||||
|
||||
"min_decryption_version": &framework.FieldSchema{
|
||||
Type: framework.TypeInt,
|
||||
Description: `If set, the minimum version of the key allowed
|
||||
to be decrypted.`,
|
||||
},
|
||||
|
||||
"deletion_allowed": &framework.FieldSchema{
|
||||
Type: framework.TypeBool,
|
||||
Description: "Whether to allow deletion of the key",
|
||||
"allow_upsert": &framework.FieldSchema{
|
||||
Type: framework.TypeBool,
|
||||
Default: false,
|
||||
Description: `Whether to allow upserting keys on first use,
|
||||
rather than requiring them to be manually
|
||||
specified through the keys endpoint`,
|
||||
},
|
||||
},
|
||||
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
logical.ReadOperation: b.pathConfigRead,
|
||||
logical.UpdateOperation: b.pathConfigWrite,
|
||||
},
|
||||
|
||||
@@ -37,85 +29,69 @@ to be decrypted.`,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *backend) pathConfigWrite(
|
||||
req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
name := d.Get("name").(string)
|
||||
type transitConfig struct {
|
||||
AllowUpsert bool `json:"allow_upsert" structs:"allow_upsert" mapstructure:"allow_upsert"`
|
||||
}
|
||||
|
||||
// Check if the policy already exists
|
||||
lp, err := b.policies.getPolicy(req, name)
|
||||
func (b *backend) getConfig(s logical.Storage) (*transitConfig, error) {
|
||||
entry, err := s.Get("config")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if lp == nil {
|
||||
return logical.ErrorResponse(
|
||||
fmt.Sprintf("no existing role named %s could be found", name)),
|
||||
logical.ErrInvalidRequest
|
||||
}
|
||||
|
||||
lp.Lock()
|
||||
defer lp.Unlock()
|
||||
|
||||
// Verify if wasn't deleted before we grabbed the lock
|
||||
if lp.policy == nil {
|
||||
return nil, fmt.Errorf("no existing role named %s could be found", name)
|
||||
}
|
||||
|
||||
resp := &logical.Response{}
|
||||
|
||||
persistNeeded := false
|
||||
|
||||
minDecryptionVersionRaw, ok := d.GetOk("min_decryption_version")
|
||||
if ok {
|
||||
minDecryptionVersion := minDecryptionVersionRaw.(int)
|
||||
|
||||
if minDecryptionVersion < 0 {
|
||||
return logical.ErrorResponse("min decryption version cannot be negative"), nil
|
||||
}
|
||||
|
||||
if minDecryptionVersion == 0 {
|
||||
minDecryptionVersion = 1
|
||||
resp.AddWarning("since Vault 0.3, transit key numbering starts at 1; forcing minimum to 1")
|
||||
}
|
||||
|
||||
if minDecryptionVersion > 0 &&
|
||||
minDecryptionVersion != lp.policy.MinDecryptionVersion {
|
||||
if minDecryptionVersion > lp.policy.LatestVersion {
|
||||
return logical.ErrorResponse(
|
||||
fmt.Sprintf("cannot set min decryption version of %d, latest key version is %d", minDecryptionVersion, lp.policy.LatestVersion)), nil
|
||||
}
|
||||
lp.policy.MinDecryptionVersion = minDecryptionVersion
|
||||
persistNeeded = true
|
||||
}
|
||||
}
|
||||
|
||||
allowDeletionInt, ok := d.GetOk("deletion_allowed")
|
||||
if ok {
|
||||
allowDeletion := allowDeletionInt.(bool)
|
||||
if allowDeletion != lp.policy.DeletionAllowed {
|
||||
lp.policy.DeletionAllowed = allowDeletion
|
||||
persistNeeded = true
|
||||
}
|
||||
}
|
||||
|
||||
// Add this as a guard here before persisting since we now require the min
|
||||
// decryption version to start at 1; even if it's not explicitly set here,
|
||||
// force the upgrade
|
||||
if lp.policy.MinDecryptionVersion == 0 {
|
||||
lp.policy.MinDecryptionVersion = 1
|
||||
persistNeeded = true
|
||||
}
|
||||
|
||||
if !persistNeeded {
|
||||
if entry == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return resp, lp.policy.Persist(req.Storage)
|
||||
var result transitConfig
|
||||
if err := entry.DecodeJSON(&result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
const pathConfigHelpSyn = `Configure a named encryption key`
|
||||
func (b *backend) pathConfigRead(
|
||||
req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
config, err := b.getConfig(req.Storage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if config == nil {
|
||||
config = &transitConfig{}
|
||||
}
|
||||
|
||||
resp := &logical.Response{
|
||||
Data: structs.New(config).Map(),
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (b *backend) pathConfigWrite(
|
||||
req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
allowUpsertInt, ok := d.GetOk("allow_upsert")
|
||||
if !ok {
|
||||
return logical.ErrorResponse("no known configuration parameters supplied"), nil
|
||||
}
|
||||
|
||||
config := &transitConfig{
|
||||
AllowUpsert: allowUpsertInt.(bool),
|
||||
}
|
||||
|
||||
jsonEntry, err := logical.StorageEntryJSON("config", config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := req.Storage.Put(jsonEntry); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
const pathConfigHelpSyn = `Configure the backend`
|
||||
|
||||
const pathConfigHelpDesc = `
|
||||
This path is used to configure the named key. Currently, this
|
||||
supports adjusting the minimum version of the key allowed to
|
||||
be used for decryption via the min_decryption_version paramter.
|
||||
`
|
||||
This path is used to configure the backend. Currently, this allows configuring
|
||||
whether or not keys can be created via upsert from the encryption endpoint.`
|
||||
|
||||
@@ -65,7 +65,24 @@ func (b *backend) pathEncryptWrite(
|
||||
|
||||
// Error if invalid policy
|
||||
if lp == nil {
|
||||
return logical.ErrorResponse("policy not found"), logical.ErrInvalidRequest
|
||||
config, err := b.getConfig(req.Storage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if config == nil || !config.AllowUpsert {
|
||||
return logical.ErrorResponse("policy not found"), logical.ErrInvalidRequest
|
||||
}
|
||||
|
||||
isDerived := len(context) != 0
|
||||
|
||||
lp, err = b.policies.generatePolicy(req.Storage, name, isDerived)
|
||||
// If the error is that the policy has been created in the interim we
|
||||
// will get the policy back, so only consider it an error if err is not
|
||||
// nil and we do not get a policy back
|
||||
if err != nil && lp != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
lp.RLock()
|
||||
|
||||
121
builtin/logical/transit/path_keys_config.go
Normal file
121
builtin/logical/transit/path_keys_config.go
Normal file
@@ -0,0 +1,121 @@
|
||||
package transit
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/vault/logical"
|
||||
"github.com/hashicorp/vault/logical/framework"
|
||||
)
|
||||
|
||||
func (b *backend) pathKeysConfig() *framework.Path {
|
||||
return &framework.Path{
|
||||
Pattern: "keys/" + framework.GenericNameRegex("name") + "/config",
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"name": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Description: "Name of the key",
|
||||
},
|
||||
|
||||
"min_decryption_version": &framework.FieldSchema{
|
||||
Type: framework.TypeInt,
|
||||
Description: `If set, the minimum version of the key allowed
|
||||
to be decrypted.`,
|
||||
},
|
||||
|
||||
"deletion_allowed": &framework.FieldSchema{
|
||||
Type: framework.TypeBool,
|
||||
Description: "Whether to allow deletion of the key",
|
||||
},
|
||||
},
|
||||
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
logical.UpdateOperation: b.pathKeysConfigWrite,
|
||||
},
|
||||
|
||||
HelpSynopsis: pathKeysConfigHelpSyn,
|
||||
HelpDescription: pathKeysConfigHelpDesc,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *backend) pathKeysConfigWrite(
|
||||
req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
name := d.Get("name").(string)
|
||||
|
||||
// Check if the policy already exists
|
||||
lp, err := b.policies.getPolicy(req, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if lp == nil {
|
||||
return logical.ErrorResponse(
|
||||
fmt.Sprintf("no existing role named %s could be found", name)),
|
||||
logical.ErrInvalidRequest
|
||||
}
|
||||
|
||||
lp.Lock()
|
||||
defer lp.Unlock()
|
||||
|
||||
// Verify if wasn't deleted before we grabbed the lock
|
||||
if lp.policy == nil {
|
||||
return nil, fmt.Errorf("no existing role named %s could be found", name)
|
||||
}
|
||||
|
||||
resp := &logical.Response{}
|
||||
|
||||
persistNeeded := false
|
||||
|
||||
minDecryptionVersionRaw, ok := d.GetOk("min_decryption_version")
|
||||
if ok {
|
||||
minDecryptionVersion := minDecryptionVersionRaw.(int)
|
||||
|
||||
if minDecryptionVersion < 0 {
|
||||
return logical.ErrorResponse("min decryption version cannot be negative"), nil
|
||||
}
|
||||
|
||||
if minDecryptionVersion == 0 {
|
||||
minDecryptionVersion = 1
|
||||
resp.AddWarning("since Vault 0.3, transit key numbering starts at 1; forcing minimum to 1")
|
||||
}
|
||||
|
||||
if minDecryptionVersion > 0 &&
|
||||
minDecryptionVersion != lp.policy.MinDecryptionVersion {
|
||||
if minDecryptionVersion > lp.policy.LatestVersion {
|
||||
return logical.ErrorResponse(
|
||||
fmt.Sprintf("cannot set min decryption version of %d, latest key version is %d", minDecryptionVersion, lp.policy.LatestVersion)), nil
|
||||
}
|
||||
lp.policy.MinDecryptionVersion = minDecryptionVersion
|
||||
persistNeeded = true
|
||||
}
|
||||
}
|
||||
|
||||
allowDeletionInt, ok := d.GetOk("deletion_allowed")
|
||||
if ok {
|
||||
allowDeletion := allowDeletionInt.(bool)
|
||||
if allowDeletion != lp.policy.DeletionAllowed {
|
||||
lp.policy.DeletionAllowed = allowDeletion
|
||||
persistNeeded = true
|
||||
}
|
||||
}
|
||||
|
||||
// Add this as a guard here before persisting since we now require the min
|
||||
// decryption version to start at 1; even if it's not explicitly set here,
|
||||
// force the upgrade
|
||||
if lp.policy.MinDecryptionVersion == 0 {
|
||||
lp.policy.MinDecryptionVersion = 1
|
||||
persistNeeded = true
|
||||
}
|
||||
|
||||
if !persistNeeded {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return resp, lp.policy.Persist(req.Storage)
|
||||
}
|
||||
|
||||
const pathKeysConfigHelpSyn = `Configure a named encryption key`
|
||||
|
||||
const pathKeysConfigHelpDesc = `
|
||||
This path is used to configure the named key. Currently, this
|
||||
supports adjusting the minimum version of the key allowed to
|
||||
be used for decryption via the min_decryption_version paramter.
|
||||
`
|
||||
@@ -134,7 +134,7 @@ func (p *policyCache) generatePolicy(storage logical.Storage, name string, deriv
|
||||
// created since we checked getPolicy. A policy being created holds a write
|
||||
// lock until it's done, so it'll be in the cache at this point.
|
||||
if lp := p.cache[name]; lp != nil {
|
||||
return nil, fmt.Errorf("policy %s already exists", name)
|
||||
return lp, fmt.Errorf("policy %s already exists", name)
|
||||
}
|
||||
|
||||
// Create the policy object
|
||||
|
||||
@@ -123,13 +123,81 @@ only encrypt or decrypt using the named keys they need access to.
|
||||
|
||||
## API
|
||||
|
||||
### /transit/config
|
||||
#### POST
|
||||
|
||||
<dl class="api">
|
||||
<dt>Description</dt>
|
||||
<dd>
|
||||
Allows setting backend configuration options.
|
||||
</dd>
|
||||
|
||||
<dt>Method</dt>
|
||||
<dd>POST</dd>
|
||||
|
||||
<dt>URL</dt>
|
||||
<dd>`/transit/config`</dd>
|
||||
|
||||
<dt>Parameters</dt>
|
||||
<dd>
|
||||
<ul>
|
||||
<li>
|
||||
<span class="param">allow_upsert</span>
|
||||
<span class="param-flags">optional</span>
|
||||
Boolean flag indicating if upserting of keys is allowed. If true, if a
|
||||
key with the given name does not exist when a call to
|
||||
`/transit/encrypt/<name>` is made, the key will be created on-the-fly.
|
||||
Defaults to false.
|
||||
</li>
|
||||
</ul>
|
||||
</dd>
|
||||
|
||||
<dt>Returns</dt>
|
||||
<dd>
|
||||
A `204` response code.
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
#### GET
|
||||
|
||||
<dl class="api">
|
||||
<dt>Description</dt>
|
||||
<dd>
|
||||
Returns the current backend configuration.
|
||||
</dd>
|
||||
|
||||
<dt>Method</dt>
|
||||
<dd>GET</dd>
|
||||
|
||||
<dt>URL</dt>
|
||||
<dd>`/transit/config`</dd>
|
||||
|
||||
<dt>Parameters</dt>
|
||||
<dd>
|
||||
None
|
||||
</dd>
|
||||
|
||||
<dt>Returns</dt>
|
||||
<dd>
|
||||
|
||||
```javascript
|
||||
{
|
||||
"data": {
|
||||
"allow_upsert": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
### /transit/keys/
|
||||
#### POST
|
||||
|
||||
<dl class="api">
|
||||
<dt>Description</dt>
|
||||
<dd>
|
||||
Creates a new named encryption key. This is a root protected endpoint.
|
||||
Creates a new named encryption key.
|
||||
</dd>
|
||||
|
||||
<dt>Method</dt>
|
||||
@@ -165,7 +233,7 @@ only encrypt or decrypt using the named keys they need access to.
|
||||
<dd>
|
||||
Returns information about a named encryption key. The `keys` object shows
|
||||
the creation time of each key version; the values are not the keys
|
||||
themselves. This is a root protected endpoint.
|
||||
themselves.
|
||||
</dd>
|
||||
|
||||
<dt>Method</dt>
|
||||
@@ -205,10 +273,10 @@ only encrypt or decrypt using the named keys they need access to.
|
||||
<dl class="api">
|
||||
<dt>Description</dt>
|
||||
<dd>
|
||||
Deletes a named encryption key. This is a root protected endpoint.
|
||||
It will no longer be possible to decrypt any data encrypted with the
|
||||
named key. Because this is a potentially catastrophic operation, the
|
||||
`deletion_allowed` tunable must be set in the key's `/config` endpoint.
|
||||
Deletes a named encryption key. It will no longer be possible to decrypt
|
||||
any data encrypted with the named key. Because this is a potentially
|
||||
catastrophic operation, the `deletion_allowed` tunable must be set in the
|
||||
key's `/config` endpoint.
|
||||
</dd>
|
||||
|
||||
<dt>Method</dt>
|
||||
@@ -235,8 +303,7 @@ only encrypt or decrypt using the named keys they need access to.
|
||||
<dt>Description</dt>
|
||||
<dd>
|
||||
Allows tuning configuration values for a given key. (These values are
|
||||
returned during a read operation on the named key.) This is a
|
||||
root-protected endpoint.
|
||||
returned during a read operation on the named key.)
|
||||
</dd>
|
||||
|
||||
<dt>Method</dt>
|
||||
@@ -279,7 +346,7 @@ only encrypt or decrypt using the named keys they need access to.
|
||||
Rotates the version of the named key. After rotation, new plaintext
|
||||
requests will be encrypted with the new version of the key. To upgrade
|
||||
ciphertext to be encrypted with the latest version of the key, use the
|
||||
`rewrap` endpoint. This is a root-protected endpoint.
|
||||
`rewrap` endpoint.
|
||||
</dd>
|
||||
|
||||
<dt>Method</dt>
|
||||
@@ -305,7 +372,9 @@ only encrypt or decrypt using the named keys they need access to.
|
||||
<dl class="api">
|
||||
<dt>Description</dt>
|
||||
<dd>
|
||||
Encrypts the provided plaintext using the named key.
|
||||
Encrypts the provided plaintext using the named key. If `allow_upsert` is
|
||||
enabled in the backend config, if the named key does not exist, it will be
|
||||
created on-the-fly.
|
||||
</dd>
|
||||
|
||||
<dt>Method</dt>
|
||||
|
||||
Reference in New Issue
Block a user