diff --git a/cmd/karma/alerts.go b/cmd/karma/alerts.go
index 7e3f27482..7efec171f 100644
--- a/cmd/karma/alerts.go
+++ b/cmd/karma/alerts.go
@@ -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++
diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md
index 3e0844f8f..562e0b98c 100644
--- a/docs/CONFIGURATION.md
+++ b/docs/CONFIGURATION.md
@@ -69,10 +69,10 @@ alertmanager:
(`https://user:password@alertmanager.example.com`) and you don't want it to
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.
+ Note: if URI contains username and password and proxy option is NOT enabled
+ (see below), then the username & password information will be stripped from
+ the URI and `Authorization` header using Basic Auth will be set for all
+ in browser requests.
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
diff --git a/internal/alertmanager/model_test.go b/internal/alertmanager/model_test.go
index 3adfce608..89a6e879b 100644
--- a/internal/alertmanager/model_test.go
+++ b/internal/alertmanager/model_test.go
@@ -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",
diff --git a/internal/alertmanager/models.go b/internal/alertmanager/models.go
index 551dda3eb..9a4dcd6fb 100644
--- a/internal/alertmanager/models.go
+++ b/internal/alertmanager/models.go
@@ -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 {
diff --git a/internal/models/alertmanager.go b/internal/models/alertmanager.go
index 930224716..2eb08980d 100644
--- a/internal/models/alertmanager.go
+++ b/internal/models/alertmanager.go
@@ -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
diff --git a/internal/uri/urls.go b/internal/uri/urls.go
index 07c0e8035..13e77072d 100644
--- a/internal/uri/urls.go
+++ b/internal/uri/urls.go
@@ -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()
+}
diff --git a/internal/uri/urls_test.go b/internal/uri/urls_test.go
index d0e6d7337..998e2a735 100644
--- a/internal/uri/urls_test.go
+++ b/internal/uri/urls_test.go
@@ -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)
+ }
+ }
+}
diff --git a/ui/package.json b/ui/package.json
index a78b13e76..f64c53f1e 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -19,6 +19,7 @@
"favico.js": "0.3.10",
"fontfaceobserver": "2.1.0",
"lodash.debounce": "4.0.8",
+ "lodash.merge": "4.6.2",
"lodash.throttle": "4.1.1",
"lodash.uniqueid": "4.0.1",
"mobx": "5.13.0",
diff --git a/ui/src/Common/Fetch.js b/ui/src/Common/Fetch.js
new file mode 100644
index 000000000..15a1e2e11
--- /dev/null
+++ b/ui/src/Common/Fetch.js
@@ -0,0 +1,6 @@
+import merge from "lodash.merge";
+
+const FetchWithCredentials = async (uri, options) =>
+ await fetch(uri, merge({}, { credentials: "include" }, options));
+
+export { FetchWithCredentials };
diff --git a/ui/src/Common/Fetch.test.js b/ui/src/Common/Fetch.test.js
new file mode 100644
index 000000000..14483d44c
--- /dev/null
+++ b/ui/src/Common/Fetch.test.js
@@ -0,0 +1,40 @@
+import { FetchWithCredentials } from "./Fetch";
+
+beforeEach(() => {
+ fetch.resetMocks();
+});
+
+afterEach(() => {
+ jest.restoreAllMocks();
+});
+
+describe("FetchWithCredentials", () => {
+ it("fetch passes '{credentials: include}' to all requests", async () => {
+ const request = FetchWithCredentials("http://example.com", {});
+ await expect(request).resolves.toBeUndefined();
+ expect(fetch).toHaveBeenCalledWith("http://example.com", {
+ credentials: "include"
+ });
+ });
+
+ it("custom keys are merged with defaults", async () => {
+ const request = FetchWithCredentials("http://example.com", {
+ foo: "bar"
+ });
+ await expect(request).resolves.toBeUndefined();
+ expect(fetch).toHaveBeenCalledWith("http://example.com", {
+ credentials: "include",
+ foo: "bar"
+ });
+ });
+
+ it("custom credentials are used when passed", async () => {
+ const request = FetchWithCredentials("http://example.com", {
+ credentials: "none"
+ });
+ await expect(request).resolves.toBeUndefined();
+ expect(fetch).toHaveBeenCalledWith("http://example.com", {
+ credentials: "none"
+ });
+ });
+});
diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/Silence/DeleteSilence.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/Silence/DeleteSilence.js
index 055dbbc58..d22aa16e8 100644
--- a/ui/src/Components/Grid/AlertGrid/AlertGroup/Silence/DeleteSilence.js
+++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/Silence/DeleteSilence.js
@@ -15,6 +15,7 @@ import { faCircleNotch } from "@fortawesome/free-solid-svg-icons/faCircleNotch";
import { APIAlertmanagerUpstream } from "Models/API";
import { AlertStore, FormatBackendURI, FormatAlertsQ } from "Stores/AlertStore";
import { FormatQuery, QueryOperators, StaticLabels } from "Common/Query";
+import { FetchWithCredentials } from "Common/Fetch";
import { Modal } from "Components/Modal";
import {
LabelSetList,
@@ -136,7 +137,7 @@ const DeleteSilenceModalContent = observer(
FormatQuery(StaticLabels.SilenceID, QueryOperators.Equal, silenceID)
]);
- this.previewState.fetch = fetch(alertsURI, { credentials: "include" })
+ this.previewState.fetch = FetchWithCredentials(alertsURI, {})
.then(result => result.json())
.then(result => {
this.previewState.groupsToUniqueLabels(Object.values(result.groups));
@@ -165,9 +166,9 @@ const DeleteSilenceModalContent = observer(
? `${alertmanager.publicURI}/api/v2/silence/${silenceID}`
: `${alertmanager.publicURI}/api/v1/silence/${silenceID}`;
- this.deleteState.fetch = fetch(uri, {
+ this.deleteState.fetch = FetchWithCredentials(uri, {
method: "DELETE",
- credentials: "include"
+ headers: alertmanager.headers
})
.then(result => {
if (isOpenAPI) {
diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/Silence/DeleteSilence.test.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/Silence/DeleteSilence.test.js
index 7704ff8b2..f74f47a93 100644
--- a/ui/src/Components/Grid/AlertGrid/AlertGroup/Silence/DeleteSilence.test.js
+++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/Silence/DeleteSilence.test.js
@@ -122,7 +122,7 @@ describe("", () => {
expect(tree.find("ErrorMessage")).toHaveLength(1);
});
- it("[v1] sends a DELETE request after clicking 'Confirm' button ", async () => {
+ it("[v1] sends a DELETE request after clicking 'Confirm' button", async () => {
await VerifyResponse({ status: "success" });
expect(fetch.mock.calls[1][0]).toBe(
"http://am.example.com/api/v1/silence/123456789"
@@ -130,7 +130,7 @@ describe("", () => {
expect(fetch.mock.calls[1][1]).toMatchObject({ method: "DELETE" });
});
- it("[v2] sends a DELETE request after clicking 'Confirm' button ", async () => {
+ it("[v2] sends a DELETE request after clicking 'Confirm' button", async () => {
alertmanager.version = "0.16.2";
await VerifyResponse({ status: "success" });
expect(fetch.mock.calls[1][0]).toBe(
@@ -139,6 +139,33 @@ describe("", () => {
expect(fetch.mock.calls[1][1]).toMatchObject({ method: "DELETE" });
});
+ it("[v1] sends headers from alertmanager config", async () => {
+ alertmanager.headers = { Authorization: "Basic ***" };
+ await VerifyResponse({ status: "success" });
+ expect(fetch.mock.calls[1][0]).toBe(
+ "http://am.example.com/api/v1/silence/123456789"
+ );
+ expect(fetch.mock.calls[1][1]).toMatchObject({
+ credentials: "include",
+ method: "DELETE",
+ headers: { Authorization: "Basic ***" }
+ });
+ });
+
+ it("[v1] sends headers from alertmanager config", async () => {
+ alertmanager.headers = { Authorization: "Basic ***" };
+ alertmanager.version = "0.16.2";
+ await VerifyResponse({ status: "success" });
+ expect(fetch.mock.calls[1][0]).toBe(
+ "http://am.example.com/api/v2/silence/123456789"
+ );
+ expect(fetch.mock.calls[1][1]).toMatchObject({
+ credentials: "include",
+ method: "DELETE",
+ headers: { Authorization: "Basic ***" }
+ });
+ });
+
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/Grid/AlertGrid/AlertGroup/Silence/index.test.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/Silence/index.test.js
index 1270712d6..51630994e 100644
--- a/ui/src/Components/Grid/AlertGrid/AlertGroup/Silence/index.test.js
+++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/Silence/index.test.js
@@ -67,6 +67,7 @@ beforeEach(() => {
cluster: "default",
uri: "file:///mock",
publicURI: "http://example.com",
+ headers: {},
error: "",
version: "0.15.0",
clusterMembers: ["default"]
@@ -203,6 +204,7 @@ describe("", () => {
cluster: "default",
uri: "file:///mock",
publicURI: "http://example.com",
+ headers: {},
error: "",
version: "0.15.0",
clusterMembers: ["default"]
diff --git a/ui/src/Components/MainModal/Configuration/SortLabelName.js b/ui/src/Components/MainModal/Configuration/SortLabelName.js
index 02c44cdf4..096b3f6aa 100644
--- a/ui/src/Components/MainModal/Configuration/SortLabelName.js
+++ b/ui/src/Components/MainModal/Configuration/SortLabelName.js
@@ -7,6 +7,7 @@ import { observer } from "mobx-react";
import Creatable from "react-select/creatable";
import { StaticLabels } from "Common/Query";
+import { FetchWithCredentials } from "Common/Fetch";
import { FormatBackendURI } from "Stores/AlertStore";
import { Settings } from "Stores/Settings";
import { ReactSelectStyles } from "Components/MultiSelect";
@@ -33,9 +34,10 @@ const SortLabelName = observer(
});
populateNameSuggestions = action(() => {
- this.nameSuggestionsFetch = fetch(FormatBackendURI(`labelNames.json`), {
- credentials: "include"
- })
+ this.nameSuggestionsFetch = FetchWithCredentials(
+ FormatBackendURI(`labelNames.json`),
+ {}
+ )
.then(
result => result.json(),
err => {
diff --git a/ui/src/Components/NavBar/FilterInput/index.js b/ui/src/Components/NavBar/FilterInput/index.js
index 7b285880e..0dc7b5329 100644
--- a/ui/src/Components/NavBar/FilterInput/index.js
+++ b/ui/src/Components/NavBar/FilterInput/index.js
@@ -15,6 +15,7 @@ import { faSearch } from "@fortawesome/free-solid-svg-icons/faSearch";
import { AlertStore, FormatBackendURI } from "Stores/AlertStore";
import { Settings } from "Stores/Settings";
import { IsMobile } from "Common/Device";
+import { FetchWithCredentials } from "Common/Fetch";
import { FilterInputLabel } from "Components/Labels/FilterInputLabel";
import { AutosuggestTheme } from "./Constants";
import { History } from "./History";
@@ -72,9 +73,9 @@ const FilterInput = observer(
onSuggestionsFetchRequested = debounce(
action(({ value }) => {
if (value !== "") {
- this.inputStore.suggestionsFetch = fetch(
+ this.inputStore.suggestionsFetch = FetchWithCredentials(
FormatBackendURI(`autocomplete.json?term=${value}`),
- { credentials: "include" }
+ {}
)
.then(
result => result.json(),
diff --git a/ui/src/Components/SilenceModal/AlertManagerInput/index.test.js b/ui/src/Components/SilenceModal/AlertManagerInput/index.test.js
index 1313dd7ec..cd8b4b442 100644
--- a/ui/src/Components/SilenceModal/AlertManagerInput/index.test.js
+++ b/ui/src/Components/SilenceModal/AlertManagerInput/index.test.js
@@ -22,6 +22,7 @@ beforeEach(() => {
name: "am1",
uri: "http://am1.example.com",
publicURI: "http://am1.example.com",
+ headers: {},
error: "",
version: "0.15.0",
cluster: "ha",
@@ -31,6 +32,7 @@ beforeEach(() => {
name: "am2",
uri: "http://am2.example.com",
publicURI: "http://am2.example.com",
+ headers: {},
error: "",
version: "0.15.0",
cluster: "ha",
@@ -40,6 +42,7 @@ beforeEach(() => {
name: "am3",
uri: "http://am3.example.com",
publicURI: "http://am3.example.com",
+ headers: {},
error: "",
version: "0.15.0",
cluster: "am3",
diff --git a/ui/src/Components/SilenceModal/SilenceMatch/LabelNameInput.js b/ui/src/Components/SilenceModal/SilenceMatch/LabelNameInput.js
index 4a3176af8..8fc6c6a9e 100644
--- a/ui/src/Components/SilenceModal/SilenceMatch/LabelNameInput.js
+++ b/ui/src/Components/SilenceModal/SilenceMatch/LabelNameInput.js
@@ -8,6 +8,7 @@ import { SilenceFormMatcher } from "Models/SilenceForm";
import { MultiSelect } from "Components/MultiSelect";
import { ValidationError } from "Components/MultiSelect/ValidationError";
import { FormatBackendURI } from "Stores/AlertStore";
+import { FetchWithCredentials } from "Common/Fetch";
const LabelNameInput = observer(
class LabelNameInput extends MultiSelect {
@@ -19,9 +20,10 @@ const LabelNameInput = observer(
populateNameSuggestions = action(() => {
const { matcher } = this.props;
- this.nameSuggestionsFetch = fetch(FormatBackendURI(`labelNames.json`), {
- credentials: "include"
- })
+ this.nameSuggestionsFetch = FetchWithCredentials(
+ FormatBackendURI(`labelNames.json`),
+ {}
+ )
.then(
result => result.json(),
err => {
@@ -43,11 +45,9 @@ const LabelNameInput = observer(
populateValueSuggestions = action(() => {
const { matcher } = this.props;
- this.valueSuggestionsFetch = fetch(
+ this.valueSuggestionsFetch = FetchWithCredentials(
FormatBackendURI(`labelValues.json?name=${matcher.name}`),
- {
- credentials: "include"
- }
+ {}
)
.then(
result => result.json(),
diff --git a/ui/src/Components/SilenceModal/SilenceMatch/MatchCounter.js b/ui/src/Components/SilenceModal/SilenceMatch/MatchCounter.js
index c68ef230d..8f9d19bc5 100644
--- a/ui/src/Components/SilenceModal/SilenceMatch/MatchCounter.js
+++ b/ui/src/Components/SilenceModal/SilenceMatch/MatchCounter.js
@@ -14,6 +14,7 @@ import { FormatBackendURI, FormatAlertsQ } from "Stores/AlertStore";
import { SilenceFormStore } from "Stores/SilenceFormStore";
import { SilenceFormMatcher } from "Models/SilenceForm";
import { TooltipWrapper } from "Components/TooltipWrapper";
+import { FetchWithCredentials } from "Common/Fetch";
import { MatcherToFilter, AlertManagersToFilter } from "../Matchers";
const MatchCounter = observer(
@@ -54,7 +55,7 @@ const MatchCounter = observer(
const alertsURI =
FormatBackendURI("alerts.json?") + FormatAlertsQ(filters);
- this.matchedAlerts.fetch = fetch(alertsURI, { credentials: "include" })
+ this.matchedAlerts.fetch = FetchWithCredentials(alertsURI, {})
.then(result => {
return result.json();
})
diff --git a/ui/src/Components/SilenceModal/SilencePreview/index.js b/ui/src/Components/SilenceModal/SilencePreview/index.js
index 41d3c8d2d..2701169e2 100644
--- a/ui/src/Components/SilenceModal/SilencePreview/index.js
+++ b/ui/src/Components/SilenceModal/SilencePreview/index.js
@@ -15,6 +15,7 @@ import {
LabelSetList,
GroupListToUniqueLabelsList
} from "Components/LabelSetList";
+import { FetchWithCredentials } from "Common/Fetch";
import { MatcherToFilter, AlertManagersToFilter } from "../Matchers";
const FetchError = ({ message }) => (
@@ -67,7 +68,7 @@ const SilencePreview = observer(
const alertsURI =
FormatBackendURI("alerts.json?") + FormatAlertsQ(filters);
- this.matchedAlerts.fetch = fetch(alertsURI, { credentials: "include" })
+ this.matchedAlerts.fetch = FetchWithCredentials(alertsURI, {})
.then(result => {
return result.json();
})
diff --git a/ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitProgress.js b/ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitProgress.js
index 45c322ae7..d16ed4a6e 100644
--- a/ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitProgress.js
+++ b/ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitProgress.js
@@ -13,6 +13,7 @@ import { faExclamationCircle } from "@fortawesome/free-solid-svg-icons/faExclama
import { APISilenceMatcher } from "Models/API";
import { AlertStore } from "Stores/AlertStore";
+import { FetchWithCredentials } from "Common/Fetch";
const SubmitState = Object.freeze({
InProgress: "InProgress",
@@ -108,13 +109,13 @@ const SilenceSubmitProgress = observer(
? `${am.publicURI}/api/v2/silences`
: `${am.publicURI}/api/v1/silences`;
- this.submitState.fetch = fetch(uri, {
+ this.submitState.fetch = FetchWithCredentials(uri, {
method: "POST",
body: JSON.stringify(payload),
headers: {
- "Content-Type": "application/json"
- },
- credentials: "include"
+ "Content-Type": "application/json",
+ ...am.headers
+ }
})
.then(result => {
if (isOpenAPI) {
diff --git a/ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitProgress.test.js b/ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitProgress.test.js
index d04bab3cb..73d7852fb 100644
--- a/ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitProgress.test.js
+++ b/ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitProgress.test.js
@@ -15,6 +15,7 @@ beforeEach(() => {
name: "mockAlertmanager",
uri: "file:///mock",
publicURI: "http://example.com",
+ headers: { foo: "bar" },
error: "",
version: "0.15.0",
cluster: "mockAlertmanager",
@@ -68,7 +69,7 @@ describe("", () => {
const payload = fetch.mock.calls[0][1];
expect(payload).toMatchObject({
method: "POST",
- headers: { "Content-Type": "application/json" },
+ headers: { "Content-Type": "application/json", foo: "bar" },
body: JSON.stringify({
matchers: [],
startsAt: "now",
@@ -93,6 +94,7 @@ describe("", () => {
name: "am1",
uri: "file:///mock",
publicURI: "http://am1.example.com",
+ headers: {},
error: "",
version: "0.15.0",
cluster: "ha",
@@ -102,6 +104,7 @@ describe("", () => {
name: "am2",
uri: "file:///mock",
publicURI: "http://am2.example.com",
+ headers: {},
error: "",
version: "0.15.0",
cluster: "ha",
@@ -147,6 +150,7 @@ describe("", () => {
name: "am1",
uri: "file:///mock",
publicURI: "http://am1.example.com",
+ headers: {},
error: "",
version: "0.15.0",
cluster: "ha",
diff --git a/ui/src/Models/API.js b/ui/src/Models/API.js
index 46a452ef7..61710615f 100644
--- a/ui/src/Models/API.js
+++ b/ui/src/Models/API.js
@@ -70,6 +70,7 @@ const APIAlertmanagerUpstream = PropTypes.exact({
cluster: PropTypes.string.isRequired,
uri: PropTypes.string.isRequired,
publicURI: PropTypes.string.isRequired,
+ headers: PropTypes.object.isRequired,
error: PropTypes.string.isRequired,
version: PropTypes.string.isRequired,
clusterMembers: PropTypes.arrayOf(PropTypes.string).isRequired
diff --git a/ui/src/Stores/AlertStore.js b/ui/src/Stores/AlertStore.js
index e6025abb5..64ecc7935 100644
--- a/ui/src/Stores/AlertStore.js
+++ b/ui/src/Stores/AlertStore.js
@@ -6,6 +6,8 @@ import equal from "fast-deep-equal";
import qs from "qs";
+import { FetchWithCredentials } from "Common/Fetch";
+
const QueryStringEncodeOptions = {
encodeValuesOnly: true, // don't encode q[]
indices: false // go-gin doesn't support parsing q[0]=foo&q[1]=bar
@@ -247,7 +249,7 @@ class AlertStore {
`alerts.json?sortOrder=${sortOrder}&sortLabel=${sortLabel}&sortReverse=${sortReverse}&`
) + FormatAPIFilterQuery(this.filters.values.map(f => f.raw));
- return fetch(alertsURI, { credentials: "include" })
+ return FetchWithCredentials(alertsURI, {})
.then(result => {
this.status.setProcessing();
return result.json();
diff --git a/ui/src/__mocks__/Alerts.js b/ui/src/__mocks__/Alerts.js
index e0059eebf..edaf24c93 100644
--- a/ui/src/__mocks__/Alerts.js
+++ b/ui/src/__mocks__/Alerts.js
@@ -71,6 +71,9 @@ const MockAlertmanager = () => ({
cluster: "default",
uri: "http://localhost",
publicURI: "http://am.example.com",
+ headers: {
+ Authorization: "Basic foo bar"
+ },
error: "",
version: "0.15.0",
clusterMembers: ["default"]