Skip to content

Commit

Permalink
[CLOUDGA-17429] Add Azure CMK support to Terraform (#86)
Browse files Browse the repository at this point in the history
Add azure cmk support

Updated Mock

Update Docs

addressed comments
arishta-yb authored Nov 6, 2023
1 parent be076b0 commit 4a528df
Showing 12 changed files with 256 additions and 12 deletions.
13 changes: 13 additions & 0 deletions docs/data-sources/cluster.md
Original file line number Diff line number Diff line change
@@ -94,6 +94,7 @@ Read-Only:
Read-Only:

- `aws_cmk_spec` (Attributes) AWS CMK Provider Configuration. (see [below for nested schema](#nestedatt--cmk_spec--aws_cmk_spec))
- `azure_cmk_spec` (Attributes) AZURE CMK Provider Configuration. (see [below for nested schema](#nestedatt--cmk_spec--azure_cmk_spec))
- `gcp_cmk_spec` (Attributes) GCP CMK Provider Configuration. (see [below for nested schema](#nestedatt--cmk_spec--gcp_cmk_spec))
- `is_enabled` (Boolean) Is Enabled
- `provider_type` (String) CMK Provider Type.
@@ -108,6 +109,18 @@ Read-Only:
- `secret_key` (String) Secret Key


<a id="nestedatt--cmk_spec--azure_cmk_spec"></a>
### Nested Schema for `cmk_spec.azure_cmk_spec`

Read-Only:

- `client_id` (String) Client ID
- `client_secret` (String) Client Secret
- `key_name` (String) Key Name
- `key_vault_uri` (String) Key Vault URI
- `tenant_id` (String) Tenant ID


<a id="nestedatt--cmk_spec--gcp_cmk_spec"></a>
### Nested Schema for `cmk_spec.gcp_cmk_spec`

15 changes: 15 additions & 0 deletions docs/resources/cluster.md
Original file line number Diff line number Diff line change
@@ -364,6 +364,7 @@ To create an AWS Cluster with Customer Managed Keys
```terraform
# EAR enabled single region cluster
# The same cmk_spec can be used for multi region/read replica clusters as well
# Encryption at rest is supported on clusters with database version 2.16.7.0 or later
variable "ysql_password" {
type = string
@@ -423,6 +424,7 @@ To create a GCP Cluster with Customer Managed Keys
```terraform
# EAR enabled single region cluster
# The same cmk_spec can be used for multi region/read replica clusters as well
# Encryption at rest is supported on clusters with database version 2.16.7.0 or later
variable "ysql_password" {
type = string
@@ -696,6 +698,7 @@ Required:
Optional:

- `aws_cmk_spec` (Attributes) AWS CMK Provider Configuration. (see [below for nested schema](#nestedatt--cmk_spec--aws_cmk_spec))
- `azure_cmk_spec` (Attributes) AZURE CMK Provider Configuration. (see [below for nested schema](#nestedatt--cmk_spec--azure_cmk_spec))
- `gcp_cmk_spec` (Attributes) GCP CMK Provider Configuration. (see [below for nested schema](#nestedatt--cmk_spec--gcp_cmk_spec))

<a id="nestedatt--cmk_spec--aws_cmk_spec"></a>
@@ -708,6 +711,18 @@ Required:
- `secret_key` (String) Secret Key


<a id="nestedatt--cmk_spec--azure_cmk_spec"></a>
### Nested Schema for `cmk_spec.azure_cmk_spec`

Required:

- `client_id` (String) Azure Active Directory (AD) Client ID for Key Vault service principal.
- `client_secret` (String) Azure AD Client Secret for Key Vault service principal.
- `key_name` (String) Name of cryptographic key in Azure Key Vault.
- `key_vault_uri` (String) URI of Azure Key Vault storing cryptographic keys.
- `tenant_id` (String) Azure AD Tenant ID for Key Vault service principal.


<a id="nestedatt--cmk_spec--gcp_cmk_spec"></a>
### Nested Schema for `cmk_spec.gcp_cmk_spec`

1 change: 1 addition & 0 deletions examples/resources/ybm_cluster/single-region-aws-cmk.tf
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# EAR enabled single region cluster
# The same cmk_spec can be used for multi region/read replica clusters as well
# Encryption at rest is supported on clusters with database version 2.16.7.0 or later

variable "ysql_password" {
type = string
54 changes: 54 additions & 0 deletions examples/resources/ybm_cluster/single-region-azure-cmk.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# EAR enabled single region cluster
# The same cmk_spec can be used for multi region/read replica clusters as well
# Encryption at rest is supported on clusters with database version 2.16.7.0 or later

variable "ysql_password" {
type = string
description = "YSQL Password."
sensitive = true
}

variable "ycql_password" {
type = string
description = "YCQL Password."
sensitive = true
}

resource "ybm_cluster" "single_region" {
cluster_name = "test-cluster-with-azure-cmk"
# The cloud provider for the cluster is indepedent of the CMK Provider
# eg. GCP cluster with AZURE CMK is supported
cloud_type = "GCP"
cluster_type = "SYNCHRONOUS"
cluster_region_info = [
{
region = "us-west1"
num_nodes = 6
}
]
cluster_tier = "PAID"
# fault tolerance cannot be NONE for CMK enabled cluster
fault_tolerance = "ZONE"
cmk_spec = {
provider_type = "AZURE"
azure_cmk_spec = {
client_id = "your-client-id"
client_secret = "your-client-secret"
tenant_id = "your-tenant-id"
key_name = "your-key-name"
key_vault_uri = "your-key-vault-uri"
}
is_enabled = true
}

node_config = {
num_cores = 4
disk_size_gb = 50
}
credentials = {
ysql_username = "example_ysql_user"
ysql_password = var.ysql_password
ycql_username = "example_ycql_user"
ycql_password = var.ycql_password
}
}
1 change: 1 addition & 0 deletions examples/resources/ybm_cluster/single-region-gcp-cmk.tf
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# EAR enabled single region cluster
# The same cmk_spec can be used for multi region/read replica clusters as well
# Encryption at rest is supported on clusters with database version 2.16.7.0 or later

variable "ysql_password" {
type = string
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@ require (
github.com/hashicorp/terraform-plugin-framework-validators v0.4.0
github.com/hashicorp/terraform-plugin-log v0.9.0
github.com/sethvargo/go-retry v0.2.3
github.com/yugabyte/yugabytedb-managed-go-client-internal v0.0.0-20230915211221-361825bc5618
github.com/yugabyte/yugabytedb-managed-go-client-internal v0.0.0-20231030163130-9268a00de50c
)

require (
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -161,6 +161,8 @@ github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAh
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/yugabyte/yugabytedb-managed-go-client-internal v0.0.0-20230915211221-361825bc5618 h1:ubU9s9gJPqDVcl9ZFd6sXZWhvRWJtcgdG5WtVVn4WE4=
github.com/yugabyte/yugabytedb-managed-go-client-internal v0.0.0-20230915211221-361825bc5618/go.mod h1:5vW0xIzIZw+1djkiWKx0qqNmqbRBSf4mjc4qw8lIMik=
github.com/yugabyte/yugabytedb-managed-go-client-internal v0.0.0-20231030163130-9268a00de50c h1:MzOpYz9LCniKMUhWZOnp6kn8bda5zq8vQ0oI58akrtc=
github.com/yugabyte/yugabytedb-managed-go-client-internal v0.0.0-20231030163130-9268a00de50c/go.mod h1:5vW0xIzIZw+1djkiWKx0qqNmqbRBSf4mjc4qw8lIMik=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
31 changes: 31 additions & 0 deletions managed/data_source_cluster_name.go
Original file line number Diff line number Diff line change
@@ -242,6 +242,37 @@ func (r dataClusterNameType) GetSchema(_ context.Context) (tfsdk.Schema, diag.Di
},
}),
},
"azure_cmk_spec": {
Description: "AZURE CMK Provider Configuration.",
Computed: true,
Attributes: tfsdk.SingleNestedAttributes(map[string]tfsdk.Attribute{
"client_id": {
Description: "Client ID",
Type: types.StringType,
Computed: true,
},
"client_secret": {
Description: "Client Secret",
Type: types.StringType,
Computed: true,
},
"tenant_id": {
Description: "Tenant ID",
Type: types.StringType,
Computed: true,
},
"key_vault_uri": {
Description: "Key Vault URI",
Type: types.StringType,
Computed: true,
},
"key_name": {
Description: "Key Name",
Type: types.StringType,
Computed: true,
},
}),
},
}),
},
"cluster_tier": {
18 changes: 14 additions & 4 deletions managed/models.go
Original file line number Diff line number Diff line change
@@ -41,10 +41,11 @@ type ClusterEndpoint struct {
}

type CMKSpec struct {
ProviderType types.String `tfsdk:"provider_type"`
AWSCMKSpec *AWSCMKSpec `tfsdk:"aws_cmk_spec"`
GCPCMKSpec *GCPCMKSpec `tfsdk:"gcp_cmk_spec"`
IsEnabled types.Bool `tfsdk:"is_enabled"`
ProviderType types.String `tfsdk:"provider_type"`
AWSCMKSpec *AWSCMKSpec `tfsdk:"aws_cmk_spec"`
GCPCMKSpec *GCPCMKSpec `tfsdk:"gcp_cmk_spec"`
AzureCMKSpec *AzureCMKSpec `tfsdk:"azure_cmk_spec"`
IsEnabled types.Bool `tfsdk:"is_enabled"`
}

type AWSCMKSpec struct {
@@ -60,6 +61,15 @@ type GCPCMKSpec struct {
ProtectionLevel types.String `tfsdk:"protection_level"`
GcpServiceAccount GCPServiceAccount `tfsdk:"gcp_service_account"`
}

type AzureCMKSpec struct {
ClientID types.String `tfsdk:"client_id"`
ClientSecret types.String `tfsdk:"client_secret"`
TenantID types.String `tfsdk:"tenant_id"`
KeyVaultUri types.String `tfsdk:"key_vault_uri"`
KeyName types.String `tfsdk:"key_name"`
}

type GCPServiceAccount struct {
Type types.String `tfsdk:"type"`
ProjectId types.String `tfsdk:"project_id"`
4 changes: 2 additions & 2 deletions managed/resource_backup.go
Original file line number Diff line number Diff line change
@@ -144,7 +144,7 @@ func (r resourceBackup) Create(ctx context.Context, req tfsdk.CreateResourceRequ
backupRetentionPeriodInDays := int32(plan.RetentionPeriodInDays.Value)

backupSpec := *openapiclient.NewBackupSpec(clusterId)
backupSpec.Description = &backupDescription
backupSpec.SetDescription(backupDescription)
backupSpec.RetentionPeriodInDays = &backupRetentionPeriodInDays

backupResp, response, err := apiClient.BackupApi.CreateBackup(context.Background(), accountId, projectId).BackupSpec(backupSpec).Execute()
@@ -203,7 +203,7 @@ func resourceBackupRead(accountId string, projectId string, backupId string, api
backup.BackupID.Value = backupId

backup.ClusterID.Value = backupResp.Data.Spec.ClusterId
backup.BackupDescription.Value = *(backupResp.Data.Spec.Description)
backup.BackupDescription.Value = *(backupResp.Data.Spec.Description.Get())
backup.RetentionPeriodInDays.Value = int64(*backupResp.Data.Spec.RetentionPeriodInDays)
backup.MostRecent.Null = true
backup.Timestamp.Null = true
97 changes: 92 additions & 5 deletions managed/resource_cluster.go
Original file line number Diff line number Diff line change
@@ -155,7 +155,7 @@ and modify the backup schedule of the cluster being created.`,
Description: "CMK Provider Type.",
Type: types.StringType,
Required: true,
Validators: []tfsdk.AttributeValidator{stringvalidator.OneOf("AWS", "GCP")},
Validators: []tfsdk.AttributeValidator{stringvalidator.OneOf("AWS", "GCP", "AZURE")},
},
"is_enabled": {
Description: "Is Enabled",
@@ -270,6 +270,37 @@ and modify the backup schedule of the cluster being created.`,
},
}),
},
"azure_cmk_spec": {
Description: "AZURE CMK Provider Configuration.",
Optional: true,
Attributes: tfsdk.SingleNestedAttributes(map[string]tfsdk.Attribute{
"client_id": {
Description: "Azure Active Directory (AD) Client ID for Key Vault service principal.",
Type: types.StringType,
Required: true,
},
"client_secret": {
Description: "Azure AD Client Secret for Key Vault service principal.",
Type: types.StringType,
Required: true,
},
"tenant_id": {
Description: "Azure AD Tenant ID for Key Vault service principal.",
Type: types.StringType,
Required: true,
},
"key_vault_uri": {
Description: "URI of Azure Key Vault storing cryptographic keys.",
Type: types.StringType,
Required: true,
},
"key_name": {
Description: "Name of cryptographic key in Azure Key Vault.",
Type: types.StringType,
Required: true,
},
}),
},
}),
},
"cluster_tier": {
@@ -466,7 +497,7 @@ func EditBackupSchedule(ctx context.Context, backupScheduleStruct BackupSchedule
backupRetentionPeriodInDays := int32(backupScheduleStruct.RetentionPeriodInDays.Value)
backupDescription := backupDes
backupSpec := *openapiclient.NewBackupSpec(clusterId)
backupSpec.Description = &backupDescription
backupSpec.SetDescription(backupDescription)
backupSpec.RetentionPeriodInDays = &backupRetentionPeriodInDays
scheduleSpec := *openapiclient.NewScheduleSpec(openapiclient.ScheduleStateEnum(backupScheduleStruct.State.Value))
if backupScheduleStruct.TimeIntervalInDays.Value != 0 {
@@ -693,12 +724,32 @@ func validateCredentials(credentials Credentials) bool {

}

func validateOnlyOneCMKSpec(plan *Cluster) error {
count := 0

if plan.CMKSpec.GCPCMKSpec != nil {
count++
}
if plan.CMKSpec.AWSCMKSpec != nil {
count++
}
if plan.CMKSpec.AzureCMKSpec != nil {
count++
}

if count != 1 {
return errors.New("Invalid input. Only one CMK Provider out of AWS, GCP, or AZURE must be present.")
}

return nil
}

func createCmkSpec(plan Cluster) (*openapiclient.CMKSpec, error) {
cmkProvider := plan.CMKSpec.ProviderType.Value
cmkSpec := openapiclient.NewCMKSpec(openapiclient.CMKProviderEnum(cmkProvider))

if plan.CMKSpec.GCPCMKSpec != nil && plan.CMKSpec.AWSCMKSpec != nil {
return nil, errors.New("Invalid input. Both AWS and GCP spec cannot be present")
if err := validateOnlyOneCMKSpec(&plan); err != nil {
return nil, err
}

switch cmkProvider {
@@ -748,6 +799,18 @@ func createCmkSpec(plan Cluster) (*openapiclient.CMKSpec, error) {

awsCmkSpec := openapiclient.NewAWSCMKSpec(awsAccessKey, awsSecretKey, awsArnList)
cmkSpec.SetAwsCmkSpec(*awsCmkSpec)
case "AZURE":
if plan.CMKSpec.AzureCMKSpec == nil {
return nil, errors.New("Provider type is AZURE but AZURE CMK spec is missing.")
}
azureClientId := plan.CMKSpec.AzureCMKSpec.ClientID.Value
azureClientSecret := plan.CMKSpec.AzureCMKSpec.ClientSecret.Value
azureTenantId := plan.CMKSpec.AzureCMKSpec.TenantID.Value
azureKeyVaultUri := plan.CMKSpec.AzureCMKSpec.KeyVaultUri.Value
azureKeyName := plan.CMKSpec.AzureCMKSpec.KeyName.Value

azureCmkSpec := openapiclient.NewAzureCMKSpec(azureClientId, azureClientSecret, azureTenantId, azureKeyVaultUri, azureKeyName)
cmkSpec.SetAzureCmkSpec(*azureCmkSpec)
}

cmkSpec.SetIsEnabled(plan.CMKSpec.IsEnabled.Value)
@@ -1039,6 +1102,10 @@ func (r resourceCluster) Create(ctx context.Context, req tfsdk.CreateResourceReq
case "GCP":
cluster.CMKSpec.GCPCMKSpec.GcpServiceAccount.ClientId = types.String{Value: string(cmkSpec.GetGcpCmkSpec().GcpServiceAccount.ClientId)}
cluster.CMKSpec.GCPCMKSpec.GcpServiceAccount.PrivateKey = types.String{Value: string(*cmkSpec.GetGcpCmkSpec().GcpServiceAccount.PrivateKey)}
case "AZURE":
cluster.CMKSpec.AzureCMKSpec.ClientID = types.String{Value: string(cmkSpec.GetAzureCmkSpec().ClientId)}
cluster.CMKSpec.AzureCMKSpec.ClientSecret = types.String{Value: string(cmkSpec.GetAzureCmkSpec().ClientSecret)}
cluster.CMKSpec.AzureCMKSpec.TenantID = types.String{Value: string(cmkSpec.GetAzureCmkSpec().TenantId)}
}
}

@@ -1245,6 +1312,10 @@ func (r resourceCluster) Read(ctx context.Context, req tfsdk.ReadResourceRequest
case "GCP":
cluster.CMKSpec.GCPCMKSpec.GcpServiceAccount.ClientId.Value = cmkSpec.GCPCMKSpec.GcpServiceAccount.ClientId.Value
cluster.CMKSpec.GCPCMKSpec.GcpServiceAccount.PrivateKey.Value = cmkSpec.GCPCMKSpec.GcpServiceAccount.PrivateKey.Value
case "AZURE":
cluster.CMKSpec.AzureCMKSpec.ClientID = types.String{Value: string(cmkSpec.AzureCMKSpec.ClientID.Value)}
cluster.CMKSpec.AzureCMKSpec.ClientSecret = types.String{Value: string(cmkSpec.AzureCMKSpec.ClientSecret.Value)}
cluster.CMKSpec.AzureCMKSpec.TenantID = types.String{Value: string(cmkSpec.AzureCMKSpec.TenantID.Value)}
}
}

@@ -1374,6 +1445,17 @@ func resourceClusterRead(ctx context.Context, accountId string, projectId string

cmkSpec.GCPCMKSpec = &gcpCMKSpec
cluster.CMKSpec = &cmkSpec
case "AZURE":
azureCMKSpec := AzureCMKSpec{
ClientID: types.String{Value: cmkDataSpec.GetAzureCmkSpec().ClientId},
ClientSecret: types.String{Value: cmkDataSpec.GetAzureCmkSpec().ClientSecret},
TenantID: types.String{Value: cmkDataSpec.GetAzureCmkSpec().TenantId},
KeyVaultUri: types.String{Value: cmkDataSpec.GetAzureCmkSpec().KeyVaultUri},
KeyName: types.String{Value: cmkDataSpec.GetAzureCmkSpec().KeyName},
}

cmkSpec.AzureCMKSpec = &azureCMKSpec
cluster.CMKSpec = &cmkSpec
}
}

@@ -1856,7 +1938,8 @@ func (r resourceCluster) Update(ctx context.Context, req tfsdk.UpdateResourceReq
tflog.Debug(ctx, "Cluster Update: Allow list IDs read from API server ", map[string]interface{}{
"Allow List IDs": cluster.ClusterAllowListIDs})

// Update the State file with the unmasked creds for AWS (secret key,access) and GCP (client id,private key)
// Update the State file with the unmasked creds for AWS (Secret Key, Access Key), GCP (Client ID, Private Key)
// and Azure (client ID, client Secret, tenant ID)
if plan.CMKSpec != nil {
providerType := cluster.CMKSpec.ProviderType.Value
switch providerType {
@@ -1866,6 +1949,10 @@ func (r resourceCluster) Update(ctx context.Context, req tfsdk.UpdateResourceReq
case "GCP":
cluster.CMKSpec.GCPCMKSpec.GcpServiceAccount.ClientId = plan.CMKSpec.GCPCMKSpec.GcpServiceAccount.ClientId
cluster.CMKSpec.GCPCMKSpec.GcpServiceAccount.PrivateKey = plan.CMKSpec.GCPCMKSpec.GcpServiceAccount.PrivateKey
case "AZURE":
cluster.CMKSpec.AzureCMKSpec.ClientID = plan.CMKSpec.AzureCMKSpec.ClientID
cluster.CMKSpec.AzureCMKSpec.ClientSecret = plan.CMKSpec.AzureCMKSpec.ClientSecret
cluster.CMKSpec.AzureCMKSpec.TenantID = plan.CMKSpec.AzureCMKSpec.TenantID
}
}

30 changes: 30 additions & 0 deletions mock_yugabytedb_managed_go_client_internal/mock_api_account.go

0 comments on commit 4a528df

Please sign in to comment.