diff --git a/azurerm/internal/services/sentinel/registration.go b/azurerm/internal/services/sentinel/registration.go index 1f225cebe7b4..c7825580b462 100644 --- a/azurerm/internal/services/sentinel/registration.go +++ b/azurerm/internal/services/sentinel/registration.go @@ -32,6 +32,7 @@ func (r Registration) SupportedResources() map[string]*schema.Resource { "azurerm_sentinel_alert_rule_fusion": resourceSentinelAlertRuleFusion(), "azurerm_sentinel_alert_rule_ms_security_incident": resourceSentinelAlertRuleMsSecurityIncident(), "azurerm_sentinel_alert_rule_scheduled": resourceSentinelAlertRuleScheduled(), + "azurerm_sentinel_data_connector_aws_cloud_trail": resourceSentinelDataConnectorAwsCloudTrail(), "azurerm_sentinel_data_connector_azure_active_directory": resourceSentinelDataConnectorAzureActiveDirectory(), "azurerm_sentinel_data_connector_office_365": resourceSentinelDataConnectorOffice365(), "azurerm_sentinel_data_connector_threat_intelligence": resourceSentinelDataConnectorThreatIntelligence(), diff --git a/azurerm/internal/services/sentinel/sentinel_data_connector_aws_cloud_trail.go b/azurerm/internal/services/sentinel/sentinel_data_connector_aws_cloud_trail.go new file mode 100644 index 000000000000..1200a6c22ee8 --- /dev/null +++ b/azurerm/internal/services/sentinel/sentinel_data_connector_aws_cloud_trail.go @@ -0,0 +1,178 @@ +package sentinel + +import ( + "fmt" + "log" + "time" + + "github.com/Azure/azure-sdk-for-go/services/preview/securityinsight/mgmt/2019-01-01-preview/securityinsight" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + loganalyticsParse "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/loganalytics/parse" + loganalyticsValidate "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/loganalytics/validate" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/sentinel/parse" + azSchema "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceSentinelDataConnectorAwsCloudTrail() *schema.Resource { + return &schema.Resource{ + Create: resourceSentinelDataConnectorAwsCloudTrailCreateUpdate, + Read: resourceSentinelDataConnectorAwsCloudTrailRead, + Update: resourceSentinelDataConnectorAwsCloudTrailCreateUpdate, + Delete: resourceSentinelDataConnectorAwsCloudTrailDelete, + + Importer: azSchema.ValidateResourceIDPriorToImportThen(func(id string) error { + _, err := parse.DataConnectorID(id) + return err + }, importSentinelDataConnector(securityinsight.DataConnectorKindAmazonWebServicesCloudTrail)), + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Read: schema.DefaultTimeout(5 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "log_analytics_workspace_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: loganalyticsValidate.LogAnalyticsWorkspaceID, + }, + + "aws_role_arn": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + }, + } +} + +func resourceSentinelDataConnectorAwsCloudTrailCreateUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Sentinel.DataConnectorsClient + ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d) + defer cancel() + + workspaceId, err := loganalyticsParse.LogAnalyticsWorkspaceID(d.Get("log_analytics_workspace_id").(string)) + if err != nil { + return err + } + name := d.Get("name").(string) + id := parse.NewDataConnectorID(workspaceId.SubscriptionId, workspaceId.ResourceGroup, workspaceId.WorkspaceName, name) + + if d.IsNewResource() { + resp, err := client.Get(ctx, id.ResourceGroup, OperationalInsightsResourceProvider, id.WorkspaceName, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("checking for existing %s: %+v", id, err) + } + } + + if !utils.ResponseWasNotFound(resp.Response) { + return tf.ImportAsExistsError("azurerm_sentinel_data_connector_aws_cloud_trail", id.ID()) + } + } + + param := securityinsight.AwsCloudTrailDataConnector{ + Name: &name, + AwsCloudTrailDataConnectorProperties: &securityinsight.AwsCloudTrailDataConnectorProperties{ + AwsRoleArn: utils.String(d.Get("aws_role_arn").(string)), + DataTypes: &securityinsight.AwsCloudTrailDataConnectorDataTypes{ + Logs: &securityinsight.AwsCloudTrailDataConnectorDataTypesLogs{ + State: securityinsight.Enabled, + }, + }, + }, + Kind: securityinsight.KindAmazonWebServicesCloudTrail, + } + + // Service avoid concurrent updates of this resource via checking the "etag" to guarantee it is the same value as last Read. + // TODO: following code can be removed once the issue below is fixed: + // https://github.com/Azure/azure-rest-api-specs/issues/13203 + if !d.IsNewResource() { + resp, err := client.Get(ctx, id.ResourceGroup, OperationalInsightsResourceProvider, id.WorkspaceName, name) + if err != nil { + return fmt.Errorf("retrieving %s: %+v", id, err) + } + + dc, ok := resp.Value.(securityinsight.AwsCloudTrailDataConnector) + if !ok { + return fmt.Errorf("%s was not an AWS Cloud Trail Data Connector", id) + } + param.Etag = dc.Etag + } + + if _, err = client.CreateOrUpdate(ctx, id.ResourceGroup, OperationalInsightsResourceProvider, id.WorkspaceName, id.Name, param); err != nil { + return fmt.Errorf("creating %s: %+v", id, err) + } + + d.SetId(id.ID()) + + return resourceSentinelDataConnectorAwsCloudTrailRead(d, meta) +} + +func resourceSentinelDataConnectorAwsCloudTrailRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Sentinel.DataConnectorsClient + ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.DataConnectorID(d.Id()) + if err != nil { + return err + } + workspaceId := loganalyticsParse.NewLogAnalyticsWorkspaceID(id.SubscriptionId, id.ResourceGroup, id.WorkspaceName) + + resp, err := client.Get(ctx, id.ResourceGroup, OperationalInsightsResourceProvider, id.WorkspaceName, id.Name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + log.Printf("[DEBUG] %s was not found - removing from state!", id) + d.SetId("") + return nil + } + + return fmt.Errorf("retrieving %s: %+v", id, err) + } + + dc, ok := resp.Value.(securityinsight.AwsCloudTrailDataConnector) + if !ok { + return fmt.Errorf("%s was not an AWS Cloud Trail Data Connector", id) + } + + d.Set("name", id.Name) + d.Set("log_analytics_workspace_id", workspaceId.ID()) + if prop := dc.AwsCloudTrailDataConnectorProperties; prop != nil { + d.Set("aws_role_arn", prop.AwsRoleArn) + } + + return nil +} + +func resourceSentinelDataConnectorAwsCloudTrailDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Sentinel.DataConnectorsClient + ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.DataConnectorID(d.Id()) + if err != nil { + return err + } + + if _, err = client.Delete(ctx, id.ResourceGroup, OperationalInsightsResourceProvider, id.WorkspaceName, id.Name); err != nil { + return fmt.Errorf("deleting %s: %+v", id, err) + } + + return nil +} diff --git a/website/azurerm.erb b/website/azurerm.erb index d748fdfc5e44..00d50a55bdf0 100644 --- a/website/azurerm.erb +++ b/website/azurerm.erb @@ -2898,11 +2898,15 @@