Skip to content

Commit

Permalink
Added TLS configuration for Slack service to allow sending alerting t…
Browse files Browse the repository at this point in the history
…o self hosted Mattermost
  • Loading branch information
THIERRY SALLE committed Apr 19, 2017
1 parent 82e1677 commit be34a10
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 35 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ kapacitor define-handler system aggregate_by_1m.yaml

### Features

- [#1322](https://github.com/influxdata/kapacitor/pull/1322): TLS configuration in Slack service for Mattermost compatibility
- [#1159](https://github.com/influxdata/kapacitor/pulls/1159): Go version 1.7.4 -> 1.7.5
- [#1175](https://github.com/influxdata/kapacitor/pull/1175): BREAKING: Add generic error counters to every node type.
Renamed `query_errors` to `errors` in batch node.
Expand Down
5 changes: 4 additions & 1 deletion integrations/streamer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6737,7 +6737,10 @@ stream
c.Enabled = true
c.URL = ts.URL + "/test/slack/url"
c.Channel = "#channel"
sl := slack.NewService(c, logService.NewLogger("[test_slack] ", log.LstdFlags))
sl, err := slack.NewService(c, logService.NewLogger("[test_slack] ", log.LstdFlags))
if err != nil {
t.Error(err)
}
tm.SlackService = sl
}
testStreamerNoOutput(t, "TestStream_Alert", script, 13*time.Second, tmInit)
Expand Down
12 changes: 9 additions & 3 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,9 @@ func New(c *Config, buildInfo BuildInfo, logService logging.Interface) (*Server,
s.appendPushoverService()
s.appendSMTPService()
s.appendTelegramService()
s.appendSlackService()
if err := s.appendSlackService(); err != nil {
return nil, errors.Wrap(err, "slack service")
}
s.appendSNMPTrapService()
s.appendSensuService()
s.appendTalkService()
Expand Down Expand Up @@ -472,16 +474,20 @@ func (s *Server) appendSensuService() {
s.AppendService("sensu", srv)
}

func (s *Server) appendSlackService() {
func (s *Server) appendSlackService() error {
c := s.config.Slack
l := s.LogService.NewLogger("[slack] ", log.LstdFlags)
srv := slack.NewService(c, l)
srv, err := slack.NewService(c, l)
if err != nil {
return err
}

s.TaskMaster.SlackService = srv
s.AlertService.SlackService = srv

s.SetDynamicService("slack", srv)
s.AppendService("slack", srv)
return nil
}

func (s *Server) appendSNMPTrapService() {
Expand Down
72 changes: 44 additions & 28 deletions server/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6037,13 +6037,17 @@ func TestServer_UpdateConfig(t *testing.T) {
Elements: []client.ConfigElement{{
Link: client.Link{Relation: client.Self, Href: "/kapacitor/v1/config/slack/"},
Options: map[string]interface{}{
"channel": "",
"enabled": false,
"global": true,
"icon-emoji": "",
"state-changes-only": false,
"url": false,
"username": "kapacitor",
"channel": "",
"enabled": false,
"global": true,
"icon-emoji": "",
"state-changes-only": false,
"url": false,
"username": "kapacitor",
"ssl-ca": "",
"ssl-cert": "",
"ssl-key": "",
"insecure-skip-verify": false,
},
Redacted: []string{
"url",
Expand All @@ -6053,13 +6057,17 @@ func TestServer_UpdateConfig(t *testing.T) {
expDefaultElement: client.ConfigElement{
Link: client.Link{Relation: client.Self, Href: "/kapacitor/v1/config/slack/"},
Options: map[string]interface{}{
"channel": "",
"enabled": false,
"global": true,
"icon-emoji": "",
"state-changes-only": false,
"url": false,
"username": "kapacitor",
"channel": "",
"enabled": false,
"global": true,
"icon-emoji": "",
"state-changes-only": false,
"url": false,
"username": "kapacitor",
"ssl-ca": "",
"ssl-cert": "",
"ssl-key": "",
"insecure-skip-verify": false,
},
Redacted: []string{
"url",
Expand All @@ -6080,13 +6088,17 @@ func TestServer_UpdateConfig(t *testing.T) {
Elements: []client.ConfigElement{{
Link: client.Link{Relation: client.Self, Href: "/kapacitor/v1/config/slack/"},
Options: map[string]interface{}{
"channel": "#general",
"enabled": true,
"global": false,
"icon-emoji": "",
"state-changes-only": false,
"url": true,
"username": "kapacitor",
"channel": "#general",
"enabled": true,
"global": false,
"icon-emoji": "",
"state-changes-only": false,
"url": true,
"username": "kapacitor",
"ssl-ca": "",
"ssl-cert": "",
"ssl-key": "",
"insecure-skip-verify": false,
},
Redacted: []string{
"url",
Expand All @@ -6096,13 +6108,17 @@ func TestServer_UpdateConfig(t *testing.T) {
expElement: client.ConfigElement{
Link: client.Link{Relation: client.Self, Href: "/kapacitor/v1/config/slack/"},
Options: map[string]interface{}{
"channel": "#general",
"enabled": true,
"global": false,
"icon-emoji": "",
"state-changes-only": false,
"url": true,
"username": "kapacitor",
"channel": "#general",
"enabled": true,
"global": false,
"icon-emoji": "",
"state-changes-only": false,
"url": true,
"username": "kapacitor",
"ssl-ca": "",
"ssl-cert": "",
"ssl-key": "",
"insecure-skip-verify": false,
},
Redacted: []string{
"url",
Expand Down
9 changes: 9 additions & 0 deletions services/slack/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@ type Config struct {
// Whether all alerts should automatically use stateChangesOnly mode.
// Only applies if global is also set.
StateChangesOnly bool `toml:"state-changes-only" override:"state-changes-only"`

// Path to CA file
SSLCA string `toml:"ssl-ca" override:"ssl-ca"`
// Path to host cert file
SSLCert string `toml:"ssl-cert" override:"ssl-cert"`
// Path to cert key file
SSLKey string `toml:"ssl-key" override:"ssl-key"`
// Use SSL but skip chain & host verification
InsecureSkipVerify bool `toml:"insecure-skip-verify" override:"insecure-skip-verify"`
}

func NewConfig() Config {
Expand Down
71 changes: 68 additions & 3 deletions services/slack/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package slack

import (
"bytes"
"crypto/tls"
"crypto/x509"
"encoding/json"
"fmt"
"io"
Expand All @@ -16,15 +18,29 @@ import (

type Service struct {
configValue atomic.Value
clientValue atomic.Value
logger *log.Logger
client *http.Client
}

func NewService(c Config, l *log.Logger) *Service {
func NewService(c Config, l *log.Logger) (*Service, error) {
tlsConfig, err := getTLSConfig(c.SSLCA, c.SSLCert, c.SSLKey, c.InsecureSkipVerify)
if err != nil {
return nil, err
}
if tlsConfig.InsecureSkipVerify {
l.Println("W! Slack service is configured to skip ssl verification")
}
s := &Service{
logger: l,
}
s.configValue.Store(c)
return s
s.clientValue.Store(&http.Client{
Transport: &http.Transport{
TLSClientConfig: tlsConfig,
},
})
return s, nil
}

func (s *Service) Open() error {
Expand All @@ -46,7 +62,19 @@ func (s *Service) Update(newConfig []interface{}) error {
if c, ok := newConfig[0].(Config); !ok {
return fmt.Errorf("expected config object to be of type %T, got %T", c, newConfig[0])
} else {
tlsConfig, err := getTLSConfig(c.SSLCA, c.SSLCert, c.SSLKey, c.InsecureSkipVerify)
if err != nil {
return err
}
if tlsConfig.InsecureSkipVerify {
s.logger.Println("W! Slack service is configured to skip ssl verification")
}
s.configValue.Store(c)
s.clientValue.Store(&http.Client{
Transport: &http.Transport{
TLSClientConfig: tlsConfig,
},
})
}
return nil
}
Expand Down Expand Up @@ -99,7 +127,8 @@ func (s *Service) Alert(channel, message, username, iconEmoji string, level aler
if err != nil {
return err
}
resp, err := http.Post(url, "application/json", post)
client := s.clientValue.Load().(*http.Client)
resp, err := client.Post(url, "application/json", post)
if err != nil {
return err
}
Expand Down Expand Up @@ -210,3 +239,39 @@ func (h *handler) Handle(event alert.Event) {
h.logger.Println("E! failed to send event to Slack", err)
}
}

// getTLSConfig creates a tls.Config object from the given certs, key, and CA files.
// you must give the full path to the files.
func getTLSConfig(
SSLCA, SSLCert, SSLKey string,
InsecureSkipVerify bool,
) (*tls.Config, error) {
t := &tls.Config{
InsecureSkipVerify: InsecureSkipVerify,
}
if SSLCert != "" && SSLKey != "" {
cert, err := tls.LoadX509KeyPair(SSLCert, SSLKey)
if err != nil {
return nil, fmt.Errorf(
"Could not load TLS client key/certificate: %s",
err)
}
t.Certificates = []tls.Certificate{cert}
} else if SSLCert != "" {
return nil, errors.New("Must provide both key and cert files: only cert file provided.")
} else if SSLKey != "" {
return nil, errors.New("Must provide both key and cert files: only key file provided.")
}

if SSLCA != "" {
caCert, err := ioutil.ReadFile(SSLCA)
if err != nil {
return nil, fmt.Errorf("Could not load TLS CA: %s",
err)
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)
t.RootCAs = caCertPool
}
return t, nil
}

0 comments on commit be34a10

Please sign in to comment.