Merge pull request #355 from hashicorp/f-transit
Improving the transit backend
This commit is contained in:
commit
89a12c99fc
|
@ -15,11 +15,13 @@ func Backend() *framework.Backend {
|
|||
PathsSpecial: &logical.Paths{
|
||||
Root: []string{
|
||||
"keys/*",
|
||||
"raw/*",
|
||||
},
|
||||
},
|
||||
|
||||
Paths: []*framework.Path{
|
||||
pathKeys(),
|
||||
pathRaw(),
|
||||
pathEncrypt(),
|
||||
pathDecrypt(),
|
||||
},
|
||||
|
|
|
@ -21,10 +21,27 @@ func TestBackend_basic(t *testing.T) {
|
|||
Steps: []logicaltest.TestStep{
|
||||
testAccStepWritePolicy(t, "test"),
|
||||
testAccStepReadPolicy(t, "test", false),
|
||||
testAccStepReadRaw(t, "test", false),
|
||||
testAccStepEncrypt(t, "test", testPlaintext, decryptData),
|
||||
testAccStepDecrypt(t, "test", testPlaintext, decryptData),
|
||||
testAccStepDeletePolicy(t, "test"),
|
||||
testAccStepReadPolicy(t, "test", true),
|
||||
testAccStepReadRaw(t, "test", true),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestBackend_upsert(t *testing.T) {
|
||||
decryptData := make(map[string]interface{})
|
||||
logicaltest.Test(t, logicaltest.TestCase{
|
||||
Backend: Backend(),
|
||||
Steps: []logicaltest.TestStep{
|
||||
testAccStepReadPolicy(t, "test", true),
|
||||
testAccStepEncrypt(t, "test", testPlaintext, decryptData),
|
||||
testAccStepReadPolicy(t, "test", false),
|
||||
testAccStepDecrypt(t, "test", testPlaintext, decryptData),
|
||||
testAccStepDeletePolicy(t, "test"),
|
||||
testAccStepReadPolicy(t, "test", true),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@ -65,6 +82,43 @@ func testAccStepReadPolicy(t *testing.T, name string, expectNone bool) logicalte
|
|||
return err
|
||||
}
|
||||
|
||||
if d.Name != name {
|
||||
return fmt.Errorf("bad: %#v", d)
|
||||
}
|
||||
if d.CipherMode != "aes-gcm" {
|
||||
return fmt.Errorf("bad: %#v", d)
|
||||
}
|
||||
// Should NOT get a key back
|
||||
if d.Key != nil {
|
||||
return fmt.Errorf("bad: %#v", d)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func testAccStepReadRaw(t *testing.T, name string, expectNone bool) logicaltest.TestStep {
|
||||
return logicaltest.TestStep{
|
||||
Operation: logical.ReadOperation,
|
||||
Path: "raw/" + name,
|
||||
Check: func(resp *logical.Response) error {
|
||||
if resp == nil && !expectNone {
|
||||
return fmt.Errorf("missing response")
|
||||
} else if expectNone {
|
||||
if resp != nil {
|
||||
return fmt.Errorf("response when expecting none")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
var d struct {
|
||||
Name string `mapstructure:"name"`
|
||||
Key []byte `mapstructure:"key"`
|
||||
CipherMode string `mapstructure:"cipher_mode"`
|
||||
}
|
||||
if err := mapstructure.Decode(resp.Data, &d); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if d.Name != name {
|
||||
return fmt.Errorf("bad: %#v", d)
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/vault/logical"
|
||||
"github.com/hashicorp/vault/logical/framework"
|
||||
|
@ -56,7 +57,10 @@ func pathEncryptWrite(
|
|||
|
||||
// Error if invalid policy
|
||||
if p == nil {
|
||||
return logical.ErrorResponse("policy not found"), logical.ErrInvalidRequest
|
||||
p, err = generatePolicy(req.Storage, name)
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(fmt.Sprintf("failed to upsert policy: %v", err)), logical.ErrInvalidRequest
|
||||
}
|
||||
}
|
||||
|
||||
// Guard against a potentially invalid cipher-mode
|
||||
|
|
|
@ -45,6 +45,41 @@ func getPolicy(req *logical.Request, name string) (*Policy, error) {
|
|||
return p, nil
|
||||
}
|
||||
|
||||
// generatePolicy is used to create a new named policy with
|
||||
// a randomly generated key
|
||||
func generatePolicy(storage logical.Storage, name string) (*Policy, error) {
|
||||
// Create the policy object
|
||||
p := &Policy{
|
||||
Name: name,
|
||||
CipherMode: "aes-gcm",
|
||||
}
|
||||
|
||||
// Generate a 256bit key
|
||||
p.Key = make([]byte, 32)
|
||||
_, err := rand.Read(p.Key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Encode the policy
|
||||
buf, err := p.Serialize()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Write the policy into storage
|
||||
err = storage.Put(&logical.StorageEntry{
|
||||
Key: "policy/" + name,
|
||||
Value: buf,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Return the policy
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func pathKeys() *framework.Path {
|
||||
return &framework.Path{
|
||||
Pattern: `keys/(?P<name>\w+)`,
|
||||
|
@ -79,34 +114,9 @@ func pathPolicyWrite(
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
// Create the policy object
|
||||
p := &Policy{
|
||||
Name: name,
|
||||
CipherMode: "aes-gcm",
|
||||
}
|
||||
|
||||
// Generate a 256bit key
|
||||
p.Key = make([]byte, 32)
|
||||
_, err = rand.Read(p.Key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Encode the policy
|
||||
buf, err := p.Serialize()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Write the policy into storage
|
||||
err = req.Storage.Put(&logical.StorageEntry{
|
||||
Key: "policy/" + name,
|
||||
Value: buf,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
// Generate the policy
|
||||
_, err = generatePolicy(req.Storage, name)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func pathPolicyRead(
|
||||
|
@ -124,7 +134,6 @@ func pathPolicyRead(
|
|||
resp := &logical.Response{
|
||||
Data: map[string]interface{}{
|
||||
"name": p.Name,
|
||||
"key": p.Key,
|
||||
"cipher_mode": p.CipherMode,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
package transit
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/vault/logical"
|
||||
"github.com/hashicorp/vault/logical/framework"
|
||||
)
|
||||
|
||||
func pathRaw() *framework.Path {
|
||||
return &framework.Path{
|
||||
Pattern: `raw/(?P<name>\w+)`,
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"name": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Description: "Name of the key",
|
||||
},
|
||||
},
|
||||
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
logical.ReadOperation: pathRawRead,
|
||||
},
|
||||
|
||||
HelpSynopsis: pathPolicyHelpSyn,
|
||||
HelpDescription: pathPolicyHelpDesc,
|
||||
}
|
||||
}
|
||||
|
||||
func pathRawRead(
|
||||
req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
name := d.Get("name").(string)
|
||||
p, err := getPolicy(req, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if p == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Return the response
|
||||
resp := &logical.Response{
|
||||
Data: map[string]interface{}{
|
||||
"name": p.Name,
|
||||
"key": p.Key,
|
||||
"cipher_mode": p.CipherMode,
|
||||
},
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
const pathRawHelpSyn = `Fetch raw keys for named encrption keys`
|
||||
|
||||
const pathRawHelpDesc = `
|
||||
This path is used to get the underlying encryption keys used for the
|
||||
named keys that are available.
|
||||
`
|
|
@ -54,6 +54,15 @@ $ vault read transit/keys/foo
|
|||
Key Value
|
||||
name foo
|
||||
cipher_mode aes-gcm
|
||||
````
|
||||
|
||||
We can read from the `raw/` endpoint to see the encryption key itself:
|
||||
|
||||
```
|
||||
$ vault read transit/raw/foo
|
||||
Key Value
|
||||
name foo
|
||||
cipher_mode aes-gcm
|
||||
key PhKFTALCmhAhVQfMBAH4+UwJ6J2gybapUH9BsrtIgR8=
|
||||
````
|
||||
|
||||
|
@ -114,17 +123,7 @@ only encrypt or decrypt using the named keys they need access to.
|
|||
|
||||
<dt>Returns</dt>
|
||||
<dd>
|
||||
|
||||
```javascript
|
||||
{
|
||||
"data": {
|
||||
"name": "foo",
|
||||
"cipher_mode": "aes-gcm",
|
||||
"key": "PhKFTALCmhAhVQfMBAH4+UwJ6J2gybapUH9BsrtIgR8="
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
A `204` response code.
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
|
@ -156,7 +155,6 @@ only encrypt or decrypt using the named keys they need access to.
|
|||
"data": {
|
||||
"name": "foo",
|
||||
"cipher_mode": "aes-gcm",
|
||||
"key": "PhKFTALCmhAhVQfMBAH4+UwJ6J2gybapUH9BsrtIgR8="
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@ -196,7 +194,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 the named key
|
||||
does not already exist, it will be automatically generated for the given
|
||||
name with the default parameters.
|
||||
</dd>
|
||||
|
||||
<dt>Method</dt>
|
||||
|
@ -269,3 +269,42 @@ only encrypt or decrypt using the named keys they need access to.
|
|||
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
### /transit/raw/
|
||||
#### GET
|
||||
|
||||
<dl class="api">
|
||||
<dt>Description</dt>
|
||||
<dd>
|
||||
Returns raw information about a named encryption key,
|
||||
Including the underlying encryption key. This is a root protected endpoint.
|
||||
</dd>
|
||||
|
||||
<dt>Method</dt>
|
||||
<dd>GET</dd>
|
||||
|
||||
<dt>URL</dt>
|
||||
<dd>`/transit/raw/<name>`</dd>
|
||||
|
||||
<dt>Parameters</dt>
|
||||
<dd>
|
||||
None
|
||||
</dd>
|
||||
|
||||
<dt>Returns</dt>
|
||||
<dd>
|
||||
|
||||
```javascript
|
||||
{
|
||||
"data": {
|
||||
"name": "foo",
|
||||
"cipher_mode": "aes-gcm",
|
||||
"key": "PhKFTALCmhAhVQfMBAH4+UwJ6J2gybapUH9BsrtIgR8="
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue