Skip to content

Commit

Permalink
Merge pull request #20 from Issif/newclient_methods
Browse files Browse the repository at this point in the history
#### Enhancement
-  **all outputs use new generic methods (`NewClient()` + `Post()`), new output integration will be easier**
- 💥 some variables have been renamed to be relevant with their real names in API docs of Outputs
    - `DATADOG_TOKEN` **->** `DATADOG_API_KEY`
    - `SLACK_TOKEN` **->** `SLACK_WEBHOOK_URL`
#### Fix
- `/test` sends an event with a timestamp set at *now*
  • Loading branch information
Issif authored May 10, 2019
2 parents 8c5bb86 + 03bdd4e commit e265222
Show file tree
Hide file tree
Showing 8 changed files with 145 additions and 107 deletions.
11 changes: 9 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
# Changelog

## 1.0.7 - 2019-05-09
## 1.10.0 - 2019-05-10
#### Enhancement
- **all outputs use new generic methods (`NewClient()` + `Post()`), new output integration will be easier**
- :boom: some variables have been renamed to be relevant with their real names in API docs of Outputs
- `DATADOG_TOKEN` **->** `DATADOG_API_KEY`
- `SLACK_TOKEN` **->** `SLACK_WEBHOOK_URL`
#### Fix
- `/test` sends an event with a timestamp set at *now*

## 1.0.7 - 2019-05-09
#### Enhancement
- Change `SLACK_HIDE_FIELDS` for `SLACK_OUTPUT_FORMAT`, you can now choose how events are displayed in Slack

Expand Down Expand Up @@ -47,7 +55,6 @@
- add output status in log to get those which are enabled
- check of `LISTEN_PORT` in `init()` : port must be an integer between 1 and 65535
- long string in slack field values are not splitten anymore

