diff --git a/lwgenerate/gcp/gcp.go b/lwgenerate/gcp/gcp.go index eb894acb1..8e4df3f47 100644 --- a/lwgenerate/gcp/gcp.go +++ b/lwgenerate/gcp/gcp.go @@ -140,6 +140,18 @@ type GenerateGcpTfConfigurationArgs struct { WaitTime string Projects []string + + // Add custom blocks to the root `terraform{}` block. Can be used for advanced configuration. Things like backend, etc + ExtraBlocksRootTerraform []*hclwrite.Block + + // ExtraProviderArguments allows adding more arguments to the provider block as needed (custom use cases) + ExtraProviderArguments map[string]interface{} + + // ExtraBlocks allows adding more hclwrite.Block to the root terraform document (advanced use cases) + ExtraBlocks []*hclwrite.Block + + // Custom outputs + CustomOutputs []lwgenerate.HclOutput } // Ensure all combinations of inputs are valid for supported spec @@ -223,6 +235,36 @@ func WithGcpServiceAccountCredentials(path string) GcpTerraformModifier { } } +// WithConfigOutputs Set Custom Terraform Outputs +func WithCustomOutputs(outputs []lwgenerate.HclOutput) GcpTerraformModifier { + return func(c *GenerateGcpTfConfigurationArgs) { + c.CustomOutputs = outputs + } +} + +// WithExtraRootBlocks allows adding generic hcl blocks to the root `terraform{}` block +// this enables custom use cases +func WithExtraRootBlocks(blocks []*hclwrite.Block) GcpTerraformModifier { + return func(c *GenerateGcpTfConfigurationArgs) { + c.ExtraBlocksRootTerraform = blocks + } +} + +// WithExtraProviderArguments enables adding additional arguments into the `gcp` provider block +// this enables custom use cases +func WithExtraProviderArguments(arguments map[string]interface{}) GcpTerraformModifier { + return func(c *GenerateGcpTfConfigurationArgs) { + c.ExtraProviderArguments = arguments + } +} + +// WithExtraBlocks enables adding additional arbitrary blocks to the root hcl document +func WithExtraBlocks(blocks []*hclwrite.Block) GcpTerraformModifier { + return func(c *GenerateGcpTfConfigurationArgs) { + c.ExtraBlocks = blocks + } +} + // WithLaceworkProfile Set the Lacework Profile to utilize when integrating func WithLaceworkProfile(name string) GcpTerraformModifier { return func(c *GenerateGcpTfConfigurationArgs) { @@ -417,12 +459,12 @@ func (args *GenerateGcpTfConfigurationArgs) Generate() (string, error) { } // Create blocks - requiredProviders, err := createRequiredProviders(false) + requiredProviders, err := createRequiredProviders(false, args.ExtraBlocksRootTerraform) if err != nil { return "", errors.Wrap(err, "failed to generate required providers") } - gcpProvider, err := createGcpProvider(args.ServiceAccountCredentials, args.GcpProjectId, args.Regions, "") + gcpProvider, err := createGcpProvider(args.ExtraProviderArguments, args.ServiceAccountCredentials, args.GcpProjectId, args.Regions, "") if err != nil { return "", errors.Wrap(err, "failed to generate gcp provider") } @@ -447,6 +489,15 @@ func (args *GenerateGcpTfConfigurationArgs) Generate() (string, error) { return "", errors.Wrap(err, "failed to generate gcp audit log module") } + outputBlocks := []*hclwrite.Block{} + for _, output := range args.CustomOutputs { + outputBlock, err := output.ToBlock() + if err != nil { + return "", errors.Wrap(err, "failed to add custom output") + } + outputBlocks = append(outputBlocks, outputBlock) + } + // Render hclBlocks := lwgenerate.CreateHclStringOutput( lwgenerate.CombineHclBlocks( @@ -456,16 +507,19 @@ func (args *GenerateGcpTfConfigurationArgs) Generate() (string, error) { agentlessModule, configurationModule, auditLogModule, + outputBlocks, + args.ExtraBlocks, ), ) return hclBlocks, nil } -func createRequiredProviders(useExistingRequiredProviders bool) (*hclwrite.Block, error) { +func createRequiredProviders(useExistingRequiredProviders bool, extraBlocks []*hclwrite.Block) (*hclwrite.Block, error) { if useExistingRequiredProviders { return nil, nil } - return lwgenerate.CreateRequiredProviders( + return lwgenerate.CreateRequiredProvidersWithCustomBlocks( + extraBlocks, lwgenerate.NewRequiredProvider( "lacework", lwgenerate.HclRequiredProviderWithSource(lwgenerate.LaceworkProviderSource), @@ -485,6 +539,7 @@ func createLaceworkProvider(laceworkProfile string) (*hclwrite.Block, error) { } func createGcpProvider( + extraProvidedArguments map[string]interface{}, serviceAccountCredentials string, projectId string, regionsArg []string, @@ -500,6 +555,10 @@ func createGcpProvider( for _, region := range regions { attrs := map[string]interface{}{} + // set custom args before the required ones below to ensure expected behavior (i.e., no overrides) + for k, v := range extraProvidedArguments { + attrs[k] = v + } if serviceAccountCredentials != "" { attrs["credentials"] = serviceAccountCredentials } diff --git a/lwgenerate/gcp/gcp_test.go b/lwgenerate/gcp/gcp_test.go index 196e339a0..d3e459a7b 100644 --- a/lwgenerate/gcp/gcp_test.go +++ b/lwgenerate/gcp/gcp_test.go @@ -2,6 +2,8 @@ package gcp_test import ( "fmt" + "github.com/hashicorp/hcl/v2/hclwrite" + "github.com/lacework/go-sdk/lwgenerate" "testing" "github.com/stretchr/testify/assert" @@ -795,6 +797,70 @@ var RequiredProviders = `terraform { } ` +var requiredProvidersWithCustomBlock = `terraform { + required_providers { + lacework = { + source = "lacework/lacework" + version = "~> 1.0" + } + } + backend "s3" { + } +} +` + +func TestGenerationConfigWithExtraBlocks(t *testing.T) { + extraBlock, err := lwgenerate.HclCreateGenericBlock("variable", []string{"var_name"}, nil) + assert.NoError(t, err) + + hcl, err := gcp.NewTerraform(false, false, true, false, + gcp.WithGcpServiceAccountCredentials("/path/to/credentials"), + gcp.WithProjectId(projectName), + gcp.WithExtraBlocks([]*hclwrite.Block{extraBlock}), + ).Generate() + assert.Nil(t, err) + assert.NotNil(t, hcl) + assert.Equal(t, RequiredProviders+"\n"+gcpProvider+"\n"+moduleImportProjectLevelAuditLogWithoutConfiguration+"\n"+testVariable, hcl) +} + +func TestGenerationConfigWithCustomBackendBlock(t *testing.T) { + customBlock, err := lwgenerate.HclCreateGenericBlock("backend", []string{"s3"}, nil) + assert.NoError(t, err) + hcl, err := gcp.NewTerraform(false, false, true, false, + gcp.WithGcpServiceAccountCredentials("/path/to/credentials"), + gcp.WithProjectId(projectName), + gcp.WithExtraRootBlocks([]*hclwrite.Block{customBlock}), + ).Generate() + assert.Nil(t, err) + assert.NotNil(t, hcl) + assert.Equal(t, requiredProvidersWithCustomBlock+"\n"+gcpProvider+"\n"+moduleImportProjectLevelAuditLogWithoutConfiguration, hcl) +} + +func TestGenerationConfigWithCustomProviderAttributes(t *testing.T) { + hcl, err := gcp.NewTerraform(false, false, true, false, + gcp.WithGcpServiceAccountCredentials("/path/to/credentials"), + gcp.WithProjectId(projectName), + gcp.WithRegions([]string{"us-east1"}), + gcp.WithExtraProviderArguments(map[string]interface{}{"foo": "bar"}), + ).Generate() + assert.Nil(t, err) + assert.NotNil(t, hcl) + assert.Equal(t, RequiredProviders+"\n"+gcpProviderWithExtraArguments+"\n"+moduleImportProjectLevelAuditLogWithoutConfiguration, hcl) +} + +func TestGenerationConfigWithOutputs(t *testing.T) { + hcl, err := gcp.NewTerraform( + false, false, true, false, + gcp.WithGcpServiceAccountCredentials("/path/to/credentials"), + gcp.WithProjectId(projectName), + gcp.WithCustomOutputs([]lwgenerate.HclOutput{ + *lwgenerate.NewOutput("test", []string{"module", "gcp_config", "lacework_integration_guid"}, "test description"), + })).Generate() + assert.Nil(t, err) + assert.NotNil(t, hcl) + assert.Equal(t, RequiredProviders+"\n"+gcpProvider+"\n"+moduleImportProjectLevelAuditLogWithoutConfiguration+"\n"+customOutput, hcl) +} + func ProviderWithCredentials(projectName string) string { return fmt.Sprintf(`provider "google" { credentials = "/path/to/credentials" @@ -823,6 +889,31 @@ var gcpProviderWithoutCredentialsAndProject = `provider "google" { } ` +var gcpProvider = `provider "google" { + credentials = "/path/to/credentials" + project = "project1" +} +` + +var gcpProviderWithExtraArguments = `provider "google" { + alias = "us-east1" + credentials = "/path/to/credentials" + foo = "bar" + project = "project1" + region = "us-east1" +} +` + +var testVariable = `variable "var_name" { +} +` + +var customOutput = `output "test" { + description = "test description" + value = module.gcp_config.lacework_integration_guid +} +` + var laceworkProvider = `provider "lacework" { profile = "test-profile" } diff --git a/lwgenerate/gcp/gke.go b/lwgenerate/gcp/gke.go index 592aa478a..ed21f596d 100644 --- a/lwgenerate/gcp/gke.go +++ b/lwgenerate/gcp/gke.go @@ -24,6 +24,8 @@ type GenerateGkeTfConfigurationArgs struct { PubSubTopicLabels map[string]string ServiceAccountCredentials string WaitTime string + // Add custom blocks to the root `terraform{}` block. Can be used for advanced configuration. Things like backend, etc + ExtraBlocksRootTerraform []*hclwrite.Block } type Modifier func(c *GenerateGkeTfConfigurationArgs) @@ -33,12 +35,13 @@ func (args *GenerateGkeTfConfigurationArgs) Generate() (string, error) { return "", errors.Wrap(err, "invalid inputs") } - requiredProviders, err := createRequiredProviders(args.UseExistingRequiredProviders) + requiredProviders, err := createRequiredProviders(args.UseExistingRequiredProviders, args.ExtraBlocksRootTerraform) if err != nil { return "", errors.Wrap(err, "failed to generate required providers") } gcpProvider, err := createGcpProvider( + map[string]interface{}{}, args.ServiceAccountCredentials, args.ProjectId, []string{},