Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added the integration for MsSQL provider #1172

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
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
3 changes: 3 additions & 0 deletions .changelog/1172.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
Added MsSQL resource for integration and secrets for HVS
```
9 changes: 9 additions & 0 deletions docs/resources/vault_secrets_integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ resource "hcp_vault_secrets_integration" "example_twilio" {
- `gcp_federated_workload_identity` (Attributes) (Recommended) Federated identity configuration to authenticate against the target GCP project. Cannot be used with `service_account_key`. (see [below for nested schema](#nestedatt--gcp_federated_workload_identity))
- `gcp_service_account_key` (Attributes) GCP service account key used to authenticate against the target GCP project. Cannot be used with `federated_workload_identity`. (see [below for nested schema](#nestedatt--gcp_service_account_key))
- `mongodb_atlas_static_credentials` (Attributes) MongoDB Atlas API key used to authenticate against the target project. (see [below for nested schema](#nestedatt--mongodb_atlas_static_credentials))
- `mssql_static_credentials` (Attributes) MsSQL API key parts used to authenticate against the target MsSQL account. (see [below for nested schema](#nestedatt--mssql_static_credentials))
- `project_id` (String) HCP project ID that owns the HCP Vault Secrets integration. Inferred from the provider configuration if omitted.
- `twilio_static_credentials` (Attributes) Twilio API key parts used to authenticate against the target Twilio account. (see [below for nested schema](#nestedatt--twilio_static_credentials))

Expand Down Expand Up @@ -203,6 +204,14 @@ Required:
- `api_public_key` (String) Public key used alongside the private key to authenticate against the target project.


<a id="nestedatt--mssql_static_credentials"></a>
### Nested Schema for `mssql_static_credentials`

Required:

- `connection_string` (String) Connection string for the target MsSQL account.


<a id="nestedatt--twilio_static_credentials"></a>
### Nested Schema for `twilio_static_credentials`

Expand Down
9 changes: 9 additions & 0 deletions docs/resources/vault_secrets_rotating_secret.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ resource "hcp_vault_secrets_rotating_secret" "example_azure" {
- `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))
- `mssql_users` (Attributes) MSSQL configuration to manage the database user credential rotation for the given host. Required if `secret_provider` is `mssql`. (see [below for nested schema](#nestedatt--mssql_users))
- `project_id` (String) HCP project ID that owns the HCP Vault Secrets integration. Inferred from the provider configuration if omitted.
- `twilio_api_key` (Attributes) Twilio configuration to manage the api key rotation on the given account. Required if `secret_provider` is `twilio`. (see [below for nested schema](#nestedatt--twilio_api_key))

Expand Down Expand Up @@ -148,5 +149,13 @@ Required:
- `roles` (List of String) MongoDB Atlas roles to assign to the rotating user.


<a id="nestedatt--mssql_users"></a>
### Nested Schema for `mssql_users`

Required:

- `usernames` (String) MsSQL usernames to rotate the passwords for.


<a id="nestedatt--twilio_api_key"></a>
### Nested Schema for `twilio_api_key`
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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.126.0
github.com/hashicorp/hcp-sdk-go v0.131.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
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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.126.0 h1:/ByCyXaKrwJwK5SMjp/JFK3ZbVqDxEaADQev3t6odI4=
github.com/hashicorp/hcp-sdk-go v0.126.0/go.mod h1:vQ4fzdL1AmhIAbCw+4zmFe5Hbpajj3NvRWkJoVuxmAk=
github.com/hashicorp/hcp-sdk-go v0.131.0 h1:2o2peovPIJ1/yj3GGcxyMn0ndiGaCML0mosnsHPsikE=
github.com/hashicorp/hcp-sdk-go v0.131.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=
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,14 @@ var exactlyOneIntegrationTypeFieldsValidator = objectvalidator.ExactlyOneOf(
path.MatchRoot("gcp_federated_workload_identity"),
path.MatchRoot("mongodb_atlas_static_credentials"),
path.MatchRoot("twilio_static_credentials"),
path.MatchRoot("mssql_static_credentials"),
}...,
)

type mssqlStaticCredentials struct {
ConnectionString types.String `tfsdk:"connection_string"`
}

type Integration struct {
// Input fields
ProjectID types.String `tfsdk:"project_id"`
Expand All @@ -58,6 +63,7 @@ type Integration struct {
GcpFederatedWorkloadIdentity types.Object `tfsdk:"gcp_federated_workload_identity"`
MongoDBAtlasStaticCredentials types.Object `tfsdk:"mongodb_atlas_static_credentials"`
TwilioStaticCredentials types.Object `tfsdk:"twilio_static_credentials"`
MssqlStaticCredentials types.Object `tfsdk:"mssql_static_credentials"`

// Computed fields
OrganizationID types.String `tfsdk:"organization_id"`
Expand All @@ -75,6 +81,7 @@ type Integration struct {
gcpFederatedWorkloadIdentity *secretmodels.Secrets20231128GcpFederatedWorkloadIdentityRequest `tfsdk:"-"`
mongoDBAtlasStaticCredentials *secretmodels.Secrets20231128MongoDBAtlasStaticCredentialsRequest `tfsdk:"-"`
twilioStaticCredentials *secretmodels.Secrets20231128TwilioStaticCredentialsRequest `tfsdk:"-"`
mssqlStaticCredentials *secretmodels.Secrets20231128MssqlStaticCredentialsRequest `tfsdk:"-"`
}

var _ resource.Resource = &resourceVaultSecretsIntegration{}
Expand Down Expand Up @@ -279,6 +286,19 @@ func (r *resourceVaultSecretsIntegration) Schema(_ context.Context, _ resource.S
exactlyOneIntegrationTypeFieldsValidator,
},
},
"mssql_static_credentials": schema.SingleNestedAttribute{
Description: "MsSQL API key parts used to authenticate against the target MsSQL account.",
Optional: true,
Attributes: map[string]schema.Attribute{
"connection_string": schema.StringAttribute{
Description: "Connection string for the target MsSQL account.",
Required: true,
},
},
Validators: []validator.Object{
exactlyOneIntegrationTypeFieldsValidator,
},
},
}

maps.Copy(attributes, locationAttributes)
Expand Down Expand Up @@ -352,6 +372,7 @@ func (r *resourceVaultSecretsIntegration) Create(ctx context.Context, req resour
GcpFederatedWorkloadIdentity: integration.gcpFederatedWorkloadIdentity,
MongoDbAtlasStaticCredentials: integration.mongoDBAtlasStaticCredentials,
TwilioStaticCredentials: integration.twilioStaticCredentials,
MssqlStaticCredentials: integration.mssqlStaticCredentials,
},
OrganizationID: integration.OrganizationID.ValueString(),
ProjectID: integration.ProjectID.ValueString(),
Expand Down Expand Up @@ -386,6 +407,7 @@ func (r *resourceVaultSecretsIntegration) Update(ctx context.Context, req resour
GcpFederatedWorkloadIdentity: integration.gcpFederatedWorkloadIdentity,
MongoDbAtlasStaticCredentials: integration.mongoDBAtlasStaticCredentials,
TwilioStaticCredentials: integration.twilioStaticCredentials,
MssqlStaticCredentials: integration.mssqlStaticCredentials,
},
Name: integration.Name.ValueString(),
OrganizationID: integration.OrganizationID.ValueString(),
Expand Down Expand Up @@ -568,6 +590,18 @@ func (i *Integration) initModel(ctx context.Context, orgID, projID string) diag.
}
}

if !i.MssqlStaticCredentials.IsNull() {
scd := mssqlStaticCredentials{}
diags = i.MssqlStaticCredentials.As(ctx, &scd, basetypes.ObjectAsOptions{})
if diags.HasError() {
return diags
}

i.mssqlStaticCredentials = &secretmodels.Secrets20231128MssqlStaticCredentialsRequest{
ConnectionString: scd.ConnectionString.ValueString(),
}
}

return diag.Diagnostics{}
}

