feat(backend): add external_uri config option for alertmanager upstreams

Fixes #899
This commit is contained in:
Łukasz Mierzwa
2019-08-27 21:58:52 +01:00
parent 91047c8bfa
commit ad699fc6fe
8 changed files with 98 additions and 23 deletions

View File

@@ -39,6 +39,7 @@ alertmanager:
servers:
- name: string
uri: string
external_uri: string
timeout: duration
proxy: bool
tls:
@@ -63,23 +64,28 @@ alertmanager:
every alert in the UI and for filtering alerts using `@alertmanager=NAME`
filter
- `uri` - base URI of this Alertmanager server. Supported URI schemes are
`http://`, `https://` and `file://`. `file://` scheme is only useful for
testing with JSON files, see [mock](/internal/mock/) dir for examples, files
in this directory are used for running tests and when running demo instance
of karma with `make run`.
`http://` and `https://`.
If URI contains basic auth info
(`https://user:password@alertmanager.example.com`) and you don't want it to
be visible to users then ensure `proxy: true` is also set.
be visible to users then ensure `proxy: true` is also set in order to avoid
leaking auth information to the browser.
Without proxy mode full URI needs to be passed to karma web UI code.
With proxy mode all requests will be routed via karma HTTP server and since
karma has full URI in the config it only needs Alertmanager name in that
request.
`proxy: true` in order to avoid leaking auth information to the browser.
To set a different URI for all browser requests (can be any valid URI) see
`external_uri` option below.
- `external_uri` - base URI of this Alertmanager server used for all browser
requests, which currently means requests sent to alertmanager when creating,
editing or deleting silences from karma web UI (unless proxy mode is set, see
above).
This option cannot be used when `proxy` is enabled.
- `timeout` - timeout for requests send to this Alertmanager server, a string in
[time.Duration](https://golang.org/pkg/time/#ParseDuration) format.
- `proxy` - if enabled requests from user browsers to this Alertmanager will be
proxied via karma. This applies to requests made when managing silences via
karma (creating or expiring silences).
THis option cannot be used when `external_uri` is set.
- `tls:ca` - path to CA certificate used to establish TLS connection to this
Alertmanager instance (for URIs using `https://` scheme). If unset or empty
string is set then Go will try to find system CA certificates using well known
@@ -740,6 +746,18 @@ ALERTMANAGER_URI=https://alertmanager.example.com karma
karma --alertmanager.uri https://alertmanager.example.com
```
### Alertmanager external URI
To set the `external_uri` key from `alertmanager.servers` map
`ALERTMANAGER_EXTERNAL_URI` env or `--alertmanager.external_uri` flag can be
used.
Examples:
```shell
ALERTMANAGER_EXTERNAL_URI=https://alertmanager.example.com karma
karma --alertmanager.external_uri https://alertmanager.example.com
```
### Alertmanager name
To set the `name` key from `alertmanager.servers` map `ALERTMANAGER_NAME` env or

View File

@@ -6,6 +6,7 @@ import (
type uriTest struct {
rawURI string
extURI string
proxy bool
publicURI string
}
@@ -46,11 +47,35 @@ var uriTests = []uriTest{
proxy: true,
publicURI: "/proxy/alertmanager/test",
},
{
rawURI: "http://user:pass@alertmanager.example.com",
extURI: "http://am.example.com",
proxy: true,
publicURI: "/proxy/alertmanager/test",
},
{
rawURI: "http://alertmanager.example.com",
extURI: "http://am.example.com",
proxy: true,
publicURI: "/proxy/alertmanager/test",
},
{
rawURI: "http://user:pass@alertmanager.example.com",
extURI: "http://am.example.com",
proxy: false,
publicURI: "http://am.example.com",
},
{
rawURI: "http://alertmanager.example.com",
extURI: "http://am.example.com",
proxy: false,
publicURI: "http://am.example.com",
},
}
func TestAlertmanagerURI(t *testing.T) {
for _, test := range uriTests {
am, err := NewAlertmanager("test", test.rawURI, WithProxy(test.proxy))
am, err := NewAlertmanager("test", test.rawURI, WithExternalURI(test.extURI), WithProxy(test.proxy))
if err != nil {
t.Error(err)
}

View File

@@ -34,6 +34,7 @@ type alertmanagerMetrics struct {
// Alertmanager represents Alertmanager upstream instance
type Alertmanager struct {
URI string `json:"uri"`
ExternalURI string `json:"-"`
RequestTimeout time.Duration `json:"timeout"`
Name string `json:"name"`
// whenever this instance should be proxied
@@ -207,6 +208,9 @@ func (am *Alertmanager) PublicURI() string {
}
return uri
}
if am.ExternalURI != "" {
return am.ExternalURI
}
return am.URI
}

View File

@@ -3,6 +3,7 @@ package alertmanager
import (
"fmt"
"net/http"
"net/url"
"sync"
"time"
@@ -23,6 +24,7 @@ var (
func NewAlertmanager(name, upstreamURI string, opts ...Option) (*Alertmanager, error) {
am := &Alertmanager{
URI: upstreamURI,
ExternalURI: "",
RequestTimeout: time.Second * 10,
Name: name,
lock: sync.RWMutex{},
@@ -64,6 +66,10 @@ func RegisterAlertmanager(am *Alertmanager) error {
return fmt.Errorf("alertmanager upstream '%s' already exist", am.Name)
}
if am.ExternalURI != "" && am.ProxyRequests {
return fmt.Errorf("alertmanager upstream '%s' is configured with both proxy and external_uri, only one of those options can be enabled", am.Name)
}
for _, existingAM := range upstreams {
if existingAM.URI == am.URI {
return fmt.Errorf("alertmanager upstream '%s' already collects from '%s'", existingAM.Name, existingAM.URI)
@@ -128,3 +134,16 @@ func WithHTTPTransport(httpTransport http.RoundTripper) Option {
return nil
}
}
// WithExternalURI option allows to set custom ExternalURI on our instance
func WithExternalURI(uri string) Option {
return func(am *Alertmanager) error {
// first validate that this is a valid URI
_, err := url.Parse(uri)
if err != nil {
return err
}
am.ExternalURI = uri
return nil
}
}

View File

@@ -30,6 +30,8 @@ func init() {
"Name for the Alertmanager server (only used with simplified config)")
pflag.String("alertmanager.uri", "",
"Alertmanager server URI (only used with simplified config)")
pflag.String("alertmanager.external_uri", "",
"Alertmanager server URI used for web UI links (only used with simplified config)")
pflag.Duration("alertmanager.timeout", time.Second*40,
"Timeout for requests sent to the Alertmanager server (only used with simplified config)")
pflag.Bool("alertmanager.proxy", false,
@@ -247,11 +249,12 @@ func (config *configSchema) Read() {
log.Info("Using simple config with a single Alertmanager server")
config.Alertmanager.Servers = []alertmanagerConfig{
{
Name: v.GetString("alertmanager.name"),
URI: v.GetString("alertmanager.uri"),
Timeout: v.GetDuration("alertmanager.timeout"),
Proxy: v.GetBool("alertmanager.proxy"),
Headers: make(map[string]string),
Name: v.GetString("alertmanager.name"),
URI: v.GetString("alertmanager.uri"),
ExternalURI: v.GetString("alertmanager.external_uri"),
Timeout: v.GetDuration("alertmanager.timeout"),
Proxy: v.GetBool("alertmanager.proxy"),
Headers: make(map[string]string),
},
}
}
@@ -266,12 +269,13 @@ func (config *configSchema) LogValues() {
servers := []alertmanagerConfig{}
for _, s := range cfg.Alertmanager.Servers {
server := alertmanagerConfig{
Name: s.Name,
URI: uri.SanitizeURI(s.URI),
Timeout: s.Timeout,
TLS: s.TLS,
Proxy: s.Proxy,
Headers: s.Headers,
Name: s.Name,
URI: uri.SanitizeURI(s.URI),
ExternalURI: uri.SanitizeURI(s.ExternalURI),
Timeout: s.Timeout,
TLS: s.TLS,
Proxy: s.Proxy,
Headers: s.Headers,
}
servers = append(servers, server)
}

View File

@@ -19,6 +19,7 @@ func resetEnv() {
karmaEnvVariables := []string{
"ALERTMANAGER_INTERVAL",
"ALERTMANAGER_URI",
"ALERTMANAGER_EXTERNAL_URI",
"ALERTMANAGER_NAME",
"ALERTMANAGET_TIMEOUT",
"ANNOTATIONS_DEFAULT_HIDDEN",
@@ -58,6 +59,7 @@ func testReadConfig(t *testing.T) {
servers:
- name: default
uri: http://localhost
external_uri: http://example.com
timeout: 40s
proxy: false
tls:
@@ -155,6 +157,7 @@ func TestReadConfig(t *testing.T) {
log.SetLevel(log.ErrorLevel)
os.Setenv("ALERTMANAGER_INTERVAL", "1s")
os.Setenv("ALERTMANAGER_URI", "http://localhost")
os.Setenv("ALERTMANAGER_EXTERNAL_URI", "http://example.com")
os.Setenv("ANNOTATIONS_DEFAULT_HIDDEN", "true")
os.Setenv("ANNOTATIONS_VISIBLE", "summary")
os.Setenv("CUSTOM_CSS", "/custom.css")

View File

@@ -6,11 +6,12 @@ import (
)
type alertmanagerConfig struct {
Name string
URI string
Timeout time.Duration
Proxy bool
TLS struct {
Name string
URI string
ExternalURI string `yaml:"external_uri" mapstructure:"external_uri"`
Timeout time.Duration
Proxy bool
TLS struct {
CA string
Cert string
Key string

View File

@@ -126,6 +126,7 @@ func setupUpstreams() {
am, err := alertmanager.NewAlertmanager(
s.Name,
s.URI,
alertmanager.WithExternalURI(s.ExternalURI),
alertmanager.WithRequestTimeout(s.Timeout),
alertmanager.WithProxy(s.Proxy),
alertmanager.WithHTTPTransport(httpTransport), // we will pass a nil unless TLS.CA or TLS.Cert is set