From ad699fc6fed1c699bfa17e08afabef7544f3b44a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Tue, 27 Aug 2019 21:58:52 +0100 Subject: [PATCH] feat(backend): add external_uri config option for alertmanager upstreams Fixes #899 --- docs/CONFIGURATION.md | 30 +++++++++++++++++++++++------ internal/alertmanager/model_test.go | 27 +++++++++++++++++++++++++- internal/alertmanager/models.go | 4 ++++ internal/alertmanager/upstream.go | 19 ++++++++++++++++++ internal/config/config.go | 26 ++++++++++++++----------- internal/config/config_test.go | 3 +++ internal/config/models.go | 11 ++++++----- main.go | 1 + 8 files changed, 98 insertions(+), 23 deletions(-) diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index fcc6df51a..3e0844f8f 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -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 diff --git a/internal/alertmanager/model_test.go b/internal/alertmanager/model_test.go index b150eb177..3adfce608 100644 --- a/internal/alertmanager/model_test.go +++ b/internal/alertmanager/model_test.go @@ -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) } diff --git a/internal/alertmanager/models.go b/internal/alertmanager/models.go index 02edae957..551dda3eb 100644 --- a/internal/alertmanager/models.go +++ b/internal/alertmanager/models.go @@ -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 } diff --git a/internal/alertmanager/upstream.go b/internal/alertmanager/upstream.go index c5f6609a5..f325bceb7 100644 --- a/internal/alertmanager/upstream.go +++ b/internal/alertmanager/upstream.go @@ -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 + } +} diff --git a/internal/config/config.go b/internal/config/config.go index 6e7500161..a6a8dbe79 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -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) } diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 904d294b4..b7d2bfdde 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -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") diff --git a/internal/config/models.go b/internal/config/models.go index 1cbc845b3..23c3200a9 100644 --- a/internal/config/models.go +++ b/internal/config/models.go @@ -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 diff --git a/main.go b/main.go index ee383b1e2..9546e6908 100644 --- a/main.go +++ b/main.go @@ -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