Expand Down Expand Up @@ -719,5 +753,14 @@ func (i *Integration) fromModel(ctx context.Context, orgID, projID string, model
}
}

if integrationModel.MssqlStaticCredentials != nil {
i.MssqlStaticCredentials, diags = types.ObjectValue(i.MssqlStaticCredentials.AttributeTypes(ctx), map[string]attr.Value{
"connection_string": types.StringValue(integrationModel.MssqlStaticCredentials.ConnectionString),
})
if diags.HasError() {
return diags
}
}

return diags
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// 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 TestAccVaultSecretsResourceIntegrationMssql(t *testing.T) {
connectionString := checkRequiredEnvVarOrFail(t, "MSSQL_CONNECTION_STRING")

integrationName1 := generateRandomSlug()
integrationName2 := generateRandomSlug()

resource.Test(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(t) },
ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories,
Steps: []resource.TestStep{
// Create initial integration with access keys
{
Config: mssqlConfig(integrationName1, connectionString),
Check: resource.ComposeTestCheckFunc(
mssqlCheckFuncs(integrationName1, connectionString)...,
),
},
// Changing the name forces a recreation
{
Config: mssqlConfig(integrationName2, connectionString),
Check: resource.ComposeTestCheckFunc(
mssqlCheckFuncs(integrationName2, connectionString)...,
),
},
// Modifying mutable fields causes an update
{
Config: mssqlConfig(integrationName2, connectionString),
Check: resource.ComposeTestCheckFunc(
mssqlCheckFuncs(integrationName2, connectionString)...,
),
},
// Deleting the integration out of band causes a recreation
{
PreConfig: func() {
t.Helper()
client := acctest.HCPClients(t)
_, err := client.VaultSecrets.DeleteIntegration(&secret_service.DeleteIntegrationParams{
Name: integrationName2,
OrganizationID: client.Config.OrganizationID,
ProjectID: client.Config.ProjectID,
}, nil)
if err != nil {
t.Fatal(err)
}
},
Config: mssqlConfig(integrationName2, connectionString),
Check: resource.ComposeTestCheckFunc(
mssqlCheckFuncs(integrationName2, connectionString)...,
),
PlanOnly: true,
ExpectNonEmptyPlan: true,
},
// Pre-existing integration can be imported
{
PreConfig: func() {
t.Helper()
client := acctest.HCPClients(t)
_, err := client.VaultSecrets.CreateIntegration(&secret_service.CreateIntegrationParams{
Body: &secretmodels.SecretServiceCreateIntegrationBody{
Capabilities: []*secretmodels.Secrets20231128Capability{secretmodels.Secrets20231128CapabilityROTATION.Pointer()},
MssqlStaticCredentials: &secretmodels.Secrets20231128MssqlStaticCredentialsRequest{
ConnectionString: connectionString,
},
Name: integrationName2,
},
OrganizationID: client.Config.OrganizationID,
ProjectID: client.Config.ProjectID,
}, nil)
if err != nil {
t.Fatal(err)
}
},
Config: mssqlConfig(integrationName2, connectionString),
Check: resource.ComposeTestCheckFunc(
mssqlCheckFuncs(integrationName2, connectionString)...,
),
ResourceName: "hcp_vault_secrets_integration.acc_test",
ImportStateId: integrationName2,
ImportState: true,
},
},
CheckDestroy: func(_ *terraform.State) error {
if mssqlIntegrationExists(t, integrationName1) {
return fmt.Errorf("test mssql integration %s was not destroyed", integrationName1)
}
if mssqlIntegrationExists(t, integrationName2) {
return fmt.Errorf("test mssql integration %s was not destroyed", integrationName2)
}
return nil
},
})
}

