Skip to content

Commit

Permalink
feat(api): Migrate Service Now alert channel to API v2 (#582)
Browse files Browse the repository at this point in the history
* feat(api): Migrate Service Now alert channel to API v2

Signed-off-by: Darren Murray <[email protected]>
  • Loading branch information
dmurray-lacework authored Oct 14, 2021
1 parent c0c422b commit 692c8e5
Show file tree
Hide file tree
Showing 6 changed files with 295 additions and 9 deletions.
15 changes: 9 additions & 6 deletions api/_examples/service-now-alert-channel/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,33 @@ package main
import (
"fmt"
"log"
"os"

"github.com/lacework/go-sdk/api"
)

func main() {
lacework, err := api.NewClient("account", api.WithApiKeys("KEY", "SECRET"))
lacework, err := api.NewClient(os.Getenv("LW_ACCOUNT"), api.WithApiV2(),
api.WithApiKeys(os.Getenv("LW_API_KEY"), os.Getenv("LW_API_SECRET")))
if err != nil {
log.Fatal(err)
}

myServiceNowChannel := api.NewServiceNowAlertChannel("service-now-alert-from-golang",
api.ServiceNowChannelData{
InstanceURL: "snow-lacework.com",
myServiceNowChannel := api.NewAlertChannel("service-now-alert-from-golang",
api.ServiceNowRestAlertChannelType,
api.ServiceNowRestDataV2{
InstanceURL: "https://dev123.service-now.com",
Username: "snow-user",
Password: "snow-password",
IssueGrouping: "Events",
},
)

response, err := lacework.Integrations.CreateServiceNowAlertChannel(myServiceNowChannel)
response, err := lacework.V2.AlertChannels.Create(myServiceNowChannel)
if err != nil {
log.Fatal(err)
}

// Output: Service Now alert channel created: THE-INTEGRATION-GUID
fmt.Printf("Service Now alert channel created: %s", response.Data[0].IntgGuid)
fmt.Printf("Service Now alert channel created: %s", response.Data.IntgGuid)
}
2 changes: 2 additions & 0 deletions api/alert_channels.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ const (
CiscoSparkWebhookAlertChannelType
MicrosoftTeamsAlertChannelType
SplunkHecAlertChannelType
ServiceNowRestAlertChannelType
)

// AlertChannelTypeTypes is the list of available Alert Channel integration types
Expand All @@ -105,6 +106,7 @@ var AlertChannelTypes = map[alertChannelType]string{
CiscoSparkWebhookAlertChannelType: "CiscoSparkWebhook",
MicrosoftTeamsAlertChannelType: "MicrosoftTeams",
SplunkHecAlertChannelType: "SplunkHec",
ServiceNowRestAlertChannelType: "ServiceNowRest",
}

// String returns the string representation of a Alert Channel integration type
Expand Down
82 changes: 82 additions & 0 deletions api/alert_channels_service_now_rest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
//
// Author:: Darren Murray (<[email protected]>)
// Copyright:: Copyright 2021, 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

import (
"encoding/base64"
"fmt"
"strings"
)

// GetServiceNowRest gets a single ServiceNowRest alert channel matching the
// provided integration guid
func (svc *AlertChannelsService) GetServiceNowRest(guid string) (
response ServiceNowRestAlertChannelResponseV2,
err error,
) {
err = svc.get(guid, &response)
return
}

// UpdateServiceNowRest updates a single ServiceNowRest integration on the Lacework Server
func (svc *AlertChannelsService) UpdateServiceNowRest(data AlertChannel) (
response ServiceNowRestAlertChannelResponseV2,
err error,
) {
err = svc.update(data.ID(), data, &response)
return
}

func (snow *ServiceNowRestDataV2) EncodeCustomTemplateFile(template string) {
encodedTemplate := base64.StdEncoding.EncodeToString([]byte(template))
snow.CustomTemplateFile = fmt.Sprintf("data:application/json;name=i.json;base64,%s", encodedTemplate)
}

func (snow *ServiceNowRestDataV2) DecodeCustomTemplateFile() (string, error) {
if len(snow.CustomTemplateFile) == 0 {
return "", nil
}

var (
b64 = strings.Split(snow.CustomTemplateFile, ",")
raw, err = base64.StdEncoding.DecodeString(b64[1])
)
if err != nil {
return "", err
}

return string(raw), nil
}

type ServiceNowRestAlertChannelResponseV2 struct {
Data ServiceNowRestAlertChannelV2 `json:"data"`
}

type ServiceNowRestAlertChannelV2 struct {
v2CommonIntegrationData
Data ServiceNowRestDataV2 `json:"data"`
}

type ServiceNowRestDataV2 struct {
Username string `json:"userName"`
Password string `json:"password"`
InstanceURL string `json:"instanceUrl"`
CustomTemplateFile string `json:"customTemplateFile,omitempty"`
IssueGrouping string `json:"issueGrouping,omitempty"`
}
185 changes: 185 additions & 0 deletions api/alert_channels_service_now_rest_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
//
// Author:: Darren Murray (<[email protected]>)
// Copyright:: Copyright 2021, 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"
"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 TestAlertChannelsGetServiceNowRest(t *testing.T) {
var (
intgGUID = intgguid.New()
apiPath = fmt.Sprintf("AlertChannels/%s", intgGUID)
fakeServer = lacework.MockServer()
)
fakeServer.UseApiV2()
fakeServer.MockToken("TOKEN")
defer fakeServer.Close()

fakeServer.MockAPI(apiPath, func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "GET", r.Method, "GetServiceNowRest() should be a GET method")
fmt.Fprintf(w, generateAlertChannelResponse(singleServiceNowRestAlertChannel(intgGUID)))
})

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

response, err := c.V2.AlertChannels.GetServiceNowRest(intgGUID)
if assert.NoError(t, err) {
assert.NotNil(t, response)
assert.Equal(t, intgGUID, response.Data.IntgGuid)
assert.Equal(t, "integration_name", response.Data.Name)
assert.True(t, response.Data.State.Ok)
assert.Equal(t, response.Data.Data.Username, "snow-user")
assert.Equal(t, response.Data.Data.Password, "snow-password")
assert.Equal(t, response.Data.Data.InstanceURL, "snow-lacework.com")
assert.Equal(t, response.Data.Data.IssueGrouping, "Events")
}
}

