diff --git a/api/_examples/pagerduty-alert-channel/main.go b/api/_examples/pagerduty-alert-channel/main.go new file mode 100644 index 000000000..a68bf1a0f --- /dev/null +++ b/api/_examples/pagerduty-alert-channel/main.go @@ -0,0 +1,30 @@ +package main + +import ( + "fmt" + "log" + + "github.com/lacework/go-sdk/api" +) + +func main() { + lacework, err := api.NewClient("account", api.WithApiKeys("KEY", "SECRET")) + if err != nil { + log.Fatal(err) + } + + alert := api.NewPagerDutyAlertChannel("pagerduty-alert-from-golang", + api.PagerDutyData{ + IntegrationKey: "1234abc8901abc567abc123abc78e012", + MinAlertSeverity: 5, + }, + ) + + response, err := lacework.Integrations.CreatePagerDutyAlertChannel(alert) + if err != nil { + log.Fatal(err) + } + + // Output: PagerDuty alert channel created: THE-INTEGRATION-GUID + fmt.Printf("PagerDuty alert channel created: %s", response.Data[0].IntgGuid) +} diff --git a/api/integration_alert_channels_pagerduty.go b/api/integration_alert_channels_pagerduty.go new file mode 100644 index 000000000..d104669d6 --- /dev/null +++ b/api/integration_alert_channels_pagerduty.go @@ -0,0 +1,101 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +// NewPagerDutyAlertChannel returns an instance of PagerDutyAlertChannel +// with the provided name and data. +// +// Basic usage: Initialize a new PagerDutyAlertChannel struct, then +// use the new instance to do CRUD operations +// +// client, err := api.NewClient("account") +// if err != nil { +// return err +// } +// +// pagerduty := api.NewPagerDutyAlertChannel("foo", +// api.PagerDutyData{ +// IntegrationKey: "1234abc8901abc567abc123abc78e012", +// MinAlertSeverity: api.AllAlertLevel, +// }, +// ) +// +// client.Integrations.CreatePagerDutyAlertChannel(pagerduty) +// +func NewPagerDutyAlertChannel(name string, data PagerDutyData) PagerDutyAlertChannel { + return PagerDutyAlertChannel{ + commonIntegrationData: commonIntegrationData{ + Name: name, + Type: PagerDutyIntegration.String(), + Enabled: 1, + }, + Data: data, + } +} + +// CreatePagerDutyAlertChannel creates a pager duty alert channel integration on the Lacework Server +func (svc *IntegrationsService) CreatePagerDutyAlertChannel(integration PagerDutyAlertChannel) ( + response PagerDutyAlertChannelResponse, + err error, +) { + err = svc.create(integration, &response) + return +} + +// GetPagerDutyAlertChannel gets a pager duty alert channel integration that matches with +// the provided integration guid on the Lacework Server +func (svc *IntegrationsService) GetPagerDutyAlertChannel(guid string) ( + response PagerDutyAlertChannelResponse, + err error, +) { + err = svc.get(guid, &response) + return +} + +// UpdatePagerDutyAlertChannel updates a single pager duty alert channel integration +func (svc *IntegrationsService) UpdatePagerDutyAlertChannel(data PagerDutyAlertChannel) ( + response PagerDutyAlertChannelResponse, + err error, +) { + err = svc.update(data.IntgGuid, data, &response) + return +} + +// ListPagerDutyAlertChannel lists the PAGER_DUTY_API external integrations available on the Lacework Server +func (svc *IntegrationsService) ListPagerDutyAlertChannel() (response PagerDutyAlertChannelResponse, err error) { + err = svc.listByType(PagerDutyIntegration, &response) + return +} + +type PagerDutyAlertChannelResponse struct { + Data []PagerDutyAlertChannel `json:"data"` + Ok bool `json:"ok"` + Message string `json:"message"` +} + +type PagerDutyAlertChannel struct { + commonIntegrationData + Data PagerDutyData `json:"DATA"` +} + +type PagerDutyData struct { + IssueGrouping string `json:"ISSUE_GROUPING,omitempty" mapstructure:"ISSUE_GROUPING"` + IntegrationKey string `json:"API_INTG_KEY" mapstructure:"API_INTG_KEY"` + MinAlertSeverity AlertLevel `json:"MIN_ALERT_SEVERITY,omitempty" mapstructure:"MIN_ALERT_SEVERITY"` +} diff --git a/api/integration_alert_channels_pagerduty_test.go b/api/integration_alert_channels_pagerduty_test.go new file mode 100644 index 000000000..338cf99ce --- /dev/null +++ b/api/integration_alert_channels_pagerduty_test.go @@ -0,0 +1,252 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api_test + +import ( + "fmt" + "net/http" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/lacework/go-sdk/api" + "github.com/lacework/go-sdk/internal/intgguid" + "github.com/lacework/go-sdk/internal/lacework" +) + +func TestIntegrationsNewPagerDutyAlertChannel(t *testing.T) { + subject := api.NewPagerDutyAlertChannel("integration_name", + api.PagerDutyData{ + IntegrationKey: "1234567890abcd1234567890", + MinAlertSeverity: 3, + }, + ) + assert.Equal(t, api.PagerDutyIntegration.String(), subject.Type) + assert.Equal(t, api.MediumAlertLevel, subject.Data.MinAlertSeverity) +} + +func TestIntegrationsCreatePagerDutyAlertChannel(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, "CreatePagerDutyAlertChannel 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, "PAGER_DUTY_API", "wrong integration type") + assert.Contains(t, body, "1234567890abcd1234567890", "wrong integration_key") + assert.Contains(t, body, "MIN_ALERT_SEVERITY\":3", "wrong alert severity") + assert.Contains(t, body, "ENABLED\":1", "integration is not enabled") + } + + fmt.Fprintf(w, pagerDutyIntegrationJsonResponse(intgGUID)) + }) + defer fakeServer.Close() + + c, err := api.NewClient("test", + api.WithToken("TOKEN"), + api.WithURL(fakeServer.URL()), + ) + assert.Nil(t, err) + + data := api.NewPagerDutyAlertChannel("integration_name", + api.PagerDutyData{ + IntegrationKey: "1234567890abcd1234567890", + MinAlertSeverity: 3, + }, + ) + assert.Equal(t, "integration_name", data.Name, "PagerDuty integration name mismatch") + assert.Equal(t, "PAGER_DUTY_API", data.Type, "a new PagerDuty integration should match its type") + assert.Equal(t, 1, data.Enabled, "a new PagerDuty integration should be enabled") + + response, err := c.Integrations.CreatePagerDutyAlertChannel(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) + assert.Equal(t, "1234567890abcd1234567890", resData.Data.IntegrationKey) + assert.Equal(t, api.AlertLevel(3), resData.Data.MinAlertSeverity) + } +} + +func TestIntegrationsGetPagerDutyAlertChannel(t *testing.T) { + var ( + intgGUID = intgguid.New() + apiPath = fmt.Sprintf("external/integrations/%s", intgGUID) + fakeServer = lacework.MockServer() + ) + fakeServer.MockAPI(apiPath, func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "GET", r.Method, "GetPagerDutyAlertChannel should be a GET method") + fmt.Fprintf(w, pagerDutyIntegrationJsonResponse(intgGUID)) + }) + defer fakeServer.Close() + + c, err := api.NewClient("test", + api.WithToken("TOKEN"), + api.WithURL(fakeServer.URL()), + ) + assert.Nil(t, err) + + response, err := c.Integrations.GetPagerDutyAlertChannel(intgGUID) + 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) + assert.Equal(t, "1234567890abcd1234567890", resData.Data.IntegrationKey) + assert.Equal(t, api.AlertLevel(3), resData.Data.MinAlertSeverity) + } +} + +func TestIntegrationsUpdatePagerDutyAlertChannel(t *testing.T) { + var ( + intgGUID = intgguid.New() + apiPath = fmt.Sprintf("external/integrations/%s", intgGUID) + fakeServer = lacework.MockServer() + ) + fakeServer.MockAPI(apiPath, func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "PATCH", r.Method, "UpdatePagerDutyAlertChannel should be a PATCH method") + + if assert.NotNil(t, r.Body) { + body := httpBodySniffer(r) + assert.Contains(t, body, intgGUID, "INTG_GUID missing") + assert.Contains(t, body, "integration_name", "integration name is missing") + assert.Contains(t, body, "PAGER_DUTY_API", "wrong integration type") + assert.Contains(t, body, "1234567890abcd1234567890", "wrong integration_key") + assert.Contains(t, body, "MIN_ALERT_SEVERITY\":3", "wrong alert severity") + assert.Contains(t, body, "ENABLED\":1", "integration is not enabled") + } + + fmt.Fprintf(w, pagerDutyIntegrationJsonResponse(intgGUID)) + }) + defer fakeServer.Close() + + c, err := api.NewClient("test", + api.WithToken("TOKEN"), + api.WithURL(fakeServer.URL()), + ) + assert.Nil(t, err) + + data := api.NewPagerDutyAlertChannel("integration_name", + api.PagerDutyData{ + IntegrationKey: "1234567890abcd1234567890", + MinAlertSeverity: 3, + }, + ) + assert.Equal(t, "integration_name", data.Name, "PagerDuty integration name mismatch") + assert.Equal(t, "PAGER_DUTY_API", data.Type, "a new PagerDuty integration should match its type") + assert.Equal(t, 1, data.Enabled, "a new PagerDuty integration should be enabled") + data.IntgGuid = intgGUID + + response, err := c.Integrations.UpdatePagerDutyAlertChannel(data) + assert.Nil(t, err) + assert.NotNil(t, response) + assert.Equal(t, "SUCCESS", response.Message) + assert.Equal(t, 1, len(response.Data)) + assert.Equal(t, intgGUID, response.Data[0].IntgGuid) +} + +func TestIntegrationsListPagerDutyAlertChannel(t *testing.T) { + var ( + intgGUIDs = []string{intgguid.New(), intgguid.New(), intgguid.New()} + fakeServer = lacework.MockServer() + ) + fakeServer.MockAPI("external/integrations/type/PAGER_DUTY_API", + func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "GET", r.Method, "ListPagerDutyAlertChannel should be a GET method") + fmt.Fprintf(w, pagerDutyMultiIntegrationJsonResponse(intgGUIDs)) + }, + ) + defer fakeServer.Close() + + c, err := api.NewClient("test", + api.WithToken("TOKEN"), + api.WithURL(fakeServer.URL()), + ) + assert.Nil(t, err) + + response, err := c.Integrations.ListPagerDutyAlertChannel() + assert.Nil(t, err) + assert.NotNil(t, response) + assert.True(t, response.Ok) + assert.Equal(t, len(intgGUIDs), len(response.Data)) + for _, d := range response.Data { + assert.Contains(t, intgGUIDs, d.IntgGuid) + } +} + +func pagerDutyIntegrationJsonResponse(intgGUID string) string { + return ` +{ + "data": [` + singlePagerDutyIntegration(intgGUID) + `], + "ok": true, + "message": "SUCCESS" +} +` +} + +func pagerDutyMultiIntegrationJsonResponse(guids []string) string { + integrations := []string{} + for _, guid := range guids { + integrations = append(integrations, singlePagerDutyIntegration(guid)) + } + return ` +{ +"data": [` + strings.Join(integrations, ", ") + `], +"ok": true, +"message": "SUCCESS" +} +` +} + +func singlePagerDutyIntegration(id string) string { + return ` +{ + "INTG_GUID": "` + id + `", + "CREATED_OR_UPDATED_BY": "user@email.com", + "CREATED_OR_UPDATED_TIME": "2020-Jul-16 19:59:22 UTC", + "DATA": { + "ISSUE_GROUPING": "Events", + "MIN_ALERT_SEVERITY": 3, + "API_INTG_KEY": "1234567890abcd1234567890" + }, + "ENABLED": 1, + "IS_ORG": 0, + "NAME": "integration_name", + "STATE": { + "lastSuccessfulTime": "2020-Jul-16 18:26:54 UTC", + "lastUpdatedTime": "2020-Jul-16 18:26:54 UTC", + "ok": true + }, + "TYPE": "PAGER_DUTY_API", + "TYPE_NAME": "PAGER_DUTY_API" +} +` +} diff --git a/api/integrations.go b/api/integrations.go index 4ed8a4c09..58f6691fb 100644 --- a/api/integrations.go +++ b/api/integrations.go @@ -60,6 +60,9 @@ const ( // AWS CloudWatch integration type AwsCloudWatchIntegration + + // Pager Duty integration type + PagerDutyIntegration ) // IntegrationTypes is the list of available integration types @@ -74,6 +77,7 @@ var IntegrationTypes = map[integrationType]string{ ContainerRegistryIntegration: "CONT_VULN_CFG", SlackChannelIntegration: "SLACK_CHANNEL", AwsCloudWatchIntegration: "CLOUDWATCH_EB", + PagerDutyIntegration: "PAGER_DUTY_API", } // String returns the string representation of an integration type