diff --git a/docs/resources/integration_azure_ad_al.md b/docs/resources/integration_azure_ad_al.md new file mode 100644 index 00000000..e0ba5992 --- /dev/null +++ b/docs/resources/integration_azure_ad_al.md @@ -0,0 +1,59 @@ +--- +subcategory: "Cloud Account Integrations" +layout: "lacework" +page_title: "Lacework: lacework_integration_azure_ad_al" +description: |- + Create and manage Azure Active Directory Activity Log integrations +--- + +# lacework\_integration\_azure\_ad\_al + +!> **Warning:** This integration is not yet generally available. Please contact your Lacework account team to request access to the Azure AD feature preview. + +Use this resource to configure an Azure Active Directory Activity Log integration to analyze audit logs +for monitoring cloud account security. + +## Example Usage + +```hcl +resource "lacework_integration_azure_ad_al" "account_abc" { + name = "account ABC" + tenant_id = "abbc1234-abc1-123a-1234-abcd1234abcd" + event_hub_namespace = "your-eventhub-ns.servicebus.windows.net" + event_hub_name = "your-event-hub-name" + credentials { + client_id = "1234abcd-abcd-1234-ab12-abcd1234abcd" + client_secret = "ABCD1234abcd1234abdc1234ABCD1234abcdefxxx=" + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The Azure Active Directory Activity Log integration name. +* `tenant_id` - (Required) The directory tenant ID. +* `event_hub_namespace` - (Required) The EventHub Namespace. +* `event_hub_name` - (Required) The EventHub Name. +* `credentials` - (Required) The credentials needed by the integration. See [Credentials](#credentials) below for details. +* `enabled` - (Optional) The state of the external integration. Defaults to `true`. +* `retries` - (Optional) The number of attempts to create the external integration. Defaults to `5`. + +### Credentials + +`credentials` supports the following arguments: + +* `client_id` - (Required) The application client ID. +* `client_secret` - (Required) The client secret. + +## Import + +A Lacework Azure Active Directory Activity Log integration can be imported using a `INT_GUID`, e.g. + +``` +$ terraform import lacework_integration_azure_ad_al.account_abc EXAMPLE_1234BAE1E42182964D23973F44CFEA3C4AB63B99E9A1EC5 +``` +-> **Note:** To retrieve the `INT_GUID` from existing integrations in your account, use the + Lacework CLI command `lacework cloud-account list`. To install this tool follow + [this documentation](https://docs.lacework.com/cli/). diff --git a/examples/resource_lacework_integration_azure_ad_al/main.tf b/examples/resource_lacework_integration_azure_ad_al/main.tf new file mode 100644 index 00000000..ad9a0d2f --- /dev/null +++ b/examples/resource_lacework_integration_azure_ad_al/main.tf @@ -0,0 +1,24 @@ +terraform { + required_providers { + lacework = { + source = "lacework/lacework" + } + } +} + +variable "name" { + type = string + default = "Azure Active Directory Activity Log integration example" +} + +resource "lacework_integration_azure_ad_al" "example" { + name = var.name + tenant_id = "your-tenant-id-goes-here" + event_hub_namespace = "your-eventhub-ns.servicebus.windows.net" + event_hub_name = "your-event-hub-name" + credentials { + client_id = "1234567890-abcd-client-id" + client_secret = "SUPER_SECURE_SECRET" + } + retries = 10 +} diff --git a/integration/integration.go b/integration/integration.go index 29d9e6f5..c30b2d02 100644 --- a/integration/integration.go +++ b/integration/integration.go @@ -84,6 +84,17 @@ func GetCloudOrgAccountIntegrationName(result string) string { return res.Data.Name } +func GetCloudAccountAzureAdAlIntegrationResponse(result string) api.AzureAdAlIntegrationResponse { + id := GetIDFromTerraResults(result) + + response, err := LwClient.V2.CloudAccounts.GetAzureAdAl(id) + if err != nil { + log.Fatalf("Unable to find AzureAdAl integration with id: %s\n Response: %v", id, response) + } + + return response +} + func GetCloudAccountEksAuditLogData(result string) api.AwsEksAuditData { id := GetIDFromTerraResults(result) diff --git a/integration/resource_lacework_integration_azure_ad_al_test.go b/integration/resource_lacework_integration_azure_ad_al_test.go new file mode 100644 index 00000000..b9bcb4e0 --- /dev/null +++ b/integration/resource_lacework_integration_azure_ad_al_test.go @@ -0,0 +1,39 @@ +package integration + +import ( + "fmt" + "testing" + + "github.com/gruntwork-io/terratest/modules/terraform" + "github.com/stretchr/testify/assert" +) + +// TestIntegrationAzureAdAl applies integration terraform: +// => '../examples/resource_lacework_integration_azure_ad_al' +// +// It uses the go-sdk to verify the created integration, +// applies an update with new integration name and destroys it +func TestIntegrationAzureAdAl(t *testing.T) { + integration_name := "Azure Ad Al Example Integration Test With Terraform" + updated_integration_name := fmt.Sprintf("%s Updated", integration_name) + + terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{ + TerraformDir: "../examples/resource_lacework_integration_azure_ad_al", + Vars: map[string]interface{}{ + "name": integration_name, + }, + }) + defer terraform.Destroy(t, terraformOptions) + + // Create new AzureAdAl integration + create := terraform.InitAndApplyAndIdempotent(t, terraformOptions) + intgRes := GetCloudAccountAzureAdAlIntegrationResponse(create) + assert.Equal(t, integration_name, intgRes.Data.Name) + + // Update integration + terraformOptions.Vars["name"] = updated_integration_name + + update := terraform.InitAndApplyAndIdempotent(t, terraformOptions) + intgRes = GetCloudAccountAzureAdAlIntegrationResponse(update) + assert.Equal(t, updated_integration_name, intgRes.Data.Name) +} diff --git a/lacework/provider.go b/lacework/provider.go index 890981d7..db2a571a 100644 --- a/lacework/provider.go +++ b/lacework/provider.go @@ -96,6 +96,7 @@ func Provider() *schema.Provider { "lacework_integration_aws_govcloud_cfg": resourceLaceworkIntegrationAwsGovCloudCfg(), "lacework_integration_aws_govcloud_ct": resourceLaceworkIntegrationAwsGovCloudCT(), "lacework_integration_azure_cfg": resourceLaceworkIntegrationAzureCfg(), + "lacework_integration_azure_ad_al": resourceLaceworkIntegrationAzureAdAl(), "lacework_integration_azure_al": resourceLaceworkIntegrationAzureActivityLog(), "lacework_integration_docker_hub": resourceLaceworkIntegrationDockerHub(), "lacework_integration_docker_v2": resourceLaceworkIntegrationDockerV2(), diff --git a/lacework/resource_lacework_integration_azure_ad_al.go b/lacework/resource_lacework_integration_azure_ad_al.go new file mode 100644 index 00000000..f2dadb26 --- /dev/null +++ b/lacework/resource_lacework_integration_azure_ad_al.go @@ -0,0 +1,249 @@ +package lacework + +import ( + "context" + "fmt" + "log" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/lacework/go-sdk/api" +) + +func resourceLaceworkIntegrationAzureAdAl() *schema.Resource { + return &schema.Resource{ + Create: resourceLaceworkIntegrationAzureAdAlCreate, + Read: resourceLaceworkIntegrationAzureAdAlRead, + Update: resourceLaceworkIntegrationAzureAdAlUpdate, + Delete: resourceLaceworkIntegrationAzureAdAlDelete, + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "intg_guid": { + Type: schema.TypeString, + Computed: true, + }, + "enabled": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + "retries": { + Type: schema.TypeInt, + Optional: true, + Default: 5, + Description: "The number of attempts to create the external integration.", + }, + "tenant_id": { + Type: schema.TypeString, + Required: true, + }, + "event_hub_namespace": { + Type: schema.TypeString, + Required: true, + }, + "event_hub_name": { + Type: schema.TypeString, + Required: true, + }, + "credentials": { + Type: schema.TypeList, + MaxItems: 1, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "client_id": { + Type: schema.TypeString, + Required: true, + }, + "client_secret": { + Type: schema.TypeString, + Required: true, + Sensitive: true, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + // @afiune we can't compare this element since our API, for security reasons, + // does NOT return the client secret configured in the Lacework server. So if + // any other element changed from the credentials then we trigger a diff + return !d.HasChanges( + "name", "tenant_id", "org_level", "event_hub_namespace", + "event_hub_name", "enabled", "credentials.0.client_id", + ) + }, + }, + }, + }, + }, + "created_or_updated_time": { + Type: schema.TypeString, + Computed: true, + }, + "created_or_updated_by": { + Type: schema.TypeString, + Computed: true, + }, + "type_name": { + Type: schema.TypeString, + Computed: true, + }, + "org_level": { + Type: schema.TypeBool, + Computed: true, + }, + }, + } +} + +func resourceLaceworkIntegrationAzureAdAlCreate(d *schema.ResourceData, meta interface{}) error { + var ( + lacework = meta.(*api.Client) + retries = d.Get("retries").(int) + azure = api.NewCloudAccount(d.Get("name").(string), + api.AzureAdAlCloudAccount, + api.AzureAdAlData{ + TenantID: d.Get("tenant_id").(string), + EventHubNamespace: d.Get("event_hub_namespace").(string), + EventHubName: d.Get("event_hub_name").(string), + Credentials: api.AzureAdAlCredentials{ + ClientID: d.Get("credentials.0.client_id").(string), + ClientSecret: d.Get("credentials.0.client_secret").(string), + }, + }, + ) + ) + if !d.Get("enabled").(bool) { + azure.Enabled = 0 + } + + return retry.RetryContext(context.Background(), d.Timeout(schema.TimeoutCreate), func() *retry.RetryError { + retries-- + log.Printf("[INFO] Creating %s integration\n", api.AzureAdAlCloudAccount.String()) + response, err := lacework.V2.CloudAccounts.Create(azure) + if err != nil { + if retries <= 0 { + return retry.NonRetryableError( + fmt.Errorf("error creating %s integration: %s", + api.AzureAdAlCloudAccount.String(), err, + )) + } + log.Printf( + "[INFO] Unable to create %s integration. (retrying %d more time(s))\n%s\n", + api.AzureAdAlCloudAccount.String(), retries, err, + ) + return retry.RetryableError(fmt.Errorf( + "unable to create %s integration (retrying %d more time(s))", + api.AzureAdAlCloudAccount.String(), retries, + )) + } + + integration := response.Data + d.SetId(integration.IntgGuid) + d.Set("name", integration.Name) + d.Set("intg_guid", integration.IntgGuid) + d.Set("enabled", integration.Enabled == 1) + d.Set("created_or_updated_time", integration.CreatedOrUpdatedTime) + d.Set("created_or_updated_by", integration.CreatedOrUpdatedBy) + d.Set("type_name", integration.Type) + d.Set("org_level", integration.IsOrg == 1) + + log.Printf("[INFO] Created %s integration with guid: %v\n", + api.AzureAdAlCloudAccount.String(), integration.IntgGuid) + return nil + }) +} + +func resourceLaceworkIntegrationAzureAdAlRead(d *schema.ResourceData, meta interface{}) error { + lacework := meta.(*api.Client) + + log.Printf("[INFO] Reading %s integration with guid: %v\n", api.AzureAdAlCloudAccount.String(), d.Id()) + response, err := lacework.V2.CloudAccounts.GetAzureAdAl(d.Id()) + if err != nil { + return err + } + + integration := response.Data + if integration.IntgGuid == d.Id() { + d.Set("name", integration.Name) + d.Set("intg_guid", integration.IntgGuid) + d.Set("enabled", integration.Enabled == 1) + d.Set("created_or_updated_time", integration.CreatedOrUpdatedTime) + d.Set("created_or_updated_by", integration.CreatedOrUpdatedBy) + d.Set("type_name", integration.Type) + d.Set("org_level", integration.IsOrg == 1) + + creds := make(map[string]string) + creds["client_id"] = integration.Data.Credentials.ClientID + d.Set("credentials", []map[string]string{creds}) + d.Set("event_hub_namespace", integration.Data.EventHubNamespace) + d.Set("event_hub_name", integration.Data.EventHubName) + d.Set("tenant_id", integration.Data.TenantID) + + log.Printf("[INFO] Read %s integration with guid: %v\n", api.AzureAdAlCloudAccount.String(), integration.IntgGuid) + return nil + } + + d.SetId("") + return nil +} + +func resourceLaceworkIntegrationAzureAdAlUpdate(d *schema.ResourceData, meta interface{}) error { + var ( + lacework = meta.(*api.Client) + azure = api.NewCloudAccount(d.Get("name").(string), + api.AzureAdAlCloudAccount, + api.AzureAdAlData{ + TenantID: d.Get("tenant_id").(string), + EventHubNamespace: d.Get("event_hub_namespace").(string), + EventHubName: d.Get("event_hub_name").(string), + Credentials: api.AzureAdAlCredentials{ + ClientID: d.Get("credentials.0.client_id").(string), + ClientSecret: d.Get("credentials.0.client_secret").(string), + }, + }, + ) + ) + + if !d.Get("enabled").(bool) { + azure.Enabled = 0 + } + + azure.IntgGuid = d.Id() + + log.Printf("[INFO] Updating %s integration with data:\n%+v\n", api.AzureAdAlCloudAccount.String(), azure) + response, err := lacework.V2.CloudAccounts.UpdateAzureAdAl(azure) + if err != nil { + return err + } + + integration := response.Data + d.Set("name", integration.Name) + d.Set("intg_guid", integration.IntgGuid) + d.Set("enabled", integration.Enabled == 1) + d.Set("created_or_updated_time", integration.CreatedOrUpdatedTime) + d.Set("created_or_updated_by", integration.CreatedOrUpdatedBy) + d.Set("type_name", integration.Type) + d.Set("org_level", integration.IsOrg == 1) + + log.Printf("[INFO] Updated %sw integration with guid: %v\n", api.AzureAdAlCloudAccount.String(), d.Id()) + return nil +} + +func resourceLaceworkIntegrationAzureAdAlDelete(d *schema.ResourceData, meta interface{}) error { + lacework := meta.(*api.Client) + + log.Printf("[INFO] Deleting %s integration with guid: %v\n", api.AzureAdAlCloudAccount.String(), d.Id()) + err := lacework.V2.CloudAccounts.Delete(d.Id()) + if err != nil { + return err + } + + log.Printf("[INFO] Deleted %s integration with guid: %v\n", api.AzureAdAlCloudAccount.String(), d.Id()) + return nil +}