Skip to content

Commit

Permalink
feat(cli): AWS GovCloud Config integration (#421)
Browse files Browse the repository at this point in the history
  • Loading branch information
dmurray-lacework authored May 26, 2021
1 parent 8c53e8e commit 68d7087
Show file tree
Hide file tree
Showing 6 changed files with 263 additions and 14 deletions.
4 changes: 4 additions & 0 deletions api/integrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ const (
// AWS CloudTrail integration type
AwsCloudTrailIntegration

// AWS Gov Cloud Config integration type
AwsGovCloudCfgIntegration

// AWS S3 channel integration type
AwsS3ChannelIntegration

Expand Down Expand Up @@ -106,6 +109,7 @@ var IntegrationTypes = map[integrationType]string{
NoneIntegration: "NONE",
AwsCfgIntegration: "AWS_CFG",
AwsCloudTrailIntegration: "AWS_CT_SQS",
AwsGovCloudCfgIntegration: "AWS_US_GOV_CFG",
AwsS3ChannelIntegration: "AWS_S3",
CiscoWebexChannelIntegration: "CISCO_SPARK_WEBHOOK",
DatadogChannelIntegration: "DATADOG",
Expand Down
49 changes: 47 additions & 2 deletions api/integrations_aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import (
// aws := api.NewAwsIntegration("foo",
// api.AwsCfgIntegration,
// api.AwsIntegrationData{
// Credentials: api.AwsCrossAccountCreds {
// Credentials: &api.AwsCrossAccountCreds {
// RoleArn: "arn:aws:XYZ",
// ExternalID: "1",
// },
Expand Down Expand Up @@ -131,7 +131,7 @@ type AwsIntegration struct {
}

type AwsIntegrationData struct {
Credentials AwsCrossAccountCreds `json:"CROSS_ACCOUNT_CREDENTIALS" mapstructure:"CROSS_ACCOUNT_CREDENTIALS"`
Credentials *AwsCrossAccountCreds `json:"CROSS_ACCOUNT_CREDENTIALS,omitempty" mapstructure:"CROSS_ACCOUNT_CREDENTIALS"`

// QueueUrl is a field that exists and is required for the AWS_CT_SQS integration,
// though, it doesn't exist for AWS_CFG integrations, that's why we omit it if empty
Expand All @@ -146,6 +146,30 @@ type AwsIntegrationData struct {

// AwsAccountID is the AWS account that owns the IAM role credentials
AwsAccountID string `json:"AWS_ACCOUNT_ID,omitempty" mapstructure:"AWS_ACCOUNT_ID"`

// GovCloudCredentials represents the credential structure for AWS_US_GOV_CFG and AWS_US_GOV_CT_SQS integrations
GovCloudCredentials *AwsGovCloudCreds `json:"ACCESS_KEY_CREDENTIALS,omitempty" mapstructure:"ACCESS_KEY_CREDENTIALS"`
}

func (aws *AwsIntegrationData) GetCredentials() *AwsCrossAccountCreds {
if aws.Credentials != nil {
return aws.Credentials
}
return &AwsCrossAccountCreds{}
}

func (aws *AwsIntegrationData) GetGovCloudCredentials() *AwsGovCloudCreds {
if aws.GovCloudCredentials != nil {
return aws.GovCloudCredentials
}
return &AwsGovCloudCreds{}
}

func (aws *AwsIntegrationData) GetAccountID() string {
if aws.GovCloudCredentials != nil {
return aws.GovCloudCredentials.AccountID
}
return aws.AwsAccountID
}

func (aws *AwsIntegrationData) EncodeAccountMappingFile(mapping []byte) {
Expand Down Expand Up @@ -173,3 +197,24 @@ type AwsCrossAccountCreds struct {
RoleArn string `json:"ROLE_ARN" mapstructure:"ROLE_ARN"`
ExternalID string `json:"EXTERNAL_ID" mapstructure:"EXTERNAL_ID"`
}

type AwsGovCloudIntegrationsResponse struct {
Data []AwsGovCloudIntegration `json:"data"`
Ok bool `json:"ok"`
Message string `json:"message"`
}

type AwsGovCloudIntegration struct {
commonIntegrationData
Data AwsGovCloudIntegrationData `json:"DATA"`
}

type AwsGovCloudIntegrationData struct {
Credentials AwsGovCloudCreds `json:"ACCESS_KEY_CREDENTIALS" mapstructure:"ACCESS_KEY_CREDENTIALS"`
}

type AwsGovCloudCreds struct {
AccountID string `json:"ACCOUNT_ID" mapstructure:"ACCOUNT_ID"`
AccessKeyID string `json:"ACCESS_KEY_ID" mapstructure:"ACCESS_KEY_ID"`
SecretAccessKey string `json:"SECRET_ACCESS_KEY" mapstructure:"SECRET_ACCESS_KEY"`
}
138 changes: 128 additions & 10 deletions api/integrations_aws_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import (
func TestIntegrationsNewAwsCfgIntegration(t *testing.T) {
subject := api.NewAwsCfgIntegration("integration_name",
api.AwsIntegrationData{
Credentials: api.AwsCrossAccountCreds{
Credentials: &api.AwsCrossAccountCreds{
RoleArn: "arn:foo:bar",
ExternalID: "0123456789",
},
Expand All @@ -43,6 +43,28 @@ func TestIntegrationsNewAwsCfgIntegration(t *testing.T) {
assert.Equal(t, api.AwsCfgIntegration.String(), subject.Type)
}

func TestIntegrationsEmptyAwsCredentials(t *testing.T) {
var awsData api.AwsIntegrationData
credentials := awsData.GetCredentials()

externalID := credentials.ExternalID
roleArn := credentials.RoleArn
assert.Empty(t, externalID)
assert.Empty(t, roleArn)
}

func TestIntegrationsEmptyAwsGovCloudCredentials(t *testing.T) {
var awsData api.AwsIntegrationData
credentials := awsData.GetGovCloudCredentials()
accountID := awsData.GetAccountID()

secretKey := credentials.SecretAccessKey
accessID := credentials.AccessKeyID
assert.Empty(t, accountID)
assert.Empty(t, secretKey)
assert.Empty(t, accessID)
}

func TestIntegrationsNewAwsCfgIntegrationWithCustomTemplateFile(t *testing.T) {
accountMappingJSON := []byte(`{
"defaultLaceworkAccountAws": "lw_account_1",
Expand All @@ -62,7 +84,7 @@ func TestIntegrationsNewAwsCfgIntegrationWithCustomTemplateFile(t *testing.T) {
}
}`)
awsData := api.AwsIntegrationData{
Credentials: api.AwsCrossAccountCreds{
Credentials: &api.AwsCrossAccountCreds{
RoleArn: "arn:foo:bar",
ExternalID: "0123456789",
},
Expand Down Expand Up @@ -120,15 +142,15 @@ func TestIntegrationsCreateAws(t *testing.T) {
data := api.NewAwsIntegration("integration_name",
api.AwsCfgIntegration,
api.AwsIntegrationData{
Credentials: api.AwsCrossAccountCreds{
Credentials: &api.AwsCrossAccountCreds{
RoleArn: "arn:foo:bar",
ExternalID: "0123456789",
},
},
)
assert.Equal(t, "integration_name", data.Name, "GCP integration name mismatch")
assert.Equal(t, "AWS_CFG", data.Type, "a new GCP integration should match its type")
assert.Equal(t, 1, data.Enabled, "a new GCP integration should be enabled")
assert.Equal(t, "integration_name", data.Name, "AWS integration name mismatch")
assert.Equal(t, "AWS_CFG", data.Type, "a new AWS integration should match its type")
assert.Equal(t, 1, data.Enabled, "a new AWS integration should be enabled")

response, err := c.Integrations.CreateAws(data)
assert.Nil(t, err)
Expand All @@ -144,6 +166,65 @@ func TestIntegrationsCreateAws(t *testing.T) {
}
}

func TestIntegrationsCreateAwsGovCloudConfig(t *testing.T) {
var (
intgGUID = intgguid.New()
fakeServer = lacework.MockServer()
)

fakeServer.MockAPI("external/integrations", func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "POST", r.Method, "CreateAwsGovCloud should be a POST method")

if assert.NotNil(t, r.Body) {
body := httpBodySniffer(r)
assert.Contains(t, body, "integration_name", "integration name is missing")
assert.Contains(t, body, "AWS_US_GOV_CFG", "wrong integration type")
assert.Contains(t, body, "0123456789", "wrong account id")
assert.Contains(t, body, "AWS123abcAccessKeyID", "wrong access key id")
assert.Contains(t, body, "AWS123abc123abcSecretAccessKey0000000000", "wrong secret access key")
assert.Contains(t, body, "ENABLED\":1", "integration is not enabled")
}

fmt.Fprintf(w, awsGovCloudIntegrationJsonResponse(intgGUID))
})
defer fakeServer.Close()

c, err := api.NewClient("test",
api.WithToken("TOKEN"),
api.WithURL(fakeServer.URL()),
)
assert.Nil(t, err)

data := api.NewAwsIntegration("integration_name",
api.AwsGovCloudCfgIntegration,
api.AwsIntegrationData{
GovCloudCredentials: &api.AwsGovCloudCreds{
AccountID: "0123456789",
AccessKeyID: "AWS123abcAccessKeyID",
SecretAccessKey: "AWS123abc123abcSecretAccessKey0000000000",
},
},
)
assert.Equal(t, "integration_name", data.Name, "AWS integration name mismatch")
assert.Equal(t, "AWS_US_GOV_CFG", data.Type, "a new AWS GovCloud integration should match its type")
assert.Equal(t, 1, data.Enabled, "a new AWS integration should be enabled")

response, err := c.Integrations.CreateAws(data)
assert.Nil(t, err)
assert.NotNil(t, response)
assert.True(t, response.Ok)
if assert.Equal(t, 1, len(response.Data)) {
resData := response.Data[0]
assert.Equal(t, intgGUID, resData.IntgGuid)
assert.Equal(t, "integration_name", resData.Name)
assert.True(t, resData.State.Ok)
credentials := resData.Data.GetGovCloudCredentials()
assert.Equal(t, "AWS123abcAccessKeyID", credentials.AccessKeyID)
assert.Equal(t, "AWS123abc123abcSecretAccessKey0000000000", credentials.SecretAccessKey)
assert.Equal(t, "0123456789", resData.Data.GetAccountID())
}
}

func TestIntegrationsGetAws(t *testing.T) {
var (
intgGUID = intgguid.New()
Expand Down Expand Up @@ -208,15 +289,15 @@ func TestIntegrationsUpdateAws(t *testing.T) {
data := api.NewAwsIntegration("integration_name",
api.AwsCloudTrailIntegration,
api.AwsIntegrationData{
Credentials: api.AwsCrossAccountCreds{
Credentials: &api.AwsCrossAccountCreds{
RoleArn: "arn:foo:bar",
ExternalID: "0123456789",
},
},
)
assert.Equal(t, "integration_name", data.Name, "GCP integration name mismatch")
assert.Equal(t, "AWS_CT_SQS", data.Type, "a new GCP integration should match its type")
assert.Equal(t, 1, data.Enabled, "a new GCP integration should be enabled")
assert.Equal(t, "integration_name", data.Name, "AWS integration name mismatch")
assert.Equal(t, "AWS_CT_SQS", data.Type, "a new AWS integration should match its type")
assert.Equal(t, 1, data.Enabled, "a new AWS integration should be enabled")
data.IntgGuid = intgGUID

response, err := c.Integrations.UpdateAws(data)
Expand Down Expand Up @@ -292,6 +373,16 @@ func awsIntegrationJsonResponse(intgGUID string) string {
`
}

func awsGovCloudIntegrationJsonResponse(intgGUID string) string {
return `
{
"data": [` + singleAwsGovCloudCfgIntegration(intgGUID) + `],
"ok": true,
"message": "SUCCESS"
}
`
}

func awsMultiIntegrationJsonResponse(guids []string) string {
integrations := []string{}
for _, guid := range guids {
Expand Down Expand Up @@ -331,3 +422,30 @@ func singleAwsIntegration(id string) string {
}
`
}

func singleAwsGovCloudCfgIntegration(id string) string {
return `
{
"INTG_GUID": "` + id + `",
"NAME": "integration_name",
"CREATED_OR_UPDATED_TIME": "2020-Mar-10 01:00:00 UTC",
"CREATED_OR_UPDATED_BY": "[email protected]",
"TYPE": "AWS_US_GOV_CFG",
"ENABLED": 1,
"STATE": {
"ok": true,
"lastUpdatedTime": "2020-Mar-10 01:00:00 UTC",
"lastSuccessfulTime": "2020-Mar-10 01:00:00 UTC"
},
"IS_ORG": 0,
"DATA": {
"ACCESS_KEY_CREDENTIALS": {
"ACCOUNT_ID": "0123456789",
"ACCESS_KEY_ID": "AWS123abcAccessKeyID",
"SECRET_ACCESS_KEY": "AWS123abc123abcSecretAccessKey0000000000"
}
},
"TYPE_NAME": "AWS Compliance"
}
`
}
3 changes: 3 additions & 0 deletions cli/cmd/integration.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ func promptCreateIntegration() error {
"Google Container Registry (GCR)",
"AWS Config",
"AWS CloudTrail",
"AWS Config (US GovCloud)",
"GCP Config",
"GCP Audit Log",
"Azure Config",
Expand Down Expand Up @@ -279,6 +280,8 @@ func promptCreateIntegration() error {
return createAwsConfigIntegration()
case "AWS CloudTrail":
return createAwsCloudTrailIntegration()
case "AWS GovCloud Config":
return createAwsGovCloudConfigIntegration()
case "GCP Config":
return createGcpConfigIntegration()
case "GCP Audit Log":
Expand Down
4 changes: 2 additions & 2 deletions cli/cmd/integration_aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func createAwsConfigIntegration() error {

awsCfg := api.NewAwsCfgIntegration(answers.Name,
api.AwsIntegrationData{
Credentials: api.AwsCrossAccountCreds{
Credentials: &api.AwsCrossAccountCreds{
RoleArn: answers.RoleArn,
ExternalID: answers.ExternalID,
},
Expand Down Expand Up @@ -110,7 +110,7 @@ func createAwsCloudTrailIntegration() error {

aws := api.AwsIntegrationData{
QueueUrl: answers.QueueUrl,
Credentials: api.AwsCrossAccountCreds{
Credentials: &api.AwsCrossAccountCreds{
RoleArn: answers.RoleArn,
ExternalID: answers.ExternalID,
},
Expand Down
Loading

0 comments on commit 68d7087

Please sign in to comment.