Skip to content

Commit 4c1c21c

Browse files
feat: lwgenerate for aws controltower module (#1327)
* feat: lwgenerate for aws controltower module Signed-off-by: Darren Murray <[email protected]> * refactor: address code review comments Signed-off-by: Darren Murray <[email protected]> --------- Signed-off-by: Darren Murray <[email protected]>
1 parent 182e9ad commit 4c1c21c

File tree

13 files changed

+1854
-8
lines changed

13 files changed

+1854
-8
lines changed

cli/cmd/generate.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ func init() {
4747
iacGenerateTfCommand.AddCommand(generateAwsTfCommand)
4848
iacGenerateTfCommand.AddCommand(generateGcpTfCommand)
4949
iacGenerateTfCommand.AddCommand(generateAzureTfCommand)
50+
51+
// aws subcommands
52+
generateAwsTfCommand.AddCommand(generateAwsControlTowerTfCommand)
5053
}
5154

5255
type SurveyQuestionWithValidationArgs struct {

cli/cmd/generate_aws_controltower.go

Lines changed: 730 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package cmd
2+
3+
import (
4+
"testing"
5+
6+
"github.com/lacework/go-sdk/lwgenerate/aws_controltower"
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func toggleControlTowerNonInteractive() {
11+
cli.noCache = !cli.noCache
12+
cli.nonInteractive = !cli.nonInteractive
13+
}
14+
15+
func TestGenerateBasicControlTowerArgs(t *testing.T) {
16+
toggleControlTowerNonInteractive()
17+
defer toggleControlTowerNonInteractive()
18+
19+
data := aws_controltower.GenerateAwsControlTowerTfConfigurationArgs{}
20+
err := promptAwsControlTowerGenerate(
21+
&data,
22+
&AwsControlTowerGenerateCommandExtraState{Output: "/tmp"},
23+
)
24+
25+
assert.Nil(t, err)
26+
}
Lines changed: 312 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,312 @@
1+
//go:build !windows && generation
2+
3+
package integration
4+
5+
import (
6+
"fmt"
7+
"os"
8+
"path/filepath"
9+
"testing"
10+
11+
"github.com/Netflix/go-expect"
12+
"github.com/lacework/go-sdk/cli/cmd"
13+
"github.com/lacework/go-sdk/lwgenerate/aws_controltower"
14+
"github.com/stretchr/testify/assert"
15+
)
16+
17+
func assertControlTowerTerraformSaved(t *testing.T, message string) {
18+
assert.Contains(t, message, "Terraform code saved in")
19+
}
20+
21+
const (
22+
controltowerPath = "/lacework/aws_controltower/"
23+
)
24+
25+
func runControlTowerGenerateTest(t *testing.T, conditions func(*expect.Console), args ...string) string {
26+
os.Setenv("HOME", tfPath)
27+
28+
hcl_path := filepath.Join(tfPath, controltowerPath, "main.tf")
29+
30+
runFakeTerminalTestFromDir(t, tfPath, conditions, args...)
31+
out, err := os.ReadFile(hcl_path)
32+
if err != nil {
33+
return fmt.Sprintf("main.tf not found: %s", err)
34+
}
35+
36+
t.Cleanup(func() {
37+
os.Remove(hcl_path)
38+
})
39+
40+
result := terraformValidate(filepath.Join(tfPath, controltowerPath))
41+
42+
assert.True(t, result.Valid)
43+
44+
return string(out)
45+
}
46+
47+
func TestGenerationControlTowerBasic(t *testing.T) {
48+
os.Setenv("LW_NOCACHE", "true")
49+
defer os.Setenv("LW_NOCACHE", "")
50+
var final string
51+
52+
tfResult := runControlTowerGenerateTest(t,
53+
func(c *expect.Console) {
54+
expectsCliOutput(t, c, []MsgRspHandler{
55+
MsgRsp{cmd.QuestionAwsControlTowerCoreS3Bucket, "arn:aws:s3:::aws-controltower-logs-0123456789-us-east-1"},
56+
MsgRsp{cmd.QuestionAwsControlTowerCoreSnsTopic, "arn:aws:sns:us-east-1:0123456789:aws-controltower-AllConfigNotifications"},
57+
MsgRsp{cmd.QuestionAwsControlTowerCoreLogProfile, "AWSAdministratorAccess"},
58+
MsgRsp{cmd.QuestionAwsControlTowerCoreLogRegion, "us-east-1"},
59+
MsgRsp{cmd.QuestionAwsControlTowerCoreAuditProfile, "AWSAdministratorAccess"},
60+
MsgRsp{cmd.QuestionAwsControlTowerCoreAuditRegion, "us-east-1"},
61+
MsgRsp{cmd.QuestionAwsControlTowerConfigureAdvanced, "n"},
62+
MsgRsp{cmd.QuestionRunTfPlan, "n"},
63+
})
64+
65+
final, _ = c.ExpectEOF()
66+
},
67+
"generate",
68+
"ca",
69+
"aws",
70+
"controltower",
71+
)
72+
73+
assertControlTowerTerraformSaved(t, final)
74+
75+
buildTf, _ := aws_controltower.NewTerraform(
76+
"arn:aws:s3:::aws-controltower-logs-0123456789-us-east-1",
77+
"arn:aws:sns:us-east-1:0123456789:aws-controltower-AllConfigNotifications",
78+
aws_controltower.WithSubaccounts(
79+
aws_controltower.NewAwsSubAccount("AWSAdministratorAccess", "us-east-1", "log_archive"),
80+
aws_controltower.NewAwsSubAccount("AWSAdministratorAccess", "us-east-1", "audit"))).Generate()
81+
assert.Equal(t, buildTf, tfResult)
82+
}
83+
84+
func TestGenerationControlTowerPromptOrgAccountMappings(t *testing.T) {
85+
os.Setenv("LW_NOCACHE", "true")
86+
defer os.Setenv("LW_NOCACHE", "")
87+
var final string
88+
89+
tfResult := runControlTowerGenerateTest(t,
90+
func(c *expect.Console) {
91+
expectsCliOutput(t, c, []MsgRspHandler{
92+
MsgRsp{cmd.QuestionAwsControlTowerCoreS3Bucket, "arn:aws:s3:::aws-controltower-logs-0123456789-us-east-1"},
93+
MsgRsp{cmd.QuestionAwsControlTowerCoreSnsTopic, "arn:aws:sns:us-east-1:0123456789:aws-controltower-AllConfigNotifications"},
94+
MsgRsp{cmd.QuestionAwsControlTowerCoreLogProfile, "AWSAdministratorAccess"},
95+
MsgRsp{cmd.QuestionAwsControlTowerCoreLogRegion, "us-east-1"},
96+
MsgRsp{cmd.QuestionAwsControlTowerCoreAuditProfile, "AWSAdministratorAccess"},
97+
MsgRsp{cmd.QuestionAwsControlTowerCoreAuditRegion, "us-east-1"},
98+
MsgRsp{cmd.QuestionAwsControlTowerConfigureAdvanced, "y"},
99+
MsgMenu{cmd.ControlTowerAdvancedOptMappings, 5},
100+
MsgRsp{cmd.QuestionControlTowerOrgAccountMappingsLWDefaultAccount, "main"},
101+
MsgRsp{cmd.QuestionControlTowerOrgAccountMappingsLWAccount, "sub-account-1"},
102+
MsgMultilineRsp{cmd.QuestionControlTowerOrgAccountMappingsAwsAccounts, []string{"123456789011"}},
103+
MsgRsp{cmd.QuestionControlTowerOrgAccountMappingAnotherAdvancedOpt, "n"},
104+
MsgRsp{cmd.QuestionControlTowerAnotherAdvancedOpt, "n"},
105+
106+
MsgRsp{cmd.QuestionRunTfPlan, "n"},
107+
})
108+
109+
final, _ = c.ExpectEOF()
110+
},
111+
"generate",
112+
"ca",
113+
"aws",
114+
"controltower",
115+
)
116+
117+
assertControlTowerTerraformSaved(t, final)
118+
119+
orgAccountMappings := aws_controltower.OrgAccountMapping{
120+
DefaultLaceworkAccount: "main",
121+
Mapping: []aws_controltower.OrgAccountMap{
122+
{
123+
LaceworkAccount: "sub-account-1",
124+
AwsAccounts: []string{"123456789011"},
125+
},
126+
},
127+
}
128+
129+
buildTf, _ := aws_controltower.NewTerraform(
130+
"arn:aws:s3:::aws-controltower-logs-0123456789-us-east-1",
131+
"arn:aws:sns:us-east-1:0123456789:aws-controltower-AllConfigNotifications",
132+
aws_controltower.WithSubaccounts(
133+
aws_controltower.NewAwsSubAccount("AWSAdministratorAccess", "us-east-1", "log_archive"),
134+
aws_controltower.NewAwsSubAccount("AWSAdministratorAccess", "us-east-1", "audit")),
135+
aws_controltower.WithOrgAccountMappings(orgAccountMappings)).Generate()
136+
assert.Equal(t, buildTf, tfResult)
137+
}
138+
139+
func TestGenerationControlTowerNonInteractive(t *testing.T) {
140+
os.Setenv("LW_NOCACHE", "true")
141+
defer os.Setenv("LW_NOCACHE", "")
142+
var final string
143+
144+
tfResult := runControlTowerGenerateTest(t,
145+
func(c *expect.Console) {
146+
final, _ = c.ExpectEOF()
147+
},
148+
"generate",
149+
"ca",
150+
"aws",
151+
"controltower",
152+
"--s3_bucket_arn",
153+
"arn:aws:s3:::aws-controltower-logs-0123456789-us-east-1",
154+
"--sns_topic_arn",
155+
"arn:aws:sns:us-east-1:0123456789:aws-controltower-AllConfigNotifications",
156+
"--audit_account",
157+
"AWSAdministratorAccess:us-east-1",
158+
"--log_archive_account",
159+
"AWSAdministratorAccess:us-east-2",
160+
"--org_account_mapping",
161+
"{\"default_lacework_account\":\"main\", \"mapping\": [{ \"aws_accounts\": [\"123456789011\"], \"lacework_account\": \"sub-account-1\"}]}",
162+
"--noninteractive",
163+
)
164+
165+
assertControlTowerTerraformSaved(t, final)
166+
167+
orgAccountMappings := aws_controltower.OrgAccountMapping{
168+
DefaultLaceworkAccount: "main",
169+
Mapping: []aws_controltower.OrgAccountMap{
170+
{
171+
LaceworkAccount: "sub-account-1",
172+
AwsAccounts: []string{"123456789011"},
173+
},
174+
},
175+
}
176+
177+
buildTf, _ := aws_controltower.NewTerraform(
178+
"arn:aws:s3:::aws-controltower-logs-0123456789-us-east-1",
179+
"arn:aws:sns:us-east-1:0123456789:aws-controltower-AllConfigNotifications",
180+
aws_controltower.WithSubaccounts(
181+
aws_controltower.NewAwsSubAccount("AWSAdministratorAccess", "us-east-2", "log_archive"),
182+
aws_controltower.NewAwsSubAccount("AWSAdministratorAccess", "us-east-1", "audit")),
183+
aws_controltower.WithOrgAccountMappings(orgAccountMappings),
184+
).Generate()
185+
assert.Equal(t, buildTf, tfResult)
186+
}
187+
188+
func TestGenerationControlTowerPromptOptionalAttributes(t *testing.T) {
189+
os.Setenv("LW_NOCACHE", "true")
190+
defer os.Setenv("LW_NOCACHE", "")
191+
var final string
192+
193+
tfResult := runControlTowerGenerateTest(t,
194+
func(c *expect.Console) {
195+
expectsCliOutput(t, c, []MsgRspHandler{
196+
MsgRsp{cmd.QuestionAwsControlTowerCoreS3Bucket, "arn:aws:s3:::aws-controltower-logs-0123456789-us-east-1"},
197+
MsgRsp{cmd.QuestionAwsControlTowerCoreSnsTopic, "arn:aws:sns:us-east-1:0123456789:aws-controltower-AllConfigNotifications"},
198+
MsgRsp{cmd.QuestionAwsControlTowerCoreLogProfile, "AWSAdministratorAccess"},
199+
MsgRsp{cmd.QuestionAwsControlTowerCoreLogRegion, "us-east-1"},
200+
MsgRsp{cmd.QuestionAwsControlTowerCoreAuditProfile, "AWSAdministratorAccess"},
201+
MsgRsp{cmd.QuestionAwsControlTowerCoreAuditRegion, "us-east-1"},
202+
MsgRsp{cmd.QuestionAwsControlTowerConfigureAdvanced, "y"},
203+
MsgMenu{cmd.ControlTowerAdvancedOptMappings, 5},
204+
MsgRsp{cmd.QuestionControlTowerOrgAccountMappingsLWDefaultAccount, "main"},
205+
MsgRsp{cmd.QuestionControlTowerOrgAccountMappingsLWAccount, "sub-account-1"},
206+
MsgMultilineRsp{cmd.QuestionControlTowerOrgAccountMappingsAwsAccounts, []string{"123456789011"}},
207+
MsgRsp{cmd.QuestionControlTowerOrgAccountMappingAnotherAdvancedOpt, "n"},
208+
MsgRsp{cmd.QuestionControlTowerAnotherAdvancedOpt, "y"},
209+
MsgMenu{cmd.ControlTowerConfigureExistingIamRoleOpt, 0},
210+
MsgRsp{cmd.QuestionAwsControlTowerCoreIamRoleName, "lw-role-name"},
211+
MsgRsp{cmd.QuestionAwsControlTowerCoreIamRoleArn, "arn:aws:iam::12345678:role/test"},
212+
MsgRsp{cmd.QuestionAwsControlTowerCoreIamRoleExternalID, "01234567"},
213+
MsgRsp{cmd.QuestionControlTowerAnotherAdvancedOpt, "y"},
214+
MsgMenu{cmd.ControlTowerIntegrationPrefixOpt, 3},
215+
MsgRsp{cmd.QuestionControlTowerPrefix, "prefix-"},
216+
MsgRsp{cmd.QuestionControlTowerAnotherAdvancedOpt, "y"},
217+
MsgMenu{cmd.ControlTowerIntegrationSqsOpt, 4},
218+
MsgRsp{cmd.QuestionControlTowerSqsQueueName, "lw-queue-name"},
219+
MsgRsp{cmd.QuestionControlTowerAnotherAdvancedOpt, "n"},
220+
221+
MsgRsp{cmd.QuestionRunTfPlan, "n"},
222+
})
223+
224+
final, _ = c.ExpectEOF()
225+
},
226+
"generate",
227+
"ca",
228+
"aws",
229+
"controltower",
230+
)
231+
232+
assertControlTowerTerraformSaved(t, final)
233+
234+
orgAccountMappings := aws_controltower.OrgAccountMapping{
235+
DefaultLaceworkAccount: "main",
236+
Mapping: []aws_controltower.OrgAccountMap{
237+
{
238+
LaceworkAccount: "sub-account-1",
239+
AwsAccounts: []string{"123456789011"},
240+
},
241+
},
242+
}
243+
244+
buildTf, _ := aws_controltower.NewTerraform(
245+
"arn:aws:s3:::aws-controltower-logs-0123456789-us-east-1",
246+
"arn:aws:sns:us-east-1:0123456789:aws-controltower-AllConfigNotifications",
247+
aws_controltower.WithSubaccounts(
248+
aws_controltower.NewAwsSubAccount("AWSAdministratorAccess", "us-east-1", "log_archive"),
249+
aws_controltower.NewAwsSubAccount("AWSAdministratorAccess", "us-east-1", "audit")),
250+
aws_controltower.WithOrgAccountMappings(orgAccountMappings),
251+
aws_controltower.WithPrefix("prefix-"),
252+
aws_controltower.WithSqsQueueName("lw-queue-name"),
253+
aws_controltower.WithExisitingIamRole("arn:aws:iam::12345678:role/test",
254+
"lw-role-name", "01234567")).Generate()
255+
assert.Equal(t, buildTf, tfResult)
256+
}
257+
258+
func TestGenerationControlTowerExistingIamRoleNonInteractive(t *testing.T) {
259+
os.Setenv("LW_NOCACHE", "true")
260+
defer os.Setenv("LW_NOCACHE", "")
261+
var final string
262+
263+
tfResult := runControlTowerGenerateTest(t,
264+
func(c *expect.Console) {
265+
final, _ = c.ExpectEOF()
266+
},
267+
"generate",
268+
"ca",
269+
"aws",
270+
"controltower",
271+
"--s3_bucket_arn",
272+
"arn:aws:s3:::aws-controltower-logs-0123456789-us-east-1",
273+
"--sns_topic_arn",
274+
"arn:aws:sns:us-east-1:0123456789:aws-controltower-AllConfigNotifications",
275+
"--audit_account",
276+
"AWSAdministratorAccess:us-east-1",
277+
"--log_archive_account",
278+
"AWSAdministratorAccess:us-east-2",
279+
"--org_account_mapping",
280+
"{\"default_lacework_account\":\"main\", \"mapping\": [{ \"aws_accounts\": [\"123456789011\"], \"lacework_account\": \"sub-account-1\"}]}",
281+
"--iam_role_arn",
282+
"arn:aws:iam::12345678:role/test",
283+
"--iam_role_name",
284+
"lw-role-name",
285+
"--iam_role_external_id",
286+
"01234567",
287+
"--noninteractive",
288+
)
289+
290+
assertControlTowerTerraformSaved(t, final)
291+
292+
orgAccountMappings := aws_controltower.OrgAccountMapping{
293+
DefaultLaceworkAccount: "main",
294+
Mapping: []aws_controltower.OrgAccountMap{
295+
{
296+
LaceworkAccount: "sub-account-1",
297+
AwsAccounts: []string{"123456789011"},
298+
},
299+
},
300+
}
301+
302+
buildTf, _ := aws_controltower.NewTerraform(
303+
"arn:aws:s3:::aws-controltower-logs-0123456789-us-east-1",
304+
"arn:aws:sns:us-east-1:0123456789:aws-controltower-AllConfigNotifications",
305+
aws_controltower.WithSubaccounts(
306+
aws_controltower.NewAwsSubAccount("AWSAdministratorAccess", "us-east-2", "log_archive"),
307+
aws_controltower.NewAwsSubAccount("AWSAdministratorAccess", "us-east-1", "audit")),
308+
aws_controltower.WithOrgAccountMappings(orgAccountMappings),
309+
aws_controltower.WithExisitingIamRole("arn:aws:iam::12345678:role/test",
310+
"lw-role-name", "01234567")).Generate()
311+
assert.Equal(t, buildTf, tfResult)
312+
}

integration/context/ctx.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@
141141
"generate.go",
142142
"generate_aws.go",
143143
"generate_aws_eks_audit.go",
144+
"generate_aws_controltower.go",
144145
"generate_aws_test.go",
145146
"generate_azure.go",
146147
"generate_cloud_account.go",

integration/framework_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,22 @@ func (m MsgRsp) handle(t *testing.T, c *expect.Console) {
425425
c.SendLine(m.response)
426426
}
427427

428+
type MsgMultilineRsp struct {
429+
message string
430+
responses []string
431+
}
432+
433+
func (m MsgMultilineRsp) handle(t *testing.T, c *expect.Console) {
434+
expectString(t, c, m.message)
435+
436+
for _, rsp := range m.responses {
437+
c.SendLine(rsp)
438+
}
439+
440+
// Send 2 Empty lines to proceed
441+
c.SendLine("\n\n")
442+
}
443+
428444
type Select struct {
429445
message string
430446
}

integration/test_resources/help/generate_cloud-account_aws

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ See help output for more details on the parameter value(s) required for Terrafor
1919

2020
Usage:
2121
lacework generate cloud-account aws [flags]
22+
lacework generate cloud-account aws [command]
23+
24+
Available Commands:
25+
controltower Generate and/or execute Terraform code for ControlTower integration
2226

2327
Flags:
2428
--apply run terraform apply without executing plan or prompting
@@ -62,3 +66,5 @@ Global Flags:
6266
--organization access organization level data sets (org admins only)
6367
-p, --profile string switch between profiles configured at ~/.lacework.toml
6468
--subaccount string sub-account name inside your organization (org admins only)
69+
70+
Use "lacework generate cloud-account aws [command] --help" for more information about a command.

0 commit comments

Comments
 (0)