From f53d31a580575160cbe024c38857dfe01cd00df4 Mon Sep 17 00:00:00 2001 From: Armon Dadgar Date: Wed, 17 Jun 2015 18:42:23 -0700 Subject: [PATCH 1/4] secret/transit: Use special endpoint to get underlying keys. Fixes #219 --- builtin/logical/transit/backend.go | 2 + builtin/logical/transit/backend_test.go | 39 ++++++++++++++++++ builtin/logical/transit/path_keys.go | 1 - builtin/logical/transit/path_raw.go | 54 +++++++++++++++++++++++++ 4 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 builtin/logical/transit/path_raw.go diff --git a/builtin/logical/transit/backend.go b/builtin/logical/transit/backend.go index b63f903b1..d719d42c6 100644 --- a/builtin/logical/transit/backend.go +++ b/builtin/logical/transit/backend.go @@ -15,11 +15,13 @@ func Backend() *framework.Backend { PathsSpecial: &logical.Paths{ Root: []string{ "keys/*", + "raw/*", }, }, Paths: []*framework.Path{ pathKeys(), + pathRaw(), pathEncrypt(), pathDecrypt(), }, diff --git a/builtin/logical/transit/backend_test.go b/builtin/logical/transit/backend_test.go index c4c7c3d43..cb2a5e187 100644 --- a/builtin/logical/transit/backend_test.go +++ b/builtin/logical/transit/backend_test.go @@ -21,10 +21,12 @@ 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), }, }) } @@ -65,6 +67,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) } diff --git a/builtin/logical/transit/path_keys.go b/builtin/logical/transit/path_keys.go index b85696468..9b2424004 100644 --- a/builtin/logical/transit/path_keys.go +++ b/builtin/logical/transit/path_keys.go @@ -124,7 +124,6 @@ func pathPolicyRead( resp := &logical.Response{ Data: map[string]interface{}{ "name": p.Name, - "key": p.Key, "cipher_mode": p.CipherMode, }, } diff --git a/builtin/logical/transit/path_raw.go b/builtin/logical/transit/path_raw.go new file mode 100644 index 000000000..ebe411a57 --- /dev/null +++ b/builtin/logical/transit/path_raw.go @@ -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\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. +` From 93ee9f6b764bc4c1d4becc92033e84c5b7bd9950 Mon Sep 17 00:00:00 2001 From: Armon Dadgar Date: Wed, 17 Jun 2015 18:45:29 -0700 Subject: [PATCH 2/4] website: update the transit documentation --- .../source/docs/secrets/transit/index.html.md | 61 +++++++++++++++---- 1 file changed, 49 insertions(+), 12 deletions(-) diff --git a/website/source/docs/secrets/transit/index.html.md b/website/source/docs/secrets/transit/index.html.md index 3506eca03..ae04b2794 100644 --- a/website/source/docs/secrets/transit/index.html.md +++ b/website/source/docs/secrets/transit/index.html.md @@ -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.
Returns
- - ```javascript - { - "data": { - "name": "foo", - "cipher_mode": "aes-gcm", - "key": "PhKFTALCmhAhVQfMBAH4+UwJ6J2gybapUH9BsrtIgR8=" - } - } - ``` - + A `204` response code.
@@ -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=" } } ``` @@ -269,3 +267,42 @@ only encrypt or decrypt using the named keys they need access to. + +### /transit/raw/ +#### GET + +
+
Description
+
+ Returns raw information about a named encryption key, + Including the underlying encryption key. This is a root protected endpoint. +
+ +
Method
+
GET
+ +
URL
+
`/transit/raw/`
+ +
Parameters
+
+ None +
+ +
Returns
+
+ + ```javascript + { + "data": { + "name": "foo", + "cipher_mode": "aes-gcm", + "key": "PhKFTALCmhAhVQfMBAH4+UwJ6J2gybapUH9BsrtIgR8=" + } + } + ``` + +
+
+ + From d34861b811abb97f4c1aec31780ce9db0c91cac1 Mon Sep 17 00:00:00 2001 From: Armon Dadgar Date: Wed, 17 Jun 2015 18:51:05 -0700 Subject: [PATCH 3/4] secret/transit: allow policies to be upserted --- builtin/logical/transit/backend_test.go | 15 ++++++ builtin/logical/transit/path_encrypt.go | 6 ++- builtin/logical/transit/path_keys.go | 66 ++++++++++++++----------- 3 files changed, 58 insertions(+), 29 deletions(-) diff --git a/builtin/logical/transit/backend_test.go b/builtin/logical/transit/backend_test.go index cb2a5e187..c03ab0e32 100644 --- a/builtin/logical/transit/backend_test.go +++ b/builtin/logical/transit/backend_test.go @@ -31,6 +31,21 @@ func TestBackend_basic(t *testing.T) { }) } +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), + }, + }) +} + func testAccStepWritePolicy(t *testing.T, name string) logicaltest.TestStep { return logicaltest.TestStep{ Operation: logical.WriteOperation, diff --git a/builtin/logical/transit/path_encrypt.go b/builtin/logical/transit/path_encrypt.go index d30d72f3b..761af7e35 100644 --- a/builtin/logical/transit/path_encrypt.go +++ b/builtin/logical/transit/path_encrypt.go @@ -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 diff --git a/builtin/logical/transit/path_keys.go b/builtin/logical/transit/path_keys.go index 9b2424004..f83ae4037 100644 --- a/builtin/logical/transit/path_keys.go +++ b/builtin/logical/transit/path_keys.go @@ -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\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( From 7e6f44e39e4d8003dd5f20d2418cd3fad0e1857f Mon Sep 17 00:00:00 2001 From: Armon Dadgar Date: Wed, 17 Jun 2015 18:51:58 -0700 Subject: [PATCH 4/4] website: document transit upsert behavior --- website/source/docs/secrets/transit/index.html.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/website/source/docs/secrets/transit/index.html.md b/website/source/docs/secrets/transit/index.html.md index ae04b2794..743ba0662 100644 --- a/website/source/docs/secrets/transit/index.html.md +++ b/website/source/docs/secrets/transit/index.html.md @@ -194,7 +194,9 @@ only encrypt or decrypt using the named keys they need access to.
Description
- 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.
Method