diff --git a/api/_examples/microsoft-teams-alert-channel/main.go b/api/_examples/microsoft-teams-alert-channel/main.go new file mode 100644 index 000000000..6e15827ca --- /dev/null +++ b/api/_examples/microsoft-teams-alert-channel/main.go @@ -0,0 +1,29 @@ +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) + } + + myTeamsChannel := api.NewMicrosoftTeamsAlertChannel("microsoft-teams-alert-from-golang", + api.MicrosoftTeamsChannelData{ + WebhookURL: "https://outlook.office.com/webhook/api-token", + }, + ) + + response, err := lacework.Integrations.CreateMicrosoftTeamsAlertChannel(myTeamsChannel) + if err != nil { + log.Fatal(err) + } + + // Output: Microsoft Teams alert channel created: THE-INTEGRATION-GUID + fmt.Printf("Microsoft Teams alert channel created: %s", response.Data[0].IntgGuid) +} diff --git a/api/integration_alert_channels_microsoft_teams.go b/api/integration_alert_channels_microsoft_teams.go new file mode 100644 index 000000000..3b91d7a62 --- /dev/null +++ b/api/integration_alert_channels_microsoft_teams.go @@ -0,0 +1,96 @@ +// +// 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 + +// NewMicrosoftTeamsAlertChannel returns an instance of MicrosoftTeamsAlertChannel +// with the provided name and data. +// +// Basic usage: Initialize a new MicrosoftTeamsAlertChannel struct, then +// use the new instance to do CRUD operations +// +// client, err := api.NewClient("account") +// if err != nil { +// return err +// } +// +// microsoftTeamsChannel := api.NewMicrosoftTeamsAlertChannel("foo", +// api.MicrosoftTeamsChannelData{ +// WebhookURL: "https://outlook.office.com/webhook/api-token", +// }, +// ) +// +// client.Integrations.CreateMicrosoftTeamsAlertChannel(microsoftTeamsChannel) +// +func NewMicrosoftTeamsAlertChannel(name string, data MicrosoftTeamsChannelData) MicrosoftTeamsAlertChannel { + return MicrosoftTeamsAlertChannel{ + commonIntegrationData: commonIntegrationData{ + Name: name, + Type: MicrosoftTeamsChannelIntegration.String(), + Enabled: 1, + }, + Data: data, + } +} + +// CreateMicrosoftTeamsAlertChannel creates a msTeams alert channel integration on the Lacework Server +func (svc *IntegrationsService) CreateMicrosoftTeamsAlertChannel(integration MicrosoftTeamsAlertChannel) ( + response MicrosoftTeamsAlertChannelResponse, + err error, +) { + err = svc.create(integration, &response) + return +} + +// GetMicrosoftTeamsAlertChannel gets a msTeams alert channel integration that matches with +// the provided integration guid on the Lacework Server +func (svc *IntegrationsService) GetMicrosoftTeamsAlertChannel(guid string) (response MicrosoftTeamsAlertChannelResponse, + err error) { + err = svc.get(guid, &response) + return +} + +// UpdateMicrosoftTeamsAlertChannel updates a single msTeams alert channel integration +func (svc *IntegrationsService) UpdateMicrosoftTeamsAlertChannel(data MicrosoftTeamsAlertChannel) ( + response MicrosoftTeamsAlertChannelResponse, + err error, +) { + err = svc.update(data.IntgGuid, data, &response) + return +} + +// ListMicrosoftTeamsAlertChannel lists the Microsoft Teams external integrations available on the Lacework Server +func (svc *IntegrationsService) ListMicrosoftTeamsAlertChannel() (response MicrosoftTeamsAlertChannelResponse, err error) { + err = svc.listByType(MicrosoftTeamsChannelIntegration, &response) + return +} + +type MicrosoftTeamsAlertChannelResponse struct { + Data []MicrosoftTeamsAlertChannel `json:"data"` + Ok bool `json:"ok"` + Message string `json:"message"` +} + +type MicrosoftTeamsAlertChannel struct { + commonIntegrationData + Data MicrosoftTeamsChannelData `json:"DATA"` +} + +type MicrosoftTeamsChannelData struct { + WebhookURL string `json:"TEAMS_URL" mapstructure:"TEAMS_URL"` +} diff --git a/api/integration_alert_channels_microsoft_teams_test.go b/api/integration_alert_channels_microsoft_teams_test.go new file mode 100644 index 000000000..cb906a10f --- /dev/null +++ b/api/integration_alert_channels_microsoft_teams_test.go @@ -0,0 +1,242 @@ +// +// 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 TestIntegrationsNewMicrosoftTeamsAlertChannel(t *testing.T) { + subject := api.NewMicrosoftTeamsAlertChannel("integration_name", + api.MicrosoftTeamsChannelData{ + WebhookURL: "https://outlook.office.com/webhook/api-token", + }, + ) + assert.Equal(t, api.MicrosoftTeamsChannelIntegration.String(), subject.Type) +} + +func TestIntegrationsCreateMicrosoftTeamsAlertChannel(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, "CreateMicrosoftTeamsAlertChannel 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, "MICROSOFT_TEAMS", "wrong integration type") + assert.Contains(t, body, "https://outlook.office.com/webhook/api-token", "wrong microsoft Teams url") + assert.Contains(t, body, "ENABLED\":1", "integration is not enabled") + } + + fmt.Fprintf(w, microsoftTeamsChannelIntegrationJsonResponse(intgGUID)) + }) + defer fakeServer.Close() + + c, err := api.NewClient("test", + api.WithToken("TOKEN"), + api.WithURL(fakeServer.URL()), + ) + assert.Nil(t, err) + + data := api.NewMicrosoftTeamsAlertChannel("integration_name", + api.MicrosoftTeamsChannelData{ + WebhookURL: "https://outlook.office.com/webhook/api-token", + }, + ) + assert.Equal(t, "integration_name", data.Name, "MicrosoftTeams integration name mismatch") + assert.Equal(t, "MICROSOFT_TEAMS", data.Type, "a new Microsoft Teams integration should match its type") + assert.Equal(t, 1, data.Enabled, "a new MicrosoftTeams integration should be enabled") + + response, err := c.Integrations.CreateMicrosoftTeamsAlertChannel(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, "https://outlook.office.com/webhook/api-token", resData.Data.WebhookURL) + } +} + +func TestIntegrationsGetMicrosoftTeamsAlertChannel(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, "GetMicrosoftTeamsAlertChannel should be a GET method") + fmt.Fprintf(w, microsoftTeamsChannelIntegrationJsonResponse(intgGUID)) + }) + defer fakeServer.Close() + + c, err := api.NewClient("test", + api.WithToken("TOKEN"), + api.WithURL(fakeServer.URL()), + ) + assert.Nil(t, err) + + response, err := c.Integrations.GetMicrosoftTeamsAlertChannel(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, "https://outlook.office.com/webhook/api-token", resData.Data.WebhookURL) + } +} + +func TestIntegrationsUpdateMicrosoftTeamsAlertChannel(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, "UpdateMicrosoftTeamsAlertChannel 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, "MICROSOFT_TEAMS", "wrong integration type") + assert.Contains(t, body, "https://outlook.office.com/webhook/api-token", "wrong microsoftTeams url") + assert.Contains(t, body, "ENABLED\":1", "integration is not enabled") + } + + fmt.Fprintf(w, microsoftTeamsChannelIntegrationJsonResponse(intgGUID)) + }) + defer fakeServer.Close() + + c, err := api.NewClient("test", + api.WithToken("TOKEN"), + api.WithURL(fakeServer.URL()), + ) + assert.Nil(t, err) + + data := api.NewMicrosoftTeamsAlertChannel("integration_name", + api.MicrosoftTeamsChannelData{ + WebhookURL: "https://outlook.office.com/webhook/api-token", + }, + ) + assert.Equal(t, "integration_name", data.Name, "MicrosoftTeams integration name mismatch") + assert.Equal(t, "MICROSOFT_TEAMS", data.Type, "a new MicrosoftTeams integration should match its type") + assert.Equal(t, 1, data.Enabled, "a new MicrosoftTeams integration should be enabled") + data.IntgGuid = intgGUID + + response, err := c.Integrations.UpdateMicrosoftTeamsAlertChannel(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 TestIntegrationsListMicrosoftTeamsAlertChannel(t *testing.T) { + var ( + intgGUIDs = []string{intgguid.New(), intgguid.New(), intgguid.New()} + fakeServer = lacework.MockServer() + ) + fakeServer.MockAPI("external/integrations/type/MICROSOFT_TEAMS", + func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "GET", r.Method, "ListMicrosoftTeamsAlertChannel should be a GET method") + fmt.Fprintf(w, microsoftTeamsChanMultiIntegrationJsonResponse(intgGUIDs)) + }, + ) + defer fakeServer.Close() + + c, err := api.NewClient("test", + api.WithToken("TOKEN"), + api.WithURL(fakeServer.URL()), + ) + assert.Nil(t, err) + + response, err := c.Integrations.ListMicrosoftTeamsAlertChannel() + 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 microsoftTeamsChannelIntegrationJsonResponse(intgGUID string) string { + return ` +{ + "data": [` + singleMicrosoftTeamsChanIntegration(intgGUID) + `], + "ok": true, + "message": "SUCCESS" +} +` +} + +func microsoftTeamsChanMultiIntegrationJsonResponse(guids []string) string { + integrations := []string{} + for _, guid := range guids { + integrations = append(integrations, singleMicrosoftTeamsChanIntegration(guid)) + } + return ` +{ +"data": [` + strings.Join(integrations, ", ") + `], +"ok": true, +"message": "SUCCESS" +} +` +} + +func singleMicrosoftTeamsChanIntegration(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": { + "TEAMS_URL": "https://outlook.office.com/webhook/api-token" + }, + "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": "MICROSOFT_TEAMS", + "TYPE_NAME": "MICROSOFT_TEAMS" +} +` +} diff --git a/api/integrations.go b/api/integrations.go index bdeefb4d9..02c323990 100644 --- a/api/integrations.go +++ b/api/integrations.go @@ -64,6 +64,9 @@ const ( // Container registry integration type ContainerRegistryIntegration + // Microsoft Teams channel integration type + MicrosoftTeamsChannelIntegration + // Slack channel integration type SlackChannelIntegration @@ -88,24 +91,25 @@ const ( // IntegrationTypes is the list of available integration types var IntegrationTypes = map[integrationType]string{ - NoneIntegration: "NONE", - AwsCfgIntegration: "AWS_CFG", - AwsCloudTrailIntegration: "AWS_CT_SQS", - AwsS3ChannelIntegration: "AWS_S3", - DatadogChannelIntegration: "DATADOG", - GcpCfgIntegration: "GCP_CFG", - GcpAuditLogIntegration: "GCP_AT_SES", - GcpPubSubChannelIntegration: "GCP_PUBSUB", - AzureCfgIntegration: "AZURE_CFG", - AzureActivityLogIntegration: "AZURE_AL_SEQ", - ContainerRegistryIntegration: "CONT_VULN_CFG", - SlackChannelIntegration: "SLACK_CHANNEL", - SplunkIntegration: "SPLUNK_HEC", - ServiceNowChannelIntegration: "SERVICE_NOW_REST", - AwsCloudWatchIntegration: "CLOUDWATCH_EB", - PagerDutyIntegration: "PAGER_DUTY_API", - JiraIntegration: "JIRA", - WebhookIntegration: "WEBHOOK", + NoneIntegration: "NONE", + AwsCfgIntegration: "AWS_CFG", + AwsCloudTrailIntegration: "AWS_CT_SQS", + AwsS3ChannelIntegration: "AWS_S3", + DatadogChannelIntegration: "DATADOG", + GcpCfgIntegration: "GCP_CFG", + GcpAuditLogIntegration: "GCP_AT_SES", + GcpPubSubChannelIntegration: "GCP_PUBSUB", + AzureCfgIntegration: "AZURE_CFG", + AzureActivityLogIntegration: "AZURE_AL_SEQ", + ContainerRegistryIntegration: "CONT_VULN_CFG", + MicrosoftTeamsChannelIntegration: "MICROSOFT_TEAMS", + SlackChannelIntegration: "SLACK_CHANNEL", + SplunkIntegration: "SPLUNK_HEC", + ServiceNowChannelIntegration: "SERVICE_NOW_REST", + AwsCloudWatchIntegration: "CLOUDWATCH_EB", + PagerDutyIntegration: "PAGER_DUTY_API", + JiraIntegration: "JIRA", + WebhookIntegration: "WEBHOOK", } // String returns the string representation of an integration type diff --git a/cli/cmd/integration.go b/cli/cmd/integration.go index cde65cce0..9a0c43ba7 100644 --- a/cli/cmd/integration.go +++ b/cli/cmd/integration.go @@ -203,6 +203,7 @@ func promptCreateIntegration() error { "AWS S3 Alert Channel", "Datadog Alert Channel", "GCP PubSub Alert Channel", + "Microsoft Teams Alert Channel", "Webhook Alert Channel", "Splunk Alert Channel", "Service Now Alert Channel", @@ -234,6 +235,8 @@ func promptCreateIntegration() error { return createSlackAlertChannelIntegration() case "GCP PubSub Alert Channel": return createGcpPubSubChannelIntegration() + case "Microsoft Teams Alert Channel": + return createMicrosoftTeamsChannelIntegration() case "AWS S3 Alert Channel": return createAwsS3ChannelIntegration() case "Datadog Alert Channel": @@ -568,6 +571,24 @@ func reflectIntegrationData(raw api.RawIntegration) [][]string { return out + case api.MicrosoftTeamsChannelIntegration.String(): + + var iData api.MicrosoftTeamsChannelData + 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{"WEBHOOK URL", iData.WebhookURL}, + } + + return out + case api.AwsCloudWatchIntegration.String(): var iData api.AwsCloudWatchData diff --git a/cli/cmd/integration_microsoft_teams.go b/cli/cmd/integration_microsoft_teams.go new file mode 100644 index 000000000..84d0450f7 --- /dev/null +++ b/cli/cmd/integration_microsoft_teams.go @@ -0,0 +1,63 @@ +// +// 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 createMicrosoftTeamsChannelIntegration() error { + questions := []*survey.Question{ + { + Name: "name", + Prompt: &survey.Input{Message: "Name: "}, + Validate: survey.Required, + }, + { + Name: "webhook_url", + Prompt: &survey.Input{Message: "Webhook URL: "}, + Validate: survey.Required, + }, + } + + answers := struct { + Name string + WebhookUrl string `survey:"webhook_url"` + }{} + + err := survey.Ask(questions, &answers, + survey.WithIcons(promptIconsFunc), + ) + if err != nil { + return err + } + + teams := api.NewMicrosoftTeamsAlertChannel(answers.Name, + api.MicrosoftTeamsChannelData{ + WebhookURL: answers.WebhookUrl, + }, + ) + + cli.StartProgress(" Creating integration...") + _, err = cli.LwApi.Integrations.CreateMicrosoftTeamsAlertChannel(teams) + cli.StopProgress() + return err +}