Skip to content

Commit

Permalink
azurerm_kubernetes_cluster - Support for KMS arguments (#19893)
Browse files Browse the repository at this point in the history
* feat(aks): Ability to use KMS encryption

* chore: Adapt construction of Defender inside securityProfile

* chore: Extend error handling and test cases

* chore: Rename key_vault_kms {} -> key_management_service {}

* chore: Drop "bytes" and "text/template" dependency and use common code style

* chore: Automatically set key vault resource ID

* chore: Add nil check on input attributes

Apply suggestions from code review

Co-authored-by: stephybun <[email protected]>

Co-authored-by: stephybun <[email protected]>
  • Loading branch information
mkilchhofer and stephybun authored Jan 18, 2023
1 parent 22b24fc commit 31237e9
Show file tree
Hide file tree
Showing 5 changed files with 269 additions and 27 deletions.
45 changes: 45 additions & 0 deletions internal/services/containers/kubernetes_cluster_data_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,23 @@ func dataSourceKubernetesCluster() *pluginsdk.Resource {

"identity": commonschema.SystemOrUserAssignedIdentityComputed(),

"key_management_service": {
Type: pluginsdk.TypeList,
Computed: true,
Elem: &pluginsdk.Resource{
Schema: map[string]*pluginsdk.Schema{
"key_vault_key_id": {
Type: pluginsdk.TypeString,
Computed: true,
},
"key_vault_network_access": {
Type: pluginsdk.TypeString,
Computed: true,
},
},
},
},

"kubernetes_version": {
Type: pluginsdk.TypeString,
Computed: true,
Expand Down Expand Up @@ -718,6 +735,11 @@ func dataSourceKubernetesClusterRead(d *pluginsdk.ResourceData, meta interface{}
return fmt.Errorf("setting `agent_pool_profile`: %+v", err)
}

azureKeyVaultKms := flattenKubernetesClusterDataSourceKeyVaultKms(props.SecurityProfile.AzureKeyVaultKms)
if err := d.Set("key_management_service", azureKeyVaultKms); err != nil {
return fmt.Errorf("setting `key_management_service`: %+v", err)
}

kubeletIdentity, err := flattenKubernetesClusterDataSourceIdentityProfile(props.IdentityProfile)
if err != nil {
return err
Expand Down Expand Up @@ -827,6 +849,29 @@ func dataSourceKubernetesClusterRead(d *pluginsdk.ResourceData, meta interface{}
return nil
}

func flattenKubernetesClusterDataSourceKeyVaultKms(input *managedclusters.AzureKeyVaultKms) []interface{} {
azureKeyVaultKms := make([]interface{}, 0)

if input != nil && input.Enabled != nil && *input.Enabled {
keyId := ""
if v := input.KeyId; v != nil {
keyId = *v
}

networkAccess := ""
if v := input.KeyVaultNetworkAccess; v != nil {
networkAccess = string(*v)
}

azureKeyVaultKms = append(azureKeyVaultKms, map[string]interface{}{
"key_vault_key_id": keyId,
"key_vault_network_access": networkAccess,
})
}

return azureKeyVaultKms
}

func flattenKubernetesClusterDataSourceStorageProfile(input *managedclusters.ManagedClusterStorageProfile) []interface{} {
storageProfile := make([]interface{}, 0)

Expand Down
129 changes: 102 additions & 27 deletions internal/services/containers/kubernetes_cluster_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,11 @@ import (
"github.com/hashicorp/terraform-provider-azurerm/internal/services/containers/kubernetes"
"github.com/hashicorp/terraform-provider-azurerm/internal/services/containers/migration"
containerValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/containers/validate"
keyVaultClient "github.com/hashicorp/terraform-provider-azurerm/internal/services/keyvault/client"
keyVaultParse "github.com/hashicorp/terraform-provider-azurerm/internal/services/keyvault/parse"
keyVaultValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/keyvault/validate"
networkValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/network/validate"
resourcesClient "github.com/hashicorp/terraform-provider-azurerm/internal/services/resource/client"
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk"
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/suppress"
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation"
Expand Down Expand Up @@ -709,6 +713,28 @@ func resourceKubernetesCluster() *pluginsdk.Resource {
},
},

"key_management_service": {
Type: pluginsdk.TypeList,
Optional: true,
ForceNew: false,
MaxItems: 1,
Elem: &pluginsdk.Resource{
Schema: map[string]*pluginsdk.Schema{
"key_vault_key_id": {
Type: pluginsdk.TypeString,
Required: true,
ValidateFunc: keyVaultValidate.NestedItemId,
},
"key_vault_network_access": {
Type: pluginsdk.TypeString,
Default: string(managedclusters.KeyVaultNetworkAccessTypesPublic),
Optional: true,
ValidateFunc: validation.StringInSlice(managedclusters.PossibleValuesForKeyVaultNetworkAccessTypes(), false),
},
},
},
},

"microsoft_defender": {
Type: pluginsdk.TypeList,
Optional: true,
Expand Down Expand Up @@ -1225,6 +1251,8 @@ func resourceKubernetesClusterCreate(d *pluginsdk.ResourceData, meta interface{}
subscriptionId := meta.(*clients.Client).Account.SubscriptionId
tenantId := meta.(*clients.Client).Account.TenantId
client := meta.(*clients.Client).Containers.KubernetesClustersClient
keyVaultsClient := meta.(*clients.Client).KeyVault
resourcesClient := meta.(*clients.Client).Resource
env := meta.(*clients.Client).Containers.Environment
ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d)
defer cancel()
Expand Down Expand Up @@ -1332,12 +1360,15 @@ func resourceKubernetesClusterCreate(d *pluginsdk.ResourceData, meta interface{}
publicNetworkAccess = managedclusters.PublicNetworkAccessDisabled
}

microsoftDefenderRaw := d.Get("microsoft_defender").([]interface{})
securityProfile := expandKubernetesClusterMicrosoftDefender(d, microsoftDefenderRaw)

storageProfileRaw := d.Get("storage_profile").([]interface{})
storageProfile := expandStorageProfile(storageProfileRaw)

// assemble securityProfile (Defender, WorkloadIdentity, ImageCleaner, AzureKeyVaultKms)
securityProfile := &managedclusters.ManagedClusterSecurityProfile{}

microsoftDefenderRaw := d.Get("microsoft_defender").([]interface{})
securityProfile.Defender = expandKubernetesClusterMicrosoftDefender(d, microsoftDefenderRaw)

workloadIdentity := false
if v, ok := d.GetOk("workload_identity_enabled"); ok {
workloadIdentity = v.(bool)
Expand All @@ -1346,23 +1377,22 @@ func resourceKubernetesClusterCreate(d *pluginsdk.ResourceData, meta interface{}
return fmt.Errorf("`oidc_issuer_enabled` must be set to `true` to enable Azure AD Workload Identity")
}

if securityProfile == nil {
securityProfile = &managedclusters.ManagedClusterSecurityProfile{}
}

securityProfile.WorkloadIdentity = &managedclusters.ManagedClusterSecurityProfileWorkloadIdentity{
Enabled: &workloadIdentity,
}
}
if securityProfile == nil {
securityProfile = &managedclusters.ManagedClusterSecurityProfile{}
}

securityProfile.ImageCleaner = &managedclusters.ManagedClusterSecurityProfileImageCleaner{
Enabled: utils.Bool(d.Get("image_cleaner_enabled").(bool)),
IntervalHours: utils.Int64(int64(d.Get("image_cleaner_interval_hours").(int))),
}

azureKeyVaultKmsRaw := d.Get("key_management_service").([]interface{})
securityProfile.AzureKeyVaultKms, err = expandKubernetesClusterAzureKeyVaultKms(ctx, keyVaultsClient, resourcesClient, d, azureKeyVaultKmsRaw)
if err != nil {
return err
}

parameters := managedclusters.ManagedCluster{
Name: utils.String(id.ResourceName),
ExtendedLocation: expandEdgeZone(d.Get("edge_zone").(string)),
Expand Down Expand Up @@ -1488,6 +1518,8 @@ func resourceKubernetesClusterUpdate(d *pluginsdk.ResourceData, meta interface{}
containersClient := meta.(*clients.Client).Containers
nodePoolsClient := containersClient.AgentPoolsClient
clusterClient := containersClient.KubernetesClustersClient
keyVaultsClient := meta.(*clients.Client).KeyVault
resourcesClient := meta.(*clients.Client).Resource
env := containersClient.Environment
ctx, cancel := timeouts.ForUpdate(meta.(*clients.Client).StopContext, d)
defer cancel()
Expand Down Expand Up @@ -1834,11 +1866,18 @@ func resourceKubernetesClusterUpdate(d *pluginsdk.ResourceData, meta interface{}
existing.Model.Properties.OidcIssuerProfile = oidcIssuerProfile
}

if d.HasChanges("key_management_service") {
updateCluster = true
azureKeyVaultKmsRaw := d.Get("key_management_service").([]interface{})
azureKeyVaultKms, _ := expandKubernetesClusterAzureKeyVaultKms(ctx, keyVaultsClient, resourcesClient, d, azureKeyVaultKmsRaw)
existing.Model.Properties.SecurityProfile.AzureKeyVaultKms = azureKeyVaultKms
}

if d.HasChanges("microsoft_defender") {
updateCluster = true
microsoftDefenderRaw := d.Get("microsoft_defender").([]interface{})
microsoftDefender := expandKubernetesClusterMicrosoftDefender(d, microsoftDefenderRaw)
existing.Model.Properties.SecurityProfile = microsoftDefender
existing.Model.Properties.SecurityProfile.Defender = microsoftDefender
}

if d.HasChanges("storage_profile") {
Expand Down Expand Up @@ -2207,7 +2246,7 @@ func resourceKubernetesClusterRead(d *pluginsdk.ResourceData, meta interface{})
d.Set("oidc_issuer_enabled", oidcIssuerEnabled)
d.Set("oidc_issuer_url", oidcIssuerUrl)

microsoftDefender := flattenKubernetesClusterMicrosoftDefender(props.SecurityProfile)
microsoftDefender := flattenKubernetesClusterMicrosoftDefender(props.SecurityProfile.Defender)
if err := d.Set("microsoft_defender", microsoftDefender); err != nil {
return fmt.Errorf("setting `microsoft_defender`: %+v", err)
}
Expand All @@ -2223,6 +2262,11 @@ func resourceKubernetesClusterRead(d *pluginsdk.ResourceData, meta interface{})
}
d.Set("workload_identity_enabled", workloadIdentity)

azureKeyVaultKms := flattenKubernetesClusterDataSourceKeyVaultKms(props.SecurityProfile.AzureKeyVaultKms)
if err := d.Set("key_management_service", azureKeyVaultKms); err != nil {
return fmt.Errorf("setting `key_management_service`: %+v", err)
}

// adminProfile is only available for RBAC enabled clusters with AAD and local account is not disabled
if props.AadProfile != nil && (props.DisableLocalAccounts == nil || !*props.DisableLocalAccounts) {
accessProfileId := managedclusters.NewAccessProfileID(id.SubscriptionId, id.ResourceGroupName, id.ResourceName, "clusterAdmin")
Expand Down Expand Up @@ -3376,6 +3420,41 @@ func expandKubernetesClusterAutoScalerProfile(input []interface{}) *managedclust
}
}

func expandKubernetesClusterAzureKeyVaultKms(ctx context.Context, keyVaultsClient *keyVaultClient.Client, resourcesClient *resourcesClient.Client, d *pluginsdk.ResourceData, input []interface{}) (*managedclusters.AzureKeyVaultKms, error) {
if ((input == nil) || len(input) == 0) && d.HasChanges("key_management_service") {
return &managedclusters.AzureKeyVaultKms{
Enabled: utils.Bool(false),
}, nil
} else if (input == nil) || len(input) == 0 {
return nil, nil
}

raw := input[0].(map[string]interface{})
kvAccess := managedclusters.KeyVaultNetworkAccessTypes(raw["key_vault_network_access"].(string))

azureKeyVaultKms := &managedclusters.AzureKeyVaultKms{
Enabled: utils.Bool(true),
KeyId: utils.String(raw["key_vault_key_id"].(string)),
KeyVaultNetworkAccess: &kvAccess,
}

// Set Key vault Resource ID in case public access is disabled
if kvAccess == managedclusters.KeyVaultNetworkAccessTypesPrivate {
keyVaultKeyId, err := keyVaultParse.ParseNestedItemID(*azureKeyVaultKms.KeyId)
if err != nil {
return nil, err
}
keyVaultID, err := keyVaultsClient.KeyVaultIDFromBaseUrl(ctx, resourcesClient, keyVaultKeyId.KeyVaultBaseUrl)
if err != nil {
return nil, fmt.Errorf("retrieving the Resource ID the Key Vault at URL %q: %s", keyVaultKeyId.KeyVaultBaseUrl, err)
}

azureKeyVaultKms.KeyVaultResourceId = keyVaultID
}

return azureKeyVaultKms, nil
}

func expandKubernetesClusterMaintenanceConfiguration(input []interface{}) *maintenanceconfigurations.MaintenanceConfigurationProperties {
if len(input) == 0 {
return nil
Expand Down Expand Up @@ -3530,37 +3609,33 @@ func flattenKubernetesClusterHttpProxyConfig(props *managedclusters.ManagedClust
})
}

func expandKubernetesClusterMicrosoftDefender(d *pluginsdk.ResourceData, input []interface{}) *managedclusters.ManagedClusterSecurityProfile {
func expandKubernetesClusterMicrosoftDefender(d *pluginsdk.ResourceData, input []interface{}) *managedclusters.ManagedClusterSecurityProfileDefender {
if (len(input) == 0 || input[0] == nil) && d.HasChange("microsoft_defender") {
return &managedclusters.ManagedClusterSecurityProfile{
Defender: &managedclusters.ManagedClusterSecurityProfileDefender{
SecurityMonitoring: &managedclusters.ManagedClusterSecurityProfileDefenderSecurityMonitoring{
Enabled: utils.Bool(false),
},
return &managedclusters.ManagedClusterSecurityProfileDefender{
SecurityMonitoring: &managedclusters.ManagedClusterSecurityProfileDefenderSecurityMonitoring{
Enabled: utils.Bool(false),
},
}
} else if len(input) == 0 || input[0] == nil {
return nil
}

config := input[0].(map[string]interface{})
return &managedclusters.ManagedClusterSecurityProfile{
Defender: &managedclusters.ManagedClusterSecurityProfileDefender{
SecurityMonitoring: &managedclusters.ManagedClusterSecurityProfileDefenderSecurityMonitoring{
Enabled: utils.Bool(true),
},
LogAnalyticsWorkspaceResourceId: utils.String(config["log_analytics_workspace_id"].(string)),
return &managedclusters.ManagedClusterSecurityProfileDefender{
SecurityMonitoring: &managedclusters.ManagedClusterSecurityProfileDefenderSecurityMonitoring{
Enabled: utils.Bool(true),
},
LogAnalyticsWorkspaceResourceId: utils.String(config["log_analytics_workspace_id"].(string)),
}
}

func flattenKubernetesClusterMicrosoftDefender(input *managedclusters.ManagedClusterSecurityProfile) []interface{} {
if input == nil || input.Defender == nil || (input.Defender.SecurityMonitoring != nil && input.Defender.SecurityMonitoring.Enabled != nil && !*input.Defender.SecurityMonitoring.Enabled) {
func flattenKubernetesClusterMicrosoftDefender(input *managedclusters.ManagedClusterSecurityProfileDefender) []interface{} {
if input == nil || (input.SecurityMonitoring != nil && input.SecurityMonitoring.Enabled != nil && !*input.SecurityMonitoring.Enabled) {
return []interface{}{}
}

logAnalyticsWorkspace := ""
if v := input.Defender.LogAnalyticsWorkspaceResourceId; v != nil {
if v := input.LogAnalyticsWorkspaceResourceId; v != nil {
logAnalyticsWorkspace = *v
}

Expand Down
Loading

0 comments on commit 31237e9

Please sign in to comment.