Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## Unreleased

* Add support for key_usage to `vault_pki_secret_backend_root_sign_intermediate` ([#2421])(https://github.com/hashicorp/terraform-provider-vault/pull/2421)
* Add new ephemeral resource `vault_transit_decrypt`


## 5.0.0 (May 21, 2025)
Expand Down
3 changes: 3 additions & 0 deletions internal/consts/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,9 @@ const (
FieldDeletionTime = "deletion_time"
FieldDestroyed = "destroyed"
FieldDeleteAllVersions = "delete_all_versions"
FieldKey = "key"
FieldPlaintext = "plaintext"
FieldCiphertext = "ciphertext"

/*
ephemeral resource constants and write-only attributes
Expand Down
4 changes: 3 additions & 1 deletion internal/provider/fwprovider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ package fwprovider
import (
"context"
"fmt"
"regexp"

"github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
"github.com/hashicorp/terraform-plugin-framework/ephemeral"
ephemeralsecrets "github.com/hashicorp/terraform-provider-vault/internal/vault/secrets/ephemeral"
"regexp"

"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/datasource"
Expand Down Expand Up @@ -231,6 +232,7 @@ func (p *fwprovider) EphemeralResources(_ context.Context) []func() ephemeral.Ep
return []func() ephemeral.EphemeralResource{
ephemeralsecrets.NewKVV2EphemeralSecretResource,
ephemeralsecrets.NewDBEphemeralSecretResource,
ephemeralsecrets.NewTransitDecryptEphemeralSecretResource,
}

}
Expand Down
131 changes: 131 additions & 0 deletions internal/vault/secrets/ephemeral/transit_decrypt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package ephemeralsecrets

import (
"context"
"encoding/base64"
"fmt"

"github.com/hashicorp/terraform-plugin-framework/ephemeral"
"github.com/hashicorp/terraform-plugin-framework/ephemeral/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-provider-vault/internal/consts"
"github.com/hashicorp/terraform-provider-vault/internal/framework/base"
"github.com/hashicorp/terraform-provider-vault/internal/framework/client"
"github.com/hashicorp/terraform-provider-vault/internal/framework/errutil"
"github.com/hashicorp/terraform-provider-vault/internal/framework/model"
)

var _ ephemeral.EphemeralResource = &TransitDecryptEphemeralSecretResource{}

var NewTransitDecryptEphemeralSecretResource = func() ephemeral.EphemeralResource {
return &TransitDecryptEphemeralSecretResource{}
}

type TransitDecryptEphemeralSecretResource struct {
base.EphemeralResourceWithConfigure
}

type TransitDecryptEphemeralSecretModel struct {
base.BaseModelEphemeral

Key types.String `tfsdk:"key"`
Backend types.String `tfsdk:"backend"`
Plaintext types.String `tfsdk:"plaintext"`
Context types.String `tfsdk:"context"`
Ciphertext types.String `tfsdk:"ciphertext"`
}

type TransitDecryptEphemeralSecretAPIModel struct {
Plaintext string `json:"plaintext" mapstructure:"plaintext"`
}

func (r *TransitDecryptEphemeralSecretResource) Schema(_ context.Context, _ ephemeral.SchemaRequest, resp *ephemeral.SchemaResponse) {
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
consts.FieldKey: schema.StringAttribute{
MarkdownDescription: "Name of the decryption key to use.",
Required: true,
},
consts.FieldBackend: schema.StringAttribute{
MarkdownDescription: "The Transit secret backend the key belongs to.",
Required: true,
},
consts.FieldPlaintext: schema.StringAttribute{
MarkdownDescription: "Decrypted plain text",
Computed: true,
},
consts.FieldContext: schema.StringAttribute{
MarkdownDescription: "Specifies the context for key derivation",
Optional: true,
},
consts.FieldCiphertext: schema.StringAttribute{
MarkdownDescription: "Transit encrypted cipher text.",
Required: true,
},
},
MarkdownDescription: "Provides an ephemeral resource to decrypt a ciphertext from Vault using transit.",
}

base.MustAddBaseEphemeralSchema(&resp.Schema)
}

func (r *TransitDecryptEphemeralSecretResource) Metadata(ctx context.Context, req ephemeral.MetadataRequest, resp *ephemeral.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_transit_decrypt"
}

func (r *TransitDecryptEphemeralSecretResource) Open(ctx context.Context, req ephemeral.OpenRequest, resp *ephemeral.OpenResponse) {
var data TransitDecryptEphemeralSecretModel
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)

if resp.Diagnostics.HasError() {
return
}

c, err := client.GetClient(ctx, r.Meta(), data.Namespace.ValueString())
if err != nil {
resp.Diagnostics.AddError(errutil.ClientConfigureErr(err))
}

path := r.path(data.Backend.ValueString(), data.Key.ValueString())

payload := map[string]interface{}{
"ciphertext": data.Ciphertext.ValueString(),
"context": base64.StdEncoding.EncodeToString([]byte(data.Context.ValueString())),
}

secretResp, err := c.Logical().WriteWithContext(ctx, path, payload)
if err != nil {
resp.Diagnostics.AddError(
errutil.VaultReadErr(err),
)

return
}
if secretResp == nil {
resp.Diagnostics.AddError(
errutil.VaultReadResponseNil(),
)

return
}

var decryptedResp TransitDecryptEphemeralSecretAPIModel
err = model.ToAPIModel(secretResp.Data, &decryptedResp)
if err != nil {
resp.Diagnostics.AddError("Unable to translate Vault response Data", err.Error())
return
}

plaintext, _ := base64.StdEncoding.DecodeString(decryptedResp.Plaintext)

data.Plaintext = types.StringValue(string(plaintext))

resp.Diagnostics.Append(resp.Result.Set(ctx, &data)...)
}

func (r *TransitDecryptEphemeralSecretResource) path(backend, key string) string {
return fmt.Sprintf("/%s/decrypt/%s", backend, key)
}
83 changes: 83 additions & 0 deletions internal/vault/secrets/ephemeral/transit_decrypt_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package ephemeralsecrets_test

import (
"fmt"
"testing"

"github.com/hashicorp/terraform-plugin-go/tfprotov6"
"github.com/hashicorp/terraform-plugin-testing/echoprovider"
"github.com/hashicorp/terraform-plugin-testing/helper/acctest"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/knownvalue"
"github.com/hashicorp/terraform-plugin-testing/statecheck"
"github.com/hashicorp/terraform-plugin-testing/tfjsonpath"
"github.com/hashicorp/terraform-provider-vault/internal/providertest"
"github.com/hashicorp/terraform-provider-vault/testutil"
)

// TestAccTransitDecrypt confirms that a transit encrypted
// secret is Correctly decrypted and read into the ephemeral resource
//
// Uses the Echo Provider to test values set in ephemeral resources
// see documentation here for more details:
// https://developer.hashicorp.com/terraform/plugin/testing/acceptance-tests/ephemeral-resources#using-echo-provider-in-acceptance-tests
func TestAccTransitDecrypt(t *testing.T) {
testutil.SkipTestAcc(t)
mount := acctest.RandomWithPrefix("transit")
keyName := acctest.RandomWithPrefix("key")
secret := "password1"

resource.UnitTest(t, resource.TestCase{
PreCheck: func() { testutil.TestAccPreCheck(t) },
// Include the provider we want to test
ProtoV5ProviderFactories: providertest.ProtoV5ProviderFactories,
// Include `echo` as a v6 provider from `terraform-plugin-testing`
ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){
"echo": echoprovider.NewProviderServer(),
},
Steps: []resource.TestStep{
{
Config: testTransitSecretConfig(mount, keyName, secret),
ConfigStateChecks: []statecheck.StateCheck{
statecheck.ExpectKnownValue("echo.test_transit", tfjsonpath.New("data"), knownvalue.StringExact(secret)),
},
},
},
})
}

func testTransitSecretConfig(mount, keyName, secret string) string {
return fmt.Sprintf(`
resource "vault_mount" "test" {
path = "%s"
type = "transit"
}

resource "vault_transit_secret_backend_key" "test" {
name = "%s"
backend = vault_mount.test.path
deletion_allowed = true
}

data "vault_transit_encrypt" "encrypted" {
backend = vault_mount.test.path
key = vault_transit_secret_backend_key.test.name
plaintext = "%s"
}

ephemeral "vault_transit_decrypt" "decrypted" {
backend = vault_mount.test.path
key = vault_transit_secret_backend_key.test.name
ciphertext = data.vault_transit_encrypt.encrypted.ciphertext
}

provider "echo" {
data = ephemeral.vault_transit_decrypt.decrypted.plaintext
}

resource "echo" "test_transit" {}
`, mount, keyName, secret)
}
58 changes: 58 additions & 0 deletions website/docs/ephemeral-resources/transit_decrypt.html.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
---
layout: "vault"
page_title: "Vault: ephemeral vault_transit_decrypt resource"
sidebar_current: "docs-vault-ephemeral-transit-decrypt"
description: |-
Decrypt ciphertext using a Vault Transit encryption key.
---

# vault_transit_decrypt

This is a data source which can be used to decrypt ciphertext using a Vault Transit key.

Decrypts an ephemeral cyphertext from the Vault Transit engine that is not stored in the remote TF state.
For more information, please refer to [the Vault documentation](https://developer.hashicorp.com/vault/docs/secrets/transit)
for the Transit engine.

## Example Usage

```hcl
resource "vault_mount" "transit" {
path = "transit"
type = "transit"
}

resource "vault_transit_secret_backend_key" "my-key" {
name = "my-key"
backend = vault_mount.transit.path
deletion_allowed = true
}

data "vault_transit_encrypt" "encrypted" {
backend = vault_mount.transit.path
key = vault_transit_secret_backend_key.my-key.name
plaintext = "foo"
}

ephemeral "vault_transit_decrypt" "decrypted" {
backend = vault_mount.transit.path
key = vault_transit_secret_backend_key.my-key.name
ciphertext = data.vault_transit_encrypt.encrypted.ciphertext
}
```

## Argument Reference

Each document configuration may have one or more `rule` blocks, which each accept the following arguments:

- `key` - (Required) Specifies the name of the transit key to decrypt against.

- `backend` - (Required) The path the transit secret backend is mounted at, with no leading or trailing `/`.

- `ciphertext` - (Required) Ciphertext to be decoded.

- `context` - (Optional) Context for key derivation. This is required if key derivation is enabled for this key.

## Attributes Reference

- `plaintext` - Decrypted plaintext returned from Vault
3 changes: 3 additions & 0 deletions website/vault.erb
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,9 @@
<li<%= sidebar_current("docs-vault-ephemeral-database-secret") %>>
<a href="/docs/providers/vault/ephemeral-resources/database_secret.html">vault_database_secret</a>
</li>
<li<%= sidebar_current("docs-vault-ephemeral-transit-decrypt") %>>
<a href="/docs/providers/vault/ephemeral-resources/transit_decrypt.html">vault_transit_decrypt</a>
</li>

</ul>
</li>
Expand Down
Loading