From f502319005078c79dabeb1eb6b3978f1e024682b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Tue, 18 Feb 2020 16:49:00 +0000 Subject: [PATCH 1/6] feat(backend): allow setting CORS credentials policy --- cmd/karma/alerts.go | 19 ++++++++++--------- cmd/karma/main.go | 1 + .../tests/testscript/log_full_config_env.txt | 2 ++ .../tests/testscript/log_full_config_file.txt | 12 ++++++++++++ internal/alertmanager/models.go | 2 ++ internal/alertmanager/upstream.go | 8 ++++++++ internal/config/config.go | 6 ++++++ internal/config/config_test.go | 2 ++ internal/config/models.go | 18 ++++++++++++------ internal/models/alertmanager.go | 15 ++++++++------- 10 files changed, 63 insertions(+), 22 deletions(-) diff --git a/cmd/karma/alerts.go b/cmd/karma/alerts.go index 1f797c7ea..4e79991db 100644 --- a/cmd/karma/alerts.go +++ b/cmd/karma/alerts.go @@ -109,15 +109,16 @@ func getUpstreams() models.AlertmanagerAPISummary { } u := models.AlertmanagerAPIStatus{ - Name: upstream.Name, - URI: upstream.InternalURI(), - PublicURI: upstream.PublicURI(), - ReadOnly: upstream.ReadOnly, - Headers: map[string]string{}, - Error: upstream.Error(), - Version: upstream.Version(), - Cluster: upstream.ClusterID(), - ClusterMembers: members, + Name: upstream.Name, + URI: upstream.InternalURI(), + PublicURI: upstream.PublicURI(), + ReadOnly: upstream.ReadOnly, + Headers: map[string]string{}, + CORSCredentials: upstream.CORSCredentials, + Error: upstream.Error(), + Version: upstream.Version(), + Cluster: upstream.ClusterID(), + ClusterMembers: members, } if !upstream.ProxyRequests { for k, v := range uri.HeadersForBasicAuth(upstream.URI) { diff --git a/cmd/karma/main.go b/cmd/karma/main.go index 7d898cc64..613c4549d 100644 --- a/cmd/karma/main.go +++ b/cmd/karma/main.go @@ -143,6 +143,7 @@ func setupUpstreams() error { alertmanager.WithReadOnly(s.ReadOnly), alertmanager.WithHTTPTransport(httpTransport), // we will pass a nil unless TLS.CA or TLS.Cert is set alertmanager.WithHTTPHeaders(s.Headers), + alertmanager.WithCORSCredentials(s.CORS.Credentials), ) if err != nil { return fmt.Errorf("Failed to create Alertmanager '%s' with URI '%s': %s", s.Name, uri.SanitizeURI(s.URI), err) diff --git a/cmd/karma/tests/testscript/log_full_config_env.txt b/cmd/karma/tests/testscript/log_full_config_env.txt index c973bd3c2..91012dd53 100644 --- a/cmd/karma/tests/testscript/log_full_config_env.txt +++ b/cmd/karma/tests/testscript/log_full_config_env.txt @@ -84,6 +84,8 @@ level=info msg=" cert: \"\"" level=info msg=" key: \"\"" level=info msg=" insecureSkipVerify: false" level=info msg=" headers: {}" +level=info msg=" cors:" +level=info msg=" credentials: include" level=info msg="alertAcknowledgement:" level=info msg=" enabled: true" level=info msg=" duration: 5m0s" diff --git a/cmd/karma/tests/testscript/log_full_config_file.txt b/cmd/karma/tests/testscript/log_full_config_file.txt index ee9f5677b..894d33f51 100644 --- a/cmd/karma/tests/testscript/log_full_config_file.txt +++ b/cmd/karma/tests/testscript/log_full_config_file.txt @@ -15,12 +15,16 @@ alertmanager: uri: "http://localhost:9094" timeout: 10s readonly: true + cors: + credentials: omit - name: local uri: http://localhost:9095 proxy: true readonly: false headers: X-Auth-Test: some-token-or-other-string + cors: + credentials: same-origin - name: client-auth uri: https://localhost:9096 timeout: 10s @@ -242,6 +246,8 @@ level=info msg=" cert: \"\"" level=info msg=" key: \"\"" level=info msg=" insecureSkipVerify: false" level=info msg=" headers: {}" +level=info msg=" cors:" +level=info msg=" credentials: include" level=info msg=" - name: ha2" level=info msg=" uri: http://localhost:9094" level=info msg=" external_uri: \"\"" @@ -254,6 +260,8 @@ level=info msg=" cert: \"\"" level=info msg=" key: \"\"" level=info msg=" insecureSkipVerify: false" level=info msg=" headers: {}" +level=info msg=" cors:" +level=info msg=" credentials: omit" level=info msg=" - name: local" level=info msg=" uri: http://localhost:9095" level=info msg=" external_uri: \"\"" @@ -267,6 +275,8 @@ level=info msg=" key: \"\"" level=info msg=" insecureSkipVerify: false" level=info msg=" headers:" level=info msg=" X-Auth-Test: some-token-or-other-string" +level=info msg=" cors:" +level=info msg=" credentials: same-origin" level=info msg=" - name: client-auth" level=info msg=" uri: https://localhost:9096" level=info msg=" external_uri: \"\"" @@ -279,6 +289,8 @@ level=info msg=" cert: cert.pem" level=info msg=" key: key.pem" level=info msg=" insecureSkipVerify: false" level=info msg=" headers: {}" +level=info msg=" cors:" +level=info msg=" credentials: include" level=info msg="alertAcknowledgement:" level=info msg=" enabled: true" level=info msg=" duration: 7m0s" diff --git a/internal/alertmanager/models.go b/internal/alertmanager/models.go index 619d00c29..8cc573fce 100644 --- a/internal/alertmanager/models.go +++ b/internal/alertmanager/models.go @@ -59,6 +59,8 @@ type Alertmanager struct { Metrics alertmanagerMetrics // headers to send with each AlertManager request HTTPHeaders map[string]string + // CORS credentials + CORSCredentials string `json:"corsCredentials"` } func (am *Alertmanager) probeVersion() string { diff --git a/internal/alertmanager/upstream.go b/internal/alertmanager/upstream.go index 5ac04afd6..60fe54844 100644 --- a/internal/alertmanager/upstream.go +++ b/internal/alertmanager/upstream.go @@ -156,3 +156,11 @@ func WithExternalURI(uri string) Option { return nil } } + +// WithCORSCredentials option sets fetch CORS credentials policy +func WithCORSCredentials(val string) Option { + return func(am *Alertmanager) error { + am.CORSCredentials = val + return nil + } +} diff --git a/internal/config/config.go b/internal/config/config.go index c069fab1d..e0df17bd7 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -49,6 +49,7 @@ func SetupFlags(f *pflag.FlagSet) { "Proxy all client requests to Alertmanager via karma (only used with simplified config)") f.Bool("alertmanager.readonly", false, "Enable read-only mode that disable silence management (only used with simplified config)") + f.String("alertmanager.cors.credentials", "include", "CORS credentials policy for browser fetch requests") f.String("karma.name", "karma", "Name for the karma instance") @@ -280,6 +281,9 @@ func (config *configSchema) Read(flags *pflag.FlagSet) string { if s.Timeout.Seconds() == 0 { config.Alertmanager.Servers[i].Timeout = config.Alertmanager.Timeout } + if s.CORS.Credentials == "" { + config.Alertmanager.Servers[i].CORS.Credentials = config.Alertmanager.CORS.Credentials + } } for labelName, customColors := range config.Labels.Color.Custom { @@ -319,6 +323,7 @@ func (config *configSchema) Read(flags *pflag.FlagSet) string { Proxy: config.Alertmanager.Proxy, ReadOnly: config.Alertmanager.ReadOnly, Headers: make(map[string]string), + CORS: config.Alertmanager.CORS, }, } } @@ -345,6 +350,7 @@ func (config *configSchema) LogValues() { Proxy: s.Proxy, ReadOnly: s.ReadOnly, Headers: s.Headers, + CORS: s.CORS, } servers = append(servers, server) } diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 68691ea78..a045d95f5 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -35,6 +35,8 @@ func testReadConfig(t *testing.T) { key: "" insecureSkipVerify: false headers: {} + cors: + credentials: include alertAcknowledgement: enabled: false duration: 15m0s diff --git a/internal/config/models.go b/internal/config/models.go index 088dcf094..9a3b68f52 100644 --- a/internal/config/models.go +++ b/internal/config/models.go @@ -5,6 +5,10 @@ import ( "time" ) +type AlertmanagerCORS struct { + Credentials string +} + type AlertmanagerConfig struct { Name string URI string @@ -19,6 +23,7 @@ type AlertmanagerConfig struct { InsecureSkipVerify bool `yaml:"insecureSkipVerify" koanf:"insecureSkipVerify"` } Headers map[string]string + CORS AlertmanagerCORS `yaml:"cors" koanf:"cors"` } type LinkDetectRules struct { @@ -39,12 +44,13 @@ type configSchema struct { Alertmanager struct { Interval time.Duration Servers []AlertmanagerConfig - Name string `yaml:"-" koanf:"name"` - Timeout time.Duration `yaml:"-" koanf:"timeout"` - URI string `yaml:"-" koanf:"uri"` - ExternalURI string `yaml:"-" koanf:"external_uri"` - Proxy bool `yaml:"-" koanf:"proxy"` - ReadOnly bool `yaml:"-" koanf:"readonly"` + Name string `yaml:"-" koanf:"name"` + Timeout time.Duration `yaml:"-" koanf:"timeout"` + URI string `yaml:"-" koanf:"uri"` + ExternalURI string `yaml:"-" koanf:"external_uri"` + Proxy bool `yaml:"-" koanf:"proxy"` + ReadOnly bool `yaml:"-" koanf:"readonly"` + CORS AlertmanagerCORS `yaml:"-" koanf:"cors"` } AlertAcknowledgement struct { Enabled bool diff --git a/internal/models/alertmanager.go b/internal/models/alertmanager.go index 00f9e69f5..900b112c7 100644 --- a/internal/models/alertmanager.go +++ b/internal/models/alertmanager.go @@ -28,13 +28,14 @@ type AlertmanagerAPIStatus struct { // this is the Alertmanager URI used for all requests made by the UI URI string `json:"uri"` // this is the Alertmanager URI used for links in the browser - PublicURI string `json:"publicURI"` - ReadOnly bool `json:"readonly"` - Headers map[string]string `json:"headers"` - Error string `json:"error"` - Version string `json:"version"` - Cluster string `json:"cluster"` - ClusterMembers []string `json:"clusterMembers"` + PublicURI string `json:"publicURI"` + ReadOnly bool `json:"readonly"` + Headers map[string]string `json:"headers"` + CORSCredentials string `json:"corsCredentials"` + Error string `json:"error"` + Version string `json:"version"` + Cluster string `json:"cluster"` + ClusterMembers []string `json:"clusterMembers"` } // AlertmanagerAPICounters returns number of Alertmanager instances in each From af8d1637a37e5aa7b227b2722554c63aee52b58c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Wed, 19 Feb 2020 11:36:08 +0000 Subject: [PATCH 2/6] fix(docs): mention CORS credentials settings --- docs/CONFIGURATION.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index 96b80211a..33caa7fb6 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -50,6 +50,8 @@ alertmanager: insecureSkipVerify: bool headers: any: string + cors: + credentials: string ``` - `interval` - how often alerts should be refreshed, a string in @@ -103,6 +105,15 @@ alertmanager: - `headers` - a map with a list of key: values which are header: value. These custom headers will be sent with every request to the alert manager instance. +- `cors:credentials` - sets the + [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) credentials + settings for browser requests, + [see docs](https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials) + for the list of possible values. + By default credentials are included in all requests (`include`), set it to + `omit` or `same-origin` if Alertmanager is configured to respond with + `Access-Control-Allow-Origin: *`, + [see docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Errors/CORSNotSupportingCredentials). Note: there are multiple supported combination of URI settings which result in a slightly different behavior. Settings that control it are: From 1f22a11e14a2f6598eea9477d5530a5a8552791d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Wed, 19 Feb 2020 12:10:42 +0000 Subject: [PATCH 3/6] fix(ui): use CORS credential settings from each alertmanager configuration Fixes #1432 --- ui/src/Components/AlertAck/index.js | 1 + ui/src/Components/AlertAck/index.test.js | 4 ++++ .../AlertGrid/AlertGroup/Alert/AlertMenu.test.js | 1 + .../AlertGroup/GroupHeader/GroupMenu.test.js | 1 + .../Components/ManagedSilence/DeleteSilence.js | 3 ++- .../ManagedSilence/DeleteSilence.test.js | 12 ++++++++++++ .../ManagedSilence/SilenceComment.test.js | 3 +++ .../ManagedSilence/SilenceDetails.test.js | 3 ++- .../Components/ManagedSilence/index.stories.js | 4 +++- ui/src/Components/ManagedSilence/index.test.js | 15 ++++++++++----- .../SilenceModal/AlertManagerInput/index.test.js | 3 +++ .../SilenceModal/Browser/index.test.js | 3 ++- .../SilenceSubmit/SilenceSubmitProgress.js | 1 + .../SilenceSubmit/SilenceSubmitProgress.test.js | 16 ++++++++++++++++ ui/src/Components/SilenceModal/index.stories.js | 8 ++++++-- ui/src/Models/API.js | 2 ++ ui/src/Stores/AlertStore.test.js | 4 ++++ ui/src/__mocks__/Alerts.js | 1 + 18 files changed, 74 insertions(+), 11 deletions(-) diff --git a/ui/src/Components/AlertAck/index.js b/ui/src/Components/AlertAck/index.js index 657682e6b..63bc222e3 100644 --- a/ui/src/Components/AlertAck/index.js +++ b/ui/src/Components/AlertAck/index.js @@ -146,6 +146,7 @@ const AlertAck = observer( body: JSON.stringify( this.submitState.silencesByCluster[cluster].payload ), + credentials: am.corsCredentials, headers: { "Content-Type": "application/json", ...am.headers diff --git a/ui/src/Components/AlertAck/index.test.js b/ui/src/Components/AlertAck/index.test.js index 03e20c8b0..58155561f 100644 --- a/ui/src/Components/AlertAck/index.test.js +++ b/ui/src/Components/AlertAck/index.test.js @@ -37,6 +37,7 @@ beforeEach(() => { publicURI: "http://example.com", readonly: false, headers: { foo: "bar" }, + corsCredentials: "include", error: "", version: "0.17.0", cluster: "default", @@ -265,6 +266,7 @@ describe("", () => { publicURI: "http://am1.example.com", readonly: false, headers: {}, + corsCredentials: "include", error: "", version: "0.17.0", cluster: "default", @@ -276,6 +278,7 @@ describe("", () => { publicURI: "http://am2.example.com", readonly: false, headers: {}, + corsCredentials: "include", error: "", version: "0.17.0", cluster: "default", @@ -314,6 +317,7 @@ describe("", () => { publicURI: "http://am1.example.com", readonly: false, headers: {}, + corsCredentials: "include", error: "", version: "0.17.0", cluster: "default", diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/AlertMenu.test.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/AlertMenu.test.js index 1c8a122ef..3b633f702 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/AlertMenu.test.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/AlertMenu.test.js @@ -27,6 +27,7 @@ beforeEach(() => { publicURI: "http://example.com", readonly: false, headers: {}, + corsCredentials: "include", error: "", version: "0.17.0", cluster: "default", diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.test.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.test.js index a1c6df152..221a159b6 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.test.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.test.js @@ -25,6 +25,7 @@ beforeEach(() => { publicURI: "http://example.com", readonly: false, headers: {}, + corsCredentials: "include", error: "", version: "0.17.0", cluster: "default", diff --git a/ui/src/Components/ManagedSilence/DeleteSilence.js b/ui/src/Components/ManagedSilence/DeleteSilence.js index c9caee3f2..60c352965 100644 --- a/ui/src/Components/ManagedSilence/DeleteSilence.js +++ b/ui/src/Components/ManagedSilence/DeleteSilence.js @@ -150,7 +150,8 @@ const DeleteSilenceModalContent = observer( this.deleteState.fetch = FetchDelete( `${alertmanager.uri}/api/v2/silence/${silence.id}`, { - headers: alertmanager.headers + headers: alertmanager.headers, + credentials: alertmanager.corsCredentials } ) .then(result => { diff --git a/ui/src/Components/ManagedSilence/DeleteSilence.test.js b/ui/src/Components/ManagedSilence/DeleteSilence.test.js index c30e6000e..b5f47ea77 100644 --- a/ui/src/Components/ManagedSilence/DeleteSilence.test.js +++ b/ui/src/Components/ManagedSilence/DeleteSilence.test.js @@ -207,6 +207,18 @@ describe("", () => { }); }); + it("uses CORS credentials from alertmanager config", async () => { + alertStore.data.upstreams.instances[0].corsCredentials = "omit"; + await VerifyResponse({ status: "success" }); + expect(fetch.mock.calls[1][0]).toBe( + "http://localhost:9093/api/v2/silence/04d37636-2350-4878-b382-e0b50353230f" + ); + expect(fetch.mock.calls[1][1]).toMatchObject({ + credentials: "omit", + method: "DELETE" + }); + }); + it("'Confirm' button is no-op after successful DELETE", async () => { const tree = await VerifyResponse({ status: "success" }); expect(fetch.mock.calls[1][0]).toBe( diff --git a/ui/src/Components/ManagedSilence/SilenceComment.test.js b/ui/src/Components/ManagedSilence/SilenceComment.test.js index c9aa463f2..b2de351fa 100644 --- a/ui/src/Components/ManagedSilence/SilenceComment.test.js +++ b/ui/src/Components/ManagedSilence/SilenceComment.test.js @@ -46,6 +46,7 @@ const MockMultipleClusters = () => { publicURI: "http://am1.example.com", readonly: false, headers: {}, + corsCredentials: "include", error: "", version: "0.17.0", cluster: "default", @@ -57,6 +58,7 @@ const MockMultipleClusters = () => { publicURI: "http://am2.example.com", readonly: false, headers: {}, + corsCredentials: "include", error: "", version: "0.17.0", cluster: "default", @@ -68,6 +70,7 @@ const MockMultipleClusters = () => { publicURI: "http://am3.example.com", readonly: false, headers: {}, + corsCredentials: "include", error: "", version: "0.17.0", cluster: "second", diff --git a/ui/src/Components/ManagedSilence/SilenceDetails.test.js b/ui/src/Components/ManagedSilence/SilenceDetails.test.js index c711568ed..731581a16 100644 --- a/ui/src/Components/ManagedSilence/SilenceDetails.test.js +++ b/ui/src/Components/ManagedSilence/SilenceDetails.test.js @@ -37,7 +37,8 @@ beforeEach(() => { readonly: false, error: "", version: "0.17.0", - headers: {} + headers: {}, + corsCredentials: "include" } ], clusters: { am: ["am1"] } diff --git a/ui/src/Components/ManagedSilence/index.stories.js b/ui/src/Components/ManagedSilence/index.stories.js index e950a95e0..9733bc73f 100644 --- a/ui/src/Components/ManagedSilence/index.stories.js +++ b/ui/src/Components/ManagedSilence/index.stories.js @@ -33,7 +33,8 @@ storiesOf("ManagedSilence", module) readonly: false, error: "", version: "0.17.0", - headers: {} + headers: {}, + corsCredentials: "include" } ], clusters: { am: ["am1"] } @@ -49,6 +50,7 @@ storiesOf("ManagedSilence", module) publicURI: "http://example.com", readonly: true, headers: {}, + corsCredentials: "include", error: "", version: "0.17.0", cluster: "ro", diff --git a/ui/src/Components/ManagedSilence/index.test.js b/ui/src/Components/ManagedSilence/index.test.js index ab299a74e..47b63fee9 100644 --- a/ui/src/Components/ManagedSilence/index.test.js +++ b/ui/src/Components/ManagedSilence/index.test.js @@ -36,7 +36,8 @@ beforeEach(() => { readonly: false, error: "", version: "0.17.0", - headers: {} + headers: {}, + corsCredentials: "include" } ], clusters: { am: ["am1"] } @@ -99,7 +100,8 @@ describe("", () => { readonly: false, error: "", version: "0.17.0", - headers: {} + headers: {}, + corsCredentials: "include" }); }); @@ -115,7 +117,8 @@ describe("", () => { readonly: false, error: "", version: "0.17.0", - headers: {} + headers: {}, + corsCredentials: "include" }, { name: "am2", @@ -126,7 +129,8 @@ describe("", () => { readonly: true, error: "", version: "0.17.0", - headers: {} + headers: {}, + corsCredentials: "include" } ], clusters: { am: ["am1", "am2"] } @@ -144,7 +148,8 @@ describe("", () => { readonly: false, error: "", version: "0.17.0", - headers: {} + headers: {}, + corsCredentials: "include" }); }); diff --git a/ui/src/Components/SilenceModal/AlertManagerInput/index.test.js b/ui/src/Components/SilenceModal/AlertManagerInput/index.test.js index 71cf47468..e785859e0 100644 --- a/ui/src/Components/SilenceModal/AlertManagerInput/index.test.js +++ b/ui/src/Components/SilenceModal/AlertManagerInput/index.test.js @@ -29,6 +29,7 @@ beforeEach(() => { publicURI: "http://am1.example.com", readonly: false, headers: {}, + corsCredentials: "include", error: "", version: "0.17.0", cluster: "ha", @@ -40,6 +41,7 @@ beforeEach(() => { publicURI: "http://am2.example.com", readonly: false, headers: {}, + corsCredentials: "include", error: "", version: "0.17.0", cluster: "ha", @@ -51,6 +53,7 @@ beforeEach(() => { publicURI: "http://am3.example.com", readonly: false, headers: {}, + corsCredentials: "include", error: "", version: "0.17.0", cluster: "am3", diff --git a/ui/src/Components/SilenceModal/Browser/index.test.js b/ui/src/Components/SilenceModal/Browser/index.test.js index 25ce5a651..b730efe59 100644 --- a/ui/src/Components/SilenceModal/Browser/index.test.js +++ b/ui/src/Components/SilenceModal/Browser/index.test.js @@ -39,7 +39,8 @@ beforeEach(() => { readonly: false, error: "", version: "0.17.0", - headers: {} + headers: {}, + corsCredentials: "include" } ], clusters: { am: ["am1"] } diff --git a/ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitProgress.js b/ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitProgress.js index 8a805db15..fe33d6beb 100644 --- a/ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitProgress.js +++ b/ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitProgress.js @@ -109,6 +109,7 @@ const SilenceSubmitProgress = observer( this.submitState.fetch = FetchPost(`${am.uri}/api/v2/silences`, { body: JSON.stringify(payload), + credentials: am.corsCredentials, headers: { "Content-Type": "application/json", ...am.headers diff --git a/ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitProgress.test.js b/ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitProgress.test.js index baf40f331..795020463 100644 --- a/ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitProgress.test.js +++ b/ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitProgress.test.js @@ -17,6 +17,7 @@ beforeEach(() => { publicURI: "http://example.com", readonly: false, headers: { foo: "bar" }, + corsCredentials: "include", error: "", version: "0.17.0", cluster: "mockAlertmanager", @@ -80,6 +81,16 @@ describe("", () => { }); }); + it("uses CORS credentials from alertmanager config", async () => { + alertStore.data.upstreams.instances[0].corsCredentials = "same-site"; + MountedSilenceSubmitProgress(); + expect(fetch.mock.calls[0][0]).toBe("http://localhost/api/v2/silences"); + expect(fetch.mock.calls[0][1]).toMatchObject({ + credentials: "same-site", + method: "POST" + }); + }); + it("will retry on another cluster member after fetch failure", async () => { fetch .mockRejectOnce(new Error("mock error message")) @@ -93,6 +104,7 @@ describe("", () => { publicURI: "http://am1.example.com", readonly: false, headers: {}, + corsCredentials: "include", error: "", version: "0.17.0", cluster: "ha", @@ -104,6 +116,7 @@ describe("", () => { publicURI: "http://am2.example.com", readonly: false, headers: {}, + corsCredentials: "include", error: "", version: "0.17.0", cluster: "ha", @@ -151,6 +164,7 @@ describe("", () => { publicURI: "http://am1.example.com", readonly: false, headers: {}, + corsCredentials: "include", error: "", version: "0.17.0", cluster: "ha", @@ -198,6 +212,7 @@ describe("", () => { publicURI: "http://am1.example.com", readonly: false, headers: {}, + corsCredentials: "include", error: "", version: "0.17.0", cluster: "ha", @@ -209,6 +224,7 @@ describe("", () => { publicURI: "http://am2.example.com", readonly: true, headers: {}, + corsCredentials: "include", error: "", version: "0.17.0", cluster: "ha", diff --git a/ui/src/Components/SilenceModal/index.stories.js b/ui/src/Components/SilenceModal/index.stories.js index abc9a6b21..ffe501cbf 100644 --- a/ui/src/Components/SilenceModal/index.stories.js +++ b/ui/src/Components/SilenceModal/index.stories.js @@ -49,6 +49,7 @@ storiesOf("SilenceModal", module) publicURI: "http://example.com", readonly: false, headers: {}, + corsCredentials: "include", error: "", version: "0.17.0", cluster: "default", @@ -123,6 +124,7 @@ storiesOf("SilenceModal", module) publicURI: "http://example.com", readonly: true, headers: {}, + corsCredentials: "include", error: "", version: "0.17.0", cluster: "default", @@ -190,7 +192,8 @@ storiesOf("SilenceModal", module) readonly: false, error: "", version: "0.17.0", - headers: {} + headers: {}, + corsCredentials: "include" } ], clusters: { am: ["am1"] } @@ -253,7 +256,8 @@ storiesOf("SilenceModal", module) readonly: false, error: "", version: "0.17.0", - headers: {} + headers: {}, + corsCredentials: "include" } ], clusters: { am: ["am1"] } diff --git a/ui/src/Models/API.js b/ui/src/Models/API.js index 7f4d5af63..5d7ff6dfd 100644 --- a/ui/src/Models/API.js +++ b/ui/src/Models/API.js @@ -71,6 +71,8 @@ const APIAlertmanagerUpstream = PropTypes.exact({ publicURI: PropTypes.string.isRequired, readonly: PropTypes.bool.isRequired, headers: PropTypes.object.isRequired, + corsCredentials: PropTypes.oneOf(["omit", "same-origin", "include"]) + .isRequired, error: PropTypes.string.isRequired, version: PropTypes.string.isRequired, clusterMembers: PropTypes.arrayOf(PropTypes.string).isRequired diff --git a/ui/src/Stores/AlertStore.test.js b/ui/src/Stores/AlertStore.test.js index f02cd8f62..2d3200567 100644 --- a/ui/src/Stores/AlertStore.test.js +++ b/ui/src/Stores/AlertStore.test.js @@ -32,6 +32,7 @@ describe("AlertStore.data", () => { publicURI: "http://example.com:8080", readonly: false, headers: { foo: "bar" }, + corsCredentials: "include", error: "", version: "0.17.0", cluster: "default", @@ -43,6 +44,7 @@ describe("AlertStore.data", () => { publicURI: "http://example.com", readonly: true, headers: {}, + corsCredentials: "include", error: "", version: "0.17.0", cluster: "default", @@ -68,6 +70,7 @@ describe("AlertStore.data", () => { publicURI: "http://example.com:8080", readonly: true, headers: {}, + corsCredentials: "include", error: "", version: "0.17.0", cluster: "default", @@ -79,6 +82,7 @@ describe("AlertStore.data", () => { publicURI: "http://example.com", readonly: true, headers: {}, + corsCredentials: "include", error: "", version: "0.17.0", cluster: "default", diff --git a/ui/src/__mocks__/Alerts.js b/ui/src/__mocks__/Alerts.js index 1244a7d4f..3937effda 100644 --- a/ui/src/__mocks__/Alerts.js +++ b/ui/src/__mocks__/Alerts.js @@ -74,6 +74,7 @@ const MockAlertmanager = () => ({ headers: { Authorization: "Basic foo bar" }, + corsCredentials: "include", error: "", version: "0.17.0", clusterMembers: ["default"] From 7c35a83a6df7e603464e8cc10626cad7d18d7cce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Wed, 19 Feb 2020 12:25:07 +0000 Subject: [PATCH 4/6] fix-ui --- .../SilenceModal/SilenceSubmit/SilenceSubmitProgress.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitProgress.test.js b/ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitProgress.test.js index 795020463..0dfaf79eb 100644 --- a/ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitProgress.test.js +++ b/ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitProgress.test.js @@ -82,11 +82,11 @@ describe("", () => { }); it("uses CORS credentials from alertmanager config", async () => { - alertStore.data.upstreams.instances[0].corsCredentials = "same-site"; + alertStore.data.upstreams.instances[0].corsCredentials = "same-origin"; MountedSilenceSubmitProgress(); expect(fetch.mock.calls[0][0]).toBe("http://localhost/api/v2/silences"); expect(fetch.mock.calls[0][1]).toMatchObject({ - credentials: "same-site", + credentials: "same-origin", method: "POST" }); }); From 86e0cf344cf32403eefa8d8f1ad2b70d18f5b3d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Wed, 19 Feb 2020 12:25:29 +0000 Subject: [PATCH 5/6] chore(demo): set cors credentials --- demo/karma.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/demo/karma.yaml b/demo/karma.yaml index e2cd5ccb9..6133589b2 100644 --- a/demo/karma.yaml +++ b/demo/karma.yaml @@ -5,10 +5,14 @@ alertmanager: uri: "http://localhost:9093" timeout: 10s proxy: true + cors: + credentials: same-origin - name: ha2 uri: "http://localhost:9094" timeout: 10s proxy: true + cors: + credentials: same-origin alertAcknowledgement: enabled: true duration: 15m0s From 6ce7b88bd66192dd5a039c43b4d90b48bb43dddd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Wed, 19 Feb 2020 13:08:08 +0000 Subject: [PATCH 6/6] fix(tests): more test coverage for cors credential settings --- .../invalid_alertmanager_cors_credentials.txt | 12 ++++++++++++ .../log_full_config_file_invalid_values.txt | 3 +++ internal/config/config.go | 7 +++++++ internal/config/config_test.go | 16 ++++++++++++++++ 4 files changed, 38 insertions(+) create mode 100644 cmd/karma/tests/testscript/invalid_alertmanager_cors_credentials.txt diff --git a/cmd/karma/tests/testscript/invalid_alertmanager_cors_credentials.txt b/cmd/karma/tests/testscript/invalid_alertmanager_cors_credentials.txt new file mode 100644 index 000000000..f0b87303a --- /dev/null +++ b/cmd/karma/tests/testscript/invalid_alertmanager_cors_credentials.txt @@ -0,0 +1,12 @@ +# Raises an error if we cors.credentials value is incorrect +karma.bin-should-fail --log.format=text --log.config=false --log.level=error --config.file karma.yaml +! stdout . +stderr 'msg="Invalid cors.credentials value ''foo'' for alertmanager ''am1'', allowed options: omit, inclue, same-origin' + +-- karma.yaml -- +alertmanager: + servers: + - name: am1 + uri: https://localhost:9093 + cors: + credentials: foo diff --git a/cmd/karma/tests/testscript/log_full_config_file_invalid_values.txt b/cmd/karma/tests/testscript/log_full_config_file_invalid_values.txt index f9121037e..409faa013 100644 --- a/cmd/karma/tests/testscript/log_full_config_file_invalid_values.txt +++ b/cmd/karma/tests/testscript/log_full_config_file_invalid_values.txt @@ -11,6 +11,8 @@ alertmanager: uri: "http://localhost:9093" timeout: bbb proxy: YEs + cors: + credentials: foo - name: ha2 uri: "http://localhost:9094" timeout: 11 @@ -58,6 +60,7 @@ ui: -- expected.stderr -- level=fatal msg="Failed to unmarshal configuration: 12 error(s) decoding:\n\n* 'Alertmanager.Servers[2].Headers[0]' expected a map, got 'string'\n* cannot parse 'Alertmanager.Servers[0].Proxy' as bool: strconv.ParseBool: parsing \"YEs\": invalid syntax\n* cannot parse 'Annotations.Default.Hidden' as bool: strconv.ParseBool: parsing \"z\": invalid syntax\n* cannot parse 'UI.alertsPerGroup' as int: strconv.ParseInt: parsing \"5a\": invalid syntax\n* cannot parse 'UI.colorTitlebar' as bool: strconv.ParseBool: parsing \"yum\": invalid syntax\n* cannot parse 'UI.hideFiltersWhenIdle' as bool: strconv.ParseBool: parsing \"z\": invalid syntax\n* cannot parse 'UI.minimalGroupWidth' as int: strconv.ParseInt: parsing \"abc4\": invalid syntax\n* cannot parse 'alertAcknowledgement.Enabled' as bool: strconv.ParseBool: parsing \"zzz\": invalid syntax\n* error decoding 'Alertmanager.Interval': time: invalid duration jjs88\n* error decoding 'Alertmanager.Servers[0].Timeout': time: invalid duration bbb\n* error decoding 'Alertmanager.Servers[2].Timeout': time: invalid duration z\n* error decoding 'UI.Refresh': time: unknown unit sm in duration 10sm" +level=fatal msg="Invalid alertmanager.cors.credentials value '', allowed options: omit, inclue, same-origin" level=fatal msg="Invalid grid.sorting.order value '', allowed options: disabled, startsAt, label" level=fatal msg="Invalid ui.collapseGroups value '', allowed options: expanded, collapsed, collapsedOnMobile" level=fatal msg="Invalid ui.theme value '', allowed options: light, dark, auto" diff --git a/internal/config/config.go b/internal/config/config.go index e0df17bd7..883094aa9 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -277,6 +277,10 @@ func (config *configSchema) Read(flags *pflag.FlagSet) string { log.Fatalf("silenceform.author.populate_from_header.value_re is required when silenceform.author.populate_from_header.header is set") } + if !slices.StringInSlice([]string{"omit", "include", "same-origin"}, config.Alertmanager.CORS.Credentials) { + log.Fatalf("Invalid alertmanager.cors.credentials value '%s', allowed options: omit, inclue, same-origin", config.Alertmanager.CORS.Credentials) + } + for i, s := range config.Alertmanager.Servers { if s.Timeout.Seconds() == 0 { config.Alertmanager.Servers[i].Timeout = config.Alertmanager.Timeout @@ -284,6 +288,9 @@ func (config *configSchema) Read(flags *pflag.FlagSet) string { if s.CORS.Credentials == "" { config.Alertmanager.Servers[i].CORS.Credentials = config.Alertmanager.CORS.Credentials } + if !slices.StringInSlice([]string{"omit", "include", "same-origin"}, config.Alertmanager.Servers[i].CORS.Credentials) { + log.Fatalf("Invalid cors.credentials value '%s' for alertmanager '%s', allowed options: omit, inclue, same-origin", config.Alertmanager.Servers[i].CORS.Credentials, s.Name) + } } for labelName, customColors := range config.Labels.Color.Custom { diff --git a/internal/config/config_test.go b/internal/config/config_test.go index a045d95f5..610dd94d6 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -316,6 +316,22 @@ func TestInvalidUITheme(t *testing.T) { } } +func TestInvalidCORSCredentials(t *testing.T) { + resetEnv() + os.Setenv("ALERTMANAGER_CORS_CREDENTIALS", "foo") + + log.SetLevel(log.PanicLevel) + defer func() { log.StandardLogger().ExitFunc = nil }() + var wasFatal bool + log.StandardLogger().ExitFunc = func(int) { wasFatal = true } + + mockConfigRead() + + if !wasFatal { + t.Error("Invalid alertmanager.cors.credentials value didn't cause log.Fatal()") + } +} + func TestDefaultConfig(t *testing.T) { resetEnv() log.SetLevel(log.ErrorLevel)