func TestAlertChannelServiceNowRestUpdate(t *testing.T) {
var (
intgGUID = intgguid.New()
apiPath = fmt.Sprintf("AlertChannels/%s", intgGUID)
fakeServer = lacework.MockServer()
)
fakeServer.UseApiV2()
fakeServer.MockToken("TOKEN")
defer fakeServer.Close()

fakeServer.MockAPI(apiPath, func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "PATCH", r.Method, "UpdateServiceNowRest() 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", "alert channel name is missing")
assert.Contains(t, body, "ServiceNowRest", "wrong alert channel type")
assert.Contains(t, body, "snow-lacework.com", "missing service now instance url")
assert.Contains(t, body, "snow-user", "missing service now username")
assert.Contains(t, body, "snow-password", "missing service now password")
assert.Contains(t, body, "Events", "missing service now issue grouping")
}

fmt.Fprintf(w, generateAlertChannelResponse(singleServiceNowRestAlertChannel(intgGUID)))
})

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

serviceNowRestAlertChan := api.NewAlertChannel("integration_name",
api.ServiceNowRestAlertChannelType,
api.ServiceNowRestDataV2{
InstanceURL: "snow-lacework.com",
Username: "snow-user",
Password: "snow-password",
IssueGrouping: "Events",
},
)
assert.Equal(t, "integration_name", serviceNowRestAlertChan.Name, "ServiceNowRest alert channel name mismatch")
assert.Equal(t, "ServiceNowRest", serviceNowRestAlertChan.Type, "a new ServiceNowRest alert channel should match its type")
assert.Equal(t, 1, serviceNowRestAlertChan.Enabled, "a new ServiceNowRest alert channel should be enabled")
serviceNowRestAlertChan.IntgGuid = intgGUID

response, err := c.V2.AlertChannels.UpdateServiceNowRest(serviceNowRestAlertChan)
if assert.NoError(t, err) {
assert.NotNil(t, response)
assert.Equal(t, intgGUID, response.Data.IntgGuid)
assert.True(t, response.Data.State.Ok)
assert.Contains(t, response.Data.Data.InstanceURL, "snow-lacework.com")
assert.Contains(t, response.Data.Data.Username, "snow-user")
assert.Contains(t, response.Data.Data.Password, "snow-password")
assert.Contains(t, response.Data.Data.IssueGrouping, "Events")
}
}

func TestAlertChannelServiceNowRestUpdateWithCustomTemplateFile(t *testing.T) {
templateJSON := `{
"description" : "Generated by Lacework:",
"approval" : "Approved"
}`

serviceNowData := api.ServiceNowRestDataV2{
InstanceURL: "snow-lacework.com",
Username: "snow-user",
Password: "snow-password",
IssueGrouping: "Events",
}

serviceNowData.EncodeCustomTemplateFile(templateJSON)

assert.Contains(t,
serviceNowData.CustomTemplateFile,
"data:application/json;name=i.json;base64,",
"check the custom_template_file encoder",
)
templateString, err := serviceNowData.DecodeCustomTemplateFile()
assert.Nil(t, err)
assert.Equal(t, templateJSON, templateString)

// When there is no custom template file, this function should
// return an empty string to match the pattern
serviceNowData.CustomTemplateFile = ""
templateString, err = serviceNowData.DecodeCustomTemplateFile()
assert.Nil(t, err)
assert.Equal(t, "", templateString)

}

