diff --git a/cli/cdk/go/proto/v1/cdk.pb.go b/cli/cdk/go/proto/v1/cdk.pb.go index 2e5716254..4ab3a18e6 100644 --- a/cli/cdk/go/proto/v1/cdk.pb.go +++ b/cli/cdk/go/proto/v1/cdk.pb.go @@ -25,11 +25,12 @@ package cdk import ( + reflect "reflect" + sync "sync" + timestamp "github.com/golang/protobuf/ptypes/timestamp" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" - reflect "reflect" - sync "sync" ) const ( diff --git a/cli/cdk/go/proto/v1/cdk_grpc.pb.go b/cli/cdk/go/proto/v1/cdk_grpc.pb.go index 7431fd74e..3603a28ac 100644 --- a/cli/cdk/go/proto/v1/cdk_grpc.pb.go +++ b/cli/cdk/go/proto/v1/cdk_grpc.pb.go @@ -8,6 +8,7 @@ package cdk import ( context "context" + grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" diff --git a/cli/cmd/generate_gcp.go b/cli/cmd/generate_gcp.go index 630e6394f..e2fecb48a 100644 --- a/cli/cmd/generate_gcp.go +++ b/cli/cmd/generate_gcp.go @@ -315,7 +315,7 @@ func initGenerateGcpTfCommandFlags() { &GenerateGcpCommandState.GcpOrganizationId, "organization_id", "", - "specify the organization id (only set if organization_integration is set)") + "specify the organization id (only set if agentless integration or organization_integration is set)") generateGcpTfCommand.PersistentFlags().StringVar( &GenerateGcpCommandState.GcpProjectId, "project_id", @@ -736,9 +736,17 @@ func promptGcpGenerate( Prompt: &survey.Confirm{Message: QuestionGcpOrganizationIntegration, Default: config.OrganizationIntegration}, Response: &config.OrganizationIntegration, }, + }); err != nil { + return err + } + + organizationIdRequired := config.Agentless || config.OrganizationIntegration + + if err := SurveyMultipleQuestionWithValidation( + []SurveyQuestionWithValidationArgs{ { Prompt: &survey.Input{Message: QuestionGcpOrganizationID, Default: config.GcpOrganizationId}, - Checks: []*bool{&config.OrganizationIntegration}, + Checks: []*bool{&organizationIdRequired}, Required: true, Response: &config.GcpOrganizationId, }, diff --git a/cli/docs/lacework_generate_cloud-account_gcp.md b/cli/docs/lacework_generate_cloud-account_gcp.md index c5eaca95f..c695dde0b 100644 --- a/cli/docs/lacework_generate_cloud-account_gcp.md +++ b/cli/docs/lacework_generate_cloud-account_gcp.md @@ -58,7 +58,7 @@ lacework generate cloud-account gcp [flags] -h, --help help for gcp --include_root_projects Disables logic that includes root-level projects if excluding folders (default true) --k8s_filter filter out GKE logs from GCP Audit Log sinks (default true) - --organization_id string specify the organization id (only set if organization_integration is set) + --organization_id string specify the organization id (only set if agentless integration or organization_integration is set) --organization_integration enable organization integration --output string location to write generated content (default is ~/lacework/gcp) --prefix string prefix that will be used at the beginning of every generated resource diff --git a/integration/gcp_generation_test.go b/integration/gcp_generation_test.go index 334513046..e1680b1d1 100644 --- a/integration/gcp_generation_test.go +++ b/integration/gcp_generation_test.go @@ -121,6 +121,48 @@ func TestGenerationGcpAgentless(t *testing.T) { assert.Equal(t, buildTf, tfResult) } +// Test Agentless only generation +func TestGenerationGcpAgentlessProjectLevel(t *testing.T) { + os.Setenv("LW_NOCACHE", "true") + defer os.Setenv("LW_NOCACHE", "") + var final string + + tfResult := runGcpGenerateTest(t, + func(c *expect.Console) { + expectsCliOutput(t, c, []MsgRspHandler{ + MsgRsp{cmd.QuestionGcpEnableAgentless, "y"}, + MsgRsp{cmd.QuestionGcpEnableConfiguration, "n"}, + MsgRsp{cmd.QuestionGcpEnableAuditLog, "n"}, + MsgRsp{cmd.QuestionGcpProjectID, projectId}, + MsgRsp{cmd.QuestionGcpRegions, "us-east1"}, + MsgRsp{cmd.QuestionGcpOrganizationIntegration, "n"}, + MsgRsp{cmd.QuestionGcpOrganizationID, organizationId}, + MsgRsp{cmd.QuestionGcpConfigureAdvanced, "y"}, + MsgMenu{cmd.GcpAdvancedOptAgentless, 0}, + MsgRsp{cmd.QuestionGcpProjectFilterList, "p1,p2"}, + MsgRsp{cmd.QuestionGcpAnotherAdvancedOpt, "n"}, + MsgRsp{cmd.QuestionRunTfPlan, "n"}, + }) + + final, _ = c.ExpectEOF() + }, + "generate", + "cloud-account", + "gcp", + ) + + assertTerraformSaved(t, final) + + buildTf, _ := gcp.NewTerraform(true, false, false, true, + gcp.WithProjectId(projectId), + gcp.WithOrganizationIntegration(false), + gcp.WithOrganizationId(organizationId), + gcp.WithRegions([]string{"us-east1"}), + gcp.WithProjectFilterList([]string{"p1", "p2"}), + ).Generate() + assert.Equal(t, buildTf, tfResult) +} + // Test configuration only generation func TestGenerationGcpConfig(t *testing.T) { os.Setenv("LW_NOCACHE", "true") diff --git a/integration/test_resources/help/generate_cloud-account_gcp b/integration/test_resources/help/generate_cloud-account_gcp index b5efc231d..ad6aeb856 100644 --- a/integration/test_resources/help/generate_cloud-account_gcp +++ b/integration/test_resources/help/generate_cloud-account_gcp @@ -37,7 +37,7 @@ Flags: -h, --help help for gcp --include_root_projects Disables logic that includes root-level projects if excluding folders (default true) --k8s_filter filter out GKE logs from GCP Audit Log sinks (default true) - --organization_id string specify the organization id (only set if organization_integration is set) + --organization_id string specify the organization id (only set if agentless integration or organization_integration is set) --organization_integration enable organization integration --output string location to write generated content (default is ~/lacework/gcp) --prefix string prefix that will be used at the beginning of every generated resource diff --git a/lwgenerate/gcp/gcp.go b/lwgenerate/gcp/gcp.go index a0c93d67f..791b83ec0 100644 --- a/lwgenerate/gcp/gcp.go +++ b/lwgenerate/gcp/gcp.go @@ -173,11 +173,6 @@ func (args *GenerateGcpTfConfigurationArgs) validate() error { return errors.New("an Organization ID must be provided for an Organization Integration") } - // Validate if an organization id has been provided that this is and organization integration - if !args.OrganizationIntegration && args.GcpOrganizationId != "" { - return errors.New("to provide an Organization ID, Organization Integration must be true") - } - // Validate existing Service Account values, if set if args.ExistingServiceAccount != nil { if args.ExistingServiceAccount.Name == "" || @@ -631,6 +626,8 @@ func createAgentless(args *GenerateGcpTfConfigurationArgs) ([]*hclwrite.Block, e } if args.OrganizationIntegration { attributes["integration_type"] = "ORGANIZATION" + } + if len(args.GcpOrganizationId) > 0 { attributes["organization_id"] = args.GcpOrganizationId } } diff --git a/lwgenerate/gcp/gcp_test.go b/lwgenerate/gcp/gcp_test.go index 31dd74b65..0865b3a19 100644 --- a/lwgenerate/gcp/gcp_test.go +++ b/lwgenerate/gcp/gcp_test.go @@ -44,6 +44,17 @@ func TestGenerateGcpTfConfigurationArgs_Generate_AuditLog(t *testing.T) { gcp.WithProjectId(projectName)), ReqProvider(projectName, moduleImportProjectLevelPubSubAuditLogWithoutConfiguration), }, + { + "TestGenerationProjectLevelPubSubAuditLogWithoutConfigWithOrgId", + gcp.NewTerraform( + false, + false, + true, + true, + gcp.WithGcpServiceAccountCredentials("/path/to/credentials"), + gcp.WithProjectId(projectName), gcp.WithOrganizationId("123456789")), + ReqProvider(projectName, moduleImportProjectLevelPubSubAuditLogWithoutConfiguration), + }, { "TestGenerationProjectLevelAuditLogWithoutCredentialsAndProject", gcp.NewTerraform(false, false, true, false), @@ -513,6 +524,16 @@ func TestGenerateGcpTfConfigurationArgs_Generate_Configuration(t *testing.T) { ), ReqProvider(projectName, moduleImportProjectLevelConfigurationExistingSA), }, + { + "TestGenerationProjectLevelConfigurationExistingSAWithOrgId", + gcp.NewTerraform(false, true, false, false, + gcp.WithGcpServiceAccountCredentials("/path/to/credentials"), + gcp.WithProjectId(projectName), + gcp.WithOrganizationId("123456789"), + gcp.WithExistingServiceAccount(gcp.NewExistingServiceAccountDetails("foo", "123456789")), + ), + ReqProvider(projectName, moduleImportProjectLevelConfigurationExistingSA), + }, { "TestGenerationProjectLevelConfigurationCustomIntegrationName", gcp.NewTerraform(false, true, false, false, @@ -720,6 +741,7 @@ func TestGenerateGcpTfConfigurationArgs_Generate_Agentless(t *testing.T) { "TestGenerationProjectLevelAgentless", gcp.NewTerraform(true, false, false, false, gcp.WithProjectId(projectName), + gcp.WithOrganizationId("123456789"), gcp.WithRegions([]string{"us-east1"}), ), fmt.Sprintf("%s\n%s", RequiredProviders, moduleImportProjectLevelAgentless), @@ -769,16 +791,6 @@ func TestGenerationOrganizationLevelAuditLogNoOrgId(t *testing.T) { assert.EqualError(t, err, "invalid inputs: an Organization ID must be provided for an Organization Integration") } -func TestGenerationOrganizationLevelAuditLogNoOrgIntegrationFlag(t *testing.T) { - hcl, err := gcp.NewTerraform(false, false, true, false, - gcp.WithGcpServiceAccountCredentials("/path/to/credentials"), - gcp.WithProjectId(projectName), - gcp.WithOrganizationId("123456789"), - ).Generate() - assert.Empty(t, hcl) - assert.EqualError(t, err, "invalid inputs: to provide an Organization ID, Organization Integration must be true") -} - func TestGenerationNoIntegration(t *testing.T) { hcl, err := gcp.NewTerraform(false, false, false, false, gcp.WithGcpServiceAccountCredentials("/path/to/credentials"), @@ -1181,10 +1193,11 @@ var moduleImportProjectLevelAgentless = `provider "google" { } module "lacework_gcp_agentless_scanning_global" { - source = "lacework/agentless-scanning/gcp" - version = "~> 2.0" - global = true - regional = true + source = "lacework/agentless-scanning/gcp" + version = "~> 2.0" + global = true + organization_id = "123456789" + regional = true providers = { google = google.us-east1