Skip to content

Commit

Permalink
add org account mappings to awls org scanning resource
Browse files Browse the repository at this point in the history
  • Loading branch information
wl-smith committed May 30, 2023
1 parent f235c8d commit ea3bb24
Show file tree
Hide file tree
Showing 4 changed files with 289 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ terraform {
}
}

provider "lacework" {
organization = true
}

resource "lacework_integration_aws_org_agentless_scanning" "example" {
name = var.name
query_text = var.query_text
Expand All @@ -22,6 +26,21 @@ resource "lacework_integration_aws_org_agentless_scanning" "example" {
role_arn = var.role_arn
external_id = var.external_id
}

dynamic "org_account_mappings" {
for_each = var.org_account_mappings
content {
default_lacework_account = org_account_mappings.value["default_lacework_account"]

dynamic "integration_mappings" {
for_each = org_account_mappings.value["integration_mappings"]
content {
lacework_account = integration_mappings.value["lacework_account"]
aws_accounts = integration_mappings.value["aws_accounts"]
}
}
}
}
}

variable "account_id" {
Expand Down Expand Up @@ -69,6 +88,24 @@ variable "monitored_accounts" {
default = []
}

variable "org_account_mappings" {
type = list(object({
default_lacework_account = string
integration_mappings = list(object({
lacework_account = string
aws_accounts = list(string)
}))
}))
default = []
description = "Mapping of AWS accounts to Lacework accounts within a Lacework organization"
}


output "name" {
value = lacework_integration_aws_org_agentless_scanning.example.name
}

output "org_account_mappings" {
value = lacework_integration_aws_org_agentless_scanning.example.org_account_mappings
}

23 changes: 23 additions & 0 deletions integration/integration.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,18 @@ func GetCloudAccountIntegrationName(result string) string {
return res.Data.Name
}

func GetCloudOrgAccountIntegrationName(result string) string {
var res api.CloudAccountResponse
id := GetIDFromTerraResults(result)

err := LwOrgClient.V2.CloudAccounts.Get(id, &res)
if err != nil {
log.Fatalf("Unable to find integration id: %s\n Response: %v", id, res)
}

return res.Data.Name
}

func GetCloudAccountEksAuditLogData(result string) api.AwsEksAuditData {
id := GetIDFromTerraResults(result)

Expand All @@ -94,6 +106,17 @@ func GetCloudAccountAgentlessScanningResponse(result string) api.AwsSidekickResp
return response
}

func GetAwsAgentlessOrgScanningResponse(result string) api.AwsSidekickOrgResponse {
id := GetIDFromTerraResults(result)

response, err := LwOrgClient.V2.CloudAccounts.GetAwsSidekickOrg(id)
if err != nil {
log.Fatalf("Unable to find aws sidekick id: %s\n Response: %v", id, response)
}

return response
}

func GetCloudAccountGkeAuditLogData(result string) api.GcpGkeAuditData {
id := GetIDFromTerraResults(result)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package integration

import (
"testing"

"github.com/gruntwork-io/terratest/modules/terraform"
"github.com/stretchr/testify/assert"
)

// TestIntegrationAwsAgentlessScanningLog applies integration terraform:
// => '../examples/resource_lacework_integration_aws_agentless_scanning'
//
// It uses the go-sdk to verify the created integration,
// applies an update with new integration name and destroys it
// nolint

func TestIntegrationAwsOrgAgentlessScanningLog(t *testing.T) {
terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
TerraformDir: "../examples/resource_lacework_integration_aws_org_agentless_scanning",
Vars: map[string]interface{}{
"name": "AWS Org Agentless Scanning created by terraform",
"role_arn": "arn:aws:iam::249446771485:role/lacework-iam-example-role",
"external_id": "12345",
"org_account_mappings": []map[string]interface{}{
{
"default_lacework_account": "customerdemo",
"integration_mappings": []map[string]interface{}{
{
"lacework_account": "abc",
"aws_accounts": []string{"327958430571"},
},
},
},
},
},
})

defer terraform.Destroy(t, terraformOptions)

// Create new AWS Agentless Scanning Integration
create := terraform.InitAndApplyAndIdempotent(t, terraformOptions)
createData := GetAwsAgentlessOrgScanningResponse(create)
println(create)
actualName := terraform.Output(t, terraformOptions, "name")
assert.Equal(
t,
"AWS Org Agentless Scanning created by terraform",
GetCloudOrgAccountIntegrationName(create),
)
assert.Equal(t, "AWS Org Agentless Scanning created by terraform", createData.Data.Name)
assert.Equal(t, "AWS Org Agentless Scanning created by terraform", actualName)

// Update AWS Agentless Scanning Integration
terraformOptions.Vars = map[string]interface{}{
"name": "AWS Org Agentless Scanning updated by terraform",
"role_arn": "arn:aws:iam::249446771485:role/lacework-iam-example-role",
"external_id": "12345",
"org_account_mappings": []map[string]interface{}{
{
"default_lacework_account": "customerdemo",
"integration_mappings": []map[string]interface{}{
{
"lacework_account": "abc",
"aws_accounts": []string{"327958430571"},
},
},
},
},
}

update := terraform.ApplyAndIdempotent(t, terraformOptions)
updateData := GetAwsAgentlessOrgScanningResponse(update)
actualName = terraform.Output(t, terraformOptions, "name")
assert.Equal(
t,
"AWS Org Agentless Scanning updated by terraform",
GetCloudOrgAccountIntegrationName(update),
)
assert.Equal(t, "AWS Org Agentless Scanning updated by terraform", updateData.Data.Name)
assert.Equal(t, "AWS Org Agentless Scanning updated by terraform", actualName)
}
148 changes: 148 additions & 0 deletions lacework/resource_lacework_integration_aws_org_agentless_scanning.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package lacework

