From d7242b84a525a4b9484324f2dfb49b172528302b Mon Sep 17 00:00:00 2001 From: Darren <75614232+dmurray-lacework@users.noreply.github.com> Date: Fri, 19 Feb 2021 03:07:40 +0000 Subject: [PATCH] feat(cli): New Relic Insights alert channel (#323) Signed-off-by: Darren Murray --- api/_examples/new-relic-alert-channel/main.go | 30 +++ api/integration_alert_channels_new_relic.go | 100 +++++++ ...tegration_alert_channels_new_relic_test.go | 250 ++++++++++++++++++ api/integrations.go | 4 + cli/cmd/integration.go | 22 ++ cli/cmd/integration_new_relic_channel.go | 70 +++++ 6 files changed, 476 insertions(+) create mode 100644 api/_examples/new-relic-alert-channel/main.go create mode 100644 api/integration_alert_channels_new_relic.go create mode 100644 api/integration_alert_channels_new_relic_test.go create mode 100644 cli/cmd/integration_new_relic_channel.go diff --git a/api/_examples/new-relic-alert-channel/main.go b/api/_examples/new-relic-alert-channel/main.go new file mode 100644 index 000000000..f98cf560d --- /dev/null +++ b/api/_examples/new-relic-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) + } + + myNewRelicChannel := api.NewNewRelicAlertChannel("new-relic-alert-from-golang", + api.NewRelicChannelData{ + AccountID: 2338053, + InsertKey: "x-xx-xxxxxxxxxxxxxxxxxx", + }, + ) + + response, err := lacework.Integrations.CreateNewRelicAlertChannel(myNewRelicChannel) + if err != nil { + log.Fatal(err) + } + + // Output: New Relic alert channel created: THE-INTEGRATION-GUID + fmt.Printf("New Relic alert channel created: %s", response.Data[0].IntgGuid) +} diff --git a/api/integration_alert_channels_new_relic.go b/api/integration_alert_channels_new_relic.go new file mode 100644 index 000000000..7b1cb3b86 --- /dev/null +++ b/api/integration_alert_channels_new_relic.go @@ -0,0 +1,100 @@ +// +// Author:: Darren Murray () +// 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 + +// NewNewRelicAlertChannel returns an instance of NewRelicAlertChannel +// with the provided name and data. +// +// Basic usage: Initialize a new NewRelicAlertChannel struct, then +// use the new instance to do CRUD operations +// +// client, err := api.NewClient("account") +// if err != nil { +// return err +// } +// +// newRelicChannel := api.NewNewRelicAlertChannel("foo", +// api.NewRelicChannelData{ +// AccountID: 2338053, +// InsertKey: "x-xx-xxxxxxxxxxxxxxxxxx", +// }, +// ) +// +// client.Integrations.CreateNewRelicAlertChannel(newRelicChannel) +// +func NewNewRelicAlertChannel(name string, data NewRelicChannelData) NewRelicAlertChannel { + return NewRelicAlertChannel{ + commonIntegrationData: commonIntegrationData{ + Name: name, + Type: NewRelicChannelIntegration.String(), + Enabled: 1, + }, + Data: data, + } +} + +// CreateNewRelicAlertChannel creates an NEW_RELIC_INSIGHTS alert channel integration on the Lacework Server +func (svc *IntegrationsService) CreateNewRelicAlertChannel(integration NewRelicAlertChannel) ( + response NewRelicAlertChannelResponse, + err error, +) { + err = svc.create(integration, &response) + return +} + +// GetNewRelicAlertChannel gets an NEW_RELIC_INSIGHTS alert channel integration that matches with +// the provided integration guid on the Lacework Server +func (svc *IntegrationsService) GetNewRelicAlertChannel(guid string) ( + response NewRelicAlertChannelResponse, + err error, +) { + err = svc.get(guid, &response) + return +} + +// UpdateNewRelicAlertChannel updates a single NEW_RELIC_INSIGHTS alert channel integration +func (svc *IntegrationsService) UpdateNewRelicAlertChannel(data NewRelicAlertChannel) ( + response NewRelicAlertChannelResponse, + err error, +) { + err = svc.update(data.IntgGuid, data, &response) + return +} + +// ListNewRelicAlertChannel lists the NEW_RELIC_INSIGHTS external integrations available on the Lacework Server +func (svc *IntegrationsService) ListNewRelicAlertChannel() (response NewRelicAlertChannelResponse, err error) { + err = svc.listByType(NewRelicChannelIntegration, &response) + return +} + +type NewRelicAlertChannelResponse struct { + Data []NewRelicAlertChannel `json:"data"` + Ok bool `json:"ok"` + Message string `json:"message"` +} + +type NewRelicAlertChannel struct { + commonIntegrationData + Data NewRelicChannelData `json:"DATA"` +} + +type NewRelicChannelData struct { + AccountID int `json:"ACCOUNT_ID" mapstructure:"ACCOUNT_ID"` + InsertKey string `json:"INSERT_KEY" mapstructure:"INSERT_KEY"` +} diff --git a/api/integration_alert_channels_new_relic_test.go b/api/integration_alert_channels_new_relic_test.go new file mode 100644 index 000000000..ff2c727f9 --- /dev/null +++ b/api/integration_alert_channels_new_relic_test.go @@ -0,0 +1,250 @@ +// +// Author:: Darren Murray () +// 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 TestIntegrationsNewNewRelicAlertChannel(t *testing.T) { + subject := api.NewNewRelicAlertChannel("integration_name", + api.NewRelicChannelData{ + AccountID: 2338053, + InsertKey: "x-xx-xxxxxxxxxxxxxxxxxx", + }, + ) + assert.Equal(t, api.NewRelicChannelIntegration.String(), subject.Type) +} + +func TestIntegrationsCreateNewRelicAlertChannel(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, "CreateNewRelicAlertChannel 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, "NEW_RELIC_INSIGHTS", "wrong integration type") + assert.Contains(t, body, "2338053", "wrong account id") + assert.Contains(t, body, "x-xx-xxxxxxxxxxxxxxxxxx", "wrong insert key") + assert.Contains(t, body, "ENABLED\":1", "integration is not enabled") + } + + fmt.Fprintf(w, newRelicChannelIntegrationJsonResponse(intgGUID)) + }) + defer fakeServer.Close() + + c, err := api.NewClient("test", + api.WithToken("TOKEN"), + api.WithURL(fakeServer.URL()), + ) + assert.Nil(t, err) + + data := api.NewNewRelicAlertChannel("integration_name", + api.NewRelicChannelData{ + AccountID: 2338053, + InsertKey: "x-xx-xxxxxxxxxxxxxxxxxx", + }, + ) + assert.Equal(t, "integration_name", data.Name, "NewRelicChannel integration name mismatch") + assert.Equal(t, "NEW_RELIC_INSIGHTS", data.Type, "a new NewRelicChannel integration should match its type") + assert.Equal(t, 1, data.Enabled, "a new NewRelicChannel integration should be enabled") + + response, err := c.Integrations.CreateNewRelicAlertChannel(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, 2338053, resData.Data.AccountID) + assert.Equal(t, "x-xx-xxxxxxxxxxxxxxxxxx", resData.Data.InsertKey) + } +} + +func TestIntegrationsGetNewRelicAlertChannel(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, "GetNewRelicAlertChannel should be a GET method") + fmt.Fprintf(w, newRelicChannelIntegrationJsonResponse(intgGUID)) + }) + defer fakeServer.Close() + + c, err := api.NewClient("test", + api.WithToken("TOKEN"), + api.WithURL(fakeServer.URL()), + ) + assert.Nil(t, err) + + response, err := c.Integrations.GetNewRelicAlertChannel(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, 2338053, resData.Data.AccountID) + assert.Equal(t, "x-xx-xxxxxxxxxxxxxxxxxx", resData.Data.InsertKey) + } +} + +func TestIntegrationsUpdateNewRelicAlertChannel(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, "UpdateNewRelicAlertChannel 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, "NEW_RELIC_INSIGHTS", "wrong integration type") + assert.Contains(t, body, "2338053", "wrong account id") + assert.Contains(t, body, "x-xx-xxxxxxxxxxxxxxxxxx", "wrong insert key") + assert.Contains(t, body, "ENABLED\":1", "integration is not enabled") + } + + fmt.Fprintf(w, newRelicChannelIntegrationJsonResponse(intgGUID)) + }) + defer fakeServer.Close() + + c, err := api.NewClient("test", + api.WithToken("TOKEN"), + api.WithURL(fakeServer.URL()), + ) + assert.Nil(t, err) + + data := api.NewNewRelicAlertChannel("integration_name", + api.NewRelicChannelData{ + AccountID: 2338053, + InsertKey: "x-xx-xxxxxxxxxxxxxxxxxx", + }, + ) + assert.Equal(t, "integration_name", data.Name, "NewRelicChannel integration name mismatch") + assert.Equal(t, "NEW_RELIC_INSIGHTS", data.Type, "a new NewRelicChannel integration should match its type") + assert.Equal(t, 1, data.Enabled, "a new NewRelicChannel integration should be enabled") + data.IntgGuid = intgGUID + + response, err := c.Integrations.UpdateNewRelicAlertChannel(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 TestIntegrationsListNewRelicAlertChannel(t *testing.T) { + var ( + intgGUIDs = []string{intgguid.New(), intgguid.New(), intgguid.New()} + fakeServer = lacework.MockServer() + ) + fakeServer.MockAPI("external/integrations/type/NEW_RELIC_INSIGHTS", + func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "GET", r.Method, "ListNewRelicAlertChannel should be a GET method") + fmt.Fprintf(w, newRelicChanMultiIntegrationJsonResponse(intgGUIDs)) + }, + ) + defer fakeServer.Close() + + c, err := api.NewClient("test", + api.WithToken("TOKEN"), + api.WithURL(fakeServer.URL()), + ) + assert.Nil(t, err) + + response, err := c.Integrations.ListNewRelicAlertChannel() + 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 newRelicChannelIntegrationJsonResponse(intgGUID string) string { + return ` +{ + "data": [` + singleNewRelicChanIntegration(intgGUID) + `], + "ok": true, + "message": "SUCCESS" +} +` +} + +func newRelicChanMultiIntegrationJsonResponse(guids []string) string { + integrations := []string{} + for _, guid := range guids { + integrations = append(integrations, singleNewRelicChanIntegration(guid)) + } + return ` +{ +"data": [` + strings.Join(integrations, ", ") + `], +"ok": true, +"message": "SUCCESS" +} +` +} + +func singleNewRelicChanIntegration(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": { + "ACCOUNT_ID": 2338053, + "INSERT_KEY": "x-xx-xxxxxxxxxxxxxxxxxx" + }, + "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": "NEW_RELIC_INSIGHTS", + "TYPE_NAME": "NEW_RELIC_INSIGHTS" +} +` +} diff --git a/api/integrations.go b/api/integrations.go index 427a1cefb..9c5491d20 100644 --- a/api/integrations.go +++ b/api/integrations.go @@ -55,6 +55,9 @@ const ( // GCP Pub Sub alert channel integration type GcpPubSubChannelIntegration + // New Relic Insights alert channel integration type + NewRelicChannelIntegration + // Azure Config integration type AzureCfgIntegration @@ -106,6 +109,7 @@ var IntegrationTypes = map[integrationType]string{ GcpCfgIntegration: "GCP_CFG", GcpAuditLogIntegration: "GCP_AT_SES", GcpPubSubChannelIntegration: "GCP_PUBSUB", + NewRelicChannelIntegration: "NEW_RELIC_INSIGHTS", AzureCfgIntegration: "AZURE_CFG", AzureActivityLogIntegration: "AZURE_AL_SEQ", ContainerRegistryIntegration: "CONT_VULN_CFG", diff --git a/cli/cmd/integration.go b/cli/cmd/integration.go index dbab82ab5..b15bd3906 100644 --- a/cli/cmd/integration.go +++ b/cli/cmd/integration.go @@ -205,6 +205,7 @@ func promptCreateIntegration() error { "Datadog Alert Channel", "GCP PubSub Alert Channel", "Microsoft Teams Alert Channel", + "New Relic Insights Alert Channel", "Webhook Alert Channel", "VictorOps Alert Channel", "Splunk Alert Channel", @@ -239,6 +240,8 @@ func promptCreateIntegration() error { return createGcpPubSubChannelIntegration() case "Microsoft Teams Alert Channel": return createMicrosoftTeamsChannelIntegration() + case "New Relic Insights Alert Channel": + return createNewRelicAlertChannelIntegration() case "AWS S3 Alert Channel": return createAwsS3ChannelIntegration() case "Cisco Webex Alert Channel": @@ -570,6 +573,25 @@ func reflectIntegrationData(raw api.RawIntegration) [][]string { return out + case api.NewRelicChannelIntegration.String(): + + var iData api.NewRelicChannelData + err := mapstructure.Decode(raw.Data, &iData) + if err != nil { + cli.Log.Debugw("unable to decode integration data", + "integration_type", raw.Type, + "raw_data", raw.Data, + "error", err, + ) + break + } + out := [][]string{ + []string{"ACCOUNT ID", fmt.Sprint(iData.AccountID)}, + []string{"INSERT API KEY", iData.InsertKey}, + } + + return out + case api.DatadogChannelIntegration.String(): var iData api.DatadogChannelData diff --git a/cli/cmd/integration_new_relic_channel.go b/cli/cmd/integration_new_relic_channel.go new file mode 100644 index 000000000..86f52ffeb --- /dev/null +++ b/cli/cmd/integration_new_relic_channel.go @@ -0,0 +1,70 @@ +// +// Author:: Darren Murray() +// 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 cmd + +import ( + "github.com/AlecAivazis/survey/v2" + + "github.com/lacework/go-sdk/api" +) + +func createNewRelicAlertChannelIntegration() error { + questions := []*survey.Question{ + { + Name: "name", + Prompt: &survey.Input{Message: "Name:"}, + Validate: survey.Required, + }, + { + Name: "account_id", + Prompt: &survey.Input{Message: "Account ID:"}, + Validate: survey.Required, + }, + { + Name: "insert_key", + Prompt: &survey.Input{Message: "Insert API Key:"}, + Validate: survey.Required, + }, + } + + answers := struct { + Name string + AccountID int `survey:"account_id"` + InsertKey string `survey:"insert_key"` + }{} + + err := survey.Ask(questions, &answers, + survey.WithIcons(promptIconsFunc), + ) + if err != nil { + return err + } + + relic := api.NewNewRelicAlertChannel(answers.Name, + api.NewRelicChannelData{ + AccountID: answers.AccountID, + InsertKey: answers.InsertKey, + }, + ) + + cli.StartProgress(" Creating integration...") + _, err = cli.LwApi.Integrations.CreateNewRelicAlertChannel(relic) + cli.StopProgress() + return err +}