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

azurerm_synapse_workspace_key - can not be correctly rotated #15897

Merged
Merged
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
11 changes: 5 additions & 6 deletions internal/services/synapse/synapse_workspace_key_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"github.com/Azure/azure-sdk-for-go/services/synapse/mgmt/2021-03-01/synapse"
"github.com/hashicorp/terraform-provider-azurerm/internal/clients"
"github.com/hashicorp/terraform-provider-azurerm/internal/locks"
keyVaultValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/keyvault/validate"
"github.com/hashicorp/terraform-provider-azurerm/internal/services/synapse/parse"
"github.com/hashicorp/terraform-provider-azurerm/internal/services/synapse/validate"
Expand Down Expand Up @@ -115,6 +116,8 @@ func resourceSynapseWorkspaceKeysCreateUpdate(d *pluginsdk.ResourceData, meta in
actualKeyName = keyNameTypoed
}

locks.ByName(workspaceId.Name, "azurerm_synapse_workspace")
defer locks.UnlockByName(workspaceId.Name, "azurerm_synapse_workspace")
keyresult, err := client.CreateOrUpdate(ctx, workspaceId.ResourceGroup, workspaceId.Name, actualKeyName, synapseKey)
if err != nil {
return fmt.Errorf("creating Synapse Workspace Key %q (Workspace %q): %+v", workspaceId.Name, workspaceId.Name, err)
Expand Down Expand Up @@ -185,13 +188,9 @@ func resourceSynapseWorkspaceKeysDelete(d *pluginsdk.ResourceData, meta interfac

// Azure only lets you delete keys that are not active
if !*keyresult.KeyProperties.IsActiveCMK {
keyresult, err := client.Delete(ctx, id.ResourceGroup, id.WorkspaceName, id.KeyName)
_, err := client.Delete(ctx, id.ResourceGroup, id.WorkspaceName, id.KeyName)
if err != nil {
return fmt.Errorf("Unable to delete key %s in workspace %s: %v", id.KeyName, id.WorkspaceName, err)
}

if keyresult.ID == nil || *keyresult.ID == "" {
return fmt.Errorf("empty or nil ID returned for Synapse Key %q during delete", id.KeyName)
return fmt.Errorf("unable to delete key %s in workspace %s: %v", id.KeyName, id.WorkspaceName, err)
}
}

Expand Down
178 changes: 131 additions & 47 deletions internal/services/synapse/synapse_workspace_key_resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,39 @@ import (

type SynapseWorkspaceKeysResource struct{}

func TestAccSynapseWorkspaceKeys_customerManagedKey(t *testing.T) {
func TestAccSynapseWorkspaceKeys_basic(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_synapse_workspace_key", "test")
r := SynapseWorkspaceKeysResource{}

data.ResourceTest(t, r, []acceptance.TestStep{
{
Config: r.customerManagedKey(data),
Config: r.basic(data),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
})
}

func TestAccSynapseWorkspaceKeys_basicUpdate(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_synapse_workspace_key", "test")
r := SynapseWorkspaceKeysResource{}

data.ResourceTest(t, r, []acceptance.TestStep{
{
Config: r.basic(data),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
{
Config: r.basicUpdate(data),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
// CMK takes a while to activate, so validation against the plan tends to fail.
data.ImportStep(),
})
}
Expand All @@ -48,66 +69,76 @@ func (r SynapseWorkspaceKeysResource) Exists(ctx context.Context, client *client
return utils.Bool(true), nil
}

func (r SynapseWorkspaceKeysResource) customerManagedKey(data acceptance.TestData) string {
func (r SynapseWorkspaceKeysResource) basic(data acceptance.TestData) string {
template := r.template(data)
return fmt.Sprintf(`
%s

data "azurerm_client_config" "current" {}

resource "azurerm_key_vault" "test" {
name = "acckv%d"
location = azurerm_resource_group.test.location
resource_group_name = azurerm_resource_group.test.name
tenant_id = data.azurerm_client_config.current.tenant_id
sku_name = "standard"
purge_protection_enabled = true
resource "azurerm_synapse_workspace" "test" {
name = "acctestsw%[2]d"
resource_group_name = azurerm_resource_group.test.name
location = azurerm_resource_group.test.location
storage_data_lake_gen2_filesystem_id = azurerm_storage_data_lake_gen2_filesystem.test.id
sql_administrator_login = "sqladminuser"
sql_administrator_login_password = "H@Sh1CoR3!"
customer_managed_key {
key_versionless_id = azurerm_key_vault_key.test.versionless_id
key_name = "test_key"
}
identity {
type = "SystemAssigned"
}
}

resource "azurerm_key_vault_access_policy" "deployer" {
resource "azurerm_key_vault_access_policy" "workspace_policy" {
key_vault_id = azurerm_key_vault.test.id
tenant_id = data.azurerm_client_config.current.tenant_id
object_id = data.azurerm_client_config.current.object_id
tenant_id = azurerm_synapse_workspace.test.identity[0].tenant_id
object_id = azurerm_synapse_workspace.test.identity[0].principal_id

key_permissions = [
"Create", "Get", "Delete", "Purge"
"Get", "WrapKey", "UnwrapKey"
]
}

resource "azurerm_key_vault_key" "test" {
name = "key"
key_vault_id = azurerm_key_vault.test.id
key_type = "RSA"
key_size = 2048
key_opts = [
"unwrapKey",
"wrapKey"
]
depends_on = [
azurerm_key_vault_access_policy.deployer
]
resource "azurerm_synapse_workspace_key" "test" {
customer_managed_key_versionless_id = azurerm_key_vault_key.test.versionless_id
synapse_workspace_id = azurerm_synapse_workspace.test.id
active = true
cusomter_managed_key_name = "test_key"
depends_on = [azurerm_key_vault_access_policy.workspace_policy]
}

resource "azurerm_synapse_workspace_key" "test2" {
customer_managed_key_versionless_id = azurerm_key_vault_key.test2.versionless_id
synapse_workspace_id = azurerm_synapse_workspace.test.id
active = false
cusomter_managed_key_name = "test_key2"
depends_on = [azurerm_key_vault_access_policy.workspace_policy, azurerm_synapse_workspace_key.test]
}
`, template, data.RandomInteger)
}

func (r SynapseWorkspaceKeysResource) basicUpdate(data acceptance.TestData) string {
template := r.template(data)
return fmt.Sprintf(`
%s

resource "azurerm_synapse_workspace" "test" {
name = "acctestsw%d"
name = "acctestsw%[2]d"
resource_group_name = azurerm_resource_group.test.name
location = azurerm_resource_group.test.location
storage_data_lake_gen2_filesystem_id = azurerm_storage_data_lake_gen2_filesystem.test.id
sql_administrator_login = "sqladminuser"
sql_administrator_login_password = "H@Sh1CoR3!"
customer_managed_key {
key_versionless_id = azurerm_key_vault_key.test.versionless_id
key_name = "test_key"

key_versionless_id = azurerm_key_vault_key.test2.versionless_id
key_name = "test_key2"
}
identity {
type = "SystemAssigned"
}
}




resource "azurerm_key_vault_access_policy" "workspace_policy" {
key_vault_id = azurerm_key_vault.test.id
tenant_id = azurerm_synapse_workspace.test.identity[0].tenant_id
Expand All @@ -121,16 +152,19 @@ resource "azurerm_key_vault_access_policy" "workspace_policy" {
resource "azurerm_synapse_workspace_key" "test" {
customer_managed_key_versionless_id = azurerm_key_vault_key.test.versionless_id
synapse_workspace_id = azurerm_synapse_workspace.test.id
active = true
active = false
cusomter_managed_key_name = "test_key"
depends_on = [azurerm_key_vault_access_policy.workspace_policy]
}





`, template, data.RandomInteger, data.RandomInteger)
resource "azurerm_synapse_workspace_key" "test2" {
customer_managed_key_versionless_id = azurerm_key_vault_key.test2.versionless_id
synapse_workspace_id = azurerm_synapse_workspace.test.id
active = true
cusomter_managed_key_name = "test_key2"
depends_on = [azurerm_key_vault_access_policy.workspace_policy, azurerm_synapse_workspace_key.test]
}
`, template, data.RandomInteger)
}

func (r SynapseWorkspaceKeysResource) template(data acceptance.TestData) string {
Expand All @@ -145,12 +179,12 @@ provider "azurerm" {
}

resource "azurerm_resource_group" "test" {
name = "acctestRG-synapse-%d"
location = "%s"
name = "acctestRG-synapse-%[3]d"
location = "%[1]s"
}

resource "azurerm_storage_account" "test" {
name = "acctestacc%s"
name = "acctestacc%[2]s"
resource_group_name = azurerm_resource_group.test.name
location = azurerm_resource_group.test.location
account_kind = "BlobStorage"
Expand All @@ -159,8 +193,58 @@ resource "azurerm_storage_account" "test" {
}

resource "azurerm_storage_data_lake_gen2_filesystem" "test" {
name = "acctest-%d"
name = "acctest-%[3]d"
storage_account_id = azurerm_storage_account.test.id
}
`, data.RandomInteger, data.Locations.Primary, data.RandomString, data.RandomInteger)

data "azurerm_client_config" "current" {}

resource "azurerm_key_vault" "test" {
name = "acckv%[3]d"
location = azurerm_resource_group.test.location
resource_group_name = azurerm_resource_group.test.name
tenant_id = data.azurerm_client_config.current.tenant_id
sku_name = "standard"
purge_protection_enabled = true
}

resource "azurerm_key_vault_access_policy" "deployer" {
key_vault_id = azurerm_key_vault.test.id
tenant_id = data.azurerm_client_config.current.tenant_id
object_id = data.azurerm_client_config.current.object_id

key_permissions = [
"Create", "Get", "Delete", "Purge"
]
}

resource "azurerm_key_vault_key" "test" {
name = "key"
key_vault_id = azurerm_key_vault.test.id
key_type = "RSA"
key_size = 2048
key_opts = [
"unwrapKey",
"wrapKey"
]
depends_on = [
azurerm_key_vault_access_policy.deployer
]
}

resource "azurerm_key_vault_key" "test2" {
name = "key2"
key_vault_id = azurerm_key_vault.test.id
key_type = "RSA"
key_size = 2048
key_opts = [
"unwrapKey",
"wrapKey"
]
depends_on = [
azurerm_key_vault_access_policy.deployer
]
}

`, data.Locations.Primary, data.RandomString, data.RandomInteger)
}
51 changes: 50 additions & 1 deletion internal/services/synapse/synapse_workspace_resource.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package synapse

import (
"context"
"fmt"
"log"
"net/url"
Expand Down Expand Up @@ -415,6 +416,10 @@ func resourceSynapseWorkspaceCreate(d *pluginsdk.ResourceData, meta interface{})
return fmt.Errorf("waiting for creation of %s: %+v", id, err)
}

if err := waitSynapseWorkspaceCMKState(ctx, client, &id); err != nil {
return fmt.Errorf("failed waiting for updating %s: %+v", id, err)
}

aadAdmin := expandArmWorkspaceAadAdminInfo(d.Get("aad_admin").([]interface{}))
if aadAdmin != nil {
future, err := aadAdminClient.CreateOrUpdate(ctx, id.ResourceGroup, id.Name, *aadAdmin)
Expand Down Expand Up @@ -566,7 +571,7 @@ func resourceSynapseWorkspaceUpdate(d *pluginsdk.ResourceData, meta interface{})
return err
}

if d.HasChanges("tags", "sql_administrator_login_password", "github_repo", "azure_devops_repo", "customer_managed_key_versionless_id") {
if d.HasChanges("tags", "sql_administrator_login_password", "github_repo", "azure_devops_repo", "customer_managed_key") {
publicNetworkAccess := synapse.WorkspacePublicNetworkAccessEnabled
if !d.Get("public_network_access_enabled").(bool) {
publicNetworkAccess = synapse.WorkspacePublicNetworkAccessDisabled
Expand Down Expand Up @@ -602,6 +607,10 @@ func resourceSynapseWorkspaceUpdate(d *pluginsdk.ResourceData, meta interface{})
if err = future.WaitForCompletionRef(ctx, client.Client); err != nil {
return fmt.Errorf("waiting on updating future for Synapse Workspace %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err)
}

if err := waitSynapseWorkspaceCMKState(ctx, client, id); err != nil {
return fmt.Errorf("failed waiting for updating %s: %+v", id, err)
}
}

if d.HasChange("aad_admin") {
Expand Down Expand Up @@ -685,6 +694,46 @@ func resourceSynapseWorkspaceDelete(d *pluginsdk.ResourceData, meta interface{})
return nil
}

func waitSynapseWorkspaceCMKState(ctx context.Context, client *synapse.WorkspacesClient, id *parse.WorkspaceId) error {
deadline, ok := ctx.Deadline()
if !ok {
return fmt.Errorf("context had no deadline")
}
stateConf := &pluginsdk.StateChangeConf{
Pending: []string{
"Updating",
"ActivatingWorkspace",
},
Target: []string{
"Succeeded",
"Consistent",
"AwaitingUserAction",
},
Refresh: synapseWorkspaceCMKUpdateStateRefreshFunc(ctx, client, id),
MinTimeout: 5 * time.Second,
ContinuousTargetOccurence: 5,
Timeout: time.Until(deadline),
}

if _, err := stateConf.WaitForStateContext(ctx); err != nil {
return fmt.Errorf("failed waiting for updating %s: %+v", id, err)
}
return nil
}

func synapseWorkspaceCMKUpdateStateRefreshFunc(ctx context.Context, client *synapse.WorkspacesClient, id *parse.WorkspaceId) pluginsdk.StateRefreshFunc {
return func() (interface{}, string, error) {
res, err := client.Get(ctx, id.ResourceGroup, id.Name)
if err != nil {
return nil, "", fmt.Errorf("retrieving %s: %+v", id, err)
}
if res.Encryption != nil && res.Encryption.Cmk != nil {
return res, *res.Encryption.Cmk.Status, nil
}
return res, "Succeeded", nil
}
}

func expandArmWorkspaceDataLakeStorageAccountDetails(storageDataLakeGen2FilesystemId string) *synapse.DataLakeStorageAccountDetails {
uri, _ := url.Parse(storageDataLakeGen2FilesystemId)
return &synapse.DataLakeStorageAccountDetails{
Expand Down