mirror of
https://github.com/prymitive/karma
synced 2026-05-05 03:16:51 +00:00
feat(backend): add basic auth support
This commit is contained in:
@@ -98,16 +98,28 @@ func setupRouter(router *gin.Engine) {
|
||||
ExposeHeaders: []string{"Content-Length"},
|
||||
}))
|
||||
|
||||
router.GET(getViewURL("/"), index)
|
||||
router.GET(getViewURL("/health"), pong)
|
||||
router.GET(getViewURL("/alerts.json"), alerts)
|
||||
router.GET(getViewURL("/autocomplete.json"), autocomplete)
|
||||
router.GET(getViewURL("/labelNames.json"), knownLabelNames)
|
||||
router.GET(getViewURL("/labelValues.json"), knownLabelValues)
|
||||
router.GET(getViewURL("/silences.json"), silences)
|
||||
var protected *gin.RouterGroup
|
||||
if len(config.Config.Authentication.Users) > 0 {
|
||||
users := map[string]string{}
|
||||
for _, u := range config.Config.Authentication.Users {
|
||||
users[u.Username] = u.Password
|
||||
}
|
||||
protected = router.Group(getViewURL("/"), gin.BasicAuth(users))
|
||||
} else {
|
||||
protected = router.Group(getViewURL("/"))
|
||||
}
|
||||
|
||||
router.GET(getViewURL("/custom.css"), customCSS)
|
||||
router.GET(getViewURL("/custom.js"), customJS)
|
||||
router.GET(getViewURL("/health"), pong)
|
||||
|
||||
protected.GET("/", index)
|
||||
protected.GET("/alerts.json", alerts)
|
||||
protected.GET("/autocomplete.json", autocomplete)
|
||||
protected.GET("/labelNames.json", knownLabelNames)
|
||||
protected.GET("/labelValues.json", knownLabelValues)
|
||||
protected.GET("/silences.json", silences)
|
||||
|
||||
protected.GET("/custom.css", customCSS)
|
||||
protected.GET("/custom.js", customJS)
|
||||
|
||||
router.NoRoute(notFound)
|
||||
}
|
||||
|
||||
@@ -69,6 +69,8 @@ cmp stderr expected.stderr
|
||||
-- expected.stderr --
|
||||
level=info msg="Version: dev"
|
||||
level=info msg="Parsed configuration:"
|
||||
level=info msg="authentication:"
|
||||
level=info msg=" users: []"
|
||||
level=info msg="alertmanager:"
|
||||
level=info msg=" interval: 10s"
|
||||
level=info msg=" servers:"
|
||||
|
||||
@@ -4,6 +4,12 @@ karma.bin-should-work --config.file=custom.yaml --check-config
|
||||
cmp stderr expected.stderr
|
||||
|
||||
-- custom.yaml --
|
||||
authentication:
|
||||
users:
|
||||
- username: number
|
||||
password: 1234
|
||||
- username: string
|
||||
password: '1234'
|
||||
alertmanager:
|
||||
interval: 10s
|
||||
servers:
|
||||
@@ -231,6 +237,12 @@ FLR1flnW2lx5o5csDzTpi+jgC6nu1zE0DWo1c5ZdpVO289POIpqh
|
||||
level=info msg="Reading configuration file custom.yaml"
|
||||
level=info msg="Version: dev"
|
||||
level=info msg="Parsed configuration:"
|
||||
level=info msg="authentication:"
|
||||
level=info msg=" users:"
|
||||
level=info msg=" - username: number"
|
||||
level=info msg=" password: '***'"
|
||||
level=info msg=" - username: string"
|
||||
level=info msg=" password: '***'"
|
||||
level=info msg="alertmanager:"
|
||||
level=info msg=" interval: 10s"
|
||||
level=info msg=" servers:"
|
||||
|
||||
@@ -143,6 +143,11 @@ func alerts(c *gin.Context) {
|
||||
start := time.Now()
|
||||
ts, _ := start.UTC().MarshalText()
|
||||
|
||||
var username string
|
||||
if len(config.Config.Authentication.Users) > 0 {
|
||||
username = c.MustGet(gin.AuthUserKey).(string)
|
||||
}
|
||||
|
||||
// initialize response object, set fields that don't require any locking
|
||||
resp := models.AlertsResponse{}
|
||||
resp.Status = "success"
|
||||
@@ -175,6 +180,10 @@ func alerts(c *gin.Context) {
|
||||
CommentPrefix: config.Config.AlertAcknowledgement.CommentPrefix,
|
||||
},
|
||||
}
|
||||
resp.Authentication = models.AuthenticationInfo{
|
||||
Enabled: len(config.Config.Authentication.Users) > 0,
|
||||
Username: username,
|
||||
}
|
||||
|
||||
if config.Config.Grid.Sorting.CustomValues.Labels != nil {
|
||||
resp.Settings.Sorting.ValueMapping = config.Config.Grid.Sorting.CustomValues.Labels
|
||||
|
||||
@@ -818,3 +818,18 @@ func TestEmptySettings(t *testing.T) {
|
||||
t.Errorf("Wrong settings returned (-want +got):\n%s", diff)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBasicAuth(t *testing.T) {
|
||||
config.Config.Authentication.Users = []config.AuthenticationUser{
|
||||
{Username: "john", Password: "foobar"},
|
||||
}
|
||||
r := ginTestEngine()
|
||||
for _, path := range []string{"/", "/alerts.json", "/autocomplete.json"} {
|
||||
req := httptest.NewRequest("GET", path, nil)
|
||||
resp := httptest.NewRecorder()
|
||||
r.ServeHTTP(resp, req)
|
||||
if resp.Code != 401 {
|
||||
t.Errorf("Expected 401 from %s, got %d", path, resp.Code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,22 @@ Example with environment variables:
|
||||
CONFIG_FILE="docs/example.yaml"
|
||||
```
|
||||
|
||||
### Authentication
|
||||
|
||||
`authentication` sections allows enabling authentication support in karma.
|
||||
When set users will be require to authenticate to access karma.
|
||||
Enabling authentication will also force silences to be created with usernames
|
||||
passed from credentials.
|
||||
|
||||
```YAML
|
||||
authentication:
|
||||
users:
|
||||
- username: string
|
||||
password: string
|
||||
```
|
||||
|
||||
- `authentication:users` - list of users (username & password) allowed to login.
|
||||
|
||||
### Alertmanagers
|
||||
|
||||
`alertmanager` section allows setting Alertmanager servers that should be
|
||||
|
||||
@@ -265,6 +265,12 @@ func (config *configSchema) Read(flags *pflag.FlagSet) string {
|
||||
config.SilenceForm.Strip.Labels = []string{}
|
||||
}
|
||||
|
||||
for _, u := range config.Authentication.Users {
|
||||
if u.Username == "" || u.Password == "" {
|
||||
log.Fatalf("authentication.users require both username and password to be set")
|
||||
}
|
||||
}
|
||||
|
||||
if config.SilenceForm.Author.PopulateFromHeader.ValueRegex != "" {
|
||||
_, err = regexp.Compile(config.SilenceForm.Author.PopulateFromHeader.ValueRegex)
|
||||
if err != nil {
|
||||
@@ -345,6 +351,16 @@ func (config *configSchema) LogValues() {
|
||||
// make a copy of our config so we can edit it
|
||||
cfg := configSchema(*config)
|
||||
|
||||
auth := []AuthenticationUser{}
|
||||
for _, u := range cfg.Authentication.Users {
|
||||
uu := AuthenticationUser{
|
||||
Username: u.Username,
|
||||
Password: "***",
|
||||
}
|
||||
auth = append(auth, uu)
|
||||
}
|
||||
cfg.Authentication.Users = auth
|
||||
|
||||
// replace passwords in Alertmanager URIs with 'xxx'
|
||||
servers := []AlertmanagerConfig{}
|
||||
for _, s := range cfg.Alertmanager.Servers {
|
||||
|
||||
@@ -20,7 +20,9 @@ func resetEnv() {
|
||||
}
|
||||
|
||||
func testReadConfig(t *testing.T) {
|
||||
expectedConfig := `alertmanager:
|
||||
expectedConfig := `authentication:
|
||||
users: []
|
||||
alertmanager:
|
||||
interval: 1s
|
||||
servers:
|
||||
- name: default
|
||||
|
||||
@@ -40,7 +40,15 @@ type CustomLabelColor struct {
|
||||
|
||||
type CustomLabelColors map[string][]CustomLabelColor
|
||||
|
||||
type AuthenticationUser struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
type configSchema struct {
|
||||
Authentication struct {
|
||||
Users []AuthenticationUser
|
||||
}
|
||||
Alertmanager struct {
|
||||
Interval time.Duration
|
||||
Servers []AlertmanagerConfig
|
||||
|
||||
@@ -296,19 +296,25 @@ type Settings struct {
|
||||
AlertAcknowledgement AlertAcknowledgementSettings `json:"alertAcknowledgement"`
|
||||
}
|
||||
|
||||
type AuthenticationInfo struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
// AlertsResponse is the structure of JSON response UI will use to get alert data
|
||||
type AlertsResponse struct {
|
||||
Status string `json:"status"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
Version string `json:"version"`
|
||||
Upstreams AlertmanagerAPISummary `json:"upstreams"`
|
||||
Silences map[string]map[string]Silence `json:"silences"`
|
||||
AlertGroups []APIAlertGroup `json:"groups"`
|
||||
TotalAlerts int `json:"totalAlerts"`
|
||||
Colors LabelsColorMap `json:"colors"`
|
||||
Filters []Filter `json:"filters"`
|
||||
Counters LabelNameStatsList `json:"counters"`
|
||||
Settings Settings `json:"settings"`
|
||||
Status string `json:"status"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
Version string `json:"version"`
|
||||
Upstreams AlertmanagerAPISummary `json:"upstreams"`
|
||||
Silences map[string]map[string]Silence `json:"silences"`
|
||||
AlertGroups []APIAlertGroup `json:"groups"`
|
||||
TotalAlerts int `json:"totalAlerts"`
|
||||
Colors LabelsColorMap `json:"colors"`
|
||||
Filters []Filter `json:"filters"`
|
||||
Counters LabelNameStatsList `json:"counters"`
|
||||
Settings Settings `json:"settings"`
|
||||
Authentication AuthenticationInfo `json:"authentication"`
|
||||
}
|
||||
|
||||
// Autocomplete is the structure of autocomplete object for filter hints
|
||||
|
||||
Reference in New Issue
Block a user