Skip to content

Commit

Permalink
chore: support auto snapshot
Browse files Browse the repository at this point in the history
  • Loading branch information
PengyuanZhao committed Oct 30, 2023
1 parent b841165 commit a40311f
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 138 deletions.
101 changes: 20 additions & 81 deletions cli/cmd/generate_aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,6 @@ var (
"(e.g. 123456789000,ou-abcd-12345678,r-abcd):"
QuestionAgentlessMonitoredAccountIDsHelp = "Please provide a comma seprated list that may " +
" contain account IDs, OUs, or the organization root."
QuestionAgentlessMonitoredAccountProfile = "Specify the AWS account profile name to be used for " +
"the monitored account:"
QuestionAgentlessMonitoredAccountRegion = "Specify the AWS region to be used for the monitored account:"
QuestionAgentlessMonitoredAccountAddMore = "Add another monitored AWS account?"
QuestionAgentlessMonitoredAccountsReplace = "Currently configured Agentless monitored accounts: %s, replace?"

QuestionAwsEnableConfig = "Enable configuration integration?"
QuestionCustomizeConfigName = "Customize Config integration name?"
Expand All @@ -40,15 +35,14 @@ var (
QuestionExistingIamRoleName = "Specify an existing IAM role name for CloudTrail access:"
QuestionExistingIamRoleArn = "Specify an existing IAM role ARN for CloudTrail access:"
QuestionExistingIamRoleExtID = "Specify the external ID to be used with the existing IAM role:"
QuestionPrimaryAwsAccountProfile = "Before adding sub-accounts, your primary AWS account profile name " +
"must be set; which profile should the main account use?"
QuestionSubAccountProfileName = "Supply the profile name for this additional AWS account:"
QuestionSubAccountRegion = "What region should be used for this account?"
QuestionSubAccountAddMore = "Add another AWS account?"
QuestionSubAccountReplace = "Currently configured AWS sub-accounts: %s, replace?"
QuestionAwsConfigAdvanced = "Configure advanced integration options?"
QuestionAwsAnotherAdvancedOpt = "Configure another advanced integration option"
QuestionAwsCustomizeOutputLocation = "Provide the location for the output to be written:"
QuestionPrimaryAwsAccountProfile = "Specify the primary AWS account profile name:"
QuestionSubAccountProfileName = "Supply the profile name for this additional AWS account:"
QuestionSubAccountRegion = "What region should be used for this account?"
QuestionSubAccountAddMore = "Add another AWS account?"
QuestionSubAccountReplace = "Currently configured AWS sub-accounts: %s, replace?"
QuestionAwsConfigAdvanced = "Configure advanced integration options?"
QuestionAwsAnotherAdvancedOpt = "Configure another advanced integration option"
QuestionAwsCustomizeOutputLocation = "Provide the location for the output to be written:"

// S3 Bucket Questions
QuestionBucketEnableEncryption = "Enable S3 bucket encryption when creating bucket"
Expand Down Expand Up @@ -131,7 +125,6 @@ See help output for more details on the parameter value(s) required for Terrafor
aws.WithLaceworkAccountID(GenerateAwsCommandState.LaceworkAccountID),
aws.WithAgentlessManagementAccountID(GenerateAwsCommandState.AgentlessManagementAccountID),
aws.WithAgentlessMonitoredAccountIDs(GenerateAwsCommandState.AgentlessMonitoredAccountIDs),
aws.WithAgentlessMonitoredAccounts(GenerateAwsCommandState.AgentlessMonitoredAccounts...),
aws.ExistingCloudtrailBucketArn(GenerateAwsCommandState.ExistingCloudtrailBucketArn),
aws.ExistingSnsTopicArn(GenerateAwsCommandState.ExistingSnsTopicArn),
aws.WithSubaccounts(GenerateAwsCommandState.SubAccounts...),
Expand Down Expand Up @@ -516,7 +509,7 @@ func validateAgentlessMonitoredAccountIDs(val interface{}) error {
if err := validateStringWithRegex(
id,
regex,
fmt.Sprintf("invalid account ID, OU ID or root ID supplied: $s", id),
fmt.Sprintf("invalid account ID, OU ID or root ID supplied: %s", id),
); err != nil {
return err
}
Expand All @@ -542,10 +535,6 @@ func promptAgentlessQuestions(
config *aws.GenerateAwsTfConfigurationArgs,
extraState *AwsGenerateCommandExtraState,
) error {
askAgain := true
monitoredAccountIDsInput := ""
monitoredAccounts := []aws.AwsSubAccount{}

if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{
{
Prompt: &survey.Confirm{
Expand All @@ -558,7 +547,18 @@ func promptAgentlessQuestions(
return err
}

monitoredAccountIDsInput := ""

if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{
{
Prompt: &survey.Input{
Message: QuestionPrimaryAwsAccountProfile,
Default: config.AwsProfile,
},
Opts: []survey.AskOpt{survey.WithValidator(validateAwsProfile)},
Response: &config.AwsProfile,
Required: true,
},
{
Prompt: &survey.Input{
Message: QuestionAgentlessManagementAccountID,
Expand Down Expand Up @@ -588,67 +588,6 @@ func promptAgentlessQuestions(
config.AgentlessMonitoredAccountIDs = strings.Split(monitoredAccountIDsInput, ",")
}

// If there are existing monitored accounts configured (i.e., from the CLI),
// display them and ask if they want to add more
if len(config.AgentlessMonitoredAccounts) > 0 {
accountListing := []string{}
for _, account := range config.AgentlessMonitoredAccounts {
accountListing = append(
accountListing,
fmt.Sprintf("%s:%s", account.AwsProfile, account.AwsRegion),
)
}

if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{
Prompt: &survey.Confirm{
Message: fmt.Sprintf(
QuestionAgentlessMonitoredAccountsReplace,
strings.Trim(strings.Join(strings.Fields(fmt.Sprint(accountListing)), ", "), "[]"),
),
},
Response: &askAgain}); err != nil {
return err
}
}

for askAgain && extraState.EnableAgentlessOrganization {
var accountProfileName string
var accountProfileRegion string

if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{
{
Prompt: &survey.Input{Message: QuestionAgentlessMonitoredAccountProfile},
Opts: []survey.AskOpt{survey.WithValidator(validateAwsProfile)},
Required: true,
Response: &accountProfileName,
},
{
Prompt: &survey.Input{Message: QuestionAgentlessMonitoredAccountRegion},
Opts: []survey.AskOpt{survey.WithValidator(validateAwsRegion)},
Required: true,
Response: &accountProfileRegion,
},
}); err != nil {
return err
}

monitoredAccounts = append(
monitoredAccounts,
aws.AwsSubAccount{AwsProfile: accountProfileName, AwsRegion: accountProfileRegion})

if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{
Prompt: &survey.Confirm{Message: QuestionAgentlessMonitoredAccountAddMore},
Response: &askAgain,
}); err != nil {
return err
}
}

// If we created new accounts, re-write config
if len(monitoredAccounts) > 0 {
config.AgentlessMonitoredAccounts = monitoredAccounts
}

return nil
}

Expand Down
28 changes: 9 additions & 19 deletions integration/aws_generation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -930,8 +930,8 @@ func TestGenerationAwsS3BucketNotificationInteractive(t *testing.T) {
assert.Equal(t, buildTf, tfResult)
}

// Test using agentless with monitored accounts
func TestGenerationAgentlessWithMonitoredAccounts(t *testing.T) {
// Test Agentless organization integration
func TestGenerationAgentlessOrganization(t *testing.T) {
os.Setenv("LW_NOCACHE", "true")
defer os.Setenv("LW_NOCACHE", "")
var final string
Expand All @@ -947,17 +947,10 @@ func TestGenerationAgentlessWithMonitoredAccounts(t *testing.T) {
MsgRsp{cmd.QuestionAwsRegion, region},
MsgRsp{cmd.QuestionAwsConfigAdvanced, "y"},
MsgMenu{cmd.AwsAdvancedOptDone, 0},
MsgRsp{cmd.QuestionAgentlessName, "custom_agentless_name"},
MsgRsp{cmd.QuestionEnableAgentlessMultiAccount, "y"},
MsgRsp{cmd.QuestionEnableAgentlessOrganization, "y"},
MsgRsp{cmd.QuestionPrimaryAwsAccountProfile, "default-profile"},
MsgRsp{cmd.QuestionAgentlessManagementAccountID, "123456789000"},
MsgRsp{cmd.QuestionAgentlessMonitoredAccountID, "123456789001"},
MsgRsp{cmd.QuestionAgentlessMonitoredAccountProfile, "monitored-account-1"},
MsgRsp{cmd.QuestionAgentlessMonitoredAccountRegion, "us-east-1"},
MsgRsp{cmd.QuestionAgentlessMonitoredAccountAddMore, "y"},
MsgRsp{cmd.QuestionAgentlessMonitoredAccountID, "123456789002"},
MsgRsp{cmd.QuestionAgentlessMonitoredAccountProfile, "monitored-account-2"},
MsgRsp{cmd.QuestionAgentlessMonitoredAccountRegion, "us-east-2"},
MsgRsp{cmd.QuestionAgentlessMonitoredAccountAddMore, "n"},
MsgRsp{cmd.QuestionAgentlessMonitoredAccountIDs, "123456789000,ou-abcd-12345678,r-abcd"},
MsgRsp{cmd.QuestionAwsAnotherAdvancedOpt, "n"},
MsgRsp{cmd.QuestionRunTfPlan, "n"},
})
Expand All @@ -972,14 +965,11 @@ func TestGenerationAgentlessWithMonitoredAccounts(t *testing.T) {
assert.Contains(t, final, "Terraform code saved in")

// Create the TF directly with lwgenerate and validate same result via CLI
buildTf, _ := aws.NewTerraform(region, true, true, true,
buildTf, _ := aws.NewTerraform(region, true, false, false,
aws.UseConsolidatedCloudtrail(),
aws.WithAwsProfile("default"),
aws.WithAgentlessName("custom_agentless_name"),
aws.WithAgentlessMonitoredAccounts(
aws.AwsSubAccount{AwsProfile: "monitored-account-1", AwsRegion: "us-east-1", AccountID: "123456789001"},
aws.AwsSubAccount{AwsProfile: "monitored-account-2", AwsRegion: "us-east-2", AccountID: "123456789002"},
),
aws.WithAwsProfile("default-profile"),
aws.WithAgentlessManagementAccountID("123456789000"),
aws.WithAgentlessMonitoredAccountIDs([]string{"123456789000", "ou-abcd-12345678", "r-abcd"}),
).Generate()
assert.Equal(t, buildTf, tfResult)
}
Expand Down
129 changes: 91 additions & 38 deletions lwgenerate/aws/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,6 @@ type GenerateAwsTfConfigurationArgs struct {
// Agentless Monitored AWS account IDs, OUs, or the organization root.
AgentlessMonitoredAccountIDs []string

// Monitored AWS accounts for multi-account agentless integration
AgentlessMonitoredAccounts []AwsSubAccount

// Should we configure Cloudtrail integration in LW?
Cloudtrail bool

Expand Down Expand Up @@ -263,13 +260,6 @@ func WithAgentlessMonitoredAccountIDs(accountIDs []string) AwsTerraformModifier
}
}

// WithAgentlessMonitoredAccounts Set Agentless monitored accounts
func WithAgentlessMonitoredAccounts(accounts ...AwsSubAccount) AwsTerraformModifier {
return func(c *GenerateAwsTfConfigurationArgs) {
c.AgentlessMonitoredAccounts = accounts
}
}

// ExistingCloudtrailBucketArn Set the bucket ARN of an existing Cloudtrail setup
func ExistingCloudtrailBucketArn(arn string) AwsTerraformModifier {
return func(c *GenerateAwsTfConfigurationArgs) {
Expand Down Expand Up @@ -458,11 +448,8 @@ func createAwsProvider(args *GenerateAwsTfConfigurationArgs) ([]*hclwrite.Block,
}

if args.AwsProfile != "" {
attrs["profile"] = args.AwsProfile
}

if len(args.SubAccounts) > 0 {
attrs["alias"] = "main"
attrs["profile"] = args.AwsProfile
}

provider, err := lwgenerate.NewProvider(
Expand All @@ -476,10 +463,8 @@ func createAwsProvider(args *GenerateAwsTfConfigurationArgs) ([]*hclwrite.Block,
blocks = append(blocks, provider)
}

accounts := append(args.SubAccounts, args.AgentlessMonitoredAccounts...)

if len(accounts) > 0 {
for _, subaccount := range accounts {
if len(args.SubAccounts) > 0 {
for _, subaccount := range args.SubAccounts {
attrs := map[string]interface{}{
"alias": subaccount.AwsProfile,
"profile": subaccount.AwsProfile,
Expand Down Expand Up @@ -667,11 +652,12 @@ func createAgentless(args *GenerateAwsTfConfigurationArgs) ([]*hclwrite.Block, e

blocks := []*hclwrite.Block{}

enableMultiAccount := args.AgentlessManagementAccountID != "" && len(args.AgentlessMonitoredAccountIDs) > 0

globalModuleAttributes := map[string]interface{}{
"global": true,
"regional": true,
}
enableMultiAccount := args.AgentlessManagementAccountID != "" && len(args.AgentlessMonitoredAccounts) > 0

if enableMultiAccount {
ids := []string{}
Expand All @@ -684,12 +670,21 @@ func createAgentless(args *GenerateAwsTfConfigurationArgs) ([]*hclwrite.Block, e
})
}

globalModuleModifiers := []lwgenerate.HclModuleModifier{
lwgenerate.HclModuleWithVersion(lwgenerate.AwsAgentlessVersion),
lwgenerate.HclModuleWithAttributes(globalModuleAttributes),
}

if args.AwsProfile != "" {
globalModuleModifiers = append(globalModuleModifiers,
lwgenerate.HclModuleWithProviderDetails(map[string]string{"aws": "aws.main"}))
}

// Add global module
globalModule, err := lwgenerate.NewModule(
"lacework_aws_agentless_scanning_global",
lwgenerate.AwsAgentlessSource,
lwgenerate.HclModuleWithVersion(lwgenerate.AwsAgentlessVersion),
lwgenerate.HclModuleWithAttributes(globalModuleAttributes),
globalModuleModifiers...,
).ToBlock()

if err != nil {
Expand Down Expand Up @@ -737,9 +732,7 @@ func createAgentless(args *GenerateAwsTfConfigurationArgs) ([]*hclwrite.Block, e
"lacework_aws_agentless_management_scanning_role",
lwgenerate.AwsAgentlessSource,
lwgenerate.HclModuleWithVersion(lwgenerate.AwsAgentlessVersion),
lwgenerate.HclModuleWithProviderDetails(map[string]string{
"aws": fmt.Sprintf("aws.%s", args.AwsProfile),
}),
lwgenerate.HclModuleWithProviderDetails(map[string]string{"aws": "aws.main"}),
lwgenerate.HclModuleWithAttributes(attributes),
).ToBlock()

Expand All @@ -749,24 +742,84 @@ func createAgentless(args *GenerateAwsTfConfigurationArgs) ([]*hclwrite.Block, e

blocks = append(blocks, managementModule)

// Add management modules
for _, monitoredAccount := range args.AgentlessMonitoredAccounts {
monitoredModule, err := lwgenerate.NewModule(
fmt.Sprintf("lacework_aws_agentless_monitored_scanning_role_%s", monitoredAccount.AwsProfile),
lwgenerate.AwsAgentlessSource,
lwgenerate.HclModuleWithVersion(lwgenerate.AwsAgentlessVersion),
lwgenerate.HclModuleWithProviderDetails(map[string]string{
"aws": fmt.Sprintf("aws.%s", monitoredAccount.AwsProfile),
}),
lwgenerate.HclModuleWithAttributes(attributes),
).ToBlock()
autoDeploymentBlock, err := lwgenerate.HclCreateGenericBlock(
"auto_deployment",
nil,
map[string]interface{}{"enabled": true, "retain_stacks_on_account_removal": false},
)

if err != nil {
return nil, err
if err != nil {
return nil, err
}

stacksetResource, err := lwgenerate.NewResource(
"aws_cloudformation_stack_set",
"snapshot_role",
lwgenerate.HclResourceWithAttributesAndProviderDetails(
map[string]interface{}{
"capabilities": lwgenerate.CreateSimpleTraversal([]string{"[\"CAPABILITY_NAMED_IAM\"]"}),
"description": "Lacework AWS Agentless Workload Scanning Organization Roles",
"name": "lacework-agentless-scanning-stackset",
"permission_model": "SERVICE_MANAGED",
"template_url": "https://agentless-workload-scanner.s3.amazonaws.com" +
"/cloudformation-lacework/latest/snapshot-role.json",
"parameters": lwgenerate.CreateMapTraversalTokens(map[string]string{
"ExternalId": "module.lacework_aws_agentless_scanning_global.external_id",
"ECSTaskRoleArn": "module.lacework_aws_agentless_scanning_global.agentless_scan_ecs_task_role_arn",
"ResourceNamePrefix": "module.lacework_aws_agentless_scanning_global.prefix",
"ResourceNameSuffix": "module.lacework_aws_agentless_scanning_global.suffix",
}),
},
[]string{"aws.main"},
),
lwgenerate.HclResourceWithGenericBlocks(autoDeploymentBlock),
).ToResourceBlock()

if err != nil {
return nil, err
}

blocks = append(blocks, stacksetResource)

// Get OU IDs for the organizational_unit_ids attribute
OUIDs := []string{}
for _, accountID := range args.AgentlessMonitoredAccountIDs {
if strings.HasPrefix(accountID, "ou-") {
OUIDs = append(OUIDs, fmt.Sprintf("\"%s\"", accountID))
}
}

blocks = append(blocks, monitoredModule)
deploymentTargetsBlock, err := lwgenerate.HclCreateGenericBlock(
"deployment_targets",
nil,
map[string]interface{}{"organizational_unit_ids": lwgenerate.CreateSimpleTraversal(
[]string{fmt.Sprintf("[%s]", strings.Join(OUIDs, ","))},
)},
)

if err != nil {
return nil, err
}

stacksetInstanceResource, err := lwgenerate.NewResource(
"aws_cloudformation_stack_set_instance",
"snapshot_role",
lwgenerate.HclResourceWithAttributesAndProviderDetails(
map[string]interface{}{
"stack_set_name": lwgenerate.CreateSimpleTraversal(
[]string{"aws_cloudformation_stack_set", "snapshot_role"},
),
},
[]string{"aws.main"},
),
lwgenerate.HclResourceWithGenericBlocks(deploymentTargetsBlock),
).ToResourceBlock()

if err != nil {
return nil, err
}

blocks = append(blocks, stacksetInstanceResource)
}

return blocks, nil
Expand Down
Loading

0 comments on commit a40311f

Please sign in to comment.