Skip to content
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ IMPROVEMENTS:
* Add support for configuration parameters (`allowed_ipv4_addresses`,`allowed_ipv6_addresses`,`allowed_ports`,`disable_strict_networking`,`secrets_location`,`environment_name`) in `vault_secrets_sync_gh_destination` resource. Requires Vault 1.18+ for `secrets_location`,`environment_name`.Requires Vault 1.19+ for `allowed_ipv4_addresses`,`allowed_ipv6_addresses`,`allowed_ports`,`disable_strict_networking`.([#2697](https://github.com/hashicorp/terraform-provider-vault/pull/2697)).
* Add support for `tls_server_name` , `local_datacenter`, `socket_keep_alive`, `consistency` and `username_template` parameters for Cassandra in `vault_database_secret_backend_connection` resource. ([#2677](https://github.com/hashicorp/terraform-provider-vault/pull/2677))
* `vault_secrets_sync_aws_destination`: Add support for networking configuration parameters `allowed_ipv4_addresses`, `allowed_ipv6_addresses`, `allowed_ports`, and `disable_strict_networking` to control outbound connections from Vault to AWS Secrets Manager. Requires Vault 1.19.0+.([#2698](https://github.com/hashicorp/terraform-provider-vault/pull/2698))
* Add support for Enterprise Plugins in `vault_plugin` resource. ([#2707](https://github.com/hashicorp/terraform-provider-vault/pull/2707))
* Updated dependencies:
* `github.com/hashicorp/go-secure-stdlib/awsutil` v0.3.0 -> v2.1.1
* Docs: fix heredoc example for LDAP dynamic role LDIFs ([#2728]https://github.com/hashicorp/terraform-provider-vault/pull/2728)
Expand Down
25 changes: 23 additions & 2 deletions vault/resource_plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"fmt"
"log"
"regexp"
"strings"

"github.com/hashicorp/go-version"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
Expand All @@ -32,7 +33,7 @@ var (
// Version regex is intentionally loose, its main purpose is to disallow
// slashes so they can be used to delineate from the name. Version segment
// is optional.
pluginIDRegex = regexp.MustCompile(`^(auth|secret|database)(?:/version/([0-9a-zA-Z.-]+?))?/name/(.+)$`)
pluginIDRegex = regexp.MustCompile(`^(auth|secret|database)(?:/version/([0-9a-zA-Z.+-]+?))?/name/(.+)$`)
)

func pluginFromID(id string) (typ string, name string, version string) {
Expand Down Expand Up @@ -62,6 +63,7 @@ func pluginResource() *schema.Resource {
UpdateContext: pluginWrite,
ReadContext: pluginRead,
DeleteContext: pluginDelete,
CustomizeDiff: pluginCustomizeDiff,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Expand Down Expand Up @@ -90,7 +92,7 @@ func pluginResource() *schema.Resource {
fieldSHA256: {
Type: schema.TypeString,
Description: "SHA256 sum of the plugin binary.",
Required: true,
Optional: true,
},
fieldCommand: {
Type: schema.TypeString,
Expand Down Expand Up @@ -208,6 +210,11 @@ func pluginRead(ctx context.Context, d *schema.ResourceData, meta interface{}) d
return diag.Errorf("error reading plugin %q: %s", d.Id(), err)
}

// Unset SHA256 for enteprise plugin version
if strings.HasSuffix(version, "+ent") {
resp.SHA256 = ""
}

result := map[string]any{
consts.FieldType: typ,
consts.FieldName: name,
Expand Down Expand Up @@ -289,3 +296,17 @@ func diffSuppressEqualSemver(_, oldValue, newValue string, _ *schema.ResourceDat
}
return false
}

func pluginCustomizeDiff(ctx context.Context, d *schema.ResourceDiff, meta interface{}) error {
curVersion := d.Get(consts.FieldVersion).(string)
if strings.HasSuffix(curVersion, "+ent") {
if d.Get(fieldSHA256).(string) != "" {
return fmt.Errorf("field %s needs to be empty for enterprise plugin", fieldSHA256)
}
} else {
if d.Get(fieldSHA256).(string) == "" {
return fmt.Errorf("field %s needs to be set for non enterprise plugin", fieldSHA256)
}
}
return nil
}
57 changes: 57 additions & 0 deletions vault/resource_plugin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import (
)

const envPluginCommand = "VAULT_PLUGIN_COMMAND"
const envPluginEntCommand = "VAULT_PLUGIN_ENT_COMMAND"
const envPluginEntVersion = "VAULT_PLUGIN_ENT_VERSION"

func TestPlugin(t *testing.T) {
const (
Expand Down Expand Up @@ -81,6 +83,48 @@ func TestPlugin(t *testing.T) {
})
}

func TestPlugin_ent(t *testing.T) {
const (
typ = "database"
args = `["--foo"]`
env = `["FOO=BAR"]`

argsUpdated = `["--bar"]`
envUpdated = `["FOO=BAZ"]`
)

destName := acctest.RandomWithPrefix("tf/plugin")
resourceName := "vault_plugin.test"

// VAULT_PLUGIN_ENT_COMMAND,VAULT_PLUGIN_ENT_VERSION should be set to the name of the plugin executable
// in the configured plugin_directory for Vault.
cmd := os.Getenv(envPluginEntCommand)
version := os.Getenv(envPluginEntVersion)

resource.Test(t, resource.TestCase{
ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(context.Background(), t),
PreCheck: func() {
testutil.TestAccPreCheck(t)
testutil.SkipTestEnvUnset(t, envPluginEntCommand, envPluginEntVersion)
SkipIfAPIVersionLT(t, testProvider.Meta(), provider.VaultVersion112)
},
Steps: []resource.TestStep{
{
Config: testPluginConfig_ent(typ, destName, version, cmd, args, env),
Check: resource.ComposeTestCheckFunc(

resource.TestCheckResourceAttr(resourceName, consts.FieldType, typ),
resource.TestCheckResourceAttr(resourceName, consts.FieldName, destName),
resource.TestCheckResourceAttr(resourceName, consts.FieldVersion, version),
resource.TestCheckResourceAttr(resourceName, fieldCommand, cmd),
testValidateList(resourceName, fieldArgs, []string{"--foo"}),
testValidateList(resourceName, fieldEnv, []string{"FOO=BAR"}),
),
},
},
})
}

func testPluginConfig(pluginType, name, version, sha256, command, args, env string) string {
return fmt.Sprintf(`
resource "vault_plugin" "test" {
Expand All @@ -95,6 +139,19 @@ resource "vault_plugin" "test" {
`, pluginType, name, version, sha256, command, args, env)
}

func testPluginConfig_ent(pluginType, name, version, command, args, env string) string {
return fmt.Sprintf(`
resource "vault_plugin" "test" {
type = "%s"
name = "%s"
version = "%s"
command = "%s"
args = %s
env = %s
}
`, pluginType, name, version, command, args, env)
}

func testValidateList(resourceName, attr string, expected []string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, err := testutil.GetResourceFromRootModule(s, resourceName)
Expand Down
15 changes: 14 additions & 1 deletion website/docs/r/plugin.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ For more information on managing external plugins, please refer to the Vault

## Example Usage

Register a standard plugin

```hcl
resource "vault_plugin" "jwt" {
type = "auth"
Expand All @@ -39,6 +41,17 @@ resource "vault_auth_backend" "jwt_auth" {
}
```

Register an enterprise plugin

```hcl
resource "vault_plugin" "oracle" {
type = "database"
name = "vault-plugin-database-oracle"
command = "vault-plugin-database-oracle"
version = "v0.13.0+ent"
}
```

## Argument Reference

The following arguments are supported:
Expand All @@ -49,7 +62,7 @@ The following arguments are supported:

* `version` - (Optional) Semantic version of the plugin.

* `sha256` - (Required) SHA256 sum of the plugin binary.
* `sha256` - (Optional) SHA256 sum of the plugin binary. Need to be set for non-enterprise plugin.

* `command` - (Required) Command to execute the plugin, relative to the server's configured `plugin_directory`.

Expand Down