func mssqlConfig(integrationName, connectionString string) string {
return fmt.Sprintf(`
resource "hcp_vault_secrets_integration" "acc_test" {
name = %q
capabilities = ["ROTATION"]
provider_type = "mssql"
mssql_static_credentials = {
connection_string = %q
}
}`, integrationName, connectionString)
}

func mssqlCheckFuncs(integrationName, connectionString string) []resource.TestCheckFunc {
return []resource.TestCheckFunc{
resource.TestCheckResourceAttrSet("hcp_vault_secrets_integration.acc_test", "organization_id"),
resource.TestCheckResourceAttr("hcp_vault_secrets_integration.acc_test", "project_id", os.Getenv("HCP_PROJECT_ID")),
resource.TestCheckResourceAttrSet("hcp_vault_secrets_integration.acc_test", "resource_id"),
resource.TestCheckResourceAttrSet("hcp_vault_secrets_integration.acc_test", "resource_name"),
resource.TestCheckResourceAttr("hcp_vault_secrets_integration.acc_test", "name", integrationName),
resource.TestCheckResourceAttr("hcp_vault_secrets_integration.acc_test", "capabilities.#", "1"),
resource.TestCheckResourceAttr("hcp_vault_secrets_integration.acc_test", "capabilities.0", "ROTATION"),
resource.TestCheckResourceAttr("hcp_vault_secrets_integration.acc_test", "provider_type", "mssql"),
resource.TestCheckResourceAttr("hcp_vault_secrets_integration.acc_test", "mssql_static_credentials.connection_string", connectionString),
}
}

