From 52d0c9a10e677797d77a7b42a3374e2786836b70 Mon Sep 17 00:00:00 2001 From: Matthias Theuermann Date: Wed, 4 Dec 2024 18:20:33 +0100 Subject: [PATCH] feat: added MsDefender integration Signed-off-by: Matthias Theuermann --- docs/resources/integration_msdefender.md | 301 +++++++++++++++++ .../mondoo_integration_msdefender/main.tf | 20 ++ .../mondoo_integration_msdefender/outputs.tf | 22 ++ .../mondoo_integration_msdefender/resource.tf | 258 +++++++++++++++ .../integration_msdefender_resource.go | 312 ++++++++++++++++++ internal/provider/provider.go | 1 + 6 files changed, 914 insertions(+) create mode 100644 docs/resources/integration_msdefender.md create mode 100644 examples/resources/mondoo_integration_msdefender/main.tf create mode 100644 examples/resources/mondoo_integration_msdefender/outputs.tf create mode 100644 examples/resources/mondoo_integration_msdefender/resource.tf create mode 100644 internal/provider/integration_msdefender_resource.go diff --git a/docs/resources/integration_msdefender.md b/docs/resources/integration_msdefender.md new file mode 100644 index 0000000..5ffb320 --- /dev/null +++ b/docs/resources/integration_msdefender.md @@ -0,0 +1,301 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "mondoo_integration_msdefender Resource - terraform-provider-mondoo" +subcategory: "" +description: |- + Microsoft Defender for Cloud integration. +--- + +# mondoo_integration_msdefender (Resource) + +Microsoft Defender for Cloud integration. + +## Example Usage + +```terraform +# Variables +# ---------------------------------------------- + +variable "tenant_id" { + description = "The Azure Active Directory Tenant ID" + type = string + default = "ffffffff-ffff-ffff-ffff-ffffffffffff" +} + +variable "primary_subscription" { + description = "The primary Azure Subscription ID" + type = string + default = "ffffffff-ffff-ffff-ffff-ffffffffffff" +} + +locals { + mondoo_security_integration_name = "Mondoo Security Integration" +} + +# Azure AD with Application and Certificate +# ---------------------------------------------- + +provider "azuread" { + tenant_id = var.tenant_id +} + +data "azuread_client_config" "current" {} + +# Add the required permissions to the application +# User still need to be grant the permissions to the application via the Azure Portal +resource "azuread_application" "mondoo_security" { + display_name = local.mondoo_security_integration_name + + required_resource_access { + resource_app_id = "00000003-0000-0000-c000-000000000000" # Microsoft Graph + + resource_access { + id = "246dd0d5-5bd0-4def-940b-0421030a5b68" + type = "Role" + } + + resource_access { + id = "e321f0bb-e7f7-481e-bb28-e3b0b32d4bd0" + type = "Role" + } + + resource_access { + id = "5e0edab9-c148-49d0-b423-ac253e121825" + type = "Role" + } + + resource_access { + id = "bf394140-e372-4bf9-a898-299cfc7564e5" + type = "Role" + } + + resource_access { + id = "6e472fd1-ad78-48da-a0f0-97ab2c6b769e" + type = "Role" + } + + resource_access { + id = "dc5007c0-2d7d-4c42-879c-2dab87571379" + type = "Role" + } + + resource_access { + id = "b0afded3-3588-46d8-8b3d-9842eff778da" + type = "Role" + } + + resource_access { + id = "7ab1d382-f21e-4acd-a863-ba3e13f7da61" + type = "Role" + } + + resource_access { + id = "197ee4e9-b993-4066-898f-d6aecc55125b" + type = "Role" + } + + resource_access { + id = "9a5d68dd-52b0-4cc2-bd40-abcf44ac3a30" + type = "Role" + } + + resource_access { + id = "f8f035bb-2cce-47fb-8bf5-7baf3ecbee48" + type = "Role" + } + + resource_access { + id = "dbb9058a-0e50-45d7-ae91-66909b5d4664" + type = "Role" + } + + resource_access { + id = "9e640839-a198-48fb-8b9a-013fd6f6cbcd" + type = "Role" + } + + resource_access { + id = "37730810-e9ba-4e46-b07e-8ca78d182097" + type = "Role" + } + + resource_access { + id = "c7fbd983-d9aa-4fa7-84b8-17382c103bc4" + type = "Role" + } + } +} + +resource "tls_private_key" "credential" { + algorithm = "RSA" + rsa_bits = 4096 +} + +resource "tls_self_signed_cert" "credential" { + private_key_pem = tls_private_key.credential.private_key_pem + + # Certificate expires after 3 months. + validity_period_hours = 1680 + + # Generate a new certificate if Terraform is run within three + # hours of the certificate's expiration time. + early_renewal_hours = 3 + + # Reasonable set of uses for a server SSL certificate. + allowed_uses = [ + "key_encipherment", + "digital_signature", + "data_encipherment", + "cert_signing", + ] + + subject { + common_name = "mondoo" + } +} + +# Attach the certificate to the application +resource "azuread_application_certificate" "mondoo_security_integration" { + # see https://github.com/hashicorp/terraform-provider-azuread/issues/1227 + application_id = azuread_application.mondoo_security.id + type = "AsymmetricX509Cert" + value = tls_self_signed_cert.credential.cert_pem +} + +# Create a service principal for the application +resource "azuread_service_principal" "mondoo_security" { + client_id = azuread_application.mondoo_security.client_id + app_role_assignment_required = false + owners = [data.azuread_client_config.current.object_id] +} + +# Azure Permissions to Azure AD Application +# ---------------------------------------------- + +provider "azurerm" { + tenant_id = var.tenant_id + features {} +} + +data "azurerm_subscription" "primary" { + subscription_id = var.primary_subscription +} + +data "azurerm_subscriptions" "available" {} + +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/role_definition +resource "azurerm_role_definition" "mondoo_security_role" { + name = "tf-mondoo-security-role" + description = "This role includes all permissions for Mondoo Security to assess the security." + scope = data.azurerm_subscription.primary.id + + permissions { + actions = [ + "Microsoft.Authorization/*/read", + "Microsoft.ResourceHealth/availabilityStatuses/read", + "Microsoft.Insights/alertRules/*", + "Microsoft.Resources/deployments/*", + "Microsoft.Resources/subscriptions/resourceGroups/read", + "Microsoft.Support/*", + "Microsoft.Web/listSitesAssignedToHostName/read", + "Microsoft.Web/serverFarms/read", + "Microsoft.Web/sites/config/read", + "Microsoft.Web/sites/config/web/appsettings/read", + "Microsoft.Web/sites/config/web/connectionstrings/read", + "Microsoft.Web/sites/config/appsettings/read", + "Microsoft.web/sites/config/snapshots/read", + "Microsoft.Web/sites/config/list/action", + "Microsoft.Web/sites/read", + "Microsoft.KeyVault/checkNameAvailability/read", + "Microsoft.KeyVault/deletedVaults/read", + "Microsoft.KeyVault/locations/*/read", + "Microsoft.KeyVault/vaults/*/read", + "Microsoft.KeyVault/operations/read", + "Microsoft.Compute/virtualMachines/runCommands/read", + "Microsoft.Compute/virtualMachines/runCommands/write", + "Microsoft.Compute/virtualMachines/runCommands/delete" + ] + not_actions = [] + data_actions = [ + "Microsoft.KeyVault/vaults/*/read", + "Microsoft.KeyVault/vaults/secrets/readMetadata/action" + ] + not_data_actions = [] + } + + assignable_scopes = data.azurerm_subscriptions.available.subscriptions[*].id +} + +# add custom role to all subscriptions +resource "azurerm_role_assignment" "mondoo_security" { + count = length(data.azurerm_subscriptions.available.subscriptions) + scope = data.azurerm_subscriptions.available.subscriptions[count.index].id + role_definition_id = azurerm_role_definition.mondoo_security_role.role_definition_resource_id + principal_id = azuread_service_principal.mondoo_security.object_id + depends_on = [ + azurerm_role_definition.mondoo_security_role, + ] +} + +# add reader role to all subscriptions +resource "azurerm_role_assignment" "reader" { + count = length(data.azurerm_subscriptions.available.subscriptions) + scope = data.azurerm_subscriptions.available.subscriptions[count.index].id + role_definition_name = "Reader" + principal_id = azuread_service_principal.mondoo_security.object_id +} + +# Configure the Mondoo +# ---------------------------------------------- + +provider "mondoo" { + space = "hungry-poet-123456" +} + +# Setup the MsDefender integration +resource "mondoo_integration_msdefender" "msdefender_integration" { + name = "Azure ${local.mondoo_security_integration_name}" + tenant_id = var.tenant_id + client_id = azuread_application.mondoo_security.client_id + + # subscription_allow_list= ["ffffffff-ffff-ffff-ffff-ffffffffffff", "ffffffff-ffff-ffff-ffff-ffffffffffff"] + # subscription_deny_list = ["ffffffff-ffff-ffff-ffff-ffffffffffff", "ffffffff-ffff-ffff-ffff-ffffffffffff"] + credentials = { + pem_file = join("\n", [tls_self_signed_cert.credential.cert_pem, tls_private_key.credential.private_key_pem]) + } + # wait for the permissions to provisioned + depends_on = [ + azuread_application.mondoo_security, + azuread_service_principal.mondoo_security, + azurerm_role_assignment.mondoo_security, + azurerm_role_assignment.reader, + ] +} +``` + + +## Schema + +### Required + +- `client_id` (String) Azure Client ID. +- `credentials` (Attributes) (see [below for nested schema](#nestedatt--credentials)) +- `name` (String) Name of the integration. +- `tenant_id` (String) Azure Tenant ID. + +### Optional + +- `space_id` (String) Mondoo Space Identifier. If it is not provided, the provider space is used. +- `subscription_allow_list` (List of String) List of Azure subscriptions to scan. +- `subscription_deny_list` (List of String) List of Azure subscriptions to exclude from scanning. + +### Read-Only + +- `mrn` (String) Integration identifier + + +### Nested Schema for `credentials` + +Required: + +- `pem_file` (String, Sensitive) PEM file for Azure integration. diff --git a/examples/resources/mondoo_integration_msdefender/main.tf b/examples/resources/mondoo_integration_msdefender/main.tf new file mode 100644 index 0000000..70e7508 --- /dev/null +++ b/examples/resources/mondoo_integration_msdefender/main.tf @@ -0,0 +1,20 @@ +terraform { + required_providers { + azuread = { + source = "hashicorp/azuread" + version = ">= 2.48.0" + } + azurerm = { + source = "hashicorp/azurerm" + version = ">= 3.0.0" + } + mondoo = { + source = "mondoohq/mondoo" + version = ">= 0.19" + } + tls = { + source = "hashicorp/tls" + version = ">= 4.0.5" + } + } +} diff --git a/examples/resources/mondoo_integration_msdefender/outputs.tf b/examples/resources/mondoo_integration_msdefender/outputs.tf new file mode 100644 index 0000000..82debb7 --- /dev/null +++ b/examples/resources/mondoo_integration_msdefender/outputs.tf @@ -0,0 +1,22 @@ +output "cert_pem" { + description = "The self-signed certificate in PEM format" + value = tls_self_signed_cert.credential.cert_pem + sensitive = true +} + +output "private_key_pem" { + description = "The private key in PEM format" + value = join("\n", [tls_self_signed_cert.credential.cert_pem, tls_private_key.credential.private_key_pem]) + sensitive = true +} + +output "available_subscriptions" { + description = "Azure Subscriptions" + value = data.azurerm_subscriptions.available.subscriptions +} + +output "cnspec" { + description = "cnspec cli command" + value = "terraform output -raw private_key_pem > key.pem\ncnspec scan azure --tenant-id ${var.tenant_id} --client-id ${azuread_application.mondoo_security.client_id} --certificate-path key.pem" +} + diff --git a/examples/resources/mondoo_integration_msdefender/resource.tf b/examples/resources/mondoo_integration_msdefender/resource.tf new file mode 100644 index 0000000..3f12c31 --- /dev/null +++ b/examples/resources/mondoo_integration_msdefender/resource.tf @@ -0,0 +1,258 @@ +# Variables +# ---------------------------------------------- + +variable "tenant_id" { + description = "The Azure Active Directory Tenant ID" + type = string + default = "ffffffff-ffff-ffff-ffff-ffffffffffff" +} + +variable "primary_subscription" { + description = "The primary Azure Subscription ID" + type = string + default = "ffffffff-ffff-ffff-ffff-ffffffffffff" +} + +locals { + mondoo_security_integration_name = "Mondoo Security Integration" +} + +# Azure AD with Application and Certificate +# ---------------------------------------------- + +provider "azuread" { + tenant_id = var.tenant_id +} + +data "azuread_client_config" "current" {} + +# Add the required permissions to the application +# User still need to be grant the permissions to the application via the Azure Portal +resource "azuread_application" "mondoo_security" { + display_name = local.mondoo_security_integration_name + + required_resource_access { + resource_app_id = "00000003-0000-0000-c000-000000000000" # Microsoft Graph + + resource_access { + id = "246dd0d5-5bd0-4def-940b-0421030a5b68" + type = "Role" + } + + resource_access { + id = "e321f0bb-e7f7-481e-bb28-e3b0b32d4bd0" + type = "Role" + } + + resource_access { + id = "5e0edab9-c148-49d0-b423-ac253e121825" + type = "Role" + } + + resource_access { + id = "bf394140-e372-4bf9-a898-299cfc7564e5" + type = "Role" + } + + resource_access { + id = "6e472fd1-ad78-48da-a0f0-97ab2c6b769e" + type = "Role" + } + + resource_access { + id = "dc5007c0-2d7d-4c42-879c-2dab87571379" + type = "Role" + } + + resource_access { + id = "b0afded3-3588-46d8-8b3d-9842eff778da" + type = "Role" + } + + resource_access { + id = "7ab1d382-f21e-4acd-a863-ba3e13f7da61" + type = "Role" + } + + resource_access { + id = "197ee4e9-b993-4066-898f-d6aecc55125b" + type = "Role" + } + + resource_access { + id = "9a5d68dd-52b0-4cc2-bd40-abcf44ac3a30" + type = "Role" + } + + resource_access { + id = "f8f035bb-2cce-47fb-8bf5-7baf3ecbee48" + type = "Role" + } + + resource_access { + id = "dbb9058a-0e50-45d7-ae91-66909b5d4664" + type = "Role" + } + + resource_access { + id = "9e640839-a198-48fb-8b9a-013fd6f6cbcd" + type = "Role" + } + + resource_access { + id = "37730810-e9ba-4e46-b07e-8ca78d182097" + type = "Role" + } + + resource_access { + id = "c7fbd983-d9aa-4fa7-84b8-17382c103bc4" + type = "Role" + } + } +} + +resource "tls_private_key" "credential" { + algorithm = "RSA" + rsa_bits = 4096 +} + +resource "tls_self_signed_cert" "credential" { + private_key_pem = tls_private_key.credential.private_key_pem + + # Certificate expires after 3 months. + validity_period_hours = 1680 + + # Generate a new certificate if Terraform is run within three + # hours of the certificate's expiration time. + early_renewal_hours = 3 + + # Reasonable set of uses for a server SSL certificate. + allowed_uses = [ + "key_encipherment", + "digital_signature", + "data_encipherment", + "cert_signing", + ] + + subject { + common_name = "mondoo" + } +} + +# Attach the certificate to the application +resource "azuread_application_certificate" "mondoo_security_integration" { + # see https://github.com/hashicorp/terraform-provider-azuread/issues/1227 + application_id = azuread_application.mondoo_security.id + type = "AsymmetricX509Cert" + value = tls_self_signed_cert.credential.cert_pem +} + +# Create a service principal for the application +resource "azuread_service_principal" "mondoo_security" { + client_id = azuread_application.mondoo_security.client_id + app_role_assignment_required = false + owners = [data.azuread_client_config.current.object_id] +} + +# Azure Permissions to Azure AD Application +# ---------------------------------------------- + +provider "azurerm" { + tenant_id = var.tenant_id + features {} +} + +data "azurerm_subscription" "primary" { + subscription_id = var.primary_subscription +} + +data "azurerm_subscriptions" "available" {} + +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/role_definition +resource "azurerm_role_definition" "mondoo_security_role" { + name = "tf-mondoo-security-role" + description = "This role includes all permissions for Mondoo Security to assess the security." + scope = data.azurerm_subscription.primary.id + + permissions { + actions = [ + "Microsoft.Authorization/*/read", + "Microsoft.ResourceHealth/availabilityStatuses/read", + "Microsoft.Insights/alertRules/*", + "Microsoft.Resources/deployments/*", + "Microsoft.Resources/subscriptions/resourceGroups/read", + "Microsoft.Support/*", + "Microsoft.Web/listSitesAssignedToHostName/read", + "Microsoft.Web/serverFarms/read", + "Microsoft.Web/sites/config/read", + "Microsoft.Web/sites/config/web/appsettings/read", + "Microsoft.Web/sites/config/web/connectionstrings/read", + "Microsoft.Web/sites/config/appsettings/read", + "Microsoft.web/sites/config/snapshots/read", + "Microsoft.Web/sites/config/list/action", + "Microsoft.Web/sites/read", + "Microsoft.KeyVault/checkNameAvailability/read", + "Microsoft.KeyVault/deletedVaults/read", + "Microsoft.KeyVault/locations/*/read", + "Microsoft.KeyVault/vaults/*/read", + "Microsoft.KeyVault/operations/read", + "Microsoft.Compute/virtualMachines/runCommands/read", + "Microsoft.Compute/virtualMachines/runCommands/write", + "Microsoft.Compute/virtualMachines/runCommands/delete" + ] + not_actions = [] + data_actions = [ + "Microsoft.KeyVault/vaults/*/read", + "Microsoft.KeyVault/vaults/secrets/readMetadata/action" + ] + not_data_actions = [] + } + + assignable_scopes = data.azurerm_subscriptions.available.subscriptions[*].id +} + +# add custom role to all subscriptions +resource "azurerm_role_assignment" "mondoo_security" { + count = length(data.azurerm_subscriptions.available.subscriptions) + scope = data.azurerm_subscriptions.available.subscriptions[count.index].id + role_definition_id = azurerm_role_definition.mondoo_security_role.role_definition_resource_id + principal_id = azuread_service_principal.mondoo_security.object_id + depends_on = [ + azurerm_role_definition.mondoo_security_role, + ] +} + +# add reader role to all subscriptions +resource "azurerm_role_assignment" "reader" { + count = length(data.azurerm_subscriptions.available.subscriptions) + scope = data.azurerm_subscriptions.available.subscriptions[count.index].id + role_definition_name = "Reader" + principal_id = azuread_service_principal.mondoo_security.object_id +} + +# Configure the Mondoo +# ---------------------------------------------- + +provider "mondoo" { + space = "hungry-poet-123456" +} + +# Setup the MsDefender integration +resource "mondoo_integration_msdefender" "msdefender_integration" { + name = "Azure ${local.mondoo_security_integration_name}" + tenant_id = var.tenant_id + client_id = azuread_application.mondoo_security.client_id + + # subscription_allow_list= ["ffffffff-ffff-ffff-ffff-ffffffffffff", "ffffffff-ffff-ffff-ffff-ffffffffffff"] + # subscription_deny_list = ["ffffffff-ffff-ffff-ffff-ffffffffffff", "ffffffff-ffff-ffff-ffff-ffffffffffff"] + credentials = { + pem_file = join("\n", [tls_self_signed_cert.credential.cert_pem, tls_private_key.credential.private_key_pem]) + } + # wait for the permissions to provisioned + depends_on = [ + azuread_application.mondoo_security, + azuread_service_principal.mondoo_security, + azurerm_role_assignment.mondoo_security, + azurerm_role_assignment.reader, + ] +} diff --git a/internal/provider/integration_msdefender_resource.go b/internal/provider/integration_msdefender_resource.go new file mode 100644 index 0000000..365b1df --- /dev/null +++ b/internal/provider/integration_msdefender_resource.go @@ -0,0 +1,312 @@ +package provider + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "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/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + mondoov1 "go.mondoo.com/mondoo-go" +) + +var _ resource.Resource = (*integrationMsDefenderResource)(nil) + +func NewIntegrationMsDefenderResource() resource.Resource { + return &integrationMsDefenderResource{} +} + +type integrationMsDefenderResource struct { + client *ExtendedGqlClient +} + +type integrationMsDefenderResourceModel struct { + // scope + SpaceID types.String `tfsdk:"space_id"` + + // integration details + Mrn types.String `tfsdk:"mrn"` + Name types.String `tfsdk:"name"` + ClientId types.String `tfsdk:"client_id"` + TenantId types.String `tfsdk:"tenant_id"` + SubscriptionAllowList types.List `tfsdk:"subscription_allow_list"` + SubscriptionDenyList types.List `tfsdk:"subscription_deny_list"` + + // credentials + Credential integrationMsDefenderCredentialModel `tfsdk:"credentials"` +} + +type integrationMsDefenderCredentialModel struct { + PEMFile types.String `tfsdk:"pem_file"` +} + +func (r *integrationMsDefenderResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_integration_msdefender" +} + +func (r *integrationMsDefenderResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Microsoft Defender for Cloud integration.", + Attributes: map[string]schema.Attribute{ + "space_id": schema.StringAttribute{ + MarkdownDescription: "Mondoo Space Identifier. If it is not provided, the provider space is used.", + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "mrn": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "Integration identifier", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + MarkdownDescription: "Name of the integration.", + Required: true, + Validators: []validator.String{ + stringvalidator.LengthAtMost(250), + }, + }, + "client_id": schema.StringAttribute{ + MarkdownDescription: "Azure Client ID.", + Required: true, + }, + "tenant_id": schema.StringAttribute{ + MarkdownDescription: "Azure Tenant ID.", + Required: true, + }, + "subscription_allow_list": schema.ListAttribute{ + MarkdownDescription: "List of Azure subscriptions to scan.", + Optional: true, + ElementType: types.StringType, + Validators: []validator.List{ + // Validate only this attribute or other_attr is configured. + listvalidator.ConflictsWith(path.Expressions{ + path.MatchRoot("subscription_deny_list"), + }...), + }, + }, + "subscription_deny_list": schema.ListAttribute{ + MarkdownDescription: "List of Azure subscriptions to exclude from scanning.", + Optional: true, + ElementType: types.StringType, + Validators: []validator.List{ + // Validate only this attribute or other_attr is configured. + listvalidator.ConflictsWith(path.Expressions{ + path.MatchRoot("subscription_allow_list"), + }...), + }, + }, + "credentials": schema.SingleNestedAttribute{ + Required: true, + Attributes: map[string]schema.Attribute{ + "pem_file": schema.StringAttribute{ + MarkdownDescription: "PEM file for Azure integration.", + Required: true, + Sensitive: true, + }, + }, + }, + }, + } +} + +func (r *integrationMsDefenderResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*ExtendedGqlClient) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *http.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + r.client = client +} + +func (r *integrationMsDefenderResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data integrationMsDefenderResourceModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + space, err := r.client.ComputeSpace(data.SpaceID) + if err != nil { + resp.Diagnostics.AddError("Invalid Configuration", err.Error()) + return + } + ctx = tflog.SetField(ctx, "space_mrn", space.MRN()) + + // Do GraphQL request to API to create the resource. + var listAllow []mondoov1.String + allowlist, _ := data.SubscriptionAllowList.ToListValue(ctx) + allowlist.ElementsAs(ctx, &listAllow, true) + + var listDeny []mondoov1.String + denylist, _ := data.SubscriptionDenyList.ToListValue(ctx) + denylist.ElementsAs(ctx, &listDeny, true) + + // Check if both whitelist and blacklist are provided + if len(listDeny) > 0 && len(listAllow) > 0 { + resp.Diagnostics. + AddError("ConflictingAttributesError", + "Both subscription_allow_list and subscription_deny_list cannot be provided simultaneously.", + ) + return + } + + tflog.Debug(ctx, "Creating integration") + integration, err := r.client.CreateIntegration(ctx, + space.MRN(), + data.Name.ValueString(), + mondoov1.ClientIntegrationTypeMicrosoftDefender, + mondoov1.ClientIntegrationConfigurationInput{ + MicrosoftDefenderConfigurationOptions: &mondoov1.MicrosoftDefenderConfigurationOptionsInput{ + TenantID: mondoov1.String(data.TenantId.ValueString()), + ClientID: mondoov1.String(data.ClientId.ValueString()), + SubscriptionsAllowlist: &listAllow, + SubscriptionsDenylist: &listDeny, + Certificate: mondoov1.NewStringPtr(mondoov1.String(data.Credential.PEMFile.ValueString())), + }, + }) + if err != nil { + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to create MsDefender integration, got error: %s", err), + ) + return + } + + // trigger integration to gather results quickly after the first setup + // NOTE: we ignore the error since the integration state does not depend on it + _, err = r.client.TriggerAction(ctx, string(integration.Mrn), mondoov1.ActionTypeRunScan) + if err != nil { + resp.Diagnostics. + AddWarning("Client Error", + fmt.Sprintf("Unable to trigger integration, got error: %s", err), + ) + return + } + + // Save space mrn into the Terraform state. + data.Mrn = types.StringValue(string(integration.Mrn)) + data.Name = types.StringValue(string(integration.Name)) + data.SpaceID = types.StringValue(space.ID()) + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *integrationMsDefenderResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data integrationMsDefenderResourceModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Read API call logic + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *integrationMsDefenderResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data integrationMsDefenderResourceModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Do GraphQL request to API to update the resource. + var listAllow []mondoov1.String + allowlist, _ := data.SubscriptionAllowList.ToListValue(ctx) + allowlist.ElementsAs(ctx, &listAllow, true) + + var listDeny []mondoov1.String + denylist, _ := data.SubscriptionDenyList.ToListValue(ctx) + denylist.ElementsAs(ctx, &listDeny, true) + + // Check if both whitelist and blacklist are provided + if len(listDeny) > 0 && len(listAllow) > 0 { + resp.Diagnostics. + AddError("ConflictingAttributesError", + "Both subscription_allow_list and subscription_deny_list cannot be provided simultaneously.", + ) + return + } + + opts := mondoov1.ClientIntegrationConfigurationInput{ + MicrosoftDefenderConfigurationOptions: &mondoov1.MicrosoftDefenderConfigurationOptionsInput{ + TenantID: mondoov1.String(data.TenantId.ValueString()), + ClientID: mondoov1.String(data.ClientId.ValueString()), + SubscriptionsAllowlist: &listAllow, + SubscriptionsDenylist: &listDeny, + Certificate: mondoov1.NewStringPtr(mondoov1.String(data.Credential.PEMFile.ValueString())), + }, + } + + _, err := r.client.UpdateIntegration(ctx, + data.Mrn.ValueString(), + data.Name.ValueString(), + mondoov1.ClientIntegrationTypeMicrosoftDefender, + opts, + ) + if err != nil { + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to update MsDefender integration, got error: %s", err), + ) + return + } + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *integrationMsDefenderResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data integrationMsDefenderResourceModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Do GraphQL request to API to update the resource. + _, err := r.client.DeleteIntegration(ctx, data.Mrn.ValueString()) + if err != nil { + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to delete MsDefender integration, got error: %s", err), + ) + return + } +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index fe54ab6..d6a1071 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -204,6 +204,7 @@ func (p *MondooProvider) Resources(ctx context.Context) []func() resource.Resour NewIntegrationShodanResource, NewFrameworkAssignmentResource, NewCustomFrameworkResource, + NewIntegrationMsDefenderResource, } }