Skip to content

Commit

Permalink
feat(actions): Add Pagerduty action. (#468)
Browse files Browse the repository at this point in the history
Signed-off-by: Simar <simar@linux.com>

Signed-off-by: Simar <simar@linux.com>
  • Loading branch information
simar7 committed Sep 26, 2022
1 parent b32011f commit 23cdf37
Show file tree
Hide file tree
Showing 10 changed files with 305 additions and 50 deletions.
78 changes: 78 additions & 0 deletions actions/pagerduty.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package actions

import (
"context"
"fmt"
"log"
"time"

"github.com/PagerDuty/go-pagerduty"
"github.com/aquasecurity/postee/v2/formatting"
"github.com/aquasecurity/postee/v2/layout"
)

type Clock interface {
Now() time.Time
}

type realClock struct{}

func (rc *realClock) Now() time.Time {
return time.Now()
}

type PagerdutyClient struct {
client *pagerduty.Client
clock Clock

Name string
AuthToken string
RoutingKey string
}

func (p *PagerdutyClient) GetName() string {
return p.Name
}

func (p *PagerdutyClient) Init() error {
if len(p.AuthToken) <= 0 {
return fmt.Errorf("pagerduty auth token is required to send events")
}
if len(p.RoutingKey) <= 0 {
return fmt.Errorf("pagerduty routing key is required to send events")
}

p.client = pagerduty.NewClient(p.AuthToken)
p.clock = &realClock{}
return nil
}

func (p *PagerdutyClient) Send(m map[string]string) error {
ctx := context.Background()
resp, err := p.client.ManageEventWithContext(ctx, &pagerduty.V2Event{
RoutingKey: p.RoutingKey,
Action: "trigger",
Payload: &pagerduty.V2Payload{
Summary: m["title"], // required
Source: "postee",
Severity: "critical",
Timestamp: p.clock.Now().Format(time.RFC3339),
Details: m["description"], // required
},
})
if err != nil {
return fmt.Errorf("failed to send event to pagerduty: %w", err)
}

log.Printf("successfully sent event to pagerduty, response msg: %s, status: %s", resp.Message, resp.Status)
return nil
}

func (p *PagerdutyClient) Terminate() error {
return nil
}

func (p *PagerdutyClient) GetLayoutProvider() layout.LayoutProvider {
/*TODO come up with smaller interface that doesn't include GetLayoutProvider()*/
return new(formatting.HtmlProvider)
}
108 changes: 108 additions & 0 deletions actions/pagerduty_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package actions

import (
"fmt"
"io"
"net/http"
"net/http/httptest"
"testing"
"time"

"github.com/stretchr/testify/require"

"github.com/PagerDuty/go-pagerduty"
"github.com/stretchr/testify/assert"
)

type fakeClock struct{}

func (fc *fakeClock) Now() time.Time {
t, _ := time.Parse(time.RFC3339, "2022-09-22T22:07:55-07:00")
return t
}

func TestPagerdutyClient_Init(t *testing.T) {
t.Run("happy path", func(t *testing.T) {
require.NoError(t, (&PagerdutyClient{
Name: "my-pagerduty",
AuthToken: "123456",
RoutingKey: "foobarbaz",
}).Init())
})

t.Run("sad path, no auth token", func(t *testing.T) {
assert.Equal(t, "pagerduty auth token is required to send events", (&PagerdutyClient{
Name: "my-pagerduty",
RoutingKey: "foobarbaz",
}).Init().Error())
})

t.Run("sad path, no routing key", func(t *testing.T) {
assert.Equal(t, "pagerduty routing key is required to send events", (&PagerdutyClient{
Name: "my-pagerduty",
AuthToken: "123456",
}).Init().Error())
})
}

func TestPagerdutyClient_Send(t *testing.T) {
testCases := []struct {
name string
handlerFunc http.HandlerFunc
expectedError string
pagerdutyClient PagerdutyClient
inputEvent map[string]string
}{
{
name: "happy path",
handlerFunc: func(writer http.ResponseWriter, request *http.Request) {
b, _ := io.ReadAll(request.Body)
assert.JSONEq(t, `{"routing_key":"123456","event_action":"trigger","payload":{"summary":"my fancy title","source":"postee","severity":"critical","timestamp":"2022-09-22T22:07:55-07:00","custom_details":"foo bar baz details"}}`, string(b))
_, _ = fmt.Fprint(writer, `{"status": "ok", "dedup_key": "yes", "message": "ok"}`)
},
pagerdutyClient: PagerdutyClient{
Name: "my-pagerduty",
AuthToken: "foo-bar-baz",
RoutingKey: "123456",
},
inputEvent: map[string]string{
"description": "foo bar baz details",
"title": "my fancy title",
},
},
{
name: "sad path, pagerduty api returns an error",
handlerFunc: func(writer http.ResponseWriter, request *http.Request) {
writer.WriteHeader(http.StatusInternalServerError)
},
pagerdutyClient: PagerdutyClient{
Name: "my-pagerduty",
AuthToken: "foo-bar-baz",
RoutingKey: "123456",
},
inputEvent: map[string]string{
"description": "foo bar baz details",
"title": "my fancy title",
},
expectedError: "failed to send event to pagerduty: HTTP response with status code 500 does not contain Content-Type: application/json",
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
ts := httptest.NewServer(tc.handlerFunc)
defer ts.Close()

tc.pagerdutyClient.client = pagerduty.NewClient(tc.pagerdutyClient.AuthToken, pagerduty.WithV2EventsAPIEndpoint(ts.URL))
tc.pagerdutyClient.clock = &fakeClock{}

err := tc.pagerdutyClient.Send(tc.inputEvent)
switch {
case tc.expectedError != "":
assert.Equal(t, tc.expectedError, err.Error(), tc.name)
default:
assert.NoError(t, err, tc.name)
}
})
}
}
Binary file added docs/blueprints/assets/trivy-pagerduty.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
36 changes: 36 additions & 0 deletions docs/blueprints/devops-pagerduty.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Paging DevOps Teams

