From e5ecd21efc4fd62a0d7899feb51398a35bbb60ee Mon Sep 17 00:00:00 2001 From: Manan Bhatia Date: Thu, 15 Aug 2024 16:38:16 -0700 Subject: [PATCH] feat(cli): CLI guided config support for creating Azure AD integration (#1628) --- cli/cmd/generate_azure.go | 111 +++++++++++++++--- cli/cmd/generate_azure_test.go | 12 +- .../lacework_generate_cloud-account_azure.md | 44 +++---- integration/azure_generation_test.go | 57 ++++++--- .../help/generate_cloud-account_azure | 44 +++---- lwgenerate/azure/azure.go | 99 +++++++++++++++- lwgenerate/azure/azure_test.go | 73 ++++++++---- ...ty-log-event-hub-location-and-partition.tf | 32 +++++ .../entra-id-activity-log-existing-ad-app.tf | 25 ++++ ...a-id-activity-log-existing-event-hub-ns.tf | 30 +++++ .../entra-id-activity-log-no-custom-input.tf | 30 +++++ lwgenerate/constants.go | 14 ++- 12 files changed, 461 insertions(+), 110 deletions(-) create mode 100644 lwgenerate/azure/test-data/entra-id-activity-log-event-hub-location-and-partition.tf create mode 100644 lwgenerate/azure/test-data/entra-id-activity-log-existing-ad-app.tf create mode 100644 lwgenerate/azure/test-data/entra-id-activity-log-existing-event-hub-ns.tf create mode 100644 lwgenerate/azure/test-data/entra-id-activity-log-no-custom-input.tf diff --git a/cli/cmd/generate_azure.go b/cli/cmd/generate_azure.go index 893000c8a..34724f39d 100644 --- a/cli/cmd/generate_azure.go +++ b/cli/cmd/generate_azure.go @@ -1,6 +1,7 @@ package cmd import ( + "strconv" "strings" "time" @@ -14,18 +15,24 @@ import ( var ( // Define question text here so they can be reused in testing - QuestionAzureEnableConfig = "Enable Azure configuration integration?" - QuestionAzureConfigName = "Specify custom configuration integration name: (optional)" - QuestionEnableActivityLog = "Enable Azure Activity Log Integration?" - QuestionActivityLogName = "Specify custom Activity Log integration name: (optional)" - QuestionAddAzureSubscriptionID = "Set Azure Subscription ID?" - QuestionAzureSubscriptionID = "Specify the Azure Subscription ID to be used to provision " + + QuestionAzureEnableConfig = "Enable Azure configuration integration?" + QuestionAzureConfigName = "Specify custom configuration integration name: (optional)" + QuestionEnableActivityLog = "Enable Azure Activity Log Integration?" + QuestionActivityLogName = "Specify custom Activity Log integration name: (optional)" + QuestionEnableEntraIdActivityLog = "Enable Azure Entra ID Activity Log Integration?" + QuestionEntraIdActivityLogName = "Specify custom EntraID Activity Log integration name: (optional)" + QuestionAddAzureSubscriptionID = "Set Azure Subscription ID?" + QuestionAzureSubscriptionID = "Specify the Azure Subscription ID to be used to provision " + "Lacework resources: (optional)" QuestionAzureAnotherAdvancedOpt = "Configure another advanced integration option" QuestionAzureConfigAdvanced = "Configure advanced integration options?" QuestionAzureCustomizeOutputLocation = "Provide the location for the output to be written:" + // EntraID Activity Log + QuestionEventHubLocation = "Specify Azure region where the event hub for logging will reside" + QuestionEventHubPartitionCount = "Specify the number of partitions in the event hub for logging" + // Active Directory QuestionEnableAdIntegration = "Create Active Directory Integration?" QuestionADApplicationPass = "Specify the password of an existing Active Directory application" @@ -58,6 +65,7 @@ var ( AzureUserIntegrationNames = "Customize integration name(s)" AzureAdvancedOptLocation = "Customize output location (optional)" AzureRegionStorage = "Customize Azure region for Storage Account (optional)" + AzureEntraIdAdvancedOpt = "Configure Entra ID activity log integration advanced options" GenerateAzureCommandState = &azure.GenerateAzureTfConfigurationArgs{} GenerateAzureCommandExtraState = &AzureGenerateCommandExtraState{} @@ -65,7 +73,7 @@ var ( CachedAzureAssetExtraState = "iac-azure-extra-state" // List of valid Azure Storage locations - validStorageLocations = map[string]bool{ + validAzureLocations = map[string]bool{ "East US": true, "East US 2": true, "South Central US": true, @@ -183,6 +191,9 @@ the new cloud account. In interactive mode, this command will: azure.WithStorageLocation(GenerateAzureCommandState.StorageLocation), azure.WithActivityLogIntegrationName(GenerateAzureCommandState.ActivityLogIntegrationName), azure.WithConfigIntegrationName(GenerateAzureCommandState.ConfigIntegrationName), + azure.WithEntraIdActivityLogIntegrationName(GenerateAzureCommandState.EntraIdIntegrationName), + azure.WithEventHubLocation(GenerateAzureCommandState.EventHubLocation), + azure.WithEventHubPartitionCount(GenerateAzureCommandState.EventHubPartitionCount), } // Check if AD Creation is required, need to set values for current integration @@ -214,6 +225,7 @@ the new cloud account. In interactive mode, this command will: data := azure.NewTerraform( GenerateAzureCommandState.Config, GenerateAzureCommandState.ActivityLog, + GenerateAzureCommandState.EntraIdActivityLog, GenerateAzureCommandState.CreateAdIntegration, mods...) @@ -275,7 +287,7 @@ the new cloud account. In interactive mode, this command will: if err != nil { return errors.Wrap(err, "failed to load command flags") } - if err := validateStorageLocation(storageLocation); storageLocation != "" && err != nil { + if err := validateAzureLocation(storageLocation); storageLocation != "" && err != nil { return err } @@ -346,9 +358,9 @@ func (a *AzureGenerateCommandExtraState) writeCache() { } } -func validateStorageLocation(location string) error { - if !validStorageLocations[location] { - return errors.New("invalid storage location supplied") +func validateAzureLocation(location string) error { + if !validAzureLocations[location] { + return errors.New("invalid Azure region prvovided") } return nil } @@ -359,7 +371,7 @@ func initGenerateAzureTfCommandFlags() { &GenerateAzureCommandState.ActivityLog, "activity_log", false, - "enable active log integration") + "enable activity log integration") generateAzureTfCommand.PersistentFlags().StringVar( &GenerateAzureCommandState.ActivityLogIntegrationName, @@ -367,6 +379,18 @@ func initGenerateAzureTfCommandFlags() { "", "specify a custom activity log integration name") + generateAzureTfCommand.PersistentFlags().BoolVar( + &GenerateAzureCommandState.EntraIdActivityLog, + "entra_id_activity_log", + false, + "enable Entra ID activity log integration") + + generateAzureTfCommand.PersistentFlags().StringVar( + &GenerateAzureCommandState.EntraIdIntegrationName, + "entra_id_activity_log_integration_name", + "", + "specify a custom Entra ID activity log integration name") + generateAzureTfCommand.PersistentFlags().BoolVar( &GenerateAzureCommandState.Config, "configuration", @@ -409,6 +433,18 @@ func initGenerateAzureTfCommandFlags() { false, "use existing storage account") + generateAzureTfCommand.PersistentFlags().StringVar( + &GenerateAzureCommandState.EventHubLocation, + "event_hub_location", + "", + "specify the location where the Event Hub for logging will reside") + + generateAzureTfCommand.PersistentFlags().IntVar( + &GenerateAzureCommandState.EventHubPartitionCount, + "event_hub_partition_count", + 1, + "specify the number of partitions for the Event Hub") + generateAzureTfCommand.PersistentFlags().StringVar( &GenerateAzureCommandState.StorageAccountName, "storage_account_name", @@ -492,6 +528,11 @@ func promptAzureIntegrationNameQuestions(config *azure.GenerateAzureTfConfigurat Checks: []*bool{&config.ActivityLog}, Response: &config.ActivityLogIntegrationName, }, + { + Prompt: &survey.Input{Message: QuestionEntraIdActivityLogName, Default: config.EntraIdIntegrationName}, + Checks: []*bool{&config.EntraIdActivityLog}, + Response: &config.EntraIdIntegrationName, + }, }); err != nil { return err } @@ -526,6 +567,31 @@ func promptAzureStorageAccountQuestions(config *azure.GenerateAzureTfConfigurati return nil } +func promptAzureEntraIdActivityLogQuestions(config *azure.GenerateAzureTfConfigurationArgs) error { + + if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ + { + Prompt: &survey.Input{Message: QuestionEventHubLocation, Default: config.EventHubLocation}, + Required: true, + Response: &config.EventHubLocation, + }, + }); err != nil { + return err + } + + if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ + { + Prompt: &survey.Input{Message: QuestionEventHubPartitionCount, + Default: strconv.Itoa(config.EventHubPartitionCount)}, + Response: &config.EventHubPartitionCount, + }, + }); err != nil { + return err + } + + return nil +} + func promptAzureSubscriptionQuestions(config *azure.GenerateAzureTfConfigurationArgs) error { if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ @@ -609,7 +675,7 @@ func promptCustomizeAzureOutputLocation(extraState *AzureGenerateCommandExtraSta return nil } -func promptCustomizeAzureLoggingRegion(config *azure.GenerateAzureTfConfigurationArgs) error { +func promptCustomizeAzureStorageLoggingRegion(config *azure.GenerateAzureTfConfigurationArgs) error { var region string if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ { @@ -619,7 +685,7 @@ func promptCustomizeAzureLoggingRegion(config *azure.GenerateAzureTfConfiguratio }); err != nil { return err } - if err := validateStorageLocation(region); err != nil { + if err := validateAzureLocation(region); err != nil { return err } config.StorageLocation = region @@ -678,6 +744,11 @@ func askAdvancedAzureOptions( options = append(options, AzureManagmentGroup) } + // Only show Entra ID options in the case of Entra ID integration + if config.EntraIdActivityLog { + options = append(options, AzureEntraIdAdvancedOpt) + } + options = append(options, AzureAdvancedOptLocation, AzureAdvancedOptDone) if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ Prompt: &survey.Select{ @@ -699,6 +770,10 @@ func askAdvancedAzureOptions( if err := promptAzureStorageAccountQuestions(config); err != nil { return err } + case AzureEntraIdAdvancedOpt: + if err := promptAzureEntraIdActivityLogQuestions(config); err != nil { + return err + } case AzureSubscriptions: if err := promptAzureSubscriptionQuestions(config); err != nil { return err @@ -712,7 +787,7 @@ func askAdvancedAzureOptions( return err } case AzureRegionStorage: - if err := promptCustomizeAzureLoggingRegion(config); err != nil { + if err := promptCustomizeAzureStorageLoggingRegion(config); err != nil { return err } case AzureAdvancedOptLocation: @@ -784,12 +859,16 @@ func promptAzureGenerate( Prompt: &survey.Confirm{Message: QuestionEnableAdIntegration, Default: config.CreateAdIntegration}, Response: &config.CreateAdIntegration, }, + { + Prompt: &survey.Confirm{Message: QuestionEnableEntraIdActivityLog, Default: config.EntraIdActivityLog}, + Response: &config.EntraIdActivityLog, + }, }); err != nil { return err } // Validate one of config or activity log was enabled; otherwise error out - if !config.Config && !config.ActivityLog { + if !config.Config && !config.ActivityLog && !config.EntraIdActivityLog { return errors.New("must enable activity log or config") } diff --git a/cli/cmd/generate_azure_test.go b/cli/cmd/generate_azure_test.go index 370e7e5fb..069fbc5f9 100644 --- a/cli/cmd/generate_azure_test.go +++ b/cli/cmd/generate_azure_test.go @@ -42,20 +42,20 @@ func TestMissingValidEntity(t *testing.T) { } func TestValidStorageLocations(t *testing.T) { - err := validateStorageLocation("East US") + err := validateAzureLocation("East US") assert.Nil(t, err) - err = validateStorageLocation("Brazil Southeast") + err = validateAzureLocation("Brazil Southeast") assert.Nil(t, err) } func TestInvalidStorageLocations(t *testing.T) { - err := validateStorageLocation("Mars") + err := validateAzureLocation("Mars") assert.Error(t, err) - assert.Equal(t, "invalid storage location supplied", err.Error()) - err = validateStorageLocation("Jupiter") + assert.Equal(t, "invalid Azure region prvovided", err.Error()) + err = validateAzureLocation("Jupiter") assert.Error(t, err) - assert.Equal(t, "invalid storage location supplied", err.Error()) + assert.Equal(t, "invalid Azure region prvovided", err.Error()) } func TestAzureGenerationCache(t *testing.T) { diff --git a/cli/docs/lacework_generate_cloud-account_azure.md b/cli/docs/lacework_generate_cloud-account_azure.md index ccbd936f3..f9922ade7 100644 --- a/cli/docs/lacework_generate_cloud-account_azure.md +++ b/cli/docs/lacework_generate_cloud-account_azure.md @@ -34,26 +34,30 @@ lacework generate cloud-account azure [flags] ### Options ``` - --activity_log enable active log integration - --activity_log_integration_name string specify a custom activity log integration name - --ad_create create new active directory integration (default true) - --ad_id string existing active directory application id - --ad_pass string existing active directory application password - --ad_pid string existing active directory application service principle id - --all_subscriptions subscription ids grant read access to ALL subscriptions within Tenant (overrides subscription ids) - --apply run terraform apply for the generated hcl - --configuration enable configuration integration - --configuration_name string specify a custom configuration integration name - --existing_storage use existing storage account - -h, --help help for azure - --location string specify azure region where storage account logging resides - --management_group management group level integration - --management_group_id string specify management group id. Required if mgmt_group provided - --output string location to write generated content (default is ~/lacework/azure) - --storage_account_name string specify storage account name - --storage_resource_group string specify storage resource group - --subscription_id string specify the Azure Subscription ID to be used to provision Lacework resources - --subscription_ids strings list of subscriptions to grant read access; format is id1,id2,id3 + --activity_log enable activity log integration + --activity_log_integration_name string specify a custom activity log integration name + --ad_create create new active directory integration (default true) + --ad_id string existing active directory application id + --ad_pass string existing active directory application password + --ad_pid string existing active directory application service principle id + --all_subscriptions subscription ids grant read access to ALL subscriptions within Tenant (overrides subscription ids) + --apply run terraform apply for the generated hcl + --configuration enable configuration integration + --configuration_name string specify a custom configuration integration name + --entra_id_activity_log enable Entra ID activity log integration + --entra_id_activity_log_integration_name string specify a custom Entra ID activity log integration name + --event_hub_location string specify the location where the Event Hub for logging will reside + --event_hub_partition_count int specify the number of partitions for the Event Hub (default 1) + --existing_storage use existing storage account + -h, --help help for azure + --location string specify azure region where storage account logging resides + --management_group management group level integration + --management_group_id string specify management group id. Required if mgmt_group provided + --output string location to write generated content (default is ~/lacework/azure) + --storage_account_name string specify storage account name + --storage_resource_group string specify storage resource group + --subscription_id string specify the Azure Subscription ID to be used to provision Lacework resources + --subscription_ids strings list of subscriptions to grant read access; format is id1,id2,id3 ``` ### Options inherited from parent commands diff --git a/integration/azure_generation_test.go b/integration/azure_generation_test.go index 1dfc2e48f..0e07e99df 100644 --- a/integration/azure_generation_test.go +++ b/integration/azure_generation_test.go @@ -41,6 +41,7 @@ func TestGenerationAzureErrorOnNoSelection(t *testing.T) { MsgRsp{cmd.QuestionAzureEnableConfig, "n"}, MsgRsp{cmd.QuestionEnableActivityLog, "n"}, MsgRsp{cmd.QuestionEnableAdIntegration, "n"}, + MsgRsp{cmd.QuestionEnableEntraIdActivityLog, "n"}, MsgOnly{"ERROR collecting/confirming parameters: must enable activity log or config"}, }) }, @@ -67,6 +68,7 @@ func TestGenerationAzureSimple(t *testing.T) { MsgRsp{cmd.QuestionAzureEnableConfig, "y"}, MsgRsp{cmd.QuestionEnableActivityLog, "y"}, MsgRsp{cmd.QuestionEnableAdIntegration, "y"}, + MsgRsp{cmd.QuestionEnableEntraIdActivityLog, "n"}, MsgRsp{cmd.QuestionAddAzureSubscriptionID, "n"}, MsgRsp{cmd.QuestionAzureConfigAdvanced, "n"}, MsgRsp{cmd.QuestionRunTfPlan, "n"}, @@ -83,7 +85,7 @@ func TestGenerationAzureSimple(t *testing.T) { assert.Contains(t, final, "Terraform code saved in") // Create the TF directly with lwgenerate and validate same result via CLI - buildTf, _ := azure.NewTerraform(true, true, true).Generate() + buildTf, _ := azure.NewTerraform(true, true, false, true).Generate() assert.Equal(t, buildTf, tfResult) } @@ -108,6 +110,7 @@ func TestGenerationAzureCustomizedOutputLocation(t *testing.T) { MsgRsp{cmd.QuestionAzureEnableConfig, "y"}, MsgRsp{cmd.QuestionEnableActivityLog, "y"}, MsgRsp{cmd.QuestionEnableAdIntegration, "y"}, + MsgRsp{cmd.QuestionEnableEntraIdActivityLog, "n"}, MsgRsp{cmd.QuestionAddAzureSubscriptionID, "n"}, MsgRsp{cmd.QuestionAzureConfigAdvanced, "y"}, MsgMenu{cmd.AzureAdvancedOptDone, 5}, @@ -129,7 +132,7 @@ func TestGenerationAzureCustomizedOutputLocation(t *testing.T) { result, _ := os.ReadFile(filepath.FromSlash(fmt.Sprintf("%s/main.tf", dir))) // Create the TF directly with lwgenerate and validate same result via CLI - buildTf, _ := azure.NewTerraform(true, true, true).Generate() + buildTf, _ := azure.NewTerraform(true, true, false, true).Generate() assert.Equal(t, buildTf, string(result)) } @@ -147,6 +150,7 @@ func TestGenerationAzureConfigOnly(t *testing.T) { MsgRsp{cmd.QuestionAzureEnableConfig, "y"}, MsgRsp{cmd.QuestionEnableActivityLog, "n"}, MsgRsp{cmd.QuestionEnableAdIntegration, "y"}, + MsgRsp{cmd.QuestionEnableEntraIdActivityLog, "n"}, MsgRsp{cmd.QuestionAddAzureSubscriptionID, "n"}, MsgRsp{cmd.QuestionAzureConfigAdvanced, "n"}, MsgRsp{cmd.QuestionRunTfPlan, "n"}, @@ -163,7 +167,7 @@ func TestGenerationAzureConfigOnly(t *testing.T) { assert.Contains(t, final, "Terraform code saved in") // Create the TF directly with lwgenerate and validate same result via CLI - buildTf, _ := azure.NewTerraform(true, false, true).Generate() + buildTf, _ := azure.NewTerraform(true, false, false, true).Generate() assert.Equal(t, buildTf, tfResult) } @@ -181,6 +185,7 @@ func TestGenerationAzureActivityLogOnly(t *testing.T) { MsgRsp{cmd.QuestionAzureEnableConfig, "n"}, MsgRsp{cmd.QuestionEnableActivityLog, "y"}, MsgRsp{cmd.QuestionEnableAdIntegration, "y"}, + MsgRsp{cmd.QuestionEnableEntraIdActivityLog, "n"}, MsgRsp{cmd.QuestionAddAzureSubscriptionID, "n"}, MsgRsp{cmd.QuestionAzureConfigAdvanced, "n"}, MsgRsp{cmd.QuestionRunTfPlan, "n"}, @@ -197,7 +202,7 @@ func TestGenerationAzureActivityLogOnly(t *testing.T) { assert.Contains(t, final, "Terraform code saved in") // Create the TF directly with lwgenerate and validate same result via CLI - buildTf, _ := azure.NewTerraform(false, true, true).Generate() + buildTf, _ := azure.NewTerraform(false, true, false, true).Generate() assert.Equal(t, buildTf, tfResult) } @@ -218,6 +223,7 @@ func TestGenerationAzureNoADEnabled(t *testing.T) { MsgRsp{cmd.QuestionAzureEnableConfig, "y"}, MsgRsp{cmd.QuestionEnableActivityLog, "y"}, MsgRsp{cmd.QuestionEnableAdIntegration, "n"}, + MsgRsp{cmd.QuestionEnableEntraIdActivityLog, "n"}, MsgRsp{cmd.QuestionAddAzureSubscriptionID, "n"}, MsgRsp{cmd.QuestionAzureConfigAdvanced, "y"}, MsgMenu{cmd.AzureAdvancedOptLocation, 2}, @@ -239,7 +245,7 @@ func TestGenerationAzureNoADEnabled(t *testing.T) { assert.Contains(t, final, "Terraform code saved in") // Create the TF directly with lwgenerate and validate same result via CLI - buildTf, _ := azure.NewTerraform(true, true, false, + buildTf, _ := azure.NewTerraform(true, true, false, false, azure.WithAdApplicationPassword(pass), azure.WithAdServicePrincipalId(principalId), azure.WithAdApplicationId(applicationId), @@ -263,6 +269,7 @@ func _TestGenerationAzureNamedConfig(t *testing.T) { MsgRsp{cmd.QuestionAzureEnableConfig, "y"}, MsgRsp{cmd.QuestionEnableActivityLog, "n"}, MsgRsp{cmd.QuestionEnableAdIntegration, "y"}, + MsgRsp{cmd.QuestionEnableEntraIdActivityLog, "n"}, MsgRsp{cmd.QuestionAddAzureSubscriptionID, "n"}, MsgRsp{cmd.QuestionAzureConfigAdvanced, "y"}, @@ -285,7 +292,7 @@ func _TestGenerationAzureNamedConfig(t *testing.T) { assert.Contains(t, final, "Terraform code saved in") // Create the TF directly with lwgenerate and validate same result via CLI - buildTf, _ := azure.NewTerraform(true, false, true, + buildTf, _ := azure.NewTerraform(true, false, false, true, azure.WithConfigIntegrationName(configName), ).Generate() assert.Equal(t, buildTf, tfResult) @@ -307,6 +314,7 @@ func _TestGenerationAzureNamedActivityLog(t *testing.T) { MsgRsp{cmd.QuestionAzureEnableConfig, "n"}, MsgRsp{cmd.QuestionEnableActivityLog, "y"}, MsgRsp{cmd.QuestionEnableAdIntegration, "y"}, + MsgRsp{cmd.QuestionEnableEntraIdActivityLog, "n"}, MsgRsp{cmd.QuestionAddAzureSubscriptionID, "n"}, MsgRsp{cmd.QuestionAzureConfigAdvanced, "y"}, @@ -329,7 +337,7 @@ func _TestGenerationAzureNamedActivityLog(t *testing.T) { assert.Contains(t, final, "Terraform code saved in") // Create the TF directly with lwgenerate and validate same result via CLI - buildTf, _ := azure.NewTerraform(false, true, true, + buildTf, _ := azure.NewTerraform(false, true, false, true, azure.WithActivityLogIntegrationName(activityName)).Generate() assert.Equal(t, buildTf, tfResult) } @@ -348,6 +356,7 @@ func TestGenerationAzureAdvancedOptsDone(t *testing.T) { MsgRsp{cmd.QuestionAzureEnableConfig, "y"}, MsgRsp{cmd.QuestionEnableActivityLog, "y"}, MsgRsp{cmd.QuestionEnableAdIntegration, "y"}, + MsgRsp{cmd.QuestionEnableEntraIdActivityLog, "n"}, MsgRsp{cmd.QuestionAddAzureSubscriptionID, "n"}, MsgRsp{cmd.QuestionAzureConfigAdvanced, "y"}, @@ -366,7 +375,7 @@ func TestGenerationAzureAdvancedOptsDone(t *testing.T) { assert.Contains(t, final, "Terraform code saved in") // Create the TF directly with lwgenerate and validate same result via CLI - buildTf, _ := azure.NewTerraform(true, true, true).Generate() + buildTf, _ := azure.NewTerraform(true, true, false, true).Generate() assert.Equal(t, buildTf, tfResult) } @@ -396,6 +405,7 @@ func TestGenerationAzureWithExistingTerraform(t *testing.T) { MsgRsp{cmd.QuestionAzureEnableConfig, "y"}, MsgRsp{cmd.QuestionEnableActivityLog, "y"}, MsgRsp{cmd.QuestionEnableAdIntegration, "y"}, + MsgRsp{cmd.QuestionEnableEntraIdActivityLog, "n"}, MsgRsp{cmd.QuestionAddAzureSubscriptionID, "n"}, MsgRsp{cmd.QuestionAzureConfigAdvanced, "y"}, MsgMenu{cmd.AzureAdvancedOptDone, 5}, @@ -435,6 +445,7 @@ func TestGenerationAzureConfigAllSubs(t *testing.T) { MsgRsp{cmd.QuestionAzureEnableConfig, "y"}, MsgRsp{cmd.QuestionEnableActivityLog, "n"}, MsgRsp{cmd.QuestionEnableAdIntegration, "y"}, + MsgRsp{cmd.QuestionEnableEntraIdActivityLog, "n"}, MsgRsp{cmd.QuestionAddAzureSubscriptionID, "n"}, MsgRsp{cmd.QuestionAzureConfigAdvanced, "y"}, MsgMenu{cmd.AzureAdvancedOptDone, 1}, @@ -455,7 +466,7 @@ func TestGenerationAzureConfigAllSubs(t *testing.T) { assert.Contains(t, final, "Terraform code saved in") // Create the TF directly with lwgenerate and validate same result via CLI - buildTf, _ := azure.NewTerraform(true, false, true, + buildTf, _ := azure.NewTerraform(true, false, false, true, azure.WithAllSubscriptions(true), ).Generate() assert.Equal(t, buildTf, tfResult) @@ -476,6 +487,7 @@ func TestGenerationAzureConfigMgmntGroup(t *testing.T) { MsgRsp{cmd.QuestionAzureEnableConfig, "y"}, MsgRsp{cmd.QuestionEnableActivityLog, "n"}, MsgRsp{cmd.QuestionEnableAdIntegration, "y"}, + MsgRsp{cmd.QuestionEnableEntraIdActivityLog, "n"}, MsgRsp{cmd.QuestionAddAzureSubscriptionID, "n"}, MsgRsp{cmd.QuestionAzureConfigAdvanced, "y"}, @@ -498,7 +510,7 @@ func TestGenerationAzureConfigMgmntGroup(t *testing.T) { assert.Contains(t, final, "Terraform code saved in") // Create the TF directly with lwgenerate and validate same result via CLI - buildTf, _ := azure.NewTerraform(true, false, true, + buildTf, _ := azure.NewTerraform(true, false, false, true, azure.WithManagementGroup(true), azure.WithManagementGroupId(mgmtGrpId), ).Generate() @@ -520,6 +532,7 @@ func TestGenerationAzureConfigSubs(t *testing.T) { MsgRsp{cmd.QuestionAzureEnableConfig, "y"}, MsgRsp{cmd.QuestionEnableActivityLog, "n"}, MsgRsp{cmd.QuestionEnableAdIntegration, "y"}, + MsgRsp{cmd.QuestionEnableEntraIdActivityLog, "n"}, MsgRsp{cmd.QuestionAddAzureSubscriptionID, "n"}, MsgRsp{cmd.QuestionAzureConfigAdvanced, "y"}, @@ -543,7 +556,7 @@ func TestGenerationAzureConfigSubs(t *testing.T) { assert.Contains(t, final, "Terraform code saved in") // Create the TF directly with lwgenerate and validate same result via CLI - buildTf, _ := azure.NewTerraform(true, false, true, + buildTf, _ := azure.NewTerraform(true, false, false, true, azure.WithSubscriptionIds(testIds), ).Generate() assert.Equal(t, buildTf, tfResult) @@ -564,6 +577,7 @@ func TestGenerationAzureActivityLogSubs(t *testing.T) { MsgRsp{cmd.QuestionAzureEnableConfig, "n"}, MsgRsp{cmd.QuestionEnableActivityLog, "y"}, MsgRsp{cmd.QuestionEnableAdIntegration, "y"}, + MsgRsp{cmd.QuestionEnableEntraIdActivityLog, "n"}, MsgRsp{cmd.QuestionAddAzureSubscriptionID, "n"}, MsgRsp{cmd.QuestionAzureConfigAdvanced, "y"}, @@ -587,7 +601,7 @@ func TestGenerationAzureActivityLogSubs(t *testing.T) { assert.Contains(t, final, "Terraform code saved in") // Create the TF directly with lwgenerate and validate same result via CLI - buildTf, _ := azure.NewTerraform(false, true, true, + buildTf, _ := azure.NewTerraform(false, true, false, true, azure.WithSubscriptionIds(testIds), ).Generate() assert.Equal(t, buildTf, tfResult) @@ -609,6 +623,7 @@ func TestGenerationAzureActivityLogStorageAccount(t *testing.T) { MsgRsp{cmd.QuestionAzureEnableConfig, "n"}, MsgRsp{cmd.QuestionEnableActivityLog, "y"}, MsgRsp{cmd.QuestionEnableAdIntegration, "y"}, + MsgRsp{cmd.QuestionEnableEntraIdActivityLog, "n"}, MsgRsp{cmd.QuestionAddAzureSubscriptionID, "n"}, MsgRsp{cmd.QuestionAzureConfigAdvanced, "y"}, @@ -634,7 +649,7 @@ func TestGenerationAzureActivityLogStorageAccount(t *testing.T) { assert.Contains(t, final, "Terraform code saved in") // Create the TF directly with lwgenerate and validate same result via CLI - buildTf, _ := azure.NewTerraform(false, true, true, + buildTf, _ := azure.NewTerraform(false, true, false, true, azure.WithExistingStorageAccount(true), azure.WithStorageAccountName(storageAccountName), azure.WithStorageAccountResourceGroup(storageResourceGrp), @@ -656,6 +671,7 @@ func TestGenerationAzureActivityLogAllSubs(t *testing.T) { MsgRsp{cmd.QuestionAzureEnableConfig, "n"}, MsgRsp{cmd.QuestionEnableActivityLog, "y"}, MsgRsp{cmd.QuestionEnableAdIntegration, "y"}, + MsgRsp{cmd.QuestionEnableEntraIdActivityLog, "n"}, MsgRsp{cmd.QuestionAddAzureSubscriptionID, "n"}, MsgRsp{cmd.QuestionAzureConfigAdvanced, "y"}, @@ -678,7 +694,7 @@ func TestGenerationAzureActivityLogAllSubs(t *testing.T) { assert.Contains(t, final, "Terraform code saved in") // Create the TF directly with lwgenerate and validate same result via CLI - buildTf, _ := azure.NewTerraform(false, true, true, + buildTf, _ := azure.NewTerraform(false, true, false, true, azure.WithAllSubscriptions(true), ).Generate() assert.Equal(t, buildTf, tfResult) @@ -699,6 +715,7 @@ func TestGenerationAzureActivityLogLocation(t *testing.T) { MsgRsp{cmd.QuestionAzureEnableConfig, "n"}, MsgRsp{cmd.QuestionEnableActivityLog, "y"}, MsgRsp{cmd.QuestionEnableAdIntegration, "y"}, + MsgRsp{cmd.QuestionEnableEntraIdActivityLog, "n"}, MsgRsp{cmd.QuestionAddAzureSubscriptionID, "n"}, MsgRsp{cmd.QuestionAzureConfigAdvanced, "y"}, @@ -721,7 +738,7 @@ func TestGenerationAzureActivityLogLocation(t *testing.T) { assert.Contains(t, final, "Terraform code saved in") // Create the TF directly with lwgenerate and validate same result via CLI - buildTf, _ := azure.NewTerraform(false, true, true, + buildTf, _ := azure.NewTerraform(false, true, false, true, azure.WithStorageLocation(region), ).Generate() assert.Equal(t, buildTf, tfResult) @@ -745,6 +762,7 @@ func TestGenerationAzureOverwrite(t *testing.T) { MsgRsp{cmd.QuestionAzureEnableConfig, "y"}, MsgRsp{cmd.QuestionEnableActivityLog, "y"}, MsgRsp{cmd.QuestionEnableAdIntegration, "y"}, + MsgRsp{cmd.QuestionEnableEntraIdActivityLog, "n"}, MsgRsp{cmd.QuestionAddAzureSubscriptionID, "n"}, MsgRsp{cmd.QuestionAzureConfigAdvanced, "n"}, MsgRsp{cmd.QuestionRunTfPlan, "n"}, @@ -764,6 +782,7 @@ func TestGenerationAzureOverwrite(t *testing.T) { MsgRsp{cmd.QuestionAzureEnableConfig, "y"}, MsgRsp{cmd.QuestionEnableActivityLog, "y"}, MsgRsp{cmd.QuestionEnableAdIntegration, "y"}, + MsgRsp{cmd.QuestionEnableEntraIdActivityLog, "n"}, MsgRsp{cmd.QuestionAddAzureSubscriptionID, "n"}, MsgRsp{cmd.QuestionAzureConfigAdvanced, "n"}, MsgRsp{"already exists, overwrite?", "n"}, @@ -800,6 +819,7 @@ func TestGenerationAzureOverwriteOutput(t *testing.T) { MsgRsp{cmd.QuestionAzureEnableConfig, "y"}, MsgRsp{cmd.QuestionEnableActivityLog, "y"}, MsgRsp{cmd.QuestionEnableAdIntegration, "y"}, + MsgRsp{cmd.QuestionEnableEntraIdActivityLog, "n"}, MsgRsp{cmd.QuestionAddAzureSubscriptionID, "n"}, MsgRsp{cmd.QuestionAzureConfigAdvanced, "n"}, MsgRsp{cmd.QuestionRunTfPlan, "n"}, @@ -821,6 +841,7 @@ func TestGenerationAzureOverwriteOutput(t *testing.T) { MsgRsp{cmd.QuestionAzureEnableConfig, "y"}, MsgRsp{cmd.QuestionEnableActivityLog, "y"}, MsgRsp{cmd.QuestionEnableAdIntegration, "y"}, + MsgRsp{cmd.QuestionEnableEntraIdActivityLog, "n"}, MsgRsp{cmd.QuestionAddAzureSubscriptionID, "n"}, MsgRsp{cmd.QuestionAzureConfigAdvanced, "n"}, MsgRsp{"already exists, overwrite?", "n"}, @@ -851,6 +872,7 @@ func TestGenerationAzureLaceworkProfile(t *testing.T) { MsgRsp{cmd.QuestionAzureEnableConfig, "y"}, MsgRsp{cmd.QuestionEnableActivityLog, "y"}, MsgRsp{cmd.QuestionEnableAdIntegration, "y"}, + MsgRsp{cmd.QuestionEnableEntraIdActivityLog, "n"}, MsgRsp{cmd.QuestionAddAzureSubscriptionID, "n"}, MsgRsp{cmd.QuestionAzureConfigAdvanced, "n"}, MsgRsp{cmd.QuestionRunTfPlan, "n"}, @@ -867,7 +889,7 @@ func TestGenerationAzureLaceworkProfile(t *testing.T) { assert.Nil(t, runError) assert.Contains(t, final, "Terraform code saved in") - buildTf, _ := azure.NewTerraform(true, true, true, + buildTf, _ := azure.NewTerraform(true, true, false, true, azure.WithLaceworkProfile(azProfile), ).Generate() assert.Equal(t, buildTf, tfResult) @@ -887,6 +909,7 @@ func TestGenerationAzureWithSubscriptionID(t *testing.T) { MsgRsp{cmd.QuestionAzureEnableConfig, "y"}, MsgRsp{cmd.QuestionEnableActivityLog, "y"}, MsgRsp{cmd.QuestionEnableAdIntegration, "y"}, + MsgRsp{cmd.QuestionEnableEntraIdActivityLog, "n"}, MsgRsp{cmd.QuestionAddAzureSubscriptionID, "y"}, MsgRsp{cmd.QuestionAzureSubscriptionID, mockSubscriptionID}, MsgRsp{cmd.QuestionAzureConfigAdvanced, "n"}, @@ -904,7 +927,7 @@ func TestGenerationAzureWithSubscriptionID(t *testing.T) { assert.Contains(t, final, "Terraform code saved in") // Create the TF directly with lwgenerate and validate same result via CLI - buildTf, _ := azure.NewTerraform(true, true, true, azure.WithSubscriptionID(mockSubscriptionID)).Generate() + buildTf, _ := azure.NewTerraform(true, true, false, true, azure.WithSubscriptionID(mockSubscriptionID)).Generate() assert.Equal(t, buildTf, tfResult) } diff --git a/integration/test_resources/help/generate_cloud-account_azure b/integration/test_resources/help/generate_cloud-account_azure index fe6401076..fc59264c3 100644 --- a/integration/test_resources/help/generate_cloud-account_azure +++ b/integration/test_resources/help/generate_cloud-account_azure @@ -21,26 +21,30 @@ Aliases: azure, az Flags: - --activity_log enable active log integration - --activity_log_integration_name string specify a custom activity log integration name - --ad_create create new active directory integration (default true) - --ad_id string existing active directory application id - --ad_pass string existing active directory application password - --ad_pid string existing active directory application service principle id - --all_subscriptions subscription ids grant read access to ALL subscriptions within Tenant (overrides subscription ids) - --apply run terraform apply for the generated hcl - --configuration enable configuration integration - --configuration_name string specify a custom configuration integration name - --existing_storage use existing storage account - -h, --help help for azure - --location string specify azure region where storage account logging resides - --management_group management group level integration - --management_group_id string specify management group id. Required if mgmt_group provided - --output string location to write generated content (default is ~/lacework/azure) - --storage_account_name string specify storage account name - --storage_resource_group string specify storage resource group - --subscription_id string specify the Azure Subscription ID to be used to provision Lacework resources - --subscription_ids strings list of subscriptions to grant read access; format is id1,id2,id3 + --activity_log enable activity log integration + --activity_log_integration_name string specify a custom activity log integration name + --ad_create create new active directory integration (default true) + --ad_id string existing active directory application id + --ad_pass string existing active directory application password + --ad_pid string existing active directory application service principle id + --all_subscriptions subscription ids grant read access to ALL subscriptions within Tenant (overrides subscription ids) + --apply run terraform apply for the generated hcl + --configuration enable configuration integration + --configuration_name string specify a custom configuration integration name + --entra_id_activity_log enable Entra ID activity log integration + --entra_id_activity_log_integration_name string specify a custom Entra ID activity log integration name + --event_hub_location string specify the location where the Event Hub for logging will reside + --event_hub_partition_count int specify the number of partitions for the Event Hub (default 1) + --existing_storage use existing storage account + -h, --help help for azure + --location string specify azure region where storage account logging resides + --management_group management group level integration + --management_group_id string specify management group id. Required if mgmt_group provided + --output string location to write generated content (default is ~/lacework/azure) + --storage_account_name string specify storage account name + --storage_resource_group string specify storage resource group + --subscription_id string specify the Azure Subscription ID to be used to provision Lacework resources + --subscription_ids strings list of subscriptions to grant read access; format is id1,id2,id3 Global Flags: -a, --account string account subdomain of URL (i.e. .lacework.net) diff --git a/lwgenerate/azure/azure.go b/lwgenerate/azure/azure.go index 4cb1d23c5..e1fb2f987 100644 --- a/lwgenerate/azure/azure.go +++ b/lwgenerate/azure/azure.go @@ -14,6 +14,9 @@ type GenerateAzureTfConfigurationArgs struct { // Should we add Config integration in LW? Config bool + // Should we create an Entra ID integration in LW? + EntraIdActivityLog bool + // Should we create an Active Directory integration CreateAdIntegration bool @@ -23,6 +26,10 @@ type GenerateAzureTfConfigurationArgs struct { // If ActivityLog is true, give the user the opportunity to name their integration. Defaults to "TF activity log" ActivityLogIntegrationName string + // If EntraIdIntegration is true, give the user the opportunity to name their integration. + // Defaults to "TF Entra ID activity log" + EntraIdIntegrationName string + // Active Directory application Id AdApplicationId string @@ -61,6 +68,12 @@ type GenerateAzureTfConfigurationArgs struct { LaceworkProfile string + // Azure region where the event hub for logging will reside + EventHubLocation string + + // Number of partitions in the Event Hub for logging + EventHubPartitionCount int + // Add custom blocks to the root `terraform{}` block. Can be used for advanced configuration. Things like backend, etc ExtraBlocksRootTerraform []*hclwrite.Block @@ -80,7 +93,7 @@ type GenerateAzureTfConfigurationArgs struct { // Ensure all combinations of inputs are valid for supported spec func (args *GenerateAzureTfConfigurationArgs) validate() error { // Validate one of config or activity log was enabled; otherwise error out - if !args.ActivityLog && !args.Config { + if !args.ActivityLog && !args.Config && !args.EntraIdActivityLog { return errors.New("audit log or config integration must be enabled") } @@ -110,11 +123,13 @@ type AzureTerraformModifier func(c *GenerateAzureTfConfigurationArgs) // // Note: Additional configuration details may be set using modifiers of the AzureTerraformModifier type func NewTerraform( - enableConfig bool, enableActivityLog bool, createAdIntegration bool, mods ...AzureTerraformModifier, + enableConfig bool, enableActivityLog bool, enableEntraIdActivityLog, createAdIntegration bool, + mods ...AzureTerraformModifier, ) *GenerateAzureTfConfigurationArgs { config := &GenerateAzureTfConfigurationArgs{ ActivityLog: enableActivityLog, Config: enableConfig, + EntraIdActivityLog: enableEntraIdActivityLog, CreateAdIntegration: createAdIntegration, } for _, m := range mods { @@ -175,6 +190,14 @@ func WithActivityLogIntegrationName(name string) AzureTerraformModifier { } } +// WithEntraIdActivityLogIntegrationName Set the Entra ID Activity Log Integration name +// to be displayed on the Lacework UI +func WithEntraIdActivityLogIntegrationName(name string) AzureTerraformModifier { + return func(c *GenerateAzureTfConfigurationArgs) { + c.EntraIdIntegrationName = name + } +} + // WithAdApplicationId Set Active Directory application id func WithAdApplicationId(AdApplicationId string) AzureTerraformModifier { return func(c *GenerateAzureTfConfigurationArgs) { @@ -254,6 +277,20 @@ func WithStorageLocation(location string) AzureTerraformModifier { } } +// WithEventHubLocation The Azure region where the event hub for logging resides +func WithEventHubLocation(location string) AzureTerraformModifier { + return func(c *GenerateAzureTfConfigurationArgs) { + c.EventHubLocation = location + } +} + +// WitthEventHubPartitionCount The number of partitions in the Event Hub for logging +func WithEventHubPartitionCount(partitionCount int) AzureTerraformModifier { + return func(c *GenerateAzureTfConfigurationArgs) { + c.EventHubPartitionCount = partitionCount + } +} + func WithLaceworkProfile(name string) AzureTerraformModifier { return func(c *GenerateAzureTfConfigurationArgs) { c.LaceworkProfile = name @@ -309,6 +346,11 @@ func (args *GenerateAzureTfConfigurationArgs) Generate() (string, error) { return "", errors.Wrap(err, "failed to generate azure activity log module") } + entraIdActivityLogModule, err := createEntraIdActivityLog(args) + if err != nil { + return "", errors.Wrap(err, "failed to generate azure Entra ID activity log module") + } + outputBlocks := []*hclwrite.Block{} for _, output := range args.CustomOutputs { outputBlock, err := output.ToBlock() @@ -328,6 +370,7 @@ func (args *GenerateAzureTfConfigurationArgs) Generate() (string, error) { laceworkADProvider, configModule, activityLogModule, + entraIdActivityLogModule, outputBlocks, args.ExtraBlocks), ) @@ -574,3 +617,55 @@ func createActivityLog(args *GenerateAzureTfConfigurationArgs) ([]*hclwrite.Bloc } return blocks, nil } + +func createEntraIdActivityLog(args *GenerateAzureTfConfigurationArgs) ([]*hclwrite.Block, error) { + blocks := []*hclwrite.Block{} + if args.EntraIdActivityLog { + attributes := map[string]interface{}{} + moduleDetails := []lwgenerate.HclModuleModifier{} + + if args.EntraIdIntegrationName != "" { + attributes["lacework_integration_name"] = args.EntraIdIntegrationName + } + + // Check if we have created an Active Directory integration + if args.CreateAdIntegration { + attributes["use_existing_ad_application"] = false + attributes["application_id"] = lwgenerate.CreateSimpleTraversal( + []string{"module", "az_ad_application", "application_id"}) + attributes["application_password"] = lwgenerate.CreateSimpleTraversal( + []string{"module", "az_ad_application", "application_password"}) + attributes["service_principal_id"] = lwgenerate.CreateSimpleTraversal( + []string{"module", "az_ad_application", "service_principal_id"}) + } else { + attributes["use_existing_ad_application"] = true + attributes["application_id"] = args.AdApplicationId + attributes["application_password"] = args.AdApplicationPassword + attributes["service_principal_id"] = args.AdServicePrincipalId + } + + if args.EventHubLocation != "" { + attributes["location"] = args.EventHubLocation + } + + if args.EventHubPartitionCount > 0 { + attributes["num_partitions"] = args.EventHubPartitionCount + } + + moduleDetails = append(moduleDetails, + lwgenerate.HclModuleWithAttributes(attributes), + ) + + moduleBlock, err := lwgenerate.NewModule( + "microsoft-entra-id-activity-log", + lwgenerate.LWAzureEntraIdActivityLogSource, + append(moduleDetails, lwgenerate.HclModuleWithVersion(lwgenerate.LWAzureEntraIdActivityLogVersion))..., + ).ToBlock() + + if err != nil { + return nil, err + } + blocks = append(blocks, moduleBlock) + } + return blocks, nil +} diff --git a/lwgenerate/azure/azure_test.go b/lwgenerate/azure/azure_test.go index ae153ace0..fab832a66 100644 --- a/lwgenerate/azure/azure_test.go +++ b/lwgenerate/azure/azure_test.go @@ -22,7 +22,7 @@ func getFileContent(filename string) (string, error) { func TestGenerationActivityLogWithoutConfig(t *testing.T) { ActivityLogWithoutConfig, fileErr := getFileContent("test-data/activity_log_without_config.tf") assert.Nil(t, fileErr) - hcl, err := azure.NewTerraform(false, true, true).Generate() + hcl, err := azure.NewTerraform(false, true, false, true).Generate() assert.Nil(t, err) assert.NotNil(t, hcl) assert.Equal(t, ActivityLogWithoutConfig, hcl) @@ -31,7 +31,7 @@ func TestGenerationActivityLogWithoutConfig(t *testing.T) { func TestGenerationActivityLogWithConfig(t *testing.T) { var ActivityLogWithConfig, fileErr = getFileContent("test-data/activity_log_with_config.tf") assert.Nil(t, fileErr) - hcl, err := azure.NewTerraform(true, true, true).Generate() + hcl, err := azure.NewTerraform(true, true, false, true).Generate() assert.Nil(t, err) assert.NotNil(t, hcl) assert.Equal(t, ActivityLogWithConfig, hcl) @@ -43,7 +43,7 @@ func TestGenerationActivityLogWithConfigAndExtraBlocks(t *testing.T) { assert.Nil(t, fileErr) extraBlock, err := lwgenerate.HclCreateGenericBlock("variable", []string{"var_name"}, nil) assert.NoError(t, err) - hcl, err := azure.NewTerraform(true, true, true, azure.WithExtraBlocks([]*hclwrite.Block{extraBlock})).Generate() + hcl, err := azure.NewTerraform(true, true, false, true, azure.WithExtraBlocks([]*hclwrite.Block{extraBlock})).Generate() assert.Nil(t, err) assert.NotNil(t, hcl) assert.Equal(t, ActivityLogWithConfig, hcl) @@ -52,7 +52,7 @@ func TestGenerationActivityLogWithConfigAndExtraBlocks(t *testing.T) { func TestGenerationActivityLogWithConfigAndExtraAzureRMProviderBlocks(t *testing.T) { var ActivityLogWithConfig, fileErr = getFileContent("test-data/activity_log_with_config_provider_args.tf") assert.Nil(t, fileErr) - hcl, err := azure.NewTerraform(true, true, true, azure.WithExtraAZRMArguments(map[string]interface{}{"foo": "bar"})).Generate() + hcl, err := azure.NewTerraform(true, true, false, true, azure.WithExtraAZRMArguments(map[string]interface{}{"foo": "bar"})).Generate() assert.Nil(t, err) assert.NotNil(t, hcl) assert.Equal(t, ActivityLogWithConfig, hcl) @@ -61,7 +61,7 @@ func TestGenerationActivityLogWithConfigAndExtraAzureRMProviderBlocks(t *testing func TestGenerationActivityLogWithConfigAndExtraAZUReadProviderBlocks(t *testing.T) { var ActivityLogWithConfig, fileErr = getFileContent("test-data/activity_log_with_config_azureadprovider_args.tf") assert.Nil(t, fileErr) - hcl, err := azure.NewTerraform(true, true, true, azure.WithExtraAZReadArguments(map[string]interface{}{"foo": "bar"})).Generate() + hcl, err := azure.NewTerraform(true, true, false, true, azure.WithExtraAZReadArguments(map[string]interface{}{"foo": "bar"})).Generate() assert.Nil(t, err) assert.NotNil(t, hcl) assert.Equal(t, ActivityLogWithConfig, hcl) @@ -72,7 +72,7 @@ func TestGenerationActivityLogWithConfigAndCustomBackendBlock(t *testing.T) { assert.NoError(t, err) var ActivityLogWithConfig, fileErr = getFileContent("test-data/activity_log_with_config_root_blocks.tf") assert.Nil(t, fileErr) - hcl, err := azure.NewTerraform(true, true, true, azure.WithExtraRootBlocks([]*hclwrite.Block{customBlock})).Generate() + hcl, err := azure.NewTerraform(true, true, false, true, azure.WithExtraRootBlocks([]*hclwrite.Block{customBlock})).Generate() assert.Nil(t, err) assert.NotNil(t, hcl) assert.Equal(t, ActivityLogWithConfig, hcl) @@ -81,14 +81,14 @@ func TestGenerationActivityLogWithConfigAndCustomBackendBlock(t *testing.T) { func TestGenerationConfigWithoutActivityLog(t *testing.T) { ConfigWithoutActivityLog, fileErr := getFileContent("test-data/config_without_activity_log.tf") assert.Nil(t, fileErr) - hcl, err := azure.NewTerraform(true, false, true).Generate() + hcl, err := azure.NewTerraform(true, false, false, true).Generate() assert.Nil(t, err) assert.NotNil(t, hcl) assert.Equal(t, ConfigWithoutActivityLog, hcl) } func TestGenerationWithoutActivityLogOrConfig(t *testing.T) { - hcl, err := azure.NewTerraform(false, false, true).Generate() + hcl, err := azure.NewTerraform(false, false, false, true).Generate() assert.NotNil(t, err) assert.True(t, strings.Contains(errors.Unwrap(err).Error(), "invalid inputs")) assert.Empty(t, hcl) @@ -96,7 +96,7 @@ func TestGenerationWithoutActivityLogOrConfig(t *testing.T) { func TestGenerationRenamedConfig(t *testing.T) { RenamedConfig, fileErr := getFileContent("test-data/renamed_config.tf") assert.Nil(t, fileErr) - hcl, err := azure.NewTerraform(true, false, true, + hcl, err := azure.NewTerraform(true, false, false, true, azure.WithConfigIntegrationName("Test Config Rename"), ).Generate() assert.Nil(t, err) @@ -107,7 +107,7 @@ func TestGenerationRenamedConfig(t *testing.T) { func TestGenerationRenamedActivityLog(t *testing.T) { RenamedActivityLog, fileErr := getFileContent("test-data/renamed_activity_log.tf") assert.Nil(t, fileErr) - hcl, err := azure.NewTerraform(false, true, true, + hcl, err := azure.NewTerraform(false, true, false, true, azure.WithActivityLogIntegrationName("Test Activity Log Rename"), ).Generate() assert.Nil(t, err) @@ -118,7 +118,7 @@ func TestGenerationRenamedActivityLog(t *testing.T) { func TestGenerationRenamedConfigAndActivityLog(t *testing.T) { RenamedConfigAndActivityLog, fileErr := getFileContent("test-data/renamed_config_and_activity_log.tf") assert.Nil(t, fileErr) - hcl, err := azure.NewTerraform(true, true, true, + hcl, err := azure.NewTerraform(true, true, false, true, azure.WithConfigIntegrationName("Test Config Rename"), azure.WithActivityLogIntegrationName("Test Activity Log Rename"), ).Generate() @@ -128,7 +128,7 @@ func TestGenerationRenamedConfigAndActivityLog(t *testing.T) { } func TestGenerationNoActiveDirectorySettings(t *testing.T) { - hcl, err := azure.NewTerraform(true, true, false, + hcl, err := azure.NewTerraform(true, true, false, false, azure.WithConfigIntegrationName("Test Config Rename"), azure.WithActivityLogIntegrationName("Test Activity Log Rename"), ).Generate() @@ -139,7 +139,7 @@ func TestGenerationNoActiveDirectorySettings(t *testing.T) { func TestGenerationCustomActiveDirectory(t *testing.T) { CustomADDetails, fileErr := getFileContent("test-data/customer-ad-details.tf") assert.Nil(t, fileErr) - hcl, err := azure.NewTerraform(true, true, false, + hcl, err := azure.NewTerraform(true, true, false, false, azure.WithConfigIntegrationName("Test Config Rename"), azure.WithActivityLogIntegrationName("Test Activity Log Rename"), azure.WithAdApplicationPassword("AD-Test-Password"), @@ -154,7 +154,7 @@ func TestGenerationCustomActiveDirectory(t *testing.T) { func TestGenerationActivityLogWithExistingStorageAccount(t *testing.T) { ActivityLogWithStorage, fileErr := getFileContent("test-data/activity-log-with-existing-storage.tf") assert.Nil(t, fileErr) - hcl, err := azure.NewTerraform(false, true, true, + hcl, err := azure.NewTerraform(false, true, false, true, azure.WithExistingStorageAccount(true), azure.WithStorageAccountName("Test-Storage-Account-Name"), azure.WithStorageAccountResourceGroup("Test-Storage-Account-Resource-Group"), @@ -167,7 +167,7 @@ func TestGenerationActivityLogWithExistingStorageAccount(t *testing.T) { func TestGenerationActivityLogWithAllSubscriptions(t *testing.T) { ActivityLogAllSubs, fileErr := getFileContent("test-data/activity-log-with-all-subscriptions.tf") assert.Nil(t, fileErr) - hcl, err := azure.NewTerraform(false, true, true, + hcl, err := azure.NewTerraform(false, true, false, true, azure.WithAllSubscriptions(true), ).Generate() assert.Nil(t, err) @@ -178,7 +178,7 @@ func TestGenerationActivityLogWithAllSubscriptions(t *testing.T) { func TestGenerationConfigWithAllSubscriptions(t *testing.T) { ConfigAllSubs, fileErr := getFileContent("test-data/config-with-all-subscriptions.tf") assert.Nil(t, fileErr) - hcl, err := azure.NewTerraform(true, false, true, + hcl, err := azure.NewTerraform(true, false, false, true, azure.WithAllSubscriptions(true), ).Generate() assert.Nil(t, err) @@ -189,7 +189,7 @@ func TestGenerationConfigWithAllSubscriptions(t *testing.T) { func TestGenerationConfigWithManagementGroup(t *testing.T) { ConfigWithMgmtGroup, fileErr := getFileContent("test-data/config-with-management-group.tf") assert.Nil(t, fileErr) - hcl, err := azure.NewTerraform(true, false, true, + hcl, err := azure.NewTerraform(true, false, false, true, azure.WithManagementGroup(true), azure.WithManagementGroupId("test-management-group-1"), ).Generate() @@ -199,7 +199,7 @@ func TestGenerationConfigWithManagementGroup(t *testing.T) { } func TestGenerationConfigWithManagementGroupError(t *testing.T) { - hcl, err := azure.NewTerraform(true, false, true, + hcl, err := azure.NewTerraform(true, false, false, true, azure.WithManagementGroup(true), ).Generate() assert.NotNil(t, err) @@ -211,7 +211,7 @@ func TestGenerationActivityLogWithSubscriptionsList(t *testing.T) { ActivityLogWithSubscriptions, fileErr := getFileContent("test-data/activity-log-with-list-subscriptions.tf") assert.Nil(t, fileErr) testIds := []string{"test-id-1", "test-id-2", "test-id-3"} - hcl, err := azure.NewTerraform(false, true, true, + hcl, err := azure.NewTerraform(false, true, false, true, azure.WithSubscriptionIds(testIds), ).Generate() assert.Nil(t, err) @@ -223,7 +223,7 @@ func TestGenerationConfigWithSubscriptionsList(t *testing.T) { ConfigWithSubscriptions, fileErr := getFileContent("test-data/config-log-with-list-subscriptions.tf") assert.Nil(t, fileErr) testIds := []string{"test-id-1", "test-id-2", "test-id-3"} - hcl, err := azure.NewTerraform(true, false, true, + hcl, err := azure.NewTerraform(true, false, false, true, azure.WithSubscriptionIds(testIds), ).Generate() assert.Nil(t, err) @@ -234,7 +234,7 @@ func TestGenerationConfigWithSubscriptionsList(t *testing.T) { func TestGenerationLocation(t *testing.T) { ActivityLogLocation, fileErr := getFileContent("test-data/activity-log-with-location.tf") assert.Nil(t, fileErr) - hcl, err := azure.NewTerraform(false, true, true, + hcl, err := azure.NewTerraform(false, true, false, true, azure.WithStorageLocation("West US 2"), ).Generate() assert.Nil(t, err) @@ -246,7 +246,7 @@ func TestGenerationWithLaceworkProvider(t *testing.T) { laceworkProfile, fileErr := getFileContent("test-data/activity-log-with-lacework-profile.tf") assert.Nil(t, fileErr) - hcl, err := azure.NewTerraform(false, true, true, azure.WithLaceworkProfile("test-profile")).Generate() + hcl, err := azure.NewTerraform(false, true, false, true, azure.WithLaceworkProfile("test-profile")).Generate() assert.Nil(t, err) assert.NotNil(t, hcl) assert.Equal(t, laceworkProfile, hcl) @@ -256,8 +256,35 @@ func TestGenerationAzureRmProviderWithSubscriptionID(t *testing.T) { configWithSubscription, fileErr := getFileContent("test-data/config-with-azurerm-subscription.tf") assert.Nil(t, fileErr) - hcl, err := azure.NewTerraform(true, false, true, azure.WithSubscriptionID("test-subscription")).Generate() + hcl, err := azure.NewTerraform(true, false, false, true, azure.WithSubscriptionID("test-subscription")).Generate() assert.Nil(t, err) assert.NotNil(t, hcl) assert.Equal(t, configWithSubscription, hcl) } + +func TestGenerationEntraIDActivityLog(t *testing.T) { + ActivityLogEntraID, fileErr := getFileContent("test-data/entra-id-activity-log-no-custom-input.tf") + assert.Nil(t, fileErr) + hcl, err := azure.NewTerraform(false, false, true, true).Generate() + assert.Nil(t, err) + assert.NotNil(t, hcl) + assert.Equal(t, ActivityLogEntraID, hcl) +} + +func TestGenerationEntraIDActivityLogExistingActiveDirectoryApp(t *testing.T) { + ActivityLogEntraID, fileErr := getFileContent("test-data/entra-id-activity-log-existing-ad-app.tf") + assert.Nil(t, fileErr) + hcl, err := azure.NewTerraform(false, false, true, false, azure.WithAdApplicationId("testID"), azure.WithAdApplicationPassword("pass"), azure.WithAdServicePrincipalId("principal")).Generate() + assert.Nil(t, err) + assert.NotNil(t, hcl) + assert.Equal(t, ActivityLogEntraID, hcl) +} + +func TestGenerationEntraIDActivityLogEventHubLocationAndPartition(t *testing.T) { + ActivityLogEntraID, fileErr := getFileContent("test-data/entra-id-activity-log-event-hub-location-and-partition.tf") + assert.Nil(t, fileErr) + hcl, err := azure.NewTerraform(false, false, true, true, azure.WithEventHubLocation("West US 2"), azure.WithEventHubPartitionCount(2)).Generate() + assert.Nil(t, err) + assert.NotNil(t, hcl) + assert.Equal(t, ActivityLogEntraID, hcl) +} diff --git a/lwgenerate/azure/test-data/entra-id-activity-log-event-hub-location-and-partition.tf b/lwgenerate/azure/test-data/entra-id-activity-log-event-hub-location-and-partition.tf new file mode 100644 index 000000000..ad69a4a04 --- /dev/null +++ b/lwgenerate/azure/test-data/entra-id-activity-log-event-hub-location-and-partition.tf @@ -0,0 +1,32 @@ +terraform { + required_providers { + lacework = { + source = "lacework/lacework" + version = "~> 1.0" + } + } +} + +provider "azuread" { +} + +provider "azurerm" { + features { + } +} + +module "az_ad_application" { + source = "lacework/ad-application/azure" + version = "~> 1.0" +} + +module "microsoft-entra-id-activity-log" { + source = "lacework/microsoft-entra-id-activity-log/azure" + version = "~> 0.2" + application_id = module.az_ad_application.application_id + application_password = module.az_ad_application.application_password + location = "West US 2" + num_partitions = 2 + service_principal_id = module.az_ad_application.service_principal_id + use_existing_ad_application = false +} diff --git a/lwgenerate/azure/test-data/entra-id-activity-log-existing-ad-app.tf b/lwgenerate/azure/test-data/entra-id-activity-log-existing-ad-app.tf new file mode 100644 index 000000000..3555295a4 --- /dev/null +++ b/lwgenerate/azure/test-data/entra-id-activity-log-existing-ad-app.tf @@ -0,0 +1,25 @@ +terraform { + required_providers { + lacework = { + source = "lacework/lacework" + version = "~> 1.0" + } + } +} + +provider "azuread" { +} + +provider "azurerm" { + features { + } +} + +module "microsoft-entra-id-activity-log" { + source = "lacework/microsoft-entra-id-activity-log/azure" + version = "~> 0.2" + application_id = "testID" + application_password = "pass" + service_principal_id = "principal" + use_existing_ad_application = true +} diff --git a/lwgenerate/azure/test-data/entra-id-activity-log-existing-event-hub-ns.tf b/lwgenerate/azure/test-data/entra-id-activity-log-existing-event-hub-ns.tf new file mode 100644 index 000000000..be17974d7 --- /dev/null +++ b/lwgenerate/azure/test-data/entra-id-activity-log-existing-event-hub-ns.tf @@ -0,0 +1,30 @@ +terraform { + required_providers { + lacework = { + source = "lacework/lacework" + version = "~> 1.0" + } + } +} + +provider "azuread" { +} + +provider "azurerm" { + features { + } +} + +module "az_ad_application" { + source = "lacework/ad-application/azure" + version = "~> 1.0" +} + +module "microsoft-entra-id-activity-log" { + source = "lacework/microsoft-entra-id-activity-log/azure" + version = "~> 0.2" + application_id = module.az_ad_application.application_id + application_password = module.az_ad_application.application_passwor + service_principal_id = module.az_ad_application.service_principal_id + use_existing_ad_application = false +} diff --git a/lwgenerate/azure/test-data/entra-id-activity-log-no-custom-input.tf b/lwgenerate/azure/test-data/entra-id-activity-log-no-custom-input.tf new file mode 100644 index 000000000..1992b4b32 --- /dev/null +++ b/lwgenerate/azure/test-data/entra-id-activity-log-no-custom-input.tf @@ -0,0 +1,30 @@ +terraform { + required_providers { + lacework = { + source = "lacework/lacework" + version = "~> 1.0" + } + } +} + +provider "azuread" { +} + +provider "azurerm" { + features { + } +} + +module "az_ad_application" { + source = "lacework/ad-application/azure" + version = "~> 1.0" +} + +module "microsoft-entra-id-activity-log" { + source = "lacework/microsoft-entra-id-activity-log/azure" + version = "~> 0.2" + application_id = module.az_ad_application.application_id + application_password = module.az_ad_application.application_password + service_principal_id = module.az_ad_application.service_principal_id + use_existing_ad_application = false +} diff --git a/lwgenerate/constants.go b/lwgenerate/constants.go index 651e8ec61..56c56c84d 100644 --- a/lwgenerate/constants.go +++ b/lwgenerate/constants.go @@ -18,12 +18,14 @@ const ( AwsEksAuditSource = "lacework/eks-audit-log/aws" AwsEksAuditVersion = "~> 1.0" - LWAzureConfigSource = "lacework/config/azure" - LWAzureConfigVersion = "~> 2.0" - LWAzureActivityLogSource = "lacework/activity-log/azure" - LWAzureActivityLogVersion = "~> 2.0" - LWAzureADSource = "lacework/ad-application/azure" - LWAzureADVersion = "~> 1.0" + LWAzureConfigSource = "lacework/config/azure" + LWAzureConfigVersion = "~> 2.0" + LWAzureActivityLogSource = "lacework/activity-log/azure" + LWAzureActivityLogVersion = "~> 2.0" + LWAzureEntraIdActivityLogSource = "lacework/microsoft-entra-id-activity-log/azure" + LWAzureEntraIdActivityLogVersion = "~> 0.2" + LWAzureADSource = "lacework/ad-application/azure" + LWAzureADVersion = "~> 1.0" GcpAgentlessSource = "lacework/agentless-scanning/gcp" GcpAgentlessVersion = "~> 2.0"