From 2c33981b9f58a2971827e0957729129c7047db48 Mon Sep 17 00:00:00 2001 From: magodo Date: Tue, 8 Nov 2022 13:34:18 +0800 Subject: [PATCH 1/7] WIP: sentinel dc ti taxii --- internal/services/sentinel/' | 348 ++++++++++++++++++ internal/services/sentinel/registration.go | 1 + .../sentinel/sentinel_data_connector.go | 2 + ...ata_connector_threat_intelligence_taxii.go | 344 +++++++++++++++++ 4 files changed, 695 insertions(+) create mode 100644 internal/services/sentinel/' create mode 100644 internal/services/sentinel/sentinel_data_connector_threat_intelligence_taxii.go diff --git a/internal/services/sentinel/' b/internal/services/sentinel/' new file mode 100644 index 000000000000..080aa280ea78 --- /dev/null +++ b/internal/services/sentinel/' @@ -0,0 +1,348 @@ +package sentinel + +import ( + "context" + "fmt" + "time" + + "github.com/hashicorp/go-azure-sdk/resource-manager/operationalinsights/2020-08-01/workspaces" + "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/sentinel/parse" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/sentinel/validate" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" + "github.com/hashicorp/terraform-provider-azurerm/utils" + securityinsight "github.com/tombuildsstuff/kermit/sdk/securityinsights/2022-10-01-preview/securityinsights" +) + +type DataConnectorThreatIntelligenceTAXIIResource struct{} + +var _ sdk.ResourceWithUpdate = DataConnectorThreatIntelligenceTAXIIResource{} + +type DataConnectorThreatIntelligenceTAXIIModel struct { + Name string `tfschema:"name"` + LogAnalyticsWorkspaceId string `tfschema:"log_analytics_workspace_id"` + FriendlyName string `tfschema:"friendly_name"` + APIRootURL string `tfschema:"api_root_url"` + CollectionID string `tfschema:"collection_id"` + UserName string `tfschema:"user_name"` + Password string `tfschema:"password"` + PollingFrequency string `tfschema:"polling_frequency"` + TenantId string `tfschema:"tenant_id"` +} + +func (r DataConnectorThreatIntelligenceTAXIIResource) Arguments() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + "log_analytics_workspace_id": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: workspaces.ValidateWorkspaceID, + }, + "friendly_name": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + "api_root_url": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + "collection_id": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + "user_name": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + "password": { + Type: pluginsdk.TypeString, + Optional: true, + Sensitive: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + "polling_frequency": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + string(securityinsight.PollingFrequencyOnceADay), + string(securityinsight.PollingFrequencyOnceAnHour), + string(securityinsight.PollingFrequencyOnceAMinute), + }, false), + }, + "tenant_id": { + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ValidateFunc: validation.IsUUID, + }, + } +} + +func (r DataConnectorThreatIntelligenceTAXIIResource) Attributes() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{} +} + +func (r DataConnectorThreatIntelligenceTAXIIResource) ResourceType() string { + return "azurerm_sentinel_data_connector_threat_intelligence_taxii" +} + +func (r DataConnectorThreatIntelligenceTAXIIResource) ModelObject() interface{} { + return &DataConnectorThreatIntelligenceTAXIIModel{} +} + +func (r DataConnectorThreatIntelligenceTAXIIResource) IDValidationFunc() pluginsdk.SchemaValidateFunc { + return validate.DataConnectorID +} + +func (r DataConnectorThreatIntelligenceTAXIIResource) CustomImporter() sdk.ResourceRunFunc { + return func(ctx context.Context, metadata sdk.ResourceMetaData) error { + _, err := importSentinelDataConnector(securityinsight.DataConnectorKindThreatIntelligenceTaxii)(ctx, metadata.ResourceData, metadata.Client) + return err + } +} + +func (r DataConnectorThreatIntelligenceTAXIIResource) Create() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 30 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.Sentinel.DataConnectorsClient + wspClient := metadata.Client.LogAnalytics.WorkspacesClient + + var plan DataConnectorThreatIntelligenceTAXIIModel + if err := metadata.Decode(&plan); err != nil { + return fmt.Errorf("decoding %+v", err) + } + + workspaceId, err := workspaces.ParseWorkspaceID(plan.LogAnalyticsWorkspaceId) + if err != nil { + return err + } + + wsp, err := wspClient.Get(ctx, *workspaceId) + if err != nil { + return fmt.Errorf("retrieving the workspace %s: %+v", workspaceId, err) + } + if wsp.Model == nil { + return fmt.Errorf("nil model of the workspace %s", workspaceId) + } + if wsp.Model.Properties == nil { + return fmt.Errorf("nil properties of the workspace %s", workspaceId) + } + if wsp.Model.Properties.CustomerId == nil { + return fmt.Errorf("nil workspace id of the workspace %s", workspaceId) + } + wspId := *wsp.Model.Properties.CustomerId + + id := parse.NewDataConnectorID(workspaceId.SubscriptionId, workspaceId.ResourceGroupName, workspaceId.WorkspaceName, plan.Name) + existing, err := client.Get(ctx, id.ResourceGroup, id.WorkspaceName, id.Name) + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("checking for presence of existing %s: %+v", id, err) + } + } + if !utils.ResponseWasNotFound(existing.Response) { + return metadata.ResourceRequiresImport(r.ResourceType(), id) + } + + tenantId := plan.TenantId + if tenantId == "" { + tenantId = metadata.Client.Account.TenantId + } + + params := securityinsight.TiTaxiiDataConnector{ + Name: &plan.Name, + TiTaxiiDataConnectorProperties: &securityinsight.TiTaxiiDataConnectorProperties{ + WorkspaceID: &wspId, + FriendlyName: &plan.FriendlyName, + TaxiiServer: &plan.APIRootURL, + CollectionID: &plan.CollectionID, + // TaxiiLookbackPeriod: &date.Time{ + // Time: time.Time{}, + // }, + PollingFrequency: securityinsight.PollingFrequency(plan.PollingFrequency), + DataTypes: &securityinsight.TiTaxiiDataConnectorDataTypes{ + TaxiiClient: &securityinsight.TiTaxiiDataConnectorDataTypesTaxiiClient{ + State: securityinsight.DataTypeStateEnabled, + }, + }, + TenantID: &tenantId, + }, + Kind: securityinsight.KindBasicDataConnectorKindThreatIntelligenceTaxii, + } + + if plan.UserName != "" { + params.TiTaxiiDataConnectorProperties.UserName = &plan.UserName + } + + if plan.Password != "" { + params.TiTaxiiDataConnectorProperties.Password = &plan.Password + } + + if _, err = client.CreateOrUpdate(ctx, id.ResourceGroup, id.WorkspaceName, id.Name, params); err != nil { + return fmt.Errorf("creating %s: %+v", id, err) + } + + metadata.SetID(id) + return nil + }, + } +} + +func (r DataConnectorThreatIntelligenceTAXIIResource) Read() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 5 * time.Minute, + + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.Sentinel.DataConnectorsClient + + var state DataConnectorThreatIntelligenceTAXIIModel + if err := metadata.Decode(&state); err != nil { + return err + } + + id, err := parse.DataConnectorID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + workspaceId := workspaces.NewWorkspaceID(id.SubscriptionId, id.ResourceGroup, id.WorkspaceName) + + existing, err := client.Get(ctx, id.ResourceGroup, id.WorkspaceName, id.Name) + if err != nil { + if utils.ResponseWasNotFound(existing.Response) { + return metadata.MarkAsGone(id) + } + return fmt.Errorf("retrieving %s: %+v", id, err) + } + + dc, ok := existing.Value.(securityinsight.TiTaxiiDataConnector) + if !ok { + return fmt.Errorf("%s was not an Threat Intelligence TAXII Data Connector", id) + } + + model := DataConnectorThreatIntelligenceTAXIIModel{ + Name: id.Name, + LogAnalyticsWorkspaceId: workspaceId.ID(), + Password: state.Password, // setting the password from state, as it is not returned from API + } + + if props := dc.TiTaxiiDataConnectorProperties; props != nil { + if props.FriendlyName != nil { + model.FriendlyName = *props.FriendlyName + } + + if props.TaxiiServer != nil { + model.APIRootURL = *props.TaxiiServer + } + + if props.CollectionID != nil { + model.CollectionID = *props.CollectionID + } + + if props.UserName != nil { + model.UserName = *props.UserName + } + + model.PollingFrequency = string(props.PollingFrequency) + + if props.TenantID != nil { + model.TenantId = *props.TenantID + } + } + + return metadata.Encode(&model) + }, + } +} + +func (r DataConnectorThreatIntelligenceTAXIIResource) Update() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 30 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.Sentinel.DataConnectorsClient + + id, err := parse.DataConnectorID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + workspaceId := workspaces.NewWorkspaceID(id.SubscriptionId, id.ResourceGroup, id.WorkspaceName) + + var plan DataConnectorThreatIntelligenceTAXIIModel + if err := metadata.Decode(&plan); err != nil { + return err + } + + existing, err := client.Get(ctx, id.ResourceGroup, id.WorkspaceName, id.Name) + if err != nil { + return fmt.Errorf("retrieving %s: %+v", id, err) + } + + dc, ok := existing.Value.(securityinsight.TiTaxiiDataConnector) + if !ok { + return fmt.Errorf("%s was not an Threat Intelligence TAXII Data Connector", id) + } + + if props := dc.TiTaxiiDataConnectorProperties; props != nil { + if metadata.ResourceData.HasChange("friendly_name") { + props.FriendlyName = &plan.FriendlyName + } + if metadata.ResourceData.HasChange("api_root_url") { + props.TaxiiServer = &plan.APIRootURL + } + if metadata.ResourceData.HasChange("collection_id") { + props.CollectionID = &plan.CollectionID + } + if metadata.ResourceData.HasChange("user_name") { + props.UserName = &plan.UserName + } + if metadata.ResourceData.HasChange("password") { + props.Password = &plan.Password + } + } + + if _, err := client.CreateOrUpdate(ctx, id.ResourceGroup, id.Name, params); err != nil { + return fmt.Errorf("updating %s: %+v", id, err) + } + + return nil + }, + } +} + +func (r DataConnectorThreatIntelligenceTAXIIResource) Delete() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 30 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.Sentinel.DataConnectorsClient + + id, err := parse.DataConnectorThreatIntelligenceTAXIIID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + future, err := client.Delete(ctx, id.ResourceGroup, id.Name) + if err != nil { + return fmt.Errorf("deleting %s: %+v", id, err) + } + + if err := future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("waiting for removal of %s: %+v", id, err) + } + + return nil + }, + } +} diff --git a/internal/services/sentinel/registration.go b/internal/services/sentinel/registration.go index c57a0e159798..e80fa2c20bb4 100644 --- a/internal/services/sentinel/registration.go +++ b/internal/services/sentinel/registration.go @@ -68,5 +68,6 @@ func (r Registration) Resources() []sdk.Resource { DataConnectorOffice365ProjectResource{}, DataConnectorOfficePowerBIResource{}, DataConnectorOfficeIRMResource{}, + DataConnectorThreatIntelligenceTAXIIResource{}, } } diff --git a/internal/services/sentinel/sentinel_data_connector.go b/internal/services/sentinel/sentinel_data_connector.go index 63b1bdd86ebd..04cea7bc036c 100644 --- a/internal/services/sentinel/sentinel_data_connector.go +++ b/internal/services/sentinel/sentinel_data_connector.go @@ -63,6 +63,8 @@ func assertDataConnectorKind(dc securityinsight.BasicDataConnector, expectKind s kind = securityinsight.DataConnectorKindMicrosoftDefenderAdvancedThreatProtection case securityinsight.AwsS3DataConnector: kind = securityinsight.DataConnectorKindAmazonWebServicesS3 + case securityinsight.TiTaxiiDataConnector: + kind = securityinsight.DataConnectorKindThreatIntelligenceTaxii } if expectKind != kind { return fmt.Errorf("Sentinel Data Connector has mismatched kind, expected: %q, got %q", expectKind, kind) diff --git a/internal/services/sentinel/sentinel_data_connector_threat_intelligence_taxii.go b/internal/services/sentinel/sentinel_data_connector_threat_intelligence_taxii.go new file mode 100644 index 000000000000..322e79782003 --- /dev/null +++ b/internal/services/sentinel/sentinel_data_connector_threat_intelligence_taxii.go @@ -0,0 +1,344 @@ +package sentinel + +import ( + "context" + "fmt" + "time" + + "github.com/hashicorp/go-azure-sdk/resource-manager/operationalinsights/2020-08-01/workspaces" + "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/sentinel/parse" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/sentinel/validate" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" + "github.com/hashicorp/terraform-provider-azurerm/utils" + securityinsight "github.com/tombuildsstuff/kermit/sdk/securityinsights/2022-10-01-preview/securityinsights" +) + +type DataConnectorThreatIntelligenceTAXIIResource struct{} + +var _ sdk.ResourceWithUpdate = DataConnectorThreatIntelligenceTAXIIResource{} + +type DataConnectorThreatIntelligenceTAXIIModel struct { + Name string `tfschema:"name"` + LogAnalyticsWorkspaceId string `tfschema:"log_analytics_workspace_id"` + DisplayName string `tfschema:"display_name"` + APIRootURL string `tfschema:"api_root_url"` + CollectionID string `tfschema:"collection_id"` + UserName string `tfschema:"user_name"` + Password string `tfschema:"password"` + PollingFrequency string `tfschema:"polling_frequency"` + TenantId string `tfschema:"tenant_id"` +} + +func (r DataConnectorThreatIntelligenceTAXIIResource) Arguments() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + "log_analytics_workspace_id": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: workspaces.ValidateWorkspaceID, + }, + "display_name": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + "api_root_url": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + "collection_id": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + "user_name": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + "password": { + Type: pluginsdk.TypeString, + Optional: true, + Sensitive: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + "polling_frequency": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + string(securityinsight.PollingFrequencyOnceADay), + string(securityinsight.PollingFrequencyOnceAnHour), + string(securityinsight.PollingFrequencyOnceAMinute), + }, false), + }, + "tenant_id": { + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ValidateFunc: validation.IsUUID, + }, + } +} + +func (r DataConnectorThreatIntelligenceTAXIIResource) Attributes() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{} +} + +func (r DataConnectorThreatIntelligenceTAXIIResource) ResourceType() string { + return "azurerm_sentinel_data_connector_threat_intelligence_taxii" +} + +func (r DataConnectorThreatIntelligenceTAXIIResource) ModelObject() interface{} { + return &DataConnectorThreatIntelligenceTAXIIModel{} +} + +func (r DataConnectorThreatIntelligenceTAXIIResource) IDValidationFunc() pluginsdk.SchemaValidateFunc { + return validate.DataConnectorID +} + +func (r DataConnectorThreatIntelligenceTAXIIResource) CustomImporter() sdk.ResourceRunFunc { + return func(ctx context.Context, metadata sdk.ResourceMetaData) error { + _, err := importSentinelDataConnector(securityinsight.DataConnectorKindThreatIntelligenceTaxii)(ctx, metadata.ResourceData, metadata.Client) + return err + } +} + +func (r DataConnectorThreatIntelligenceTAXIIResource) Create() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 30 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.Sentinel.DataConnectorsClient + wspClient := metadata.Client.LogAnalytics.WorkspacesClient + + var plan DataConnectorThreatIntelligenceTAXIIModel + if err := metadata.Decode(&plan); err != nil { + return fmt.Errorf("decoding %+v", err) + } + + workspaceId, err := workspaces.ParseWorkspaceID(plan.LogAnalyticsWorkspaceId) + if err != nil { + return err + } + + wsp, err := wspClient.Get(ctx, *workspaceId) + if err != nil { + return fmt.Errorf("retrieving the workspace %s: %+v", workspaceId, err) + } + if wsp.Model == nil { + return fmt.Errorf("nil model of the workspace %s", workspaceId) + } + if wsp.Model.Properties == nil { + return fmt.Errorf("nil properties of the workspace %s", workspaceId) + } + if wsp.Model.Properties.CustomerId == nil { + return fmt.Errorf("nil workspace id of the workspace %s", workspaceId) + } + wspId := *wsp.Model.Properties.CustomerId + + id := parse.NewDataConnectorID(workspaceId.SubscriptionId, workspaceId.ResourceGroupName, workspaceId.WorkspaceName, plan.Name) + existing, err := client.Get(ctx, id.ResourceGroup, id.WorkspaceName, id.Name) + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("checking for presence of existing %s: %+v", id, err) + } + } + if !utils.ResponseWasNotFound(existing.Response) { + return metadata.ResourceRequiresImport(r.ResourceType(), id) + } + + tenantId := plan.TenantId + if tenantId == "" { + tenantId = metadata.Client.Account.TenantId + } + + params := securityinsight.TiTaxiiDataConnector{ + Name: &plan.Name, + TiTaxiiDataConnectorProperties: &securityinsight.TiTaxiiDataConnectorProperties{ + WorkspaceID: &wspId, + FriendlyName: &plan.DisplayName, + TaxiiServer: &plan.APIRootURL, + CollectionID: &plan.CollectionID, + // TaxiiLookbackPeriod: &date.Time{ + // Time: time.Time{}, + // }, + PollingFrequency: securityinsight.PollingFrequency(plan.PollingFrequency), + DataTypes: &securityinsight.TiTaxiiDataConnectorDataTypes{ + TaxiiClient: &securityinsight.TiTaxiiDataConnectorDataTypesTaxiiClient{ + State: securityinsight.DataTypeStateEnabled, + }, + }, + TenantID: &tenantId, + }, + Kind: securityinsight.KindBasicDataConnectorKindThreatIntelligenceTaxii, + } + + if plan.UserName != "" { + params.TiTaxiiDataConnectorProperties.UserName = &plan.UserName + } + + if plan.Password != "" { + params.TiTaxiiDataConnectorProperties.Password = &plan.Password + } + + if _, err = client.CreateOrUpdate(ctx, id.ResourceGroup, id.WorkspaceName, id.Name, params); err != nil { + return fmt.Errorf("creating %s: %+v", id, err) + } + + metadata.SetID(id) + return nil + }, + } +} + +func (r DataConnectorThreatIntelligenceTAXIIResource) Read() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 5 * time.Minute, + + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.Sentinel.DataConnectorsClient + + var state DataConnectorThreatIntelligenceTAXIIModel + if err := metadata.Decode(&state); err != nil { + return err + } + + id, err := parse.DataConnectorID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + workspaceId := workspaces.NewWorkspaceID(id.SubscriptionId, id.ResourceGroup, id.WorkspaceName) + + existing, err := client.Get(ctx, id.ResourceGroup, id.WorkspaceName, id.Name) + if err != nil { + if utils.ResponseWasNotFound(existing.Response) { + return metadata.MarkAsGone(id) + } + return fmt.Errorf("retrieving %s: %+v", id, err) + } + + dc, ok := existing.Value.(securityinsight.TiTaxiiDataConnector) + if !ok { + return fmt.Errorf("%s was not an Threat Intelligence TAXII Data Connector", id) + } + + model := DataConnectorThreatIntelligenceTAXIIModel{ + Name: id.Name, + LogAnalyticsWorkspaceId: workspaceId.ID(), + Password: state.Password, // setting the password from state, as it is not returned from API + } + + if props := dc.TiTaxiiDataConnectorProperties; props != nil { + if props.FriendlyName != nil { + model.DisplayName = *props.FriendlyName + } + + if props.TaxiiServer != nil { + model.APIRootURL = *props.TaxiiServer + } + + if props.CollectionID != nil { + model.CollectionID = *props.CollectionID + } + + if props.UserName != nil { + model.UserName = *props.UserName + } + + model.PollingFrequency = string(props.PollingFrequency) + + if props.TenantID != nil { + model.TenantId = *props.TenantID + } + } + + return metadata.Encode(&model) + }, + } +} + +func (r DataConnectorThreatIntelligenceTAXIIResource) Update() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 30 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.Sentinel.DataConnectorsClient + + id, err := parse.DataConnectorID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + var plan DataConnectorThreatIntelligenceTAXIIModel + if err := metadata.Decode(&plan); err != nil { + return err + } + + existing, err := client.Get(ctx, id.ResourceGroup, id.WorkspaceName, id.Name) + if err != nil { + return fmt.Errorf("retrieving %s: %+v", id, err) + } + + dc, ok := existing.Value.(securityinsight.TiTaxiiDataConnector) + if !ok { + return fmt.Errorf("%s was not an Threat Intelligence TAXII Data Connector", id) + } + + if props := dc.TiTaxiiDataConnectorProperties; props != nil { + if metadata.ResourceData.HasChange("friendly_name") { + props.FriendlyName = &plan.DisplayName + } + if metadata.ResourceData.HasChange("api_root_url") { + props.TaxiiServer = &plan.APIRootURL + } + if metadata.ResourceData.HasChange("collection_id") { + props.CollectionID = &plan.CollectionID + } + if metadata.ResourceData.HasChange("user_name") { + props.UserName = &plan.UserName + } + if metadata.ResourceData.HasChange("password") { + props.Password = &plan.Password + } + if metadata.ResourceData.HasChange("polling_frequency") { + props.PollingFrequency = securityinsight.PollingFrequency(plan.PollingFrequency) + } + } + + if _, err := client.CreateOrUpdate(ctx, id.ResourceGroup, id.WorkspaceName, id.Name, dc); err != nil { + return fmt.Errorf("updating %s: %+v", id, err) + } + + return nil + }, + } +} + +func (r DataConnectorThreatIntelligenceTAXIIResource) Delete() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 30 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.Sentinel.DataConnectorsClient + + id, err := parse.DataConnectorID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + if _, err := client.Delete(ctx, id.ResourceGroup, id.WorkspaceName, id.Name); err != nil { + return fmt.Errorf("deleting %s: %+v", id, err) + } + + return nil + }, + } +} From 813f71fb0f9a8f98ceea1d3aac6ffc7ad779f8c6 Mon Sep 17 00:00:00 2001 From: magodo Date: Tue, 8 Nov 2022 17:53:00 +0800 Subject: [PATCH 2/7] hacking... --- .../sentinel/azuresdkhacks/dataconnectors.go | 90 +++++ .../services/sentinel/azuresdkhacks/models.go | 354 ++++++++++++++++++ ...ata_connector_threat_intelligence_taxii.go | 45 ++- 3 files changed, 476 insertions(+), 13 deletions(-) create mode 100644 internal/services/sentinel/azuresdkhacks/dataconnectors.go create mode 100644 internal/services/sentinel/azuresdkhacks/models.go diff --git a/internal/services/sentinel/azuresdkhacks/dataconnectors.go b/internal/services/sentinel/azuresdkhacks/dataconnectors.go new file mode 100644 index 000000000000..38a5226a8f60 --- /dev/null +++ b/internal/services/sentinel/azuresdkhacks/dataconnectors.go @@ -0,0 +1,90 @@ +package azuresdkhacks + +import ( + "context" + "net/http" + + "github.com/Azure/go-autorest/autorest" + "github.com/Azure/go-autorest/autorest/azure" + "github.com/Azure/go-autorest/autorest/validation" + securityinsight "github.com/tombuildsstuff/kermit/sdk/securityinsights/2022-10-01-preview/securityinsights" +) + +type DataConnectorsClient struct { + securityinsight.BaseClient +} + +func (client DataConnectorsClient) Get(ctx context.Context, resourceGroupName string, workspaceName string, dataConnectorID string) (result DataConnectorModel, err error) { + if err := validation.Validate([]validation.Validation{ + {TargetValue: client.SubscriptionID, + Constraints: []validation.Constraint{{Target: "client.SubscriptionID", Name: validation.MinLength, Rule: 1, Chain: nil}}}, + {TargetValue: resourceGroupName, + Constraints: []validation.Constraint{{Target: "resourceGroupName", Name: validation.MaxLength, Rule: 90, Chain: nil}, + {Target: "resourceGroupName", Name: validation.MinLength, Rule: 1, Chain: nil}}}, + {TargetValue: workspaceName, + Constraints: []validation.Constraint{{Target: "workspaceName", Name: validation.MaxLength, Rule: 90, Chain: nil}, + {Target: "workspaceName", Name: validation.MinLength, Rule: 1, Chain: nil}}}}); err != nil { + return result, validation.NewError("securityinsight.DataConnectorsClient", "Get", err.Error()) + } + + req, err := client.GetPreparer(ctx, resourceGroupName, workspaceName, dataConnectorID) + if err != nil { + err = autorest.NewErrorWithError(err, "securityinsight.DataConnectorsClient", "Get", nil, "Failure preparing request") + return + } + + resp, err := client.GetSender(req) + if err != nil { + result.Response = autorest.Response{Response: resp} + err = autorest.NewErrorWithError(err, "securityinsight.DataConnectorsClient", "Get", resp, "Failure sending request") + return + } + + result, err = client.GetResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "securityinsight.DataConnectorsClient", "Get", resp, "Failure responding to request") + return + } + + return +} + +// GetPreparer prepares the Get request. +func (client DataConnectorsClient) GetPreparer(ctx context.Context, resourceGroupName string, workspaceName string, dataConnectorID string) (*http.Request, error) { + pathParameters := map[string]interface{}{ + "dataConnectorId": autorest.Encode("path", dataConnectorID), + "resourceGroupName": autorest.Encode("path", resourceGroupName), + "subscriptionId": autorest.Encode("path", client.SubscriptionID), + "workspaceName": autorest.Encode("path", workspaceName), + } + + const APIVersion = "2022-10-01-preview" + queryParameters := map[string]interface{}{ + "api-version": APIVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsGet(), + autorest.WithBaseURL(client.BaseURI), + autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.OperationalInsights/workspaces/{workspaceName}/providers/Microsoft.SecurityInsights/dataConnectors/{dataConnectorId}", pathParameters), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare((&http.Request{}).WithContext(ctx)) +} + +// GetSender sends the Get request. The method will close the +// http.Response Body if it receives an error. +func (client DataConnectorsClient) GetSender(req *http.Request) (*http.Response, error) { + return client.Send(req, azure.DoRetryWithRegistration(client.Client)) +} + +// GetResponder handles the response to the Get request. The method always +// closes the http.Response Body. +func (client DataConnectorsClient) GetResponder(resp *http.Response) (result DataConnectorModel, err error) { + err = autorest.Respond( + resp, + azure.WithErrorUnlessStatusCode(http.StatusOK), + autorest.ByUnmarshallingJSON(&result), + autorest.ByClosing()) + result.Response = autorest.Response{Response: resp} + return +} diff --git a/internal/services/sentinel/azuresdkhacks/models.go b/internal/services/sentinel/azuresdkhacks/models.go new file mode 100644 index 000000000000..7e429d2e3803 --- /dev/null +++ b/internal/services/sentinel/azuresdkhacks/models.go @@ -0,0 +1,354 @@ +package azuresdkhacks + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/Azure/go-autorest/autorest" + "github.com/Azure/go-autorest/autorest/date" + securityinsight "github.com/tombuildsstuff/kermit/sdk/securityinsights/2022-10-01-preview/securityinsights" +) + +type DataConnectorModel struct { + autorest.Response `json:"-"` + Value securityinsight.BasicDataConnector `json:"value,omitempty"` +} + +func (dcm *DataConnectorModel) UnmarshalJSON(body []byte) error { + dc, err := unmarshalBasicDataConnector(body) + if err != nil { + return err + } + dcm.Value = dc + + return nil +} + +func unmarshalBasicDataConnector(body []byte) (securityinsight.BasicDataConnector, error) { + var m map[string]interface{} + err := json.Unmarshal(body, &m) + if err != nil { + return nil, err + } + + switch m["kind"] { + case string(securityinsight.KindBasicDataConnectorKindAzureActiveDirectory): + var adc securityinsight.AADDataConnector + err := json.Unmarshal(body, &adc) + return adc, err + case string(securityinsight.KindBasicDataConnectorKindMicrosoftThreatIntelligence): + var mdc securityinsight.MSTIDataConnector + err := json.Unmarshal(body, &mdc) + return mdc, err + case string(securityinsight.KindBasicDataConnectorKindMicrosoftThreatProtection): + var mdc securityinsight.MTPDataConnector + err := json.Unmarshal(body, &mdc) + return mdc, err + case string(securityinsight.KindBasicDataConnectorKindAzureAdvancedThreatProtection): + var adc securityinsight.AATPDataConnector + err := json.Unmarshal(body, &adc) + return adc, err + case string(securityinsight.KindBasicDataConnectorKindAzureSecurityCenter): + var adc securityinsight.ASCDataConnector + err := json.Unmarshal(body, &adc) + return adc, err + case string(securityinsight.KindBasicDataConnectorKindAmazonWebServicesCloudTrail): + var actdc securityinsight.AwsCloudTrailDataConnector + err := json.Unmarshal(body, &actdc) + return actdc, err + case string(securityinsight.KindBasicDataConnectorKindAmazonWebServicesS3): + var asdc securityinsight.AwsS3DataConnector + err := json.Unmarshal(body, &asdc) + return asdc, err + case string(securityinsight.KindBasicDataConnectorKindMicrosoftCloudAppSecurity): + var mdc securityinsight.MCASDataConnector + err := json.Unmarshal(body, &mdc) + return mdc, err + case string(securityinsight.KindBasicDataConnectorKindDynamics365): + var d3dc securityinsight.Dynamics365DataConnector + err := json.Unmarshal(body, &d3dc) + return d3dc, err + case string(securityinsight.KindBasicDataConnectorKindOfficeATP): + var oadc securityinsight.OfficeATPDataConnector + err := json.Unmarshal(body, &oadc) + return oadc, err + case string(securityinsight.KindBasicDataConnectorKindOffice365Project): + var o3pdc securityinsight.Office365ProjectDataConnector + err := json.Unmarshal(body, &o3pdc) + return o3pdc, err + case string(securityinsight.KindBasicDataConnectorKindOfficePowerBI): + var opbdc securityinsight.OfficePowerBIDataConnector + err := json.Unmarshal(body, &opbdc) + return opbdc, err + case string(securityinsight.KindBasicDataConnectorKindOfficeIRM): + var oidc securityinsight.OfficeIRMDataConnector + err := json.Unmarshal(body, &oidc) + return oidc, err + case string(securityinsight.KindBasicDataConnectorKindMicrosoftDefenderAdvancedThreatProtection): + var mdc securityinsight.MDATPDataConnector + err := json.Unmarshal(body, &mdc) + return mdc, err + case string(securityinsight.KindBasicDataConnectorKindOffice365): + var odc securityinsight.OfficeDataConnector + err := json.Unmarshal(body, &odc) + return odc, err + case string(securityinsight.KindBasicDataConnectorKindThreatIntelligence): + var tdc securityinsight.TIDataConnector + err := json.Unmarshal(body, &tdc) + return tdc, err + case string(securityinsight.KindBasicDataConnectorKindThreatIntelligenceTaxii): + var ttdc TiTaxiiDataConnector // using the hacked one + err := json.Unmarshal(body, &ttdc) + return ttdc, err + case string(securityinsight.KindBasicDataConnectorKindIOT): + var itdc securityinsight.IoTDataConnector + err := json.Unmarshal(body, &itdc) + return itdc, err + case string(securityinsight.KindBasicDataConnectorKindGenericUI): + var cudc securityinsight.CodelessUIDataConnector + err := json.Unmarshal(body, &cudc) + return cudc, err + case string(securityinsight.KindBasicDataConnectorKindAPIPolling): + var capdc securityinsight.CodelessAPIPollingDataConnector + err := json.Unmarshal(body, &capdc) + return capdc, err + default: + var dc securityinsight.DataConnector + err := json.Unmarshal(body, &dc) + return dc, err + } +} + +var _ securityinsight.BasicDataConnector = TiTaxiiDataConnector{} + +type TiTaxiiDataConnector struct { + *TiTaxiiDataConnectorProperties `json:"properties,omitempty"` + Kind securityinsight.KindBasicDataConnector `json:"kind,omitempty"` + Etag *string `json:"etag,omitempty"` + ID *string `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + Type *string `json:"type,omitempty"` + SystemData *securityinsight.SystemData `json:"systemData,omitempty"` +} + +func (t TiTaxiiDataConnector) AsAADDataConnector() (*securityinsight.AADDataConnector, bool) { + return nil, false +} + +func (t TiTaxiiDataConnector) AsMSTIDataConnector() (*securityinsight.MSTIDataConnector, bool) { + return nil, false +} + +func (t TiTaxiiDataConnector) AsMTPDataConnector() (*securityinsight.MTPDataConnector, bool) { + return nil, false +} + +func (t TiTaxiiDataConnector) AsAATPDataConnector() (*securityinsight.AATPDataConnector, bool) { + return nil, false +} + +func (t TiTaxiiDataConnector) AsASCDataConnector() (*securityinsight.ASCDataConnector, bool) { + return nil, false +} + +func (t TiTaxiiDataConnector) AsAwsCloudTrailDataConnector() (*securityinsight.AwsCloudTrailDataConnector, bool) { + return nil, false +} + +func (t TiTaxiiDataConnector) AsAwsS3DataConnector() (*securityinsight.AwsS3DataConnector, bool) { + return nil, false +} + +func (t TiTaxiiDataConnector) AsMCASDataConnector() (*securityinsight.MCASDataConnector, bool) { + return nil, false +} + +func (t TiTaxiiDataConnector) AsDynamics365DataConnector() (*securityinsight.Dynamics365DataConnector, bool) { + return nil, false +} + +func (t TiTaxiiDataConnector) AsOfficeATPDataConnector() (*securityinsight.OfficeATPDataConnector, bool) { + return nil, false +} + +func (t TiTaxiiDataConnector) AsOffice365ProjectDataConnector() (*securityinsight.Office365ProjectDataConnector, bool) { + return nil, false +} + +func (t TiTaxiiDataConnector) AsOfficePowerBIDataConnector() (*securityinsight.OfficePowerBIDataConnector, bool) { + return nil, false +} + +func (t TiTaxiiDataConnector) AsOfficeIRMDataConnector() (*securityinsight.OfficeIRMDataConnector, bool) { + return nil, false +} + +func (t TiTaxiiDataConnector) AsMDATPDataConnector() (*securityinsight.MDATPDataConnector, bool) { + return nil, false +} + +func (t TiTaxiiDataConnector) AsOfficeDataConnector() (*securityinsight.OfficeDataConnector, bool) { + return nil, false +} + +func (t TiTaxiiDataConnector) AsTIDataConnector() (*securityinsight.TIDataConnector, bool) { + return nil, false +} + +func (t TiTaxiiDataConnector) AsTiTaxiiDataConnector() (*securityinsight.TiTaxiiDataConnector, bool) { + // This method is not used at all, only for implementing the interface. + return nil, false +} + +func (t TiTaxiiDataConnector) AsIoTDataConnector() (*securityinsight.IoTDataConnector, bool) { + return nil, false +} + +func (t TiTaxiiDataConnector) AsCodelessUIDataConnector() (*securityinsight.CodelessUIDataConnector, bool) { + return nil, false +} + +func (t TiTaxiiDataConnector) AsCodelessAPIPollingDataConnector() (*securityinsight.CodelessAPIPollingDataConnector, bool) { + return nil, false +} + +func (t TiTaxiiDataConnector) AsDataConnector() (*securityinsight.DataConnector, bool) { + return nil, false +} + +func (ttdc TiTaxiiDataConnector) MarshalJSON() ([]byte, error) { + ttdc.Kind = securityinsight.KindBasicDataConnectorKindThreatIntelligenceTaxii + objectMap := make(map[string]interface{}) + if ttdc.TiTaxiiDataConnectorProperties != nil { + objectMap["properties"] = ttdc.TiTaxiiDataConnectorProperties + } + if ttdc.Kind != "" { + objectMap["kind"] = ttdc.Kind + } + if ttdc.Etag != nil { + objectMap["etag"] = ttdc.Etag + } + return json.Marshal(objectMap) +} + +func (ttdc *TiTaxiiDataConnector) UnmarshalJSON(body []byte) error { + var m map[string]*json.RawMessage + err := json.Unmarshal(body, &m) + if err != nil { + return err + } + for k, v := range m { + switch k { + case "properties": + if v != nil { + var tiTaxiiDataConnectorProperties TiTaxiiDataConnectorProperties + err = json.Unmarshal(*v, &tiTaxiiDataConnectorProperties) + if err != nil { + return err + } + ttdc.TiTaxiiDataConnectorProperties = &tiTaxiiDataConnectorProperties + } + case "kind": + if v != nil { + var kind securityinsight.KindBasicDataConnector + err = json.Unmarshal(*v, &kind) + if err != nil { + return err + } + ttdc.Kind = kind + } + case "etag": + if v != nil { + var etag string + err = json.Unmarshal(*v, &etag) + if err != nil { + return err + } + ttdc.Etag = &etag + } + case "id": + if v != nil { + var ID string + err = json.Unmarshal(*v, &ID) + if err != nil { + return err + } + ttdc.ID = &ID + } + case "name": + if v != nil { + var name string + err = json.Unmarshal(*v, &name) + if err != nil { + return err + } + ttdc.Name = &name + } + case "type": + if v != nil { + var typeVar string + err = json.Unmarshal(*v, &typeVar) + if err != nil { + return err + } + ttdc.Type = &typeVar + } + case "systemData": + if v != nil { + var systemData securityinsight.SystemData + err = json.Unmarshal(*v, &systemData) + if err != nil { + return err + } + ttdc.SystemData = &systemData + } + } + } + + return nil +} + +type PollingFrequency string + +func (freq *PollingFrequency) UnmarshalJSON(body []byte) error { + var v int + if err := json.Unmarshal(body, &v); err != nil { + return fmt.Errorf("unmarshalling pollingFrequency: %s: %v", string(body), err) + } + switch v { + case 0: + *freq = PollingFrequencyOnceAMinute + case 1: + *freq = PollingFrequencyOnceAnHour + case 2: + *freq = PollingFrequencyOnceADay + } + return fmt.Errorf("unknown enum for pollingFrequency %d", v) +} + +const ( + PollingFrequencyOnceAMinute PollingFrequency = "OnceAMinute" // API returns 0 + PollingFrequencyOnceAnHour PollingFrequency = "OnceAnHour" // API returns 1 + PollingFrequencyOnceADay PollingFrequency = "OnceADay" // API returns 2 +) + +type Time date.Time + +func (t *Time) UnmarshalJSON(data []byte) (err error) { + t.Time, err = time.Parse("01/02/2006 15:04:05", string(data)) + return err +} + +type TiTaxiiDataConnectorProperties struct { + WorkspaceID *string `json:"workspaceId,omitempty"` + FriendlyName *string `json:"friendlyName,omitempty"` + TaxiiServer *string `json:"taxiiServer,omitempty"` + CollectionID *string `json:"collectionId,omitempty"` + UserName *string `json:"userName,omitempty"` + Password *string `json:"password,omitempty"` + TaxiiLookbackPeriod *Time `json:"taxiiLookbackPeriod,omitempty"` + PollingFrequency PollingFrequency `json:"pollingFrequency,omitempty"` + DataTypes *securityinsight.TiTaxiiDataConnectorDataTypes `json:"dataTypes,omitempty"` + TenantID *string `json:"tenantId,omitempty"` +} diff --git a/internal/services/sentinel/sentinel_data_connector_threat_intelligence_taxii.go b/internal/services/sentinel/sentinel_data_connector_threat_intelligence_taxii.go index 322e79782003..6c94a6b17425 100644 --- a/internal/services/sentinel/sentinel_data_connector_threat_intelligence_taxii.go +++ b/internal/services/sentinel/sentinel_data_connector_threat_intelligence_taxii.go @@ -5,8 +5,10 @@ import ( "fmt" "time" + "github.com/Azure/go-autorest/autorest/date" "github.com/hashicorp/go-azure-sdk/resource-manager/operationalinsights/2020-08-01/workspaces" "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/sentinel/azuresdkhacks" "github.com/hashicorp/terraform-provider-azurerm/internal/services/sentinel/parse" "github.com/hashicorp/terraform-provider-azurerm/internal/services/sentinel/validate" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" @@ -28,6 +30,7 @@ type DataConnectorThreatIntelligenceTAXIIModel struct { UserName string `tfschema:"user_name"` Password string `tfschema:"password"` PollingFrequency string `tfschema:"polling_frequency"` + LookbackDate string `tfschema:"lookback_date"` TenantId string `tfschema:"tenant_id"` } @@ -73,12 +76,20 @@ func (r DataConnectorThreatIntelligenceTAXIIResource) Arguments() map[string]*pl }, "polling_frequency": { Type: pluginsdk.TypeString, - Required: true, + Optional: true, + Default: string(securityinsight.PollingFrequencyOnceAnHour), ValidateFunc: validation.StringInSlice([]string{ - string(securityinsight.PollingFrequencyOnceADay), - string(securityinsight.PollingFrequencyOnceAnHour), string(securityinsight.PollingFrequencyOnceAMinute), - }, false), + string(securityinsight.PollingFrequencyOnceAnHour), + string(securityinsight.PollingFrequencyOnceADay), + }, + false), + }, + "lookback_date": { + Type: pluginsdk.TypeString, + Optional: true, + Default: "1970-01-01T00:00:00Z", + ValidateFunc: validation.IsRFC3339Time, }, "tenant_id": { Type: pluginsdk.TypeString, @@ -161,17 +172,20 @@ func (r DataConnectorThreatIntelligenceTAXIIResource) Create() sdk.ResourceFunc tenantId = metadata.Client.Account.TenantId } + // Format is guaranteed by schema validation + lookbackDate, _ := time.Parse(time.RFC3339, plan.LookbackDate) + params := securityinsight.TiTaxiiDataConnector{ Name: &plan.Name, TiTaxiiDataConnectorProperties: &securityinsight.TiTaxiiDataConnectorProperties{ - WorkspaceID: &wspId, - FriendlyName: &plan.DisplayName, - TaxiiServer: &plan.APIRootURL, - CollectionID: &plan.CollectionID, - // TaxiiLookbackPeriod: &date.Time{ - // Time: time.Time{}, - // }, + WorkspaceID: &wspId, + FriendlyName: &plan.DisplayName, + TaxiiServer: &plan.APIRootURL, + CollectionID: &plan.CollectionID, PollingFrequency: securityinsight.PollingFrequency(plan.PollingFrequency), + TaxiiLookbackPeriod: &date.Time{ + Time: lookbackDate, + }, DataTypes: &securityinsight.TiTaxiiDataConnectorDataTypes{ TaxiiClient: &securityinsight.TiTaxiiDataConnectorDataTypesTaxiiClient{ State: securityinsight.DataTypeStateEnabled, @@ -206,6 +220,7 @@ func (r DataConnectorThreatIntelligenceTAXIIResource) Read() sdk.ResourceFunc { Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { client := metadata.Client.Sentinel.DataConnectorsClient + wrapperClient := azuresdkhacks.DataConnectorsClient{BaseClient: client.BaseClient} var state DataConnectorThreatIntelligenceTAXIIModel if err := metadata.Decode(&state); err != nil { @@ -219,7 +234,7 @@ func (r DataConnectorThreatIntelligenceTAXIIResource) Read() sdk.ResourceFunc { workspaceId := workspaces.NewWorkspaceID(id.SubscriptionId, id.ResourceGroup, id.WorkspaceName) - existing, err := client.Get(ctx, id.ResourceGroup, id.WorkspaceName, id.Name) + existing, err := wrapperClient.Get(ctx, id.ResourceGroup, id.WorkspaceName, id.Name) if err != nil { if utils.ResponseWasNotFound(existing.Response) { return metadata.MarkAsGone(id) @@ -227,7 +242,7 @@ func (r DataConnectorThreatIntelligenceTAXIIResource) Read() sdk.ResourceFunc { return fmt.Errorf("retrieving %s: %+v", id, err) } - dc, ok := existing.Value.(securityinsight.TiTaxiiDataConnector) + dc, ok := existing.Value.(azuresdkhacks.TiTaxiiDataConnector) if !ok { return fmt.Errorf("%s was not an Threat Intelligence TAXII Data Connector", id) } @@ -257,6 +272,10 @@ func (r DataConnectorThreatIntelligenceTAXIIResource) Read() sdk.ResourceFunc { model.PollingFrequency = string(props.PollingFrequency) + if props.TaxiiLookbackPeriod != nil { + model.LookbackDate = props.TaxiiLookbackPeriod.Format(time.RFC3339) + } + if props.TenantID != nil { model.TenantId = *props.TenantID } From f7188f20a27e3fd25126ffecfbab88591882b739 Mon Sep 17 00:00:00 2001 From: magodo Date: Wed, 9 Nov 2022 16:00:29 +0800 Subject: [PATCH 3/7] New resource: `azurerm_sentinel_data_connector_threat_intelligence_taxii --- .../sentinel/azuresdkhacks/dataconnectors.go | 82 ++++++- .../services/sentinel/azuresdkhacks/models.go | 8 +- .../sentinel/sentinel_data_connector.go | 5 +- ...ata_connector_threat_intelligence_taxii.go | 45 ++-- ...onnector_threat_intelligence_taxii_test.go | 229 ++++++++++++++++++ ...or_threat_intelligence_taxii.html.markdown | 99 ++++++++ 6 files changed, 442 insertions(+), 26 deletions(-) create mode 100644 internal/services/sentinel/sentinel_data_connector_threat_intelligence_taxii_test.go create mode 100644 website/docs/r/sentinel_data_connector_threat_intelligence_taxii.html.markdown diff --git a/internal/services/sentinel/azuresdkhacks/dataconnectors.go b/internal/services/sentinel/azuresdkhacks/dataconnectors.go index 38a5226a8f60..db3b9207ae7f 100644 --- a/internal/services/sentinel/azuresdkhacks/dataconnectors.go +++ b/internal/services/sentinel/azuresdkhacks/dataconnectors.go @@ -49,7 +49,6 @@ func (client DataConnectorsClient) Get(ctx context.Context, resourceGroupName st return } -// GetPreparer prepares the Get request. func (client DataConnectorsClient) GetPreparer(ctx context.Context, resourceGroupName string, workspaceName string, dataConnectorID string) (*http.Request, error) { pathParameters := map[string]interface{}{ "dataConnectorId": autorest.Encode("path", dataConnectorID), @@ -71,14 +70,10 @@ func (client DataConnectorsClient) GetPreparer(ctx context.Context, resourceGrou return preparer.Prepare((&http.Request{}).WithContext(ctx)) } -// GetSender sends the Get request. The method will close the -// http.Response Body if it receives an error. func (client DataConnectorsClient) GetSender(req *http.Request) (*http.Response, error) { return client.Send(req, azure.DoRetryWithRegistration(client.Client)) } -// GetResponder handles the response to the Get request. The method always -// closes the http.Response Body. func (client DataConnectorsClient) GetResponder(resp *http.Response) (result DataConnectorModel, err error) { err = autorest.Respond( resp, @@ -88,3 +83,80 @@ func (client DataConnectorsClient) GetResponder(resp *http.Response) (result Dat result.Response = autorest.Response{Response: resp} return } + +func (client DataConnectorsClient) CreateOrUpdate(ctx context.Context, resourceGroupName string, workspaceName string, dataConnectorID string, dataConnector securityinsight.BasicDataConnector) (result DataConnectorModel, err error) { + if err := validation.Validate([]validation.Validation{ + {TargetValue: client.SubscriptionID, + Constraints: []validation.Constraint{{Target: "client.SubscriptionID", Name: validation.MinLength, Rule: 1, Chain: nil}}}, + {TargetValue: resourceGroupName, + Constraints: []validation.Constraint{{Target: "resourceGroupName", Name: validation.MaxLength, Rule: 90, Chain: nil}, + {Target: "resourceGroupName", Name: validation.MinLength, Rule: 1, Chain: nil}}}, + {TargetValue: workspaceName, + Constraints: []validation.Constraint{{Target: "workspaceName", Name: validation.MaxLength, Rule: 90, Chain: nil}, + {Target: "workspaceName", Name: validation.MinLength, Rule: 1, Chain: nil}}}}); err != nil { + return result, validation.NewError("securityinsight.DataConnectorsClient", "CreateOrUpdate", err.Error()) + } + + req, err := client.CreateOrUpdatePreparer(ctx, resourceGroupName, workspaceName, dataConnectorID, dataConnector) + if err != nil { + err = autorest.NewErrorWithError(err, "securityinsight.DataConnectorsClient", "CreateOrUpdate", nil, "Failure preparing request") + return + } + + resp, err := client.CreateOrUpdateSender(req) + if err != nil { + result.Response = autorest.Response{Response: resp} + err = autorest.NewErrorWithError(err, "securityinsight.DataConnectorsClient", "CreateOrUpdate", resp, "Failure sending request") + return + } + + result, err = client.CreateOrUpdateResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "securityinsight.DataConnectorsClient", "CreateOrUpdate", resp, "Failure responding to request") + return + } + + return +} + +// CreateOrUpdatePreparer prepares the CreateOrUpdate request. +func (client DataConnectorsClient) CreateOrUpdatePreparer(ctx context.Context, resourceGroupName string, workspaceName string, dataConnectorID string, dataConnector securityinsight.BasicDataConnector) (*http.Request, error) { + pathParameters := map[string]interface{}{ + "dataConnectorId": autorest.Encode("path", dataConnectorID), + "resourceGroupName": autorest.Encode("path", resourceGroupName), + "subscriptionId": autorest.Encode("path", client.SubscriptionID), + "workspaceName": autorest.Encode("path", workspaceName), + } + + const APIVersion = "2022-10-01-preview" + queryParameters := map[string]interface{}{ + "api-version": APIVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsContentType("application/json; charset=utf-8"), + autorest.AsPut(), + autorest.WithBaseURL(client.BaseURI), + autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.OperationalInsights/workspaces/{workspaceName}/providers/Microsoft.SecurityInsights/dataConnectors/{dataConnectorId}", pathParameters), + autorest.WithJSON(dataConnector), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare((&http.Request{}).WithContext(ctx)) +} + +// CreateOrUpdateSender sends the CreateOrUpdate request. The method will close the +// http.Response Body if it receives an error. +func (client DataConnectorsClient) CreateOrUpdateSender(req *http.Request) (*http.Response, error) { + return client.Send(req, azure.DoRetryWithRegistration(client.Client)) +} + +// CreateOrUpdateResponder handles the response to the CreateOrUpdate request. The method always +// closes the http.Response Body. +func (client DataConnectorsClient) CreateOrUpdateResponder(resp *http.Response) (result DataConnectorModel, err error) { + err = autorest.Respond( + resp, + azure.WithErrorUnlessStatusCode(http.StatusOK, http.StatusCreated), + autorest.ByUnmarshallingJSON(&result), + autorest.ByClosing()) + result.Response = autorest.Response{Response: resp} + return +} diff --git a/internal/services/sentinel/azuresdkhacks/models.go b/internal/services/sentinel/azuresdkhacks/models.go index 7e429d2e3803..ebbb361bd7c7 100644 --- a/internal/services/sentinel/azuresdkhacks/models.go +++ b/internal/services/sentinel/azuresdkhacks/models.go @@ -10,6 +10,8 @@ import ( securityinsight "github.com/tombuildsstuff/kermit/sdk/securityinsights/2022-10-01-preview/securityinsights" ) +// Hacking the SDK model, together with the Create and Get method for working around issue: https://github.com/Azure/azure-rest-api-specs/issues/21487 + type DataConnectorModel struct { autorest.Response `json:"-"` Value securityinsight.BasicDataConnector `json:"value,omitempty"` @@ -323,8 +325,10 @@ func (freq *PollingFrequency) UnmarshalJSON(body []byte) error { *freq = PollingFrequencyOnceAnHour case 2: *freq = PollingFrequencyOnceADay + default: + return fmt.Errorf("unknown enum for pollingFrequency %d", v) } - return fmt.Errorf("unknown enum for pollingFrequency %d", v) + return nil } const ( @@ -336,7 +340,7 @@ const ( type Time date.Time func (t *Time) UnmarshalJSON(data []byte) (err error) { - t.Time, err = time.Parse("01/02/2006 15:04:05", string(data)) + t.Time, err = time.Parse(`"01/02/2006 15:04:05"`, string(data)) return err } diff --git a/internal/services/sentinel/sentinel_data_connector.go b/internal/services/sentinel/sentinel_data_connector.go index 04cea7bc036c..437dc97d7d80 100644 --- a/internal/services/sentinel/sentinel_data_connector.go +++ b/internal/services/sentinel/sentinel_data_connector.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/hashicorp/terraform-provider-azurerm/internal/clients" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/sentinel/azuresdkhacks" "github.com/hashicorp/terraform-provider-azurerm/internal/services/sentinel/parse" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" securityinsight "github.com/tombuildsstuff/kermit/sdk/securityinsights/2022-10-01-preview/securityinsights" @@ -17,7 +18,7 @@ func importSentinelDataConnector(expectKind securityinsight.DataConnectorKind) p return nil, err } - client := meta.(*clients.Client).Sentinel.DataConnectorsClient + client := azuresdkhacks.DataConnectorsClient{BaseClient: meta.(*clients.Client).Sentinel.DataConnectorsClient.BaseClient} resp, err := client.Get(ctx, id.ResourceGroup, id.WorkspaceName, id.Name) if err != nil { return nil, fmt.Errorf("retrieving Sentinel Alert Rule %q: %+v", id, err) @@ -63,7 +64,7 @@ func assertDataConnectorKind(dc securityinsight.BasicDataConnector, expectKind s kind = securityinsight.DataConnectorKindMicrosoftDefenderAdvancedThreatProtection case securityinsight.AwsS3DataConnector: kind = securityinsight.DataConnectorKindAmazonWebServicesS3 - case securityinsight.TiTaxiiDataConnector: + case azuresdkhacks.TiTaxiiDataConnector: kind = securityinsight.DataConnectorKindThreatIntelligenceTaxii } if expectKind != kind { diff --git a/internal/services/sentinel/sentinel_data_connector_threat_intelligence_taxii.go b/internal/services/sentinel/sentinel_data_connector_threat_intelligence_taxii.go index 6c94a6b17425..6e9dcc3624fe 100644 --- a/internal/services/sentinel/sentinel_data_connector_threat_intelligence_taxii.go +++ b/internal/services/sentinel/sentinel_data_connector_threat_intelligence_taxii.go @@ -5,7 +5,6 @@ import ( "fmt" "time" - "github.com/Azure/go-autorest/autorest/date" "github.com/hashicorp/go-azure-sdk/resource-manager/operationalinsights/2020-08-01/workspaces" "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" "github.com/hashicorp/terraform-provider-azurerm/internal/services/sentinel/azuresdkhacks" @@ -66,6 +65,7 @@ func (r DataConnectorThreatIntelligenceTAXIIResource) Arguments() map[string]*pl "user_name": { Type: pluginsdk.TypeString, Optional: true, + Sensitive: true, ValidateFunc: validation.StringIsNotEmpty, }, "password": { @@ -128,7 +128,7 @@ func (r DataConnectorThreatIntelligenceTAXIIResource) Create() sdk.ResourceFunc return sdk.ResourceFunc{ Timeout: 30 * time.Minute, Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { - client := metadata.Client.Sentinel.DataConnectorsClient + client := azuresdkhacks.DataConnectorsClient{BaseClient: metadata.Client.Sentinel.DataConnectorsClient.BaseClient} wspClient := metadata.Client.LogAnalytics.WorkspacesClient var plan DataConnectorThreatIntelligenceTAXIIModel @@ -175,15 +175,15 @@ func (r DataConnectorThreatIntelligenceTAXIIResource) Create() sdk.ResourceFunc // Format is guaranteed by schema validation lookbackDate, _ := time.Parse(time.RFC3339, plan.LookbackDate) - params := securityinsight.TiTaxiiDataConnector{ + params := azuresdkhacks.TiTaxiiDataConnector{ Name: &plan.Name, - TiTaxiiDataConnectorProperties: &securityinsight.TiTaxiiDataConnectorProperties{ + TiTaxiiDataConnectorProperties: &azuresdkhacks.TiTaxiiDataConnectorProperties{ WorkspaceID: &wspId, FriendlyName: &plan.DisplayName, TaxiiServer: &plan.APIRootURL, CollectionID: &plan.CollectionID, - PollingFrequency: securityinsight.PollingFrequency(plan.PollingFrequency), - TaxiiLookbackPeriod: &date.Time{ + PollingFrequency: azuresdkhacks.PollingFrequency(plan.PollingFrequency), + TaxiiLookbackPeriod: &azuresdkhacks.Time{ Time: lookbackDate, }, DataTypes: &securityinsight.TiTaxiiDataConnectorDataTypes{ @@ -219,8 +219,7 @@ func (r DataConnectorThreatIntelligenceTAXIIResource) Read() sdk.ResourceFunc { Timeout: 5 * time.Minute, Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { - client := metadata.Client.Sentinel.DataConnectorsClient - wrapperClient := azuresdkhacks.DataConnectorsClient{BaseClient: client.BaseClient} + client := azuresdkhacks.DataConnectorsClient{BaseClient: metadata.Client.Sentinel.DataConnectorsClient.BaseClient} var state DataConnectorThreatIntelligenceTAXIIModel if err := metadata.Decode(&state); err != nil { @@ -234,7 +233,7 @@ func (r DataConnectorThreatIntelligenceTAXIIResource) Read() sdk.ResourceFunc { workspaceId := workspaces.NewWorkspaceID(id.SubscriptionId, id.ResourceGroup, id.WorkspaceName) - existing, err := wrapperClient.Get(ctx, id.ResourceGroup, id.WorkspaceName, id.Name) + existing, err := client.Get(ctx, id.ResourceGroup, id.WorkspaceName, id.Name) if err != nil { if utils.ResponseWasNotFound(existing.Response) { return metadata.MarkAsGone(id) @@ -250,6 +249,7 @@ func (r DataConnectorThreatIntelligenceTAXIIResource) Read() sdk.ResourceFunc { model := DataConnectorThreatIntelligenceTAXIIModel{ Name: id.Name, LogAnalyticsWorkspaceId: workspaceId.ID(), + UserName: state.UserName, // setting the user name from state, as it is not returned from API Password: state.Password, // setting the password from state, as it is not returned from API } @@ -266,10 +266,6 @@ func (r DataConnectorThreatIntelligenceTAXIIResource) Read() sdk.ResourceFunc { model.CollectionID = *props.CollectionID } - if props.UserName != nil { - model.UserName = *props.UserName - } - model.PollingFrequency = string(props.PollingFrequency) if props.TaxiiLookbackPeriod != nil { @@ -290,7 +286,7 @@ func (r DataConnectorThreatIntelligenceTAXIIResource) Update() sdk.ResourceFunc return sdk.ResourceFunc{ Timeout: 30 * time.Minute, Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { - client := metadata.Client.Sentinel.DataConnectorsClient + client := azuresdkhacks.DataConnectorsClient{BaseClient: metadata.Client.Sentinel.DataConnectorsClient.BaseClient} id, err := parse.DataConnectorID(metadata.ResourceData.Id()) if err != nil { @@ -307,13 +303,13 @@ func (r DataConnectorThreatIntelligenceTAXIIResource) Update() sdk.ResourceFunc return fmt.Errorf("retrieving %s: %+v", id, err) } - dc, ok := existing.Value.(securityinsight.TiTaxiiDataConnector) + dc, ok := existing.Value.(azuresdkhacks.TiTaxiiDataConnector) if !ok { return fmt.Errorf("%s was not an Threat Intelligence TAXII Data Connector", id) } if props := dc.TiTaxiiDataConnectorProperties; props != nil { - if metadata.ResourceData.HasChange("friendly_name") { + if metadata.ResourceData.HasChange("display_name") { props.FriendlyName = &plan.DisplayName } if metadata.ResourceData.HasChange("api_root_url") { @@ -329,7 +325,22 @@ func (r DataConnectorThreatIntelligenceTAXIIResource) Update() sdk.ResourceFunc props.Password = &plan.Password } if metadata.ResourceData.HasChange("polling_frequency") { - props.PollingFrequency = securityinsight.PollingFrequency(plan.PollingFrequency) + props.PollingFrequency = azuresdkhacks.PollingFrequency(plan.PollingFrequency) + } + if metadata.ResourceData.HasChange("lookback_date") { + // Format is guaranteed by schema validation + lookbackDate, _ := time.Parse(time.RFC3339, plan.LookbackDate) + props.TaxiiLookbackPeriod = &azuresdkhacks.Time{ + Time: lookbackDate, + } + } + + // Setting the user name and password if non empty in plan, which are required by the API. + if plan.UserName != "" { + props.UserName = &plan.UserName + } + if plan.Password != "" { + props.Password = &plan.Password } } diff --git a/internal/services/sentinel/sentinel_data_connector_threat_intelligence_taxii_test.go b/internal/services/sentinel/sentinel_data_connector_threat_intelligence_taxii_test.go new file mode 100644 index 000000000000..7945dbdc4482 --- /dev/null +++ b/internal/services/sentinel/sentinel_data_connector_threat_intelligence_taxii_test.go @@ -0,0 +1,229 @@ +package sentinel_test + +import ( + "context" + "fmt" + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance" + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance/check" + "github.com/hashicorp/terraform-provider-azurerm/internal/clients" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/sentinel/azuresdkhacks" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/sentinel/parse" + "github.com/hashicorp/terraform-provider-azurerm/utils" +) + +type taxiiInfo struct { + APIRootURL string + CollectionID string + UserName string + Password string +} + +type DataConnectorThreatIntelligenceTAXIIResource struct { + taxiiInfo taxiiInfo + taxiiInfoAlt taxiiInfo +} + +func NewDataConnectorThreatIntelligenceTAXIIResource() DataConnectorThreatIntelligenceTAXIIResource { + return DataConnectorThreatIntelligenceTAXIIResource{ + taxiiInfo: taxiiInfo{ + APIRootURL: os.Getenv("ARM_TEST_TAXII_API_ROOT_URL"), + CollectionID: os.Getenv("ARM_TEST_TAXII_COLLECTION_ID"), + UserName: os.Getenv("ARM_TEST_TAXII_USERNAME"), + Password: os.Getenv("ARM_TEST_TAXII_PASSWORD"), + }, + taxiiInfoAlt: taxiiInfo{ + APIRootURL: os.Getenv("ARM_TEST_TAXII_API_ROOT_URL_ALT"), + CollectionID: os.Getenv("ARM_TEST_TAXII_COLLECTION_ID_ALT"), + UserName: os.Getenv("ARM_TEST_TAXII_USERNAME_ALT"), + Password: os.Getenv("ARM_TEST_TAXII_PASSWORD_ALT"), + }, + } +} + +func (r DataConnectorThreatIntelligenceTAXIIResource) preCheck(t *testing.T, forUpdate bool) { + if r.taxiiInfo.APIRootURL == "" { + t.Skipf(`"ARM_TEST_TAXII_API_ROOT_URL" not specified`) + } + if r.taxiiInfo.APIRootURL == "" { + t.Skipf(`"ARM_TEST_TAXII_COLLECTION_ID" not specified`) + } + if forUpdate { + if r.taxiiInfoAlt.APIRootURL == "" { + t.Skipf(`"ARM_TEST_TAXII_API_ROOT_URL_ALT" not specified`) + } + if r.taxiiInfoAlt.CollectionID == "" { + t.Skipf(`"ARM_TEST_TAXII_COLLECTION_ID_ALT" not specified`) + } + } +} + +func TestAccDataConnectorThreatIntelligenceTAXII_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_sentinel_data_connector_threat_intelligence_taxii", "test") + r := NewDataConnectorThreatIntelligenceTAXIIResource() + r.preCheck(t, false) + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.basic(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep("user_name", "password"), + }) +} + +func TestAccDataConnectorThreatIntelligenceTAXII_update(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_sentinel_data_connector_threat_intelligence_taxii", "test") + r := NewDataConnectorThreatIntelligenceTAXIIResource() + r.preCheck(t, true) + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.basic(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep("user_name", "password"), + { + Config: r.update(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep("user_name", "password"), + { + Config: r.basic(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep("user_name", "password"), + }) +} + +func TestAccDataConnectorThreatIntelligenceTAXII_requiresImport(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_sentinel_data_connector_threat_intelligence_taxii", "test") + r := NewDataConnectorThreatIntelligenceTAXIIResource() + r.preCheck(t, false) + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.basic(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.RequiresImportErrorStep(r.requiresImport), + }) +} + +func (r DataConnectorThreatIntelligenceTAXIIResource) Exists(ctx context.Context, clients *clients.Client, state *terraform.InstanceState) (*bool, error) { + client := azuresdkhacks.DataConnectorsClient{BaseClient: clients.Sentinel.DataConnectorsClient.BaseClient} + + id, err := parse.DataConnectorID(state.ID) + if err != nil { + return nil, err + } + + if resp, err := client.Get(ctx, id.ResourceGroup, id.WorkspaceName, id.Name); err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return utils.Bool(false), nil + } + return nil, fmt.Errorf("retrieving %s: %+v", id, err) + } + + return utils.Bool(true), nil +} + +func (r DataConnectorThreatIntelligenceTAXIIResource) basic(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_sentinel_data_connector_threat_intelligence_taxii" "test" { + name = "acctestDC-%d" + log_analytics_workspace_id = azurerm_log_analytics_workspace.test.id + display_name = "test" + api_root_url = "%s" + collection_id = "%s" + user_name = "%s" + password = "%s" + depends_on = [azurerm_log_analytics_solution.test] +} +`, template, data.RandomInteger, r.taxiiInfo.APIRootURL, r.taxiiInfo.CollectionID, r.taxiiInfo.UserName, r.taxiiInfo.Password) +} + +func (r DataConnectorThreatIntelligenceTAXIIResource) update(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_sentinel_data_connector_threat_intelligence_taxii" "test" { + name = "acctestDC-%d" + log_analytics_workspace_id = azurerm_log_analytics_workspace.test.id + display_name = "test_update" + api_root_url = "%s" + collection_id = "%s" + user_name = "%s" + password = "%s" + polling_frequency = "OnceADay" + lookback_date = "1990-01-01T00:00:00Z" + depends_on = [azurerm_log_analytics_solution.test] +} +`, template, data.RandomInteger, r.taxiiInfoAlt.APIRootURL, r.taxiiInfoAlt.CollectionID, r.taxiiInfoAlt.UserName, r.taxiiInfoAlt.Password) +} + +func (r DataConnectorThreatIntelligenceTAXIIResource) requiresImport(data acceptance.TestData) string { + template := r.basic(data) + return fmt.Sprintf(` +%s + +resource "azurerm_sentinel_data_connector_threat_intelligence_taxii" "import" { + name = azurerm_sentinel_data_connector_threat_intelligence_taxii.test.name + log_analytics_workspace_id = azurerm_sentinel_data_connector_threat_intelligence_taxii.test.log_analytics_workspace_id + display_name = azurerm_sentinel_data_connector_threat_intelligence_taxii.test.display_name + api_root_url = azurerm_sentinel_data_connector_threat_intelligence_taxii.test.api_root_url + collection_id = azurerm_sentinel_data_connector_threat_intelligence_taxii.test.collection_id +} +`, template) +} + +func (r DataConnectorThreatIntelligenceTAXIIResource) template(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-sentinel-%d" + location = "%s" +} + +resource "azurerm_log_analytics_workspace" "test" { + name = "acctestLAW-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + sku = "PerGB2018" +} + +resource "azurerm_log_analytics_solution" "test" { + solution_name = "SecurityInsights" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + workspace_resource_id = azurerm_log_analytics_workspace.test.id + workspace_name = azurerm_log_analytics_workspace.test.name + + plan { + publisher = "Microsoft" + product = "OMSGallery/SecurityInsights" + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger) +} diff --git a/website/docs/r/sentinel_data_connector_threat_intelligence_taxii.html.markdown b/website/docs/r/sentinel_data_connector_threat_intelligence_taxii.html.markdown new file mode 100644 index 000000000000..e7da7b5d6b8d --- /dev/null +++ b/website/docs/r/sentinel_data_connector_threat_intelligence_taxii.html.markdown @@ -0,0 +1,99 @@ +--- +subcategory: "Sentinel" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_sentinel_data_connector_threat_intelligence_taxii" +description: |- + Manages an Threat Intelligence TAXII Data Connector. +--- + +# azurerm_sentinel_data_connector_threat_intelligence_taxii + +Manages an Threat Intelligence TAXII Data Connector. + +## Example Usage + +```hcl +resource "azurerm_resource_group" "example" { + name = "example-rg" + location = "West Europe" +} + +resource "azurerm_log_analytics_workspace" "example" { + name = "example-workspace" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name + sku = "PerGB2018" +} + +resource "azurerm_log_analytics_solution" "example" { + solution_name = "SecurityInsights" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name + workspace_resource_id = azurerm_log_analytics_workspace.example.id + workspace_name = azurerm_log_analytics_workspace.example.name + + plan { + publisher = "Microsoft" + product = "OMSGallery/SecurityInsights" + } +} + +resource "azurerm_sentinel_data_connector_threat_intelligence_taxii" "example" { + name = "example" + log_analytics_workspace_id = azurerm_log_analytics_solution.example.workspace_resource_id + display_name = "example" + api_root_url = "https://foo/taxii2/api2/" + collection_id = "someid" + depends_on = [azurerm_log_analytics_solution.test] +} +``` + +## Arguments Reference + +The following arguments are supported: + +* `log_analytics_workspace_id` - (Required) The ID of the Log Analytics Workspace that this Threat Intelligence TAXII Data Connector resides in. Changing this forces a new Threat Intelligence TAXII Data Connector to be created. + +* `name` - (Required) The name which should be used for this Threat Intelligence TAXII Data Connector. Changing this forces a new Threat Intelligence TAXII Data Connector to be created. + +* `display_name` - (Required) The friendly name which should be used for this Threat Intelligence TAXII Data Connector. + +* `api_root_url` - (Required) The API root URI of the TAXII server. + +* `collection_id` - (Required) The collection ID of the TAXII server. + +* `user_name` - (Optional) The user name for the TAXII server. + +* `password` - (Optional) The password for the TAXII server. + +* `polling_frequency` - (Optional) The polling frequency for the TAXII server. Possible values are `OnceAMinute`, `OnceAnHour` and `OnceADay`. Defaults to `OnceAnHour`. + +* `lookback_date` - (Optional) The lookback date for the TAXII server in RFC3339. Defaults to `1970-01-01T00:00:00Z`. + +--- + +* `tenant_id` - (Optional) The ID of the tenant that this Threat Intelligence TAXII Data Connector connects to. Changing this forces a new Threat Intelligence TAXII Data Connector to be created. + +-> **NOTE** Currently, only the same tenant as the running account is allowed. Cross-tenant scenario is not supported yet. + +## Attributes Reference + +In addition to the Arguments listed above - the following Attributes are exported: + +* `id` - The ID of the Threat Intelligence TAXII Data Connector. + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/language/resources/syntax#operation-timeouts) for certain actions: + +* `create` - (Defaults to 30 minutes) Used when creating the Threat Intelligence TAXII Data Connector. +* `read` - (Defaults to 5 minutes) Used when retrieving the Threat Intelligence TAXII Data Connector. +* `delete` - (Defaults to 30 minutes) Used when deleting the Threat Intelligence TAXII Data Connector. + +## Import + +Threat Intelligence TAXII Data Connectors can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_sentinel_data_connector_threat_intelligence_taxii.example /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.OperationalInsights/workspaces/workspace1/providers/Microsoft.SecurityInsights/dataConnectors/dc1 +``` From e39e85256f9156e2e8b8d88efa3f911f3ad2681d Mon Sep 17 00:00:00 2001 From: magodo Date: Wed, 9 Nov 2022 16:07:37 +0800 Subject: [PATCH 4/7] remove a file.. --- internal/services/sentinel/' | 348 ----------------------------------- 1 file changed, 348 deletions(-) delete mode 100644 internal/services/sentinel/' diff --git a/internal/services/sentinel/' b/internal/services/sentinel/' deleted file mode 100644 index 080aa280ea78..000000000000 --- a/internal/services/sentinel/' +++ /dev/null @@ -1,348 +0,0 @@ -package sentinel - -import ( - "context" - "fmt" - "time" - - "github.com/hashicorp/go-azure-sdk/resource-manager/operationalinsights/2020-08-01/workspaces" - "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" - "github.com/hashicorp/terraform-provider-azurerm/internal/services/sentinel/parse" - "github.com/hashicorp/terraform-provider-azurerm/internal/services/sentinel/validate" - "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" - "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" - "github.com/hashicorp/terraform-provider-azurerm/utils" - securityinsight "github.com/tombuildsstuff/kermit/sdk/securityinsights/2022-10-01-preview/securityinsights" -) - -type DataConnectorThreatIntelligenceTAXIIResource struct{} - -var _ sdk.ResourceWithUpdate = DataConnectorThreatIntelligenceTAXIIResource{} - -type DataConnectorThreatIntelligenceTAXIIModel struct { - Name string `tfschema:"name"` - LogAnalyticsWorkspaceId string `tfschema:"log_analytics_workspace_id"` - FriendlyName string `tfschema:"friendly_name"` - APIRootURL string `tfschema:"api_root_url"` - CollectionID string `tfschema:"collection_id"` - UserName string `tfschema:"user_name"` - Password string `tfschema:"password"` - PollingFrequency string `tfschema:"polling_frequency"` - TenantId string `tfschema:"tenant_id"` -} - -func (r DataConnectorThreatIntelligenceTAXIIResource) Arguments() map[string]*pluginsdk.Schema { - return map[string]*pluginsdk.Schema{ - "name": { - Type: pluginsdk.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - "log_analytics_workspace_id": { - Type: pluginsdk.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: workspaces.ValidateWorkspaceID, - }, - "friendly_name": { - Type: pluginsdk.TypeString, - Required: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - "api_root_url": { - Type: pluginsdk.TypeString, - Required: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - "collection_id": { - Type: pluginsdk.TypeString, - Required: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - "user_name": { - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - "password": { - Type: pluginsdk.TypeString, - Optional: true, - Sensitive: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - "polling_frequency": { - Type: pluginsdk.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice([]string{ - string(securityinsight.PollingFrequencyOnceADay), - string(securityinsight.PollingFrequencyOnceAnHour), - string(securityinsight.PollingFrequencyOnceAMinute), - }, false), - }, - "tenant_id": { - Type: pluginsdk.TypeString, - Optional: true, - Computed: true, - ForceNew: true, - ValidateFunc: validation.IsUUID, - }, - } -} - -func (r DataConnectorThreatIntelligenceTAXIIResource) Attributes() map[string]*pluginsdk.Schema { - return map[string]*pluginsdk.Schema{} -} - -func (r DataConnectorThreatIntelligenceTAXIIResource) ResourceType() string { - return "azurerm_sentinel_data_connector_threat_intelligence_taxii" -} - -func (r DataConnectorThreatIntelligenceTAXIIResource) ModelObject() interface{} { - return &DataConnectorThreatIntelligenceTAXIIModel{} -} - -func (r DataConnectorThreatIntelligenceTAXIIResource) IDValidationFunc() pluginsdk.SchemaValidateFunc { - return validate.DataConnectorID -} - -func (r DataConnectorThreatIntelligenceTAXIIResource) CustomImporter() sdk.ResourceRunFunc { - return func(ctx context.Context, metadata sdk.ResourceMetaData) error { - _, err := importSentinelDataConnector(securityinsight.DataConnectorKindThreatIntelligenceTaxii)(ctx, metadata.ResourceData, metadata.Client) - return err - } -} - -func (r DataConnectorThreatIntelligenceTAXIIResource) Create() sdk.ResourceFunc { - return sdk.ResourceFunc{ - Timeout: 30 * time.Minute, - Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { - client := metadata.Client.Sentinel.DataConnectorsClient - wspClient := metadata.Client.LogAnalytics.WorkspacesClient - - var plan DataConnectorThreatIntelligenceTAXIIModel - if err := metadata.Decode(&plan); err != nil { - return fmt.Errorf("decoding %+v", err) - } - - workspaceId, err := workspaces.ParseWorkspaceID(plan.LogAnalyticsWorkspaceId) - if err != nil { - return err - } - - wsp, err := wspClient.Get(ctx, *workspaceId) - if err != nil { - return fmt.Errorf("retrieving the workspace %s: %+v", workspaceId, err) - } - if wsp.Model == nil { - return fmt.Errorf("nil model of the workspace %s", workspaceId) - } - if wsp.Model.Properties == nil { - return fmt.Errorf("nil properties of the workspace %s", workspaceId) - } - if wsp.Model.Properties.CustomerId == nil { - return fmt.Errorf("nil workspace id of the workspace %s", workspaceId) - } - wspId := *wsp.Model.Properties.CustomerId - - id := parse.NewDataConnectorID(workspaceId.SubscriptionId, workspaceId.ResourceGroupName, workspaceId.WorkspaceName, plan.Name) - existing, err := client.Get(ctx, id.ResourceGroup, id.WorkspaceName, id.Name) - if err != nil { - if !utils.ResponseWasNotFound(existing.Response) { - return fmt.Errorf("checking for presence of existing %s: %+v", id, err) - } - } - if !utils.ResponseWasNotFound(existing.Response) { - return metadata.ResourceRequiresImport(r.ResourceType(), id) - } - - tenantId := plan.TenantId - if tenantId == "" { - tenantId = metadata.Client.Account.TenantId - } - - params := securityinsight.TiTaxiiDataConnector{ - Name: &plan.Name, - TiTaxiiDataConnectorProperties: &securityinsight.TiTaxiiDataConnectorProperties{ - WorkspaceID: &wspId, - FriendlyName: &plan.FriendlyName, - TaxiiServer: &plan.APIRootURL, - CollectionID: &plan.CollectionID, - // TaxiiLookbackPeriod: &date.Time{ - // Time: time.Time{}, - // }, - PollingFrequency: securityinsight.PollingFrequency(plan.PollingFrequency), - DataTypes: &securityinsight.TiTaxiiDataConnectorDataTypes{ - TaxiiClient: &securityinsight.TiTaxiiDataConnectorDataTypesTaxiiClient{ - State: securityinsight.DataTypeStateEnabled, - }, - }, - TenantID: &tenantId, - }, - Kind: securityinsight.KindBasicDataConnectorKindThreatIntelligenceTaxii, - } - - if plan.UserName != "" { - params.TiTaxiiDataConnectorProperties.UserName = &plan.UserName - } - - if plan.Password != "" { - params.TiTaxiiDataConnectorProperties.Password = &plan.Password - } - - if _, err = client.CreateOrUpdate(ctx, id.ResourceGroup, id.WorkspaceName, id.Name, params); err != nil { - return fmt.Errorf("creating %s: %+v", id, err) - } - - metadata.SetID(id) - return nil - }, - } -} - -func (r DataConnectorThreatIntelligenceTAXIIResource) Read() sdk.ResourceFunc { - return sdk.ResourceFunc{ - Timeout: 5 * time.Minute, - - Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { - client := metadata.Client.Sentinel.DataConnectorsClient - - var state DataConnectorThreatIntelligenceTAXIIModel - if err := metadata.Decode(&state); err != nil { - return err - } - - id, err := parse.DataConnectorID(metadata.ResourceData.Id()) - if err != nil { - return err - } - - workspaceId := workspaces.NewWorkspaceID(id.SubscriptionId, id.ResourceGroup, id.WorkspaceName) - - existing, err := client.Get(ctx, id.ResourceGroup, id.WorkspaceName, id.Name) - if err != nil { - if utils.ResponseWasNotFound(existing.Response) { - return metadata.MarkAsGone(id) - } - return fmt.Errorf("retrieving %s: %+v", id, err) - } - - dc, ok := existing.Value.(securityinsight.TiTaxiiDataConnector) - if !ok { - return fmt.Errorf("%s was not an Threat Intelligence TAXII Data Connector", id) - } - - model := DataConnectorThreatIntelligenceTAXIIModel{ - Name: id.Name, - LogAnalyticsWorkspaceId: workspaceId.ID(), - Password: state.Password, // setting the password from state, as it is not returned from API - } - - if props := dc.TiTaxiiDataConnectorProperties; props != nil { - if props.FriendlyName != nil { - model.FriendlyName = *props.FriendlyName - } - - if props.TaxiiServer != nil { - model.APIRootURL = *props.TaxiiServer - } - - if props.CollectionID != nil { - model.CollectionID = *props.CollectionID - } - - if props.UserName != nil { - model.UserName = *props.UserName - } - - model.PollingFrequency = string(props.PollingFrequency) - - if props.TenantID != nil { - model.TenantId = *props.TenantID - } - } - - return metadata.Encode(&model) - }, - } -} - -func (r DataConnectorThreatIntelligenceTAXIIResource) Update() sdk.ResourceFunc { - return sdk.ResourceFunc{ - Timeout: 30 * time.Minute, - Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { - client := metadata.Client.Sentinel.DataConnectorsClient - - id, err := parse.DataConnectorID(metadata.ResourceData.Id()) - if err != nil { - return err - } - - workspaceId := workspaces.NewWorkspaceID(id.SubscriptionId, id.ResourceGroup, id.WorkspaceName) - - var plan DataConnectorThreatIntelligenceTAXIIModel - if err := metadata.Decode(&plan); err != nil { - return err - } - - existing, err := client.Get(ctx, id.ResourceGroup, id.WorkspaceName, id.Name) - if err != nil { - return fmt.Errorf("retrieving %s: %+v", id, err) - } - - dc, ok := existing.Value.(securityinsight.TiTaxiiDataConnector) - if !ok { - return fmt.Errorf("%s was not an Threat Intelligence TAXII Data Connector", id) - } - - if props := dc.TiTaxiiDataConnectorProperties; props != nil { - if metadata.ResourceData.HasChange("friendly_name") { - props.FriendlyName = &plan.FriendlyName - } - if metadata.ResourceData.HasChange("api_root_url") { - props.TaxiiServer = &plan.APIRootURL - } - if metadata.ResourceData.HasChange("collection_id") { - props.CollectionID = &plan.CollectionID - } - if metadata.ResourceData.HasChange("user_name") { - props.UserName = &plan.UserName - } - if metadata.ResourceData.HasChange("password") { - props.Password = &plan.Password - } - } - - if _, err := client.CreateOrUpdate(ctx, id.ResourceGroup, id.Name, params); err != nil { - return fmt.Errorf("updating %s: %+v", id, err) - } - - return nil - }, - } -} - -func (r DataConnectorThreatIntelligenceTAXIIResource) Delete() sdk.ResourceFunc { - return sdk.ResourceFunc{ - Timeout: 30 * time.Minute, - Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { - client := metadata.Client.Sentinel.DataConnectorsClient - - id, err := parse.DataConnectorThreatIntelligenceTAXIIID(metadata.ResourceData.Id()) - if err != nil { - return err - } - - future, err := client.Delete(ctx, id.ResourceGroup, id.Name) - if err != nil { - return fmt.Errorf("deleting %s: %+v", id, err) - } - - if err := future.WaitForCompletionRef(ctx, client.Client); err != nil { - return fmt.Errorf("waiting for removal of %s: %+v", id, err) - } - - return nil - }, - } -} From 9526847722f41d93ad79e945560bc4279ce58c8b Mon Sep 17 00:00:00 2001 From: magodo Date: Thu, 5 Jan 2023 11:14:13 +0800 Subject: [PATCH 5/7] Add a TODO 4.0 to check whether the service has fixed the incorrect behavior --- internal/services/sentinel/azuresdkhacks/models.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/services/sentinel/azuresdkhacks/models.go b/internal/services/sentinel/azuresdkhacks/models.go index ebbb361bd7c7..7915321e045d 100644 --- a/internal/services/sentinel/azuresdkhacks/models.go +++ b/internal/services/sentinel/azuresdkhacks/models.go @@ -10,6 +10,7 @@ import ( securityinsight "github.com/tombuildsstuff/kermit/sdk/securityinsights/2022-10-01-preview/securityinsights" ) +// TODO 4.0 check if this can be removed // Hacking the SDK model, together with the Create and Get method for working around issue: https://github.com/Azure/azure-rest-api-specs/issues/21487 type DataConnectorModel struct { From 142d82fe81eb6ef5b810e6a2636f38be983e835e Mon Sep 17 00:00:00 2001 From: magodo Date: Thu, 5 Jan 2023 11:35:20 +0800 Subject: [PATCH 6/7] Pass build --- .../sentinel_data_connector_threat_intelligence_taxii.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/services/sentinel/sentinel_data_connector_threat_intelligence_taxii.go b/internal/services/sentinel/sentinel_data_connector_threat_intelligence_taxii.go index 6e9dcc3624fe..8f237b22e2e3 100644 --- a/internal/services/sentinel/sentinel_data_connector_threat_intelligence_taxii.go +++ b/internal/services/sentinel/sentinel_data_connector_threat_intelligence_taxii.go @@ -5,7 +5,7 @@ import ( "fmt" "time" - "github.com/hashicorp/go-azure-sdk/resource-manager/operationalinsights/2020-08-01/workspaces" + "github.com/hashicorp/go-azure-sdk/resource-manager/operationalinsights/2022-10-01/workspaces" "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" "github.com/hashicorp/terraform-provider-azurerm/internal/services/sentinel/azuresdkhacks" "github.com/hashicorp/terraform-provider-azurerm/internal/services/sentinel/parse" @@ -129,7 +129,7 @@ func (r DataConnectorThreatIntelligenceTAXIIResource) Create() sdk.ResourceFunc Timeout: 30 * time.Minute, Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { client := azuresdkhacks.DataConnectorsClient{BaseClient: metadata.Client.Sentinel.DataConnectorsClient.BaseClient} - wspClient := metadata.Client.LogAnalytics.WorkspacesClient + wspClient := metadata.Client.LogAnalytics.WorkspaceClient var plan DataConnectorThreatIntelligenceTAXIIModel if err := metadata.Decode(&plan); err != nil { From 6afe958e850c2c6625aec35ff360586903820c9a Mon Sep 17 00:00:00 2001 From: magodo Date: Tue, 10 Jan 2023 10:47:17 +0800 Subject: [PATCH 7/7] Make sure the hacks can still work even if the service fixed the API --- .../services/sentinel/azuresdkhacks/models.go | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/internal/services/sentinel/azuresdkhacks/models.go b/internal/services/sentinel/azuresdkhacks/models.go index 7915321e045d..aa9b2ed59fa0 100644 --- a/internal/services/sentinel/azuresdkhacks/models.go +++ b/internal/services/sentinel/azuresdkhacks/models.go @@ -315,19 +315,15 @@ func (ttdc *TiTaxiiDataConnector) UnmarshalJSON(body []byte) error { type PollingFrequency string func (freq *PollingFrequency) UnmarshalJSON(body []byte) error { - var v int - if err := json.Unmarshal(body, &v); err != nil { - return fmt.Errorf("unmarshalling pollingFrequency: %s: %v", string(body), err) - } - switch v { - case 0: + switch string(body) { + case "0", string(PollingFrequencyOnceAMinute): *freq = PollingFrequencyOnceAMinute - case 1: + case "1", string(PollingFrequencyOnceAnHour): *freq = PollingFrequencyOnceAnHour - case 2: + case "2", string(PollingFrequencyOnceADay): *freq = PollingFrequencyOnceADay default: - return fmt.Errorf("unknown enum for pollingFrequency %d", v) + return fmt.Errorf("unknown enum for pollingFrequency %s", string(body)) } return nil } @@ -341,7 +337,16 @@ const ( type Time date.Time func (t *Time) UnmarshalJSON(data []byte) (err error) { - t.Time, err = time.Parse(`"01/02/2006 15:04:05"`, string(data)) + // Firstly, try to parse the date time via RFC3339, which is the expected format defined by Swagger. + // However, since the service issue (#21487), it currently doesn't return in this format. + // In order not to break the code once the service fix it, we keep this try at first. + if time, err := time.Parse(time.RFC3339, string(data)); err == nil { + t.Time = time + return nil + } + + // This is the format that the service returns at this moment, which is not the expected format (RFC3339). + t.Time, err = time.Parse(`"1/2/2006 15:04:05 PM -07:00"`, string(data)) return err }