#### Fix
- some log level tags were missing
- fix cert errors in alpine ([PR#1](https://github.com/Issif/falcosidekick/pull/1) thanks to [@palmerabollo](https://github.com/palmerabollo))
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Run the daemon as any other daemon in your architecture (systemd, k8s daemonset,

## With docker
```
docker run -d -p 2801:2801 -e SLACK_TOKEN=XXXX -e DATADOG_TOKEN=XXXX issif/falcosidekick
docker run -d -p 2801:2801 -e SLACK_WEBHOOK_URL=XXXX -e DATADOG_API_KEY=XXXX issif/falcosidekick
```

## Falco's config
Expand All @@ -38,11 +38,11 @@ program_output:
Configuration of the daemon is made by Env vars :

* **LISTEN_PORT** : port to listen for daemon (default: 2801)
* **SLACK_TOKEN** : Slack URL + token (ex: https://hooks.slack.com/services/XXXX/YYYY/ZZZZ), if not `empty`, Slack output is *enabled*
* **SLACK_WEBHOOK_URL** : Slack WebhookURL (ex: https://hooks.slack.com/services/XXXX/YYYY/ZZZZ), if not `empty`, Slack output is *enabled*
* **SLACK_FOOTER** : Slack footer
* **SLACK_ICON** : Slack icon (avatar)
* **SLACK_OUTPUT_FORMAT** : `all` (default), `text` (only text is displayed in Slack), `fields` (only fields are displayed in Slack)
* **DATADOG_TOKEN** : Datadog token, if not `empty`, Datadog output is *enabled*
* **DATADOG_API_KEY** : Datadog API Key, if not `empty`, Datadog output is *enabled*
* **ALERTMANAGER_HOST_PORT** : AlertManager host:port, if not `empty`, AlertManager is *enabled*
* **DEBUG** : if *true* all outputs will print in stdout the payload they send

Expand All @@ -59,8 +59,8 @@ Different URI (handlers) are available :
All logs are sent to `stdout`.

```
2018/10/11 08:53:25 [INFO] : Outputs configuration : Slack=enabled, Datadog=disabled, Alertmanager=disabled
2018/10/11 08:53:25 [INFO] : Falco Sidekick is up and listening on port 2801
2019/05/10 14:32:06 [INFO] : Enable Outputs : Slack Datadog
2019/05/10 14:32:06 [INFO] : Disable Outputs : AlertManager
```

# Examples
Expand Down
20 changes: 9 additions & 11 deletions handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import (
"net/http"
"os"
"strconv"
"time"

"github.com/Issif/falcosidekick/outputs"
"github.com/Issif/falcosidekick/types"
)

// mainHandler is Falco Sidekick' main handler (default).
// mainHandler is Falco Sidekick main handler (default).
func mainHandler(w http.ResponseWriter, r *http.Request) {

var falcopayload types.FalcoPayload
Expand All @@ -33,14 +33,14 @@ func mainHandler(w http.ResponseWriter, r *http.Request) {
log.Printf("[DEBUG] : Falco's payload : %v", string(body))
}

if os.Getenv("SLACK_TOKEN") != "" {
go outputs.SlackPost(falcopayload)
if os.Getenv("SLACK_WEBHOOK_URL") != "" {
go slackClient.SlackPost(falcopayload)
}
if os.Getenv("DATADOG_TOKEN") != "" {
go outputs.DatadogPost(falcopayload)
if os.Getenv("DATADOG_API_KEY") != "" {
go datadogClient.DatadogPost(falcopayload)
}
if os.Getenv("ALERTMANAGER_HOST_PORT") != "" {
go outputs.AlertmanagerPost(falcopayload)
go alertmanagerClient.AlertmanagerPost(falcopayload)
}
}

Expand All @@ -51,7 +51,7 @@ func pingHandler(w http.ResponseWriter, r *http.Request) {

// testHandler sends a test event to all enabled outputs.
func testHandler(w http.ResponseWriter, r *http.Request) {
testEvent := `{"output":"This is a test from Falco Sidekick","priority":"Debug","rule":"Test rule", "output_fields": {"proc.name":"falcosidekick","user.name":"falcosidekick"}}`
testEvent := `{"output":"This is a test from falcosidekick","priority":"Debug","rule":"Test rule", "time":"`+time.Now().UTC().Format(time.RFC3339)+`","output_fields": {"proc.name":"falcosidekick","user.name":"falcosidekick"}}`

port = "2801"
if lport, err := strconv.Atoi(os.Getenv("LISTEN_PORT")); err == nil {
Expand All @@ -67,9 +67,7 @@ func testHandler(w http.ResponseWriter, r *http.Request) {
defer resp.Body.Close()

log.Printf("[DEBUG] : Test sent\n")
if resp.StatusCode == http.StatusOK {
log.Printf("[DEBUG] : Test OK (%v)\n", resp.Status)
} else {
if resp.StatusCode != http.StatusOK {
log.Printf("[DEBUG] : Test KO (%v)\n", resp.Status)
}
}
40 changes: 30 additions & 10 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ import (
"net/http"
"os"
"strconv"

"github.com/Issif/falcosidekick/outputs"
)

// Env variables
// Globale variables
var port string
var slackClient, datadogClient, alertmanagerClient *outputs.Client

func init() {
port = "2801"
Expand All @@ -21,20 +24,38 @@ func init() {
}
enableOutputsText := "[INFO] : Enable Outputs : "
disableOutputsText := "[INFO] : Disable Outputs : "
if os.Getenv("SLACK_TOKEN") != "" {
enableOutputsText += "Slack, "
if os.Getenv("SLACK_WEBHOOK_URL") != "" {
var err error
slackClient, err = outputs.NewClient("Slack", os.Getenv("SLACK_WEBHOOK_URL"))
if err != nil {
disableOutputsText += "Slack "
} else {
enableOutputsText += "Slack "
}
} else {
disableOutputsText += "Slack, "
disableOutputsText += "Slack "
}
if os.Getenv("DATADOG_TOKEN") != "" {
enableOutputsText += "Datadog, "
if os.Getenv("DATADOG_API_KEY") != "" {
var err error
datadogClient, err = outputs.NewClient("Datadog", outputs.DatadogURL+"?api_key="+os.Getenv("DATADOG_API_KEY"))
if err != nil {
disableOutputsText += "Datadog "
} else {
enableOutputsText += "Datadog "
}
} else {
disableOutputsText += "Datadog, "
disableOutputsText += "Datadog "
}
if os.Getenv("ALERTMANAGER_HOST_PORT") != "" {
enableOutputsText += "AlertManager"
var err error
alertmanagerClient, err = outputs.NewClient("Alertmanager", os.Getenv("ALERTMANAGER_HOST_PORT")+outputs.AlertmanagerURI)
if err != nil {
disableOutputsText += "Alertmanager "
} else {
enableOutputsText += "Alertmanager "
}
} else {
disableOutputsText += "AlertManager"
disableOutputsText += "Alertmanager "
}

log.Printf("%v\n", enableOutputsText)
Expand All @@ -49,6 +70,5 @@ func main() {
log.Printf("[INFO] : Falco Sidekick is up and listening on port %v\n", port)
if err := http.ListenAndServe(":"+port, nil); err != nil {
log.Fatalf("[ERROR] : %v\n", err.Error())
} else {
}
}
28 changes: 4 additions & 24 deletions outputs/alertmanager.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
package outputs

import (
"bytes"
"encoding/json"
"log"
"net/http"
"os"
"strings"

"github.com/Issif/falcosidekick/types"
)

const (
alertmanagerURL string = "/api/v1/alerts"
AlertmanagerURI string = "/api/v1/alerts"
)

type alertmanagerIncident struct {
Expand Down Expand Up @@ -45,22 +40,7 @@ func newAlertmanagerPayload(falcopayload types.FalcoPayload) []alertmanagerIncid
return a
}

// AlertmanagerPost posts event to Alert Manager
func AlertmanagerPost(falcopayload types.FalcoPayload) {
alertmanagerPayload := newAlertmanagerPayload(falcopayload)
b := new(bytes.Buffer)
json.NewEncoder(b).Encode(alertmanagerPayload)

if os.Getenv("DEBUG") == "true" {
log.Printf("[DEBUG] : AlertManager's payload : %v\n", b)
}

resp, err := http.Post(os.Getenv("ALERTMANAGER_HOST_PORT")+alertmanagerURL, "application/json; charset=utf-8", b)
if err != nil {
log.Printf("[ERROR] : AlertManager - %v\n", err.Error())
} else if resp.StatusCode != 200 {
log.Printf("[ERROR] : AlertManager - %v\n", resp)
} else {
log.Printf("[INFO] : AlertManager - Post sent successfully\n")
}
// AlertmanagerPost posts event to AlertManager
func (c *Client) AlertmanagerPost(falcopayload types.FalcoPayload) {
c.Post(newAlertmanagerPayload(falcopayload))
}
72 changes: 72 additions & 0 deletions outputs/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package outputs

import (
"bytes"
"encoding/json"
"errors"
"log"
"net/http"
"net/url"
"os"
)

// Different Error Types
var ErrHeaderMissing = errors.New("Header missing")
var ErrNotFound = errors.New("Resource not found")
var ErrClientAuthenticationError = errors.New("Authentication Error")
var ErrForbidden = errors.New("Authentication Error")
var ErrUnprocessableEntityError = errors.New("Bad Request")
var ErrTooManyRequest = errors.New("Exceeding post rate limit")
var ErrClientCreation = errors.New("Client creation Error")

// Client communicates with the different API.
type Client struct {
OutputType string
EndpointURL *url.URL
}

// NewClient returns a new output.Client for accessing the different API.
func NewClient(outpuType string, defaultEndpointURL string) (*Client, error) {
c := &Client{OutputType: outpuType}
endpointURL, err := url.Parse(defaultEndpointURL)
if err != nil {
return nil, ErrClientCreation
}
c.EndpointURL = endpointURL
return c, nil
}

// Post sends event (payload) to Output.
func (c *Client) Post(payload interface{}) {
body := new(bytes.Buffer)
json.NewEncoder(body).Encode(payload)

if os.Getenv("DEBUG") == "true" {
log.Printf("[DEBUG] : %v payload : %v\n", c.OutputType, body)
}

resp, err := http.Post(c.EndpointURL.String(), "application/json; charset=utf-8", body)
if err != nil {
log.Printf("[ERROR] : %v - %v\n", c.OutputType, err.Error())
}
defer resp.Body.Close()

switch resp.StatusCode {
case http.StatusOK, http.StatusCreated, http.StatusAccepted, http.StatusNoContent: //200, 201, 202, 204
log.Printf("[INFO] : %v - Post OK (%v)\n", c.OutputType, resp.StatusCode)
case http.StatusBadRequest: //400
log.Printf("[ERROR] : %v - %v\n", c.OutputType, ErrHeaderMissing)
case http.StatusUnauthorized: //401
log.Printf("[ERROR] : %v - %v\n", c.OutputType, ErrClientAuthenticationError)
case http.StatusForbidden: //403
log.Printf("[ERROR] : %v - %v\n", c.OutputType, ErrForbidden)
case http.StatusNotFound: //404
log.Printf("[ERROR] : %v - %v\n", c.OutputType, ErrNotFound)
case http.StatusUnprocessableEntity: //422
log.Printf("[ERROR] : %v - %v\n", c.OutputType, ErrUnprocessableEntityError)
case http.StatusTooManyRequests: //429
log.Printf("[ERROR] : %v - %v\n", c.OutputType, ErrTooManyRequest)
default:
log.Printf("[ERROR] : %v - Unknown Response: %v\n", c.OutputType, ErrHeaderMissing)
}
}
42 changes: 11 additions & 31 deletions outputs/datadog.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
package outputs

import (
"bytes"
"encoding/json"
"log"
"net/http"
"os"

"github.com/Issif/falcosidekick/types"
)

const (
datadogURL string = "https://api.datadoghq.com/api/v1/events"
DatadogURL string = "https://api.datadoghq.com/api/v1/events"
)

type datadogPayload struct {
Expand All @@ -23,7 +17,7 @@ type datadogPayload struct {
}

func newDatadogPayload(falcopayload types.FalcoPayload) datadogPayload {
var ddpayload datadogPayload
var d datadogPayload
var tags []string

for i, j := range falcopayload.OutputFields {
Expand All @@ -32,11 +26,11 @@ func newDatadogPayload(falcopayload types.FalcoPayload) datadogPayload {
tags = append(tags, i+":"+j.(string))
}
}
ddpayload.Tags = tags
d.Tags = tags

ddpayload.Title = falcopayload.Rule
ddpayload.Text = falcopayload.Output
ddpayload.SourceType = "falco"
d.Title = falcopayload.Rule
d.Text = falcopayload.Output
d.SourceType = "falco"

var status string
switch falcopayload.Priority {
Expand All @@ -47,26 +41,12 @@ func newDatadogPayload(falcopayload types.FalcoPayload) datadogPayload {
default:
status = "info"
}
ddpayload.AlertType = status
d.AlertType = status

return ddpayload
return d
}

func DatadogPost(falcopayload types.FalcoPayload) {
datadogPayload := newDatadogPayload(falcopayload)
b := new(bytes.Buffer)
json.NewEncoder(b).Encode(datadogPayload)

if os.Getenv("DEBUG") == "true" {
log.Printf("[DEBUG] : Datadog's payload : %v\n", b)
}

resp, err := http.Post(datadogURL+"?api_key="+os.Getenv("DATADOG_TOKEN"), "application/json; charset=utf-8", b)
if err != nil {
log.Printf("[ERROR] : Datadog - %v\n", err.Error())
} else if resp.StatusCode != 202 {
log.Printf("[ERROR] : Datadog - %v\n", resp)
} else {
log.Printf("[INFO] : Datadog - Post sent successfully\n")
}
// DatadogPost posts event to Datadog
func (c *Client) DatadogPost(falcopayload types.FalcoPayload) {
c.Post(newDatadogPayload(falcopayload))
}
Loading

0 comments on commit e265222

Please sign in to comment.