Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Creating and Getting GCP Integration #18

Merged
4 commits merged into from
Mar 13, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import "fmt"

const (
//apiIntegrationType = "external/integrations/type/%s/"
apiIntegrations = "external/integrations"
apiTokens = "access/tokens"
apiIntegrations = "external/integrations"
apiIntegrationByGUID = "external/integrations/%s"
apiTokens = "access/tokens"
)

// WithApiV2 configures the client to use the API version 2 (/api/v2)
Expand Down
103 changes: 99 additions & 4 deletions api/integrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,61 @@ import (
"strings"
)

type integrationType int

const (
// awsCFG - AWS Config integration type
// awsCFG integrationType = iota

// awsCT - AWS CloudTrail integration type
// awsCT

// gcpCFG - GCP Config integration type
gcpCFG integrationType = iota

// gcpAT - GCP Audit Log integration type
// gcpAT

// azureCFG - Azure Config integration type
// azureCFG

// azureAL - Azure Activity Log integration type
// azureAL
Copy link

@ghost ghost Mar 13, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that I think about it, you have to be very careful here, the fact that you add a single
constant with the keyword iota it means that you will have that constant set to 0
(gcpCFG = 0)

I bet you that this is a bug! if we call gcpCFG.String() it will return AWS_CFG

That is why I always recommend using this pattern:

var integrationTypes = map[integrationType]string{}

Mainly because you won't have to worry about the order of the array or index etc.
You just define them and it automatically works.

I would suggest implementing this pattern instead 👆🏽

Copy link

@ghost ghost Mar 13, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Look at this shippable job link

=== RUN   TestCreateGCPConfigIntegration
    TestCreateGCPConfigIntegration: integrations_test.go:78: 
        	Error Trace:	integrations_test.go:78
        	Error:      	Not equal: 
        	            	expected: "GCP_CFG"
        	            	actual  : "AWS_CFG"
        	            	
        	            	Diff:
        	            	--- Expected
        	            	+++ Actual
        	            	@@ -1 +1 @@
        	            	-GCP_CFG
        	            	+AWS_CFG
        	Test:       	TestCreateGCPConfigIntegration
        	Messages:   	a new GCP integration should match its type

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you were right. Now I see. For refactoring / adding new features, the map seems simpler.

)

var integrationTypes = []string{
"AWS_CFG",
"AWS_CT_SQS",
"GCP_CFG",
"GCP_AT_SES",
"AZURE_CFG",
"AZURE_AL_SEQ",
}

func (i integrationType) String() string {
return integrationTypes[i]
}

// gcpResourceLevel determines Project or Organization level integration
type gcpResourceLevel int

const (
// GcpProject level integration with GCP
GcpProject gcpResourceLevel = iota

// GcpOrganization level integration with GCP
GcpOrganization
)
Comment on lines +46 to +52
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since all these are global constants, we should make sure to call them appropriately,
It is a good practice to be over descriptive with these kinds of things, just like we
sometimes are with function names. Especially since these ones are public 😉

I would propose:

Suggested change
const (
// GcpProject level integration with GCP
GcpProject gcpResourceLevel = iota
// GcpOrganization level integration with GCP
GcpOrganization
)
const (
// a project level integration with GCP
GcpProjectLevelIntegration gcpResourceLevel = iota
// an organization level integration with GCP
GcpOrganizationLevelIntegration
)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let us do this in a further PR, I want us to keep iteration over this. Great work James!


var gcpResourceLevels = []string{
"PROJECT",
"ORGANIZATION",
}

func (g gcpResourceLevel) String() string {
return gcpResourceLevels[g]
}

// GetIntegrations lists the external integrations available on the server
func (c *Client) GetIntegrations() (response integrationsResponse, err error) {
err = c.RequestDecoder("GET", apiIntegrations, nil, &response)
Expand All @@ -21,6 +76,47 @@ func (c *Client) GetAWSIntegrations() (response awsIntegrationsResponse, err err
return
}

// NewGCPIntegrationData returns an instance of gcpIntegrationData
func NewGCPIntegrationData(name string, idType gcpResourceLevel) gcpIntegrationData {
return gcpIntegrationData{
commonIntegrationData: commonIntegrationData{
Name: name,
Type: gcpCFG.String(),
Enabled: 1,
},
Data: gcpCfg{
IdType: idType.String(),
},
}
}

// CreateGCPConfigIntegration creates a single integration on the server
func (c *Client) CreateGCPConfigIntegration(data gcpIntegrationData) (response gcpIntegrationsResponse, err error) {
err = c.createIntegration(data, &response)
return
}

func (c *Client) createIntegration(data interface{}, response interface{}) error {
body, err := jsonReader(data)
mjunglw marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return err
}

err = c.RequestDecoder("POST", apiIntegrations, body, response)
return err
}