func singleServiceNowRestAlertChannel(id string) string {
return fmt.Sprintf(`
{
"createdOrUpdatedBy": "[email protected]",
"createdOrUpdatedTime": "2021-06-01T18:10:40.745Z",
"data": {
"instanceUrl": "snow-lacework.com",
"userName": "snow-user",
"password": "snow-password",
"issueGrouping": "Events"
},
"enabled": 1,
"intgGuid": %q,
"isOrg": 0,
"name": "integration_name",
"state": {
"details": {},
"lastSuccessfulTime": 1627895573122,
"lastUpdatedTime": 1627895573122,
"ok": true
},
"type": "ServiceNowRest"
}
`, id)
}
12 changes: 12 additions & 0 deletions api/alert_channels_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ func TestAlertChannelTypes(t *testing.T) {
"SplunkHec", api.SplunkHecAlertChannelType.String(),
"wrong alert channel type",
)
assert.Equal(t,
"ServiceNowRest", api.ServiceNowRestAlertChannelType.String(),
"wrong alert channel type",
)
}

func TestFindAlertChannelType(t *testing.T) {
Expand Down Expand Up @@ -115,6 +119,10 @@ func TestFindAlertChannelType(t *testing.T) {
alertFound, found = api.FindAlertChannelType("SplunkHec")
assert.True(t, found, "alert channel type should exist")
assert.Equal(t, "SplunkHec", alertFound.String(), "wrong alert channel type")

alertFound, found = api.FindAlertChannelType("ServiceNowRest")
assert.True(t, found, "alert channel type should exist")
assert.Equal(t, "ServiceNowRest", alertFound.String(), "wrong alert channel type")
}

func TestAlertChannelsGet(t *testing.T) {
Expand Down Expand Up @@ -257,6 +265,7 @@ func TestAlertChannelsList(t *testing.T) {
ciscoSparkWebhookAlertChan = generateGuids(&allGUIDs, 2)
microsoftTeamsAlertChan = generateGuids(&allGUIDs, 2)
splunkHecOpsAlertChan = generateGuids(&allGUIDs, 2)
serviceNowRestOpsAlertChan = generateGuids(&allGUIDs, 2)
expectedLen = len(allGUIDs)
fakeServer = lacework.MockServer()
)
Expand All @@ -277,6 +286,7 @@ func TestAlertChannelsList(t *testing.T) {
generateAlertChannels(ciscoSparkWebhookAlertChan, "CiscoSparkWebhook"),
generateAlertChannels(microsoftTeamsAlertChan, "MicrosoftTeams"),
generateAlertChannels(splunkHecOpsAlertChan, "SplunkHec"),
generateAlertChannels(serviceNowRestOpsAlertChan, "ServiceNowRest"),
}
fmt.Fprintf(w,
generateAlertChannelsResponse(
Expand Down Expand Up @@ -338,6 +348,8 @@ func generateAlertChannels(guids []string, iType string) string {
alertChannels[i] = singleMicrosoftTeamsAlertChannel(guid)
case api.SplunkHecAlertChannelType.String():
alertChannels[i] = singleSplunkAlertChannel(guid)
case api.ServiceNowRestAlertChannelType.String():
alertChannels[i] = singleServiceNowRestAlertChannel(guid)
}
}
return strings.Join(alertChannels, ", ")
Expand Down
8 changes: 5 additions & 3 deletions cli/cmd/integration_service_now_channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func createServiceNowAlertChannelIntegration() error {
return err
}

snow := api.ServiceNowChannelData{
snow := api.ServiceNowRestDataV2{
InstanceURL: answers.InstanceURL,
Username: answers.Username,
Password: answers.Password,
Expand Down Expand Up @@ -101,10 +101,12 @@ func createServiceNowAlertChannelIntegration() error {
snow.EncodeCustomTemplateFile(content)
}

snowAlert := api.NewServiceNowAlertChannel(answers.Name, snow)
snowAlert := api.NewAlertChannel(answers.Name,
api.ServiceNowRestAlertChannelType,
snow)

cli.StartProgress(" Creating integration...")
_, err = cli.LwApi.Integrations.CreateServiceNowAlertChannel(snowAlert)
_, err = cli.LwApi.V2.AlertChannels.Create(snowAlert)
cli.StopProgress()
return err
}

0 comments on commit 692c8e5

Please sign in to comment.