diff --git a/api/api.go b/api/api.go index f77374014..f02ebf10a 100644 --- a/api/api.go +++ b/api/api.go @@ -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) diff --git a/api/integrations.go b/api/integrations.go index 9bb9bd487..529e9311b 100644 --- a/api/integrations.go +++ b/api/integrations.go @@ -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 + + // gcpAT - GCP Audit Log integration type + gcpAT + + // azureCFG - Azure Config integration type + azureCFG + + // azureAL - Azure Activity Log integration type + azureAL +) + +var integrationTypes = map[integrationType]string{ + awsCFG: "AWS_CFG", + awsCT: "AWS_CT_SQS", + gcpCFG: "GCP_CFG", + gcpAT: "GCP_AT_SES", + azureCFG: "AZURE_CFG", + azureAL: "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 +) + +var gcpResourceLevels = map[gcpResourceLevel]string{ + GcpProject: "PROJECT", + GcpOrganization: "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) @@ -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) + 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"` @@ -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 { diff --git a/api/integrations_test.go b/api/integrations_test.go index c2762df68..590c2250a 100644 --- a/api/integrations_test.go +++ b/api/integrations_test.go @@ -1,7 +1,124 @@ 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": "user@email.com", + "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": "xxxxxx@xxxxx.iam.gserviceaccount.com", + "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) + assert.Equal(t, "GCP_CFG", data.Type, "a new GCP integration should match its type") + data.Data.ID = "xxxxxxxxxx" + data.Data.Credentials.ClientId = "xxxxxxxxx" + data.Data.Credentials.ClientEmail = "xxxxxx@xxxxx.iam.gserviceaccount.com" + 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": "user@email.com", + "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": "xxxxxx@xxxxx.iam.gserviceaccount.com", + "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) +}