Skip to content

Optionally enable cookie-based auth #106

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/.project
/.idea
/*.iml
/release
/jiralert
/jiralert*.tar.gz
Expand Down
41 changes: 35 additions & 6 deletions cmd/jiralert/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"encoding/json"
"flag"
"fmt"
"github.com/pkg/errors"
"net/http"
"os"
"runtime"
Expand Down Expand Up @@ -83,6 +84,10 @@ func main() {
os.Exit(1)
}

// We re-use the HTTP client here. Otherwise, in case of cookie-based auth, we might get banned by JIRA for creating
// many new sessions.
var jiraHTTPClient = createHTTPClient(config, logger)

http.HandleFunc("/alert", func(w http.ResponseWriter, req *http.Request) {
level.Debug(logger).Log("msg", "handling /alert webhook request")
defer func() { _ = req.Body.Close() }()
Expand All @@ -101,25 +106,30 @@ func main() {
}
level.Debug(logger).Log("msg", " matched receiver", "receiver", conf.Name)

// TODO: Consider reusing notifiers or just jira clients to reuse connections.
tp := jira.BasicAuthTransport{
Username: conf.User,
Password: string(conf.Password),
}
client, err := jira.NewClient(tp.Client(), conf.APIURL)
client, err := jira.NewClient(jiraHTTPClient, conf.APIURL)
if err != nil {
errorHandler(w, http.StatusInternalServerError, err, conf.Name, &data, logger)
return
}

if retry, err := notify.NewReceiver(logger, conf, tmpl, client.Issue).Notify(&data, *hashJiraLabel); err != nil {
var status int

if errors.Is(err, notify.ErrUnauthorized) && config.Defaults.SessionCookie {
level.Info(logger).Log("msg", "refreshing session cookie")
jiraHTTPClient = createHTTPClient(config, logger)

// Next time, we start with a fresh session!
retry = true
}

if retry {
// Instruct Alertmanager to retry.
status = http.StatusServiceUnavailable
} else {
status = http.StatusInternalServerError
}

errorHandler(w, status, err, conf.Name, &data, logger)
return
}
Expand Down Expand Up @@ -187,3 +197,22 @@ func setupLogger(lvl string, fmt string) (logger log.Logger) {
logger = log.With(logger, "ts", log.DefaultTimestampUTC, "caller", log.DefaultCaller)
return
}

func createHTTPClient(config *config.Config, logger log.Logger) *http.Client {
if config.Defaults.SessionCookie {
level.Debug(logger).Log("msg", "creating new HTTP client using cookie auth")
tp := jira.CookieAuthTransport{
Username: config.Defaults.User,
Password: string(config.Defaults.Password),
AuthURL: config.Defaults.APIURL + "/rest/auth/1/session",
}
return tp.Client()
}

level.Debug(logger).Log("msg", "creating new HTTP client using basic auth")
tp := jira.BasicAuthTransport{
Username: config.Defaults.User,
Password: string(config.Defaults.Password),
}
return tp.Client()
}
1 change: 1 addition & 0 deletions examples/jiralert.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ defaults:
api_url: https://jiralert.atlassian.net
user: jiralert
password: 'JIRAlert'
session_cookie: false

# The type of JIRA issue to create. Required.
issue_type: Bug
Expand Down
7 changes: 4 additions & 3 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,10 @@ type ReceiverConfig struct {
Name string `yaml:"name" json:"name"`

// API access fields
APIURL string `yaml:"api_url" json:"api_url"`
User string `yaml:"user" json:"user"`
Password Secret `yaml:"password" json:"password"`
APIURL string `yaml:"api_url" json:"api_url"`
User string `yaml:"user" json:"user"`
Password Secret `yaml:"password" json:"password"`
SessionCookie bool `yaml:"session_cookie" json:"session_cookie"`

// Required issue fields
Project string `yaml:"project" json:"project"`
Expand Down
7 changes: 4 additions & 3 deletions pkg/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ defaults:
api_url: https://jiralert.atlassian.net
user: jiralert
password: 'JIRAlert'
session_cookie: false

# The type of JIRA issue to create. Required.
issue_type: Bug
Expand Down Expand Up @@ -60,17 +61,17 @@ receivers:
# Overrides default.
issue_type: Task
# JIRA components. Optional.
components: [ 'Operations' ]
components: ['Operations']
# Standard or custom field values to set on created issue. Optional.
#
# See https://developer.atlassian.com/server/jira/platform/jira-rest-api-examples/#setting-custom-field-data-for-other-field-types for further examples.
fields:
# TextField
customfield_10001: "Random text"
# SelectList
customfield_10002: { "value": "red" }
customfield_10002: {"value": "red"}
# MultiSelect
customfield_10003: [{"value": "red" }, {"value": "blue" }, {"value": "green" }]
customfield_10003: [{"value": "red"}, {"value": "blue"}, {"value": "green"}]

# File containing template definitions. Required.
template: jiralert.tmpl
Expand Down
14 changes: 12 additions & 2 deletions pkg/notify/notify.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ type Receiver struct {
timeNow func() time.Time
}

var ErrUnauthorized = errors.New("JIRA returned HTTP status 401 Unauthorized")

// NewReceiver creates a Receiver using the provided configuration, template and jiraIssueService.
func NewReceiver(logger log.Logger, c *config.ReceiverConfig, t *template.Template, client jiraIssueService) *Receiver {
return &Receiver{logger: logger, conf: c, tmpl: t, client: client, timeNow: time.Now}
Expand Down Expand Up @@ -385,8 +387,16 @@ func handleJiraErrResponse(api string, resp *jira.Response, err error, logger lo
if resp != nil && resp.StatusCode/100 != 2 {
retry := resp.StatusCode == 500 || resp.StatusCode == 503
body, _ := ioutil.ReadAll(resp.Body)
// go-jira error message is not particularly helpful, replace it
return retry, errors.Errorf("JIRA request %s returned status %s, body %q", resp.Request.URL, resp.Status, string(body))

var responseErr error
if resp.StatusCode == 401 {
responseErr = ErrUnauthorized
} else {
// go-jira error message is not particularly helpful, replace it
responseErr = errors.Errorf("JIRA request %s returned status %s, body %q", resp.Request.URL, resp.Status, string(body))
}

return retry, responseErr
}
return false, errors.Wrapf(err, "JIRA request %s failed", api)
}