Skip to content

Commit

Permalink
chore: update user flow and validation for using existing CloudTrail (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
PengyuanZhao authored Jan 24, 2024
1 parent ded4e39 commit 79f36d1
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 102 deletions.
86 changes: 55 additions & 31 deletions cli/cmd/generate_aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ var (

// CloudTrail questions
QuestionEnableCloudtrail = "Enable CloudTrail integration?"
QuestionCloudtrailName = "Name of cloudtrail integration (optional):"
QuestionCloudtrailName = "Existing trail name:"
QuestionCloudtrailAdvanced = "Configure advanced options?"

// CloudTrail Control Tower questions
Expand Down Expand Up @@ -97,7 +97,7 @@ var (

// CloudTrail S3 Bucket Questions
QuestionCloudtrailUseConsolidated = "Use consolidated CloudTrail?"
QuestionCloudtrailUseExistingS3 = "Use an existing CloudTrail?"
QuestionCloudtrailUseExistingTrail = "Use an existing CloudTrail?"
QuestionCloudtrailS3ExistingBucketArn = "Existing S3 bucket ARN used for CloudTrail logs:"
QuestionCloudtrailS3BucketEnableEncryption = "Enable S3 bucket encryption"

Expand All @@ -106,7 +106,7 @@ var (
QuestionCloudtrailS3BucketNotification = "Enable S3 bucket notifications"

// CloudTrail SNS Topic Questions
QuestionCloudtrailUseExistingSNSTopic = "Use an existing SNS topic?"
QuestionCloudtrailUseExistingSNSTopic = "Use an existing SNS topic? (If not, S3 notification will be used)"
QuestionCloudtrailSnsExistingTopicArn = "Existing SNS topic arn:"
QuestionCloudtrailSnsEnableEncryption = "Enable encryption on SNS topic?"
QuestionCloudtrailSnsEncryptionKeyArn = "Existing KMS encryption key arn for SNS topic (optional):"
Expand Down Expand Up @@ -204,7 +204,7 @@ See help output for more details on the parameter value(s) required for Terrafor
aws.WithControlTowerLogArchiveAccount(GenerateAwsCommandState.ControlTowerLogArchiveAccount),
aws.WithControlTowerKmsKeyArn(GenerateAwsCommandState.ControlTowerKmsKeyArn),
aws.WithConsolidatedCloudtrail(GenerateAwsCommandState.ConsolidatedCloudtrail),
aws.WithCloudtrailUseExistingS3(GenerateAwsCommandState.CloudtrailUseExistingS3),
aws.WithCloudtrailUseExistingTrail(GenerateAwsCommandState.CloudtrailUseExistingTrail),
aws.WithCloudtrailUseExistingSNSTopic(GenerateAwsCommandState.CloudtrailUseExistingSNSTopic),
aws.WithExistingCloudtrailBucketArn(GenerateAwsCommandState.ExistingCloudtrailBucketArn),
aws.WithExistingSnsTopicArn(GenerateAwsCommandState.ExistingSnsTopicArn),
Expand Down Expand Up @@ -326,7 +326,7 @@ See help output for more details on the parameter value(s) required for Terrafor
return err
}
if arn != "" {
GenerateAwsCommandState.CloudtrailUseExistingS3 = true
GenerateAwsCommandState.CloudtrailUseExistingTrail = true
}

// Validate SNS Topic Arn if passed
Expand Down Expand Up @@ -1027,6 +1027,10 @@ func promptCloudtrailQuestions(
return err
}

if err := promptCloudtrailExistingTrailQuestions(config); err != nil {
return err
}

// Find out if the customer wants to specify more advanced features
if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{
Icon: IconCloudTrail,
Expand All @@ -1046,6 +1050,13 @@ func promptCloudtrailQuestions(
OptCloudtrailIAM,
OptCloudtrailDone,
}
if config.CloudtrailUseExistingTrail {
options = []string{
OptCloudtrailSQS,
OptCloudtrailIAM,
OptCloudtrailDone,
}
}
if config.AwsOrganization {
if config.ControlTower {
options = []string{
Expand Down Expand Up @@ -1288,22 +1299,31 @@ func promptCloudtrailKmsKeyQuestions(config *aws.GenerateAwsTfConfigurationArgs)
return nil
}

func promptCloudtrailS3Questions(config *aws.GenerateAwsTfConfigurationArgs) error {
func promptCloudtrailExistingTrailQuestions(config *aws.GenerateAwsTfConfigurationArgs) error {
if config.ControlTower {
return nil
}

if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{
{
Icon: IconCloudTrail,
Prompt: &survey.Confirm{Message: QuestionCloudtrailUseExistingS3, Default: config.CloudtrailUseExistingS3},
Response: &config.CloudtrailUseExistingS3,
Prompt: &survey.Confirm{Message: QuestionCloudtrailUseExistingTrail, Default: config.CloudtrailUseExistingTrail},
Response: &config.CloudtrailUseExistingTrail,
},
}); err != nil {
return err
}

if !config.CloudtrailUseExistingTrail {
return nil
}

if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{
{
Icon: IconCloudTrail,
Prompt: &survey.Input{Message: QuestionCloudtrailName, Default: config.CloudtrailName},
Response: &config.CloudtrailName,
Required: true,
},
{
Icon: IconCloudTrail,
Expand All @@ -1315,10 +1335,35 @@ func promptCloudtrailS3Questions(config *aws.GenerateAwsTfConfigurationArgs) err
Opts: []survey.AskOpt{survey.WithValidator(validateAwsArnFormat)},
Response: &config.ExistingCloudtrailBucketArn,
},
}, config.CloudtrailUseExistingS3); err != nil {
{
Icon: IconCloudTrail,
Prompt: &survey.Confirm{
Message: QuestionCloudtrailUseExistingSNSTopic,
Default: config.CloudtrailUseExistingSNSTopic,
},
Response: &config.CloudtrailUseExistingSNSTopic,
},
{
Icon: IconCloudTrail,
Prompt: &survey.Input{Message: QuestionCloudtrailSnsExistingTopicArn, Default: config.ExistingSnsTopicArn},
Checks: []*bool{&config.CloudtrailUseExistingSNSTopic},
Required: true,
Opts: []survey.AskOpt{survey.WithValidator(validateAwsArnFormat)},
Response: &config.ExistingSnsTopicArn,
},
}); err != nil {
return err
}

// If no SNS topic is provided for the existing trail, fallback to use S3 notification
if !config.CloudtrailUseExistingSNSTopic {
config.S3BucketNotification = true
}

return nil
}

func promptCloudtrailS3Questions(config *aws.GenerateAwsTfConfigurationArgs) error {
if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{
{
Icon: IconCloudTrail,
Expand All @@ -1345,35 +1390,14 @@ func promptCloudtrailS3Questions(config *aws.GenerateAwsTfConfigurationArgs) err
Prompt: &survey.Confirm{Message: QuestionCloudtrailS3BucketNotification, Default: config.S3BucketNotification},
Response: &config.S3BucketNotification,
},
}, !config.CloudtrailUseExistingS3); err != nil {
}, !config.CloudtrailUseExistingTrail); err != nil {
return err
}

return nil
}

func promptCloudtrailSNSQuestions(config *aws.GenerateAwsTfConfigurationArgs) error {
if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{
{
Icon: IconCloudTrail,
Prompt: &survey.Confirm{
Message: QuestionCloudtrailUseExistingSNSTopic,
Default: config.CloudtrailUseExistingSNSTopic,
},
Response: &config.CloudtrailUseExistingSNSTopic,
},
{
Icon: IconCloudTrail,
Prompt: &survey.Input{Message: QuestionCloudtrailSnsExistingTopicArn, Default: config.ExistingSnsTopicArn},
Checks: []*bool{&config.CloudtrailUseExistingSNSTopic},
Required: true,
Opts: []survey.AskOpt{survey.WithValidator(validateAwsArnFormat)},
Response: &config.ExistingSnsTopicArn,
},
}); err != nil {
return err
}

if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{
{
Icon: IconCloudTrail,
Expand Down
76 changes: 18 additions & 58 deletions integration/aws_generation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ func TestGenerationAwsNoninteractive(t *testing.T) {
aws.WithConfigOrgCfResourcePrefix(cfResourcePrefix),
aws.WithConsolidatedCloudtrail(true),
aws.WithOrgAccountMappings(orgAccountMappings),
aws.WithCloudtrailUseExistingS3(true),
aws.WithCloudtrailUseExistingTrail(true),
aws.WithCloudtrailName(cloudtrailName),
aws.WithExistingCloudtrailBucketArn(s3BucketArn),
aws.WithCloudtrailUseExistingSNSTopic(true),
Expand Down Expand Up @@ -397,6 +397,7 @@ func TestGenerationAwsCloudtrail(t *testing.T) {
MsgRsp{cmd.QuestionEnableConfig, "n"},
MsgRsp{cmd.QuestionEnableCloudtrail, "y"},
MsgRsp{cmd.QuestionCloudtrailUseConsolidated, "y"},
MsgRsp{cmd.QuestionCloudtrailUseExistingTrail, "n"},
MsgRsp{cmd.QuestionCloudtrailAdvanced, "n"},
MsgRsp{cmd.QuestionAwsOutputLocation, ""},
MsgRsp{cmd.QuestionRunTfPlan, "n"},
Expand Down Expand Up @@ -438,6 +439,7 @@ func TestGenerationAwsCloudtrailOrganization(t *testing.T) {
MsgRsp{cmd.QuestionEnableCloudtrail, "y"},
MsgRsp{cmd.QuestionControlTower, "n"},
MsgRsp{cmd.QuestionCloudtrailUseConsolidated, "y"},
MsgRsp{cmd.QuestionCloudtrailUseExistingTrail, "n"},
MsgRsp{cmd.QuestionCloudtrailAdvanced, "y"},
MsgMenu{cmd.OptCloudtrailOrg, 0},
MsgRsp{cmd.QuestionCloudtrailOrgAccountMappingsDefaultLWAccount, "main"},
Expand Down Expand Up @@ -551,14 +553,15 @@ func TestGenerationAwsCloudtrailControlTower(t *testing.T) {
assert.Equal(t, buildTf, string(tfResult))
}

// Test CloudTrail integration with existing S3 bucket
func TestGenerationAwsCloudtrailWithExistingS3Bucket(t *testing.T) {
// Test CloudTrail integration with existing trail
func TestGenerationAwsCloudtrailWithExistingTrail(t *testing.T) {
os.Setenv("LW_NOCACHE", "true")
defer os.Setenv("LW_NOCACHE", "")
var final string

cloudtrailName := "cloudtrail-integration-name"
s3BucketArn := "arn:aws:s3:::bucket-name"
snsTopicArn := "arn:aws:sns:us-east-2:249446771485:topic-name"

// Run CLI
tfResult := runGenerateTest(t,
Expand All @@ -571,12 +574,12 @@ func TestGenerationAwsCloudtrailWithExistingS3Bucket(t *testing.T) {
MsgRsp{cmd.QuestionEnableConfig, "n"},
MsgRsp{cmd.QuestionEnableCloudtrail, "y"},
MsgRsp{cmd.QuestionCloudtrailUseConsolidated, "n"},
MsgRsp{cmd.QuestionCloudtrailAdvanced, "y"},
MsgMenu{cmd.OptCloudtrailS3, 0},
MsgRsp{cmd.QuestionCloudtrailUseExistingS3, "y"},
MsgRsp{cmd.QuestionCloudtrailUseExistingTrail, "y"},
MsgRsp{cmd.QuestionCloudtrailName, cloudtrailName},
MsgRsp{cmd.QuestionCloudtrailS3ExistingBucketArn, s3BucketArn},
MsgMenu{cmd.OptCloudtrailDone, 4},
MsgRsp{cmd.QuestionCloudtrailUseExistingSNSTopic, "y"},
MsgRsp{cmd.QuestionCloudtrailSnsExistingTopicArn, snsTopicArn},
MsgRsp{cmd.QuestionCloudtrailAdvanced, "n"},
MsgRsp{cmd.QuestionAwsOutputLocation, ""},
MsgRsp{cmd.QuestionRunTfPlan, "n"},
})
Expand All @@ -594,9 +597,11 @@ func TestGenerationAwsCloudtrailWithExistingS3Bucket(t *testing.T) {
buildTf, _ := aws.NewTerraform(false, false, false, true,
aws.WithAwsProfile("main"),
aws.WithAwsRegion("us-east-2"),
aws.WithCloudtrailUseExistingS3(true),
aws.WithCloudtrailUseExistingTrail(true),
aws.WithCloudtrailName(cloudtrailName),
aws.WithExistingCloudtrailBucketArn(s3BucketArn),
aws.WithCloudtrailUseExistingSNSTopic(true),
aws.WithExistingSnsTopicArn(snsTopicArn),
).Generate()
assert.Equal(t, buildTf, tfResult)
}
Expand All @@ -621,9 +626,9 @@ func TestGenerationAwsCloudtrailWithNewS3Bucket(t *testing.T) {
MsgRsp{cmd.QuestionEnableConfig, "n"},
MsgRsp{cmd.QuestionEnableCloudtrail, "y"},
MsgRsp{cmd.QuestionCloudtrailUseConsolidated, "n"},
MsgRsp{cmd.QuestionCloudtrailUseExistingTrail, "n"},
MsgRsp{cmd.QuestionCloudtrailAdvanced, "y"},
MsgMenu{cmd.OptCloudtrailS3, 0},
MsgRsp{cmd.QuestionCloudtrailUseExistingS3, "n"},
MsgRsp{cmd.QuestionCloudtrailS3BucketName, s3BucketName},
MsgRsp{cmd.QuestionCloudtrailS3BucketEnableEncryption, "y"},
MsgRsp{cmd.QuestionCloudtrailS3BucketSseKeyArn, kmsArn},
Expand All @@ -646,7 +651,7 @@ func TestGenerationAwsCloudtrailWithNewS3Bucket(t *testing.T) {
buildTf, _ := aws.NewTerraform(false, false, false, true,
aws.WithAwsProfile("main"),
aws.WithAwsRegion("us-east-2"),
aws.WithCloudtrailUseExistingS3(false),
aws.WithCloudtrailUseExistingTrail(false),
aws.WithBucketName(s3BucketName),
aws.WithBucketEncryptionEnabled(true),
aws.WithBucketSSEKeyArn(kmsArn),
Expand All @@ -655,53 +660,6 @@ func TestGenerationAwsCloudtrailWithNewS3Bucket(t *testing.T) {
assert.Equal(t, buildTf, tfResult)
}

// Test CloudTrail integration with existing SNS topic
func TestGenerationAwsCloudtrailWithExistingSnsTopic(t *testing.T) {
os.Setenv("LW_NOCACHE", "true")
defer os.Setenv("LW_NOCACHE", "")
var final string

snsTopicArn := "arn:aws:sns:us-east-2:249446771485:topic-name"

// Run CLI
tfResult := runGenerateTest(t,
func(c *expect.Console) {
expectsCliOutput(t, c, []MsgRspHandler{
MsgRsp{cmd.QuestionEnableAwsOrganization, "n"},
MsgRsp{cmd.QuestionMainAwsProfile, "main"},
MsgRsp{cmd.QuestionMainAwsRegion, "us-east-2"},
MsgRsp{cmd.QuestionEnableAgentless, "n"},
MsgRsp{cmd.QuestionEnableConfig, "n"},
MsgRsp{cmd.QuestionEnableCloudtrail, "y"},
MsgRsp{cmd.QuestionCloudtrailUseConsolidated, "n"},
MsgRsp{cmd.QuestionCloudtrailAdvanced, "y"},
MsgMenu{cmd.OptCloudtrailSNS, 1},
MsgRsp{cmd.QuestionCloudtrailUseExistingSNSTopic, "y"},
MsgRsp{cmd.QuestionCloudtrailSnsExistingTopicArn, snsTopicArn},
MsgMenu{cmd.OptCloudtrailDone, 4},
MsgRsp{cmd.QuestionAwsOutputLocation, ""},
MsgRsp{cmd.QuestionRunTfPlan, "n"},
})
final, _ = c.ExpectEOF()
},
"generate",
"cloud-account",
"aws",
)

// Ensure CLI ran correctly
assert.Contains(t, final, "Terraform code saved in")

// Create the TF directly with lwgenerate and validate same result via CLI
buildTf, _ := aws.NewTerraform(false, false, false, true,
aws.WithAwsProfile("main"),
aws.WithAwsRegion("us-east-2"),
aws.WithCloudtrailUseExistingSNSTopic(true),
aws.WithExistingSnsTopicArn(snsTopicArn),
).Generate()
assert.Equal(t, buildTf, tfResult)
}

// Test CloudTrail integration with new SNS topic
func TestGenerationAwsCloudtrailWithNewSNSTopic(t *testing.T) {
os.Setenv("LW_NOCACHE", "true")
Expand All @@ -722,9 +680,9 @@ func TestGenerationAwsCloudtrailWithNewSNSTopic(t *testing.T) {
MsgRsp{cmd.QuestionEnableConfig, "n"},
MsgRsp{cmd.QuestionEnableCloudtrail, "y"},
MsgRsp{cmd.QuestionCloudtrailUseConsolidated, "n"},
MsgRsp{cmd.QuestionCloudtrailUseExistingTrail, "n"},
MsgRsp{cmd.QuestionCloudtrailAdvanced, "y"},
MsgMenu{cmd.OptCloudtrailSNS, 1},
MsgRsp{cmd.QuestionCloudtrailUseExistingSNSTopic, "n"},
MsgRsp{cmd.QuestionCloudtrailSnsTopicName, snsTopicName},
MsgRsp{cmd.QuestionCloudtrailSnsEnableEncryption, "y"},
MsgRsp{cmd.QuestionCloudtrailSnsEncryptionKeyArn, kmsArn},
Expand Down Expand Up @@ -774,6 +732,7 @@ func TestGenerationAwsCloudtrailWithNewSQSQueue(t *testing.T) {
MsgRsp{cmd.QuestionEnableConfig, "n"},
MsgRsp{cmd.QuestionEnableCloudtrail, "y"},
MsgRsp{cmd.QuestionCloudtrailUseConsolidated, "n"},
MsgRsp{cmd.QuestionCloudtrailUseExistingTrail, "n"},
MsgRsp{cmd.QuestionCloudtrailAdvanced, "y"},
MsgMenu{cmd.OptCloudtrailSQS, 2},
MsgRsp{cmd.QuestionCloudtrailSqsQueueName, sqsQueueName},
Expand Down Expand Up @@ -825,6 +784,7 @@ func TestGenerationAwsCloudtrailWithExistingIamRole(t *testing.T) {
MsgRsp{cmd.QuestionEnableConfig, "n"},
MsgRsp{cmd.QuestionEnableCloudtrail, "y"},
MsgRsp{cmd.QuestionCloudtrailUseConsolidated, "n"},
MsgRsp{cmd.QuestionCloudtrailUseExistingTrail, "n"},
MsgRsp{cmd.QuestionCloudtrailAdvanced, "y"},
MsgMenu{cmd.OptCloudtrailIAM, 3},
MsgRsp{cmd.QuestionCloudtrailExistingIamRoleName, roleName},
Expand Down
Loading

0 comments on commit 79f36d1

Please sign in to comment.