func mssqlIntegrationExists(t *testing.T, name string) bool {
t.Helper()

client := acctest.HCPClients(t)

response, err := client.VaultSecrets.GetIntegration(
secret_service.NewGetIntegrationParamsWithContext(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
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ var exactlyOneRotatingSecretTypeFieldsValidator = objectvalidator.ExactlyOneOf(
path.MatchRoot("twilio_api_key"),
path.MatchRoot("confluent_service_account"),
path.MatchRoot("azure_application_password"),
path.MatchRoot("mssql_users"),
}...,
)

Expand All @@ -54,6 +55,7 @@ var rotatingSecretsImpl = map[Provider]rotatingSecret{
ProviderTwilio: &twilioRotatingSecret{},
ProviderConfluent: &confluentRotatingSecret{},
ProviderAzure: &azureRotatingSecret{},
ProviderMssql: &mssqlRotatingSecret{},
}

type RotatingSecret struct {
Expand All @@ -72,6 +74,7 @@ type RotatingSecret struct {
TwilioAPIKey *twilioAPIKey `tfsdk:"twilio_api_key"`
ConfluentServiceAccount *confluentServiceAccount `tfsdk:"confluent_service_account"`
AzureApplicationPassword *AzureApplicationPassword `tfsdk:"azure_application_password"`
MssqlUsers *mssqlUsers `tfsdk:"mssql_users"`
// Computed fields
OrganizationID types.String `tfsdk:"organization_id"`

Expand Down Expand Up @@ -102,6 +105,10 @@ type AzureApplicationPassword struct {
AppObjectID types.String `tfsdk:"app_object_id"`
}

type mssqlUsers struct {
Usernames []string `tfsdk:"usernames"`
}

type twilioAPIKey struct{}

var _ resource.Resource = &resourceVaultSecretsRotatingSecret{}
Expand Down Expand Up @@ -238,6 +245,22 @@ func (r *resourceVaultSecretsRotatingSecret) Schema(_ context.Context, _ resourc
exactlyOneRotatingSecretTypeFieldsValidator,
},
},
"mssql_users": schema.SingleNestedAttribute{
Description: "MSSQL configuration to manage the database user credential rotation for the given host. Required if `secret_provider` is `mssql`.",
Optional: true,
Attributes: map[string]schema.Attribute{
"usernames": schema.StringAttribute{
Description: "MsSQL usernames to rotate the passwords for.",
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
},
Validators: []validator.Object{
exactlyOneRotatingSecretTypeFieldsValidator,
},
},
}

maps.Copy(attributes, locationAttributes)
Expand Down
Loading
Loading