import (
"context"
"encoding/json"
"fmt"
"log"
"strings"
Expand Down Expand Up @@ -142,6 +143,41 @@ var awsOrgAgentlessScanningIntegrationSchema = map[string]*schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"org_account_mappings": {
Type: schema.TypeList,
Optional: true,
Description: "Mapping of AWS accounts to Lacework accounts within a Lacework organization.",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"default_lacework_account": {
Type: schema.TypeString,
Required: true,
Description: "The default Lacework account name where any non-mapped AWS account will appear",
},
"integration_mappings": {
Type: schema.TypeSet,
Required: true,
Description: "A map of AWS accounts to Lacework account. This can be specified multiple times to map multiple Lacework accounts.",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"lacework_account": {
Type: schema.TypeString,
Required: true,
Description: "The Lacework account name where the CloudTrail activity from the selected AWS accounts will appear.",
},
"aws_accounts": {
Type: schema.TypeSet,
Elem: &schema.Schema{Type: schema.TypeString},
MinItems: 1,
Required: true,
Description: "The list of AWS account IDs to map.",
},
},
},
},
},
},
},
}

func resourceLaceworkIntegrationAwsOrgAgentlessScanningCreate(d *schema.ResourceData, meta interface{}) error {
Expand All @@ -165,6 +201,17 @@ func resourceLaceworkIntegrationAwsOrgAgentlessScanningCreate(d *schema.Resource
},
}

// verify if the user provided an account mapping
accountMapFile := getAwsAgentlessOrgAccountMappings(d)
if !accountMapFile.accountMappingEmpty() {
accountMapFileBytes, err := json.Marshal(accountMapFile)
if err != nil {
return err
}

awsOrgAgentlessScanningData.EncodeAccountMappingFile(accountMapFileBytes)
}

if d.Get("query_text") != nil {
awsOrgAgentlessScanningData.QueryText = d.Get("query_text").(string)
}
Expand Down Expand Up @@ -243,6 +290,27 @@ func resourceLaceworkIntegrationAwsOrgAgentlessScanningRead(d *schema.ResourceDa

d.Set("credentials", []map[string]string{creds})

accountMapFileBytes, err := cloudAccount.Data.DecodeAccountMappingFile()
if err != nil {
return err
}

accountMapFile := new(accountMappingsFile)
if len(accountMapFileBytes) != 0 {
// The integration has an account mapping file
// unmarshal its content into the account mapping struct
err := json.Unmarshal(accountMapFileBytes, accountMapFile)
if err != nil {
return fmt.Errorf("Error decoding organization account mapping: %s", err)
}

}

err = d.Set("org_account_mappings", flattenAwsAgentlessOrgAccountMappings(accountMapFile))
if err != nil {
return fmt.Errorf("Error flattening organization account mapping: %s", err)
}

log.Printf("[INFO] Read %s cloud account integration with guid: %v\n",
api.AwsSidekickOrgCloudAccount.String(), cloudAccount.IntgGuid,
)
Expand Down Expand Up @@ -273,6 +341,17 @@ func resourceLaceworkIntegrationAwsOrgAgentlessScanningUpdate(d *schema.Resource
},
}