// GetGCPConfigIntegration gets a single integration matching the integration guid available on the server
func (c *Client) GetGCPConfigIntegration(intgGuid string) (response gcpIntegrationsResponse, err error) {
err = c.getIntegration(intgGuid, &response)
return
}

func (c *Client) getIntegration(intgGuid string, response interface{}) error {
apiPath := fmt.Sprintf(apiIntegrationByGUID, intgGuid)
return c.RequestDecoder("GET", apiPath, nil, response)
}

type commonIntegrationData struct {
IntgGuid string `json:"INTG_GUID"`
Name string `json:"NAME"`
Expand Down Expand Up @@ -76,10 +172,9 @@ type gcpIntegrationData struct {
}

type gcpCfg struct {
ID string `json:"ID"`
IdType string `json:"ID_TYPE"`
IssueGrouping string `json:"ISSUE_GROUPING"`
Credentials gcpCredentials `json:"CREDENTIALS"`
ID string `json:"ID"`
IdType string `json:"ID_TYPE"`
Credentials gcpCredentials `json:"CREDENTIALS"`
}

type gcpCredentials struct {
Expand Down
118 changes: 117 additions & 1 deletion api/integrations_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,123 @@
package api_test

import "testing"
import (
"fmt"
"net/http"
"testing"

"github.com/lacework/go-sdk/api"
"github.com/stretchr/testify/assert"
)

func TestGetIntegrations(t *testing.T) {
// TODO @afiune implement a mocked Lacework API server
}

func TestCreateGCPConfigIntegration(t *testing.T) {
intgGUID := "12345"

fakeAPI := NewLaceworkServer()
fakeAPI.MockAPI("external/integrations", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, `
{
"data": [
{
"INTG_GUID": "`+intgGUID+`",
"NAME": "integration_name",
"CREATED_OR_UPDATED_TIME": "2020-Mar-10 01:00:00 UTC",
"CREATED_OR_UPDATED_BY": "[email protected]",
"TYPE": "GCP_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": {
"CREDENTIALS": {
"CLIENT_ID": "xxxxxxxxx",
"CLIENT_EMAIL": "[email protected]",
"PRIVATE_KEY_ID": "xxxxxxxxxxxxxxxx"
},
"ID_TYPE": "PROJECT",
"ID": "xxxxxxxxxx"
},
"TYPE_NAME": "GCP Compliance"
}
],
"ok": true,
"message": "SUCCESS"
}
`)
})
defer fakeAPI.Close()

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

data := api.NewGCPIntegrationData("integration_name", api.GcpProject)
This conversation was marked as resolved.
Show resolved Hide resolved
data.Data.ID = "xxxxxxxxxx"
data.Data.Credentials.ClientId = "xxxxxxxxx"
data.Data.Credentials.ClientEmail = "[email protected]"
data.Data.Credentials.PrivateKeyId = "xxxxxxxxxxxxxxxx"

response, err := c.CreateGCPConfigIntegration(data)
assert.Nil(t, err)
assert.NotNil(t, response)
assert.True(t, response.Ok)
assert.Equal(t, 1, len(response.Data))
assert.Equal(t, intgGUID, response.Data[0].IntgGuid)
}

func TestGetGCPConfigIntegration(t *testing.T) {
intgGUID := "12345"
apiPath := fmt.Sprintf("external/integrations/%s", intgGUID)

fakeAPI := NewLaceworkServer()
fakeAPI.MockAPI(apiPath, func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, `
{
"data": [
{
"INTG_GUID": "`+intgGUID+`",
"NAME": "integration_name",
"CREATED_OR_UPDATED_TIME": "2020-Mar-10 01:00:00 UTC",
"CREATED_OR_UPDATED_BY": "[email protected]",
"TYPE": "GCP_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": {
"CREDENTIALS": {
"CLIENT_ID": "xxxxxxxxx",
"CLIENT_EMAIL": "[email protected]",
"PRIVATE_KEY_ID": "xxxxxxxxxxxxxxxx"
},
"ID_TYPE": "PROJECT",
"ID": "xxxxxxxxxx"
},
"TYPE_NAME": "GCP Compliance"
}
],
"ok": true,
"message": "SUCCESS"
}
`)
})
defer fakeAPI.Close()

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

response, err := c.GetGCPConfigIntegration(intgGUID)
assert.Nil(t, err)
assert.NotNil(t, response)
assert.True(t, response.Ok)
assert.Equal(t, 1, len(response.Data))
assert.Equal(t, intgGUID, response.Data[0].IntgGuid)
}