diff --git a/builtin/logical/transit/backend_test.go b/builtin/logical/transit/backend_test.go index 6ea5252248..7817d097be 100644 --- a/builtin/logical/transit/backend_test.go +++ b/builtin/logical/transit/backend_test.go @@ -39,6 +39,19 @@ func TestBackend_basic(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), + testAccStepEncryptUpsert(t, "test", testPlaintext, decryptData), + testAccStepReadPolicy(t, "test", false, false), + testAccStepDecrypt(t, "test", testPlaintext, decryptData), + }, + }) +} + func TestBackend_datakey(t *testing.T) { dataKeyInfo := make(map[string]interface{}) logicaltest.Test(t, logicaltest.TestCase{ @@ -268,6 +281,30 @@ func testAccStepEncrypt( } } +func testAccStepEncryptUpsert( + t *testing.T, name, plaintext string, decryptData map[string]interface{}) logicaltest.TestStep { + return logicaltest.TestStep{ + Operation: logical.CreateOperation, + Path: "encrypt/" + name, + Data: map[string]interface{}{ + "plaintext": base64.StdEncoding.EncodeToString([]byte(plaintext)), + }, + Check: func(resp *logical.Response) error { + var d struct { + Ciphertext string `mapstructure:"ciphertext"` + } + if err := mapstructure.Decode(resp.Data, &d); err != nil { + return err + } + if d.Ciphertext == "" { + return fmt.Errorf("missing ciphertext") + } + decryptData["ciphertext"] = d.Ciphertext + return nil + }, + } +} + func testAccStepEncryptContext( t *testing.T, name, plaintext, context string, decryptData map[string]interface{}) logicaltest.TestStep { return logicaltest.TestStep{ diff --git a/builtin/logical/transit/path_encrypt.go b/builtin/logical/transit/path_encrypt.go index 10dadfbd1c..fc2e2048f3 100644 --- a/builtin/logical/transit/path_encrypt.go +++ b/builtin/logical/transit/path_encrypt.go @@ -30,14 +30,28 @@ func (b *backend) pathEncrypt() *framework.Path { }, Callbacks: map[logical.Operation]framework.OperationFunc{ + logical.CreateOperation: b.pathEncryptWrite, logical.UpdateOperation: b.pathEncryptWrite, }, + ExistenceCheck: b.pathEncryptExistenceCheck, + HelpSynopsis: pathEncryptHelpSyn, HelpDescription: pathEncryptHelpDesc, } } +func (b *backend) pathEncryptExistenceCheck( + req *logical.Request, d *framework.FieldData) (bool, error) { + name := d.Get("name").(string) + lp, err := b.policies.getPolicy(req, name) + if err != nil { + return false, err + } + + return lp != nil, nil +} + func (b *backend) pathEncryptWrite( req *logical.Request, d *framework.FieldData) (*logical.Response, error) { name := d.Get("name").(string) @@ -65,7 +79,19 @@ func (b *backend) pathEncryptWrite( // Error if invalid policy if lp == nil { - return logical.ErrorResponse("policy not found"), logical.ErrInvalidRequest + if req.Operation != logical.CreateOperation { + 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() diff --git a/website/source/docs/install/upgrade-to-0.5.html.md b/website/source/docs/install/upgrade-to-0.5.html.md index bc14885ff5..6fdc4ebfef 100644 --- a/website/source/docs/install/upgrade-to-0.5.html.md +++ b/website/source/docs/install/upgrade-to-0.5.html.md @@ -48,14 +48,15 @@ endpoint. Since the `default` policy contains `auth/token/renew-self` this makes it much more likely that the request will succeed rather than somewhat confusingly failing due to a lack of permissions on `auth/token/renew`. -## Transit No Longer Upserts Keys By Default +## Transit Upsertion Behavior Uses Capabilities Previously, attempting to encrypt with a key that did not exist would create a key with default values. This was convenient but ultimately allowed a client to potentially escape an ACL policy restriction, albeit without any dangerous -access. However, this is now disabled by default. If you want to enable this -behavior, you can use the `allow_upsert` parameter to the new `transit/config` -endpoint to turn it back on. +access. Now that Vault supports more granular capabilities in policies, +upsertion behavior is controlled by whether the client has the `create` +capability for the request (upsertion is allowed) or only the `update` +capability (upsertion is denied). ## etcd Physical Backend Uses `sync` diff --git a/website/source/docs/secrets/transit/index.html.md b/website/source/docs/secrets/transit/index.html.md index 6d63099694..c18f502bd5 100644 --- a/website/source/docs/secrets/transit/index.html.md +++ b/website/source/docs/secrets/transit/index.html.md @@ -305,7 +305,13 @@ 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. This path supports the + `create` and `update` policy capabilities as follows: if the user has the + `create` capability for this endpoint in their policies, and the key does + not exist, it will be upserted with default values (whether the key + requires derivation depends on whether the context parameter is empty or + not). If the user only has `update` capability and the key does not exist, + an error will be returned.
Method