22package azure
33
44import (
5+ "fmt"
6+ "strings"
7+
58 "github.com/hashicorp/hcl/v2/hclwrite"
69 "github.com/lacework/go-sdk/v2/lwgenerate"
710 "github.com/pkg/errors"
@@ -14,6 +17,9 @@ type GenerateAzureTfConfigurationArgs struct {
1417 // Should we add Config integration in LW?
1518 Config bool
1619
20+ // Should we add Agentless integration in LW?
21+ Agentless bool
22+
1723 // Should we create an Entra ID integration in LW?
1824 EntraIdActivityLog bool
1925
@@ -88,23 +94,40 @@ type GenerateAzureTfConfigurationArgs struct {
8894
8995 // Custom outputs
9096 CustomOutputs []lwgenerate.HclOutput
97+
98+ // Integration level for agentless scanning (e.g., "SUBSCRIPTION", "TENANT")
99+ IntegrationLevel string
100+
101+ // Should agentless scanning be global?
102+ Global bool
103+
104+ // Should we create a Log Analytics Workspace for agentless scanning?
105+ CreateLogAnalyticsWorkspace bool
106+
107+ // List of regions to deploy for agentless scanning
108+ Regions []string
109+
110+ // List of subscription IDs for agentless scanning
111+ AgentlessSubscriptionIds []string
91112}
92113
93114// Ensure all combinations of inputs are valid for supported spec
94115func (args * GenerateAzureTfConfigurationArgs ) validate () error {
95- // Validate one of config or activity log was enabled; otherwise error out
96- if ! args .ActivityLog && ! args .Config && ! args .EntraIdActivityLog {
97- return errors .New ("audit log or config integration must be enabled" )
116+ // Validate one of config, agentless or activity log was enabled; otherwise error out
117+ if ! args .ActivityLog && ! args .Agentless && ! args . Config && ! args .EntraIdActivityLog {
118+ return errors .New ("audit log, agentless or config integration must be enabled" )
98119 }
99120
100- if (args .ActivityLog || args .Config || args .EntraIdActivityLog ) && args .SubscriptionID == "" {
121+ if (args .ActivityLog || args .Agentless || args . Config || args .EntraIdActivityLog ) && args .SubscriptionID == "" {
101122 return errors .New ("subscription_id must be provided" )
102123 }
103124
104125 // Validate that active directory settings are correct
105- if ! args .CreateAdIntegration && (args .AdApplicationId == "" ||
106- args .AdServicePrincipalId == "" || args .AdApplicationPassword == "" ) {
107- return errors .New ("Active directory details must be set" )
126+ if ! args .CreateAdIntegration && (args .Config || args .ActivityLog || args .EntraIdActivityLog ) {
127+ if args .AdApplicationId == "" ||
128+ args .AdServicePrincipalId == "" || args .AdApplicationPassword == "" {
129+ return errors .New ("Active directory details must be set" )
130+ }
108131 }
109132
110133 // Validate the Mangement Group
@@ -117,6 +140,16 @@ func (args *GenerateAzureTfConfigurationArgs) validate() error {
117140 return errors .New ("When using existing storage account, storage account details must be configured" )
118141 }
119142
143+ // Validate Agentless Scanning
144+ if args .Agentless {
145+ if args .IntegrationLevel == "" {
146+ return errors .New ("integration_level must be set for Agentless Integration" )
147+ }
148+ if args .IntegrationLevel == "SUBSCRIPTION" && len (args .AgentlessSubscriptionIds ) == 0 {
149+ return errors .New ("subscription_ids must be provided for Agentless Integration with SUBSCRIPTION integration level" )
150+ }
151+ }
152+
120153 return nil
121154}
122155
@@ -127,12 +160,13 @@ type AzureTerraformModifier func(c *GenerateAzureTfConfigurationArgs)
127160//
128161// Note: Additional configuration details may be set using modifiers of the AzureTerraformModifier type
129162func NewTerraform (
130- enableConfig bool , enableActivityLog bool , enableEntraIdActivityLog , createAdIntegration bool ,
163+ enableConfig bool , enableActivityLog bool , enableAgentless bool , enableEntraIdActivityLog , createAdIntegration bool ,
131164 mods ... AzureTerraformModifier ,
132165) * GenerateAzureTfConfigurationArgs {
133166 config := & GenerateAzureTfConfigurationArgs {
134167 ActivityLog : enableActivityLog ,
135168 Config : enableConfig ,
169+ Agentless : enableAgentless ,
136170 EntraIdActivityLog : enableEntraIdActivityLog ,
137171 CreateAdIntegration : createAdIntegration ,
138172 }
@@ -245,6 +279,19 @@ func WithSubscriptionIds(subscriptionIds []string) AzureTerraformModifier {
245279 }
246280}
247281
282+ // WithAgentlessSubscriptionIds List of subscriptions for agentless scanning.
283+ func WithAgentlessSubscriptionIds (agentlessSubscriptionIds []string ) AzureTerraformModifier {
284+ return func (c * GenerateAzureTfConfigurationArgs ) {
285+ c .AgentlessSubscriptionIds = agentlessSubscriptionIds
286+ }
287+ }
288+
289+ func WithRegions (regions []string ) AzureTerraformModifier {
290+ return func (c * GenerateAzureTfConfigurationArgs ) {
291+ c .Regions = regions
292+ }
293+ }
294+
248295// WithAllSubscriptions Grant read access to ALL subscriptions within
249296// the selected Tenant (overrides 'subscription_ids')
250297func WithAllSubscriptions (allSubscriptions bool ) AzureTerraformModifier {
@@ -307,6 +354,27 @@ func WithSubscriptionID(subcriptionID string) AzureTerraformModifier {
307354 }
308355}
309356
357+ // WithGlobal sets the Global field for agentless scanning
358+ func WithGlobal (global bool ) AzureTerraformModifier {
359+ return func (c * GenerateAzureTfConfigurationArgs ) {
360+ c .Global = global
361+ }
362+ }
363+
364+ // WithCreateLogAnalyticsWorkspace sets the CreateLogAnalyticsWorkspace field for agentless scanning
365+ func WithCreateLogAnalyticsWorkspace (create bool ) AzureTerraformModifier {
366+ return func (c * GenerateAzureTfConfigurationArgs ) {
367+ c .CreateLogAnalyticsWorkspace = create
368+ }
369+ }
370+
371+ // WithIntegrationLevel sets the IntegrationLevel field for agentless scanning
372+ func WithIntegrationLevel (level string ) AzureTerraformModifier {
373+ return func (c * GenerateAzureTfConfigurationArgs ) {
374+ c .IntegrationLevel = level
375+ }
376+ }
377+
310378// Generate new Terraform code based on the supplied args.
311379func (args * GenerateAzureTfConfigurationArgs ) Generate () (string , error ) {
312380 // Validate inputs
@@ -350,6 +418,11 @@ func (args *GenerateAzureTfConfigurationArgs) Generate() (string, error) {
350418 return "" , errors .Wrap (err , "failed to generate azure activity log module" )
351419 }
352420
421+ agentlessLogModule , err := createAgentless (args )
422+ if err != nil {
423+ return "" , errors .Wrap (err , "failed to generate azure agentless module" )
424+ }
425+
353426 entraIdActivityLogModule , err := createEntraIdActivityLog (args )
354427 if err != nil {
355428 return "" , errors .Wrap (err , "failed to generate azure Entra ID activity log module" )
@@ -374,6 +447,7 @@ func (args *GenerateAzureTfConfigurationArgs) Generate() (string, error) {
374447 laceworkADProvider ,
375448 configModule ,
376449 activityLogModule ,
450+ agentlessLogModule ,
377451 entraIdActivityLogModule ,
378452 outputBlocks ,
379453 args .ExtraBlocks ),
@@ -622,6 +696,82 @@ func createActivityLog(args *GenerateAzureTfConfigurationArgs) ([]*hclwrite.Bloc
622696 return blocks , nil
623697}
624698
699+ func createAgentless (args * GenerateAzureTfConfigurationArgs ) ([]* hclwrite.Block , error ) {
700+ blocks := []* hclwrite.Block {}
701+
702+ if ! args .Agentless {
703+ return blocks , nil
704+ }
705+
706+ // Helper function to format region names for module naming
707+ formatRegionForModuleName := func (region string ) string {
708+ return strings .ToLower (strings .ReplaceAll (region , " " , "_" ))
709+ }
710+
711+ // Determine regions to process
712+ regions := args .Regions
713+ if len (regions ) == 0 {
714+ regions = []string {"West US" } // Default to West US if no regions specified
715+ }
716+
717+ isTenant := strings .EqualFold (args .IntegrationLevel , "TENANT" )
718+ isSubscription := strings .EqualFold (args .IntegrationLevel , "SUBSCRIPTION" )
719+
720+ var firstModuleName string
721+ for i , region := range regions {
722+ isFirstRegion := (i == 0 )
723+
724+ // Build module name
725+ var moduleName string
726+ if isTenant {
727+ moduleName = fmt .Sprintf ("lacework_azure_agentless_scanning_tenant_%s" , formatRegionForModuleName (region ))
728+ } else {
729+ moduleName = fmt .Sprintf ("lacework_azure_agentless_scanning_subscription_%s" , formatRegionForModuleName (region ))
730+ }
731+
732+ // Build attributes
733+ attrs := map [string ]interface {}{
734+ "integration_level" : args .IntegrationLevel ,
735+ "region" : region ,
736+ "global" : args .Global && isFirstRegion ,
737+ "create_log_analytics_workspace" : args .CreateLogAnalyticsWorkspace ,
738+ }
739+
740+ // set the first module name for global reference
741+ if isFirstRegion {
742+ firstModuleName = moduleName
743+ } else {
744+ attrs ["global_module_reference" ] = lwgenerate .CreateSimpleTraversal ([]string {"module" , firstModuleName })
745+ }
746+
747+ if args .SubscriptionID != "" {
748+ attrs ["scanning_subscription_id" ] = args .SubscriptionID
749+ }
750+ if isSubscription && isFirstRegion && len (args .AgentlessSubscriptionIds ) > 0 {
751+ subs := make ([]string , len (args .AgentlessSubscriptionIds ))
752+ for j , id := range args .AgentlessSubscriptionIds {
753+ subs [j ] = fmt .Sprintf ("/subscriptions/%s" , id )
754+ }
755+ attrs ["included_subscriptions" ] = subs
756+ }
757+
758+ // Create module details
759+ moduleDetails := []lwgenerate.HclModuleModifier {
760+ lwgenerate .HclModuleWithAttributes (attrs ),
761+ }
762+ block , err := lwgenerate .NewModule (
763+ moduleName ,
764+ lwgenerate .LWAzureAgentlessSource ,
765+ append (moduleDetails , lwgenerate .HclModuleWithVersion (lwgenerate .LWAzureAgentlessVersion ))... ,
766+ ).ToBlock ()
767+ if err != nil {
768+ return nil , err
769+ }
770+ blocks = append (blocks , block )
771+ }
772+ return blocks , nil
773+ }
774+
625775func createEntraIdActivityLog (args * GenerateAzureTfConfigurationArgs ) ([]* hclwrite.Block , error ) {
626776 blocks := []* hclwrite.Block {}
627777 if args .EntraIdActivityLog {
0 commit comments