## Introduction
In this walkthrough, we will setup vulnerability scanning with [Trivy](https://github.com/aquasecurity/trivy) and send the results to Postee for paging DevOps team members for critical vulnerabilities as they are introduced.

## Scenario
A DevOps team would like to configure alerts for scheduled vulnerability scans to notify them about any vulnerable images that they might be running in their clusters. For this they decide to install Trivy, run it on a schedule and send the results to Postee.

They decide to configure Postee so that upon receiving such alerts, Postee sends an event to PagerDuty which fires off an alert to inform DevOps teams to take necessary action.

![img.png](assets/trivy-pagerduty.png)

## Sample Configs
In this case a sample configuration for the components can be described as follows:

### Postee Config
```yaml
routes:
- name: Trivy Alerts to Pagerduty
input: input.report.summary.criticalCount > 0
actions: [alert-devops]
template: trivy-raw-json

# Templates are used to format a message
templates:
- name: trivy-raw-json
rego-package: postee.rawmessage.json

# Actions are target services that should consume the messages
actions:
- name: alert-devops
type: pagerduty
enable: true
pagerduty-auth-token: "<auth token>"
pagerduty-routing-key: "<routing key>"
```
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/aquasecurity/postee/v2
go 1.18

require (
github.com/PagerDuty/go-pagerduty v1.5.1
github.com/aquasecurity/go-jira v0.0.0-20211103111421-b62ce48827be
github.com/aws/aws-sdk-go-v2 v1.16.11
github.com/aws/aws-sdk-go-v2/config v1.17.1
Expand Down
14 changes: 14 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpz
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8=
github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
github.com/PagerDuty/go-pagerduty v1.5.1 h1:zpMQ8WwWlUahipB2q+ERVIA9D0/ti8kvsQUSagCK86g=
github.com/PagerDuty/go-pagerduty v1.5.1/go.mod h1:txr8VbObXdk2RkqF+C2an4qWssdGY99fK26XYUDjh+4=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8=
Expand All @@ -61,6 +63,7 @@ github.com/aquasecurity/go-jira v0.0.0-20211103111421-b62ce48827be h1:xUasZnauNA
github.com/aquasecurity/go-jira v0.0.0-20211103111421-b62ce48827be/go.mod h1:IHtKzIAdk0t3Xse7rJSY7pJlA8gB7lqY2b4l5WYZYsk=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/aws/aws-sdk-go-v2 v1.16.11 h1:xM1ZPSvty3xVmdxiGr7ay/wlqv+MWhH0rMlyLdbC0YQ=
github.com/aws/aws-sdk-go-v2 v1.16.11/go.mod h1:WTACcleLz6VZTp7fak4EO5b9Q4foxbn+8PIz3PmyKlo=
Expand All @@ -87,6 +90,7 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.16.13/go.mod h1:Ru3QVMLygVs/07UQ3YDu
github.com/aws/smithy-go v1.12.1 h1:yQRC55aXN/y1W10HgwHle01DRuV9Dpf31iGkotjt3Ag=
github.com/aws/smithy-go v1.12.1/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bytecodealliance/wasmtime-go v0.36.0 h1:B6thr7RMM9xQmouBtUqm1RpkJjuLS37m6nxX+iwsQSc=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
Expand Down Expand Up @@ -128,6 +132,7 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84=
github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
Expand Down Expand Up @@ -212,6 +217,7 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
Expand Down Expand Up @@ -245,9 +251,11 @@ github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-retryablehttp v0.5.1/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
github.com/hashicorp/go-retryablehttp v0.5.3 h1:QlWt0KvWT0lq8MFppF9tsJGF+ynG7ztc2KIPhzRGk7s=
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
Expand Down Expand Up @@ -282,10 +290,14 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=
github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg=
github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g=
github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 h1:dcztxKSvZ4Id8iPpHERQBbIJfabdt4wUm5qy3wOL2Zc=
Expand Down Expand Up @@ -337,6 +349,7 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_golang v1.13.0 h1:b71QUfeo5M8gq2+evJdTPfZhYMAU0uKPkyPJ7TPsloU=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
Expand Down Expand Up @@ -511,6 +524,7 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
Expand Down
6 changes: 4 additions & 2 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ nav:
- Trivy Scan: blueprints/trivy-vulnerability-scan.md
- Trivy AWS Security Hub: blueprints/trivy-aws-security-hub.md
- Trivy Operator: blueprints/trivy-operator.md
- External Healthcheck: blueprints/external-healthcheck.md
- Image Processing: blueprints/image-processing.md
- Pagerduty: blueprints/devops-pagerduty.md
- Others:
- External Healthcheck: blueprints/external-healthcheck.md
- Image Processing: blueprints/image-processing.md
- Installation: install.md
- Configuration:
- Config File: config.md
Expand Down
8 changes: 8 additions & 0 deletions router/builders.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,3 +230,11 @@ func buildDockerAction(sourceSettings *ActionSettings) (*actions.DockerClient, e
func buildAWSSecurityHubAction(sourceSettings *ActionSettings) (*actions.AWSSecurityHubClient, error) {
return &actions.AWSSecurityHubClient{Name: sourceSettings.Name}, nil
}

func buildPagerdutyAction(sourceSettings *ActionSettings) (*actions.PagerdutyClient, error) {
return &actions.PagerdutyClient{
Name: sourceSettings.Name,
AuthToken: sourceSettings.PagerdutyAuthToken,
RoutingKey: sourceSettings.PagerdutyRoutingKey,
}, nil
}
Loading

0 comments on commit 23cdf37

Please sign in to comment.