Merge pull request #355 from hashicorp/f-transit

Improving the transit backend
This commit is contained in:
Armon Dadgar 2015-06-17 18:55:29 -07:00
commit 89a12c99fc
6 changed files with 205 additions and 43 deletions

View File

@ -15,11 +15,13 @@ func Backend() *framework.Backend {
PathsSpecial: &logical.Paths{
Root: []string{
"keys/*",
"raw/*",
},
},
Paths: []*framework.Path{
pathKeys(),
pathRaw(),
pathEncrypt(),
pathDecrypt(),
},

View File

@ -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)
}

View File

@ -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

View File

@ -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,
},
}

View File

@ -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.
`

View File

@ -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>