Skip to content

Commit

Permalink
refactor: migrate lacework_integration_aws_cfg to use v2 api (#361)
Browse files Browse the repository at this point in the history
* refactor: migrate lacework_integration_aws_cfg to use v2 api
  • Loading branch information
dmurray-lacework authored Aug 22, 2022
1 parent e2d2a1f commit f182648
Show file tree
Hide file tree
Showing 12 changed files with 380 additions and 113 deletions.
18 changes: 16 additions & 2 deletions examples/resource_lacework_integration_aws_cfg/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,23 @@ provider "lacework" {}
resource "lacework_integration_aws_cfg" "example" {
name = "AWS config integration example"
credentials {
role_arn = "arn:aws:iam::1234567890:role/lacework_iam_example_role"
external_id = "12345"
role_arn = var.role_arn
external_id = var.external_id
}

retries = 10
}

variable "role_arn" {
type = string
default = "arn:aws:iam::1234567890:role/lacework_iam_example_role"
}

variable "external_id" {
type = string
default = "12345"
}

output "name" {
value = lacework_integration_aws_cfg.example.name
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ go 1.18
require (
github.com/gruntwork-io/terratest v0.40.18
github.com/hashicorp/terraform-plugin-sdk/v2 v2.20.0
github.com/lacework/go-sdk v0.40.1-0.20220809151654-331175320a59
github.com/lacework/go-sdk v0.40.1-0.20220818151645-53bd1ca21bd5
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.8.0
golang.org/x/text v0.3.7
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -282,8 +282,8 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lacework/go-sdk v0.40.1-0.20220809151654-331175320a59 h1:LnsttQooj5mSuHKpRtSPHTAHMTnZCslggeOpfnURAn4=
github.com/lacework/go-sdk v0.40.1-0.20220809151654-331175320a59/go.mod h1:fNco46fzYApCChr5ZwJyAOGgSRnrmcvWw3yu5fQGLVo=
github.com/lacework/go-sdk v0.40.1-0.20220818151645-53bd1ca21bd5 h1:Ryx65IZpokj7Tl0MGNwSg1kuUibD6QdCykK82hMBzwE=
github.com/lacework/go-sdk v0.40.1-0.20220818151645-53bd1ca21bd5/go.mod h1:fNco46fzYApCChr5ZwJyAOGgSRnrmcvWw3yu5fQGLVo=
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
Expand Down
20 changes: 20 additions & 0 deletions integration/aws_auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package integration

import (
"encoding/json"
"os"
)

type awsCredentialsFile struct {
RoleArn string `json:"role_arn"`
ExternalID string `json:"external_id"`
}

func awsLoadDefaultCredentials() (awsCredentialsFile, error) {
return awsLoadCredentials("AWS_CREDS")
}

func awsLoadCredentials(envVar string) (c awsCredentialsFile, err error) {
err = json.Unmarshal([]byte(os.Getenv(envVar)), &c)
return
}
58 changes: 58 additions & 0 deletions integration/resource_lacework_integration_aws_cfg.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package integration

import (
"testing"

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

// TestIntegrationAwsCfg applies integration terraform:
// => '../examples/resource_lacework_integration_aws_cfg'
//
// It uses the go-sdk to verify the created integration,
// applies an update with new integration name and destroys it
// nolint
func TestIntegrationAwsCfg(t *testing.T) {
awsCreds, err := awsLoadDefaultCredentials()
if assert.Nil(t, err, "this test requires you to set AWS_ECR_IAM environment variable") {
terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
TerraformDir: "../examples/resource_lacework_integration_aws_cfg",
Vars: map[string]interface{}{
"name": "AwsCfg created by terraform",
"role_arn": awsCreds.RoleArn,
"external_id": awsCreds.ExternalID,
},
})
defer terraform.Destroy(t, terraformOptions)

// Create new AwsCfg Integration
create := terraform.InitAndApplyAndIdempotent(t, terraformOptions)
createData := GetCloudAccountAgentlessScanningResponse(create)
actualName := terraform.Output(t, terraformOptions, "name")
assert.Equal(
t,
"AwsCfg created by terraform",
GetCloudAccountIntegrationName(create),
)
assert.Equal(t, "AwsCfg created by terraform", createData.Data.Name)
assert.Equal(t, "AwsCfg created by terraform", actualName)

// Update AwsCfg Integration
terraformOptions.Vars = map[string]interface{}{
"name": "AwsCfg updated by terraform",
"role_arn": awsCreds.RoleArn,
"external_id": awsCreds.ExternalID,
}

update := terraform.ApplyAndIdempotent(t, terraformOptions)
updateData := GetCloudAccountAgentlessScanningResponse(update)
assert.Equal(
t,
"AwsCfg updated",
GetCloudAccountIntegrationName(update),
)
assert.Equal(t, "AwsCfg updated by terraform", updateData.Data.Name)
assert.Equal(t, "AwsCfg updated by terraform", actualName)
}
}
154 changes: 47 additions & 107 deletions lacework/resource_lacework_integration_aws_cfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,15 +83,14 @@ func resourceLaceworkIntegrationAwsCfgCreate(d *schema.ResourceData, meta interf
var (
lacework = meta.(*api.Client)
retries = d.Get("retries").(int)
aws = api.NewAwsIntegration(d.Get("name").(string),
api.AwsCfgIntegration,
api.AwsIntegrationData{
Credentials: &api.AwsCrossAccountCreds{
aws = api.NewCloudAccount(d.Get("name").(string),
api.AwsCfgCloudAccount,
api.AwsCfgData{
Credentials: api.AwsCfgCredentials{
RoleArn: d.Get("credentials.0.role_arn").(string),
ExternalID: d.Get("credentials.0.external_id").(string),
},
},
)
})
)

if !d.Get("enabled").(bool) {
Expand All @@ -100,45 +99,38 @@ func resourceLaceworkIntegrationAwsCfgCreate(d *schema.ResourceData, meta interf

return resource.RetryContext(context.Background(), d.Timeout(schema.TimeoutCreate), func() *resource.RetryError {
retries--
log.Printf("[INFO] Creating %s integration\n", api.AwsCfgIntegration.String())
response, err := lacework.Integrations.CreateAws(aws)
log.Printf("[INFO] Creating %s integration\n", api.AwsCfgCloudAccount.String())
response, err := lacework.V2.CloudAccounts.Create(aws)
if err != nil {
if retries <= 0 {
return resource.NonRetryableError(
fmt.Errorf("Error creating %s integration: %s",
api.AwsCfgIntegration.String(), err,
api.AwsCfgCloudAccount.String(), err,
))
}
log.Printf(
"[INFO] Unable to create %s integration. (retrying %d more time(s))\n%s\n",
api.AwsCfgIntegration.String(), retries, err,
api.AwsCfgCloudAccount.String(), retries, err,
)
return resource.RetryableError(fmt.Errorf(
"Unable to create %s integration (retrying %d more time(s))",
api.AwsCfgIntegration.String(), retries,
api.AwsCfgCloudAccount.String(), retries,
))
}

log.Printf("[INFO] Verifying server response")
err = validateAwsIntegrationResponse(&response)
if err != nil {
return resource.NonRetryableError(err)
}

// @afiune at this point in time, we know the data field has a single value
integration := response.Data[0]
integration := response.Data
d.SetId(integration.IntgGuid)
d.Set("name", integration.Name)
d.Set("intg_guid", integration.IntgGuid)
d.Set("enabled", integration.Enabled == 1)

d.Set("created_or_updated_time", integration.CreatedOrUpdatedTime)
d.Set("created_or_updated_by", integration.CreatedOrUpdatedBy)
d.Set("type_name", integration.TypeName)
d.Set("type_name", integration.Type)
d.Set("org_level", integration.IsOrg == 1)

log.Printf("[INFO] Created %s integration with guid: %v\n",
api.AwsCfgIntegration.String(), integration.IntgGuid)
api.AwsCfgCloudAccount.String(), integration.IntgGuid)
return nil
})
}
Expand All @@ -147,32 +139,31 @@ func resourceLaceworkIntegrationAwsCfgRead(d *schema.ResourceData, meta interfac
lacework := meta.(*api.Client)

log.Printf("[INFO] Reading %s integration with guid: %v\n",
api.AwsCfgIntegration.String(), d.Id())
response, err := lacework.Integrations.GetAws(d.Id())
api.AwsCfgCloudAccount.String(), d.Id())
response, err := lacework.V2.CloudAccounts.GetAwsCfg(d.Id())
if err != nil {
return resourceNotFound(d, err)
}

for _, integration := range response.Data {
if integration.IntgGuid == d.Id() {
d.Set("name", integration.Name)
d.Set("intg_guid", integration.IntgGuid)
d.Set("enabled", integration.Enabled == 1)
d.Set("created_or_updated_time", integration.CreatedOrUpdatedTime)
d.Set("created_or_updated_by", integration.CreatedOrUpdatedBy)
d.Set("type_name", integration.TypeName)
d.Set("org_level", integration.IsOrg == 1)

creds := make(map[string]string)
credentials := integration.Data.GetCredentials()
creds["role_arn"] = credentials.RoleArn
creds["external_id"] = credentials.ExternalID
d.Set("credentials", []map[string]string{creds})

log.Printf("[INFO] Read %s integration with guid: %v\n",
api.AwsCfgIntegration.String(), integration.IntgGuid)
return nil
}
cloudAccount := response.Data
if cloudAccount.IntgGuid == d.Id() {
d.Set("name", cloudAccount.Name)
d.Set("intg_guid", cloudAccount.IntgGuid)
d.Set("enabled", cloudAccount.Enabled == 1)
d.Set("created_or_updated_time", cloudAccount.CreatedOrUpdatedTime)
d.Set("created_or_updated_by", cloudAccount.CreatedOrUpdatedBy)
d.Set("type_name", cloudAccount.Type)
d.Set("org_level", cloudAccount.IsOrg == 1)

creds := make(map[string]string)
credentials := cloudAccount.Data.Credentials
creds["role_arn"] = credentials.RoleArn
creds["external_id"] = credentials.ExternalID
d.Set("credentials", []map[string]string{creds})

log.Printf("[INFO] Read %s integration with guid: %v\n",
api.AwsCfgCloudAccount.String(), cloudAccount.IntgGuid)
return nil
}

d.SetId("")
Expand All @@ -182,15 +173,14 @@ func resourceLaceworkIntegrationAwsCfgRead(d *schema.ResourceData, meta interfac
func resourceLaceworkIntegrationAwsCfgUpdate(d *schema.ResourceData, meta interface{}) error {
var (
lacework = meta.(*api.Client)
aws = api.NewAwsIntegration(d.Get("name").(string),
api.AwsCfgIntegration,
api.AwsIntegrationData{
Credentials: &api.AwsCrossAccountCreds{
aws = api.NewCloudAccount(d.Get("name").(string),
api.AwsCfgCloudAccount,
api.AwsCfgData{
Credentials: api.AwsCfgCredentials{
RoleArn: d.Get("credentials.0.role_arn").(string),
ExternalID: d.Get("credentials.0.external_id").(string),
},
},
)
})
)

if !d.Get("enabled").(bool) {
Expand All @@ -200,87 +190,37 @@ func resourceLaceworkIntegrationAwsCfgUpdate(d *schema.ResourceData, meta interf
aws.IntgGuid = d.Id()

log.Printf("[INFO] Updating %s integration with data:\n%+v\n",
api.AwsCfgIntegration.String(), aws)
response, err := lacework.Integrations.UpdateAws(aws)
if err != nil {
return err
}

log.Println("[INFO] Verifying server response data")
err = validateAwsIntegrationResponse(&response)
api.AwsCfgCloudAccount.String(), aws)
response, err := lacework.V2.CloudAccounts.UpdateAwsCfg(aws)
if err != nil {
return err
}

// @afiune at this point in time, we know the data field has a single value
integration := response.Data[0]
integration := response.Data
d.Set("name", integration.Name)
d.Set("intg_guid", integration.IntgGuid)
d.Set("enabled", integration.Enabled == 1)
d.Set("created_or_updated_time", integration.CreatedOrUpdatedTime)
d.Set("created_or_updated_by", integration.CreatedOrUpdatedBy)
d.Set("type_name", integration.TypeName)
d.Set("type_name", integration.Type)
d.Set("org_level", integration.IsOrg == 1)

log.Printf("[INFO] Updated %s integration with guid: %v\n",
api.AwsCfgIntegration.String(), d.Id())
api.AwsCfgCloudAccount.String(), d.Id())
return nil
}

func resourceLaceworkIntegrationAwsCfgDelete(d *schema.ResourceData, meta interface{}) error {
lacework := meta.(*api.Client)

log.Printf("[INFO] Deleting %s integration with guid: %v\n",
api.AwsCfgIntegration.String(), d.Id())
_, err := lacework.Integrations.DeleteAws(d.Id())
api.AwsCfgCloudAccount.String(), d.Id())
err := lacework.V2.CloudAccounts.Delete(d.Id())
if err != nil {
return err
}

log.Printf("[INFO] Deleted %s integration with guid: %v\n",
api.AwsCfgIntegration.String(), d.Id())
api.AwsCfgCloudAccount.String(), d.Id())
return nil
}

// validateAwsIntegrationResponse checks weather or not the server response has
// any inconsistent data, it returns a friendly error message describing the
// problem and how to report it
func validateAwsIntegrationResponse(response *api.AwsIntegrationsResponse) error {
if len(response.Data) == 0 {
// @afiune this edge case should never happen, if we land here it means that
// something went wrong in the server side of things (Lacework API), so let
// us inform that to our users
msg := `
Unable to read sever response data. (empty 'data' field)
This was an unexpected behavior, verify that your integration has been
created successfully and report this issue to support@lacework.net
`
return fmt.Errorf(msg)
}

if len(response.Data) > 1 {
// @afiune if we are creating a single integration and the server returns
// more than one integration inside the 'data' field, it is definitely another
// edge case that should never happen
msg := `
There is more that one integration inside the server response data.
List of integrations:
`
for _, integration := range response.Data {
msg = msg + fmt.Sprintf("\t%s: %s\n", integration.IntgGuid, integration.Name)
}
msg = msg + unexpectedBehaviorMsg()
return fmt.Errorf(msg)
}

return nil
}

func unexpectedBehaviorMsg() string {
return `
This was an unexpected behavior, verify that your integration has been
created successfully and report this issue to support@lacework.net
`
}
Loading

0 comments on commit f182648

Please sign in to comment.