From 00e266b6adc35258e2d7faaff32c95d84a8d1480 Mon Sep 17 00:00:00 2001
From: murali-partha <137029787+murali-partha@users.noreply.github.com>
Date: Thu, 5 Dec 2024 22:58:31 +0530
Subject: [PATCH] Azure Rotating Secrets Changes (#1143)
* adding azure integration for rotating secrets changes
* adding test file
* adding changelog file
* updateing validation
* fixing test failures
* docs
* minor changes
* Update internal/provider/vaultsecrets/resource_vault_secrets_rotating_secret.go
Co-authored-by: Max Coulombe <109547106+maxcoulombe@users.noreply.github.com>
* addressing review comments
* addressing review comments
* addressing review comments
---------
Co-authored-by: Max Coulombe <109547106+maxcoulombe@users.noreply.github.com>
---
.changelog/1143.txt | 3 +
.../vault_secrets_integration_azure.md | 75 ++++
.../vault_secrets_rotating_secret.md | 22 ++
.../import.sh | 4 +
.../resource.tf | 9 +
.../resource.tf | 11 +
go.mod | 2 +-
go.sum | 4 +-
internal/provider/provider.go | 1 +
...esource_vault_secrets_integration_azure.go | 359 ++++++++++++++++++
...ce_vault_secrets_integration_azure_test.go | 190 +++++++++
.../resource_vault_secrets_rotating_secret.go | 42 +-
.../vaultsecrets/rotating_secret_azure.go | 89 +++++
.../vaultsecrets/vault_secrets_utils.go | 1 +
14 files changed, 803 insertions(+), 9 deletions(-)
create mode 100644 .changelog/1143.txt
create mode 100644 docs/resources/vault_secrets_integration_azure.md
create mode 100644 examples/resources/hcp_vault_secrets_integration_azure/import.sh
create mode 100644 examples/resources/hcp_vault_secrets_integration_azure/resource.tf
create mode 100644 internal/provider/vaultsecrets/resource_vault_secrets_integration_azure.go
create mode 100644 internal/provider/vaultsecrets/resource_vault_secrets_integration_azure_test.go
create mode 100644 internal/provider/vaultsecrets/rotating_secret_azure.go
diff --git a/.changelog/1143.txt b/.changelog/1143.txt
new file mode 100644
index 000000000..db72e3c80
--- /dev/null
+++ b/.changelog/1143.txt
@@ -0,0 +1,3 @@
+```release-note:feature
+add vault_secrets_integration_azure resource and add support for azure secrets to vault_secrets_rotating_secret resource
+```
\ No newline at end of file
diff --git a/docs/resources/vault_secrets_integration_azure.md b/docs/resources/vault_secrets_integration_azure.md
new file mode 100644
index 000000000..122de295e
--- /dev/null
+++ b/docs/resources/vault_secrets_integration_azure.md
@@ -0,0 +1,75 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "hcp_vault_secrets_integration_azure Resource - terraform-provider-hcp"
+subcategory: ""
+description: |-
+ The Vault Secrets Azure integration resource manages an Azure integration.
+---
+
+# hcp_vault_secrets_integration_azure (Resource)
+
+The Vault Secrets Azure integration resource manages an Azure integration.
+
+## Example Usage
+
+```terraform
+resource "hcp_vault_secrets_integration_azure" "example" {
+ name = "my-azure-1"
+ capabilities = ["ROTATION"]
+ client_secret = {
+ "tenant_id" = "7eb3...",
+ "client_id" = "9de0...",
+ "client_secret" = "WZk8..."
+ }
+}
+```
+
+
+## Schema
+
+### Required
+
+- `capabilities` (Set of String) Capabilities enabled for the integration. See the Vault Secrets documentation for the list of supported capabilities per provider.
+- `name` (String) The Vault Secrets integration name.
+
+### Optional
+
+- `client_secret` (Attributes) Azure client secret used to authenticate against the target Azure application. Cannot be used with `federated_workload_identity`. (see [below for nested schema](#nestedatt--client_secret))
+- `federated_workload_identity` (Attributes) (Recommended) Federated identity configuration to authenticate against the target Azure application. Cannot be used with `client_secret`. (see [below for nested schema](#nestedatt--federated_workload_identity))
+- `project_id` (String) HCP project ID that owns the HCP Vault Secrets integration. Inferred from the provider configuration if omitted.
+
+### Read-Only
+
+- `organization_id` (String) HCP organization ID that owns the HCP Vault Secrets integration.
+- `resource_id` (String) Resource ID used to uniquely identify the integration instance on the HCP platform.
+- `resource_name` (String) Resource name used to uniquely identify the integration instance on the HCP platform.
+
+
+### Nested Schema for `client_secret`
+
+Required:
+
+- `client_id` (String) Azure client ID corresponding to the Azure application.
+- `client_secret` (String) Secret value corresponding to the Azure client secret.
+- `tenant_id` (String) Azure tenant ID corresponding to the Azure application.
+
+
+
+### Nested Schema for `federated_workload_identity`
+
+Required:
+
+- `audience` (String) Audience configured on the Azure federated identity credentials to federate access with HCP.
+- `client_id` (String) Azure client ID corresponding to the Azure application.
+- `tenant_id` (String) Azure tenant ID corresponding to the Azure application.
+
+## Import
+
+Import is supported using the following syntax:
+
+```shell
+# Vault Secrets Azure Integration can be imported by specifying the name of the integration
+# Note that since the client secret is never returned on the Vault Secrets API,
+# the next plan or apply will show a diff for that field.
+terraform import hcp_vault_secrets_integration_azure.example my-azure-1
+```
diff --git a/docs/resources/vault_secrets_rotating_secret.md b/docs/resources/vault_secrets_rotating_secret.md
index 388d81f8c..4c24966b2 100644
--- a/docs/resources/vault_secrets_rotating_secret.md
+++ b/docs/resources/vault_secrets_rotating_secret.md
@@ -66,6 +66,18 @@ resource "hcp_vault_secrets_rotating_secret" "example_confluent" {
service_account_id = ""
}
}
+
+resource "hcp_vault_secrets_rotating_secret" "example_azure" {
+ app_name = "my-app-1"
+ secret_provider = "azure"
+ name = "my_azure_1_secret"
+ integration_name = "my-azure-1"
+ rotation_policy_name = "built-in:60-days-2-active"
+ azure_application_password = {
+ app_object_id = ""
+ app_client_id = ""
+ }
+}
```
@@ -82,6 +94,7 @@ resource "hcp_vault_secrets_rotating_secret" "example_confluent" {
### Optional
- `aws_access_keys` (Attributes) AWS configuration to manage the access key rotation for the given IAM user. Required if `secret_provider` is `aws`. (see [below for nested schema](#nestedatt--aws_access_keys))
+- `azure_application_password` (Attributes) Azure configuration to manage the application password rotation for the given application. Required if `secret_provider` is `Azure`. (see [below for nested schema](#nestedatt--azure_application_password))
- `confluent_service_account` (Attributes) Confluent configuration to manage the cloud api key rotation for the given service account. Required if `secret_provider` is `confluent`. (see [below for nested schema](#nestedatt--confluent_service_account))
- `gcp_service_account_key` (Attributes) GCP configuration to manage the service account key rotation for the given service account. Required if `secret_provider` is `gcp`. (see [below for nested schema](#nestedatt--gcp_service_account_key))
- `mongodb_atlas_user` (Attributes) MongoDB Atlas configuration to manage the user password rotation on the given database. Required if `secret_provider` is `mongodb_atlas`. (see [below for nested schema](#nestedatt--mongodb_atlas_user))
@@ -100,6 +113,15 @@ Required:
- `iam_username` (String) AWS IAM username to rotate the access keys for.
+
+### Nested Schema for `azure_application_password`
+
+Required:
+
+- `app_client_id` (String) Application client ID to rotate the application password for.
+- `app_object_id` (String) Application object ID to rotate the application password for.
+
+
### Nested Schema for `confluent_service_account`
diff --git a/examples/resources/hcp_vault_secrets_integration_azure/import.sh b/examples/resources/hcp_vault_secrets_integration_azure/import.sh
new file mode 100644
index 000000000..ac22c516c
--- /dev/null
+++ b/examples/resources/hcp_vault_secrets_integration_azure/import.sh
@@ -0,0 +1,4 @@
+# Vault Secrets Azure Integration can be imported by specifying the name of the integration
+# Note that since the client secret is never returned on the Vault Secrets API,
+# the next plan or apply will show a diff for that field.
+terraform import hcp_vault_secrets_integration_azure.example my-azure-1
diff --git a/examples/resources/hcp_vault_secrets_integration_azure/resource.tf b/examples/resources/hcp_vault_secrets_integration_azure/resource.tf
new file mode 100644
index 000000000..f33fe393f
--- /dev/null
+++ b/examples/resources/hcp_vault_secrets_integration_azure/resource.tf
@@ -0,0 +1,9 @@
+resource "hcp_vault_secrets_integration_azure" "example" {
+ name = "my-azure-1"
+ capabilities = ["ROTATION"]
+ client_secret = {
+ "tenant_id" = "7eb3...",
+ "client_id" = "9de0...",
+ "client_secret" = "WZk8..."
+ }
+}
\ No newline at end of file
diff --git a/examples/resources/hcp_vault_secrets_rotating_secret/resource.tf b/examples/resources/hcp_vault_secrets_rotating_secret/resource.tf
index 3aea335ba..ea115d9c4 100644
--- a/examples/resources/hcp_vault_secrets_rotating_secret/resource.tf
+++ b/examples/resources/hcp_vault_secrets_rotating_secret/resource.tf
@@ -53,3 +53,14 @@ resource "hcp_vault_secrets_rotating_secret" "example_confluent" {
}
}
+resource "hcp_vault_secrets_rotating_secret" "example_azure" {
+ app_name = "my-app-1"
+ secret_provider = "azure"
+ name = "my_azure_1_secret"
+ integration_name = "my-azure-1"
+ rotation_policy_name = "built-in:60-days-2-active"
+ azure_application_password = {
+ app_object_id = ""
+ app_client_id = ""
+ }
+}
diff --git a/go.mod b/go.mod
index 9db7b44b6..1f287ace3 100644
--- a/go.mod
+++ b/go.mod
@@ -11,7 +11,7 @@ require (
github.com/hashicorp/go-cty v1.4.1-0.20200723130312-85980079f637
github.com/hashicorp/go-uuid v1.0.3
github.com/hashicorp/go-version v1.7.0
- github.com/hashicorp/hcp-sdk-go v0.123.0
+ github.com/hashicorp/hcp-sdk-go v0.124.0
github.com/hashicorp/terraform-plugin-docs v0.19.4
github.com/hashicorp/terraform-plugin-framework v1.5.0
github.com/hashicorp/terraform-plugin-framework-validators v0.12.0
diff --git a/go.sum b/go.sum
index 93bbb3b81..3d88edefb 100644
--- a/go.sum
+++ b/go.sum
@@ -124,8 +124,8 @@ github.com/hashicorp/hc-install v0.7.0 h1:Uu9edVqjKQxxuD28mR5TikkKDd/p55S8vzPC16
github.com/hashicorp/hc-install v0.7.0/go.mod h1:ELmmzZlGnEcqoUMKUuykHaPCIR1sYLYX+KSggWSKZuA=
github.com/hashicorp/hcl/v2 v2.19.1 h1://i05Jqznmb2EXqa39Nsvyan2o5XyMowW5fnCKW5RPI=
github.com/hashicorp/hcl/v2 v2.19.1/go.mod h1:ThLC89FV4p9MPW804KVbe/cEXoQ8NZEh+JtMeeGErHE=
-github.com/hashicorp/hcp-sdk-go v0.123.0 h1:kUf/kSCVkQ4XXyny8GUyUWjvIIIanGRRkhRmgj2lC+4=
-github.com/hashicorp/hcp-sdk-go v0.123.0/go.mod h1:vQ4fzdL1AmhIAbCw+4zmFe5Hbpajj3NvRWkJoVuxmAk=
+github.com/hashicorp/hcp-sdk-go v0.124.0 h1:Th4qCAAqlPrC5s2riHnMTsHFIZ5GsFWzK7l2W7vqsN4=
+github.com/hashicorp/hcp-sdk-go v0.124.0/go.mod h1:vQ4fzdL1AmhIAbCw+4zmFe5Hbpajj3NvRWkJoVuxmAk=
github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/terraform-exec v0.21.0 h1:uNkLAe95ey5Uux6KJdua6+cv8asgILFVWkd/RG0D2XQ=
diff --git a/internal/provider/provider.go b/internal/provider/provider.go
index e739f2c82..288536ef7 100644
--- a/internal/provider/provider.go
+++ b/internal/provider/provider.go
@@ -159,6 +159,7 @@ func (p *ProviderFramework) Resources(ctx context.Context) []func() resource.Res
vaultsecrets.NewVaultSecretsIntegrationMongoDBAtlasResource,
vaultsecrets.NewVaultSecretsIntegrationTwilioResource,
vaultsecrets.NewVaultSecretsIntegrationsConfluentResource,
+ vaultsecrets.NewVaultSecretsIntegrationAzureResource,
vaultsecrets.NewVaultSecretsDynamicSecretResource,
vaultsecrets.NewVaultSecretsRotatingSecretResource,
// IAM
diff --git a/internal/provider/vaultsecrets/resource_vault_secrets_integration_azure.go b/internal/provider/vaultsecrets/resource_vault_secrets_integration_azure.go
new file mode 100644
index 000000000..41aff0019
--- /dev/null
+++ b/internal/provider/vaultsecrets/resource_vault_secrets_integration_azure.go
@@ -0,0 +1,359 @@
+// Copyright (c) HashiCorp, Inc.
+// SPDX-License-Identifier: MPL-2.0
+
+package vaultsecrets
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/hashicorp/hcp-sdk-go/clients/cloud-vault-secrets/stable/2023-11-28/client/secret_service"
+ secretmodels "github.com/hashicorp/hcp-sdk-go/clients/cloud-vault-secrets/stable/2023-11-28/models"
+ "github.com/hashicorp/terraform-plugin-framework-validators/objectvalidator"
+ "github.com/hashicorp/terraform-plugin-framework/attr"
+ "github.com/hashicorp/terraform-plugin-framework/diag"
+ "github.com/hashicorp/terraform-plugin-framework/path"
+ "github.com/hashicorp/terraform-plugin-framework/resource"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/hashicorp/terraform-plugin-framework/types/basetypes"
+ "github.com/hashicorp/terraform-provider-hcp/internal/clients"
+ "github.com/hashicorp/terraform-provider-hcp/internal/provider/modifiers"
+ "golang.org/x/exp/maps"
+)
+
+type IntegrationAzure struct {
+ // Input fields
+ ProjectID types.String `tfsdk:"project_id"`
+ Name types.String `tfsdk:"name"`
+ Capabilities types.Set `tfsdk:"capabilities"`
+ ClientSecret types.Object `tfsdk:"client_secret"`
+ FederatedWorkloadIdentity types.Object `tfsdk:"federated_workload_identity"`
+
+ // Computed fields
+ OrganizationID types.String `tfsdk:"organization_id"`
+ ResourceID types.String `tfsdk:"resource_id"`
+ ResourceName types.String `tfsdk:"resource_name"`
+
+ // Inner API-compatible models derived from the Terraform fields
+ capabilities []*secretmodels.Secrets20231128Capability `tfsdk:"-"`
+ clientSecret *secretmodels.Secrets20231128AzureClientSecretRequest `tfsdk:"-"`
+ federatedWorkloadIdentity *secretmodels.Secrets20231128AzureFederatedWorkloadIdentityRequest `tfsdk:"-"`
+}
+
+// Helper structs to help populate concrete targets from types.Object fields
+type clientSecret struct {
+ TenantID types.String `tfsdk:"tenant_id"`
+ ClientID types.String `tfsdk:"client_id"`
+ ClientSecret types.String `tfsdk:"client_secret"`
+}
+
+type azureFederatedWorkloadIdentity struct {
+ TenantID types.String `tfsdk:"tenant_id"`
+ ClientID types.String `tfsdk:"client_id"`
+ Audience types.String `tfsdk:"audience"`
+}
+
+var _ resource.Resource = &resourceVaultSecretsIntegrationAzure{}
+var _ resource.ResourceWithConfigure = &resourceVaultSecretsIntegrationAzure{}
+var _ resource.ResourceWithModifyPlan = &resourceVaultSecretsIntegrationAzure{}
+var _ resource.ResourceWithImportState = &resourceVaultSecretsIntegrationAzure{}
+
+func NewVaultSecretsIntegrationAzureResource() resource.Resource {
+ return &resourceVaultSecretsIntegrationAzure{}
+}
+
+type resourceVaultSecretsIntegrationAzure struct {
+ client *clients.Client
+}
+
+func (r *resourceVaultSecretsIntegrationAzure) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_vault_secrets_integration_azure"
+}
+
+func (r *resourceVaultSecretsIntegrationAzure) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
+ attributes := map[string]schema.Attribute{
+ "client_secret": schema.SingleNestedAttribute{
+ Description: "Azure client secret used to authenticate against the target Azure application. Cannot be used with `federated_workload_identity`.",
+ Optional: true,
+ Attributes: map[string]schema.Attribute{
+ "tenant_id": schema.StringAttribute{
+ Description: "Azure tenant ID corresponding to the Azure application.",
+ Required: true,
+ },
+ "client_id": schema.StringAttribute{
+ Description: "Azure client ID corresponding to the Azure application.",
+ Required: true,
+ },
+ "client_secret": schema.StringAttribute{
+ Description: "Secret value corresponding to the Azure client secret.",
+ Required: true,
+ },
+ },
+ Validators: []validator.Object{
+ objectvalidator.ExactlyOneOf(path.Expressions{
+ path.MatchRoot("federated_workload_identity"),
+ }...),
+ },
+ },
+ "federated_workload_identity": schema.SingleNestedAttribute{
+ Description: "(Recommended) Federated identity configuration to authenticate against the target Azure application. Cannot be used with `client_secret`.",
+ Optional: true,
+ Attributes: map[string]schema.Attribute{
+ "tenant_id": schema.StringAttribute{
+ Description: "Azure tenant ID corresponding to the Azure application.",
+ Required: true,
+ },
+ "client_id": schema.StringAttribute{
+ Description: "Azure client ID corresponding to the Azure application.",
+ Required: true,
+ },
+ "audience": schema.StringAttribute{
+ Description: "Audience configured on the Azure federated identity credentials to federate access with HCP.",
+ Required: true,
+ },
+ },
+ Validators: []validator.Object{
+ objectvalidator.ExactlyOneOf(path.Expressions{
+ path.MatchRoot("client_secret"),
+ }...),
+ },
+ },
+ }
+
+ maps.Copy(attributes, locationAttributes)
+ maps.Copy(attributes, sharedIntegrationAttributes)
+
+ resp.Schema = schema.Schema{
+ MarkdownDescription: "The Vault Secrets Azure integration resource manages an Azure integration.",
+ Attributes: attributes,
+ }
+}
+
+func (r *resourceVaultSecretsIntegrationAzure) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
+ if req.ProviderData == nil {
+ return
+ }
+ client, ok := req.ProviderData.(*clients.Client)
+ if !ok {
+ resp.Diagnostics.AddError(
+ "Unexpected Data Source Configure Type",
+ fmt.Sprintf("Expected *clients.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),
+ )
+ return
+ }
+ r.client = client
+}
+
+func (r *resourceVaultSecretsIntegrationAzure) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) {
+ modifiers.ModifyPlanForDefaultProjectChange(ctx, r.client.Config.ProjectID, req.State, req.Config, req.Plan, resp)
+}
+
+func (r *resourceVaultSecretsIntegrationAzure) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
+ resp.Diagnostics.Append(decorateOperation[*IntegrationAzure](ctx, r.client, &resp.State, req.State.Get, "reading", func(i hvsResource) (any, error) {
+ integration, ok := i.(*IntegrationAzure)
+ if !ok {
+ return nil, fmt.Errorf("invalid integration type, expected *IntegrationAzure, got: %T, this is a bug on the provider", i)
+ }
+
+ response, err := r.client.VaultSecrets.GetAzureIntegration(
+ secret_service.NewGetAzureIntegrationParamsWithContext(ctx).
+ WithOrganizationID(integration.OrganizationID.ValueString()).
+ WithProjectID(integration.ProjectID.ValueString()).
+ WithName(integration.Name.ValueString()), nil)
+ if err != nil && !clients.IsResponseCodeNotFound(err) {
+ return nil, err
+ }
+ if response == nil || response.Payload == nil {
+ return nil, nil
+ }
+ return response.Payload.Integration, nil
+ })...)
+}
+
+func (r *resourceVaultSecretsIntegrationAzure) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
+ resp.Diagnostics.Append(decorateOperation[*IntegrationAzure](ctx, r.client, &resp.State, req.Plan.Get, "creating", func(i hvsResource) (any, error) {
+ integration, ok := i.(*IntegrationAzure)
+ if !ok {
+ return nil, fmt.Errorf("invalid integration type, expected *IntegrationAzure, got: %T, this is a bug on the provider", i)
+ }
+
+ response, err := r.client.VaultSecrets.CreateAzureIntegration(&secret_service.CreateAzureIntegrationParams{
+ Body: &secretmodels.SecretServiceCreateAzureIntegrationBody{
+ Capabilities: integration.capabilities,
+ FederatedWorkloadIdentity: integration.federatedWorkloadIdentity,
+ Name: integration.Name.ValueString(),
+ ClientSecret: integration.clientSecret,
+ },
+ OrganizationID: integration.OrganizationID.ValueString(),
+ ProjectID: integration.ProjectID.ValueString(),
+ }, nil)
+ if err != nil && !clients.IsResponseCodeNotFound(err) {
+ return nil, err
+ }
+ if response == nil || response.Payload == nil {
+ return nil, nil
+ }
+ return response.Payload.Integration, nil
+ })...)
+}
+
+func (r *resourceVaultSecretsIntegrationAzure) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
+ resp.Diagnostics.Append(decorateOperation[*IntegrationAzure](ctx, r.client, &resp.State, req.Plan.Get, "updating", func(i hvsResource) (any, error) {
+ integration, ok := i.(*IntegrationAzure)
+ if !ok {
+ return nil, fmt.Errorf("invalid integration type, expected *IntegrationAzure, got: %T, this is a bug on the provider", i)
+ }
+
+ response, err := r.client.VaultSecrets.UpdateAzureIntegration(&secret_service.UpdateAzureIntegrationParams{
+ Body: &secretmodels.SecretServiceUpdateAzureIntegrationBody{
+ Capabilities: integration.capabilities,
+ FederatedWorkloadIdentity: integration.federatedWorkloadIdentity,
+ ClientSecret: integration.clientSecret,
+ },
+ Name: integration.Name.ValueString(),
+ OrganizationID: integration.OrganizationID.ValueString(),
+ ProjectID: integration.ProjectID.ValueString(),
+ }, nil)
+ if err != nil && !clients.IsResponseCodeNotFound(err) {
+ return nil, err
+ }
+ if response == nil || response.Payload == nil {
+ return nil, nil
+ }
+ return response.Payload.Integration, nil
+ })...)
+}
+
+func (r *resourceVaultSecretsIntegrationAzure) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
+ resp.Diagnostics.Append(decorateOperation[*IntegrationAzure](ctx, r.client, &resp.State, req.State.Get, "deleting", func(i hvsResource) (any, error) {
+ integration, ok := i.(*IntegrationAzure)
+ if !ok {
+ return nil, fmt.Errorf("invalid integration type, expected *IntegrationAzure, got: %T, this is a bug on the provider", i)
+ }
+
+ _, err := r.client.VaultSecrets.DeleteAzureIntegration(
+ secret_service.NewDeleteAzureIntegrationParams().
+ WithOrganizationID(integration.OrganizationID.ValueString()).
+ WithProjectID(integration.ProjectID.ValueString()).
+ WithName(integration.Name.ValueString()), nil)
+ if err != nil && !clients.IsResponseCodeNotFound(err) {
+ return nil, err
+ }
+ return nil, nil
+ })...)
+}
+
+func (r *resourceVaultSecretsIntegrationAzure) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
+ // The Vault Secrets API does not return sensitive values like the client secret, so they will be initialized to an empty value
+ // It means the first plan/apply after a successful import will always show a diff for the client secret.
+ resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("organization_id"), r.client.Config.OrganizationID)...)
+ resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), r.client.Config.ProjectID)...)
+ resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("name"), req.ID)...)
+}
+
+var _ hvsResource = &IntegrationAzure{}
+
+func (i *IntegrationAzure) projectID() types.String {
+ return i.ProjectID
+}
+
+func (i *IntegrationAzure) initModel(ctx context.Context, orgID, projID string) diag.Diagnostics {
+ // Init fields that depend on the Terraform provider configuration
+ i.OrganizationID = types.StringValue(orgID)
+ i.ProjectID = types.StringValue(projID)
+
+ // Init the HVS domain models from the Terraform domain models
+ var capabilities []types.String
+ diags := i.Capabilities.ElementsAs(ctx, &capabilities, false)
+ if diags.HasError() {
+ return diags
+ }
+ for _, c := range capabilities {
+ i.capabilities = append(i.capabilities, secretmodels.Secrets20231128Capability(c.ValueString()).Pointer())
+ }
+
+ if !i.ClientSecret.IsNull() {
+ cs := clientSecret{}
+ diags = i.ClientSecret.As(ctx, &cs, basetypes.ObjectAsOptions{})
+ if diags.HasError() {
+ return diags
+ }
+
+ i.clientSecret = &secretmodels.Secrets20231128AzureClientSecretRequest{
+ TenantID: cs.TenantID.ValueString(),
+ ClientID: cs.ClientID.ValueString(),
+ ClientSecret: cs.ClientSecret.ValueString(),
+ }
+ }
+
+ if !i.FederatedWorkloadIdentity.IsNull() {
+ fwi := azureFederatedWorkloadIdentity{}
+ diags = i.FederatedWorkloadIdentity.As(ctx, &fwi, basetypes.ObjectAsOptions{})
+ if diags.HasError() {
+ return diags
+ }
+
+ i.federatedWorkloadIdentity = &secretmodels.Secrets20231128AzureFederatedWorkloadIdentityRequest{
+ Audience: fwi.Audience.ValueString(),
+ TenantID: fwi.TenantID.ValueString(),
+ ClientID: fwi.ClientID.ValueString(),
+ }
+ }
+
+ return diag.Diagnostics{}
+}
+
+func (i *IntegrationAzure) fromModel(ctx context.Context, orgID, projID string, model any) diag.Diagnostics {
+ diags := diag.Diagnostics{}
+
+ integrationModel, ok := model.(*secretmodels.Secrets20231128AzureIntegration)
+ if !ok {
+ diags.AddError("Invalid model type, this is a bug on the provider.", fmt.Sprintf("Expected *secretmodels.Secrets20231128AzureIntegration, got: %T", model))
+ return diags
+ }
+
+ i.OrganizationID = types.StringValue(orgID)
+ i.ProjectID = types.StringValue(projID)
+ i.ResourceID = types.StringValue(integrationModel.ResourceID)
+ i.ResourceName = types.StringValue(integrationModel.ResourceName)
+ i.Name = types.StringValue(integrationModel.Name)
+
+ var values []attr.Value
+ for _, c := range integrationModel.Capabilities {
+ values = append(values, types.StringValue(string(*c)))
+ }
+ i.Capabilities, diags = types.SetValue(types.StringType, values)
+ if diags.HasError() {
+ return diags
+ }
+
+ if integrationModel.ClientSecret != nil {
+ clientSecret := ""
+ if i.clientSecret != nil {
+ clientSecret = i.clientSecret.ClientSecret
+ }
+ i.ClientSecret, diags = types.ObjectValue(i.ClientSecret.AttributeTypes(ctx), map[string]attr.Value{
+ "tenant_id": types.StringValue(integrationModel.ClientSecret.TenantID),
+ "client_id": types.StringValue(integrationModel.ClientSecret.ClientID),
+ "client_secret": types.StringValue(clientSecret),
+ })
+ if diags.HasError() {
+ return diags
+ }
+ }
+
+ if integrationModel.FederatedWorkloadIdentity != nil {
+ i.FederatedWorkloadIdentity, diags = types.ObjectValue(i.FederatedWorkloadIdentity.AttributeTypes(ctx), map[string]attr.Value{
+ "tenant_id": types.StringValue(integrationModel.FederatedWorkloadIdentity.TenantID),
+ "client_id": types.StringValue(integrationModel.FederatedWorkloadIdentity.ClientID),
+ "audience": types.StringValue(integrationModel.FederatedWorkloadIdentity.Audience),
+ })
+ if diags.HasError() {
+ return diags
+ }
+ }
+
+ return diags
+}
diff --git a/internal/provider/vaultsecrets/resource_vault_secrets_integration_azure_test.go b/internal/provider/vaultsecrets/resource_vault_secrets_integration_azure_test.go
new file mode 100644
index 000000000..875e0342a
--- /dev/null
+++ b/internal/provider/vaultsecrets/resource_vault_secrets_integration_azure_test.go
@@ -0,0 +1,190 @@
+// Copyright (c) HashiCorp, Inc.
+// SPDX-License-Identifier: MPL-2.0
+
+package vaultsecrets_test
+
+import (
+ "fmt"
+ "os"
+ "testing"
+
+ "github.com/hashicorp/hcp-sdk-go/clients/cloud-vault-secrets/stable/2023-11-28/client/secret_service"
+ secretmodels "github.com/hashicorp/hcp-sdk-go/clients/cloud-vault-secrets/stable/2023-11-28/models"
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+ "github.com/hashicorp/terraform-plugin-testing/terraform"
+ "github.com/hashicorp/terraform-provider-hcp/internal/clients"
+ "github.com/hashicorp/terraform-provider-hcp/internal/provider/acctest"
+)
+
+func TestAccVaultSecretsResourceIntegrationAzure(t *testing.T) {
+ tenantID := checkRequiredEnvVarOrFail(t, "AZURE_TENANT_ID")
+ clientID := checkRequiredEnvVarOrFail(t, "AZURE_CLIENT_ID")
+ clientSecret := checkRequiredEnvVarOrFail(t, "AZURE_CLIENT_SECRET")
+ audience := checkRequiredEnvVarOrFail(t, "AZURE_INTEGRATION_AUDIENCE")
+
+ integrationName1 := generateRandomSlug()
+ // Set the integration name that is configured in the subject claim while creating a federated credential.
+ integrationName2 := checkRequiredEnvVarOrFail(t, "AZURE_INTEGRATION_NAME_WIF")
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { acctest.PreCheck(t) },
+ ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories,
+ Steps: []resource.TestStep{
+ // Create initial integration with access keys
+ {
+ Config: azureClientSecretConfig(integrationName1, clientID, tenantID, clientSecret),
+ Check: resource.ComposeTestCheckFunc(
+ azureCheckClientSecretKeyFuncs(integrationName1, clientID, tenantID, clientSecret)...,
+ ),
+ },
+ // Changing the name forces a recreation
+ {
+ Config: azureClientSecretConfig(integrationName2, clientID, tenantID, clientSecret),
+ Check: resource.ComposeTestCheckFunc(
+ azureCheckClientSecretKeyFuncs(integrationName2, clientID, tenantID, clientSecret)...,
+ ),
+ },
+ // Modifying mutable fields causes an update
+ {
+ Config: azureFederatedIdentityConfig(integrationName2, clientID, tenantID, audience),
+ Check: resource.ComposeTestCheckFunc(
+ azureCheckFederatedIdentityFuncs(integrationName2, clientID, tenantID, audience)...,
+ ),
+ },
+ // Deleting the integration out of band causes a recreation
+ {
+ PreConfig: func() {
+ t.Helper()
+ client := acctest.HCPClients(t)
+ _, err := client.VaultSecrets.DeleteAzureIntegration(&secret_service.DeleteAzureIntegrationParams{
+ Name: integrationName2,
+ OrganizationID: client.Config.OrganizationID,
+ ProjectID: client.Config.ProjectID,
+ }, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ },
+ Config: azureFederatedIdentityConfig(integrationName2, clientID, tenantID, audience),
+ Check: resource.ComposeTestCheckFunc(
+ azureCheckFederatedIdentityFuncs(integrationName2, clientID, tenantID, audience)...,
+ ),
+ PlanOnly: true,
+ ExpectNonEmptyPlan: true,
+ },
+ // Pre-existing integration can be imported
+ {
+ PreConfig: func() {
+ t.Helper()
+ client := acctest.HCPClients(t)
+ _, err := client.VaultSecrets.CreateAzureIntegration(&secret_service.CreateAzureIntegrationParams{
+ Body: &secretmodels.SecretServiceCreateAzureIntegrationBody{
+ Capabilities: []*secretmodels.Secrets20231128Capability{secretmodels.Secrets20231128CapabilityROTATION.Pointer()},
+ FederatedWorkloadIdentity: &secretmodels.Secrets20231128AzureFederatedWorkloadIdentityRequest{
+ Audience: audience,
+ ClientID: clientID,
+ TenantID: tenantID,
+ },
+ Name: integrationName2,
+ },
+ OrganizationID: client.Config.OrganizationID,
+ ProjectID: client.Config.ProjectID,
+ }, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ },
+ Config: azureFederatedIdentityConfig(integrationName2, clientID, tenantID, audience),
+ Check: resource.ComposeTestCheckFunc(
+ azureCheckFederatedIdentityFuncs(integrationName2, clientID, tenantID, audience)...,
+ ),
+ ResourceName: "hcp_vault_secrets_integration_azure.acc_test",
+ ImportStateId: integrationName2,
+ ImportState: true,
+ PlanOnly: true,
+ },
+ },
+ CheckDestroy: func(_ *terraform.State) error {
+ if azureIntegrationExists(t, integrationName1) {
+ return fmt.Errorf("test azure integration %s was not destroyed", integrationName1)
+ }
+ if azureIntegrationExists(t, integrationName2) {
+ return fmt.Errorf("test azure integration %s was not destroyed", integrationName2)
+ }
+ return nil
+ },
+ })
+}
+
+func azureClientSecretConfig(integrationName, clientID, tenantID, clientSecret string) string {
+ return fmt.Sprintf(`
+ resource "hcp_vault_secrets_integration_azure" "acc_test" {
+ name = %q
+ capabilities = ["ROTATION"]
+ client_secret = {
+ tenant_id = %q
+ client_id = %q
+ client_secret = %q
+ }
+ }`, integrationName, tenantID, clientID, clientSecret)
+}
+
+func azureFederatedIdentityConfig(integrationName, clientID, tenantID, audience string) string {
+ return fmt.Sprintf(`
+ resource "hcp_vault_secrets_integration_azure" "acc_test" {
+ name = %q
+ capabilities = ["ROTATION"]
+ federated_workload_identity = {
+ tenant_id = %q
+ client_id = %q
+ audience = %q
+ }
+ }`, integrationName, tenantID, clientID, audience)
+}
+
+func azureCheckClientSecretKeyFuncs(integrationName, clientID, tenantID, clientSecret string) []resource.TestCheckFunc {
+ return []resource.TestCheckFunc{
+ resource.TestCheckResourceAttrSet("hcp_vault_secrets_integration_azure.acc_test", "organization_id"),
+ resource.TestCheckResourceAttr("hcp_vault_secrets_integration_azure.acc_test", "project_id", os.Getenv("HCP_PROJECT_ID")),
+ resource.TestCheckResourceAttrSet("hcp_vault_secrets_integration_azure.acc_test", "resource_id"),
+ resource.TestCheckResourceAttrSet("hcp_vault_secrets_integration_azure.acc_test", "resource_name"),
+ resource.TestCheckResourceAttr("hcp_vault_secrets_integration_azure.acc_test", "name", integrationName),
+ resource.TestCheckResourceAttr("hcp_vault_secrets_integration_azure.acc_test", "capabilities.#", "1"),
+ resource.TestCheckResourceAttr("hcp_vault_secrets_integration_azure.acc_test", "capabilities.0", "ROTATION"),
+ resource.TestCheckResourceAttr("hcp_vault_secrets_integration_azure.acc_test", "client_secret.client_id", clientID),
+ resource.TestCheckResourceAttr("hcp_vault_secrets_integration_azure.acc_test", "client_secret.tenant_id", tenantID),
+ resource.TestCheckResourceAttr("hcp_vault_secrets_integration_azure.acc_test", "client_secret.client_secret", clientSecret),
+ }
+}
+
+func azureCheckFederatedIdentityFuncs(integrationName, clientID, tenantID, audience string) []resource.TestCheckFunc {
+ return []resource.TestCheckFunc{
+ resource.TestCheckResourceAttrSet("hcp_vault_secrets_integration_azure.acc_test", "organization_id"),
+ resource.TestCheckResourceAttr("hcp_vault_secrets_integration_azure.acc_test", "project_id", os.Getenv("HCP_PROJECT_ID")),
+ resource.TestCheckResourceAttrSet("hcp_vault_secrets_integration_azure.acc_test", "resource_id"),
+ resource.TestCheckResourceAttrSet("hcp_vault_secrets_integration_azure.acc_test", "resource_name"),
+ resource.TestCheckResourceAttr("hcp_vault_secrets_integration_azure.acc_test", "name", integrationName),
+ resource.TestCheckResourceAttr("hcp_vault_secrets_integration_azure.acc_test", "capabilities.#", "1"),
+ resource.TestCheckResourceAttr("hcp_vault_secrets_integration_azure.acc_test", "capabilities.0", "ROTATION"),
+ resource.TestCheckResourceAttr("hcp_vault_secrets_integration_azure.acc_test", "federated_workload_identity.audience", audience),
+ resource.TestCheckResourceAttr("hcp_vault_secrets_integration_azure.acc_test", "federated_workload_identity.tenant_id", tenantID),
+ resource.TestCheckResourceAttr("hcp_vault_secrets_integration_azure.acc_test", "federated_workload_identity.client_id", clientID),
+ }
+}
+
+func azureIntegrationExists(t *testing.T, name string) bool {
+ t.Helper()
+
+ client := acctest.HCPClients(t)
+
+ response, err := client.VaultSecrets.GetAzureIntegration(
+ secret_service.NewGetAzureIntegrationParamsWithContext(ctx).
+ WithOrganizationID(client.Config.OrganizationID).
+ WithProjectID(client.Config.ProjectID).
+ WithName(name), nil)
+ if err != nil && !clients.IsResponseCodeNotFound(err) {
+ t.Fatal(err)
+ }
+
+ return !clients.IsResponseCodeNotFound(err) && response != nil && response.Payload != nil && response.Payload.Integration != nil
+}
diff --git a/internal/provider/vaultsecrets/resource_vault_secrets_rotating_secret.go b/internal/provider/vaultsecrets/resource_vault_secrets_rotating_secret.go
index a45777b73..05e9fee98 100644
--- a/internal/provider/vaultsecrets/resource_vault_secrets_rotating_secret.go
+++ b/internal/provider/vaultsecrets/resource_vault_secrets_rotating_secret.go
@@ -31,6 +31,7 @@ var exactlyOneRotatingSecretTypeFieldsValidator = objectvalidator.ExactlyOneOf(
path.MatchRoot("mongodb_atlas_user"),
path.MatchRoot("twilio_api_key"),
path.MatchRoot("confluent_service_account"),
+ path.MatchRoot("azure_application_password"),
}...,
)
@@ -50,6 +51,7 @@ var rotatingSecretsImpl = map[Provider]rotatingSecret{
ProviderMongoDBAtlas: &mongoDBAtlasRotatingSecret{},
ProviderTwilio: &twilioRotatingSecret{},
ProviderConfluent: &confluentRotatingSecret{},
+ ProviderAzure: &azureRotatingSecret{},
}
type RotatingSecret struct {
@@ -62,12 +64,12 @@ type RotatingSecret struct {
RotationPolicyName types.String `tfsdk:"rotation_policy_name"`
// Provider specific mutually exclusive fields
- AWSAccessKeys *awsAccessKeys `tfsdk:"aws_access_keys"`
- GCPServiceAccountKey *gcpServiceAccountKey `tfsdk:"gcp_service_account_key"`
- MongoDBAtlasUser *mongoDBAtlasUser `tfsdk:"mongodb_atlas_user"`
- TwilioAPIKey *twilioAPIKey `tfsdk:"twilio_api_key"`
- ConfluentServiceAccount *confluentServiceAccount `tfsdk:"confluent_service_account"`
-
+ AWSAccessKeys *awsAccessKeys `tfsdk:"aws_access_keys"`
+ GCPServiceAccountKey *gcpServiceAccountKey `tfsdk:"gcp_service_account_key"`
+ MongoDBAtlasUser *mongoDBAtlasUser `tfsdk:"mongodb_atlas_user"`
+ TwilioAPIKey *twilioAPIKey `tfsdk:"twilio_api_key"`
+ ConfluentServiceAccount *confluentServiceAccount `tfsdk:"confluent_service_account"`
+ AzureApplicationPassword *AzureApplicationPassword `tfsdk:"azure_application_password"`
// Computed fields
OrganizationID types.String `tfsdk:"organization_id"`
@@ -93,6 +95,11 @@ type confluentServiceAccount struct {
ServiceAccountID types.String `tfsdk:"service_account_id"`
}
+type AzureApplicationPassword struct {
+ AppClientID types.String `tfsdk:"app_client_id"`
+ AppObjectID types.String `tfsdk:"app_object_id"`
+}
+
type twilioAPIKey struct{}
var _ resource.Resource = &resourceVaultSecretsRotatingSecret{}
@@ -206,6 +213,29 @@ func (r *resourceVaultSecretsRotatingSecret) Schema(_ context.Context, _ resourc
exactlyOneRotatingSecretTypeFieldsValidator,
},
},
+ "azure_application_password": schema.SingleNestedAttribute{
+ Description: "Azure configuration to manage the application password rotation for the given application. Required if `secret_provider` is `Azure`.",
+ Optional: true,
+ Attributes: map[string]schema.Attribute{
+ "app_client_id": schema.StringAttribute{
+ Description: "Application client ID to rotate the application password for.",
+ Required: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ },
+ },
+ "app_object_id": schema.StringAttribute{
+ Description: "Application object ID to rotate the application password for.",
+ Required: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ },
+ },
+ },
+ Validators: []validator.Object{
+ exactlyOneRotatingSecretTypeFieldsValidator,
+ },
+ },
}
maps.Copy(attributes, locationAttributes)
diff --git a/internal/provider/vaultsecrets/rotating_secret_azure.go b/internal/provider/vaultsecrets/rotating_secret_azure.go
new file mode 100644
index 000000000..cde2cd88e
--- /dev/null
+++ b/internal/provider/vaultsecrets/rotating_secret_azure.go
@@ -0,0 +1,89 @@
+// Copyright (c) HashiCorp, Inc.
+// SPDX-License-Identifier: MPL-2.0
+
+package vaultsecrets
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/hashicorp/hcp-sdk-go/clients/cloud-vault-secrets/stable/2023-11-28/client/secret_service"
+ secretmodels "github.com/hashicorp/hcp-sdk-go/clients/cloud-vault-secrets/stable/2023-11-28/models"
+ "github.com/hashicorp/terraform-provider-hcp/internal/clients"
+)
+
+var _ rotatingSecret = &azureRotatingSecret{}
+
+type azureRotatingSecret struct{}
+
+func (s *azureRotatingSecret) read(ctx context.Context, client secret_service.ClientService, secret *RotatingSecret) (any, error) {
+ response, err := client.GetAzureApplicationPasswordRotatingSecretConfig(
+ secret_service.NewGetAzureApplicationPasswordRotatingSecretConfigParamsWithContext(ctx).
+ WithOrganizationID(secret.OrganizationID.ValueString()).
+ WithProjectID(secret.ProjectID.ValueString()).
+ WithAppName(secret.AppName.ValueString()).
+ WithName(secret.Name.ValueString()), nil)
+ if err != nil && !clients.IsResponseCodeNotFound(err) {
+ return nil, err
+ }
+ if response == nil || response.Payload == nil {
+ return nil, nil
+ }
+ return response.Payload.Config, nil
+}
+
+func (s *azureRotatingSecret) create(ctx context.Context, client secret_service.ClientService, secret *RotatingSecret) (any, error) {
+ if secret.AzureApplicationPassword == nil {
+ return nil, fmt.Errorf("missing required field 'azure_application_password'")
+ }
+
+ response, err := client.CreateAzureApplicationPasswordRotatingSecret(
+ secret_service.NewCreateAzureApplicationPasswordRotatingSecretParamsWithContext(ctx).
+ WithOrganizationID(secret.OrganizationID.ValueString()).
+ WithProjectID(secret.ProjectID.ValueString()).
+ WithAppName(secret.AppName.ValueString()).
+ WithBody(&secretmodels.SecretServiceCreateAzureApplicationPasswordRotatingSecretBody{
+ IntegrationName: secret.IntegrationName.ValueString(),
+ RotationPolicyName: secret.RotationPolicyName.ValueString(),
+ AzureApplicationPasswordParams: &secretmodels.Secrets20231128AzureApplicationPasswordParams{
+ AppClientID: secret.AzureApplicationPassword.AppClientID.ValueString(),
+ AppObjectID: secret.AzureApplicationPassword.AppObjectID.ValueString(),
+ },
+ Name: secret.Name.ValueString(),
+ }),
+ nil)
+ if err != nil {
+ return nil, err
+ }
+ if response == nil || response.Payload == nil {
+ return nil, nil
+ }
+ return response.Payload.Config, nil
+}
+
+func (s *azureRotatingSecret) update(ctx context.Context, client secret_service.ClientService, secret *RotatingSecret) (any, error) {
+ if secret.AzureApplicationPassword == nil {
+ return nil, fmt.Errorf("missing required field 'azure_application_password'")
+ }
+ response, err := client.UpdateAzureApplicationPasswordRotatingSecret(
+ secret_service.NewUpdateAzureApplicationPasswordRotatingSecretParamsWithContext(ctx).
+ WithOrganizationID(secret.OrganizationID.ValueString()).
+ WithProjectID(secret.ProjectID.ValueString()).
+ WithAppName(secret.AppName.ValueString()).
+ WithName(secret.Name.ValueString()).
+ WithBody(&secretmodels.SecretServiceUpdateAzureApplicationPasswordRotatingSecretBody{
+ RotationPolicyName: secret.RotationPolicyName.ValueString(),
+ AzureApplicationPasswordParams: &secretmodels.Secrets20231128AzureApplicationPasswordParams{
+ AppClientID: secret.AzureApplicationPassword.AppClientID.ValueString(),
+ AppObjectID: secret.AzureApplicationPassword.AppObjectID.ValueString(),
+ },
+ }),
+ nil)
+ if err != nil {
+ return nil, err
+ }
+ if response == nil || response.Payload == nil {
+ return nil, nil
+ }
+ return response.Payload.Config, nil
+}
diff --git a/internal/provider/vaultsecrets/vault_secrets_utils.go b/internal/provider/vaultsecrets/vault_secrets_utils.go
index 16a25d46d..83707569d 100644
--- a/internal/provider/vaultsecrets/vault_secrets_utils.go
+++ b/internal/provider/vaultsecrets/vault_secrets_utils.go
@@ -27,6 +27,7 @@ const (
ProviderMongoDBAtlas Provider = "mongodb_atlas"
ProviderTwilio Provider = "twilio"
ProviderConfluent Provider = "confluent"
+ ProviderAzure Provider = "azure"
)
func (p Provider) String() string {