feat(api): expose alertmanager headers in the API

This commit is contained in:
Łukasz Mierzwa
2019-09-26 20:37:23 +01:00
parent a9d1e5d80e
commit 4efe17e8f1
6 changed files with 141 additions and 21 deletions
+10
View File
@@ -13,6 +13,7 @@ import (
"github.com/prymitive/karma/internal/filters"
"github.com/prymitive/karma/internal/models"
"github.com/prymitive/karma/internal/slices"
"github.com/prymitive/karma/internal/uri"
log "github.com/sirupsen/logrus"
)
@@ -111,11 +112,20 @@ func getUpstreams() models.AlertmanagerAPISummary {
Name: upstream.Name,
URI: upstream.SanitizedURI(),
PublicURI: upstream.PublicURI(),
Headers: map[string]string{},
Error: upstream.Error(),
Version: upstream.Version(),
Cluster: upstream.ClusterID(),
ClusterMembers: members,
}
if !upstream.ProxyRequests {
for k, v := range uri.HeadersForBasicAuth(u.PublicURI) {
u.Headers[k] = v
}
for k, v := range upstream.HTTPHeaders {
u.Headers[k] = v
}
}
summary.Instances = append(summary.Instances, u)
summary.Counters.Total++
+2 -2
View File
@@ -35,12 +35,12 @@ var uriTests = []uriTest{
{
rawURI: "http://user:pass@alertmanager.example.com",
proxy: false,
publicURI: "http://user:pass@alertmanager.example.com",
publicURI: "http://alertmanager.example.com",
},
{
rawURI: "https://user:pass@alertmanager.example.com/foo",
proxy: false,
publicURI: "https://user:pass@alertmanager.example.com/foo",
publicURI: "https://alertmanager.example.com/foo",
},
{
rawURI: "http://user:pass@alertmanager.example.com",
+1 -1
View File
@@ -211,7 +211,7 @@ func (am *Alertmanager) PublicURI() string {
if am.ExternalURI != "" {
return am.ExternalURI
}
return am.URI
return uri.WithoutUserinfo(am.URI)
}
func (am *Alertmanager) pullAlerts(version string) error {
+6 -5
View File
@@ -29,11 +29,12 @@ type AlertmanagerAPIStatus struct {
URI string `json:"uri"`
// this is URI client should use to talk to this Alertmanager, it might be
// same as real or proxied URI
PublicURI string `json:"publicURI"`
Error string `json:"error"`
Version string `json:"version"`
Cluster string `json:"cluster"`
ClusterMembers []string `json:"clusterMembers"`
PublicURI string `json:"publicURI"`
Headers map[string]string `json:"headers"`
Error string `json:"error"`
Version string `json:"version"`
Cluster string `json:"cluster"`
ClusterMembers []string `json:"clusterMembers"`
}
// AlertmanagerAPICounters returns number of Alertmanager instances in each
+33
View File
@@ -1,6 +1,7 @@
package uri
import (
"encoding/base64"
"net/url"
"path"
)
@@ -30,3 +31,35 @@ func SanitizeURI(s string) string {
}
return s
}
// HeadersForBasicAuth checks if the passed uri contains user & password
// (http://user:pass@example.com) and if so generates headers for Basic Auth
// based on
func HeadersForBasicAuth(s string) map[string]string {
headers := map[string]string{}
u, err := url.Parse(s)
if err != nil {
return headers
}
if u.User != nil {
if password, pwdSet := u.User.Password(); pwdSet {
auth := u.User.Username() + ":" + password
headers["Authorization"] = "Basic " + base64.StdEncoding.EncodeToString([]byte(auth))
}
}
return headers
}
// WithoutUserinfo takes an URL and returns a copy of it with basic auth
// stripped
func WithoutUserinfo(s string) string {
u, err := url.Parse(s)
if err != nil {
return s
}
u.User = nil
return u.String()
}
+89 -13
View File
@@ -7,35 +7,48 @@ import (
)
type joinURLTest struct {
base string
sub string
url string
base string
sub string
url string
isValid bool
}
var joinURLTests = []joinURLTest{
{
base: "http://localhost",
sub: "/sub",
url: "http://localhost/sub",
base: "http://localhost",
sub: "/sub",
url: "http://localhost/sub",
isValid: true,
},
{
base: "http://localhost",
sub: "/sub/",
url: "http://localhost/sub",
base: "http://localhost",
sub: "/sub/",
url: "http://localhost/sub",
isValid: true,
},
{
base: "http://am.example.com",
sub: "/api/v1/alerts",
url: "http://am.example.com/api/v1/alerts",
base: "http://am.example.com",
sub: "/api/v1/alerts",
url: "http://am.example.com/api/v1/alerts",
isValid: true,
},
{
base: "%gh&%ij",
sub: "/a + b",
url: "",
isValid: false,
},
}
func TestJoinURL(t *testing.T) {
for _, testCase := range joinURLTests {
url, err := uri.JoinURL(testCase.base, testCase.sub)
if err != nil {
if err != nil && testCase.isValid {
t.Errorf("joinURL(%v, %v) failed: %s", testCase.base, testCase.sub, err.Error())
}
if err == nil && !testCase.isValid {
t.Errorf("expected error for '%s' and '%s' but got '%s'", testCase.base, testCase.sub, url)
}
if url != testCase.url {
t.Errorf("Invalid joined url from '%s' + '%s', expected '%s', got '%s'", testCase.base, testCase.sub, testCase.url, url)
}
@@ -80,6 +93,10 @@ var sanitizeURITests = []sanitizeURITest{
raw: "https://user:pass@alertmanager.example.com/foo",
sanitized: "https://user:xxx@alertmanager.example.com/foo",
},
{
raw: "%gh&%ij",
sanitized: "%gh&%ij",
},
}
func TestSanitizedURI(t *testing.T) {
@@ -91,3 +108,62 @@ func TestSanitizedURI(t *testing.T) {
}
}
}
func TestHeadersForBasicAuth(t *testing.T) {
type headersTest struct {
uri string
isSet bool
value string
}
testCases := []headersTest{
{
uri: "http://localhost.com",
isSet: false,
},
{
uri: "http://user@localhost.com",
isSet: false,
},
{
uri: "http://user:pass@localhost.com",
isSet: true,
value: "Basic dXNlcjpwYXNz",
},
{
uri: "%gh&%ij",
isSet: false,
},
}
for _, test := range testCases {
headers := uri.HeadersForBasicAuth(test.uri)
value, isSet := headers["Authorization"]
if isSet != test.isSet {
t.Errorf("[%s] expected Authorization header: %v, was set: %v", test.uri, test.isSet, isSet)
}
if value != test.value {
t.Errorf("[%s] expected Authorization value: %s, value: %s", test.uri, test.value, value)
}
}
}
func TestURIWithoutUserinfo(t *testing.T) {
type userinfoTest struct {
uri string
parsed string
}
testCases := []userinfoTest{
{uri: "http://localhost", parsed: "http://localhost"},
{uri: "http://localhost?foo=bar", parsed: "http://localhost?foo=bar"},
{uri: "http://user@localhost", parsed: "http://localhost"},
{uri: "http://user:pass@localhost", parsed: "http://localhost"},
{uri: "http://user:pass@localhost?foo=bar#1", parsed: "http://localhost?foo=bar#1"},
{uri: "%gh&%ij", parsed: "%gh&%ij"},
}
for _, test := range testCases {
parsed := uri.WithoutUserinfo(test.uri)
if parsed != test.parsed {
t.Errorf("'%s' got parsed as '%s', expected: '%s'", test.uri, parsed, test.parsed)
}
}
}