// verify if the user provided an account mapping
accountMapFile := getAwsAgentlessOrgAccountMappings(d)
if !accountMapFile.accountMappingEmpty() {
accountMapFileBytes, err := json.Marshal(accountMapFile)
if err != nil {
return err
}

awsOrgAgentlessScanningData.EncodeAccountMappingFile(accountMapFileBytes)
}

if d.Get("query_text") != nil {
awsOrgAgentlessScanningData.QueryText = d.Get("query_text").(string)
}
Expand Down Expand Up @@ -319,3 +398,72 @@ func resourceLaceworkIntegrationAwsOrgAgentlessScanningDelete(d *schema.Resource
log.Printf("[INFO] Deleted %s cloud account integration with guid: %v\n", api.AwsSidekickOrgCloudAccount.String(), d.Id())
return nil
}

type accountMappingFile struct {
DefaultLaceworkAccount string `json:"defaultLaceworkAccountAws"`
IntegrationMappings map[string]interface{} `json:"integration_mappings"`
}

func (f *accountMappingFile) accountMappingEmpty() bool {
return f.DefaultLaceworkAccount == ""
}

func getAwsAgentlessOrgAccountMappings(d *schema.ResourceData) *accountMappingFile {
accountMapFile := new(accountMappingFile)
accMapsInt := d.Get("org_account_mappings").([]interface{})
if len(accMapsInt) != 0 && accMapsInt[0] != nil {
accountMappings := accMapsInt[0].(map[string]interface{})

accountMapFile = &accountMappingFile{
DefaultLaceworkAccount: accountMappings["default_lacework_account"].(string),
IntegrationMappings: map[string]interface{}{},
}

mappingSet := accountMappings["integration_mappings"].(*schema.Set)
for _, m := range mappingSet.List() {
mapping := m.(map[string]interface{})
accountMapFile.IntegrationMappings[mapping["lacework_account"].(string)] = map[string]interface{}{
"aws_accounts": castStringSlice(mapping["aws_accounts"].(*schema.Set).List()),
}
}

}

return accountMapFile
}

func flattenAwsAgentlessOrgAccountMappings(mappingFile *accountMappingsFile) []map[string]interface{} {
orgAccMappings := make([]map[string]interface{}, 0, 1)

if mappingFile.Empty() {
return orgAccMappings
}

mappings := map[string]interface{}{
"default_lacework_account": mappingFile.DefaultLaceworkAccount,
"integration_mappings": flattenAwsMappings(mappingFile.Mappings),
}

orgAccMappings = append(orgAccMappings, mappings)
return orgAccMappings
}

func flattenAwsMappings(mappings map[string]interface{}) *schema.Set {
var (
orgAccountMappingsSchema = awsOrgAgentlessScanningIntegrationSchema["org_account_mappings"].Elem.(*schema.Resource)
mappingSchema = orgAccountMappingsSchema.Schema["integration_mappings"].Elem.(*schema.Resource)
awsAccountsSchema = mappingSchema.Schema["aws_accounts"].Elem.(*schema.Schema)
res = schema.NewSet(schema.HashResource(mappingSchema), []interface{}{})
)
for laceworkAccount, m := range mappings {
mappingValue := m.(map[string]interface{})
res.Add(map[string]interface{}{
"lacework_account": laceworkAccount,
"aws_accounts": schema.NewSet(schema.HashSchema(awsAccountsSchema),
mappingValue["aws_accounts"].([]interface{}),
),
})
}

return res
}

0 comments on commit ea3bb24

Please sign in to comment.