Skip to content

Commit

Permalink
feat: support Agentless origanization integration with auto snapshot (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
PengyuanZhao authored Nov 8, 2023
1 parent d5969ab commit 6084c10
Show file tree
Hide file tree
Showing 9 changed files with 921 additions and 213 deletions.
204 changes: 193 additions & 11 deletions cli/cmd/generate_aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,18 @@ import (

var (
// Define question text here so they can be reused in testing
QuestionEnableAgentless = "Enable Agentless integration?"
QuestionEnableAgentless = "Enable Agentless integration?"
QuestionEnableAgentlessOrganization = "Enable Agentless organizational integration?"
QuestionAgentlessManagementAccountID = "Specify the AWS management account ID:"
QuestionAgentlessMonitoredAccountIDs = "Specify the AWS monitored account ID list" +
"(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 monitored AWS account profile name:"
QuestionAgentlessMonitoredAccountRegion = "Specify monitored AWS account region:"
QuestionAgentlessMonitoredAccountAddMore = "Add another monitored AWS account?"
QuestionAgentlessMonitoredAccountsReplace = "Currently configured Agentless monitored accounts: %s, replace?"

QuestionAwsEnableConfig = "Enable configuration integration?"
QuestionCustomizeConfigName = "Customize Config integration name?"
QuestionConfigName = "Specify name of config integration (optional)"
Expand All @@ -28,15 +39,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 All @@ -58,7 +68,7 @@ var (

// select options
AwsAdvancedOptDone = "Done"
AdvancedOptAgentless = "Additional Agentless options (placeholder)"
AdvancedOptAgentless = "Additional Agentless options"
AdvancedOptCloudTrail = "Additional CloudTrail options"
AdvancedOptIamRole = "Configure Lacework integration with an existing IAM role"
AdvancedOptAwsAccounts = "Add additional AWS Accounts to Lacework"
Expand All @@ -69,6 +79,9 @@ var (
// AwsRegionRegex regex used for validating region input; note intentionally does not match gov cloud
AwsRegionRegex = `(af|ap|ca|eu|me|sa|us)-(central|(north|south)?(east|west)?)-\d`
AwsProfileRegex = `([A-Za-z_0-9-]+)`
AwsAccountIDRegex = `^\d{12}$`
AwsOUIDRegex = `^ou-[0-9a-z]{4,32}-[a-z0-9]{8,32}$`
AWSRootIDRegex = `^r-[0-9a-z]{4,32}$`
AwsAssumeRoleRegex = `^arn:aws:iam::\d{12}:role\/.*$`

GenerateAwsCommandState = &aws.GenerateAwsTfConfigurationArgs{}
Expand Down Expand Up @@ -116,6 +129,9 @@ See help output for more details on the parameter value(s) required for Terrafor
aws.WithAwsAssumeRole(GenerateAwsCommandState.AwsAssumeRole),
aws.WithLaceworkProfile(GenerateAwsCommandState.LaceworkProfile),
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 All @@ -141,6 +157,7 @@ See help output for more details on the parameter value(s) required for Terrafor
// Create new struct
data := aws.NewTerraform(
GenerateAwsCommandState.AwsRegion,
GenerateAwsCommandState.AwsOrganization,
GenerateAwsCommandState.Agentless,
GenerateAwsCommandState.Config,
GenerateAwsCommandState.Cloudtrail,
Expand Down Expand Up @@ -329,6 +346,21 @@ func (a *AwsGenerateCommandExtraState) writeCache() {
func initGenerateAwsTfCommandFlags() {
// add flags to sub commands
// TODO Share the help with the interactive generation
generateAwsTfCommand.PersistentFlags().BoolVar(
&GenerateAwsCommandState.AwsOrganization,
"aws_organization",
false,
"enable organization integration")
generateAwsTfCommand.PersistentFlags().StringVar(
&GenerateAwsCommandState.AgentlessManagementAccountID,
"agentless_management_account_id",
"",
"AWS management account ID for Agentless integration")
generateAwsTfCommand.PersistentFlags().StringSliceVar(
&GenerateAwsCommandState.AgentlessMonitoredAccountIDs,
"agentless_monitored_account_ids",
[]string{},
"AWS monitored account IDs for Agentless integrations")
generateAwsTfCommand.PersistentFlags().BoolVar(
&GenerateAwsCommandState.Agentless,
"agentless",
Expand Down Expand Up @@ -500,6 +532,31 @@ func validateOptionalAwsArnFormat(val interface{}) error {
return nil
}

func validateAwsAccountID(val interface{}) error {
return validateStringWithRegex(val, AwsAccountIDRegex, "invalid account ID supplied")
}

func validateAgentlessMonitoredAccountIDs(val interface{}) error {
switch value := val.(type) {
case string:
regex := fmt.Sprintf(`%s|%s|%s`, AwsAccountIDRegex, AwsOUIDRegex, AWSRootIDRegex)
ids := strings.Split(value, ",")
for _, id := range ids {
if err := validateStringWithRegex(
id,
regex,
fmt.Sprintf("invalid account ID, OU ID or root ID supplied: %s", id),
); err != nil {
return err
}
}
default:
// if the value passed is not a string
return errors.New("value must be a string")
}
return nil
}

// survey.Validator for aws region
func validateAwsRegion(val interface{}) error {
return validateStringWithRegex(val, AwsRegionRegex, "invalid region name supplied")
Expand All @@ -516,6 +573,122 @@ func validateAwsAssumeRole(val interface{}) error {
}

func promptAgentlessQuestions(config *aws.GenerateAwsTfConfigurationArgs) error {
if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{
{
Prompt: &survey.Confirm{
Message: QuestionEnableAgentlessOrganization,
Default: config.AwsOrganization,
},
Response: &config.AwsOrganization,
},
}, config.Agentless); err != nil {
return err
}

askAgain := true
monitoredAccounts := []aws.AwsSubAccount{}
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,
Default: config.AgentlessManagementAccountID,
},
Checks: []*bool{&config.AwsOrganization},
Opts: []survey.AskOpt{survey.WithValidator(validateAwsAccountID)},
Response: &config.AgentlessManagementAccountID,
Required: true,
},
{
Prompt: &survey.Input{
Message: QuestionAgentlessMonitoredAccountIDs,
Default: strings.Join(config.AgentlessMonitoredAccountIDs, ","),
Help: QuestionAgentlessMonitoredAccountIDsHelp,
},
Checks: []*bool{&config.AwsOrganization},
Opts: []survey.AskOpt{survey.WithValidator(validateAgentlessMonitoredAccountIDs)},
Response: &monitoredAccountIDsInput,
Required: true,
},
}, config.AwsOrganization); err != nil {
return err
}

if monitoredAccountIDsInput != "" {
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 && config.AwsOrganization {
var profile string
var region string

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

monitoredAccounts = append(
monitoredAccounts,
aws.AwsSubAccount{AwsProfile: profile, AwsRegion: region})

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 Expand Up @@ -951,6 +1124,15 @@ func promptAwsGenerate(
return errors.New("must enable agentless, cloudtrail or config")
}

if !cli.InteractiveMode() && config.AwsOrganization {
if config.AgentlessManagementAccountID == "" {
return errors.New("must specify a management account ID for Agentless organization integration")
}
if len(config.AgentlessMonitoredAccountIDs) == 0 {
return errors.New("must specify monitored account IDs for Agentless organization integration")
}
}

if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{
Prompt: &survey.Input{Message: QuestionAwsRegion, Default: config.AwsRegion},
Response: &config.AwsRegion,
Expand Down
61 changes: 32 additions & 29 deletions cli/docs/lacework_generate_cloud-account_aws.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,35 +37,38 @@ lacework generate cloud-account aws [flags]
### Options

```
--agentless enable agentless integration
--apply run terraform apply without executing plan or prompting
--aws_assume_role string specify aws assume role
--aws_profile string specify aws profile
--aws_region string specify aws region
--aws_subaccount strings configure an additional aws account; value format must be <aws profile>:<region>
--bucket_encryption_enabled enable S3 bucket encryption when creating bucket (default true)
--bucket_name string specify bucket name when creating bucket
--bucket_sse_key_arn string specify existing KMS encryption key arn for bucket
--cloudtrail enable cloudtrail integration
--cloudtrail_name string specify name of cloudtrail integration
--config enable config integration
--config_name string specify name of config integration
--consolidated_cloudtrail use consolidated trail
--existing_bucket_arn string specify existing cloudtrail S3 bucket ARN
--existing_iam_role_arn string specify existing iam role arn to use
--existing_iam_role_externalid string specify existing iam role external_id to use
--existing_iam_role_name string specify existing iam role name to use
--existing_sns_topic_arn string specify existing SNS topic arn
-h, --help help for aws
--lacework_aws_account_id string the Lacework AWS root account id
--output string location to write generated content (default is ~/lacework/aws)
--sns_topic_encryption_enabled enable encryption on SNS topic when creating one (default true)
--sns_topic_encryption_key_arn string specify existing KMS encryption key arn for SNS topic
--sns_topic_name string specify SNS topic name if creating new one
--sqs_encryption_enabled enable encryption on SQS queue when creating (default true)
--sqs_encryption_key_arn string specify existing KMS encryption key arn for SQS queue
--sqs_queue_name string specify SQS queue name if creating new one
--use_s3_bucket_notification enable S3 bucket notifications
--agentless enable agentless integration
--agentless_management_account_id string AWS management account ID for Agentless integration
--agentless_monitored_account_ids strings AWS monitored account IDs for Agentless integrations
--apply run terraform apply without executing plan or prompting
--aws_assume_role string specify aws assume role
--aws_organization enable organization integration
--aws_profile string specify aws profile
--aws_region string specify aws region
--aws_subaccount strings configure an additional aws account; value format must be <aws profile>:<region>
--bucket_encryption_enabled enable S3 bucket encryption when creating bucket (default true)
--bucket_name string specify bucket name when creating bucket
--bucket_sse_key_arn string specify existing KMS encryption key arn for bucket
--cloudtrail enable cloudtrail integration
--cloudtrail_name string specify name of cloudtrail integration
--config enable config integration
--config_name string specify name of config integration
--consolidated_cloudtrail use consolidated trail
--existing_bucket_arn string specify existing cloudtrail S3 bucket ARN
--existing_iam_role_arn string specify existing iam role arn to use
--existing_iam_role_externalid string specify existing iam role external_id to use
--existing_iam_role_name string specify existing iam role name to use
--existing_sns_topic_arn string specify existing SNS topic arn
-h, --help help for aws
--lacework_aws_account_id string the Lacework AWS root account id
--output string location to write generated content (default is ~/lacework/aws)
--sns_topic_encryption_enabled enable encryption on SNS topic when creating one (default true)
--sns_topic_encryption_key_arn string specify existing KMS encryption key arn for SNS topic
--sns_topic_name string specify SNS topic name if creating new one
--sqs_encryption_enabled enable encryption on SQS queue when creating (default true)
--sqs_encryption_key_arn string specify existing KMS encryption key arn for SQS queue
--sqs_queue_name string specify SQS queue name if creating new one
--use_s3_bucket_notification enable S3 bucket notifications
```

### Options inherited from parent commands
Expand Down
Loading

0 comments on commit 6084c10

Please sign in to comment.