Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support Agentless origanization integration with auto snapshot #1424

Merged
merged 13 commits into from
Nov 8, 2023
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,
PengyuanZhao marked this conversation as resolved.
Show resolved Hide resolved
},
}, 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